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 | 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 | 16 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
| 17 | 17 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; |
| 18 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; |
| 19 | +import jakarta.annotation.Resource; | |
| 19 | 20 | import org.apache.ibatis.annotations.Mapper; |
| 20 | 21 | import org.mybatis.spring.annotation.MapperScan; |
| 21 | 22 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
| ... | ... | @@ -36,6 +37,14 @@ import java.util.concurrent.TimeUnit; |
| 36 | 37 | lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试 |
| 37 | 38 | public class UrbanopsMybatisAutoConfiguration { |
| 38 | 39 | |
| 40 | + /** | |
| 41 | + * 注册自定义业务线 SQL 拦截器 | |
| 42 | + */ | |
| 43 | + @Bean | |
| 44 | + public BusinessLineSqlInterceptor businessLineSqlInterceptor() { | |
| 45 | + return new BusinessLineSqlInterceptor(); | |
| 46 | + } | |
| 47 | + | |
| 39 | 48 | static { |
| 40 | 49 | // 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存 |
| 41 | 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 | 35 | \ No newline at end of file | ... | ... |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/inspectionplan/InspectionPlanMapper.java
| 1 | 1 | package com.zteits.urbanops.module.garden.dal.mysql.inspectionplan; |
| 2 | 2 | |
| 3 | 3 | |
| 4 | +import com.zteits.urbanops.framework.annotation.ProcessBusinessLine; | |
| 4 | 5 | import com.zteits.urbanops.framework.common.pojo.PageResult; |
| 5 | 6 | import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 7 | import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; |
| ... | ... | @@ -16,7 +17,6 @@ import org.apache.ibatis.annotations.Param; |
| 16 | 17 | */ |
| 17 | 18 | @Mapper |
| 18 | 19 | public interface InspectionPlanMapper extends BaseMapperX<InspectionPlanDO> { |
| 19 | - | |
| 20 | 20 | default PageResult<InspectionPlanDO> selectPage(InspectionPlanPageReqVO reqVO) { |
| 21 | 21 | return selectPage(reqVO, new LambdaQueryWrapperX<InspectionPlanDO>() |
| 22 | 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 | 10 | import com.zteits.urbanops.module.garden.dal.dataobject.inspectionplan.InspectionPlanDetailDO; |
| 11 | 11 | import com.zteits.urbanops.module.garden.dal.dataobject.inspectionplan.InspectionPlanRoleDO; |
| 12 | 12 | import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanDetailMapper; |
| 13 | +import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanInterceptMapper; | |
| 13 | 14 | import com.zteits.urbanops.module.garden.dal.mysql.inspectionplan.InspectionPlanRoleMapper; |
| 14 | 15 | import com.zteits.urbanops.module.garden.enums.FinishStateEnum; |
| 15 | 16 | import com.zteits.urbanops.module.garden.enums.PlanConstants; |
| ... | ... | @@ -51,6 +52,9 @@ public class InspectionPlanServiceImpl implements InspectionPlanService { |
| 51 | 52 | private InspectionPlanMapper inspectionPlanMapper; |
| 52 | 53 | |
| 53 | 54 | @Resource |
| 55 | + private InspectionPlanInterceptMapper inspectionPlanInterceptMapper; | |
| 56 | + | |
| 57 | + @Resource | |
| 54 | 58 | private InspectionPlanDetailMapper inspectionPlanDetailMapper; |
| 55 | 59 | |
| 56 | 60 | @Resource |
| ... | ... | @@ -218,7 +222,7 @@ public class InspectionPlanServiceImpl implements InspectionPlanService { |
| 218 | 222 | |
| 219 | 223 | @Override |
| 220 | 224 | public PageResult<InspectionPlanDO> getInspectionPlanPage(InspectionPlanPageReqVO pageReqVO) { |
| 221 | - return inspectionPlanMapper.selectPage(pageReqVO); | |
| 225 | + return inspectionPlanInterceptMapper.selectPage(pageReqVO); | |
| 222 | 226 | } |
| 223 | 227 | |
| 224 | 228 | @Override | ... | ... |