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

前言

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

核销业务逻辑

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

  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循环,内层循环只会根据paidAmount0去判断,永远是第一个需要核销的金额,其实不会影响多少性能;
  • 循环if判断核销金额与未核销金额进行比较,如果刚好相等则一次性进行核销,如果核销金额小于未核销金额,则去取paidAmount0金额,每次循环后面会进行,paidAmount0 = paidAmount0.subtract(cancelAmount); 核销金额递减,能确保需要核销的金额,能根据每次核销的金额进行变化;
  • 最后调用setNuclearCancelData方法,组装明细,根据明细进行更新表数据;

总结

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

相关推荐
根目录下的猫3 分钟前
虚拟机复制过来运行时:“虚拟机使用的此版本,VMware Workstation 不支持的硬件版本。”错误解决办法,亲测可用
linux·运维·服务器·后端
Wang's Blog14 分钟前
Java 中间件之 RabbitMQ 快速入门: MQ 常见技术选型对比
java·中间件·java-rabbitmq
蜗牛互联网15 分钟前
Java Agent 工具调用的 allowlist、参数校验与调用预算
java·开发语言·人工智能·后端·oracle
用户8132679332515 分钟前
行情数据晚到几秒,会让量化策略失去优势吗?从信号时间到回测偏差
后端·github·api
大文说跨境15 分钟前
多账号环境隔离方案技术选型:指纹浏览器、VPS 与云手机的三种架构对比
java·开发语言·前端
九零HTTP16 分钟前
一次 TCP 连接的一生:从三次握手到四次挥手
后端
ZOnePieceC20 分钟前
消息队列之Kafka
后端
yunwei3720 分钟前
eBPF 入门实践教程十七:编写 eBPF 程序统计随机/顺序磁盘 I/O
linux·后端·性能优化
花间相见21 分钟前
【计算基础|网络07】HTTPS(下):ECDHE 握手与优化
后端
QuantiCore_IO22 分钟前
从请求风暴到可维护的数据管道:量化系统为什么需要批量接口?
后端·github·api