如何在Spring Data JPA审计中用外键替代用户名
解决方案
1. 调整审计模型字段类型
首先修改AuditModel.kt中createdBy和lastModifiedBy的类型,从原来的String(存储username)改为用户主键类型(比如Long,对应EmployeeModel的外键):
import jakarta.persistence.* import org.springframework.data.annotation.CreatedBy import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedBy import org.springframework.data.annotation.LastModifiedDate import java.time.LocalDateTime abstract class AuditModel { @CreatedBy var createdBy: Long? = null // 改为用户ID类型(如Long) @CreatedDate @Temporal(TemporalType.TIMESTAMP) var createdDate: LocalDateTime? = null @LastModifiedBy var lastModifiedBy: Long? = null // 同步修改类型 @LastModifiedDate @Temporal(TemporalType.TIMESTAMP) var lastModifiedDate: LocalDateTime? = null }
如果需要直接关联EmployeeModel实体而非仅存ID,可改为关联映射:
abstract class AuditModel { @CreatedBy @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "created_by") var createdBy: EmployeeModel? = null @LastModifiedBy @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "last_modified_by") var lastModifiedBy: EmployeeModel? = null // 日期字段保持不变 }
2. 自定义UserDetails携带用户ID
确保登录后的认证信息包含用户主键,需要自定义UserDetails实现类,在登录时将用户ID/实体注入:
import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.userdetails.UserDetails class EmployeeUserDetails(val employee: EmployeeModel) : UserDetails { override fun getAuthorities(): MutableCollection<out GrantedAuthority> { // 根据实际需求返回用户权限集合 return mutableListOf() } override fun getPassword(): String = employee.password override fun getUsername(): String = employee.username override fun isAccountNonExpired(): Boolean = true override fun isAccountNonLocked(): Boolean = true override fun isCredentialsNonExpired(): Boolean = true override fun isEnabled(): Boolean = true // 提供获取用户ID的方法 fun getEmployeeId(): Long = employee.id }
3. 修改AuditorAwareImpl获取用户ID
调整AuditorAwareImpl.kt的泛型为用户ID类型(或实体类型),从SecurityContextHolder中提取当前登录用户的主键:
import org.springframework.data.domain.AuditorAware import org.springframework.security.core.context.SecurityContextHolder import java.util.Optional @Component class AuditorAwareImpl : AuditorAware<Long> { // 泛型匹配审计字段类型(Long或EmployeeModel) override fun getCurrentAuditor(): Optional<Long> { val authentication = SecurityContextHolder.getContext().authentication // 过滤未认证或默认匿名用户的情况 if (authentication == null || !authentication.isAuthenticated || authentication.principal == "anonymousUser") { return Optional.empty() } // 从自定义UserDetails中提取用户ID val userDetails = authentication.principal as EmployeeUserDetails return Optional.of(userDetails.getEmployeeId()) } }
如果是关联实体的情况,泛型改为EmployeeModel,返回实体即可:
class AuditorAwareImpl : AuditorAware<EmployeeModel> { override fun getCurrentAuditor(): Optional<EmployeeModel> { // 认证判断逻辑同上 val userDetails = authentication.principal as EmployeeUserDetails return Optional.of(userDetails.employee) } }
4. 确认JPA审计配置
确保配置类中启用JPA审计,并指定auditorAware的Bean引用:
import org.springframework.context.annotation.Configuration import org.springframework.data.jpa.repository.config.EnableJpaAuditing @Configuration @EnableJpaAuditing(auditorAwareRef = "auditorAwareImpl") class JpaConfig
常见问题排查
- 若之前获取失败,大概率是
SecurityContext中的principal不是自定义的EmployeeUserDetails(比如用了默认内存用户),需确保登录逻辑中返回自定义的UserDetails实例。 - 若字段类型不匹配会导致JPA存储失败,需严格保证
AuditorAware的泛型与AuditModel中对应字段类型一致。
内容的提问来源于stack exchange,提问作者Pedro Barros
相关产品推荐
相关产品推荐

