后端注释添加日期人员划分
This commit is contained in:
@@ -25,9 +25,26 @@ import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 数据字典翻译AOP切面
|
||||
* <p>
|
||||
* 拦截所有Controller方法的返回结果,自动将带有@Dict注解的字段从字典编码翻译为可读文本。
|
||||
* 翻译后的文本以"字段名_dictText"的形式追加到返回的JSON对象中。
|
||||
* 同时处理Date类型字段的格式化输出。
|
||||
* </p>
|
||||
* <p>
|
||||
* 支持的数据结构:
|
||||
* - 单个对象:直接遍历字段进行翻译
|
||||
* - 列表(List):遍历每个元素进行翻译
|
||||
* - 分页结果(IPage):遍历分页记录进行翻译
|
||||
* - 嵌套List字段:递归处理子列表中的字典翻译
|
||||
* </p>
|
||||
* <p>
|
||||
* 注意:当前@Component注解被注释,如需启用字典翻译功能需取消注释或通过其他方式注册为Spring Bean。
|
||||
* </p>
|
||||
*
|
||||
* @Description 描述:数据字典AOP类,处理数据字典值
|
||||
* @Author A贾宇婷034244310
|
||||
* @Date 20260615
|
||||
* @Author C薛涵艺034244315
|
||||
* @Date 20260616
|
||||
*/
|
||||
@Aspect
|
||||
//@Component
|
||||
@@ -35,14 +52,19 @@ import java.util.List;
|
||||
public class DictAspect {
|
||||
|
||||
|
||||
/** 字典服务,用于根据字典编码查询对应的可读文本 */
|
||||
@Autowired
|
||||
private SysDictService sysDictService;
|
||||
|
||||
/**
|
||||
* 切入Controller执行
|
||||
* @param pjp
|
||||
* @return
|
||||
* @throws Throwable
|
||||
* 环绕通知:切入所有Controller方法执行前后
|
||||
* <p>
|
||||
* 切点表达式匹配com.bc.exam包下所有Controller类的public方法,
|
||||
* 在方法执行完成后对返回结果进行字典翻译处理。
|
||||
* </p>
|
||||
* @param pjp 连接点对象,封装了目标方法的执行信息
|
||||
* @return 经过字典翻译处理后的返回结果
|
||||
* @throws Throwable 目标方法执行过程中抛出的异常
|
||||
*/
|
||||
@Around("execution(public * com.bc.exam..*.*Controller.*(..))")
|
||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||
@@ -50,25 +72,30 @@ public class DictAspect {
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行翻译并返回,调用前必须实现:BaseDictService
|
||||
*
|
||||
* @param pjp
|
||||
* @return
|
||||
* @throws Throwable
|
||||
* 执行目标方法并对返回结果进行字典翻译
|
||||
* <p>
|
||||
* 调用前必须确保SysDictService已正确配置并注入。
|
||||
* </p>
|
||||
* @param pjp 连接点对象
|
||||
* @return 翻译处理后的返回结果
|
||||
* @throws Throwable 目标方法执行异常
|
||||
*/
|
||||
public Object translate(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// 处理字典
|
||||
// 先执行目标方法获取返回结果,再对结果进行字典翻译处理
|
||||
return this.parseAllDictText(pjp.proceed());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换全部数据字典
|
||||
*
|
||||
* @param result
|
||||
* <p>
|
||||
* 仅对ApiRest类型的返回值进行字典翻译处理,其他类型直接透传。
|
||||
* </p>
|
||||
* @param result Controller方法的返回结果
|
||||
* @return 处理后的返回结果(ApiRest类型会被翻译,其他类型原样返回)
|
||||
*/
|
||||
private Object parseAllDictText(Object result) {
|
||||
|
||||
// 非ApiRest类型不处理
|
||||
// 仅ApiRest统一响应格式才进行字典翻译,非ApiRest类型直接返回不做处理
|
||||
if (result instanceof ApiRest) {
|
||||
parseFullDictText(result);
|
||||
}
|
||||
@@ -78,45 +105,53 @@ public class DictAspect {
|
||||
|
||||
|
||||
/**
|
||||
* 转换所有类型的数据字典、包含子列表
|
||||
*
|
||||
* @param result
|
||||
* 解析并翻译ApiRest响应体中的所有数据字典字段
|
||||
* <p>
|
||||
* 根据data字段的实际类型分三种情况处理:
|
||||
* 1. 分页对象(IPage):遍历分页记录逐条翻译
|
||||
* 2. 列表对象(List):遍历列表元素逐条翻译
|
||||
* 3. 单个对象:直接进行字段翻译
|
||||
* 基本数据类型和null值不做处理。
|
||||
* </p>
|
||||
* @param result ApiRest统一响应对象
|
||||
*/
|
||||
private void parseFullDictText(Object result) {
|
||||
|
||||
try {
|
||||
|
||||
// 从ApiRest中提取实际业务数据
|
||||
Object rest = ((ApiRest) result).getData();
|
||||
|
||||
// 不处理普通数据类型
|
||||
// null值或基本数据类型(String、Integer等)无需字典翻译,直接跳过
|
||||
if (rest == null || this.isBaseType(rest.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 分页的
|
||||
// 处理分页查询结果:遍历分页记录(records)逐条翻译字典字段
|
||||
if (rest instanceof IPage) {
|
||||
List<Object> items = new ArrayList<>(16);
|
||||
for (Object record : ((IPage) rest).getRecords()) {
|
||||
Object item = this.parseObject(record);
|
||||
items.add(item);
|
||||
}
|
||||
// 用翻译后的记录替换原始分页记录
|
||||
((IPage) rest).setRecords(items);
|
||||
return;
|
||||
}
|
||||
|
||||
// 数据列表的
|
||||
// 处理列表查询结果:遍历列表中每个元素进行字典翻译
|
||||
if (rest instanceof List) {
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (Object record : ((List) rest)) {
|
||||
Object item = this.parseObject(record);
|
||||
items.add(item);
|
||||
}
|
||||
// 重新回写值
|
||||
// 将翻译后的列表重新写回ApiRest的data字段
|
||||
((ApiRest) result).setData(items);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理单对象
|
||||
// 处理单个对象:直接对对象的字段进行字典翻译
|
||||
Object item = this.parseObject(((ApiRest) result).getData());
|
||||
((ApiRest) result).setData(item);
|
||||
|
||||
@@ -126,10 +161,17 @@ public class DictAspect {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据字典值
|
||||
*
|
||||
* @param record
|
||||
* @return
|
||||
* 处理单个对象的字典翻译和日期格式化
|
||||
* <p>
|
||||
* 处理流程:
|
||||
* 1. 将对象序列化为JSON再解析为JSONObject,便于动态添加翻译字段
|
||||
* 2. 遍历对象所有字段,按类型分别处理:
|
||||
* - List类型字段:递归调用processList处理嵌套列表
|
||||
* - 带@Dict注解的字段:查询字典服务翻译编码为可读文本,结果以"字段名_dictText"存储
|
||||
* - Date类型字段:按@JsonFormat注解指定的格式或默认格式(yyyy-MM-dd HH:mm:ss)格式化
|
||||
* </p>
|
||||
* @param record 待处理的业务对象
|
||||
* @return 处理后的JSONObject(包含字典翻译字段和格式化日期),null输入则返回null
|
||||
*/
|
||||
public Object parseObject(Object record) {
|
||||
|
||||
@@ -137,18 +179,19 @@ public class DictAspect {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 不处理普通数据类型
|
||||
// 基本数据类型(String、Number等)没有复杂字段,无需翻译处理
|
||||
if (this.isBaseType(record.getClass())) {
|
||||
return record;
|
||||
}
|
||||
|
||||
// 转换JSON字符
|
||||
// 将对象转为JSON格式处理,便于动态添加字典翻译字段(如xxx_dictText)
|
||||
String json = JSON.toJSONString(record);
|
||||
JSONObject item = JSONObject.parseObject(json);
|
||||
|
||||
// 遍历对象的所有字段(包括父类字段),逐个进行翻译或格式化处理
|
||||
for (Field field : Reflections.getAllFields(record)) {
|
||||
|
||||
// 如果是List类型
|
||||
// 分支1:List类型字段 - 递归处理嵌套列表中的字典翻译
|
||||
if (List.class.isAssignableFrom(field.getType())) {
|
||||
try {
|
||||
List list = this.processList(field, item.getObject(field.getName(), List.class));
|
||||
@@ -160,37 +203,39 @@ public class DictAspect {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理普通字段
|
||||
// 分支2:带@Dict注解的字段 - 从字典表查询编码对应的可读文本
|
||||
if (field.getAnnotation(Dict.class) != null) {
|
||||
String code = field.getAnnotation(Dict.class).dicCode();
|
||||
String text = field.getAnnotation(Dict.class).dicText();
|
||||
String table = field.getAnnotation(Dict.class).dictTable();
|
||||
String key = String.valueOf(item.get(field.getName()));
|
||||
String code = field.getAnnotation(Dict.class).dicCode(); // 字典编码字段名
|
||||
String text = field.getAnnotation(Dict.class).dicText(); // 字典文本字段名
|
||||
String table = field.getAnnotation(Dict.class).dictTable(); // 字典表名
|
||||
String key = String.valueOf(item.get(field.getName())); // 当前字段的字典编码值
|
||||
|
||||
//翻译字典值对应的txt
|
||||
// 调用字典服务将编码值翻译为可读文本
|
||||
String textValue = this.translateDictValue(code, text, table, key);
|
||||
if (StringUtils.isEmpty(textValue)) {
|
||||
textValue = "";
|
||||
}
|
||||
// 翻译结果以"字段名_dictText"的命名规则追加到JSON对象中
|
||||
item.put(field.getName() + "_dictText", textValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
//日期格式转换
|
||||
// 分支3:Date类型字段 - 按注解格式或默认格式进行日期格式化
|
||||
if (field.getType().getName().equals("java.util.Date") && item.get(field.getName()) != null) {
|
||||
|
||||
// 获取注解
|
||||
// 获取字段上的@JsonFormat注解以确定日期格式
|
||||
JsonFormat ann = field.getAnnotation(JsonFormat.class);
|
||||
// 格式化方式
|
||||
// 日期格式化器
|
||||
SimpleDateFormat fmt;
|
||||
|
||||
// 使用注解指定的
|
||||
// 优先使用@JsonFormat注解中指定的日期格式
|
||||
if (ann != null && !StringUtils.isEmpty(ann.pattern())) {
|
||||
fmt = new SimpleDateFormat(ann.pattern());
|
||||
} else {
|
||||
// 默认时间样式
|
||||
// 未指定格式时使用默认时间样式
|
||||
fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
// 将时间戳(Long)格式化为日期字符串
|
||||
item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName()))));
|
||||
continue;
|
||||
|
||||
@@ -201,49 +246,55 @@ public class DictAspect {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得类型为List的值
|
||||
*
|
||||
* @param field
|
||||
* @return
|
||||
* 处理List类型字段的字典翻译
|
||||
* <p>
|
||||
* 通过反射获取List的泛型参数确定元素实际类型,
|
||||
* 然后将每个列表元素反序列化为实际类型后递归调用parseObject进行字典翻译。
|
||||
* 基本数据类型的列表(如List<String>)无需处理直接返回。
|
||||
* </p>
|
||||
* @param field List类型的字段对象,用于获取泛型类型信息
|
||||
* @param list 字段对应的列表数据
|
||||
* @return 经过字典翻译处理的列表,若输入为空则返回空列表
|
||||
*/
|
||||
private List<Object> processList(Field field, List list) {
|
||||
|
||||
// 空判断
|
||||
// 空列表或null直接返回空集合,避免后续空指针
|
||||
if (list == null || list.size() == 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 获得List属性的真实类
|
||||
// 通过泛型反射获取List元素的实际数据类型(如List<User>中的User类)
|
||||
Type genericType = field.getGenericType();
|
||||
Class<?> actualType = null;
|
||||
if (genericType instanceof ParameterizedType) {
|
||||
// 尝试获取数据类型
|
||||
// 从参数化类型中提取第一个泛型参数作为实际类型
|
||||
ParameterizedType pt = (ParameterizedType) genericType;
|
||||
try {
|
||||
actualType = (Class) pt.getActualTypeArguments()[0];
|
||||
}catch (Exception e){
|
||||
// 泛型类型解析失败,返回原始列表不做处理
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
// 常规列表无需处理
|
||||
// 元素为基本数据类型(如String、Integer等)的列表无需字典翻译
|
||||
if (isBaseType(actualType)) {
|
||||
return list;
|
||||
}
|
||||
|
||||
// 返回列表
|
||||
// 对复杂对象列表逐条进行字典翻译处理
|
||||
List<Object> result = new ArrayList<>(16);
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
// 创建实例-->赋值-->字典处理
|
||||
// 将每个元素先转为JSON再反序列化为实际类型,确保类型准确
|
||||
Object data = list.get(i);
|
||||
try {
|
||||
data = JSON.parseObject(JSON.toJSONString(data), actualType);
|
||||
}catch (Exception e){
|
||||
// 转换出错不处理
|
||||
// 反序列化失败时使用原始数据继续处理
|
||||
}
|
||||
|
||||
// 处理后的数据
|
||||
// 递归调用parseObject对列表中每个对象进行字典翻译
|
||||
Object pds = this.parseObject(data);
|
||||
result.add(pds);
|
||||
}
|
||||
@@ -252,22 +303,27 @@ public class DictAspect {
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译实现
|
||||
*
|
||||
* @param code
|
||||
* @param text
|
||||
* @param table
|
||||
* @param key
|
||||
* @return
|
||||
* 字典翻译实现:根据字典表、编码字段、文本字段和key值查询对应的可读文本
|
||||
* <p>
|
||||
* 通过SysDictService查询指定字典表中,匹配code字段值为key的记录,
|
||||
* 返回其text字段的值作为翻译结果。
|
||||
* </p>
|
||||
* @param code 字典编码字段名(如"code")
|
||||
* @param text 字典文本字段名(如"name"),即需要返回的翻译结果字段
|
||||
* @param table 字典表名(如"sys_dict")
|
||||
* @param key 当前字段的字典编码值,用于匹配查询
|
||||
* @return 字典翻译后的可读文本,查询失败或未找到时返回空字符串
|
||||
*/
|
||||
private String translateDictValue(String code, String text, String table, String key) {
|
||||
// key为空时无需查询,直接返回null
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 翻译值
|
||||
// 调用字典服务查询翻译文本
|
||||
String dictText = null;
|
||||
if (!StringUtils.isEmpty(table)) {
|
||||
// 从指定字典表中,根据code字段匹配key值,返回text字段的翻译结果
|
||||
dictText = sysDictService.findDict(table, text, code, key.trim());
|
||||
}
|
||||
|
||||
@@ -275,16 +331,20 @@ public class DictAspect {
|
||||
return dictText;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 字典查询异常时不影响主流程,打印堆栈后返回空字符串
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否基本类型
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
* 判断给定类型是否为基本类型(无需字典翻译的简单类型)
|
||||
* <p>
|
||||
* 基本类型包括:8种包装类型(Integer/Byte/Long/Double/Float/Character/Short/Boolean)、
|
||||
* String类型以及Number类型。这些类型不包含@Dict注解字段,无需进行字典翻译处理。
|
||||
* </p>
|
||||
* @param clazz 待判断的类
|
||||
* @return true表示是基本类型无需翻译,false表示是复杂对象需要翻译
|
||||
*/
|
||||
private boolean isBaseType(Class clazz) {
|
||||
|
||||
|
||||
@@ -26,61 +26,95 @@ import java.util.Properties;
|
||||
|
||||
|
||||
/**
|
||||
* @Description 描述:查询拦截器,用于拦截处理通用的信息、如用户ID、多租户信息等;
|
||||
* 特别注意:此处继承了PaginationInterceptor分页,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题
|
||||
* MyBatis查询拦截器
|
||||
* <p>
|
||||
* 继承自MyBatis-Plus的PaginationInterceptor,在提供分页能力的同时扩展了自定义查询条件注入功能。
|
||||
* 核心作用:拦截所有SELECT查询,将SQL中的{{userId}}占位符自动替换为当前登录用户的ID,
|
||||
* 实现数据权限的自动过滤,无需在业务代码中手动拼接用户ID条件。
|
||||
* </p>
|
||||
* <p>
|
||||
* 工作原理:
|
||||
* 1. 通过@Intercepts注解拦截StatementHandler的prepare方法(SQL准备阶段)
|
||||
* 2. 获取原始SQL,检查是否包含{{userId}}占位符
|
||||
* 3. 若包含占位符,通过Shiro获取当前登录用户ID并进行替换
|
||||
* 4. 将替换后的SQL回写到BoundSql中,再交由父类PaginationInterceptor处理分页逻辑
|
||||
* </p>
|
||||
* @see PaginationInterceptor MyBatis-Plus分页拦截器(父类)
|
||||
*
|
||||
* @Author A贾宇婷034244310
|
||||
* @Date 20260615
|
||||
* @Description 描述:查询拦截器,用于拦截处理通用的信息、如用户ID、多租户信息等;
|
||||
* @Author B吉柯梦034244314
|
||||
* @Date 20260616
|
||||
*/
|
||||
@Log4j2
|
||||
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),})
|
||||
public class QueryInterceptor extends PaginationInterceptor implements Interceptor {
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
/** SQL中用户ID占位符标识,在Mapper XML的SQL语句中使用此占位符实现自动用户过滤 */
|
||||
private static final String USER_FILTER = "{{userId}}";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 拦截MyBatis的SQL准备阶段,对SELECT查询自动注入用户ID过滤条件
|
||||
* <p>
|
||||
* 处理流程:
|
||||
* 1. 通过MetaObject反射获取MappedStatement和BoundSql
|
||||
* 2. 仅对SELECT类型的SQL进行处理
|
||||
* 3. 检查SQL中是否包含{{userId}}占位符,不包含则跳过
|
||||
* 4. 替换占位符后回写SQL,再交由父类处理分页
|
||||
* </p>
|
||||
* @param invocation MyBatis拦截器调用对象,包含目标方法及其参数
|
||||
* @return SQL执行结果
|
||||
* @throws Throwable 拦截过程中可能抛出的异常
|
||||
*/
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
|
||||
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
|
||||
|
||||
//通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性
|
||||
// 通过MetaObject反射机制访问StatementHandler内部属性
|
||||
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
|
||||
//先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
|
||||
// 获取delegate(RoutingStatementHandler)中的MappedStatement,包含SQL元信息
|
||||
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
||||
|
||||
//sql语句类型
|
||||
// 获取SQL语句类型(SELECT/INSERT/UPDATE/DELETE)
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
|
||||
// 只过滤查询的
|
||||
// 仅对SELECT查询语句进行用户ID占位符替换处理
|
||||
if (SqlCommandType.SELECT == sqlCommandType) {
|
||||
// 获得原始SQL
|
||||
// 获取原始SQL语句
|
||||
String sql = statementHandler.getBoundSql().getSql();
|
||||
|
||||
// 不处理
|
||||
// 如果SQL中不包含{{userId}}占位符,则无需处理,直接交给父类(分页拦截器)处理
|
||||
if(!sql.contains(USER_FILTER)){
|
||||
return super.intercept(invocation);
|
||||
}
|
||||
// 处理SQL语句
|
||||
// 解析SQL并将{{userId}}占位符替换为当前登录用户的实际ID
|
||||
String outSql = this.parseSql(sql);
|
||||
// 设置SQL
|
||||
// 将替换后的SQL回写到BoundSql中
|
||||
metaObject.setValue("delegate.boundSql.sql", outSql);
|
||||
// 再分页
|
||||
// 调用父类PaginationInterceptor的intercept方法处理分页逻辑
|
||||
return super.intercept(invocation);
|
||||
}
|
||||
|
||||
// 非SELECT语句(INSERT/UPDATE/DELETE)直接放行,不做处理
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建拦截器代理对象
|
||||
* @param target 被拦截的目标对象(StatementHandler)
|
||||
* @return 包装后的代理对象
|
||||
*/
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拦截器属性(当前未使用)
|
||||
* @param properties 拦截器配置属性
|
||||
*/
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
|
||||
@@ -89,57 +123,67 @@ public class QueryInterceptor extends PaginationInterceptor implements Intercept
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前登录用户
|
||||
* @return
|
||||
* 从Shiro安全框架中获取当前登录用户信息
|
||||
* @return 当前登录用户的DTO对象,未登录或获取失败时返回null
|
||||
*/
|
||||
private SysUserLoginDTO getLoginUser() {
|
||||
|
||||
try {
|
||||
// 通过Shiro的Subject获取当前登录用户的Principal(用户信息)
|
||||
return SecurityUtils.getSubject().getPrincipal() != null ? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null;
|
||||
} catch (Exception e) {
|
||||
// 获取用户信息异常(如未登录状态)时返回null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换用户ID
|
||||
* @param sql
|
||||
* @return
|
||||
* 将SQL中的{{userId}}占位符替换为当前登录用户的实际ID
|
||||
* @param sql 包含{{userId}}占位符的原始SQL
|
||||
* @return 替换用户ID后的SQL,用户ID为空时返回null
|
||||
*/
|
||||
private String processUserId(String sql) {
|
||||
|
||||
// 当前用户
|
||||
// 获取当前登录用户并提取用户ID
|
||||
SysUserLoginDTO user = this.getLoginUser();
|
||||
String userId = user.getId();
|
||||
if(StringUtils.isNotBlank(userId)){
|
||||
// 将SQL中所有{{userId}}占位符替换为实际的用户ID
|
||||
return sql.replace(USER_FILTER, userId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理注入用户信息
|
||||
* @param src
|
||||
* @return
|
||||
* 解析SQL并注入用户过滤信息
|
||||
* <p>
|
||||
* 使用JSqlParser解析SQL语句,在处理完用户ID替换后重新生成SQL字符串。
|
||||
* 解析失败时返回原始SQL,确保不影响正常查询执行。
|
||||
* </p>
|
||||
* @param src 待处理的原始SQL语句
|
||||
* @return 注入用户信息后的SQL语句,解析异常时返回原始SQL
|
||||
*/
|
||||
private String parseSql(String src) {
|
||||
|
||||
// 使用JSqlParser库解析SQL语句
|
||||
CCJSqlParserManager parserManager = new CCJSqlParserManager();
|
||||
try {
|
||||
|
||||
// 将SQL字符串解析为AST(抽象语法树)
|
||||
Select select = (Select) parserManager.parse(new StringReader(src));
|
||||
PlainSelect selectBody = (PlainSelect) select.getSelectBody();
|
||||
|
||||
// 过滤客户
|
||||
// 将AST转回SQL字符串(JSqlParser可能会规范化SQL格式)
|
||||
String sql = selectBody.toString();
|
||||
|
||||
// 过滤用户ID
|
||||
// 将{{userId}}占位符替换为当前登录用户ID
|
||||
sql = this.processUserId(sql);
|
||||
|
||||
// 获得SQL
|
||||
// 返回处理完成的SQL
|
||||
return sql;
|
||||
|
||||
} catch (Exception e) {
|
||||
// SQL解析失败时打印异常,返回原始SQL保证查询不中断
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,25 @@ import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* MyBatis数据更新拦截器 - 自动填充时间字段
|
||||
* <p>
|
||||
* 拦截MyBatis的所有INSERT和UPDATE操作,自动为实体对象中的时间字段赋值:
|
||||
* - INSERT操作:同时自动填充createTime(创建时间)和updateTime(更新时间)
|
||||
* - UPDATE操作:仅自动填充updateTime(更新时间)
|
||||
* </p>
|
||||
* <p>
|
||||
* 该拦截器通过反射机制扫描实体对象的所有字段(包括父类字段),
|
||||
* 当字段名匹配"createTime"或"updateTime"时自动赋值当前时间戳,
|
||||
* 避免在业务代码中手动设置时间字段,减少重复代码。
|
||||
* </p>
|
||||
* <p>
|
||||
* 使用方式:实体类中声明createTime和updateTime字段即可自动生效,
|
||||
* 字段类型需为java.sql.Timestamp或兼容类型。
|
||||
* </p>
|
||||
*
|
||||
* @Description 描述:自动给创建时间和更新时间加值
|
||||
* @Author A贾宇婷034244310
|
||||
* @Date 20260615
|
||||
* @Author B吉柯梦034244314
|
||||
* @Date 20260616
|
||||
*/
|
||||
@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
|
||||
public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor {
|
||||
@@ -33,15 +49,29 @@ public class UpdateInterceptor extends AbstractSqlParserHandler implements Inter
|
||||
*/
|
||||
private static final String UPDATE_TIME = "updateTime";
|
||||
|
||||
/**
|
||||
* 拦截MyBatis的Executor.update方法,自动填充实体对象的时间字段
|
||||
* <p>
|
||||
* 处理逻辑:
|
||||
* 1. 获取SQL操作类型(INSERT或UPDATE)和操作的实体对象
|
||||
* 2. 通过反射获取实体所有字段(包括父类继承的字段)
|
||||
* 3. 遍历字段,匹配createTime和updateTime并按规则赋值当前时间戳
|
||||
* </p>
|
||||
* @param invocation MyBatis拦截器调用对象
|
||||
* @return 继续执行后续拦截器或实际SQL操作的结果
|
||||
* @throws Throwable 反射操作或SQL执行过程中可能抛出的异常
|
||||
*/
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
// 获取MappedStatement,包含SQL操作类型等元信息
|
||||
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
|
||||
// SQL操作命令
|
||||
// 获取SQL操作类型(INSERT/UPDATE/DELETE)
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
// 获取新增或修改的对象参数
|
||||
// 获取当前操作的实体对象参数
|
||||
Object parameter = invocation.getArgs()[1];
|
||||
// 获取对象中所有的私有成员变量(对应表字段)
|
||||
// 通过反射获取实体类的所有声明字段
|
||||
Field[] declaredFields = parameter.getClass().getDeclaredFields();
|
||||
// 同时获取父类的字段,确保继承的createTime/updateTime也能被处理
|
||||
if (parameter.getClass().getSuperclass() != null) {
|
||||
Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields();
|
||||
declaredFields = ArrayUtils.addAll(declaredFields, superField);
|
||||
@@ -50,23 +80,36 @@ public class UpdateInterceptor extends AbstractSqlParserHandler implements Inter
|
||||
String fieldName = null;
|
||||
for (Field field : declaredFields) {
|
||||
fieldName = field.getName();
|
||||
// 处理createTime字段:仅在INSERT操作时自动填充创建时间
|
||||
if (Objects.equals(CREATE_TIME, fieldName)) {
|
||||
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
|
||||
field.setAccessible(true);
|
||||
// 设置创建时间为当前时间戳
|
||||
field.set(parameter, new Timestamp(System.currentTimeMillis()));
|
||||
}
|
||||
}
|
||||
// 处理updateTime字段:INSERT和UPDATE操作时都自动填充更新时间
|
||||
if (Objects.equals(UPDATE_TIME, fieldName)) {
|
||||
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
|
||||
field.setAccessible(true);
|
||||
// 设置更新时间为当前时间戳
|
||||
field.set(parameter, new Timestamp(System.currentTimeMillis()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// 时间字段填充完毕,继续执行后续拦截器链或实际的SQL操作
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建拦截器代理对象
|
||||
* <p>
|
||||
* 仅对Executor类型的目标对象进行代理包装,其他类型直接返回原对象。
|
||||
* </p>
|
||||
* @param target 被拦截的目标对象
|
||||
* @return Executor类型返回代理对象,其他类型返回原对象
|
||||
*/
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
if (target instanceof Executor) {
|
||||
@@ -75,6 +118,10 @@ public class UpdateInterceptor extends AbstractSqlParserHandler implements Inter
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拦截器属性(当前未使用)
|
||||
* @param properties 拦截器配置属性
|
||||
*/
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @Description 描述:注入工具类 写出统一错误信息
|
||||
* @Author A贾宇婷034244310
|
||||
* @Date 20260615
|
||||
* @Author C薛涵艺034244315
|
||||
* @Date 20260616
|
||||
*/
|
||||
@Log4j2
|
||||
@Component
|
||||
|
||||
Reference in New Issue
Block a user