From 4b1d18e0f21e3a95ca1edd5b127f5094227af542 Mon Sep 17 00:00:00 2001 From: Tankaikai Date: Tue, 1 Apr 2025 11:35:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9F=AD=E5=89=A7=E6=96=B0=E9=9C=80=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sqx/common/utils/SqlFetcher.java | 35 +++++++++ .../java/com/sqx/common/utils/SqlUtil.java | 66 +++++++++++++++++ .../ext/service/impl/ExtSysServiceImpl.java | 72 +++++++++++++------ src/main/resources/db.setting | 24 +++++++ src/main/resources/mapper/ext/ExtSysDao.xml | 66 ++++++++++------- 5 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/sqx/common/utils/SqlFetcher.java create mode 100644 src/main/java/com/sqx/common/utils/SqlUtil.java create mode 100644 src/main/resources/db.setting diff --git a/src/main/java/com/sqx/common/utils/SqlFetcher.java b/src/main/java/com/sqx/common/utils/SqlFetcher.java new file mode 100644 index 00000000..f6973b47 --- /dev/null +++ b/src/main/java/com/sqx/common/utils/SqlFetcher.java @@ -0,0 +1,35 @@ +package com.sqx.common.utils; + +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMap; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author tankaikai + * @since 2025-03-28 10:16 + */ +@Component +public class SqlFetcher { + @Autowired + private SqlSessionFactory sqlSessionFactory; + + public String getSqlFromXml(String statementId, Object parameterObject) { + // 获取Configuration对象 + Configuration configuration = sqlSessionFactory.getConfiguration(); + try { + // 获取指定ID的MappedStatement对象 + MappedStatement mappedStatement = configuration.getMappedStatement(statementId); + // 获取SQL语句 + if(parameterObject instanceof String){ + SqlUtil.escapeOrderBySql((String) parameterObject); + } + return mappedStatement.getBoundSql(parameterObject).getSql(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/com/sqx/common/utils/SqlUtil.java b/src/main/java/com/sqx/common/utils/SqlUtil.java new file mode 100644 index 00000000..66b79a34 --- /dev/null +++ b/src/main/java/com/sqx/common/utils/SqlUtil.java @@ -0,0 +1,66 @@ +package com.sqx.common.utils; + +import cn.hutool.core.util.StrUtil; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +public class SqlUtil { + /** + * SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配 + */ + private static final Pattern SQL_SYNTAX_PATTERN = Pattern.compile("(insert|delete|update|select|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec|declare|show|rename|set)" + + "\\s+.*(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)|(select\\s*\\*\\s*from\\s+)" + + "|if\\s*\\(.*\\)|select\\s*\\(.*\\)|substr\\s*\\(.*\\)|substring\\s*\\(.*\\)|char\\s*\\(.*\\)|concat\\s*\\(.*\\)|benchmark\\s*\\(.*\\)|sleep\\s*\\(.*\\)|(and|or)\\s+.*", Pattern.CASE_INSENSITIVE); + /** + * 使用'、;或注释截断SQL检查正则 + */ + private static final Pattern SQL_COMMENT_PATTERN = Pattern.compile("'.*(or|union|--|#|/\\*|;)", Pattern.CASE_INSENSITIVE); + + /** + * 限制orderBy最大长度 + */ + private static final int ORDER_BY_MAX_LENGTH = 500; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) { + if (StrUtil.isNotEmpty(value)) { + if (check(value)) { + throw new IllegalArgumentException("参数不符合规范,不能进行查询"); + } + if (value.length() > ORDER_BY_MAX_LENGTH) { + throw new IllegalArgumentException("参数已超过最大限制,不能进行查询"); + } + } + return value; + } + + /** + * 检查参数是否存在 SQL 注入 + * + * @param value 检查参数 + * @return true 非法 false 合法 + */ + public static boolean check(String value) { + Objects.requireNonNull(value); + // 处理是否包含SQL注释字符 || 检查是否包含SQL注入敏感字符 + return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find(); + } + + /** + * 刪除字段转义符单引号双引号 + * + * @param text 待处理字段 + */ + public static String removeEscapeCharacter(String text) { + Objects.nonNull(text); + return text.replaceAll("\"", "").replaceAll("'", ""); + } +} diff --git a/src/main/java/com/sqx/modules/ext/service/impl/ExtSysServiceImpl.java b/src/main/java/com/sqx/modules/ext/service/impl/ExtSysServiceImpl.java index 814c2f66..bffdd399 100644 --- a/src/main/java/com/sqx/modules/ext/service/impl/ExtSysServiceImpl.java +++ b/src/main/java/com/sqx/modules/ext/service/impl/ExtSysServiceImpl.java @@ -3,19 +3,23 @@ package com.sqx.modules.ext.service.impl; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUtil; import cn.hutool.core.exceptions.ValidateException; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; +import cn.hutool.db.Db; import com.sqx.common.utils.PageUtils; +import com.sqx.common.utils.SqlFetcher; import com.sqx.modules.common.dao.CommonInfoDao; import com.sqx.modules.common.entity.CommonInfo; import com.sqx.modules.ext.dao.ExtSysDao; import com.sqx.modules.ext.dto.*; import com.sqx.modules.ext.param.InviteFriendConfigParam; import com.sqx.modules.ext.service.ExtSysService; +import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; /** @@ -30,6 +34,10 @@ public class ExtSysServiceImpl implements ExtSysService { private CommonInfoDao commonInfoDao; @Autowired private ExtSysDao extSysDao; + @Autowired + private SqlFetcher sqlFetcher; + @Value("${spring.profiles.active}") + private String profiles; @Override public void saveInviteFriendConfig(InviteFriendConfigParam param) { @@ -74,41 +82,61 @@ public class ExtSysServiceImpl implements ExtSysService { @Override public PageUtils queryInviteFriendRecord(Integer page, Integer limit, String keywords) { - PageHelper.startPage(page, limit); - List list = extSysDao.findInviteFriendList(keywords); - PageInfo pageInfo = new PageInfo<>(list); - return PageUtils.page(pageInfo); + String sql = sqlFetcher.getSqlFromXml("com.sqx.modules.ext.dao.ExtSysDao.findInviteFriendList", keywords); + int totalCount = getTotalCount(sql); + List list = getQueryList(sql, InviteFriendDTO.class, totalCount, page, limit); + return new PageUtils(list, totalCount, limit, page); } @Override public PageUtils queryInviteSignInRecord(Integer page, Integer limit, Long userId) { - PageHelper.startPage(page, limit); - List list = extSysDao.findInviteSignInList(userId); - PageInfo pageInfo = new PageInfo<>(list); - return PageUtils.page(pageInfo); + String sql = sqlFetcher.getSqlFromXml("com.sqx.modules.ext.dao.ExtSysDao.findInviteSignInList", userId); + int totalCount = getTotalCount(sql); + List list = getQueryList(sql, SignInNumDTO.class, totalCount, page, limit); + return new PageUtils(list, totalCount, limit, page); } @Override public PageUtils queryInviteAwardDetailRecord(Integer page, Integer limit, Long userId) { - PageHelper.startPage(page, limit); - List list = extSysDao.findInviteAwardDetailList(userId); - PageInfo pageInfo = new PageInfo<>(list); - return PageUtils.page(pageInfo); + String sql = sqlFetcher.getSqlFromXml("com.sqx.modules.ext.dao.ExtSysDao.findInviteAwardDetailList", userId); + int totalCount = getTotalCount(sql); + List list = getQueryList(sql, InviteAwardDTO.class, totalCount, page, limit); + return new PageUtils(list, totalCount, limit, page); } @Override public PageUtils queryLotteryCountPage(Integer page, Integer limit, String keywords) { - PageHelper.startPage(page, limit); - List list = extSysDao.findLotteryCountList(keywords); - PageInfo pageInfo = new PageInfo<>(list); - return PageUtils.page(pageInfo); + String sql = sqlFetcher.getSqlFromXml("com.sqx.modules.ext.dao.ExtSysDao.findLotteryCountList", keywords); + int totalCount = getTotalCount(sql); + List list = getQueryList(sql, LotteryCountQueryDTO.class, totalCount, page, limit); + return new PageUtils(list, totalCount, limit, page); } @Override public PageUtils queryLotteryDetailPage(Integer page, Integer limit, Long userId) { - PageHelper.startPage(page, limit); - List list = extSysDao.findLotteryDetailPage(userId); - PageInfo pageInfo = new PageInfo<>(list); - return PageUtils.page(pageInfo); + String sql = sqlFetcher.getSqlFromXml("com.sqx.modules.ext.dao.ExtSysDao.findLotteryDetailPage", userId); + int totalCount = getTotalCount(sql); + List list = getQueryList(sql, LotteryDetailDTO.class, totalCount, page, limit); + return new PageUtils(list, totalCount, limit, page); + } + + @SneakyThrows + private int getTotalCount(String sql) { + String countSql = StrUtil.format("select count(*) from ({}) as tmp", sql); + System.out.println("CountSQL:" + countSql); + Number count = Db.use(profiles).queryNumber(countSql); + return count.intValue(); + } + + @SneakyThrows + private List getQueryList(String sql, Class beanClass, int totalCount, Integer page, Integer limit) { + if(totalCount == 0){ + return new ArrayList<>(); + } + page = ObjectUtil.defaultIfNull(page, 1); + limit = ObjectUtil.defaultIfNull(limit, 10); + String querySql = StrUtil.format("select * from ({}) as tmp limit {}, {}", sql, (page - 1) * limit, limit); + System.out.println("QuerySql:" + querySql); + return Db.use(profiles).query(querySql, beanClass); } } diff --git a/src/main/resources/db.setting b/src/main/resources/db.setting new file mode 100644 index 00000000..5213e3e5 --- /dev/null +++ b/src/main/resources/db.setting @@ -0,0 +1,24 @@ +[dev] +url = jdbc:mysql://rm-gc712o11yndj78x6a6o.mysql.cn-chengdu.rds.aliyuncs.com/duanju?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=CTT +username = video_user +password = VideoUser@1 + +[test] +url = jdbc:mysql://rm-gc712o11yndj78x6a6o.mysql.cn-chengdu.rds.aliyuncs.com/duanju?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=CTT +username = video_user +password = VideoUser@1 + +[prod] +url = jdbc:mysql://rm-gc7xx913734hv5w5q.mysql.cn-chengdu.rds.aliyuncs.com/duanju?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=CTT +username = video_user +password = VideoUser@1 + +## 可选配置 +# 是否在日志中显示执行的SQL +showSql = true +# 是否格式化显示的SQL +formatSql = false +# 是否显示SQL参数 +showParams = true +# 打印SQL的日志等级,默认debug,可以是info、warn、error +sqlLevel = debug \ No newline at end of file diff --git a/src/main/resources/mapper/ext/ExtSysDao.xml b/src/main/resources/mapper/ext/ExtSysDao.xml index 12c06ce2..0ecc1084 100644 --- a/src/main/resources/mapper/ext/ExtSysDao.xml +++ b/src/main/resources/mapper/ext/ExtSysDao.xml @@ -10,27 +10,44 @@ t1.avatar, ifnull(t2.signInNum,0) as signInNum, ifnull(t2.awardAmount,0) as awardAmount - from tb_user t1 - left JOIN (select user_id,sum(money) as awardAmount,sum(case when title = '签到奖励' then 1 else 0 end) as signInNum from user_money_details where classify = 6 group by user_id) t2 on t1.user_id = t2.user_id + from v_tb_user t1 + left JOIN (select user_id,sum(money) as awardAmount,sum(case when title = '签到奖励' then 1 else 0 end) as signInNum from v_user_money_details where classify = 6 group by user_id) t2 on t1.user_id = t2.user_id - and (t1.user_name like concat('%',#{keywords},'%') or t1.phone like concat('%',#{keywords},'%')) + and (t1.user_name like concat('%',${keywords},'%') or t1.phone like concat('%',${keywords},'%')) ORDER BY t2.signInNum desc,t1.user_id asc \ No newline at end of file