You've already forked FrameTour-BE
refactor(order): 重构重复购买检查和定价逻辑
- 引入商品类型能力配置,替代硬编码的商品类型判断 - 实现策略模式处理不同商品类型的重复购买检查 - 抽象定价模式,支持固定价格和数量计价等不同方式 - 新增策略工厂自动注册各类检查器实现 - 添加缓存机制提升商品类型配置查询性能 - 解耦订单服务与具体商品类型的紧耦合关系 - 提高代码可维护性和扩展性,便于新增商品类型
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package com.ycwl.basic.order.strategy;
|
||||
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 重复购买检查上下文
|
||||
* 封装检查所需的所有参数
|
||||
*/
|
||||
@Data
|
||||
public class DuplicateCheckContext {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 景区ID
|
||||
*/
|
||||
private String scenicId;
|
||||
|
||||
/**
|
||||
* 商品类型代码
|
||||
*/
|
||||
private String productType;
|
||||
|
||||
/**
|
||||
* 商品ID(视频ID、套餐ID等)
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 当前购物车中的所有商品
|
||||
*/
|
||||
private List<ProductItem> products;
|
||||
|
||||
/**
|
||||
* 额外参数(扩展用)
|
||||
*/
|
||||
private Map<String, Object> additionalParams;
|
||||
|
||||
public DuplicateCheckContext() {
|
||||
this.additionalParams = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加额外参数
|
||||
*/
|
||||
public void addParam(String key, Object value) {
|
||||
this.additionalParams.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取额外参数
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getParam(String key) {
|
||||
return (T) this.additionalParams.get(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ycwl.basic.order.strategy;
|
||||
|
||||
import com.ycwl.basic.order.exception.DuplicatePurchaseException;
|
||||
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
||||
|
||||
/**
|
||||
* 重复购买检查策略接口
|
||||
*
|
||||
* 设计原则:
|
||||
* 1. 单一职责: 每个策略只负责一种检查逻辑
|
||||
* 2. 开放扩展: 通过实现接口添加新策略
|
||||
* 3. 自动注册: Spring 自动扫描并注册所有实现类
|
||||
*/
|
||||
public interface IDuplicatePurchaseChecker {
|
||||
|
||||
/**
|
||||
* 获取策略类型
|
||||
* 用于策略工厂的注册和查找
|
||||
*
|
||||
* @return 对应的 DuplicateCheckStrategy 枚举值
|
||||
*/
|
||||
DuplicateCheckStrategy getStrategyType();
|
||||
|
||||
/**
|
||||
* 执行重复购买检查
|
||||
*
|
||||
* @param context 检查上下文,包含用户ID、商品信息等
|
||||
* @throws DuplicatePurchaseException 如果检测到重复购买
|
||||
*/
|
||||
void check(DuplicateCheckContext context) throws DuplicatePurchaseException;
|
||||
|
||||
/**
|
||||
* 是否支持该商品类型
|
||||
* 默认实现:所有策略都支持所有商品类型
|
||||
* 可在具体实现中覆盖以限制适用范围
|
||||
*
|
||||
* @param productType 商品类型代码
|
||||
* @return true-支持, false-不支持
|
||||
*/
|
||||
default boolean supports(String productType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ycwl.basic.order.strategy.impl;
|
||||
|
||||
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
|
||||
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
|
||||
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 不检查重复购买策略
|
||||
* 适用于:打印类商品等允许重复购买的商品类型
|
||||
*
|
||||
* 检查逻辑:
|
||||
* 不执行任何检查,直接通过
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NoCheckDuplicateChecker implements IDuplicatePurchaseChecker {
|
||||
|
||||
@Override
|
||||
public DuplicateCheckStrategy getStrategyType() {
|
||||
return DuplicateCheckStrategy.NO_CHECK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(DuplicateCheckContext context) {
|
||||
// 不检查,直接通过
|
||||
log.debug("跳过重复购买检查: productType={}, productId={}",
|
||||
context.getProductType(), context.getProductId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.ycwl.basic.order.strategy.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.ycwl.basic.order.entity.OrderItemV2;
|
||||
import com.ycwl.basic.order.entity.OrderV2;
|
||||
import com.ycwl.basic.order.enums.OrderStatus;
|
||||
import com.ycwl.basic.order.enums.PaymentStatus;
|
||||
import com.ycwl.basic.order.exception.DuplicatePurchaseException;
|
||||
import com.ycwl.basic.order.mapper.OrderItemMapper;
|
||||
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
||||
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
|
||||
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
|
||||
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 按套餐ID检查重复购买策略
|
||||
* 适用于:RECORDING_SET(录像集)、PHOTO_SET(照相集)商品类型
|
||||
*
|
||||
* 检查逻辑:
|
||||
* 1. 查找用户在该景区已支付的有效订单
|
||||
* 2. 检查订单明细中是否包含相同类型的套餐
|
||||
* 3. 如果存在,抛出重复购买异常
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
||||
|
||||
@Autowired
|
||||
private OrderV2Mapper orderV2Mapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Override
|
||||
public DuplicateCheckStrategy getStrategyType() {
|
||||
return DuplicateCheckStrategy.CHECK_BY_SET_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(DuplicateCheckContext context) throws DuplicatePurchaseException {
|
||||
String userId = context.getUserId();
|
||||
String scenicId = context.getScenicId();
|
||||
String productType = context.getProductType();
|
||||
|
||||
// 获取人脸ID(从扩展参数中)
|
||||
Long faceId = context.getParam("faceId");
|
||||
|
||||
log.debug("执行套餐ID重复购买检查: userId={}, faceId={}, scenicId={}, productType={}",
|
||||
userId, faceId, scenicId, productType);
|
||||
|
||||
// 构建查询条件:查找已支付的有效订单
|
||||
QueryWrapper<OrderV2> orderQuery = new QueryWrapper<>();
|
||||
orderQuery.eq("member_id", userId)
|
||||
.eq("scenic_id", scenicId)
|
||||
.eq("payment_status", PaymentStatus.PAID.getCode())
|
||||
.in("order_status",
|
||||
OrderStatus.PAID.getCode(),
|
||||
OrderStatus.PROCESSING.getCode(),
|
||||
OrderStatus.COMPLETED.getCode())
|
||||
.eq("deleted", 0);
|
||||
|
||||
// 如果提供了人脸ID,也作为查询条件
|
||||
if (faceId != null) {
|
||||
orderQuery.eq("face_id", faceId);
|
||||
}
|
||||
|
||||
List<OrderV2> existingOrders = orderV2Mapper.selectList(orderQuery);
|
||||
|
||||
for (OrderV2 order : existingOrders) {
|
||||
// 检查订单明细中是否包含该类型的套餐
|
||||
QueryWrapper<OrderItemV2> itemQuery = new QueryWrapper<>();
|
||||
itemQuery.eq("order_id", order.getId())
|
||||
.eq("product_type", productType);
|
||||
|
||||
long count = orderItemMapper.selectCount(itemQuery);
|
||||
if (count > 0) {
|
||||
log.warn("检测到重复购买套餐: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}",
|
||||
userId, faceId, scenicId, productType, order.getId());
|
||||
|
||||
// 为了兼容旧代码,需要转换为 ProductType 枚举
|
||||
com.ycwl.basic.pricing.enums.ProductType productTypeEnum =
|
||||
com.ycwl.basic.pricing.enums.ProductType.fromCode(productType);
|
||||
|
||||
throw new DuplicatePurchaseException(
|
||||
"您已购买过此类型的套餐",
|
||||
order.getId(),
|
||||
order.getOrderNo(),
|
||||
productTypeEnum
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("套餐重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}",
|
||||
userId, faceId, scenicId, productType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.ycwl.basic.order.strategy.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.ycwl.basic.order.entity.OrderItemV2;
|
||||
import com.ycwl.basic.order.entity.OrderV2;
|
||||
import com.ycwl.basic.order.enums.OrderStatus;
|
||||
import com.ycwl.basic.order.enums.PaymentStatus;
|
||||
import com.ycwl.basic.order.exception.DuplicatePurchaseException;
|
||||
import com.ycwl.basic.order.mapper.OrderItemMapper;
|
||||
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
||||
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
|
||||
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
|
||||
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 按视频ID检查重复购买策略
|
||||
* 适用于:VLOG_VIDEO 商品类型
|
||||
*
|
||||
* 检查逻辑:
|
||||
* 1. 查找用户在该景区已支付的有效订单
|
||||
* 2. 检查订单明细中是否包含相同的视频ID
|
||||
* 3. 如果存在,抛出重复购买异常
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
||||
|
||||
@Autowired
|
||||
private OrderV2Mapper orderV2Mapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Override
|
||||
public DuplicateCheckStrategy getStrategyType() {
|
||||
return DuplicateCheckStrategy.CHECK_BY_VIDEO_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(DuplicateCheckContext context) throws DuplicatePurchaseException {
|
||||
String userId = context.getUserId();
|
||||
String scenicId = context.getScenicId();
|
||||
String productId = context.getProductId();
|
||||
|
||||
// 获取人脸ID(从扩展参数中)
|
||||
Long faceId = context.getParam("faceId");
|
||||
|
||||
log.debug("执行视频ID重复购买检查: userId={}, faceId={}, scenicId={}, videoId={}",
|
||||
userId, faceId, scenicId, productId);
|
||||
|
||||
// 构建查询条件:查找已支付的有效订单
|
||||
QueryWrapper<OrderV2> orderQuery = new QueryWrapper<>();
|
||||
orderQuery.eq("member_id", userId)
|
||||
.eq("scenic_id", scenicId)
|
||||
.eq("payment_status", PaymentStatus.PAID.getCode())
|
||||
.in("order_status",
|
||||
OrderStatus.PAID.getCode(),
|
||||
OrderStatus.PROCESSING.getCode(),
|
||||
OrderStatus.COMPLETED.getCode())
|
||||
.eq("deleted", 0);
|
||||
|
||||
// 如果提供了人脸ID,也作为查询条件
|
||||
if (faceId != null) {
|
||||
orderQuery.eq("face_id", faceId);
|
||||
}
|
||||
|
||||
List<OrderV2> existingOrders = orderV2Mapper.selectList(orderQuery);
|
||||
|
||||
for (OrderV2 order : existingOrders) {
|
||||
// 检查订单明细中是否包含该视频
|
||||
QueryWrapper<OrderItemV2> itemQuery = new QueryWrapper<>();
|
||||
itemQuery.eq("order_id", order.getId())
|
||||
.eq("product_type", "VLOG_VIDEO")
|
||||
.eq("product_id", productId);
|
||||
|
||||
long count = orderItemMapper.selectCount(itemQuery);
|
||||
if (count > 0) {
|
||||
log.warn("检测到重复购买视频: userId={}, faceId={}, scenicId={}, videoId={}, existingOrderId={}",
|
||||
userId, faceId, scenicId, productId, order.getId());
|
||||
|
||||
// 为了兼容旧代码,需要转换为 ProductType 枚举
|
||||
throw new DuplicatePurchaseException(
|
||||
"您已购买过此视频",
|
||||
order.getId(),
|
||||
order.getOrderNo(),
|
||||
com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO,
|
||||
productId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("视频重复购买检查通过: userId={}, faceId={}, scenicId={}, videoId={}",
|
||||
userId, faceId, scenicId, productId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user