223 lines
9.0 KiB
Markdown
223 lines
9.0 KiB
Markdown
# MyBatis-Plus 自动填充处理器优化指南
|
||
|
||
## 概述
|
||
本文档说明如何优化 `MybastisColumnsHandler` 以确保所有实体的审计字段(create_by、create_time、update_by、update_time)能够正确自动填充。
|
||
|
||
## 问题背景
|
||
在 OpenHIS 系统中,当保存实体时可能会遇到以下错误:
|
||
```
|
||
org.postgresql.util.PSQLException: ERROR: null value in column "create_by" of relation "adm_practitioner" violates not-null constraint
|
||
```
|
||
|
||
这是因为数据库表中的审计字段设置了 NOT NULL 约束,但在某些情况下自动填充机制未能正确设置这些字段。
|
||
|
||
## 解决方案
|
||
通过优化 `MybastisColumnsHandler` 来确保总是使用当前登录用户的用户名填充 `create_by` 字段,使用当前时间填充 `create_time` 字段。
|
||
|
||
## 实施步骤
|
||
|
||
### 1. 替换现有处理器
|
||
将 `D:\his\openhis-server-new\core-framework\src\main\java\com\core\framework\handler\MybastisColumnsHandler.java` 文件替换为以下内容:
|
||
|
||
```java
|
||
package com.core.framework.handler;
|
||
|
||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||
import com.core.common.core.domain.model.LoginUser;
|
||
import com.core.common.utils.SecurityUtils;
|
||
import com.core.framework.config.TenantContext;
|
||
import org.apache.ibatis.reflection.MetaObject;
|
||
import org.springframework.stereotype.Component;
|
||
import org.springframework.web.context.request.RequestContextHolder;
|
||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||
|
||
import javax.servlet.http.HttpServletRequest;
|
||
import java.util.Date;
|
||
|
||
/**
|
||
* MyBatis-Plus 自动填充处理器
|
||
* 用于自动填充创建时间和更新时间,以及创建人和更新人
|
||
*/
|
||
@Component
|
||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
||
|
||
// 设置数据新增时的字段自动赋值规则
|
||
@Override
|
||
public void insertFill(MetaObject metaObject) {
|
||
// 填充创建时间
|
||
Date currentTime = new Date();
|
||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
||
|
||
// 获取当前登录用户名
|
||
String username = getCurrentUsername();
|
||
|
||
// 填充创建人
|
||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
||
|
||
// 确保tenantId被设置
|
||
Integer tenantId = getCurrentTenantId();
|
||
if (tenantId == null) {
|
||
throw new RuntimeException("无法获取当前租户ID,请确保用户已登录或正确设置租户上下文");
|
||
}
|
||
this.strictInsertFill(metaObject, "tenantId", Integer.class, tenantId);
|
||
this.strictInsertFill(metaObject, "tenant_id", Integer.class, tenantId);
|
||
}
|
||
|
||
// 设置数据修改时的字段自动赋值规则
|
||
@Override
|
||
public void updateFill(MetaObject metaObject) {
|
||
// 填充更新时间
|
||
Date currentTime = new Date();
|
||
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentTime);
|
||
this.strictUpdateFill(metaObject, "update_time", Date.class, currentTime);
|
||
|
||
// 填充更新人
|
||
String username = getCurrentUsername();
|
||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
||
}
|
||
|
||
/**
|
||
* 获取当前登录用户名
|
||
* @return 当前登录用户名,如果无法获取则返回 "system"
|
||
*/
|
||
private String getCurrentUsername() {
|
||
String username = "system"; // 默认值
|
||
|
||
try {
|
||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||
if (loginUser != null) {
|
||
username = loginUser.getUsername();
|
||
} else {
|
||
// 尝试从请求中获取用户信息
|
||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||
if (attributes != null) {
|
||
HttpServletRequest request = attributes.getRequest();
|
||
// 可以在这里添加额外的逻辑来从请求中获取用户信息
|
||
// 例如从请求头、session等获取用户信息
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
// 记录异常但不中断处理流程
|
||
System.err.println("获取当前登录用户时发生异常: " + e.getMessage());
|
||
// 可以考虑记录日志
|
||
}
|
||
|
||
return username;
|
||
}
|
||
|
||
/**
|
||
* 获取当前租户 ID
|
||
*/
|
||
private Integer getCurrentTenantId() {
|
||
Integer result = null;
|
||
|
||
// 首先尝试从线程局部变量中获取租户ID(适用于定时任务等场景)
|
||
Integer threadLocalTenantId = TenantContext.getCurrentTenant();
|
||
if (threadLocalTenantId != null) {
|
||
result = threadLocalTenantId;
|
||
} else {
|
||
// 获取当前登录用户的租户ID(优先使用SecurityUtils中储存的LoginUser的租户ID)
|
||
try {
|
||
if (SecurityUtils.getAuthentication() != null) {
|
||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||
if (loginUser != null) {
|
||
result = loginUser.getTenantId();
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
// 记录异常但不中断处理
|
||
System.err.println("获取当前登录用户租户ID时发生异常: " + e.getMessage());
|
||
}
|
||
|
||
if (result == null) {
|
||
// 尝试从请求头中获取租户ID
|
||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||
if (attributes != null) {
|
||
HttpServletRequest request = attributes.getRequest();
|
||
if (request != null) {
|
||
// 从请求头获取租户ID,假设header名称为"X-Tenant-ID" ; 登录接口前端把租户id放到请求头里
|
||
String tenantIdHeader = request.getHeader("X-Tenant-ID");
|
||
String requestMethodName = request.getHeader("Request-Method-Name");
|
||
// 登录
|
||
if ("login".equals(requestMethodName)) {
|
||
if (tenantIdHeader != null && !tenantIdHeader.isEmpty()) {
|
||
try {
|
||
result = Integer.parseInt(tenantIdHeader);
|
||
} catch (NumberFormatException e) {
|
||
System.err.println("解析请求头中的租户ID时发生异常: " + e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果仍然没有获取到租户ID,返回默认值
|
||
if (result == null) {
|
||
System.out.println("警告: 未能获取当前租户ID,将使用默认租户ID 1");
|
||
result = 1; // 默认租户ID
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 验证处理器是否被正确扫描
|
||
确保在主应用类或配置类中启用了自动填充功能:
|
||
|
||
```java
|
||
@SpringBootApplication
|
||
@MapperScan("com.openhis.*.mapper") // 确保扫描到你的mapper
|
||
@EnableTransactionManagement // 启用事务管理
|
||
public class OpenHisApplication {
|
||
public static void main(String[] args) {
|
||
SpringApplication.run(OpenHisApplication.class, args);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 测试验证
|
||
创建一个简单的测试来验证自动填充是否正常工作:
|
||
|
||
```java
|
||
@SpringBootTest
|
||
public class AuditFieldTest {
|
||
@Autowired
|
||
private PractitionerMapper practitionerMapper;
|
||
|
||
@Test
|
||
public void testAuditFieldsAutoFill() {
|
||
Practitioner practitioner = new Practitioner();
|
||
practitioner.setName("Test Practitioner");
|
||
|
||
// 保存实体
|
||
practitionerMapper.insert(practitioner);
|
||
|
||
// 验证审计字段是否被正确填充
|
||
assertThat(practitioner.getCreateBy()).isNotNull();
|
||
assertThat(practitioner.getCreateBy()).isNotEqualTo("");
|
||
assertThat(practitioner.getCreateTime()).isNotNull();
|
||
|
||
// 清理测试数据
|
||
practitionerMapper.deleteById(practitioner.getId());
|
||
}
|
||
}
|
||
```
|
||
|
||
## 注意事项
|
||
|
||
1. **安全上下文**:确保在调用保存方法时用户已登录,这样 `SecurityUtils.getLoginUser()` 才能返回有效的用户对象。
|
||
|
||
2. **异常处理**:处理器中包含了异常处理,如果无法获取当前用户,将使用 "system" 作为默认值。
|
||
|
||
3. **租户ID**:处理器也处理租户ID的自动填充,这对于多租户系统很重要。
|
||
|
||
4. **兼容性**:处理器同时支持驼峰命名(createBy)和下划线命名(create_by)的字段,以兼容不同的配置。
|
||
|
||
## 总结
|
||
通过优化 `MybastisColumnsHandler`,我们可以确保所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误,同时保持数据完整性和审计跟踪功能。 |