From a426cc2f4b8ca83cdf87967c13d68a169822d729 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 7 Jun 2026 02:40:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20findInSet=20SQL=20?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E9=A3=8E=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整 MyBatisUtils.findInSet 使用 MyBatis-Plus 参数绑定 - 增加 columnName 白名单校验,避免列名被注入 - 补充 H2 兼容实现,恢复相关单测覆盖 - 替换各模块动态 FIND_IN_SET 字符串拼接写法 - 补充单参数、多参数绑定场景单测 --- .../mybatis/core/enums/DbTypeEnum.java | 24 +++---- .../mybatis/core/util/MyBatisUtils.java | 42 ++++++++++-- .../mybatis/core/util/MyBatisUtilsTest.java | 67 +++++++++++++++++++ .../CrmCustomerLimitConfigMapper.java | 5 +- .../mysql/message/ImChannelMessageMapper.java | 3 +- .../dal/mysql/alert/IotAlertConfigMapper.java | 4 +- .../iot/dal/mysql/device/IotDeviceMapper.java | 5 +- .../iot/dal/mysql/rule/IotDataRuleMapper.java | 4 +- .../mysql/coupon/CouponTemplateMapper.java | 5 +- .../mysql/reward/RewardActivityMapper.java | 11 +-- .../SeckillActivityMapper.java | 5 +- .../dal/mysql/user/MemberUserMapper.java | 13 ++-- .../qc/template/MesQcTemplateMapper.java | 2 +- .../system/dal/mysql/mail/MailLogMapper.java | 2 +- .../system/dal/mysql/tenant/TenantMapper.java | 2 +- .../service/mail/MailLogServiceImplTest.java | 6 +- .../service/tenant/TenantServiceImplTest.java | 2 - 17 files changed, 154 insertions(+), 48 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java index 460ea78116..62a5b08300 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java @@ -20,51 +20,49 @@ public enum DbTypeEnum { /** * H2 - * - * 注意:H2 不支持 find_in_set 函数 */ - H2(DbType.H2, "H2", ""), + H2(DbType.H2, "H2", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * MySQL */ - MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET('#{value}', #{column}) <> 0"), + MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET(#{value}, #{column}) <> 0"), /** * Oracle */ - ORACLE(DbType.ORACLE, "Oracle", "FIND_IN_SET('#{value}', #{column}) <> 0"), + ORACLE(DbType.ORACLE, "Oracle", "INSTR(',' || #{column} || ',', ',' || #{value} || ',') > 0"), /** * PostgreSQL * * 华为 openGauss 使用 ProductName 与 PostgreSQL 相同 */ - POSTGRE_SQL(DbType.POSTGRE_SQL,"PostgreSQL", "POSITION('#{value}' IN #{column}) <> 0"), + POSTGRE_SQL(DbType.POSTGRE_SQL, "PostgreSQL", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * SQL Server */ - SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + CAST(#{value} AS varchar(255)) + ',', ',' + #{column} + ',') > 0"), /** * SQL Server 2005 */ - SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + CAST(#{value} AS varchar(255)) + ',', ',' + #{column} + ',') > 0"), /** * 达梦 */ - DM(DbType.DM, "DM DBMS", "FIND_IN_SET('#{value}', #{column}) <> 0"), + DM(DbType.DM, "DM DBMS", "FIND_IN_SET(#{value}, #{column}) <> 0"), /** * 人大金仓 */ - KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"), + KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * OceanBase */ - OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET('#{value}', #{column}) <> 0") + OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET(#{value}, #{column}) <> 0") ; @@ -95,7 +93,9 @@ public enum DbTypeEnum { } public static String getFindInSetTemplate(DbType dbType) { - return Optional.of(MAP_BY_MP.get(dbType).getFindInSetTemplate()) + return Optional.ofNullable(MAP_BY_MP.get(dbType)) + .map(DbTypeEnum::getFindInSetTemplate) + .filter(StrUtil::isNotBlank) .orElseThrow(() -> new IllegalArgumentException("FIND_IN_SET not supported")); } } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java index a239301874..ab4612cb0f 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java @@ -34,6 +34,10 @@ public class MyBatisUtils { private static final Pattern SAFE_COLUMN_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)*$"); + private static final String FIND_IN_SET_VALUE_PLACEHOLDER = "#{value}"; + + private static final String FIND_IN_SET_COLUMN_PLACEHOLDER = "#{column}"; + public static Page buildPage(PageParam pageParam) { return buildPage(pageParam, null); } @@ -157,15 +161,43 @@ public class MyBatisUtils { /** * 跨数据库的 find_in_set 实现 * - * @param column 字段名称 - * @param value 查询值(不带单引号) + * @param columnName 字段名称 * @return sql */ - public static String findInSet(String column, Object value) { + public static String findInSet(String columnName) { + return findInSet(columnName, 0); + } + + /** + * 跨数据库的 find_in_set 实现,适用于同一个 apply 语句中有多个参数的场景 + * + * @param columnName 字段名称 + * @param paramIndex apply 参数序号 + * @return sql + */ + public static String findInSetWithParamIndex(String columnName, int paramIndex) { + return findInSet(columnName, paramIndex); + } + + private static String findInSet(String columnName, int paramIndex) { DbType dbType = JdbcUtils.getDbType(); + return findInSet(dbType, columnName, paramIndex); + } + + static String findInSet(DbType dbType, String columnName, int paramIndex) { + if (!isSafeColumnName(columnName)) { + throw new IllegalArgumentException("Invalid column name: " + columnName); + } + if (paramIndex < 0) { + throw new IllegalArgumentException("Invalid param index: " + paramIndex); + } return DbTypeEnum.getFindInSetTemplate(dbType) - .replace("#{column}", column) - .replace("#{value}", StrUtil.toString(value)); + .replace(FIND_IN_SET_COLUMN_PLACEHOLDER, columnName) + .replace(FIND_IN_SET_VALUE_PLACEHOLDER, "{" + paramIndex + "}"); + } + + private static boolean isSafeColumnName(String columnName) { + return StrUtil.isNotEmpty(columnName) && SAFE_COLUMN_NAME_PATTERN.matcher(columnName).matches(); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java index 43f5611dc9..abe8891540 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.mybatis.core.util; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.SortingField; +import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.OrderItem; @@ -13,6 +14,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -98,6 +100,71 @@ public class MyBatisUtilsTest { assertEquals("DESC", MyBatisUtils.getOrderDirection(null)); } + @Test + public void testFindInSet() { + assertEquals("FIND_IN_SET({0}, websites) <> 0", + MyBatisUtils.findInSet(DbType.MYSQL, "websites", 0)); + assertEquals("POSITION(',' || CAST({0} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", + MyBatisUtils.findInSet(DbType.H2, "websites", 0)); + assertEquals("INSTR(',' || t.websites || ',', ',' || {0} || ',') > 0", + MyBatisUtils.findInSet(DbType.ORACLE, "t.websites", 0)); + assertEquals("POSITION(',' || CAST({1} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", + MyBatisUtils.findInSet(DbType.POSTGRE_SQL, "websites", 1)); + assertEquals("CHARINDEX(',' + CAST({2} AS varchar(255)) + ',', ',' + websites + ',') > 0", + MyBatisUtils.findInSet(DbType.SQL_SERVER, "websites", 2)); + } + + @Test + public void testFindInSet_invalidColumnName() { + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "websites;drop table system_tenant", 0)); + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "FIND_IN_SET(value, websites)", 0)); + } + + @Test + public void testFindInSet_invalidParamIndex() { + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "websites", -1)); + } + + @Test + public void testFindInSet_applyBindsValue() { + // 准备参数 + QueryWrapper query = new QueryWrapper<>(); + String value = "test' OR 1 = 1"; + + // 调用 + query.apply(MyBatisUtils.findInSet(DbType.MYSQL, "to_mails", 0), value); + + // 断言:SQL 片段里只有 MyBatis Plus 参数占位,用户输入不会被直接拼接进去 + assertEquals("(FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL1}, to_mails) <> 0)", + query.getSqlSegment()); + assertFalse(query.getSqlSegment().contains(value)); + assertEquals(value, query.getParamNameValuePairs().get("MPGENVAL1")); + } + + @Test + public void testFindInSet_applyBindsMultipleValues() { + // 准备参数 + QueryWrapper query = new QueryWrapper<>(); + String value1 = "1' OR 1 = 1"; + String value2 = "2' OR 1 = 1"; + + // 调用 + query.apply(MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", 0) + + " OR " + MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", 1), value1, value2); + + // 断言:多个参数都由 MyBatis Plus 生成占位符,不拼接用户输入 + assertEquals("(FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL1}, tag_ids) <> 0" + + " OR FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL2}, tag_ids) <> 0)", + query.getSqlSegment()); + assertFalse(query.getSqlSegment().contains(value1)); + assertFalse(query.getSqlSegment().contains(value2)); + assertEquals(value1, query.getParamNameValuePairs().get("MPGENVAL1")); + assertEquals(value2, query.getParamNameValuePairs().get("MPGENVAL2")); + } + private void assertOrderItem(OrderItem orderItem, String column, boolean asc) { assertEquals(column, orderItem.getColumn()); assertEquals(asc, orderItem.isAsc()); diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java index a62aabb7ad..230ec1f173 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import org.apache.ibatis.annotations.Mapper; @@ -28,9 +29,9 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX query = new LambdaQueryWrapperX() .eq(CrmCustomerLimitConfigDO::getType, type); query.and(w -> { - w.apply("FIND_IN_SET({0}, user_ids) > 0", userId); + w.apply(MyBatisUtils.findInSet("user_ids"), userId); if (deptId != null) { - w.or().apply("FIND_IN_SET({0}, dept_ids) > 0", deptId); + w.or().apply(MyBatisUtils.findInSet("dept_ids"), deptId); } }); return selectList(query); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImChannelMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImChannelMessageMapper.java index 81f923889e..6f1cd1a1f1 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImChannelMessageMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImChannelMessageMapper.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.dal.mysql.message; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessagePageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; import org.apache.ibatis.annotations.Mapper; @@ -32,7 +33,7 @@ public interface ImChannelMessageMapper extends BaseMapperX .gt(ImChannelMessageDO::getId, minId) .and(w -> w.isNull(ImChannelMessageDO::getReceiverUserIds) .or().eq(ImChannelMessageDO::getReceiverUserIds, "") - .or().apply("FIND_IN_SET({0}, receiver_user_ids)", userId)) + .or().apply(MyBatisUtils.findInSet("receiver_user_ids"), userId)) .orderByAsc(ImChannelMessageDO::getId) .last("LIMIT " + size)); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java index 52b0d360dd..330e1055a6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java @@ -33,7 +33,7 @@ public interface IotAlertConfigMapper extends BaseMapperX { default List selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(IotAlertConfigDO::getStatus, status) - .apply(MyBatisUtils.findInSet("scene_rule_ids", sceneRuleId))); + .apply(MyBatisUtils.findInSet("scene_rule_ids"), sceneRuleId)); } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index 1e3fb2e576..84c2d99377 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; @@ -34,7 +35,7 @@ public interface IotDeviceMapper extends BaseMapperX { .likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname()) .eqIfPresent(IotDeviceDO::getState, reqVO.getStatus()) .eqIfPresent(IotDeviceDO::getGatewayId, reqVO.getGatewayId()) - .apply(ObjectUtil.isNotNull(reqVO.getGroupId()), "FIND_IN_SET(" + reqVO.getGroupId() + ",group_ids) > 0") + .apply(ObjectUtil.isNotNull(reqVO.getGroupId()), MyBatisUtils.findInSet("group_ids"), reqVO.getGroupId()) .orderByDesc(IotDeviceDO::getId)); } @@ -72,7 +73,7 @@ public interface IotDeviceMapper extends BaseMapperX { default Long selectCountByGroupId(Long groupId) { return selectCount(new LambdaQueryWrapperX() - .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0")); + .apply(MyBatisUtils.findInSet("group_ids"), groupId)); } default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java index ce2eeb04bc..579b62e9da 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java @@ -28,7 +28,7 @@ public interface IotDataRuleMapper extends BaseMapperX { default List selectListBySinkId(Long sinkId) { return selectList(new LambdaQueryWrapperX() - .apply(MyBatisUtils.findInSet("sink_ids", sinkId))); + .apply(MyBatisUtils.findInSet("sink_ids"), sinkId)); } default List selectListByStatus(Integer status) { @@ -39,4 +39,4 @@ public interface IotDataRuleMapper extends BaseMapperX { return selectOne(IotDataRuleDO::getName, name); } -} \ No newline at end of file +} diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java index 68693e0a3d..fdadd685a1 100755 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; @@ -34,7 +35,7 @@ public interface CouponTemplateMapper extends BaseMapperX { .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType()) .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime()) .eqIfPresent(CouponTemplateDO::getProductScope, reqVO.getProductScope()) - .and(reqVO.getProductScopeValue() != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)", + .and(reqVO.getProductScopeValue() != null, w -> w.apply(MyBatisUtils.findInSet("product_scope_values"), reqVO.getProductScopeValue())) .and(canTakeConsumer != null, canTakeConsumer) .orderByDesc(CouponTemplateDO::getId)); @@ -61,7 +62,7 @@ public interface CouponTemplateMapper extends BaseMapperX { Consumer> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes); return selectList(new LambdaQueryWrapperX() .eqIfPresent(CouponTemplateDO::getProductScope, productScope) - .and(productScopeValue != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)", + .and(productScopeValue != null, w -> w.apply(MyBatisUtils.findInSet("product_scope_values"), productScopeValue)) .and(canTakeConsumer != null, canTakeConsumer) .last(" LIMIT " + count) diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java index 6d8e9c6847..831ad0d8b6 100755 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.reward; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 满减送活动 Mapper @@ -34,18 +35,18 @@ public interface RewardActivityMapper extends BaseMapperX { Collection categoryIds, Integer status) { LocalDateTime now = LocalDateTime.now(); - Function, String> productScopeValuesFindInSetFunc = ids -> ids.stream() - .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) + Function, String> productScopeValuesFindInSetFunc = ids -> IntStream.range(0, ids.size()) + .mapToObj(index -> MyBatisUtils.findInSetWithParamIndex("product_scope_values", index)) .collect(Collectors.joining(" OR ")); return selectList(new LambdaQueryWrapperX() .eq(RewardActivityDO::getStatus, status) .lt(RewardActivityDO::getStartTime, now) .gt(RewardActivityDO::getEndTime, now) .and(i -> i.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) - .and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds))) + .and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds), spuIds.toArray())) .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.ALL.getScope())) .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) - .and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds))))) + .and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds), categoryIds.toArray())))) .orderByDesc(RewardActivityDO::getId) ); } diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java index 34e50f1f5d..7f0c07daf3 100644 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; @@ -27,7 +28,7 @@ public interface SeckillActivityMapper extends BaseMapperX { .likeIfPresent(SeckillActivityDO::getName, reqVO.getName()) .eqIfPresent(SeckillActivityDO::getStatus, reqVO.getStatus()) .betweenIfPresent(SeckillActivityDO::getCreateTime, reqVO.getCreateTime()) - .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), "FIND_IN_SET(" + reqVO.getConfigId() + ", config_ids) > 0") + .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), MyBatisUtils.findInSet("config_ids"), reqVO.getConfigId()) .orderByDesc(SeckillActivityDO::getId)); } @@ -70,7 +71,7 @@ public interface SeckillActivityMapper extends BaseMapperX { .eqIfPresent(SeckillActivityDO::getStatus, status) .lt(SeckillActivityDO::getStartTime, dateTime) .gt(SeckillActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动 - .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0")); + .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), MyBatisUtils.findInSet("config_ids"), pageReqVO.getConfigId())); } default SeckillActivityDO selectBySpuIdAndStatusAndNow(Long spuId, Integer status) { diff --git a/yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java b/yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java index 6200bf1259..9da83b1f89 100644 --- a/yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java +++ b/yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.member.dal.mysql.user; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -13,6 +13,7 @@ import org.apache.ibatis.annotations.Mapper; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 会员 User Mapper @@ -38,10 +39,12 @@ public interface MemberUserMapper extends BaseMapperX { default PageResult selectPage(MemberUserPageReqVO reqVO) { // 处理 tagIds 过滤条件 String tagIdSql = ""; + Object[] tagIdParams = new Object[0]; if (CollUtil.isNotEmpty(reqVO.getTagIds())) { - tagIdSql = reqVO.getTagIds().stream() - .map(tagId -> "FIND_IN_SET(" + tagId + ", tag_ids)") + tagIdSql = IntStream.range(0, reqVO.getTagIds().size()) + .mapToObj(index -> MyBatisUtils.findInSetWithParamIndex("tag_ids", index)) .collect(Collectors.joining(" OR ")); + tagIdParams = reqVO.getTagIds().toArray(); } // 分页查询 return selectPage(reqVO, new LambdaQueryWrapperX() @@ -52,7 +55,7 @@ public interface MemberUserMapper extends BaseMapperX { .betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime()) .eqIfPresent(MemberUserDO::getLevelId, reqVO.getLevelId()) .eqIfPresent(MemberUserDO::getGroupId, reqVO.getGroupId()) - .apply(StrUtil.isNotEmpty(tagIdSql), tagIdSql) + .apply(CollUtil.isNotEmpty(reqVO.getTagIds()), tagIdSql, tagIdParams) .orderByDesc(MemberUserDO::getId)); } @@ -66,7 +69,7 @@ public interface MemberUserMapper extends BaseMapperX { default Long selectCountByTagId(Long tagId) { return selectCount(new LambdaQueryWrapperX() - .apply("FIND_IN_SET({0}, tag_ids)", tagId)); + .apply(MyBatisUtils.findInSet("tag_ids"), tagId)); } /** diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java index 29242fe30e..3c7280a175 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java @@ -24,7 +24,7 @@ public interface MesQcTemplateMapper extends BaseMapperX { .likeIfPresent(MesQcTemplateDO::getName, reqVO.getName()) .orderByDesc(MesQcTemplateDO::getId); if (reqVO.getType() != null) { - query.apply(MyBatisUtils.findInSet("types", reqVO.getType())); + query.apply(MyBatisUtils.findInSet("types"), reqVO.getType()); } query.eqIfPresent(MesQcTemplateDO::getStatus, reqVO.getStatus()); return selectPage(reqVO, query); diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java index 44fab07a0d..fe2ed48990 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java @@ -21,7 +21,7 @@ public interface MailLogMapper extends BaseMapperX { .eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus()) .betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime()) .apply(StrUtil.isNotBlank(reqVO.getToMail()), - MyBatisUtils.findInSet("to_mails", reqVO.getToMail())) + MyBatisUtils.findInSet("to_mails"), reqVO.getToMail()) .orderByDesc(MailLogDO::getId)); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java index a58b057de7..c3e5ae3acb 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java @@ -29,7 +29,7 @@ public interface TenantMapper extends BaseMapperX { default List selectListByWebsite(String website) { return selectList(new LambdaQueryWrapperX() - .apply(MyBatisUtils.findInSet("websites", website))); + .apply(MyBatisUtils.findInSet("websites"), website)); } default Long selectCountByPackageId(Long packageId) { diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java index ec8aadacd9..8817519f50 100755 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java @@ -160,8 +160,8 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L))); // 测试 userType 不匹配 mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue()))); - // 测试 toMails 不匹配(特殊:find_in_set 无法单测) -// mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMails(Lists.newArrayList("788@qq.com")))); + // 测试 toMails 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMails(Lists.newArrayList("788@qq.com")))); // 测试 accountId 不匹配 mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L))); // 测试 templateId 不匹配 @@ -174,7 +174,7 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { MailLogPageReqVO reqVO = new MailLogPageReqVO(); reqVO.setUserId(1L); reqVO.setUserType(UserTypeEnum.ADMIN.getValue()); -// reqVO.setToMail("768@qq.com"); + reqVO.setToMail("768@qq.com"); reqVO.setAccountId(10L); reqVO.setTemplateId(100L); reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus()); diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java index 0194072b25..253cbe5c32 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java @@ -22,7 +22,6 @@ import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -334,7 +333,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest { } @Test - @Disabled // H2 不支持 find_in_set 函数 public void testGetTenantByWebsite() { // mock 数据 TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setWebsites(singletonList("https://www.iocoder.cn")));