支付系统设计三:渠道网关设计07-后置处理

news2025/1/20 13:31:31

文章目录

  • 前言
  • 一、订单数据更新
    • 1. 领域模型更新服务工厂
    • 2. 聚合创建工厂
      • 2.1 数据库更新服务
      • 2.2 聚合创建工厂
  • 二、限流渠道入队
  • 三、异步通知
    • 1. 判断是否需要通知
    • 2. 组装异步通知报文
    • 3. 获取异步通知协议类型
    • 3. 异步通知
  • 总结


前言

在这里插入图片描述
本篇将继业务处理之后的后置处理逻辑进行介绍,在请求过支付渠道并将支付渠道响应的报文通过解析脚本转化为系统所需的统一对象了,接下来就是订单数据更新、订单入队(配置限流)和结果通知(同步/异步标识)流程了。


一、订单数据更新

 int count = domainDBUpdateServiceFactory.getDomainDBUpdateService(context.getClientTransCode()).update(context);

1. 领域模型更新服务工厂

领域模型数据库更新服务工厂定义,根据transCode+DomainDBSaveServiceImpl获取到领域模型数据库更新服务,获取不到则采用默认domainDBUpdateService服务实现。

/**
 * @author Kkk
 * @Describe: 领域模型更新服务工厂
 */
@Component
public class DomainDBUpdateServiceFactory {
    private static final Logger logger = LoggerFactory.getLogger(DomainDBUpdateServiceFactory.class);

    /**
     * 数据更新service bean 后缀
     */
    private final static String DOMAINDBUPDATESERVICE_SUFF = "DomainDBUpdateServiceImpl";

    /**
     * 数据库更新服务
     */
    @Autowired
    private Map<String, DomainDBUpdateService> domainDBUpdateServiceMap;

    /**
     * 默认数据库更新服务
     */
    @Resource(name = "domainDBUpdateService")
    private DomainDBUpdateService defaultDomainDBUpdateService;

    /**
     * 获取数据补全服务
     * @param transCode
     * @return
     */
    public DomainDBUpdateService getDomainDBUpdateService(String transCode) {
        String dbTransCode = TransactionManager.getTransCode(transCode);
        StringBuilder key = new StringBuilder();
        if (StringUtils.isNotBlank(dbTransCode)) {
            key.append(dbTransCode).append(DOMAINDBUPDATESERVICE_SUFF);
        } else {
            key.append(transCode).append(DOMAINDBUPDATESERVICE_SUFF);
        }
        DomainDBUpdateService domainDBUpdateService = domainDBUpdateServiceMap.get(key.toString());
        if (domainDBUpdateService == null) {
            LoggerUtil.info(logger, "交易({})-未获取到交易数据补全服务-采用默认服务", transCode);
            domainDBUpdateService = defaultDomainDBUpdateService;
        }
        return domainDBUpdateService;
    }
}

2. 聚合创建工厂

数据更新相关服务类关系如下:
在这里插入图片描述

2.1 数据库更新服务

域模型工厂的bean名称为域模型类名简称+BuildFactory。

默认数据库更新服务

/**
 * @author Kkk
 * @Describe: 默认数据库更新服务
 */
@Service("domainDBUpdateService")
public class DomainDBUpdateServiceImpl extends AbstractDomainDBUpdateService{
    private static final Logger logger = LoggerFactory.getLogger(DomainDBUpdateServiceImpl.class);

    @Override
    public void execute(PayGwContext context) {
        super.update(context);
    }
}

数据库更新服务抽象层

/**
 * @author Kkk
 * @Describe: 数据库更新服务抽象层
 */
@Service
public abstract class AbstractDomainDBUpdateService extends AbstractTransactionService implements DomainDBUpdateService {
    private static final Logger logger = LoggerFactory.getLogger(AbstractDomainDBUpdateService.class);

    /**
     * 默认域模型工厂
     */
    @Resource(name = "defaultAggregateBuildFactory")
    private DefaultAggregateBuildFactory defaultAggregateBuildFactory;

    @Override
    public int update(PayGwContext context) {
        LoggerUtil.info(logger, "交易({})-数据库状态更新-开始", context.getClientTransCode());

        int count = defaultAggregateBuildFactory.modify(context.getMessageDescription().getDatas());

        LoggerUtil.info(logger, "交易({})-数据库状态更新-更新记录数({})-结束", context.getClientTransCode(), count);

        return count;
    }
}

2.2 聚合创建工厂

默认聚合创建工厂

/**
 * @author Kkk
 * @Describe: 默认域模型工厂
 */
@Component("defaultAggregateBuildFactory")
public class DefaultAggregateBuildFactory {
    private static final Logger logger = LoggerFactory.getLogger(DefaultAggregateBuildFactory.class);

    private static final String DOMAIN_FACTORY_SUFF = "BuildFactory";//域模型构建工厂后缀
    private static final String DOMAIN_SUFF = "Domain";//domain后缀
    private static final String ENTITY_SUFF = "Entity";//entity后缀
    private static final String DOMAIN_REPOSITORY_SIMPLE_SUFF = "RepositoryImpl";//简略的域模型仓储后缀
    private static final String DOMAIN_REPOSITORY_SUFF = "DomainRepositoryImpl";//域模型仓储后缀
    
    /**
     * 交易数据入库
     * @param data
     */
    public void save(Map<String, Object> data) {
        LoggerUtil.info(logger, "交易({})-交易入库-开始", data.get(PayGwConstant.PAYGW_TRANS_CODE));
        //1、模型创建
        BusinessModel model = (BusinessModel) build(data);

        //2、模型持久化
        if (model != null) {
            store(model);
        }
        LoggerUtil.info(logger, "交易({})-交易入库-结束", data.get(PayGwConstant.PAYGW_TRANS_CODE));
    }

    /**
     * 模型构建
     * @param data
     * @return
     */
    public AggregateBase build(Map<String, Object> data) {
        //1、获取域模型Class
        String transCode = StringUtils.valueOf(data.get(PayGwConstant.PAYGW_TRANS_CODE));
        Class<? extends AggregateBase> domainClass = getDomainClassByTransCode(transCode);

        //2、新建模型
        AggregateBase domain = null;
        try {
            domain = domainClass.newInstance();
        } catch (Exception e) {
            LoggerUtil.error(logger, "交易({})-模型构建-构建模型异常", transCode);
            throw new PayGwException(SystemErrorCode.SYSTEM_ERROR, e);
        }

        //3、模型数据填充
        domain.fill(data);

        return domain;
    }

    /**
     * 根据交易码获取域模型
     * @param transCode
     * @return
     */
    public Class<? extends AggregateBase> getDomainClassByTransCode(String transCode) {
        return TransactionManager.getDomainClass(transCode);
    }

    /**
     * 模型存储
     * @param domain
     */
    public void store(AggregateBase domain) {
        //1、获取模型对应的仓储;
        BusinessModelRepository businessModelRepository = getBusinessModelRepository(domain.getClass());

        //2、调用仓储进行持久化;
        businessModelRepository.store(domain);
    }

    /**
     * @Description 模型修改
     * @Params
     * @Return
     * @Exceptions
     */
    public int modify(Map<String, Object> data) {
        //1、获取域模型
        String transCode = StringUtils.valueOf(data.get(PayGwConstant.PAYGW_TRANS_CODE));
        Class<? extends AggregateBase> domainClass = getDomainClassByTransCode(transCode);

        //2、获取模型对应的仓储
        BusinessModelRepository businessModelRepository = getBusinessModelRepository(domainClass);

        //3、调用仓储进行修改
        return businessModelRepository.modify(data);
    }

    /**
     * @Description 获取域模型仓储
     * @Params
     * @Return
     * @Exceptions
     */
    public BusinessModelRepository getBusinessModelRepository(Class domainClass) {
        String domainClassName = StringUtils.convertFirstCharToLower(domainClass.getSimpleName());
        String beanName = "";
        if (domainClassName.endsWith(DOMAIN_SUFF) || domainClassName.endsWith(ENTITY_SUFF)) {
            beanName = domainClassName + DOMAIN_REPOSITORY_SIMPLE_SUFF;
        } else {
            beanName = domainClassName + DOMAIN_REPOSITORY_SUFF;
        }
        return ApplicationContextUtil.getBean(beanName, BusinessModelRepository.class);
    }
    // ... ...
}

模型修改操作如下:

  public int modify(Map<String, Object> data) {
      //1、获取域模型
      String transCode = StringUtils.valueOf(data.get(PayGwConstant.PAYGW_TRANS_CODE));
      Class<? extends AggregateBase> domainClass = getDomainClassByTransCode(transCode);

      //2、获取模型对应的仓储
      BusinessModelRepository businessModelRepository = getBusinessModelRepository(domainClass);

      //3、调用仓储进行修改
      return businessModelRepository.modify(data);
  }

模型修改:

//1、获取域模型 //2、获取模型对应的仓储 //3、调用仓储进行修改

二、限流渠道入队

  String processStatus = StringUtils.valueOf(data.get(PayGwConstant.PAYGW_PROCESS_STATUS));
  if (ProcessStatusEnum.QUEUEING.getCode().equals(processStatus)) {
      String instCode = StringUtils.valueOf(data.get(PayGwConstant.INST_CODE));
      String instTransId = StringUtils.valueOf(data.get(PayGwConstant.INST_TRANS_ID));
      String channelCode = StringUtils.valueOf(data.get(PayGwConstant.CHANNEL_CODE));
      String channelTransNo = StringUtils.valueOf(data.get(PayGwConstant.CHANNEL_TRANS_NO));

      String rateFlagRedisVal = channelCode + "|" + channelTransNo;

      rateLimitService.enqueueHeadForSend(instCode, instTransId, rateFlagRedisVal);
  }

判断processStatus状态是否为QUEUEING排队中,如果是则入队:

  @Override
  public void enqueueHeadForSend(String instCode, String instTransId, String val) {
      //1、获取redisKey
      String rateFlagRedisKey = "rateLimit_" + instCode + "_" + instTransId;
      LoggerUtil.info(logger, "限流渠道入队列首部:{}-{}", rateFlagRedisKey, val);
      //进行排队队列
      try {
          jedisCluster.lpush(rateFlagRedisKey, val);
      } catch (Exception e) {
          LoggerUtil.error(logger, "限流渠道入队列异常:{}-{}", rateFlagRedisKey, val);
      }
  }

入队后等待消费方从Redis队列中中获取到任务实体,然后组装请求支付渠道报文,进行请求。

三、异步通知

1. 判断是否需要通知

首先判断交易上送的同步/异步标识,如果为异步并且交易为终态,则通知上游系统,即paygw–>paycore的通知。

2. 组装异步通知报文

根据上送的支付渠道和交易类型信息获取到通讯信息,从通讯信息获取到关联的模板,如上paycore–>paygw通讯的报文模板,然后使用报文组装引擎进行模板填充。

#set($map =
{
    "header": {
    "transCode": "$!data.transCode",
    "transType": "$!data.transType",
    "channelCode": "$!data.channelCode",
    "channelTransNo": "$!data.channelTransNo",
    "channelDateTime": "$!DateUtil.format($data.channelDateTime , 'yyyyMMddHHmmss')",
    "success": "$!data.success",
    "errorCode": "$!data.errorCode",
    "errorMsg": "$!data.errorMsg"
},
    "body": {
    "transStatus": "$!data.transStatus",
    "respCode": "$!data.respCode",
    "respMsg": "$!data.respMsg",
    "transNo": "$!data.transNo",
    "transDateTime": "$!DateUtil.format($data.transDateTime , 'yyyyMMddHHmmss')",
    "transAmount": "$!data.transAmount",
    "acctNo": "$!data.acctNo",
    "uuid": "$!data.uuid",
    "content": "$!data.content",
    "instCode": "$!data.instCode",
    "instRespNo": "$!data.instRespNo",
    "instTransStatus": "$!data.instTransStatus",
    "instTransDate": "$!DateUtil.format($data.instTransDate , 'yyyyMMdd')",
    "instMerCode": "$!data.instMerCode",
    "instAcctNo": "$!data.instAcctNo"
}
})
#set($json = $map.get("body").put("content", $!data.content))
$!MapUtils.toJsonStr($map)

3. 获取异步通知协议类型

根据上送的支付渠道和交易类型信息获取到通讯信息,从通讯信息获取到异步通知协议,即配置通讯信息时的异步通知协议:
在这里插入图片描述

3. 异步通知

在这里插入图片描述

NotifyFactory.getNotify(notifyProtocolType).notify(notifyKey, notifyMessage);

从通知器工厂根据配置的通知协议获取到通知客户端进行结果通知。


总结

本篇简略介绍了订单数据更新、订单入队和结果通知实现流程。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

瑞吉外卖 - 后台系统退出功能(4)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

瑞吉外卖 - 项目介绍(1)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

CSS盒子模型、表格标签(table)、表单标签(form)

盒子&#xff1a;页面中所有的元素&#xff08;标签&#xff09;&#xff0c;都可以看做是一个 盒子&#xff0c;由盒子将页面中的元素包含在一个矩形区域内&#xff0c;通过盒子的视角更方便的进行页面布局 盒子模型组成&#xff1a;内容区域&#xff08;content&#xff09;…

Qt扫盲-QScatterSeries理论总结

QScatterSeries理论总结 一、概述二、使用三、扩展四、扩展使用1.创建描述散点图对象2. 对散点图像添加值3. 自定义散点4. 将绘图设备与散点图对象联系5. 设置坐标轴6. 将绘图设备与GUI控件绑定并显示 一、概述 QScatterSeries 类以散点图的形式呈现数据。散点数据在图表上显示…

基于jdk1.8的Java服务监控和性能调优

JVM的参数类型 X参数 非标准参数-Xint: 解释执行-Xcomp: 第一次使用就编译成本地代码-Xmixed: JVM自己来决定是否编译成本地代码 默认使用的是mixed mode 用的不多, 只需要做了解, 用的比较多的是XX参数 XX参数 非标准化参数相对不稳定主要用来JVM调优和Debug Boolean: …

Vivado综合属性系列之一 ASYNC_REG

目录 一、属性简介 二、示例 2.1 工程说明 ​ ​2.2 工程代码 ​ ​2.3 生效确认 一、属性简介 ASYNC_REG属性的作用对象为寄存器&#xff0c;寄存器添加该属性后&#xff0c;即表明寄存器的数据输入口D接收的是来自异步时钟触发器的数据或是该寄存器在一个同步链中属于…

【CSS系列】第九章 · CSS定位和布局

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

Uni-app 离线打包 apk

Uni-app 离线打包 apk 1. Android Studio 下载 Android Studio官网 2. HBuilderX下载 HBuilderX下载 3. App离线SDK下载 Android 离线SDK - 正式版 下载后解压文件&#xff0c;将 HBuilder-Integrate-AS 重命名 build-template 并拷贝到一个专门打包用的文件夹下作为打包…

一行代码绘制高分SCI限制立方图

一、概述 Restricted cubic splines (RCS)是一种基于样条函数的非参数化模型&#xff0c;它可以可靠地拟合非线性关系&#xff0c;可以自适应地调整分割结点。在统计学和机器学习领域&#xff0c;RCS通常用来对连续型自变量进行建模&#xff0c;并在解释自变量与响应变量的关系…

抑梯度异常初始化参数(防止梯度消失和梯度爆炸)

这里设置3种参数初始化的对比&#xff0c;分别是&#xff1a;全初始化为0、随机初始化、抑梯度异常初始化。 首先是正反向传播、画图、加载数据所需的函数init_utils.py&#xff1a; # -*- coding: utf-8 -*-import numpy as np import matplotlib.pyplot as plt import sklea…

双层优化入门(3)—基于智能优化算法的求解方法(附matlab代码)

前面两篇博客介绍了双层优化的基本原理和使用KKT条件求解双层优化的方法&#xff0c;以及使用yalmip工具箱求解双层优化的方法&#xff1a; 双层优化入门(1)—基本原理与求解方法 双层优化入门(2)—基于yalmip的双层优化求解(附matlab代码) 除了数学规划方法之外&#xff0c;…

springboot+vue大学生体质测试管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的大学生体质测试管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xf…

how2heap-fastbin_dup.c

不同libc版本的fastbin_dup.c源码有点小区别&#xff1a;主要是有tcache的&#xff0c;需要先填充 以下为有tcache的源码示例&#xff1a; #include <stdio.h> #include <stdlib.h> #include <assert.h>int main() {setbuf(stdout, NULL);printf("This…

诗词·宇宙之梦

宇宙之梦 重力枷锁必将断&#xff0c;携君翱翔万里空。 迷途夜路寻踪迹&#xff0c;一声呼唤莫轻忽。 寻觅中&#xff0c;见红瞳&#xff0c;决不装假觉清白。 黑泽之中君沉沦&#xff0c;放之不下心怎静。 重力终将解开放&#xff0c;卫星翔空自由翱。 重量减半去忧愁&#xf…

RobotFramework+Eclispe环境安装篇

环境安装是学习任何一个新东西的第一步&#xff0c;这一步没走舒坦&#xff0c;那后面就没有心情走下去了。 引用名句&#xff1a;工欲善其事必先利其器&#xff01;&#xff01; Robotframework&#xff1a;一款 自动化测试框架。 Eclipse&#xff1a;一款编辑工具。可以编…

Android MVVN 使用入门

MVVM&#xff08;Model-View-ViewModel&#xff09;是一种基于数据绑定的设计模式&#xff0c;它与传统的 MVC 和 MVP 模式相比&#xff0c;更加适合处理复杂的 UI 逻辑和数据展示。在 Android 开发中&#xff0c;MVVM 通常使用 Data Binding 和 ViewModel 实现。 下面是一个简…

正则化解决过拟合

本片举三个例子进行对比&#xff0c;分别是&#xff1a;不使用正则化、使用L2正则化、使用dropout正则化。 首先是前后向传播、加载数据、画图所需要的相关函数的reg_utils.py&#xff1a; # -*- coding: utf-8 -*-import numpy as np import matplotlib.pyplot as plt impor…

双层优化入门(2)—基于yalmip的双层优化求解(附matlab代码)

上一篇博客介绍了双层优化的基本原理和使用KKT条件求解双层优化的方法&#xff1a; 双层优化入门(1)—基本原理与求解方法 这篇博客将介绍使用yalmip的双层优化问题的求解方法。 1.KKT函数 通过调用yalmip工具箱中的KKT函数&#xff0c;可以直接求出优化问题的KKT条件&#x…

算法(一)—— 回溯(2)

文章目录 1 131 分割回文串2 93 复原 IP 地址 s.substr(n, m) // 从字符串s的索引n开始&#xff0c;向后截取m个字符 例&#xff1a; string s "aaabbbcccddd"; string s1 s.substr(2,3); 此时s1为abb 1 131 分割回文串 切割问题&#xff0c;前文均为组合问题。组…

【Promptulate】一个强大的LLM Prompt Layer框架

本文节选自笔者博客&#xff1a; https://www.blog.zeeland.cn/archives/promptulate666 项目地址&#xff1a;https://github.com/Undertone0809/promptulate &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是Zeeland&#xff0c;全栈领域优质创作者。&#x1f4dd;…