You've already forked FrameTour-BE
fix(pricing): 完善券码验证逻辑并优化使用权限判断
- 新增用户在指定批次下的使用次数统计和最后使用时间获取功能 - 重构券码验证逻辑,支持未领取券码的使用权限判断 - 优化已领取券码的使用限制检查,包括使用次数和间隔时间- 改进日志记录,增加剩余使用次数信息 -修复一些潜在的逻辑问题和边界情况处理
This commit is contained in:
@@ -89,6 +89,26 @@ public interface PriceVoucherUsageRecordMapper extends BaseMapper<PriceVoucherUs
|
||||
@Select("SELECT COUNT(*) FROM price_voucher_usage_record WHERE batch_id = #{batchId} AND deleted = 0")
|
||||
Integer countByBatchId(@Param("batchId") Long batchId);
|
||||
|
||||
/**
|
||||
* 统计用户在指定批次下的使用次数
|
||||
*
|
||||
* @param faceId 用户faceId
|
||||
* @param batchId 批次ID
|
||||
* @return 使用次数
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM price_voucher_usage_record WHERE face_id = #{faceId} AND batch_id = #{batchId} AND deleted = 0")
|
||||
Integer countByFaceIdAndBatchId(@Param("faceId") Long faceId, @Param("batchId") Long batchId);
|
||||
|
||||
/**
|
||||
* 获取用户在指定批次下最后一次使用时间
|
||||
*
|
||||
* @param faceId 用户faceId
|
||||
* @param batchId 批次ID
|
||||
* @return 最后使用时间
|
||||
*/
|
||||
@Select("SELECT MAX(use_time) FROM price_voucher_usage_record WHERE face_id = #{faceId} AND batch_id = #{batchId} AND deleted = 0")
|
||||
Date getLastUseTimeByFaceIdAndBatchId(@Param("faceId") Long faceId, @Param("batchId") Long batchId);
|
||||
|
||||
/**
|
||||
* 分页查询券码使用记录
|
||||
*
|
||||
|
@@ -112,21 +112,34 @@ public class VoucherDiscountProvider implements IDiscountProvider {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 重新验证券码
|
||||
// 重新验证券码,包括重复使用权限验证
|
||||
VoucherInfo voucherInfo = voucherService.validateAndGetVoucherInfo(
|
||||
voucherCode,
|
||||
context.getFaceId(),
|
||||
context.getScenicId()
|
||||
);
|
||||
|
||||
if (voucherInfo == null || !Boolean.TRUE.equals(voucherInfo.getAvailable())) {
|
||||
if (voucherInfo == null) {
|
||||
result.setSuccess(false);
|
||||
result.setFailureReason("券码无效或不可用");
|
||||
result.setFailureReason("券码不存在或已失效");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!Boolean.TRUE.equals(voucherInfo.getAvailable())) {
|
||||
result.setSuccess(false);
|
||||
result.setFailureReason(voucherInfo.getUnavailableReason() != null ?
|
||||
voucherInfo.getUnavailableReason() : "券码不可用");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 计算实际优惠金额
|
||||
BigDecimal actualDiscount = voucherService.calculateVoucherDiscount(voucherInfo, context);
|
||||
if (actualDiscount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
result.setSuccess(false);
|
||||
result.setFailureReason("券码无法应用到当前商品");
|
||||
return result;
|
||||
}
|
||||
|
||||
BigDecimal finalAmount;
|
||||
|
||||
// 对于全场免费券码,最终金额为0
|
||||
@@ -145,7 +158,11 @@ public class VoucherDiscountProvider implements IDiscountProvider {
|
||||
result.setFinalAmount(finalAmount);
|
||||
result.setSuccess(true);
|
||||
|
||||
log.info("成功应用券码: {}, 优惠金额: {}", voucherCode, actualDiscount);
|
||||
// 显示剩余使用次数信息,处理无限次使用的情况
|
||||
String remainingInfo = voucherInfo.getRemainingUseCount() != null ?
|
||||
voucherInfo.getRemainingUseCount().toString() : "无限次";
|
||||
log.info("成功应用券码: {}, 优惠金额: {}, 剩余使用次数: {}",
|
||||
voucherCode, actualDiscount, remainingInfo);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("应用券码失败: " + discountInfo.getVoucherCode(), e);
|
||||
|
@@ -63,33 +63,55 @@ public class VoucherServiceImpl implements IVoucherService {
|
||||
|
||||
VoucherInfo voucherInfo = buildVoucherInfo(voucherCodeEntity, batchConfig);
|
||||
|
||||
// 检查券码状态和可用性
|
||||
// 检查券码状态和可用性,包含完整的重复使用权限验证
|
||||
if (VoucherCodeStatus.UNCLAIMED.getCode().equals(voucherCodeEntity.getStatus())) {
|
||||
// 未领取状态,检查是否可以领取
|
||||
if (faceId != null) {
|
||||
voucherInfo.setAvailable(true);
|
||||
} else {
|
||||
// 未领取状态,也需要检查用户的重复使用权限
|
||||
if (faceId == null) {
|
||||
voucherInfo.setAvailable(false);
|
||||
voucherInfo.setUnavailableReason("您已在该景区领取过券码");
|
||||
}
|
||||
} else if (VoucherCodeStatus.canUse(voucherCodeEntity.getStatus())) {
|
||||
// 已领取可使用状态,检查是否为当前用户且未达到使用限制
|
||||
if (faceId != null && faceId.equals(voucherCodeEntity.getFaceId())) {
|
||||
String availabilityCheck = checkVoucherAvailability(voucherCodeEntity, batchConfig, faceId);
|
||||
voucherInfo.setUnavailableReason("用户信息缺失,无法验证券码权限");
|
||||
} else {
|
||||
// 对于未领取的券码,检查用户在该批次下的使用权限
|
||||
String availabilityCheck = checkUserUsagePermission(voucherCodeEntity, batchConfig, faceId, false);
|
||||
if (availabilityCheck == null) {
|
||||
voucherInfo.setAvailable(true);
|
||||
log.debug("未领取券码验证通过: code={}, faceId={}", voucherCode, faceId);
|
||||
} else {
|
||||
voucherInfo.setAvailable(false);
|
||||
voucherInfo.setUnavailableReason(availabilityCheck);
|
||||
log.info("未领取券码使用受限: code={}, faceId={}, reason={}",
|
||||
voucherCode, faceId, availabilityCheck);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
} else if (VoucherCodeStatus.canUse(voucherCodeEntity.getStatus())) {
|
||||
// 已领取可使用状态,进行完整的权限验证
|
||||
if (faceId == null) {
|
||||
voucherInfo.setAvailable(false);
|
||||
voucherInfo.setUnavailableReason("用户信息缺失,无法验证券码权限");
|
||||
} else if (!faceId.equals(voucherCodeEntity.getFaceId())) {
|
||||
voucherInfo.setAvailable(false);
|
||||
voucherInfo.setUnavailableReason("券码已被其他用户领取");
|
||||
} else {
|
||||
// 执行详细的重复使用权限验证(已领取状态)
|
||||
String availabilityCheck = checkUserUsagePermission(voucherCodeEntity, batchConfig, faceId, true);
|
||||
if (availabilityCheck == null) {
|
||||
voucherInfo.setAvailable(true);
|
||||
log.debug("券码验证通过: code={}, faceId={}, currentUseCount={}, maxUseCount={}",
|
||||
voucherCode, faceId, voucherCodeEntity.getCurrentUseCount(), batchConfig.getMaxUseCount());
|
||||
} else {
|
||||
voucherInfo.setAvailable(false);
|
||||
voucherInfo.setUnavailableReason(availabilityCheck);
|
||||
log.info("券码使用受限: code={}, faceId={}, reason={}",
|
||||
voucherCode, faceId, availabilityCheck);
|
||||
}
|
||||
}
|
||||
} else if (VoucherCodeStatus.isExhausted(voucherCodeEntity.getStatus())) {
|
||||
// 已用完或已使用
|
||||
voucherInfo.setAvailable(false);
|
||||
voucherInfo.setUnavailableReason("券码已用完");
|
||||
if (batchConfig.getMaxUseCount() != null && batchConfig.getMaxUseCount() == 1) {
|
||||
voucherInfo.setUnavailableReason("券码已使用");
|
||||
} else {
|
||||
voucherInfo.setUnavailableReason("券码使用次数已达上限");
|
||||
}
|
||||
} else if (VoucherCodeStatus.isExpired(voucherCodeEntity.getStatus())) {
|
||||
// 已过期
|
||||
voucherInfo.setAvailable(false);
|
||||
@@ -339,40 +361,71 @@ public class VoucherServiceImpl implements IVoucherService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查券码可用性
|
||||
* 统一检查用户券码使用权限
|
||||
*
|
||||
* @param voucherCode 券码实体
|
||||
* @param batchConfig 批次配置
|
||||
* @param faceId 用户faceId
|
||||
* @param isClaimed 是否为已领取状态(true=已领取,检查具体券码权限;false=未领取,检查批次权限)
|
||||
* @return 不可用原因,null表示可用
|
||||
*/
|
||||
private String checkVoucherAvailability(PriceVoucherCode voucherCode, PriceVoucherBatchConfig batchConfig, Long faceId) {
|
||||
// 1. 检查券码使用次数限制
|
||||
Integer maxUseCount = batchConfig.getMaxUseCount();
|
||||
Integer currentUseCount = voucherCode.getCurrentUseCount() != null ? voucherCode.getCurrentUseCount() : 0;
|
||||
private String checkUserUsagePermission(PriceVoucherCode voucherCode, PriceVoucherBatchConfig batchConfig, Long faceId, boolean isClaimed) {
|
||||
if (faceId == null) {
|
||||
return "用户信息缺失";
|
||||
}
|
||||
|
||||
if (maxUseCount != null && currentUseCount >= maxUseCount) {
|
||||
return "券码使用次数已达上限";
|
||||
// 1. 检查券码使用次数限制(仅针对已领取的券码)
|
||||
if (isClaimed) {
|
||||
Integer maxUseCount = batchConfig.getMaxUseCount();
|
||||
Integer currentUseCount = voucherCode.getCurrentUseCount() != null ? voucherCode.getCurrentUseCount() : 0;
|
||||
|
||||
if (maxUseCount != null && currentUseCount >= maxUseCount) {
|
||||
return "券码使用次数已达上限";
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查用户使用次数限制
|
||||
Integer maxUsePerUser = batchConfig.getMaxUsePerUser();
|
||||
if (maxUsePerUser != null && faceId != null) {
|
||||
Integer userUseCount = usageRecordMapper.countByFaceIdAndVoucherCodeId(faceId, voucherCode.getId());
|
||||
if (userUseCount >= maxUsePerUser) {
|
||||
return "您使用该券码的次数已达上限";
|
||||
if (maxUsePerUser != null) {
|
||||
Integer userUseCount;
|
||||
if (isClaimed) {
|
||||
// 已领取:检查用户对该具体券码的使用次数
|
||||
userUseCount = usageRecordMapper.countByFaceIdAndVoucherCodeId(faceId, voucherCode.getId());
|
||||
if (userUseCount >= maxUsePerUser) {
|
||||
return "您使用该券码的次数已达上限";
|
||||
}
|
||||
} else {
|
||||
// 未领取:检查用户在该批次下的总使用次数
|
||||
userUseCount = usageRecordMapper.countByFaceIdAndBatchId(faceId, batchConfig.getId());
|
||||
if (userUseCount >= maxUsePerUser) {
|
||||
return "您在该批次下使用券码的次数已达上限";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查使用间隔时间限制
|
||||
Integer useIntervalHours = batchConfig.getUseIntervalHours();
|
||||
if (useIntervalHours != null && faceId != null) {
|
||||
Date lastUseTime = usageRecordMapper.getLastUseTimeByFaceIdAndVoucherCodeId(faceId, voucherCode.getId());
|
||||
if (lastUseTime != null) {
|
||||
long diffMillis = System.currentTimeMillis() - lastUseTime.getTime();
|
||||
long diffHours = TimeUnit.MILLISECONDS.toHours(diffMillis);
|
||||
if (diffHours < useIntervalHours) {
|
||||
return String.format("请等待%d小时后再次使用该券码", useIntervalHours - diffHours);
|
||||
if (useIntervalHours != null) {
|
||||
Date lastUseTime;
|
||||
if (isClaimed) {
|
||||
// 已领取:检查该券码的最后使用时间
|
||||
lastUseTime = usageRecordMapper.getLastUseTimeByFaceIdAndVoucherCodeId(faceId, voucherCode.getId());
|
||||
if (lastUseTime != null) {
|
||||
long diffMillis = System.currentTimeMillis() - lastUseTime.getTime();
|
||||
long diffHours = TimeUnit.MILLISECONDS.toHours(diffMillis);
|
||||
if (diffHours < useIntervalHours) {
|
||||
return String.format("请等待%d小时后再次使用该券码", useIntervalHours - diffHours);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 未领取:检查该批次下的最后使用时间
|
||||
lastUseTime = usageRecordMapper.getLastUseTimeByFaceIdAndBatchId(faceId, batchConfig.getId());
|
||||
if (lastUseTime != null) {
|
||||
long diffMillis = System.currentTimeMillis() - lastUseTime.getTime();
|
||||
long diffHours = TimeUnit.MILLISECONDS.toHours(diffMillis);
|
||||
if (diffHours < useIntervalHours) {
|
||||
return String.format("请等待%d小时后再次使用该批次券码", useIntervalHours - diffHours);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user