You've already forked FrameTour-BE
refactor(order): 重构重复购买检查策略
- 移除SetIdDuplicateChecker和VideoIdDuplicateChecker两个具体策略类 - 更新DuplicateCheckStrategy枚举,将CHECK_BY_SET_ID和CHECK_BY_VIDEO_ID 替换为更通用的UNIQUE_RESOURCE和PARENT_RESOURCE策略 - 修改ProductTypeCapabilityManagementServiceImpl中的策略分配逻辑 - UNIQUE_RESOURCE适用于照片、视频等独立资源的重复购买检查 - PARENT_RESOURCE适用于套餐类商品的重复购买检查 - 打印类商品现在正确设置为允许重复购买且不检查 - 其他类别商品默认设置为不检查重复购买
This commit is contained in:
@@ -10,6 +10,7 @@ import com.ycwl.basic.order.mapper.OrderItemMapper;
|
|||||||
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
||||||
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
|
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
|
||||||
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
|
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
|
||||||
|
import com.ycwl.basic.pricing.enums.ProductType;
|
||||||
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -18,17 +19,19 @@ import org.springframework.stereotype.Component;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按套餐ID检查重复购买策略
|
* 父资源/套餐重复购买检查策略
|
||||||
* 适用于:RECORDING_SET(录像集)、PHOTO_SET(照相集)商品类型
|
* 适用于:套餐类商品(RECORDING_SET录像集、PHOTO_SET照片集等)
|
||||||
*
|
*
|
||||||
* 检查逻辑:
|
* 检查逻辑:
|
||||||
* 1. 查找用户在该景区已支付的有效订单
|
* 1. 查找用户在该景区已支付的有效订单
|
||||||
* 2. 检查订单明细中是否包含相同类型的套餐
|
* 2. 检查订单明细中是否包含相同product_type的商品(不关心具体productId)
|
||||||
* 3. 如果存在,抛出重复购买异常
|
* 3. 如果存在,抛出重复购买异常
|
||||||
|
*
|
||||||
|
* SQL查询: WHERE order_id = ? AND product_type = ?
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
public class ParentResourceDuplicateChecker implements IDuplicatePurchaseChecker {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private OrderV2Mapper orderV2Mapper;
|
private OrderV2Mapper orderV2Mapper;
|
||||||
@@ -38,19 +41,19 @@ public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplicateCheckStrategy getStrategyType() {
|
public DuplicateCheckStrategy getStrategyType() {
|
||||||
return DuplicateCheckStrategy.CHECK_BY_SET_ID;
|
return DuplicateCheckStrategy.PARENT_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void check(DuplicateCheckContext context) throws DuplicatePurchaseException {
|
public void check(DuplicateCheckContext context) throws DuplicatePurchaseException {
|
||||||
String userId = context.getUserId();
|
String userId = context.getUserId();
|
||||||
String scenicId = context.getScenicId();
|
String scenicId = context.getScenicId();
|
||||||
String productType = context.getProductType();
|
String productType = context.getProductType(); // 如"RECORDING_SET"或"PHOTO_SET"
|
||||||
|
|
||||||
// 获取人脸ID(从扩展参数中)
|
// 获取人脸ID(从扩展参数中)
|
||||||
Long faceId = context.getParam("faceId");
|
Long faceId = context.getParam("faceId");
|
||||||
|
|
||||||
log.debug("执行套餐ID重复购买检查: userId={}, faceId={}, scenicId={}, productType={}",
|
log.debug("执行父资源重复购买检查: userId={}, faceId={}, scenicId={}, productType={}",
|
||||||
userId, faceId, scenicId, productType);
|
userId, faceId, scenicId, productType);
|
||||||
|
|
||||||
// 构建查询条件:查找已支付的有效订单
|
// 构建查询条件:查找已支付的有效订单
|
||||||
@@ -72,22 +75,20 @@ public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
|||||||
List<OrderV2> existingOrders = orderV2Mapper.selectList(orderQuery);
|
List<OrderV2> existingOrders = orderV2Mapper.selectList(orderQuery);
|
||||||
|
|
||||||
for (OrderV2 order : existingOrders) {
|
for (OrderV2 order : existingOrders) {
|
||||||
// 检查订单明细中是否包含该类型的套餐
|
// 检查订单明细中是否包含该类型的商品(仅按productType匹配)
|
||||||
QueryWrapper<OrderItemV2> itemQuery = new QueryWrapper<>();
|
QueryWrapper<OrderItemV2> itemQuery = new QueryWrapper<>();
|
||||||
itemQuery.eq("order_id", order.getId())
|
itemQuery.eq("order_id", order.getId())
|
||||||
.eq("product_type", productType);
|
.eq("product_type", productType); // 仅按productType匹配
|
||||||
|
|
||||||
long count = orderItemMapper.selectCount(itemQuery);
|
long count = orderItemMapper.selectCount(itemQuery);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
log.warn("检测到重复购买套餐: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}",
|
log.warn("检测到重复购买父资源: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}",
|
||||||
userId, faceId, scenicId, productType, order.getId());
|
userId, faceId, scenicId, productType, order.getId());
|
||||||
|
|
||||||
// 为了兼容旧代码,需要转换为 ProductType 枚举
|
ProductType productTypeEnum = ProductType.fromCode(productType);
|
||||||
com.ycwl.basic.pricing.enums.ProductType productTypeEnum =
|
|
||||||
com.ycwl.basic.pricing.enums.ProductType.fromCode(productType);
|
|
||||||
|
|
||||||
throw new DuplicatePurchaseException(
|
throw new DuplicatePurchaseException(
|
||||||
"您已购买过此类型的套餐",
|
String.format("您已购买过%s", productTypeEnum.getDescription()),
|
||||||
order.getId(),
|
order.getId(),
|
||||||
order.getOrderNo(),
|
order.getOrderNo(),
|
||||||
productTypeEnum
|
productTypeEnum
|
||||||
@@ -95,7 +96,7 @@ public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("套餐重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}",
|
log.debug("父资源重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}",
|
||||||
userId, faceId, scenicId, productType);
|
userId, faceId, scenicId, productType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ import com.ycwl.basic.order.mapper.OrderItemMapper;
|
|||||||
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
||||||
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
|
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
|
||||||
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
|
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
|
||||||
|
import com.ycwl.basic.pricing.enums.ProductType;
|
||||||
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -18,17 +19,19 @@ import org.springframework.stereotype.Component;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按视频ID检查重复购买策略
|
* 唯一资源重复购买检查策略
|
||||||
* 适用于:VLOG_VIDEO 商品类型
|
* 适用于:独立资源类商品(PHOTO照片、VLOG_VIDEO视频、VIDEO视频片段等)
|
||||||
*
|
*
|
||||||
* 检查逻辑:
|
* 检查逻辑:
|
||||||
* 1. 查找用户在该景区已支付的有效订单
|
* 1. 查找用户在该景区已支付的有效订单
|
||||||
* 2. 检查订单明细中是否包含相同的视频ID
|
* 2. 检查订单明细中是否包含相同product_type和product_id的商品
|
||||||
* 3. 如果存在,抛出重复购买异常
|
* 3. 如果存在,抛出重复购买异常
|
||||||
|
*
|
||||||
|
* SQL查询: WHERE order_id = ? AND product_type = ? AND product_id = ?
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
public class UniqueResourceDuplicateChecker implements IDuplicatePurchaseChecker {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private OrderV2Mapper orderV2Mapper;
|
private OrderV2Mapper orderV2Mapper;
|
||||||
@@ -38,20 +41,21 @@ public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplicateCheckStrategy getStrategyType() {
|
public DuplicateCheckStrategy getStrategyType() {
|
||||||
return DuplicateCheckStrategy.CHECK_BY_VIDEO_ID;
|
return DuplicateCheckStrategy.UNIQUE_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void check(DuplicateCheckContext context) throws DuplicatePurchaseException {
|
public void check(DuplicateCheckContext context) throws DuplicatePurchaseException {
|
||||||
String userId = context.getUserId();
|
String userId = context.getUserId();
|
||||||
String scenicId = context.getScenicId();
|
String scenicId = context.getScenicId();
|
||||||
String productId = context.getProductId();
|
String productType = context.getProductType();
|
||||||
|
String productId = context.getProductId(); // 唯一资源ID
|
||||||
|
|
||||||
// 获取人脸ID(从扩展参数中)
|
// 获取人脸ID(从扩展参数中)
|
||||||
Long faceId = context.getParam("faceId");
|
Long faceId = context.getParam("faceId");
|
||||||
|
|
||||||
log.debug("执行视频ID重复购买检查: userId={}, faceId={}, scenicId={}, videoId={}",
|
log.debug("执行唯一资源重复购买检查: userId={}, faceId={}, scenicId={}, productType={}, productId={}",
|
||||||
userId, faceId, scenicId, productId);
|
userId, faceId, scenicId, productType, productId);
|
||||||
|
|
||||||
// 构建查询条件:查找已支付的有效订单
|
// 构建查询条件:查找已支付的有效订单
|
||||||
QueryWrapper<OrderV2> orderQuery = new QueryWrapper<>();
|
QueryWrapper<OrderV2> orderQuery = new QueryWrapper<>();
|
||||||
@@ -72,29 +76,30 @@ public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker {
|
|||||||
List<OrderV2> existingOrders = orderV2Mapper.selectList(orderQuery);
|
List<OrderV2> existingOrders = orderV2Mapper.selectList(orderQuery);
|
||||||
|
|
||||||
for (OrderV2 order : existingOrders) {
|
for (OrderV2 order : existingOrders) {
|
||||||
// 检查订单明细中是否包含该视频
|
// 检查订单明细中是否包含该资源(按product_type和product_id双重匹配)
|
||||||
QueryWrapper<OrderItemV2> itemQuery = new QueryWrapper<>();
|
QueryWrapper<OrderItemV2> itemQuery = new QueryWrapper<>();
|
||||||
itemQuery.eq("order_id", order.getId())
|
itemQuery.eq("order_id", order.getId())
|
||||||
.eq("product_type", "VLOG_VIDEO")
|
.eq("product_type", productType)
|
||||||
.eq("product_id", productId);
|
.eq("product_id", productId); // 按productId精确匹配
|
||||||
|
|
||||||
long count = orderItemMapper.selectCount(itemQuery);
|
long count = orderItemMapper.selectCount(itemQuery);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
log.warn("检测到重复购买视频: userId={}, faceId={}, scenicId={}, videoId={}, existingOrderId={}",
|
log.warn("检测到重复购买唯一资源: userId={}, faceId={}, scenicId={}, productType={}, productId={}, existingOrderId={}",
|
||||||
userId, faceId, scenicId, productId, order.getId());
|
userId, faceId, scenicId, productType, productId, order.getId());
|
||||||
|
|
||||||
|
ProductType productTypeEnum = ProductType.fromCode(productType);
|
||||||
|
|
||||||
// 为了兼容旧代码,需要转换为 ProductType 枚举
|
|
||||||
throw new DuplicatePurchaseException(
|
throw new DuplicatePurchaseException(
|
||||||
"您已购买过此视频",
|
String.format("您已购买过此%s", productTypeEnum.getDescription()),
|
||||||
order.getId(),
|
order.getId(),
|
||||||
order.getOrderNo(),
|
order.getOrderNo(),
|
||||||
com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO,
|
productTypeEnum,
|
||||||
productId
|
productId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("视频重复购买检查通过: userId={}, faceId={}, scenicId={}, videoId={}",
|
log.debug("唯一资源重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}, productId={}",
|
||||||
userId, faceId, scenicId, productId);
|
userId, faceId, scenicId, productType, productId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,16 +13,27 @@ public enum DuplicateCheckStrategy {
|
|||||||
NO_CHECK("NO_CHECK", "不检查"),
|
NO_CHECK("NO_CHECK", "不检查"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按视频ID检查(VLOG_VIDEO)
|
* 检查唯一资源
|
||||||
* 同一个视频不允许重复购买
|
* 检查用户是否已购买过相同的独立资源
|
||||||
|
* 查询逻辑:WHERE product_type = ? AND product_id = ?
|
||||||
|
*
|
||||||
|
* 适用商品:
|
||||||
|
* - VLOG_VIDEO:检查是否购买过同一个视频
|
||||||
|
* - PHOTO:检查是否购买过同一张照片
|
||||||
|
* - 未来的VIDEO片段:检查是否购买过同一段视频
|
||||||
*/
|
*/
|
||||||
CHECK_BY_VIDEO_ID("CHECK_BY_VIDEO_ID", "按视频ID检查"),
|
UNIQUE_RESOURCE("UNIQUE_RESOURCE", "唯一资源检查"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按套餐ID检查(RECORDING_SET, PHOTO_SET)
|
* 检查父资源/套餐
|
||||||
* 同一个套餐不允许重复购买
|
* 检查用户是否已购买过该类型的任何商品
|
||||||
|
* 查询逻辑:WHERE product_type = ?
|
||||||
|
*
|
||||||
|
* 适用商品:
|
||||||
|
* - RECORDING_SET:购买过任意录像集后不能再购买
|
||||||
|
* - PHOTO_SET:购买过任意照片集后不能再购买
|
||||||
*/
|
*/
|
||||||
CHECK_BY_SET_ID("CHECK_BY_SET_ID", "按套餐ID检查"),
|
PARENT_RESOURCE("PARENT_RESOURCE", "父资源检查"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义策略(通过扩展实现)
|
* 自定义策略(通过扩展实现)
|
||||||
|
|||||||
@@ -229,17 +229,33 @@ public class ProductTypeCapabilityManagementServiceImpl implements IProductTypeC
|
|||||||
capability.setDisplayName(productType.getDescription());
|
capability.setDisplayName(productType.getDescription());
|
||||||
capability.setCategory(productType.getCategoryCode());
|
capability.setCategory(productType.getCategoryCode());
|
||||||
|
|
||||||
// 根据分类设置默认的定价模式
|
// 根据分类设置默认的定价模式和去重策略
|
||||||
if (ProductCategory.PRINT == productType.getCategory()) {
|
if (ProductCategory.PRINT == productType.getCategory()) {
|
||||||
|
// 打印类:基于数量定价,允许重复购买
|
||||||
capability.setPricingMode(PricingMode.QUANTITY_BASED.getCode());
|
capability.setPricingMode(PricingMode.QUANTITY_BASED.getCode());
|
||||||
capability.setSupportsTierPricing(true);
|
capability.setSupportsTierPricing(true);
|
||||||
capability.setAllowDuplicatePurchase(true);
|
capability.setAllowDuplicatePurchase(true);
|
||||||
capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.NO_CHECK.getCode());
|
capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.NO_CHECK.getCode());
|
||||||
} else {
|
} else if (ProductCategory.PHOTO == productType.getCategory()
|
||||||
|
|| ProductCategory.VIDEO == productType.getCategory()
|
||||||
|
|| ProductCategory.VLOG == productType.getCategory()) {
|
||||||
|
// 照片类、视频类、Vlog类:固定价格,检查唯一资源
|
||||||
capability.setPricingMode(PricingMode.FIXED.getCode());
|
capability.setPricingMode(PricingMode.FIXED.getCode());
|
||||||
capability.setSupportsTierPricing(false);
|
capability.setSupportsTierPricing(false);
|
||||||
capability.setAllowDuplicatePurchase(false);
|
capability.setAllowDuplicatePurchase(false);
|
||||||
capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.CHECK_BY_SET_ID.getCode());
|
|
||||||
|
// 判断是套餐类还是独立资源类
|
||||||
|
if (productType.getCode().endsWith("_SET")) {
|
||||||
|
capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.PARENT_RESOURCE.getCode());
|
||||||
|
} else {
|
||||||
|
capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.UNIQUE_RESOURCE.getCode());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 其他类:默认不检查
|
||||||
|
capability.setPricingMode(PricingMode.FIXED.getCode());
|
||||||
|
capability.setSupportsTierPricing(false);
|
||||||
|
capability.setAllowDuplicatePurchase(true);
|
||||||
|
capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.NO_CHECK.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优惠能力默认全部开启
|
// 优惠能力默认全部开启
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class ProductTypeCapabilityServiceTest {
|
|||||||
assertEquals("VIDEO", capability.getCategory());
|
assertEquals("VIDEO", capability.getCategory());
|
||||||
assertEquals(PricingMode.FIXED, capability.getPricingModeEnum());
|
assertEquals(PricingMode.FIXED, capability.getPricingModeEnum());
|
||||||
assertEquals(false, capability.getAllowDuplicatePurchase());
|
assertEquals(false, capability.getAllowDuplicatePurchase());
|
||||||
assertEquals(DuplicateCheckStrategy.CHECK_BY_VIDEO_ID, capability.getDuplicateCheckStrategyEnum());
|
assertEquals(DuplicateCheckStrategy.UNIQUE_RESOURCE, capability.getDuplicateCheckStrategyEnum());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user