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

前言

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

核销业务逻辑

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

  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方法,组装明细,根据明细进行更新表数据;

总结

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

相关推荐
一只叫煤球的猫5 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9656 小时前
tcp/ip 中的多路复用
后端
bobz9656 小时前
tls ingress 简单记录
后端
皮皮林5517 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友7 小时前
什么是OpenSSL
后端·安全·程序员
bobz9658 小时前
mcp 直接操作浏览器
后端
前端小张同学10 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook10 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康11 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在11 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net