mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-10-06 06:12:58 +08:00
fix: 修复 findInSet SQL 注入风险
- 调整 MyBatisUtils.findInSet 使用 MyBatis-Plus 参数绑定 - 增加 columnName 白名单校验,避免列名被注入 - 补充 H2 兼容实现,恢复相关单测覆盖 - 替换各模块动态 FIND_IN_SET 字符串拼接写法 - 补充单参数、多参数绑定场景单测
This commit is contained in:
+12
-12
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
+37
-5
@@ -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 <T> Page<T> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+67
@@ -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<Object> 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<Object> 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());
|
||||
|
||||
+3
-2
@@ -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<CrmCustomerLim
|
||||
LambdaQueryWrapperX<CrmCustomerLimitConfigDO> query = new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
|
||||
.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);
|
||||
|
||||
+2
-1
@@ -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<ImChannelMessageDO>
|
||||
.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));
|
||||
}
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ public interface IotAlertConfigMapper extends BaseMapperX<IotAlertConfigDO> {
|
||||
default List<IotAlertConfigDO> selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
|
||||
return selectList(new LambdaQueryWrapperX<IotAlertConfigDO>()
|
||||
.eq(IotAlertConfigDO::getStatus, status)
|
||||
.apply(MyBatisUtils.findInSet("scene_rule_ids", sceneRuleId)));
|
||||
.apply(MyBatisUtils.findInSet("scene_rule_ids"), sceneRuleId));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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<IotDeviceDO> {
|
||||
.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<IotDeviceDO> {
|
||||
|
||||
default Long selectCountByGroupId(Long groupId) {
|
||||
return selectCount(new LambdaQueryWrapperX<IotDeviceDO>()
|
||||
.apply("FIND_IN_SET(" + groupId + ",group_ids) > 0"));
|
||||
.apply(MyBatisUtils.findInSet("group_ids"), groupId));
|
||||
}
|
||||
|
||||
default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) {
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ public interface IotDataRuleMapper extends BaseMapperX<IotDataRuleDO> {
|
||||
|
||||
default List<IotDataRuleDO> selectListBySinkId(Long sinkId) {
|
||||
return selectList(new LambdaQueryWrapperX<IotDataRuleDO>()
|
||||
.apply(MyBatisUtils.findInSet("sink_ids", sinkId)));
|
||||
.apply(MyBatisUtils.findInSet("sink_ids"), sinkId));
|
||||
}
|
||||
|
||||
default List<IotDataRuleDO> selectListByStatus(Integer status) {
|
||||
@@ -39,4 +39,4 @@ public interface IotDataRuleMapper extends BaseMapperX<IotDataRuleDO> {
|
||||
return selectOne(IotDataRuleDO::getName, name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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<CouponTemplateDO> {
|
||||
.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<CouponTemplateDO> {
|
||||
Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes);
|
||||
return selectList(new LambdaQueryWrapperX<CouponTemplateDO>()
|
||||
.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)
|
||||
|
||||
+6
-5
@@ -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<RewardActivityDO> {
|
||||
Collection<Long> categoryIds,
|
||||
Integer status) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
|
||||
.map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
|
||||
Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> IntStream.range(0, ids.size())
|
||||
.mapToObj(index -> MyBatisUtils.findInSetWithParamIndex("product_scope_values", index))
|
||||
.collect(Collectors.joining(" OR "));
|
||||
return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
|
||||
.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)
|
||||
);
|
||||
}
|
||||
|
||||
+3
-2
@@ -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<SeckillActivityDO> {
|
||||
.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<SeckillActivityDO> {
|
||||
.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) {
|
||||
|
||||
+8
-5
@@ -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<MemberUserDO> {
|
||||
default PageResult<MemberUserDO> 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<MemberUserDO>()
|
||||
@@ -52,7 +55,7 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
|
||||
.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<MemberUserDO> {
|
||||
|
||||
default Long selectCountByTagId(Long tagId) {
|
||||
return selectCount(new LambdaQueryWrapperX<MemberUserDO>()
|
||||
.apply("FIND_IN_SET({0}, tag_ids)", tagId));
|
||||
.apply(MyBatisUtils.findInSet("tag_ids"), tagId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ public interface MesQcTemplateMapper extends BaseMapperX<MesQcTemplateDO> {
|
||||
.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);
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ public interface MailLogMapper extends BaseMapperX<MailLogDO> {
|
||||
.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));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ public interface TenantMapper extends BaseMapperX<TenantDO> {
|
||||
|
||||
default List<TenantDO> selectListByWebsite(String website) {
|
||||
return selectList(new LambdaQueryWrapperX<TenantDO>()
|
||||
.apply(MyBatisUtils.findInSet("websites", website)));
|
||||
.apply(MyBatisUtils.findInSet("websites"), website));
|
||||
}
|
||||
|
||||
default Long selectCountByPackageId(Long packageId) {
|
||||
|
||||
+3
-3
@@ -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());
|
||||
|
||||
-2
@@ -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")));
|
||||
|
||||
Reference in New Issue
Block a user