diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java index ef50157..ddf18d3 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.time.Instant; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.regex.Pattern; /** * LocalDateTime 反序列化器,支持毫秒时间戳、常见日期时间字符串和 ISO-8601 日期。 @@ -21,6 +22,8 @@ public class TimestampLocalDateTimeDeserializer extends JsonDeserializer parse("\"not-a-date\"")); - } } diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index 6276194..3d8e179 100644 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -216,13 +216,16 @@ public class CouponServiceImpl implements CouponService { public void takeCouponByRegister(Long userId) { List templates = couponTemplateService.getCouponTemplateListByTakeType(CouponTakeTypeEnum.REGISTER); // 1. 过滤不可领取(如已过期)的模板,避免单张异常中断整批 - List takeableTemplates = templates.stream() - .filter(this::isCouponTemplateNotExpired) - .collect(Collectors.toList()); - if (takeableTemplates.size() < templates.size()) { - log.info("[takeCouponByRegister][用户({}) 过滤 {} 张不可领取的模板]", userId, templates.size() - takeableTemplates.size()); - } - // 2. 逐个发放,单张失败不影响其余 + List takeableTemplates = filterList(templates, template -> { + boolean expired = CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType()) + && LocalDateTimeUtils.beforeNow(template.getValidEndTime()); + if (expired) { + log.warn("[takeCouponByRegister][用户({}) 模板({}) 已过期,跳过发放]", userId, template.getId()); + } + return !expired; + }); + + // 2.1 逐个发放,单张失败不影响其余 int successCount = 0; for (CouponTemplateDO template : takeableTemplates) { try { @@ -232,17 +235,12 @@ public class CouponServiceImpl implements CouponService { log.error("[takeCouponByRegister][用户({}) 模板({}) 发放失败]", userId, template.getId(), e); } } - // 3. 应发全失败时留一条 ERROR 标记,供监控告警 + // 2.2 应发全失败时留一条 ERROR 标记,供监控告警 if (CollUtil.isNotEmpty(takeableTemplates) && successCount == 0) { log.error("[takeCouponByRegister][用户({}) 全部 {} 张新人券发放失败]", userId, takeableTemplates.size()); } } - private boolean isCouponTemplateNotExpired(CouponTemplateDO template) { - return !CouponTemplateValidityTypeEnum.DATE.getType().equals(template.getValidityType()) - || !LocalDateTimeUtils.beforeNow(template.getValidEndTime()); - } - @Override public int expireCoupon() { // 1. 查询待过期的优惠券 diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/autocode/MesMdAutoCodePartMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/autocode/MesMdAutoCodePartMapper.java index ec9107d..950f2b3 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/autocode/MesMdAutoCodePartMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/autocode/MesMdAutoCodePartMapper.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.dal.mysql.md.autocode; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO; -import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum; import org.apache.ibatis.annotations.Mapper; import java.util.List; @@ -22,8 +21,8 @@ public interface MesMdAutoCodePartMapper extends BaseMapperX selectFixedCharPartList() { - return selectList(MesMdAutoCodePartDO::getType, MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType()); + default List selectByType(Integer type) { + return selectList(MesMdAutoCodePartDO::getType, type); } default void deleteByRuleId(Long ruleId) { diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImpl.java index bf5388d..0a20c4a 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImpl.java @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part.MesMdAut import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO; import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodePartMapper; -import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodeRuleMapper; import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -21,6 +20,7 @@ import java.util.Map; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_FIXED_CHAR_DUPLICATE; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_NOT_EXISTS; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.AUTO_CODE_PART_SERIAL_NUMBER_DUPLICATE; @@ -36,10 +36,6 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService { @Resource private MesMdAutoCodePartMapper partMapper; - - @Resource - private MesMdAutoCodeRuleMapper ruleMapper; - @Resource private MesMdAutoCodeRuleService ruleService; @@ -125,10 +121,10 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService { * 从源头避免不同规则生成相同编码;生成编码时的全局查重仍保留,兜底存量配置的冲突。 */ private void validateFixedCharUnique(Long id, MesMdAutoCodePartSaveReqVO reqVO) { - // 1. 全租户固定字符分段,按规则分组 - Map> fixedPartsByRuleId = partMapper.selectFixedCharPartList().stream() - .collect(Collectors.groupingBy(MesMdAutoCodePartDO::getRuleId)); - // 2. 本次保存后,本规则的前缀指纹(排除正在修改的分段,加入本次保存的分段) + // 1.1 所有固定字符分段,按规则分组 + Map> fixedPartsByRuleId = convertMultiMap( + partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType()), MesMdAutoCodePartDO::getRuleId); + // 1.2 本次保存后,本规则的前缀指纹(排除正在修改的分段,加入本次保存的分段) List myFixedParts = new ArrayList<>( fixedPartsByRuleId.getOrDefault(reqVO.getRuleId(), Collections.emptyList())); myFixedParts.removeIf(part -> ObjUtil.equal(id, part.getId())); @@ -136,20 +132,23 @@ public class MesMdAutoCodePartServiceImpl implements MesMdAutoCodePartService { myFixedParts.add(BeanUtils.toBean(reqVO, MesMdAutoCodePartDO.class)); } String myFingerprint = buildFixedCharFingerprint(myFixedParts); - // 3. 与租户内其他未删除规则的前缀指纹逐一比对 - for (MesMdAutoCodeRuleDO rule : ruleMapper.selectList()) { + + // 2. 与租户内其他未删除规则的前缀指纹逐一比对 + List rules = ruleService.getAutoCodeRuleList(); + for (MesMdAutoCodeRuleDO rule : rules) { if (ObjUtil.equal(rule.getId(), reqVO.getRuleId())) { continue; } List otherFixedParts = fixedPartsByRuleId.getOrDefault(rule.getId(), Collections.emptyList()); - if (myFingerprint.equals(buildFixedCharFingerprint(otherFixedParts))) { + String otherFingerprint = buildFixedCharFingerprint(otherFixedParts); + if (myFingerprint.equals(otherFingerprint)) { throw exception(AUTO_CODE_PART_FIXED_CHAR_DUPLICATE); } } } /** - * 构建前缀指纹:固定字符分段按分段排序,拼接截取到分段长度内的固定字符(空值按空串) + * 构建固定字符分段的前缀指纹:按分段排序,拼接截取到分段长度内的固定字符 */ private String buildFixedCharFingerprint(List fixedParts) { return fixedParts.stream() diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleService.java index c4aebf6..5434101 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleService.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleService.java @@ -46,6 +46,13 @@ public interface MesMdAutoCodeRuleService { MesMdAutoCodeRuleDO getAutoCodeRule(Long id); /** + * 获得编码规则列表 + * + * @return 编码规则列表 + */ + List getAutoCodeRuleList(); + + /** * 获得编码规则分页 * * @param pageReqVO 分页查询 diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleServiceImpl.java index 7dce4e5..e160cf0 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRuleServiceImpl.java @@ -73,6 +73,11 @@ public class MesMdAutoCodeRuleServiceImpl implements MesMdAutoCodeRuleService { } @Override + public List getAutoCodeRuleList() { + return ruleMapper.selectList(); + } + + @Override public PageResult getAutoCodeRulePage(MesMdAutoCodeRulePageReqVO pageReqVO) { return ruleMapper.selectPage(pageReqVO); } diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImplTest.java index 032d19a..e6418a1 100644 --- a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImplTest.java +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodePartServiceImplTest.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.md.autocode.vo.part.MesMdAut import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodePartDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.autocode.MesMdAutoCodeRuleDO; import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodePartMapper; -import cn.iocoder.yudao.module.mes.dal.mysql.md.autocode.MesMdAutoCodeRuleMapper; import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodePartTypeEnum; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -37,8 +36,6 @@ public class MesMdAutoCodePartServiceImplTest { @Mock private MesMdAutoCodePartMapper partMapper; @Mock - private MesMdAutoCodeRuleMapper ruleMapper; - @Mock private MesMdAutoCodeRuleService ruleService; private static MesMdAutoCodePartDO fixedPart(Long id, Long ruleId, Integer sort, String fixCharacter, Integer length) { @@ -61,9 +58,9 @@ public class MesMdAutoCodePartServiceImplTest { @Test public void testCreateAutoCodePart_fixedCharDuplicate() { // 准备:其他规则已占用相同前缀 "ITEM_" - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList( fixedPart(1L, 1L, 1, "ITEM_", 5))); - when(ruleMapper.selectList()).thenReturn(Arrays.asList( + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList( new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); // 断言:保存被拒,且未插入 @@ -76,9 +73,9 @@ public class MesMdAutoCodePartServiceImplTest { @Test public void testCreateAutoCodePart_fixedCharUnique() { // 准备:其他规则前缀 "ITEM_",本次保存 "SN_" - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList( fixedPart(1L, 1L, 1, "ITEM_", 5))); - when(ruleMapper.selectList()).thenReturn(Arrays.asList( + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList( new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); // 断言:保存成功 @@ -89,9 +86,9 @@ public class MesMdAutoCodePartServiceImplTest { @Test public void testCreateAutoCodePart_fixedCharTruncatedDuplicate() { // 准备:其他规则固定字符 "IT_X" 长度 2(生成时截断为 "IT") - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList( fixedPart(1L, 1L, 1, "IT_X", 2))); - when(ruleMapper.selectList()).thenReturn(Arrays.asList( + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList( new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); // 断言:本次保存 "IT" 截断后同为 "IT",保存被拒 @@ -104,8 +101,8 @@ public class MesMdAutoCodePartServiceImplTest { @Test public void testCreateAutoCodePart_bothRulesWithoutFixedChar() { // 准备:租户内另一规则无任何固定字符分段(前缀为空串),本次保存流水号分段(本规则也无固定字符) - when(partMapper.selectFixedCharPartList()).thenReturn(Collections.emptyList()); - when(ruleMapper.selectList()).thenReturn(Arrays.asList( + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Collections.emptyList()); + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList( new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); MesMdAutoCodePartSaveReqVO reqVO = new MesMdAutoCodePartSaveReqVO(); @@ -127,9 +124,9 @@ public class MesMdAutoCodePartServiceImplTest { // 准备:本规则已有固定字符 "OLD_",修改为与其他规则相同的 "ITEM_" MesMdAutoCodePartDO myPart = fixedPart(100L, 10L, 1, "OLD_", 4); when(partMapper.selectById(100L)).thenReturn(myPart); - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList( myPart, fixedPart(1L, 1L, 1, "ITEM_", 5))); - when(ruleMapper.selectList()).thenReturn(Arrays.asList( + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList( new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); // 断言:保存被拒,且未更新 @@ -144,9 +141,9 @@ public class MesMdAutoCodePartServiceImplTest { // 准备:本规则已有固定字符 "ITEM_"(与其他规则重复),修改为 "SN_" MesMdAutoCodePartDO myPart = fixedPart(100L, 10L, 1, "ITEM_", 5); when(partMapper.selectById(100L)).thenReturn(myPart); - when(partMapper.selectFixedCharPartList()).thenReturn(Arrays.asList( + when(partMapper.selectByType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType())).thenReturn(Arrays.asList( myPart, fixedPart(1L, 1L, 1, "WO_", 3))); - when(ruleMapper.selectList()).thenReturn(Arrays.asList( + when(ruleService.getAutoCodeRuleList()).thenReturn(Arrays.asList( new MesMdAutoCodeRuleDO().setId(1L), new MesMdAutoCodeRuleDO().setId(10L))); // 断言:修改后前缀不再重复,保存成功