业务让我实现通用核销逻辑

前言

关于核销这个概念,在大部分支付的场景下会存在很多,现在很多平台像美团、大众点评、携程等,你去薅了某个商家的羊毛...准确的说应该是买了某个商家的优惠套餐,会生成一个二维码,当你去店家消费的时候店家会去扫这个二维码进行消费确认,这就是核销; 其次就是请款、还款的一些业务逻辑也涉及到核销的流程,并且有时候这些操作并不是一次性的,需要客户根据自己的实际情况分批进行请款、还款的操作,当然这个金额每次也都是不一样的,直到结清才是真正的核销完毕。

核销业务逻辑

整个业务逻辑大概是这样子的:

  1. 就是我通过一些渠道生成请款单,请款单上有两个金额字段分别是请款总金额和还款总金额;
  2. 请款单--->item明细表,一对多关系,当然还会衍生另外几个字段已请款金额、已还款金额、未请款金额、未还款金额,每次根据还款、请款的金额,进行计算;
  3. 每次请款、还款的金额可以随意,会根据请款单明细的生成时间排序,进行核销,先生成的优先进行核销,直到全部金额核销完毕;

通用核销抽象类代码实现

ini 复制代码
package com.yt.bms.bill.service;

import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springblade.core.tool.utils.CollectionUtil;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public abstract class NuclearCancelAbstract<T, K> {

    /**
     * 更新核销字段
     *
     * @param t            待核销的类
     * @param cancelAmount 核销金额
     * @param updateList   待核销的类集合
     * @param now          时间
     * @param kList        根据此集合获取一些更新的字段
     */
    public abstract void setNuclearCancelData(T t, BigDecimal cancelAmount, List<T> updateList, Date now, List<K> kList);


    /**
     * 核销
     *
     * @param realCancelList     使用此集合核销
     * @param stayCancelList     待核销的集合
     * @param uniqueNoField1      realCancelList核销的唯一标识,账单编号,或者订单号,或者运单号
     * @param uniqueNoField2      stayCancelList核销的唯一标识,账单编号,或者订单号,或者运单号
     * @param realAmountField    使用此字段核销
     * @param compareAmountField 对比的金额字段
     * @param now                时间
     * @return
     */
    public List<T> process(List<K> realCancelList, List<T> stayCancelList, String uniqueNoField1, String uniqueNoField2, String realAmountField, String compareAmountField, Date now) {
        if (now == null) {
            now = new Date();
        }
        //真实核销的金额
        Map<String, BigDecimal> realAmountMap = realCancelList.stream().collect(Collectors.groupingBy(k -> String.valueOf(ReflectUtil.getFieldValue(k, uniqueNoField1)), Collectors.mapping((k -> new BigDecimal(String.valueOf(ReflectUtil.getFieldValue(k, realAmountField)))), Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
        //待核销的金额
        Map<String, BigDecimal> stayCancelAmountMap = stayCancelList.stream().collect(Collectors.groupingBy(t -> String.valueOf(ReflectUtil.getFieldValue(t, uniqueNoField2)), Collectors.mapping(t -> new BigDecimal(String.valueOf(ReflectUtil.getFieldValue(t, compareAmountField))), Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));

        List<T> updateList = Lists.newArrayList();
        for (Map.Entry<String, BigDecimal> entry : stayCancelAmountMap.entrySet()) {
            String uniqueNo = entry.getKey();
            BigDecimal totalAmount = entry.getValue();
            List<T> willbeCancelList = stayCancelList.stream().filter(t -> String.valueOf(ReflectUtil.getFieldValue(t, uniqueNoField2)).equals(uniqueNo)).collect(Collectors.toList());
            if (CollectionUtil.isEmpty(willbeCancelList)) {
                continue;
            }
            final BigDecimal[] paidAmount = {realAmountMap.get(uniqueNo)};
            if (paidAmount[0].compareTo(totalAmount) >= 0) {
                //核销金额=未核销金额,则一次性全部核销完毕
                for (T t : willbeCancelList) {
                    this.setNuclearCancelData(t, new BigDecimal(String.valueOf(ReflectUtil.getFieldValue(t, compareAmountField))), updateList, now, realCancelList);
                    updateList.add(t);
                }
            } else {
                //核销金额小于未核销金额
                for (T t : willbeCancelList) {
                    if (paidAmount[0].compareTo(BigDecimal.ZERO) <= 0) {
                        log.info("金额已核销完毕!");
                        continue;
                    }
                    //核销金额
                    BigDecimal cancelAmount = BigDecimal.ZERO;
                    BigDecimal compareAmount = new BigDecimal(String.valueOf(ReflectUtil.getFieldValue(t, compareAmountField)));
                    if (paidAmount[0].compareTo(compareAmount) >= 0) {
                        cancelAmount = compareAmount;
                    } else {
                        cancelAmount = paidAmount[0];
                    }
                    this.setNuclearCancelData(t, cancelAmount, updateList, now, realCancelList);
                    updateList.add(t);

                    //每核销一笔,实际金额递减
                    paidAmount[0] = paidAmount[0].subtract(cancelAmount);
                }
            }
        }

        return updateList;
    }
}

通用的核销代码进行抽象,并且定义范型,可以针对不同业务场景下的对象实体;

代码解释

  • process方法会传进来两个范型的list集合,分别为主表的总核销金额和明细表待核销的金额;
  • 接下来根据uniqueNoField1、uniqueNoField2入参进行分组,这两个字段都是实际表里的业务关联主键字段;
  • 根据分组后的Map集合循环,拿到核销金额List集合,嵌套循环进行金额比对,虽然是双层for循环,内层循环只会根据paidAmount[0]去判断,永远是第一个需要核销的金额,其实不会影响多少性能;
  • 循环if判断核销金额与未核销金额进行比较,如果刚好相等则一次性进行核销,如果核销金额小于未核销金额,则去取paidAmount[0]金额,每次循环后面会进行,paidAmount[0] = paidAmount[0].subtract(cancelAmount); 核销金额递减,能确保需要核销的金额,能根据每次核销的金额进行变化;
  • 最后调用setNuclearCancelData方法,组装明细,根据明细进行更新表数据;

总结

这个抽象类的核销方法的封装,在目前业务场景下是可以实现核销逻辑操作的,如果核销逻辑有变化,存在多种规则,还是建议通过规则引擎来实现。

相关推荐
Java程序员威哥2 分钟前
使用Java自动加载OpenCV来调用YOLO模型检测
java·开发语言·人工智能·python·opencv·yolo·c#
小北方城市网9 分钟前
Spring Cloud Gateway实战:路由、限流、熔断与鉴权全解析
java·spring boot·后端·spring·mybatis
晨非辰11 分钟前
Linux权限实战速成:用户切换/文件控制/安全配置15分钟掌握,解锁核心操作与权限模型内核逻辑
linux·运维·服务器·c++·人工智能·后端
ZealSinger15 分钟前
Nacos2.x 事件驱动架构:原理与实战
java·spring boot·spring·spring cloud·nacos·架构·事件驱动
独行soc1 小时前
2026年渗透测试面试题总结-7(题目+回答)
java·网络·python·安全·web安全·渗透测试·安全狮
007php0072 小时前
PHP与Java项目在服务器上的对接准备与过程
java·服务器·开发语言·分布式·面试·职场和发展·php
sheji34162 小时前
【开题答辩全过程】以 民宿预订管理系统的设计与实现为例,包含答辩的问题和答案
java
刘大猫.3 小时前
XNMS项目-拓扑图展示
java·人工智能·算法·拓扑·拓扑图·节点树·xnms
正在努力Coding9 小时前
SpringAI - 工具调用
java·spring·ai
Loo国昌9 小时前
【LangChain1.0】第五阶段:RAG高级篇(高级检索与优化)
人工智能·后端·语言模型·架构