基于Angular前端与Spring Boot+Hibernate后端的服务器端用户活动追踪及数据库存储实现示例咨询
嘿,我刚好在Spring Boot+Hibernate的项目里实现过类似的用户活动追踪功能,给你几个实用的实现方案和代码示例思路,完全适配你的技术栈:
1. 用Hibernate拦截器监听数据库变更日志
既然你用了Hibernate,它的拦截器可以直接捕获实体的增、删、改操作,非常适合记录数据库层面的变更细节。
首先定义一个自定义拦截器,继承EmptyInterceptor:
public class AuditInterceptor extends EmptyInterceptor { @Autowired private ActivityLogRepository logRepository; @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { if (entity instanceof BaseEntity) { // 假设你的实体都继承BaseEntity,包含创建人等基础信息 saveActivityLog(id.toString(), "CREATE", entity.getClass().getSimpleName(), getCurrentUserId()); } return super.onSave(entity, id, state, propertyNames, types); } @Override public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { if (entity instanceof BaseEntity) { saveActivityLog(id.toString(), "DELETE", entity.getClass().getSimpleName(), getCurrentUserId()); } } @Override public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { if (entity instanceof BaseEntity) { // 对比前后状态,生成具体变更内容 String changes = compareChanges(currentState, previousState, propertyNames); saveActivityLog(id.toString(), "UPDATE", entity.getClass().getSimpleName(), getCurrentUserId(), changes); } return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types); } private void saveActivityLog(String entityId, String operationType, String entityType, String userId, String... changes) { ActivityLog log = new ActivityLog(); log.setEntityId(entityId); log.setOperationType(operationType); log.setEntityType(entityType); log.setUserId(userId); log.setChangeDetails(changes.length > 0 ? changes[0] : null); log.setOperationTime(LocalDateTime.now()); // 用异步线程保存,避免影响主业务流程 CompletableFuture.runAsync(() -> logRepository.save(log)); } // 辅助方法:对比实体字段前后变化 private String compareChanges(Object[] currentState, Object[] previousState, String[] propertyNames) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < propertyNames.length; i++) { if (!Objects.equals(currentState[i], previousState[i])) { sb.append(propertyNames[i]).append(": ").append(previousState[i]).append(" -> ").append(currentState[i]).append("; "); } } return sb.length() > 0 ? sb.toString() : null; } // 辅助方法:获取当前登录用户ID(结合Spring Security) private String getCurrentUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication != null ? authentication.getName() : "anonymous"; } }
然后在Spring配置里注册这个拦截器:
@Configuration public class HibernateConfig { @Autowired private AuditInterceptor auditInterceptor; @Bean public LocalSessionFactoryBean sessionFactory(DataSource dataSource) { LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setPackagesToScan("com.yourpackage.entity"); sessionFactory.setHibernateProperties(hibernateProperties()); sessionFactory.setEntityInterceptor(auditInterceptor); // 绑定拦截器 return sessionFactory; } private Properties hibernateProperties() { Properties properties = new Properties(); properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); // 其他Hibernate配置项... return properties; } }
2. Spring AOP实现通用操作日志(含登录登出)
对于登录、登出这类非数据库直接操作的行为,或者业务层的自定义操作,用Spring AOP来做更灵活。
首先定义一个自定义注解,标记需要记录日志的方法:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable { String operationType(); String description() default ""; }
然后写一个AOP切面:
@Aspect @Component public class ActivityLogAspect { @Autowired private ActivityLogRepository logRepository; @Around("@annotation(loggable)") public Object logActivity(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable { // 提取日志基础信息 String userId = getCurrentUserId(); String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); Object result = joinPoint.proceed(); // 执行原业务方法 // 异步保存日志 CompletableFuture.runAsync(() -> { ActivityLog log = new ActivityLog(); log.setOperationType(loggable.operationType()); log.setDescription(loggable.description()); log.setUserId(userId); log.setOperationTime(LocalDateTime.now()); log.setSourceMethod(className + "." + methodName); logRepository.save(log); }); return result; } private String getCurrentUserId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication != null ? authentication.getName() : "anonymous"; } }
接下来在登录登出的方法上加上注解:
@Service public class AuthService { @Loggable(operationType = "LOGIN", description = "用户登录系统") public Authentication login(String username, String password) { // 你的登录逻辑... } @Loggable(operationType = "LOGOUT", description = "用户登出系统") public void logout() { // 你的登出逻辑... } }
3. 结合Spring Security的登录登出日志
如果你的登录登出是基于Spring Security实现的,还可以直接通过自定义成功处理器来记录:
@Component public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private ActivityLogRepository logRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 记录登录日志,包含用户IP ActivityLog log = new ActivityLog(); log.setOperationType("LOGIN"); log.setUserId(authentication.getName()); log.setOperationTime(LocalDateTime.now()); log.setDescription("登录IP:" + request.getRemoteAddr()); CompletableFuture.runAsync(() -> logRepository.save(log)); // 执行原登录成功逻辑,比如跳转首页 response.sendRedirect("/dashboard"); } } @Component public class CustomLogoutSuccessHandler implements LogoutSuccessHandler { @Autowired private ActivityLogRepository logRepository; @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { if (authentication != null) { ActivityLog log = new ActivityLog(); log.setOperationType("LOGOUT"); log.setUserId(authentication.getName()); log.setOperationTime(LocalDateTime.now()); CompletableFuture.runAsync(() -> logRepository.save(log)); } // 执行原登出成功逻辑,比如跳转登录页 response.sendRedirect("/login"); } }
然后在Spring Security配置里指定这两个处理器:
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private CustomAuthenticationSuccessHandler authSuccessHandler; @Autowired private CustomLogoutSuccessHandler logoutSuccessHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/login").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .successHandler(authSuccessHandler) // 绑定自定义登录成功处理器 ) .logout(logout -> logout .logoutSuccessHandler(logoutSuccessHandler) // 绑定自定义登出成功处理器 ); return http.build(); } }
最后补充几个最佳实践
- 异步保存日志:用
CompletableFuture或者Spring的@Async注解,避免日志操作拖慢主业务流程 - 日志实体设计:建议包含
userId、operationType(枚举:LOGIN/LOGOUT/CREATE/UPDATE/DELETE)、entityType、entityId、changeDetails、operationTime、sourceIp这些核心字段 - 敏感信息脱敏:如果变更内容里有密码、手机号等敏感信息,一定要在保存前做脱敏处理
- 日志查询优化:如果日志量很大,可以考虑分表或者用Elasticsearch存储,方便后续查询分析
内容的提问来源于stack exchange,提问作者Kerk
相关产品推荐
相关产品推荐

