一、接着上文
用户在消耗积分的时候,需要根据一定的逻辑,除了扣减账户的当前余额,还需要依次消费积分订单的余额。
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.发布异步事件,通知用户其账户有变更}
四、总结
至此,关于商城的积分系统,其详细实现就介绍完了。
希望通过整个系列的五篇文章,帮助你搭建一套灵活多变的积分系统,服务于整个公司的所有业务。