Commit 7f72c75b43d7b4fa9838d9a93974fefc1933cf9b

Authored by 王富生
1 parent 04ee424b

提交处理sql自动拼接业务线逻辑

urbanops-framework/urbanops-spring-boot-starter-mybatis/src/main/java/com/zteits/urbanops/framework/mybatis/config/BusinessLineSqlInterceptor.java 0 → 100644
  1 +package com.zteits.urbanops.framework.mybatis.config;
  2 +
  3 +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
  4 +import com.zteits.urbanops.framework.annotation.ProcessBusinessLine;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.apache.ibatis.cache.CacheKey;
  7 +import org.apache.ibatis.executor.Executor;
  8 +import org.apache.ibatis.mapping.BoundSql;
  9 +import org.apache.ibatis.mapping.MappedStatement;
  10 +import org.apache.ibatis.mapping.SqlCommandType;
  11 +import org.apache.ibatis.plugin.*;
  12 +import org.apache.ibatis.reflection.MetaObject;
  13 +import org.apache.ibatis.reflection.SystemMetaObject;
  14 +import org.apache.ibatis.session.ResultHandler;
  15 +import org.apache.ibatis.session.RowBounds;
  16 +import org.springframework.stereotype.Component;
  17 +
  18 +import java.lang.reflect.Method;
  19 +import java.util.HashSet;
  20 +import java.util.Properties;
  21 +import java.util.Set;
  22 +import java.util.stream.Collectors;
  23 +
  24 +/**
  25 + * MyBatis 插件:拦截 SQL,自动拼接业务线条件
  26 + */
  27 +@Slf4j
  28 +@Component
  29 +//@Intercepts({
  30 +// @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
  31 +// //@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
  32 +//})
  33 +// 关键:同时拦截两个query重载方法
  34 +@Intercepts({
  35 + // 重载1:基础query(你原有的签名)
  36 + @Signature(
  37 + type = Executor.class,
  38 + method = "query",
  39 + args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
  40 + ),
  41 + // 重载2:带CacheKey的query(MP/分页常用)
  42 + @Signature(
  43 + type = Executor.class,
  44 + method = "query",
  45 + args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
  46 + )
  47 +})
  48 +public class BusinessLineSqlInterceptor implements Interceptor {
  49 + /**
  50 + * 缓存:Mapper方法全限定名 → 注解(避免重复反射,提升性能)
  51 + */
  52 + private static final java.util.Map<String, ProcessBusinessLine> ANNOTATION_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
  53 +
  54 + @Override
  55 + public Object intercept(Invocation invocation) throws Throwable {
  56 + // 1. 跳过过滤则直接执行原 SQL
  57 + if (!isSkipAnnotation(invocation)) {
  58 + return invocation.proceed();
  59 + }
  60 +
  61 + // 1. 解析拦截参数
  62 + Object[] args = invocation.getArgs();
  63 + MappedStatement ms = (MappedStatement) args[0];
  64 + Object parameter = args[1];
  65 + BoundSql boundSql = null;
  66 +
  67 + // 2. 仅处理 SELECT 类型的 SQL
  68 + if (ms.getSqlCommandType() != SqlCommandType.SELECT) {
  69 + return invocation.proceed();
  70 + }
  71 +
  72 + // 3. 适配两种重载方法,获取 BoundSql
  73 + if (args.length == 4) {
  74 + boundSql = ms.getBoundSql(parameter);
  75 + } else if (args.length == 6) {
  76 + boundSql = (BoundSql) args[5];
  77 + }
  78 + if (boundSql == null) {
  79 + return invocation.proceed();
  80 + }
  81 + String originalSql = boundSql.getSql().trim();
  82 + SqlCommandType sqlCommandType = ms.getSqlCommandType();
  83 + // 6. 替换 BoundSql 中的 SQL 语句(关键:反射修改)
  84 + MetaObject metaBoundSql = SystemMetaObject.forObject(boundSql);
  85 +
  86 + // 3. 仅处理 SELECT/UPDATE/DELETE(INSERT 由自动填充处理)
  87 + if (SqlCommandType.SELECT.equals(sqlCommandType)) {
  88 + Set<String> businessLineSet = new HashSet<>();//UserContextHolder.getBusinessLine();
  89 + businessLineSet.add("yl");
  90 + if (!CollectionUtils.isEmpty(businessLineSet)) {
  91 + // 拼接业务线条件
  92 + String newSql = buildSqlWithBusinessLine(originalSql, businessLineSet, sqlCommandType);
  93 + log.debug("原始 SQL:{},拼接后 SQL:{}", originalSql, newSql);
  94 + // 替换原始 SQL
  95 + metaBoundSql.setValue("sql", newSql);
  96 + }
  97 + }
  98 +
  99 + // 4. 执行原方法
  100 + return invocation.proceed();
  101 + }
  102 +
  103 + /**
  104 + * 拼接业务线条件到 SQL 中
  105 + */
  106 + private String buildSqlWithBusinessLine(String originalSql, Set<String> businessLineSet, SqlCommandType sqlCommandType) {
  107 + String upperSql = originalSql.toUpperCase();
  108 + // 核心转换:每个元素加单引号,逗号分隔
  109 + String result = businessLineSet.stream()
  110 + .map(s -> "'" + s + "'") // 为每个元素包裹单引号
  111 + .collect(Collectors.joining(",")); // 用逗号拼接所有元素
  112 + String condition = "busi_line in( " + result + ")";
  113 +
  114 + // SELECT 语句:处理 WHERE/ORDER BY/GROUP BY 等
  115 + if (SqlCommandType.SELECT.equals(sqlCommandType)) {
  116 + int whereIndex = upperSql.indexOf("WHERE");
  117 + int orderIndex = upperSql.indexOf("ORDER BY");
  118 + int groupIndex = upperSql.indexOf("GROUP BY");
  119 + int havingIndex = upperSql.indexOf("HAVING");
  120 + int insertIndex = -1;
  121 + if (whereIndex > 0) {
  122 + insertIndex = whereIndex + 5; // WHERE 后开始拼接
  123 + } else {
  124 + // 无 WHERE,找 ORDER/GROUP/HAVING 的位置,在其前加 WHERE
  125 + insertIndex = originalSql.length();
  126 + if (orderIndex > 0) insertIndex = orderIndex;
  127 + else if (groupIndex > 0) insertIndex = groupIndex;
  128 + else if (havingIndex > 0) insertIndex = havingIndex;
  129 + }
  130 +
  131 + // 拼接条件
  132 + String prefix = whereIndex > 0 ? " " +condition +" AND" : " WHERE "+ condition;
  133 + return new StringBuilder(originalSql)
  134 + .insert(insertIndex, prefix)
  135 + .toString();
  136 + }
  137 +
  138 + // UPDATE/DELETE 语句:同理拼接 WHERE 条件
  139 + if (SqlCommandType.UPDATE.equals(sqlCommandType) || SqlCommandType.DELETE.equals(sqlCommandType)) {
  140 + int whereIndex = upperSql.indexOf("WHERE");
  141 + if (whereIndex > 0) {
  142 + return new StringBuilder(originalSql)
  143 + .insert(whereIndex + 5, " AND " + condition)
  144 + .toString();
  145 + } else {
  146 + return originalSql + " WHERE " + condition;
  147 + }
  148 + }
  149 +
  150 + return originalSql;
  151 + }
  152 +
  153 + /**
  154 + * 插件代理(固定写法)
  155 + */
  156 + @Override
  157 + public Object plugin(Object target) {
  158 + return Plugin.wrap(target, this);
  159 + }
  160 +
  161 + /**
  162 + * 设置插件属性(无需配置则空实现)
  163 + */
  164 + @Override
  165 + public void setProperties(Properties properties) {
  166 + }
  167 +
  168 + /**
  169 + * 判断massper 方式上是否存在注解 @SkipBusinessLine
  170 + * 有注解则拼接业务线;没有则不拼接
  171 + * @param invocation 对象
  172 + * @return true:拼接业务线;false不拼接业务线
  173 + */
  174 + public boolean isSkipAnnotation(Invocation invocation){
  175 + // 1. 获取MappedStatement(MP封装的Mapper方法信息)
  176 + MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
  177 + String mapperMethodId = ms.getId(); // 格式:com.xxx.mapper.UserMapper.selectByAge
  178 + // 2. 解析Mapper全类名和方法名
  179 + String[] idParts = mapperMethodId.split("\\.");
  180 + String methodName = idParts[idParts.length - 1]; // 方法名:selectByAge
  181 + String mapperClassName = mapperMethodId.substring(0, mapperMethodId.lastIndexOf("."));
  182 + Class<?> mapperClass = null; // 加载Mapper接口
  183 + try {
  184 + mapperClass = Class.forName(mapperClassName);
  185 + } catch (ClassNotFoundException e) {
  186 + throw new RuntimeException(e);
  187 + }
  188 + ProcessBusinessLine skipAnnotation = ANNOTATION_CACHE.get(mapperClassName+methodName);
  189 + if (skipAnnotation == null) {
  190 + if(mapperClass.isAnnotationPresent(ProcessBusinessLine.class)){
  191 + ProcessBusinessLine processBusinessLine = mapperClass.getAnnotation(ProcessBusinessLine.class);
  192 + ANNOTATION_CACHE.put(mapperClassName+methodName, processBusinessLine);
  193 + return true;
  194 + }
  195 + }
  196 +
  197 + return false;
  198 + }
  199 +
  200 +}
0 \ No newline at end of file 201 \ No newline at end of file
urbanops-framework/urbanops-spring-boot-starter-mybatis/src/main/java/com/zteits/urbanops/framework/mybatis/config/UrbanopsMybatisAutoConfiguration.java
@@ -16,6 +16,7 @@ import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlPars @@ -16,6 +16,7 @@ import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlPars
16 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 16 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
17 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 17 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
18 import com.fasterxml.jackson.databind.ObjectMapper; 18 import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import jakarta.annotation.Resource;
19 import org.apache.ibatis.annotations.Mapper; 20 import org.apache.ibatis.annotations.Mapper;
20 import org.mybatis.spring.annotation.MapperScan; 21 import org.mybatis.spring.annotation.MapperScan;
21 import org.springframework.boot.autoconfigure.AutoConfiguration; 22 import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -36,6 +37,14 @@ import java.util.concurrent.TimeUnit; @@ -36,6 +37,14 @@ import java.util.concurrent.TimeUnit;
36 lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试 37 lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
37 public class UrbanopsMybatisAutoConfiguration { 38 public class UrbanopsMybatisAutoConfiguration {
38 39
  40 + /**
  41 + * 注册自定义业务线 SQL 拦截器
  42 + */
  43 + @Bean
  44 + public BusinessLineSqlInterceptor businessLineSqlInterceptor() {
  45 + return new BusinessLineSqlInterceptor();
  46 + }
  47 +
39 static { 48 static {
40 // 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存 49 // 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存
41 JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache( 50 JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache(
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/inspectionplan/InspectionPlanInterceptMapper.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.dal.mysql.inspectionplan;
  2 +
  3 +
  4 +import com.zteits.urbanops.framework.annotation.ProcessBusinessLine;
  5 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  6 +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
  7 +import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX;
  8 +import com.zteits.urbanops.module.garden.controller.admin.inspectionplan.vo.InspectionPlanPageReqVO;
  9 +import com.zteits.urbanops.module.garden.dal.dataobject.inspectionplan.InspectionPlanDO;
  10 +import org.apache.ibatis.annotations.Mapper;
  11 +
  12 +/**
  13 + * 巡检计划汇总 Mapper
  14 + *
  15 + * @author 超级管理员
  16 + */
  17 +@ProcessBusinessLine
  18 +@Mapper
  19 +public interface InspectionPlanInterceptMapper extends BaseMapperX<InspectionPlanDO> {
  20 + default PageResult<InspectionPlanDO> selectPage(InspectionPlanPageReqVO reqVO) {
  21 + return selectPage(reqVO, new LambdaQueryWrapperX<InspectionPlanDO>()
  22 + .eqIfPresent(InspectionPlanDO::getBatchNo, reqVO.getBatchNo())
  23 + .likeIfPresent(InspectionPlanDO::getPlanName, reqVO.getPlanName())
  24 + .eqIfPresent(InspectionPlanDO::getPlanAttr, reqVO.getPlanAttr())
  25 + .eqIfPresent(InspectionPlanDO::getPlanTypeId, reqVO.getPlanTypeId())
  26 + .eqIfPresent(InspectionPlanDO::getCompanyId, reqVO.getCompanyId())
  27 + .likeIfPresent(InspectionPlanDO::getRoadName, reqVO.getRoadName())
  28 + .eqIfPresent(InspectionPlanDO::getLevelId, reqVO.getLevelId())
  29 + .betweenIfPresent(InspectionPlanDO::getBeginTime, reqVO.getBeginTime())
  30 + .betweenIfPresent(InspectionPlanDO::getEndTime, reqVO.getEndTime())
  31 + .orderByDesc(InspectionPlanDO::getId));
  32 + }
  33 +
  34 +}
0 \ No newline at end of file 35 \ No newline at end of file
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/inspectionplan/InspectionPlanMapper.java
1 package com.zteits.urbanops.module.garden.dal.mysql.inspectionplan; 1 package com.zteits.urbanops.module.garden.dal.mysql.inspectionplan;
2 2
3 3
  4 +import com.zteits.urbanops.framework.annotation.ProcessBusinessLine;
4 import com.zteits.urbanops.framework.common.pojo.PageResult; 5 import com.zteits.urbanops.framework.common.pojo.PageResult;
5 import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; 7 import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
@@ -16,7 +17,6 @@ import org.apache.ibatis.annotations.Param; @@ -16,7 +17,6 @@ import org.apache.ibatis.annotations.Param;
16 */ 17 */
17 @Mapper 18 @Mapper
18 public interface InspectionPlanMapper extends BaseMapperX<InspectionPlanDO> { 19 public interface InspectionPlanMapper extends BaseMapperX<InspectionPlanDO> {
19 -  
20 default PageResult<InspectionPlanDO> selectPage(InspectionPlanPageReqVO reqVO) { 20 default PageResult<InspectionPlanDO> selectPage(InspectionPlanPageReqVO reqVO) {
21 return selectPage(reqVO, new LambdaQueryWrapperX<InspectionPlanDO>() 21 return selectPage(reqVO, new LambdaQueryWrapperX<InspectionPlanDO>()
22 .eqIfPresent(InspectionPlanDO::getBatchNo, reqVO.getBatchNo()) 22 .eqIfPresent(InspectionPlanDO::getBatchNo, reqVO.getBatchNo())
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/inspectionplan/InspectionPlanServiceImpl.java
@@ -10,6 +10,7 @@ import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; @@ -10,6 +10,7 @@ import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils;
10 import com.zteits.urbanops.module.garden.dal.dataobject.inspectionplan.InspectionPlanDetailDO; 10 import com.zteits.urbanops.module.garden.dal.dataobject.inspectionplan.InspectionPlanDetailDO;
11 import com.zteits.urbanops.module.garden.dal.dataobject.inspectionplan.InspectionPlanRoleDO; 11 import com.zteits.urbanops.module.garden.dal.dataobject.inspectionplan.InspectionPlanRoleDO;
12 import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanDetailMapper; 12 import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanDetailMapper;
  13 +import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanInterceptMapper;
13 import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanRoleMapper; 14 import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanRoleMapper;
14 import com.zteits.urbanops.module.garden.enums.FinishStateEnum; 15 import com.zteits.urbanops.module.garden.enums.FinishStateEnum;
15 import com.zteits.urbanops.module.garden.enums.PlanConstants; 16 import com.zteits.urbanops.module.garden.enums.PlanConstants;
@@ -51,6 +52,9 @@ public class InspectionPlanServiceImpl implements InspectionPlanService { @@ -51,6 +52,9 @@ public class InspectionPlanServiceImpl implements InspectionPlanService {
51 private InspectionPlanMapper inspectionPlanMapper; 52 private InspectionPlanMapper inspectionPlanMapper;
52 53
53 @Resource 54 @Resource
  55 + private InspectionPlanInterceptMapper inspectionPlanInterceptMapper;
  56 +
  57 + @Resource
54 private InspectionPlanDetailMapper inspectionPlanDetailMapper; 58 private InspectionPlanDetailMapper inspectionPlanDetailMapper;
55 59
56 @Resource 60 @Resource
@@ -218,7 +222,7 @@ public class InspectionPlanServiceImpl implements InspectionPlanService { @@ -218,7 +222,7 @@ public class InspectionPlanServiceImpl implements InspectionPlanService {
218 222
219 @Override 223 @Override
220 public PageResult<InspectionPlanDO> getInspectionPlanPage(InspectionPlanPageReqVO pageReqVO) { 224 public PageResult<InspectionPlanDO> getInspectionPlanPage(InspectionPlanPageReqVO pageReqVO) {
221 - return inspectionPlanMapper.selectPage(pageReqVO); 225 + return inspectionPlanInterceptMapper.selectPage(pageReqVO);
222 } 226 }
223 227
224 @Override 228 @Override