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

一、接着上文

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

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

四、总结

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot+vue 开发记录(八) 前端项目打包

本篇文章涉及到前端项目打包的一些说明 我打包后的项目在部署到服务器上后&#xff0c;访问页面时按下F12出现了这种情况&#xff1a; 它显示出了我的源码&#xff0c;这是一种很不安全的行为 该怎么办&#xff1f;很简单&#xff1a; 我们只需要下载一点点插件&#xff0c;再…

怎样查看vsphere client 的登录日志

- 问题摘要&#xff1a; 怎样查看vsphere client 的登录日志 - 解决方案/工作方法 1.登录vsphere client > vc > Monitor > Tasks and Events > Events, 查看日志 2. 查看VC 的websso.log日志 /var/log/vmware/sso/websso.log 3. 可以把websso.log文件拿到本地电…

苏东坡传-读书笔记六

苏东坡今生的浩然之气用尽。人的生活也就是心灵的生活&#xff0c;这种力量形成人的事业人品&#xff0c;与生命俱来&#xff0c;由生活中之遭遇而显示其形态。正如苏东坡在潮州韩文公庙碑中所说&#xff1a;“浩然之气、不依形而立&#xff0c;不恃力而行&#xff0c;不待生而…

智能驾驶系列报告:特斯拉智能驾驶方案简剖

不同于绝大多数国内车企在自动驾驶上采取多传感器融合方案&#xff0c;特斯拉FSD在发展初期就摒弃激光雷达、且不配备高清地图&#xff0c;成为在感知层以摄像头为核心的纯视觉解决方案代表;其依靠车身搭载的摄像头来捕捉周围的环境信息&#xff0c;并经过算法及神经网络模型处…

手机ip地址是实时位置吗

在数字时代&#xff0c;手机IP地址对于许多用户而言&#xff0c;似乎是一个既神秘又重要的存在。不少人都曾听说过&#xff0c;通过追踪IP地址可以定位到某个人的大致位置。那么&#xff0c;手机IP地址真的是实时位置的精确反映吗&#xff1f;本文将带您深入了解手机IP地址的真…

「网站开发必备」8款免费 React Gallery, Lightbox, 和 Photo Viewer开发库

大家好&#xff0c;今天给大家分8款免费 React Gallery, Lightbox, 和 Photo Viewer开发库。 在不断发展的网络开发世界中&#xff0c;开源库提供了大量创新和效率的机会。本文将带您了解一些用于Gallery, Lightbox, 和 Photo Viewer的最好的开源 React 库&#xff0c;为您的下…

2.00004 优化器执行计划生成的流程是怎么样的?

文章目录 整体架构关键结构体PlannerInfo (pathnodes.h:195)PlannerGlobal (pathnodes.h:95)函数栈关键函数pg_plan_query (postgres.c:885)planner (planner.c:274)standard_planner (planner.c:287)subquery_planner (planner.c:628)整体架构 关键结构体 PlannerInfo (pathn…

基于CST2024 Python内部环境的双锥天线自动3D建模和仿真

CST Studio Suite 2024版里面的Python相较于之前有了大的变化。 第一&#xff0c; 增加了cst.asymptotic &#xff0c;cst.radar &#xff0c;cst.units 三个包。 第二&#xff0c;之前CST python只能通过外部环境去操作&#xff0c;现在增加了内部环境控制&#xff0c;可以内…

Zabbix对接Elasticsearch(ES)数据库(未成功)

0.需求分析 不管zabbix的后端数据库是oracle还是mysql&#xff0c;当zabbix监控的量级达到了一定程度后&#xff0c;那么对数据库的性能是一个非常严峻的挑战。特别是对历史数据的查询&#xff0c;将会变得非常非常的慢&#xff0c;别告诉我可以建索引优化&#xff0c;当量级达…

8.12 矢量图层面要素单一符号使用十二(插值线渲染边界)

文章目录 前言插值线渲染边界&#xff08;Outline: Interpolated Line&#xff09;QGis设置面符号为插值线渲染边界&#xff08;Outline: Interpolated Line&#xff09;二次开发代码实现插值线渲染边界&#xff08;Outline: Interpolated Line&#xff09; 总结 前言 本章介绍…

C++ STL unique_ptr智能指针源码剖析

由于上一篇博客将shared_ptr,weak_ptr,enable_shared_form_this的源码实现整理了一遍,想着cpp智能指针还差个unique_ptr故写下此篇博客,以供学习 源码剖析 一,模板参数 首先,我们先看unique_ptr的模板参数,第一个参数_TP自是不用说表示对象类型,第二个模板参数定义了unique_p…

适合任何行业在线DIY预约报名小程序源码系统 带完整的安装代码包以及搭建教程

系统概述 在当今数字化时代&#xff0c;便捷高效的预约报名系统成为了许多行业的迫切需求。“适合任何行业在线 DIY 预约报名小程序源码系统”便是一款为满足这一需求而设计开发的创新解决方案。 这款源码系统是基于先进的技术架构&#xff0c;旨在为各类企业和组织提供一个强…

Avalonia 常用控件三 Window窗体相关二

1、效果演示 2、在Views中创建WindowDemo.axaml如下图 WindowDemo.axaml代码如下 <Window xmlns"https://github.com/avaloniaui"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend/…

Python读写文本、图片、xml

Python操作文本、图片、xml 1.Python读写文本1.1文本读取1.2文本写入 2.Python读取、显示图片3.Python读写Xml &#xff08;1&#xff09;Python读写文本 &#xff08;2&#xff09;Python读取、显示图片 &#xff08;3&#xff09;Python读写Xml 1.Python读写文本 新建hellow…

mybatis延迟加载

mybatis延迟加载 1、延迟加载概述 应用场景 ​ 如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求&#xff0c;当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。 延迟加载的好处 ​ 先从单表查询、需要时再从关联表去关联查…

el-input-number 点击加减只能加一次

el-input-number 点击加减只能加一次 <el-input-number v-model"editForm.quantity" placeholder"请输入下单数量(店均)" change"quantityChangeFn"></el-input-number>需要在方法里面加 this.$forceUpdate() quantityChangeFn(val…

web学习笔记(六十九)vue2

目录 1. vue2创建脚手架项目 2.vue2如何关闭eslint 1. vue2创建脚手架项目 &#xff08;1&#xff09;在cmd窗口输入npm install -g vue/cli命令行&#xff0c;快速搭建脚手架。 &#xff08;2&#xff09; 创建vue2项目 &#xff08;3&#xff09; 选择配置项目&#xff0c…

前端学习篇一(HTML)

Introduction ##文章内容&#xff1a;使用HBuilder制作一个简单的HTML5网页以此达到学习HTML5 的目的 ##编写内容&#xff1a;1.HTML实现平台 2.HTML简介 3.HTML语言解析 ##编写人&#xff1a;贾雯爽 ##最后更新时间&#xff1a;2024/07/01 Overview Details 一、HTML简介…

骑行十里箐:风景,挑战与心灵,在幽谷中的协奏曲

2024年6月29日&#xff0c;星期六&#xff0c;一个看似平凡的日子&#xff0c;却因一次不同寻常的骑行而变得难以忘怀。作为校长骑行群的一员&#xff0c;我有幸参加了这次骑行十里箐的活动。从滇池后海的宁静开始&#xff0c;到宝珠山顶的壮观落幕&#xff0c;这一天的旅程充满…

本地Navicat/客户端连接阿里云RDSMySQL时遇到过的问题及解决

1.之前开发的RDS MySQL版本和本地MySQL版本最好接近&#xff0c;比如8.0.28和8.0.20好像都是可以兼容的&#xff0c;他们里面都有那个utf8的字符编码&#xff0c;但是后面我选的RDS MySQL版本有点新&#xff0c;是8.0.30甚至更新的版本&#xff0c;之前用C#语言写的连接MySQL以…