You've already forked FrameTour-BE
feat(order): 添加重复购买检测功能
- 新增 DuplicatePurchaseException 类用于处理重复购买异常 - 在 OrderServiceImpl 中实现重复购买检查逻辑 - 更新 CustomExceptionHandle 以处理新的重复购买异常 -优化订单创建流程,在生成订单号前增加重复购买检查
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.ycwl.basic.exception;
|
package com.ycwl.basic.exception;
|
||||||
|
|
||||||
import com.ycwl.basic.enums.BizCodeEnum;
|
import com.ycwl.basic.enums.BizCodeEnum;
|
||||||
|
import com.ycwl.basic.order.exception.DuplicatePurchaseException;
|
||||||
import com.ycwl.basic.utils.ApiResponse;
|
import com.ycwl.basic.utils.ApiResponse;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
|
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
|
||||||
@@ -106,4 +107,18 @@ public class CustomExceptionHandle {
|
|||||||
public ApiResponse<String> handle(SizeLimitExceededException sizeLimitExceededException) {
|
public ApiResponse<String> handle(SizeLimitExceededException sizeLimitExceededException) {
|
||||||
return ApiResponse.buildResponse(415, "文件过大,请重新上传");
|
return ApiResponse.buildResponse(415, "文件过大,请重新上传");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重复购买异常处理
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(value = DuplicatePurchaseException.class)
|
||||||
|
public ApiResponse<String> handle(HttpServletResponse response, DuplicatePurchaseException exception) {
|
||||||
|
response.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||||
|
LOGGER.warn("检测到重复购买: productType={}, productId={}, existingOrderId={}, existingOrderNo={}",
|
||||||
|
exception.getProductType(), exception.getProductId(),
|
||||||
|
exception.getExistingOrderId(), exception.getExistingOrderNo());
|
||||||
|
|
||||||
|
// 返回友好的错误信息给前端
|
||||||
|
return ApiResponse.buildResponse(4001, exception.getFriendlyMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,58 @@
|
|||||||
|
package com.ycwl.basic.order.exception;
|
||||||
|
|
||||||
|
import com.ycwl.basic.exception.BaseException;
|
||||||
|
import com.ycwl.basic.pricing.enums.ProductType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重复购买异常
|
||||||
|
* 当用户尝试购买已经购买过的内容时抛出此异常
|
||||||
|
*/
|
||||||
|
public class DuplicatePurchaseException extends BaseException {
|
||||||
|
|
||||||
|
private final Long existingOrderId;
|
||||||
|
private final String existingOrderNo;
|
||||||
|
private final ProductType productType;
|
||||||
|
private final String productId;
|
||||||
|
|
||||||
|
public DuplicatePurchaseException(String message, Long existingOrderId, String existingOrderNo,
|
||||||
|
ProductType productType, String productId) {
|
||||||
|
super(message);
|
||||||
|
this.existingOrderId = existingOrderId;
|
||||||
|
this.existingOrderNo = existingOrderNo;
|
||||||
|
this.productType = productType;
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DuplicatePurchaseException(String message, Long existingOrderId, String existingOrderNo,
|
||||||
|
ProductType productType) {
|
||||||
|
this(message, existingOrderId, existingOrderNo, productType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getExistingOrderId() {
|
||||||
|
return existingOrderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExistingOrderNo() {
|
||||||
|
return existingOrderNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProductType getProductType() {
|
||||||
|
return productType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductId() {
|
||||||
|
return productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取友好的错误消息
|
||||||
|
*/
|
||||||
|
public String getFriendlyMessage() {
|
||||||
|
String productDesc = productType != null ? productType.getDescription() : "商品";
|
||||||
|
if (productId != null) {
|
||||||
|
return String.format("您已购买过该%s(商品ID:%s),订单号:%s", productDesc, productId, existingOrderNo);
|
||||||
|
} else {
|
||||||
|
return String.format("您已购买过%s,订单号:%s", productDesc, existingOrderNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ import com.ycwl.basic.order.entity.OrderItemV2;
|
|||||||
import com.ycwl.basic.order.entity.OrderRefundV2;
|
import com.ycwl.basic.order.entity.OrderRefundV2;
|
||||||
import com.ycwl.basic.order.entity.OrderV2;
|
import com.ycwl.basic.order.entity.OrderV2;
|
||||||
import com.ycwl.basic.order.enums.*;
|
import com.ycwl.basic.order.enums.*;
|
||||||
|
import com.ycwl.basic.order.exception.DuplicatePurchaseException;
|
||||||
import com.ycwl.basic.order.mapper.OrderDiscountMapper;
|
import com.ycwl.basic.order.mapper.OrderDiscountMapper;
|
||||||
import com.ycwl.basic.order.mapper.OrderItemMapper;
|
import com.ycwl.basic.order.mapper.OrderItemMapper;
|
||||||
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
import com.ycwl.basic.order.mapper.OrderV2Mapper;
|
||||||
@@ -74,10 +75,18 @@ public class OrderServiceImpl implements IOrderService {
|
|||||||
public Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult) {
|
public Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
MemberRespVO member = memberMapper.getById(userId);
|
MemberRespVO member = memberMapper.getById(userId);
|
||||||
// 1. 生成订单号
|
|
||||||
|
// 1. 检查重复购买
|
||||||
|
log.info("开始检查重复购买: userId={}, faceId={}, scenicId={}, productCount={}",
|
||||||
|
userId, request.getFaceId(), scenicId, request.getProducts().size());
|
||||||
|
checkDuplicatePurchase(userId, request.getFaceId(), scenicId, request.getProducts());
|
||||||
|
log.info("重复购买检查通过: userId={}, faceId={}, scenicId={}",
|
||||||
|
userId, request.getFaceId(), scenicId);
|
||||||
|
|
||||||
|
// 2. 生成订单号
|
||||||
String orderNo = generateOrderNo();
|
String orderNo = generateOrderNo();
|
||||||
|
|
||||||
// 2. 创建订单基础信息
|
// 3. 创建订单基础信息
|
||||||
OrderV2 order = new OrderV2();
|
OrderV2 order = new OrderV2();
|
||||||
order.setOrderNo(orderNo);
|
order.setOrderNo(orderNo);
|
||||||
order.setMemberId(userId);
|
order.setMemberId(userId);
|
||||||
@@ -107,7 +116,7 @@ public class OrderServiceImpl implements IOrderService {
|
|||||||
|
|
||||||
log.info("订单基础信息创建成功: orderId={}, orderNo={}", orderId, orderNo);
|
log.info("订单基础信息创建成功: orderId={}, orderNo={}", orderId, orderNo);
|
||||||
|
|
||||||
// 3. 保存订单商品明细
|
// 4. 保存订单商品明细
|
||||||
for (ProductItem product : request.getProducts()) {
|
for (ProductItem product : request.getProducts()) {
|
||||||
// 重新计算商品价格信息并获取商品名称
|
// 重新计算商品价格信息并获取商品名称
|
||||||
ProductPriceAndNameInfo priceInfo = calculateProductItemPriceAndName(product);
|
ProductPriceAndNameInfo priceInfo = calculateProductItemPriceAndName(product);
|
||||||
@@ -129,7 +138,7 @@ public class OrderServiceImpl implements IOrderService {
|
|||||||
|
|
||||||
log.info("订单商品明细保存成功: orderId={}, itemCount={}", orderId, request.getProducts().size());
|
log.info("订单商品明细保存成功: orderId={}, itemCount={}", orderId, request.getProducts().size());
|
||||||
|
|
||||||
// 4. 记录使用的优惠券信息并标记为已使用
|
// 5. 记录使用的优惠券信息并标记为已使用
|
||||||
if (priceResult.getUsedCoupon() != null) {
|
if (priceResult.getUsedCoupon() != null) {
|
||||||
OrderDiscountV2 couponDiscount = new OrderDiscountV2();
|
OrderDiscountV2 couponDiscount = new OrderDiscountV2();
|
||||||
couponDiscount.setOrderId(orderId);
|
couponDiscount.setOrderId(orderId);
|
||||||
@@ -165,7 +174,7 @@ public class OrderServiceImpl implements IOrderService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 记录使用的券码信息并标记为已使用
|
// 6. 记录使用的券码信息并标记为已使用
|
||||||
if (priceResult.getUsedVoucher() != null) {
|
if (priceResult.getUsedVoucher() != null) {
|
||||||
VoucherInfo voucherInfo = priceResult.getUsedVoucher();
|
VoucherInfo voucherInfo = priceResult.getUsedVoucher();
|
||||||
OrderDiscountV2 voucherDiscount = new OrderDiscountV2();
|
OrderDiscountV2 voucherDiscount = new OrderDiscountV2();
|
||||||
@@ -195,7 +204,7 @@ public class OrderServiceImpl implements IOrderService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 记录其他优惠信息(如限时立减等)
|
// 7. 记录其他优惠信息(如限时立减等)
|
||||||
if (priceResult.getDiscountDetails() != null) {
|
if (priceResult.getDiscountDetails() != null) {
|
||||||
for (DiscountDetail discount : priceResult.getDiscountDetails()) {
|
for (DiscountDetail discount : priceResult.getDiscountDetails()) {
|
||||||
// 跳过已经记录的优惠券和券码
|
// 跳过已经记录的优惠券和券码
|
||||||
@@ -881,4 +890,128 @@ public class OrderServiceImpl implements IOrderService {
|
|||||||
return new ProductPriceAndNameInfo(defaultPrice, defaultPrice, fallbackName);
|
return new ProductPriceAndNameInfo(defaultPrice, defaultPrice, fallbackName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查重复购买
|
||||||
|
* 防止用户重复购买相同内容
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @param products 商品列表
|
||||||
|
* @throws DuplicatePurchaseException 如果检测到重复购买
|
||||||
|
*/
|
||||||
|
private void checkDuplicatePurchase(Long userId, Long faceId, Long scenicId, List<ProductItem> products) {
|
||||||
|
for (ProductItem product : products) {
|
||||||
|
switch (product.getProductType()) {
|
||||||
|
case VLOG_VIDEO:
|
||||||
|
checkVideoAlreadyPurchased(userId, faceId, scenicId, product.getProductId());
|
||||||
|
break;
|
||||||
|
case RECORDING_SET:
|
||||||
|
case PHOTO_SET:
|
||||||
|
checkSetAlreadyPurchased(userId, faceId, scenicId, product.getProductType());
|
||||||
|
break;
|
||||||
|
case PHOTO_PRINT:
|
||||||
|
case MACHINE_PRINT:
|
||||||
|
// 打印类商品允许重复购买,跳过检查
|
||||||
|
log.debug("跳过打印类商品重复购买检查: productType={}, productId={}",
|
||||||
|
product.getProductType(), product.getProductId());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.warn("未知的商品类型,跳过重复购买检查: productType={}", product.getProductType());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查视频是否已经购买
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @param videoId 视频ID
|
||||||
|
* @throws DuplicatePurchaseException 如果已购买
|
||||||
|
*/
|
||||||
|
private void checkVideoAlreadyPurchased(Long userId, Long faceId, Long scenicId, String videoId) {
|
||||||
|
// 构建查询条件:查找已支付的有效订单中包含该视频的订单
|
||||||
|
QueryWrapper<OrderV2> orderQuery = new QueryWrapper<>();
|
||||||
|
orderQuery.eq("member_id", userId)
|
||||||
|
.eq("face_id", faceId)
|
||||||
|
.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);
|
||||||
|
|
||||||
|
List<OrderV2> existingOrders = orderV2Mapper.selectList(orderQuery);
|
||||||
|
|
||||||
|
for (OrderV2 order : existingOrders) {
|
||||||
|
// 检查订单明细中是否包含该视频
|
||||||
|
QueryWrapper<OrderItemV2> itemQuery = new QueryWrapper<>();
|
||||||
|
itemQuery.eq("order_id", order.getId())
|
||||||
|
.eq("product_type", com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO.name())
|
||||||
|
.eq("product_id", videoId);
|
||||||
|
|
||||||
|
long count = orderItemMapper.selectCount(itemQuery);
|
||||||
|
if (count > 0) {
|
||||||
|
log.warn("检测到重复购买视频: userId={}, faceId={}, scenicId={}, videoId={}, existingOrderId={}",
|
||||||
|
userId, faceId, scenicId, videoId, order.getId());
|
||||||
|
throw new DuplicatePurchaseException(
|
||||||
|
"您已购买过此视频",
|
||||||
|
order.getId(),
|
||||||
|
order.getOrderNo(),
|
||||||
|
com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO,
|
||||||
|
videoId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("视频重复购买检查通过: userId={}, faceId={}, scenicId={}, videoId={}",
|
||||||
|
userId, faceId, scenicId, videoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查套餐(录像集/照相集)是否已经购买
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @param productType 商品类型
|
||||||
|
* @throws DuplicatePurchaseException 如果已购买
|
||||||
|
*/
|
||||||
|
private void checkSetAlreadyPurchased(Long userId, Long faceId, Long scenicId,
|
||||||
|
com.ycwl.basic.pricing.enums.ProductType productType) {
|
||||||
|
// 构建查询条件:查找已支付的有效订单中包含该类型套餐的订单
|
||||||
|
QueryWrapper<OrderV2> orderQuery = new QueryWrapper<>();
|
||||||
|
orderQuery.eq("member_id", userId)
|
||||||
|
.eq("face_id", faceId)
|
||||||
|
.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);
|
||||||
|
|
||||||
|
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.name());
|
||||||
|
|
||||||
|
long count = orderItemMapper.selectCount(itemQuery);
|
||||||
|
if (count > 0) {
|
||||||
|
log.warn("检测到重复购买套餐: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}",
|
||||||
|
userId, faceId, scenicId, productType, order.getId());
|
||||||
|
throw new DuplicatePurchaseException(
|
||||||
|
"您已购买过此类型的套餐",
|
||||||
|
order.getId(),
|
||||||
|
order.getOrderNo(),
|
||||||
|
productType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("套餐重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}",
|
||||||
|
userId, faceId, scenicId, productType);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -238,6 +238,5 @@ public class VoucherPrintServiceImpl implements VoucherPrintService {
|
|||||||
content += "<C>赠品兑换码</C>";
|
content += "<C>赠品兑换码</C>";
|
||||||
content += "<C>有效期:"+sdf2.format(new Date())+"</C>";
|
content += "<C>有效期:"+sdf2.format(new Date())+"</C>";
|
||||||
FeiETicketPrinter.doPrint("550519002", content, 1);
|
FeiETicketPrinter.doPrint("550519002", content, 1);
|
||||||
// 小票配对码、赠品兑换码、抵扣兑换码
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user