fix(pricing): 完善券码验证逻辑并优化使用权限判断

- 新增用户在指定批次下的使用次数统计和最后使用时间获取功能
- 重构券码验证逻辑,支持未领取券码的使用权限判断
- 优化已领取券码的使用限制检查,包括使用次数和间隔时间- 改进日志记录,增加剩余使用次数信息
-修复一些潜在的逻辑问题和边界情况处理
This commit is contained in:
2025-09-16 17:55:24 +08:00
parent 57266eb535
commit 90a21c0933
3 changed files with 125 additions and 35 deletions

View File

@@ -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);
/**
* 分页查询券码使用记录
*

View File

@@ -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);

View File

@@ -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);
}
}
}
}