diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java index 08ac77e..d7284ea 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java @@ -24,8 +24,10 @@ public class TrialBalanceEntity { private String goodsName; /** 原始价格 */ private BigDecimal originalPrice; - /** 折扣价格 */ + /** 折扣金额 */ private BigDecimal deductionPrice; + /** 支付金额 */ + private BigDecimal payPrice; /** 拼团目标数量 */ private Integer targetCount; /** 拼团开始时间 */ diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/factory/DefaultActivityStrategyFactory.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/factory/DefaultActivityStrategyFactory.java index 49ab825..e44e3d9 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/factory/DefaultActivityStrategyFactory.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/factory/DefaultActivityStrategyFactory.java @@ -32,8 +32,10 @@ public class DefaultActivityStrategyFactory { private GroupBuyActivityDiscountVO groupBuyActivityDiscountVO; // 商品信息 private SkuVO skuVO; - // 折扣价格 + // 折扣金额 private BigDecimal deductionPrice; + // 支付金额 + private BigDecimal payPrice; // 活动可见性限制 private boolean visible; // 活动 diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java index b18784a..d421d08 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java @@ -27,6 +27,9 @@ public class EndNode extends AbstractGroupBuyMarketSupport tradeRuleFilter(ActivityUsabilityRuleFilter activityUsabilityRuleFilter, UserTakeLimitRuleFilter userTakeLimitRuleFilter) { + // 1. 组装链 + LinkArmory linkArmory = + new LinkArmory<>("交易规则过滤链", activityUsabilityRuleFilter, userTakeLimitRuleFilter); + + // 2. 返回链容器(即可作为责任链使用) + return linkArmory.getLogicLink(); + } + + /** + * 动态上下文:在链路中共享数据,避免重复查询。 + */ + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class DynamicContext { + /** 拼团活动信息,供后续节点复用 */ + private GroupBuyActivityEntity groupBuyActivity; + + } + +} diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/filter/ActivityUsabilityRuleFilter.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/filter/ActivityUsabilityRuleFilter.java new file mode 100644 index 0000000..f8d7ee5 --- /dev/null +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/filter/ActivityUsabilityRuleFilter.java @@ -0,0 +1,55 @@ +package edu.whut.domain.trade.service.filter; +import edu.whut.domain.trade.adapter.repository.ITradeRepository; +import edu.whut.domain.trade.model.entity.GroupBuyActivityEntity; +import edu.whut.domain.trade.model.entity.TradeRuleCommandEntity; +import edu.whut.domain.trade.model.entity.TradeRuleFilterBackEntity; +import edu.whut.domain.trade.service.factory.TradeRuleFilterFactory; +import edu.whut.types.design.framework.link.model2.handler.ILogicHandler; +import edu.whut.types.enums.ActivityStatusEnumVO; +import edu.whut.types.enums.ResponseCode; +import edu.whut.types.exception.AppException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +/** + * 活动的可用性,规则过滤【状态、有效期】 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ActivityUsabilityRuleFilter implements ILogicHandler { + + private final ITradeRepository repository; + + @Override + public TradeRuleFilterBackEntity apply(TradeRuleCommandEntity requestParameter, TradeRuleFilterFactory.DynamicContext dynamicContext) throws Exception { + log.info("交易规则过滤-活动的可用性校验{} activityId:{}", requestParameter.getUserId(), requestParameter.getActivityId()); + + // 查询拼团活动 + GroupBuyActivityEntity groupBuyActivity = repository.queryGroupBuyActivityEntityByActivityId(requestParameter.getActivityId()); + + // 校验;活动状态 - 可以抛业务异常code,或者把code写入到动态上下文dynamicContext中,最后获取。 + if (!ActivityStatusEnumVO.EFFECTIVE.equals(groupBuyActivity.getStatus())) { + log.info("活动的可用性校验,非生效状态 activityId:{}", requestParameter.getActivityId()); + throw new AppException(ResponseCode.E0101); + } + + // 校验;活动时间 + Date currentTime = new Date(); + if (currentTime.before(groupBuyActivity.getStartTime()) || currentTime.after(groupBuyActivity.getEndTime())) { + log.info("活动的可用性校验,非可参与时间范围 activityId:{}", requestParameter.getActivityId()); + throw new AppException(ResponseCode.E0102); + } + + // 写入动态上下文 + dynamicContext.setGroupBuyActivity(groupBuyActivity); + + // 走到下一个责任链节点 + return null; + } + +} diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/filter/UserTakeLimitRuleFilter.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/filter/UserTakeLimitRuleFilter.java new file mode 100644 index 0000000..423abf2 --- /dev/null +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/filter/UserTakeLimitRuleFilter.java @@ -0,0 +1,44 @@ +package edu.whut.domain.trade.service.filter; +import edu.whut.domain.trade.adapter.repository.ITradeRepository; +import edu.whut.domain.trade.model.entity.GroupBuyActivityEntity; +import edu.whut.domain.trade.model.entity.TradeRuleCommandEntity; +import edu.whut.domain.trade.model.entity.TradeRuleFilterBackEntity; +import edu.whut.domain.trade.service.factory.TradeRuleFilterFactory; +import edu.whut.types.design.framework.link.model2.handler.ILogicHandler; +import edu.whut.types.enums.ResponseCode; +import edu.whut.types.exception.AppException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 用户参与限制,规则过滤 + */ +@Slf4j +@Service +public class UserTakeLimitRuleFilter implements ILogicHandler { + + @Resource + private ITradeRepository repository; + + @Override + public TradeRuleFilterBackEntity apply(TradeRuleCommandEntity requestParameter, TradeRuleFilterFactory.DynamicContext dynamicContext) throws Exception { + log.info("交易规则过滤-用户参与次数校验{} activityId:{}", requestParameter.getUserId(), requestParameter.getActivityId()); + + GroupBuyActivityEntity groupBuyActivity = dynamicContext.getGroupBuyActivity(); + + // 查询用户在一个拼团活动上参与的次数 + Integer count = repository.queryOrderCountByActivityId(requestParameter.getActivityId(), requestParameter.getUserId()); + + if (null != groupBuyActivity.getTakeLimitCount() && count >= groupBuyActivity.getTakeLimitCount()) { + log.info("用户参与次数校验,已达可参与上限 activityId:{}", requestParameter.getActivityId()); + throw new AppException(ResponseCode.E0103); + } + + return TradeRuleFilterBackEntity.builder() + .userTakeOrderCount(count) + .build(); + } + +} diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java index e0f7357..d99692f 100644 --- a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java @@ -2,16 +2,17 @@ package edu.whut.infrastructure.adapter.repository; import edu.whut.domain.trade.adapter.repository.ITradeRepository; import edu.whut.domain.trade.model.aggregate.GroupBuyOrderAggregate; -import edu.whut.domain.trade.model.entity.MarketPayOrderEntity; -import edu.whut.domain.trade.model.entity.PayActivityEntity; -import edu.whut.domain.trade.model.entity.PayDiscountEntity; -import edu.whut.domain.trade.model.entity.UserEntity; +import edu.whut.domain.trade.model.entity.*; import edu.whut.domain.trade.model.valobj.GroupBuyProgressVO; import edu.whut.domain.trade.model.valobj.TradeOrderStatusEnumVO; +import edu.whut.infrastructure.dao.IGroupBuyActivityDao; import edu.whut.infrastructure.dao.IGroupBuyOrderDao; import edu.whut.infrastructure.dao.IGroupBuyOrderListDao; +import edu.whut.infrastructure.dao.po.GroupBuyActivity; import edu.whut.infrastructure.dao.po.GroupBuyOrder; import edu.whut.infrastructure.dao.po.GroupBuyOrderList; +import edu.whut.types.common.Constants; +import edu.whut.types.enums.ActivityStatusEnumVO; import edu.whut.types.enums.ResponseCode; import edu.whut.types.exception.AppException; import lombok.RequiredArgsConstructor; @@ -22,7 +23,6 @@ import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; /** * 仓储实现:负责把领域对象 <-> 数据库 PO 的转换与持久化; @@ -37,6 +37,8 @@ public class TradeRepository implements ITradeRepository { private final IGroupBuyOrderListDao groupBuyOrderListDao; + private final IGroupBuyActivityDao groupBuyActivityDao; + /** * 根据外部交易号 & 用户id 查询未支付的锁单记录(用于幂等) */ @@ -74,13 +76,17 @@ public class TradeRepository implements ITradeRepository { UserEntity user = agg.getUserEntity(); PayActivityEntity activity = agg.getPayActivityEntity(); PayDiscountEntity discount = agg.getPayDiscountEntity(); + Integer userTakeOrderCount = agg.getUserTakeOrderCount(); + /* ---------- 1. 处理 group_buy_order(团单主表) ---------- */ String teamId = activity.getTeamId(); + //自己发起拼团 if (StringUtils.isBlank(teamId)) { // 新建团队,随机 8 位数字作 teamId(示例中用 RandomStringUtils,线上可换雪花算法等) teamId = RandomStringUtils.randomNumeric(8); + // 构建拼团订单 GroupBuyOrder orderPo = GroupBuyOrder.builder() .teamId(teamId) .activityId(activity.getActivityId()) @@ -88,7 +94,7 @@ public class TradeRepository implements ITradeRepository { .channel(discount.getChannel()) .originalPrice(discount.getOriginalPrice()) .deductionPrice(discount.getDeductionPrice()) - .payPrice(discount.getDeductionPrice()) // 直减后应付价格 + .payPrice(discount.getOriginalPrice().subtract(discount.getDeductionPrice())) .targetCount(activity.getTargetCount()) .completeCount(0) .lockCount(1) // 首单已锁定 @@ -121,6 +127,8 @@ public class TradeRepository implements ITradeRepository { .deductionPrice(discount.getDeductionPrice()) .status(TradeOrderStatusEnumVO.CREATE.getCode()) // 0 = 初始锁定 .outTradeNo(discount.getOutTradeNo()) + // 构建 bizId 唯一值;活动id_用户id_参与次数累加 + .bizId(activity.getActivityId() + Constants.UNDERLINE + user.getUserId() + Constants.UNDERLINE + (userTakeOrderCount + 1)) .build(); try { @@ -153,4 +161,31 @@ public class TradeRepository implements ITradeRepository { .lockCount(po.getLockCount()) .build(); } + + @Override + public GroupBuyActivityEntity queryGroupBuyActivityEntityByActivityId(Long activityId) { + GroupBuyActivity groupBuyActivity = groupBuyActivityDao.queryGroupBuyActivityByActivityId(activityId); + return GroupBuyActivityEntity.builder() + .activityId(groupBuyActivity.getActivityId()) + .activityName(groupBuyActivity.getActivityName()) + .discountId(groupBuyActivity.getDiscountId()) + .groupType(groupBuyActivity.getGroupType()) + .takeLimitCount(groupBuyActivity.getTakeLimitCount()) + .target(groupBuyActivity.getTarget()) + .validTime(groupBuyActivity.getValidTime()) + .status(ActivityStatusEnumVO.valueOf(groupBuyActivity.getStatus())) + .startTime(groupBuyActivity.getStartTime()) + .endTime(groupBuyActivity.getEndTime()) + .tagId(groupBuyActivity.getTagId()) + .tagScope(groupBuyActivity.getTagScope()) + .build(); + } + + @Override + public Integer queryOrderCountByActivityId(Long activityId, String userId) { + GroupBuyOrderList groupBuyOrderListReq = new GroupBuyOrderList(); + groupBuyOrderListReq.setActivityId(activityId); + groupBuyOrderListReq.setUserId(userId); + return groupBuyOrderListDao.queryOrderCountByActivityId(groupBuyOrderListReq); + } } diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/common/Constants.java b/group-buying-sys-types/src/main/java/edu/whut/types/common/Constants.java index 381d140..885acb8 100644 --- a/group-buying-sys-types/src/main/java/edu/whut/types/common/Constants.java +++ b/group-buying-sys-types/src/main/java/edu/whut/types/common/Constants.java @@ -3,5 +3,6 @@ package edu.whut.types.common; public class Constants { public final static String SPLIT = ","; + public final static String UNDERLINE = "_"; } diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/LinkArmory.java b/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/LinkArmory.java index 86af4db..e1f6bd9 100644 --- a/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/LinkArmory.java +++ b/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/LinkArmory.java @@ -4,12 +4,16 @@ import edu.whut.types.design.framework.link.model2.chain.BusinessLinkedList; import edu.whut.types.design.framework.link.model2.handler.ILogicHandler; /** - * 链路装配 + * 链路装配工厂 —— 负责把一组 ILogicHandler 顺序注册到 BusinessLinkedList 中。 */ public class LinkArmory { private final BusinessLinkedList logicLink; + /** + * @param linkName 链路名称,便于日志排查 + * @param logicHandlers 节点列表,按传入顺序链接 + */ @SafeVarargs public LinkArmory(String linkName, ILogicHandler... logicHandlers) { logicLink = new BusinessLinkedList<>(linkName); @@ -18,6 +22,7 @@ public class LinkArmory { } } + /** 返回组装完成的链路 */ public BusinessLinkedList getLogicLink() { return logicLink; } diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/chain/BusinessLinkedList.java b/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/chain/BusinessLinkedList.java index 4256d65..d51ad22 100644 --- a/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/chain/BusinessLinkedList.java +++ b/group-buying-sys-types/src/main/java/edu/whut/types/design/framework/link/model2/chain/BusinessLinkedList.java @@ -4,7 +4,11 @@ package edu.whut.types.design.framework.link.model2.chain; import edu.whut.types.design.framework.link.model2.handler.ILogicHandler; /** - * 业务链路 + * 业务链路容器 —— 双向链表实现,同时实现 ILogicHandler,从而可以被当作单个节点使用。 + * 责任链特点: + * 链表插入、删除复杂度 O(1),运行期可动态调整节点。 + * 循环遍历,不使用递归,避免深链路导致的栈溢出风险。 + * 当某个节点返回非 null 时立即短路结束。 */ public class BusinessLinkedList extends LinkedList> implements ILogicHandler{ @@ -12,18 +16,25 @@ public class BusinessLinkedList extends LinkedList> current = this.first; - do { - ILogicHandler item = current.item; - R apply = item.apply(requestParameter, dynamicContext); - if (null != apply) return apply; - + // 顺序执行,直到链尾或返回结果 + while (current != null) { + ILogicHandler handler = current.item; + R result = handler.apply(requestParameter, dynamicContext); + if (result != null) { + // 节点命中,立即返回 + return result; + } + //result==null,则交给那一节点继续处理 current = current.next; - } while (null != current); - + } + // 全链未命中 return null; } - } diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/enums/ActivityStatusEnumVO.java b/group-buying-sys-types/src/main/java/edu/whut/types/enums/ActivityStatusEnumVO.java new file mode 100644 index 0000000..981c240 --- /dev/null +++ b/group-buying-sys-types/src/main/java/edu/whut/types/enums/ActivityStatusEnumVO.java @@ -0,0 +1,38 @@ +package edu.whut.types.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * 拼团活动状态枚举 + */ +@Getter +@AllArgsConstructor +@NoArgsConstructor +public enum ActivityStatusEnumVO { + + CREATE(0, "创建"), + EFFECTIVE(1, "生效"), + OVERDUE(2, "过期"), + ABANDONED(3, "废弃"), + ; + + private Integer code; + private String info; + + public static ActivityStatusEnumVO valueOf(Integer code) { + switch (code) { + case 0: + return CREATE; + case 1: + return EFFECTIVE; + case 2: + return OVERDUE; + case 3: + return ABANDONED; + } + throw new RuntimeException("err code not exist!"); + } + +} diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java b/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java index 50b2282..ddb29a1 100644 --- a/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java +++ b/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java @@ -43,7 +43,7 @@ public class AppException extends RuntimeException { @Override public String toString() { - return "edu.whut.x.api.types.exception.XApiException{" + + return "edu.whut.types.exception.AppException{" + "code='" + code + '\'' + ", info='" + info + '\'' + '}';