Commit 7f72c75b43d7b4fa9838d9a93974fefc1933cf9b
1 parent
04ee424b
提交处理sql自动拼接业务线逻辑
Showing
5 changed files
with
249 additions
and
2 deletions
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 |