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

前言

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

核销业务逻辑

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

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

总结

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

相关推荐
天天扭码1 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶1 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺6 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序13 分钟前
vue3 封装request请求
java·前端·typescript·vue
凡人的AI工具箱28 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
陈王卜31 分钟前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、31 分钟前
Spring Boot 注解
java·spring boot
先天牛马圣体33 分钟前
如何提升大型AI模型的智能水平
后端
java亮小白199736 分钟前
Spring循环依赖如何解决的?
java·后端·spring
飞滕人生TYF42 分钟前
java Queue 详解
java·队列