一、接着上文
用户在消耗积分的时候,需要根据一定的逻辑,除了扣减账户的当前余额,还需要依次消费积分订单的余额。
private void updatePointsOrderByUse(Integer schoolId, Long userId, String pointsType, int usingPoints) {
List<PointsOrder> pointsOrders = pointsOrderService.listAvailablePointsOrder(schoolId, userId, pointsType);
if (CollectionUtils.isNotEmpty(pointsOrders)) {
for (int i = 0; i < pointsOrders.size() && usingPoints > 0; i++) {
PointsOrder pointsOrder = pointsOrders.get(i);
int thisUsedPoints = pointsOrder.getAvailablePoints() >= usingPoints ? usingPoints : pointsOrder.getAvailablePoints();
boolean updateSuccess = this.optimisticUpdateOrder(USE_POINTS_ORDER, pointsOrder.getId(),
pointsOrder.getUsedPoints(), pointsOrder.getAvailablePoints(), 0,
0, pointsOrder.getAvailableSettlePoints(), thisUsedPoints,
pointsOrder.getVersion());
if (!updateSuccess) {
if (log.isWarnEnabled()) {
log.warn("积分订单处理使用事件出错, [orderNo={}, points={}]", pointsOrder.getOrderNo(), usingPoints);
}
Precondition.isTrue(false, "积分订单[%s]处理使用事件处理出错", pointsOrder.getOrderNo());
}
usingPoints -= thisUsedPoints;
}
}
}
查询其所有的积分订单(可用余额大于0),按创建时间升序排列,也就是说,优先扣减最早的积分订单,直至全部扣减。
方法optimisticUpdateOrder()是一个乐观锁更新积分订单,和上文的方法optimisticUpdateAccount()实现类似,这里就不重复赘述了。
二、积分订单的更新逻辑
它有三个更新途径:
// 消耗/使用积分
private static final int USE_POINTS_ORDER = 1;
// 积分订单的退款
private static final int REFUND_POINTS_ORDER = 2;
// 积分订单的结算
private static final int SETTLE_POINTS_ORDER = 3;
1、消耗积分
更新订单的可用积分数、已使用积分数、可结算积分数
@Modifying
@Query(value = "update PointsOrder set availablePoints = :availablePoints, usedPoints = :usedPoints, " +
" availableSettlePoints = :availableSettlePoints, version = version + 1, modifiedDate = now() " +
" where id = :id and version = :oldVersion ")
int modifyPointsOrderByUse(@Param("id") long id,
@Param("availablePoints") int availablePoints,
@Param("usedPoints") int usedPoints,
@Param("availableSettlePoints") int availableSettlePoints,
@Param("oldVersion") long oldVersion);
2、积分订单的退款
更新积分订单的已退款积分数、可用积分数
@Modifying
@Query(value = "update PointsOrder set refundedPoints = :refundedPoints, " +
" availablePoints = :availablePoints, version = version + 1, modifiedDate = now() " +
" where id = :id and version = :oldVersion ")
int modifyPointsOrderByRefund(@Param("id") long id,
@Param("refundedPoints") int refundedPoints,
@Param("availablePoints") int availablePoints,
@Param("oldVersion") long oldVersion);
3、积分订单的结算
更新积分订单的可结算积分数、已结算积分数
@Modifying
@Query(value = "update PointsOrder set settledPoints = :settledPoints, " +
" availableSettlePoints = :availableSettlePoints, version = version + 1, modifiedDate = now() " +
" where id = :id and version = :oldVersion ")
int modifyPointsOrderBySettle(@Param("id") long id,
@Param("settledPoints") int settledPoints,
@Param("availableSettlePoints") int availableSettlePoints,
@Param("oldVersion") long oldVersion);
三、积分订单的退款
积分订单的结算操作,不涉及积分账户和账户收支。用户消耗积分,更新积分订单,本文的开头就已详细交待(它是和上一篇紧密相关的)
积分订单的退款则不一样,它会涉及到积分账户和账户的收支。
为了减少复杂度,我们规定积分订单只能退款一次。
既然是只能退款一次,默认就是全额退款,传入积分订单的订单号,调用本接口。
@Lock(name = POINTS_DISTRIBUTE_LOCK_PRE, key = "'refund:orderNo:' + #orderNo")
@Transactional(rollbackFor = Throwable.class, isolation = Isolation.READ_COMMITTED)
public void dealRefund(String orderNo) {
PointsOrder pointsOrder = pointsOrderService.findPointsOrder(orderNo);
Precondition.notNull(pointsOrder, "订单[%s]不存在", orderNo);
// 本次退款的积分
int thisPoints = pointsOrder.getAvailablePoints();
if (thisPoints == 0) {
if (log.isInfoEnabled()) {
log.info("订单退款处理出现警告,可用积分数为0.[orderNo={}]", orderNo);
}
return;
}
// 1.更新账户的余额
this.updateAccount(REFUND_POINTS_ACCOUNT, pointsOrder.getSchoolId(),
pointsOrder.getUserId(), pointsOrder.getPointsType(), thisPoints);
// 2.更新积分订单表
boolean updateOrderSuccess = this.optimisticUpdateOrder(REFUND_POINTS_ORDER, pointsOrder.getId(),
0, pointsOrder.getAvailablePoints(), pointsOrder.getRefundedPoints(),
0, 0, thisPoints,
pointsOrder.getVersion());
if (!updateOrderSuccess) {
if (log.isWarnEnabled()) {
log.warn("订单退款出现错误, [orderNo={}, points={}]", orderNo, thisPoints);
}
Precondition.isTrue(false, "订单[%s]退款出现错误", orderNo);
}
//3.保存账户变更记录
pointsAccountFlowService.savePointsAccountFlow(FlowTypeEnum.DECREASE,
pointsOrder.getSchoolId(),
pointsOrder.getUserId(),
pointsOrder.getPointsType(), thisPoints,
PointsChannelEnum.REFUND_ORDER.getCode(), PointsChannelEnum.REFUND_ORDER.getName(),
orderNo, null,
"订单号[" + orderNo + "]退款");
//4.发布异步事件,通知用户其账户有变更
}
四、总结
至此,关于商城的积分系统,其详细实现就介绍完了。
希望通过整个系列的五篇文章,帮助你搭建一套灵活多变的积分系统,服务于整个公司的所有业务。