学习日报 20250930|优惠券事务处理模块
以下是整合后的优惠券事务处理模块,包含异常处理、事务管理及核心业务逻辑,各组件职责明确且注释清晰:
模块结构
coupon-transaction/
├── exception/ // 自定义异常类
│ └── CheckUserCouponException.java
├── service/ // 服务接口与实现
│ ├── ICouponTransactionService.java
│ └── CouponTransactionServiceImpl.java
└── entity/ // 核心实体类├── Order.java // 订单实体└── UserCoupon.java // 用户优惠券实体
1. 自定义异常类(异常处理)
package com.coupon.transaction.exception;/*** 优惠券校验异常:用于优惠券校验失败时抛出,触发事务回滚*/
public class CheckUserCouponException extends RuntimeException {// 异常编码(可用于前端展示具体错误类型)private String errorCode;public CheckUserCouponException(String message) {super(message);}public CheckUserCouponException(String message, String errorCode) {super(message);this.errorCode = errorCode;}public String getErrorCode() {return errorCode;}
}
2. 核心实体类
订单实体(Order.java)
package com.coupon.transaction.entity;import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 订单实体:与优惠券核销关联的订单信息*/
@Data
public class Order {private Long id; // 订单IDprivate Long userId; // 用户IDprivate BigDecimal totalAmount; // 订单总金额private BigDecimal payAmount; // 实付金额(扣减优惠券后)private Integer status; // 订单状态:0-待支付 1-已完成 2-已取消private LocalDateTime createTime;// 创建时间private LocalDateTime payTime; // 支付时间
}
用户优惠券实体(UserCoupon.java)
package com.coupon.transaction.entity;import lombok.Data;
import java.time.LocalDateTime;/*** 用户优惠券实体:记录用户持有的优惠券状态及使用信息*/
@Data
public class UserCoupon {private Long id; // 优惠券IDprivate Long userId; // 用户IDprivate Long couponTemplateId; // 优惠券模板IDprivate Integer status; // 状态:0-未使用 1-已核销 2-已过期private LocalDateTime expireTime;// 过期时间private Long orderId; // 关联订单ID(核销后填充)private LocalDateTime useTime; // 核销时间
}
3. 事务服务接口
package com.coupon.transaction.service;import com.coupon.transaction.entity.Order;
import com.coupon.transaction.entity.UserCoupon;
import com.coupon.transaction.exception.CheckUserCouponException;
import java.util.List;/*** 优惠券事务服务接口:定义订单与优惠券关联的核心事务操作*/
public interface ICouponTransactionService {/*** 订单完成后核销优惠券* 包含订单状态更新、优惠券核销、金额计算等操作,保证事务一致性* @param order 已支付的订单* @param userCoupons 用户选择的待核销优惠券列表* @throws CheckUserCouponException 优惠券校验失败时抛出*/void writeOffCouponsAfterOrderPay(Order order, List<UserCoupon> userCoupons) throws CheckUserCouponException;
}
4. 事务服务实现类(核心逻辑)
package com.coupon.transaction.service;import com.coupon.transaction.entity.Order;
import com.coupon.transaction.entity.UserCoupon;
import com.coupon.transaction.exception.CheckUserCouponException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;/*** 优惠券事务服务实现:基于Spring事务管理,保证订单与优惠券操作的原子性*/
@Slf4j
@Service
public class CouponTransactionServiceImpl implements ICouponTransactionService {/*** 订单支付后核销优惠券:事务管理核心方法* 注解说明:* - @Transactional:声明事务,默认 RuntimeException 触发回滚* - rollbackFor = Exception.class:指定所有异常都回滚(根据业务调整)*/@Override@Transactional(rollbackFor = Exception.class)public void writeOffCouponsAfterOrderPay(Order order, List<UserCoupon> userCoupons) {log.info("开始处理订单[{}]的优惠券核销", order.getId());// 1. 校验订单状态(必须为已支付状态)if (order.getStatus() != 1) {throw new CheckUserCouponException("订单未支付,无法核销优惠券", "ORDER_STATUS_INVALID");}// 2. 校验优惠券合法性(批量校验)validateUserCoupons(order, userCoupons);// 3. 计算优惠券总抵扣金额(根据优惠券类型计算,此处简化)BigDecimal totalDiscount = calculateTotalDiscount(userCoupons);// 4. 更新订单实付金额(总金额 - 抵扣金额)order.setPayAmount(order.getTotalAmount().subtract(totalDiscount));updateOrder(order); // 实际项目中调用DAO层更新数据库// 5. 批量核销优惠券(更新状态、关联订单、记录时间)writeOffBatch(userCoupons, order.getId());log.info("订单[{}]优惠券核销完成,总抵扣金额:{}", order.getId(), totalDiscount);}/*** 校验用户选择的优惠券是否合法* 包含用户匹配、状态、有效期、订单关联等校验*/private void validateUserCoupons(Order order, List<UserCoupon> coupons) {if (coupons == null || coupons.isEmpty()) {return; // 无优惠券时无需校验}for (UserCoupon coupon : coupons) {// 校验1:优惠券归属当前用户if (!coupon.getUserId().equals(order.getUserId())) {throw new CheckUserCouponException("优惠券[" + coupon.getId() + "]不属于当前用户", "COUPON_USER_MISMATCH");}// 校验2:优惠券状态为未使用if (coupon.getStatus() != 0) {throw new CheckUserCouponException("优惠券[" + coupon.getId() + "]状态异常(当前状态:" + coupon.getStatus() + ")", "COUPON_STATUS_INVALID");}// 校验3:优惠券未过期if (coupon.getExpireTime().isBefore(LocalDateTime.now())) {throw new CheckUserCouponException("优惠券[" + coupon.getId() + "]已过期", "COUPON_EXPIRED");}}}/*** 计算优惠券总抵扣金额(简化逻辑,实际需根据优惠券类型(折扣/现金)计算)*/private BigDecimal calculateTotalDiscount(List<UserCoupon> coupons) {BigDecimal total = BigDecimal.ZERO;for (UserCoupon coupon : coupons) {// 假设为现金券,抵扣金额固定(实际需从模板获取规则)total = total.add(new BigDecimal(10)); // 示例:每张券抵扣10元}return total;}/*** 批量更新优惠券状态为"已核销"*/private void writeOffBatch(List<UserCoupon> coupons, Long orderId) {LocalDateTime now = LocalDateTime.now();for (UserCoupon coupon : coupons) {coupon.setStatus(1); // 状态改为已核销coupon.setOrderId(orderId); // 关联订单IDcoupon.setUseTime(now); // 记录核销时间updateUserCoupon(coupon); // 实际项目中调用DAO层更新数据库}}// ------------------------------ 以下为模拟DAO层操作(实际项目中替换为MyBatis/MyBatis-Plus) ------------------------------private void updateOrder(Order order) {log.info("数据库操作:更新订单[{}]实付金额为{}", order.getId(), order.getPayAmount());}private void updateUserCoupon(UserCoupon coupon) {log.info("数据库操作:核销优惠券[{}],状态更新为{}", coupon.getId(), coupon.getStatus());}
}
模块说明
核心功能:实现订单支付后优惠券核销的完整事务流程,确保订单状态更新与优惠券核销操作的原子性(要么全部成功,要么全部回滚)。
事务保证:
- 通过
@Transactional
注解声明事务,当抛出CheckUserCouponException
或其他异常时,自动回滚数据库操作。 - 包含订单状态校验、优惠券合法性校验(用户匹配、状态、有效期)等前置校验,避免无效操作。
- 通过
异常处理:
- 自定义
CheckUserCouponException
用于标识优惠券校验失败场景,包含错误码便于前端展示具体原因。 - 异常抛出后触发事务回滚,保证数据一致性。
- 自定义
扩展性:
- 金额计算逻辑可根据优惠券类型(折扣券、现金券等)扩展。
- 校验规则可通过配置化(如从优惠券模板读取规则)进一步灵活化。
该模块可直接集成到电商系统的订单支付流程中,解决优惠券核销与订单状态同步的一致性问题。