商城积分系统的代码实现(下)-- 积分订单的退款与结算

news2025/1/17 23:10:10

一、接着上文

用户在消耗积分的时候,需要根据一定的逻辑,除了扣减账户的当前余额,还需要依次消费积分订单的余额。

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.发布异步事件,通知用户其账户有变更
    }

四、总结

至此,关于商城的积分系统,其详细实现就介绍完了。

希望通过整个系列的五篇文章,帮助你搭建一套灵活多变的积分系统,服务于整个公司的所有业务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1881392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字证书与PKI解析

目录 1. 什么是数字证书 2. 为什么需要数字证书 3. 数字证书的格式 4. 什么是PKI 5. PKI的组成要素组件 5.1 用户 5.2 认证机构&#xff08;CA&#xff09; 5.3 仓库 5.4 PKI的体系结构 5.4.1 层次结构模型 5.4.2 交叉证明模型 5.4.3 混合模型 1. 什么是数字证书 要…

Django任意URL跳转漏洞(CVE-2018-14574)

目录 Django介绍 URL跳转漏洞介绍 Django任意URL跳转漏洞介绍 环境搭建 防御方法 前段时间在面试时&#xff0c;问到了URL跳转漏洞&#xff0c;我没有回答好&#xff0c;下午把URL跳转漏洞学习了&#xff0c;发现也不难&#xff0c;看来还需要学习的东西很多呀&#xff0c…

burp靶场xss漏洞(中级篇)下

靶场地址 All labs | Web Security Academy 第九关&#xff1a;反射型&#xff08; 转义&#xff09; 1.在搜索框随机输入字符并用Burp抓包 2.测试不同字符在JavaScript字符串中的反映&#xff0c;发现查询结果被包裹在script标签中 而单引号会被转义为 \ 3.构造payload跳出j…

Qt开发报错:Q_INTERFACES Error: Undefined interface

1、背景 VS2019qt5.12.10 从svn拉下来的项目&#xff0c;结果报错&#xff1a; Q_INTERFACES Error: Undefined interface 之前在VS的扩展中在线安装了qt插件&#xff0c; 安装了一半&#xff0c;比较慢&#xff0c;直接强行退出了。。 后来安装了qt官网的插件。。。。 2、报…

OpenCV 调用自定义训练的 YOLO-V8 Onnx 模型

一、YOLO-V8 转 Onnx 在本专栏的前面几篇文章中&#xff0c;我们使用 ultralytics 公司开源发布的 YOLO-V8 模型&#xff0c;分别 Fine-Tuning 实验了 目标检测、关键点检测、分类 任务&#xff0c;实验后发现效果都非常的不错&#xff0c;但是前面的演示都是基于 ultralytics…

【SpringCloud】Zuul源码解析

Zuul是一个网关组件&#xff0c;是微服务的入口&#xff0c;网关会根据配置将请求转发给指定的服务。本章分析Zuul组件是如何实现请求过滤和转发的 参考源码&#xff1a;<spring-cloud.version>Hoxton.SR9</spring-cloud.version> 1、过滤 spring-cloud-netflix-…

重温react-13(嵌套路由和重定向等)

重定向和404 import React from react; import { Routes, Route, Link,NavLink ,Navigate} from react-router-dom; import Home from ./Home/Home import About from ./About/About import News from ./News/News import NotFound from ./NotFound/NotFound; export default …

51单片机第18步_将TIM0用作13位定时器

本章重点学习将TIM0用作13位定时器。 1、定时器0工作在模式0框图 2、定时器0工作在模式0举例 1、Keil C51中有一些关键字&#xff0c;需要牢记&#xff1a; interrupt 0&#xff1a;指定当前函数为外部中断0&#xff1b; interrupt 1&#xff1a;指定当前函数为定时器0中断…

VUE 修改密码功能+密码强度校验

效果图 <template><el-dialogtitle"修改密码":visible.sync"dialog":before-close"cancel":close-on-click-modal"false"width"500px":modal"false"><el-form ref"form" :model"…

SpringMvc 执行原理

当用户请求 会发送到前端控制器&#xff0c;DisptcherServlet根据请求参数生成代理请求&#xff0c;找到对应的实际控制器&#xff0c;控制器处理请求&#xff0c;创建数据模型&#xff0c;访问数据库&#xff0c;将模型响应给中心控制器&#xff0c;控制器使用模型与视图渲染视…

算法题型归类整理及同类题型解法思路总结(持续更新)

1、最优路线 通用思路 1、递归 #案例1-最优路测路线 题目描述 评估一个网络的信号质量&#xff0c;其中一个做法是将网络划分为栅格&#xff0c;然后对每个栅格的信号质量计算。 路测的时候&#xff0c;希望选择一条信号最好的路线&#xff08;彼此相连的栅格集合&#x…

人工智能的目标分类

欢迎来到 Papicatch的博客 目录 &#x1f349;引言 &#x1f349;目标分类的概述 &#x1f348;背景 &#x1f348;分类的重要性 &#x1f34d;明确研究重点 &#x1f34d;促进应用推广 &#x1f34d;便于评估和比较 &#x1f348;分类的原则 &#x1f34d;基于应用领…

F_GETDOWN的例子

代码&#xff1a; 7:46 2024/7/1#include <unistd.h> #include <fcntl.h> #include <stdio.h> int main(void) {int uid;int fdopen("test.txt",O_RDWR);uidfcntl(fd,F_GETOWN);printf("the SIG recv ID is %d\n",uid);close(fd);retur…

使用 Vue 实现包含单选框的弹窗功能(附Demo)

目录 前言1. Vue22. Vue3 前言 如果在弹窗中单独增设一些选项或者少部分的数据&#xff0c;可用如下的方式 &#xff08;不用单独创建专门的表单样式&#xff09; 如果单纯可以通过基本的按钮传输给后端&#xff0c;可用如下知识点 对于弹窗的基本知识推荐阅读&#xff1a; …

了解 ZooKeeper:关键概念和架构

ZooKeeper 是一种分布式协调服务&#xff0c;广泛用于分布式系统中&#xff0c;用于维护配置信息、命名、同步和组服务。它最初由雅虎开发&#xff0c;现在是一个 Apache 项目&#xff0c;已成为许多大型分布式应用程序不可或缺的一部分。本文深入探讨 ZooKeeper 的关键概念和架…

(四)Appdesigner-文件存在判断及对话框设计

目录 前言 一、文件存在判断 &#xff08;一&#xff09;基础知识 &#xff08;二&#xff09;实际操作 二、对话框设计 &#xff08;一&#xff09;基础知识 1.提示对话框 2.询问对话框 3.文件选择对话框 &#xff08;二&#xff09;实际操作 1.提示对话框 2.询问…

考研生活day1--王道课后习题2.2.1、2.2.2、2.2.3

2.2.1 题目描述&#xff1a; 解题思路&#xff1a; 这是最基础的操作&#xff0c;思路大家应该都有&#xff0c;缺少的应该是如何下笔&#xff0c;很多同学都是有思路但是不知道如何下笔&#xff0c;这时候看思路的意义不大&#xff0c;可以直接看答案怎么写&#xff0c;最好…

关于内存和外存文件不同字符集下占用空间大小问题

关于内存和外存不同字符集下文件占用空间大小问题 存储&#xff08;外存&#xff09;的文件中的字符&#xff1a; ASCII&#xff1a;每个字符占用1个字节&#xff0c;用来存储英文字符和常用标点符号。ISO-8859-1&#xff1a;每个字符占用1个字节&#xff0c;向下兼容ASCII。G…

【代码随想录】【算法训练营】【第53天】 [739]每日温度 [496]下一个更大元素I [503]下一个更大元素II

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 48&#xff0c;周六&#xff0c;不能再坚持~ 题目详情 [739] 每日温度 题目描述 739 每日温度 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 [496] 下一…

【一篇搞懂】操作系统期末大题:进程同步与互斥 PV操作

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️题型一&#xff1a;利用信号量实现前驱关系题型二&#xff1a;利用信号量实现资源同步与互斥 一、前言&#x1f680;&#x1f680;&#x1f680; 本文简介&#xff1a;这是一篇基于b…