From fad77a674d870002b2ba4cbd0523153683c46bcd Mon Sep 17 00:00:00 2001 From: 15195 Date: Wed, 28 Jan 2026 14:53:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=88=A9=E5=85=83?= =?UTF-8?q?=E4=BA=A8=E7=BA=BF=E7=A8=8B=EF=BC=8C=E5=A2=9E=E5=8A=A0mocksql?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=86=85=E7=BD=91=E7=94=9F=E4=BA=A7sql=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dao/DynamicSqlExecutorMapper.java | 12 +++ .../controller/TestSecondDbController.java | 91 +++++++++++++++++++ .../lyricDbMock/DynamicSqlExecutor.java | 41 +++++++++ .../sdm/system/model/bo/FormConfigure.java | 3 - 4 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 outbridge/src/main/java/com/sdm/outbridge/dao/DynamicSqlExecutorMapper.java create mode 100644 pbs/src/main/java/com/sdm/pbs/service/lyricDbMock/DynamicSqlExecutor.java diff --git a/outbridge/src/main/java/com/sdm/outbridge/dao/DynamicSqlExecutorMapper.java b/outbridge/src/main/java/com/sdm/outbridge/dao/DynamicSqlExecutorMapper.java new file mode 100644 index 00000000..17f1cd45 --- /dev/null +++ b/outbridge/src/main/java/com/sdm/outbridge/dao/DynamicSqlExecutorMapper.java @@ -0,0 +1,12 @@ +package com.sdm.outbridge.dao; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; +import java.util.Map; + +public interface DynamicSqlExecutorMapper { + @Select("${sql}") // 核心:用${sql}直接拼接传入的SQL字符串 + List> executeCustomSql(@Param("sql") String sql); +} diff --git a/pbs/src/main/java/com/sdm/pbs/controller/TestSecondDbController.java b/pbs/src/main/java/com/sdm/pbs/controller/TestSecondDbController.java index fb437dc8..8d256477 100644 --- a/pbs/src/main/java/com/sdm/pbs/controller/TestSecondDbController.java +++ b/pbs/src/main/java/com/sdm/pbs/controller/TestSecondDbController.java @@ -6,6 +6,7 @@ import com.sdm.outbridge.mode.FreelinkAndDingdingInformReq; import com.sdm.outbridge.mode.GetProcessDataReq; import com.sdm.outbridge.mode.HkUploadFileReq; import com.sdm.outbridge.service.lyric.*; +import com.sdm.pbs.service.lyricDbMock.DynamicSqlExecutor; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; @Slf4j @RestController @@ -38,6 +40,9 @@ public class TestSecondDbController { @Autowired private LyricVTodoInfoService lyricVTodoInfoService; + @Autowired + private DynamicSqlExecutor dynamicSqlExecutor; + @@ -154,6 +159,92 @@ public class TestSecondDbController { + /** + * 执行查询SQL接口 + * @return SQL执行结果 + */ + @PostMapping("/mockSql") + public SdmResponse>> executeSql(@RequestBody Map paramMap) { + try { + String sql = paramMap.get("sql").toString().trim(); + // 安全校验:禁止执行危险SQL(生产环境必须加!) + validateSql(sql); + List> result = dynamicSqlExecutor.executeQuery(sql); + return SdmResponse.success( result); + } catch (Exception e) { + return SdmResponse.failed("执行失败: " + e.getMessage()); + } + } + + + /** + * SQL安全校验(生产环境必须实现) + * 1. 禁止DROP/ALTER/TRUNCATE等危险操作 + * 2. 仅允许SELECT语句 + * 3. SELECT语句必须以LIMIT结尾(支持空格、换行、注释等边界情况) + */ + private void validateSql(String sql) { + // 1. 空值校验 + if (sql == null || sql.trim().isEmpty()) { + throw new RuntimeException("SQL语句不能为空"); + } + + // 2. SQL预处理:去除注释、多余空格/换行,统一格式 + String processedSql = preprocessSql(sql); + String lowerSql = processedSql.toLowerCase(); + + // 3. 禁止的危险关键字(精准匹配独立关键字,避免误判) + String[] forbiddenKeywords = {"drop", "alter", "truncate", "delete", "update", "insert", "create", + "rename", "replace", "grant", "revoke", "call", "execute"}; + for (String keyword : forbiddenKeywords) { + if (containsIndependentKeyword(lowerSql, keyword)) { + throw new RuntimeException("禁止执行包含[" + keyword + "]的SQL语句"); + } + } + + // 4. 确保是SELECT语句 + if (!lowerSql.startsWith("select")) { + throw new RuntimeException("仅允许执行SELECT查询语句"); + } + + // 5. 新增:SELECT语句必须以LIMIT结尾(支持LIMIT 数字 / LIMIT 数字,数字 / LIMIT 数字 OFFSET 数字) + if (!isEndWithValidLimit(lowerSql)) { + throw new RuntimeException("SELECT语句必须以LIMIT子句结尾,格式支持:LIMIT [数字] 或 LIMIT [数字],[数字] 或 LIMIT [数字] OFFSET [数字]"); + } + } + + /** + * 私有方法:SQL预处理(去除注释、多余空格/换行) + */ + private String preprocessSql(String sql) { + // 去除多行注释 /* ... */ + String noMultiComment = sql.replaceAll("/\\*.*?\\*/", ""); + + // 去除单行注释 -- ...(正确使用Pattern.MULTILINE,避免参数错误) + Pattern singleCommentPattern = Pattern.compile("--.*?$", Pattern.MULTILINE); + String noSingleComment = singleCommentPattern.matcher(noMultiComment).replaceAll(""); + + // 去除所有多余空白符(空格、换行、制表符等),压缩为单个空格 + String noExtraSpace = noSingleComment.replaceAll("\\s+", " ").trim(); + return noExtraSpace; + } + + /** + * 私有方法:检查是否包含独立的关键字(避免误判,如update_time不会匹配update) + */ + private boolean containsIndependentKeyword(String sql, String keyword) { + Pattern pattern = Pattern.compile("(^|\\W)" + keyword + "(\\W|$)"); + return pattern.matcher(sql).find(); + } + + /** + * 私有方法:检查SQL是否以合法的LIMIT子句结尾 + */ + private boolean isEndWithValidLimit(String lowerSql) { + // 正则匹配规则:支持 LIMIT 数字 / LIMIT 数字,数字 / LIMIT 数字 OFFSET 数字 + Pattern limitPattern = Pattern.compile("^.*limit\\s+\\d+(\\s*,\\s*\\d+)?(\\s+offset\\s+\\d+)?$"); + return limitPattern.matcher(lowerSql).matches(); + } } diff --git a/pbs/src/main/java/com/sdm/pbs/service/lyricDbMock/DynamicSqlExecutor.java b/pbs/src/main/java/com/sdm/pbs/service/lyricDbMock/DynamicSqlExecutor.java new file mode 100644 index 00000000..e3bb9262 --- /dev/null +++ b/pbs/src/main/java/com/sdm/pbs/service/lyricDbMock/DynamicSqlExecutor.java @@ -0,0 +1,41 @@ +package com.sdm.pbs.service.lyricDbMock; + +import com.sdm.outbridge.dao.DynamicSqlExecutorMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * 动态SQL执行工具类(适配多数据源) + */ +@Component +@Slf4j +public class DynamicSqlExecutor { + + // 注入纯注解版Mapper接口(多数据源需加@Qualifier指定) + @Autowired + private DynamicSqlExecutorMapper dynamicSqlExecutorMapper; + + /** + * 执行查询SQL,返回List(通用格式) + * 关键修复2:直接使用SqlSessionTemplate的selectList方法(无需获取原生SqlSession) + */ + public List> executeQuery(String sql) { + try { + long start = System.currentTimeMillis(); + // MyBatis执行动态SQL的通用方式:使用STATEMENT类型执行原生SQL + List> maps = dynamicSqlExecutorMapper.executeCustomSql(sql); + long end = System.currentTimeMillis(); + log.info("mockSql:{},本次查询返回:{}条,耗时:{} s",sql,maps.size(),(end - start)/1000); + return maps; + } catch (Exception e) { + log.error("执行动态SQL失败{}", e.getMessage()); + e.printStackTrace(); + throw new RuntimeException("执行SQL出错: " + e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/system/src/main/java/com/sdm/system/model/bo/FormConfigure.java b/system/src/main/java/com/sdm/system/model/bo/FormConfigure.java index 93f7abb7..225e4187 100644 --- a/system/src/main/java/com/sdm/system/model/bo/FormConfigure.java +++ b/system/src/main/java/com/sdm/system/model/bo/FormConfigure.java @@ -22,7 +22,4 @@ public class FormConfigure{ public String userId; - // 图表是1 动态表格2 - public String type; - }