支付系统设计三:支付网关设计09-总结

news2024/12/27 13:40:53

文章目录

  • 前言
  • 一、设计目标
  • 二、设计实现
    • 1. 开发框架
    • 2. 配置管理后台
    • 3. 屏蔽渠道差异
    • 4. 各阶段工作内容
      • 4.1 业务处理前期准备阶段
      • 4.2 业务处理阶段
        • 4.2.1 交易处理模板获取
        • 4.2.2 参数验证
        • 4.2.3 幂等性验证
        • 4.2.4 交易数据准备服务获取
        • 4.2.5 路由处理
        • 4.2.6 支付渠道数据补全
        • 4.2.7 交易信息入库
        • 4.2.8 业务处理
        • 4.2.9 后置处理
      • 4.3 业务处理后期响应阶段
        • 4.3.1 请求响应
        • 4.3.2 渲染视图
  • 总结


前言

从《支付系统设计三:渠道网关设计01-总览》—>《支付系统设计三:渠道网关设计08-结果响应》几篇文章主要讲解了支付网关系统为了实现在线对接支付渠道的实现脉络。
在这里插入图片描述


一、设计目标

在这里插入图片描述

为了增加复用、缩短业务的落地时间,就需要很多通用的能力、产品。在我们的交付过程中,主要有两个层次:

基础能力:相对原子的能力是基础(域)能力,这个可以较好地支持业务定制。由于比较基础,表达的产品能力范围也是很大的。

平台产品:基础能力的通用性,意味着缺少对场景的理解,缺少了进一步提升生产效率的“基因”。所以在交付的时候,会基于一些高频场景进行抽象,形成平台的产品能力,争取做到“拆箱即用”。业务基于“平台产品”这层进行定制的时候,理解成本会大大减少。

payGw系统作为基础能力系统:

  • 1.统一支付出口,提供丰富的支付工具原子能力(代扣、批扣、代付、批付、快捷支付、网关支付、鉴权、银行卡签约等);
  • 2.与业务场景解耦,业务场景的多变特点不会体现在Paygw系统中 ;
  • 3.屏蔽各支付渠道的接入差异(通讯方式差异、加密方式差异、业务报文差异);
  • 4.快速接入支付渠道的能力(可以达到不停机接入);

如下为了给各业务线提供代扣业务能力,所以支付平台对接了不同的支付渠道的代扣产品,对于大型的支付系统可能对接了几十家支付渠道。
在这里插入图片描述
当我们将支付系统做成一个产品提供给没有自研能力的但是需要支付环节的公司时候,如何满足甲方公司对系统稳定性、快速接入支付渠道的需求?同时降低乙方建设成本?答案是:建立样板工程,将项目产品化,然后拿着样板工程去寻目标客户,符合度越高,后期工程实施成本越低,稍微变动即可满足客户需求,同时具有高扩展性,对于客户定制化需求也能轻松满足。

paygw系统作为支付基础能力提供的系统,通过对接不同的支付渠道为支付产品层(paycore系统)提供支付工具原子能力,由于不同的支付渠道所需要的请求参数,报文格式,通讯协议等个不相同,那么如何能屏蔽这些不同,快速接入支付渠道?对接支付渠道时不需要进行系统上线,不用披着北京凌晨的月光回家,只需要简单配置就能完成系统对接工作,白天也可以进行系统上线,带着这个设计目标开始我们的整个流程设计。

本系列博文,就国内支付系统特性进行分析总结,目标设计开发一套具有高可复用性的支付系统,可以轻松满足大多数公司的支付功能需求。

二、设计实现

1. 开发框架

在《支付系统设计二:统一开发框架》的基础上,进行paygw子系统具体代码编写。
在这里插入图片描述

2. 配置管理后台

在对接新支付渠道时,后台管理系统需要进行对应的配置,因为以不停机作为系统设计目标,所以最大化参数配置,将可变的参数进行系统话配置,最大限度的满足不同支付渠道的需要。
在这里插入图片描述

3. 屏蔽渠道差异

在这里插入图片描述
分别从以下四个阶段介绍如何屏蔽各个支付渠道的差异:接收客户端请求阶段、服务端消息发送阶段、接收服务端返回报文、客户端请求返回阶段。
在这里插入图片描述
对于如何屏蔽不同支付渠道的差异,主要是将通讯相关内容进行配置化,并使用脚本Groovy和模板引擎Velocity分别进行解析、组装工作,总之思想就是将可变的参数进行配置化。

4. 各阶段工作内容

根据不同维度,我们可以把这个流程拆分多个环节,下面我们将开篇12个处理过程按照普通开发人员的程序编码思想粗略的拆解为三个阶段。业务处理阶段、业务处理前期准备阶段、业务处理后期响应阶段。这三层阶段划分比较符合我们日常的逻辑处理思想,比如写接收支付渠道交易结果接口,是不是先将渠道报文转化为系统统一对象,处理业务,构建响应对象。

下面我们从这个角度再进行总结下,希望感兴趣的同学能够知道我在说什么。
在这里插入图片描述

4.1 业务处理前期准备阶段

业务处理前期准备阶段如提供的交易结果回调通知接口将支付渠道的请求报文转换为系统对象的过程,同样的在我们写结果回调通知接口的时候我们需要看渠道接口文档,确定其使用的协议类型(http/https/hessian等)、报文格式(json/xml/byte等)、编码(UTF-8/GBK/GB2312等)等才能知道怎么转换为系统所需的对象。

同样的,在我们这个系统设计中,只是将这些作为可配置化,根据对应的协议类型获取到对应的协议处理器进行处理,然后根据报文格式以及编码进行数据读取,然后再根据对应的解析脚本将读取到的数据转化为系统所需的指定对象。

4.2 业务处理阶段

在业务处理阶段主要是通过全局上下文中的MessageDescription属性贯穿整个流程处理:
在这里插入图片描述

交易管理类,可以根据transCode找到对应的映射处理。

/**
 * @author Kkk
 * @Describe: 交易管理
 */
public class TransactionManager {
    /**
     * 交易-模板映射
     */
    private static final Map<TransactionEnum, String> templateMapping = new HashMap<TransactionEnum, String>();
    /**
     * 交易-模型映射
     */
    private static final Map<TransactionEnum, Class<? extends AggregateBase>> demainMapping = new HashMap<TransactionEnum, Class<? extends AggregateBase>>();
    /**
     * 交易-查询交易映射
     */
    private static final Map<TransactionEnum, TransactionEnum> queryTransactionMapping = new HashMap<TransactionEnum, TransactionEnum>();
    /**
     * 交易-流水序列类型
     */
    private static final Map<TransactionEnum, SeqTypeEnum> seqTypeMapping = new HashMap<TransactionEnum, SeqTypeEnum>();
    /**
     * 交易-交易码前缀映射
     */
    private static final Map<TransactionEnum, String> transCodeMapping = new HashMap<>();

    static {
        initTemplateMapping();
        initDemainMapping();
        initQueryTransactionMapping();
        initSeqTypeMapping();
        initTransCodeMapping();
    }

    /**
     * 初始化交易-模板映射
     */
    private static void initTemplateMapping() {
        templateMapping.put(TransactionEnum.WAP_SIGN, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.WAP_SIGN_QRY, "queryTransTemplate");
        templateMapping.put(TransactionEnum.QUICK_PAY, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.QUICK_PAY_SEND_MSG, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.QUICK_PAY_CONFIRM, "sigleConfirmTemplate");
        templateMapping.put(TransactionEnum.QUICK_PAY_QRY, "queryTransTemplate");
        templateMapping.put(TransactionEnum.WAP_PAY, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.WAP_PAY_QRY, "queryTransTemplate");
        templateMapping.put(TransactionEnum.DEDUCT, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.DEDUCT_ASY, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.DEDUCT_QRY, "queryTransTemplate");
        templateMapping.put(TransactionEnum.DEPUTE, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.DEPUTE_ASY, "sigleTransTemplate");
        templateMapping.put(TransactionEnum.DEPUTE_QRY, "queryTransTemplate");
        templateMapping.put(TransactionEnum.BATCH_DEDUCT, "batchTransTemplate");
        templateMapping.put(TransactionEnum.BATCH_DEDUCT_QRY, "queryTransTemplate");
        templateMapping.put(TransactionEnum.BATCH_DEPUTE, "batchTransTemplate");
        // ... ...
    }

    /**
     * 初始化交易-交易码前缀映射
     */
    private static void initTransCodeMapping() {
        transCodeMapping.put(TransactionEnum.WAP_PAY, "wapPay");
        transCodeMapping.put(TransactionEnum.WAP_PAY_QRY, "wapPay");
        transCodeMapping.put(TransactionEnum.DEDUCT_ALLOT_ACCT, "deduct");
        transCodeMapping.put(TransactionEnum.DEDUCT_ALLOT_ACCT_QRY, "deduct");
        // ... ...
    }

    /**
     * 初始化交易-模型映射
     */
    private static void initDemainMapping() {
        demainMapping.put(TransactionEnum.WAP_PAY, WapPay.class);
        demainMapping.put(TransactionEnum.WAP_PAY_QRY, WapPay.class);
        demainMapping.put(TransactionEnum.DEDUCT, Deduct.class);
        demainMapping.put(TransactionEnum.DEDUCT_ASY, Deduct.class);
        //... ...
    }

    /**
     * 初始化交易-查询交易映射
     */
    private static void initQueryTransactionMapping() {
        queryTransactionMapping.put(TransactionEnum.WAP_PAY, TransactionEnum.WAP_PAY_QRY);
        queryTransactionMapping.put(TransactionEnum.DEDUCT, TransactionEnum.DEDUCT_QRY);
        queryTransactionMapping.put(TransactionEnum.DEDUCT, TransactionEnum.DEDUCT_QRY);
        queryTransactionMapping.put(TransactionEnum.DEDUCT_ASY, TransactionEnum.DEDUCT_QRY);
        // ... ...
    }

    /**
     * 初始化交易-流水序列映射
     */
    private static void initSeqTypeMapping() {
        seqTypeMapping.put(TransactionEnum.AUTH, SeqTypeEnum.SEQ_AUTH);
        seqTypeMapping.put(TransactionEnum.AUTH_SEND_MSG, SeqTypeEnum.SEQ_SMS);
        seqTypeMapping.put(TransactionEnum.AUTH_CONFIRM, SeqTypeEnum.SEQ_AUTH);
       //... ...
    }

    /**
     * 根据交易码获取模板
     * @param transCode
     * @return
     */
    public static String getTemplate(String transCode) {
        TransactionEnum transaction = TransactionEnum.getByCode(transCode);
        return templateMapping.get(transaction);
    }

    /**
     * 根据交易码获取映射
     * @param transCode
     * @return
     */
    public static String getTransCode(String transCode) {
        TransactionEnum transaction = TransactionEnum.getByCode(transCode);
        return transCodeMapping.get(transaction);
    }

    /**
     * 根据交易码获取模型类
     * @param transCode
     * @return
     */
    public static Class<? extends AggregateBase> getDomainClass(String transCode) {
        TransactionEnum transaction = TransactionEnum.getByCode(transCode);
        return demainMapping.get(transaction);
    }

    /**
     * 根据交易码获取查询交易码
     * @param transCode
     * @return
     */
    public static String getQueryTransCode(String transCode) {
        TransactionEnum transaction = TransactionEnum.getByCode(transCode);
        return queryTransactionMapping.get(transaction).getCode();
    }

    /**
     * 根据交易码获取流水序列
     * @param transCode
     * @return
     */
    public static SeqTypeEnum getSeqType(String transCode) {
        TransactionEnum transaction = TransactionEnum.getByCode(transCode);
        SeqTypeEnum se = seqTypeMapping.get(transaction);
        if (se == null) se = SeqTypeEnum.SEQ_COMM;
        return se;
    }
}

4.2.1 交易处理模板获取

在进行业务处理第一步先根据客户端交易码从交易-模板映射Map中获取到对应的交易处理模板。
在这里插入图片描述

private static final Map<TransactionEnum, String> templateMapping = new HashMap<TransactionEnum, String>();

如Wap支付对应的 sigleTransTemplate

templateMapping.put(TransactionEnum.WAP_PAY, "sigleTransTemplate");

4.2.2 参数验证

由于我们在使用解析脚本进行参数转化时候将系统调用上送参数存储到MessageDescription中的datas(Map)中,所以我们得想办法校验对应的交易参数是否合法,我们采用Yml配置化方式进行参数校验。
在这里插入图片描述

根据transCode获取对应的校验器集合进行参数校验:

/**
 * 参数校验
 * @param transCode
 * @param data
 */
public void validate(String transCode, Map<String, Object> data) {
    Map<String, List<FieldPolicy>> fieldPolicyListMap = this.fieldValidateYamlProcessor.getFieldPolicyListMap();
    if (!CollectionUtils.isEmpty(fieldPolicyListMap)) {
        List<FieldPolicy> fieldPolicyList = fieldPolicyListMap.get(transCode);
        if (!CollectionUtils.isEmpty(fieldPolicyList)) {
            for (FieldPolicy fieldPolicy : fieldPolicyList) {
                fieldPolicy.validate(StringUtils.valueOf(data.get(fieldPolicy.getField())));
            }
        }
    }
}

每一种交易类型配置完成一次就行了,此处不再展开了。

4.2.3 幂等性验证

使用Redis进行幂等校验,比较简单,不再展开了。

4.2.4 交易数据准备服务获取

在这里插入图片描述
通过约定的格式名称进行交易数据准备服务的获取:

 /**
  * 准备数据service bean 后缀
  */
 private final static String PREPAREDATASERVICE_SUFF = "PrepareDataServiceImpl";

 /**
  * 数据补全服务缓存
  */
 @Autowired
 private Map<String, PrepareDataService> prepareDataServiceMap;
 /**
   * 获取数据补全服务
   */
public PrepareDataService getPrepareDataService(String transCode) {
    String key = new StringBuilder(transCode).append(PREPAREDATASERVICE_SUFF).toString();
    PrepareDataService prepareDataService = prepareDataServiceMap.get(key);
    if (prepareDataService == null) {
        LoggerUtil.info(logger, "交易({})-未获取到准备交易数据服务-采用默认服务(defaultPrepareDataService)", transCode);
        prepareDataService = defaultPrepareDataService;
    }else {
        LoggerUtil.info(logger, "交易({})-获取到准备交易数据服务({})", transCode, key);
    }
    return prepareDataService;
}

因为不同的交易类型需要补全的信息也不同,所以通过transCode+PrepareDataServiceImpl获取对应的补全服务类。

在交易数据准备服务类中主要是补全一些数据表需要的字段,如交易表中的业务处理状态、交易订单状态、开户机构信息(分行号、联行号等)、省市信息等。

4.2.5 路由处理

调用支付路由系统获取对应的支付渠道以及账户号,支付路由相关设计实现见《支付路由系统设计一:实现效果展示》系列文章。
将获取到的支付渠道信息填充到MessageDescription中。

4.2.6 支付渠道数据补全

 /**
  * 准备数据service bean 后缀
  */
 private final static String COMPLETIONDATASERVICE_SUFF = "CompletionDataServiceImpl";

 /**
  * 数据补全服务
  */
 @Autowired
 private Map<String, CompletionDataService> completionDataServiceMap;

  /**
    * @Description 获取数据补全服务
    * @Params
    * @Return
    * @Exceptions
    */
 public CompletionDataService getCompletionDataService(String transCode) {
     String key = new StringBuilder(transCode).append(COMPLETIONDATASERVICE_SUFF).toString();
     CompletionDataService completionDataService = completionDataServiceMap.get(key);
     if (completionDataService == null) {
         LoggerUtil.info(logger, "交易({})-未获取到交易数据补全服务-采用默认服务", transCode);
         completionDataService = defaultCompletionDataService;
     }
     return completionDataService;
 }

因为不同的交易类型需要补全的信息也不同,所以通过transCode+CompletionDataServiceImpl获取对应的补全服务类进行数据补全。在补全渠道信息时,对于特定渠道所需属性通过补全脚本进行补全。

主要补全内容:根据支付路由系统输出的账号,对账号关联的数据填充到上下文中,并将账号关联的商户号以及对应补充的属性信息填充到上下文中,使用对应脚本进行特定属性的补全。

4.2.7 交易信息入库

transCode+DomainDBSaveServiceImpl获取对应的数据持久化服务

在这里插入图片描述

 /**
  * 准备数据service bean 后缀
  */
 private final static String DOMAINDBSAVESERVICE_SUFF = "DomainDBSaveServiceImpl";

 /**
  * 领域模型持久化服务
  */
 @Autowired
 private Map<String, DomainDBSaveService> domainDBSaveServiceMap;

 /**
  * 获取数据持久化服务
  * @param transCode
  * @return
  */
 public DomainDBSaveService getDomainDBSaveService(String transCode) {
     String dbTransCode = TransactionManager.getTransCode(transCode);
     StringBuilder key = new StringBuilder();
     if (StringUtils.isNotBlank(dbTransCode)) {
         key.append(dbTransCode).append(DOMAINDBSAVESERVICE_SUFF);
     } else {
         key.append(transCode).append(DOMAINDBSAVESERVICE_SUFF);
     }
     DomainDBSaveService domainDBSaveService = domainDBSaveServiceMap.get(key.toString());
     if (domainDBSaveService == null) {
         LoggerUtil.info(logger, "交易({})-未获取到交易入库服务-采用默认服务", transCode);
         domainDBSaveService = defaultDomainDBSaveService;
     }
     return domainDBSaveService;
 }

在数据持久化服务中根据交易码获取模型类

 /**
  * 交易-模型映射
  */
 private static final Map<TransactionEnum, Class<? extends AggregateBase>> demainMapping = new HashMap<TransactionEnum, Class<? extends AggregateBase>>();

根据模型类+指定指定后缀获取到模型仓储进行数据持久化操作

  /**
 * @Description 获取域模型仓储
   */
  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);
  }

4.2.8 业务处理

transCode+BusinessServiceImpl获取对应的业务处理服务

    /**
     * 业务service bean 后缀
     */
    private final static String BUSINESSSERVICE_SUFF = "BusinessServiceImpl";

    @Autowired
    private Map<String, BusinessService> businessServiceMap;

    /**
     * 获取业务服务Service
     * @param transCode+BusinessServiceImpl
     * @return
     */
    public BusinessService getBusinessService(String transCode) {
        String key = new StringBuilder(transCode).append(BUSINESSSERVICE_SUFF).toString();
        BusinessService businessService = businessServiceMap.get(key);
        return businessService;
    }

主要进行如下操作:

  • 报文组装(报文格式化+加签加密)
  • 报文发送
  • 报文解析(报文数据解析+验签解密)
  • 响应码解析

4.2.9 后置处理

  • 交易数据更新
    同数据持久化一样,先从领域模型更新服务工厂中获取到数据库更新服务,然后获取领域模型,根据领域模型获取领域模型仓储,进行交易数据更新。
  • 异步通知
    对于一些异步交易发送交易结果通知。

4.3 业务处理后期响应阶段

4.3.1 请求响应

根据报文组装引擎将MessageDescription中的datas属性填充到对应的模板中以响应客户端请求。

4.3.2 渲染视图

/**
 * 渲染视图
 * @param transCode
 * @param response
 * @param messageEnvelope
 */
public void render(String transCode, HttpServletResponse response, MessageEnvelope messageEnvelope) {
    MessageFormatEnum messageFormat = messageEnvelope.getMessageFormat();
    String contentType = StringUtils.isBlank(messageFormat.getMessage()) ? MediaType.APPLICATION_JSON.toString() : messageFormat.getMessage();
    try {
        response.setContentType(contentType);//考虑json和html两种方式
        response.setCharacterEncoding(messageEnvelope.getEncode().getMessage());
        response.getWriter().write((String) messageEnvelope.getContent());
    } catch (IOException e) {
        LoggerUtil.error(logger, "交易({})-同步响应结果-异常", transCode, e);
    }
}

总结

本篇对支付网关的实现大致过程进行总结了下,主要是高度配置化、解析脚本、模板引擎的使用。

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

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

相关文章

Docker高级(完结)

一、DockerFile DockerFile简介 Docker是用来构建Docker镜像文件&#xff0c;由一条条docker指令和参数构成的脚本。 DockerFile构建过程 小总结 从应用软件的角度来看&#xff0c;Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段&#xff0c; Dockerfile是…

浅析垃圾回收

大家好&#xff0c;我是易安&#xff01; Java虚拟机的自动内存管理&#xff0c;将原本需要由开发人员手动回收的内存&#xff0c;交给垃圾回收器来自动回收。不过既然是自动机制&#xff0c;肯定没法做到像手动回收那般精准高效 &#xff0c;而且还会带来不少与垃圾回收实现相…

《MongoDB入门教程》 - MongoDB基础介绍

前言 时间也过太快了嘛&#xff0c;一晃上次更新都又过去6个月了&#xff0c;再不更新就变成年更博客了。 对了&#xff0c;第一次疫情也过去了&#xff0c;最近开始二阳了&#xff0c;希望大家能继续挺过去吧 本篇文章初尝试使用ChatGPT进行&#xff0c;博主主要负责排版和总…

mysql使用xtrabackup方式复制过滤某一个数据库

需求&#xff1a; 搭建一个新的从库&#xff0c;只复制过滤源端数据库里的其中一个数据库workflow到新实例上。 一 操作步骤 1.1 在目标端新建一个数据库实例 略 1.2 在源端做备份 /home/urman-agent/bin/xtrabackup --defaults-file/data/mysql/etc/13314/my.cnf --targ…

redis缓存穿透、缓存击穿、缓存雪崩

一、缓存 缓存是数据交换的缓冲区&#xff0c;是存储数据的临时地方&#xff0c;一般读写性能较高。 如浏览器会把静态资源先加载到浏览器缓存中&#xff0c;tomcat中有应用层缓存等&#xff0c;则数据库也有数据库缓存。 缓存的作用&#xff1a; 降低后端负载提高读写效率…

ES8基本命令

ElasticSearch是面向文档型数据库 构造类似于数据库&#xff1a;indexes(索引库数据库)-->types(类型表)-->documents(文档行)-->field(字段字段) 但是在7.X开始&#xff0c;Type已经开始被废弃。 正排索引&#xff1a;根据主键id关联内容&#xff0c;然后再找关键字…

微服务—Redis实用篇-黑马头条项目-达人探店功能(使用set与zset实现)

微服务—Redis实用篇-黑马头条项目-达人探店功能(使用set与zset实现) 1、达人探店 1.1、达人探店-发布探店笔记 发布探店笔记 探店笔记类似点评网站的评价&#xff0c;往往是图文结合。对应的表有两个&#xff1a; tb_blog&#xff1a;探店笔记表&#xff0c;包含笔记中的标…

Liunx基础命令 - mkdir命令

mkdir命令 – 创建目录文件 mkdir命令来自英文词组”make directories“的缩写&#xff0c;其功能是用来创建目录文件。使用方法简单&#xff0c;但需要注意若要创建的目标目录已经存在&#xff0c;则会提示已存在而不继续创建&#xff0c;不覆盖已有文件。而目录不存在&#…

路径规划算法:基于蜉蝣优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于蜉蝣优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于蜉蝣优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法蜉蝣…

Linux 无网环境下离线安装rpm

概述 搭建了私有yum仓库&#xff0c;想实现无网环境下Docker rpm离线安装的方法 1. 使用和配置清华源 2. 免安装下载rpm包及其依赖 3. 寻找特定的rpm包并补全依赖 清华的清华源 名称连接帮助文档备注主页清华大学开源软件镜像站 | Tsinghua Open Source MirrorAOSP | 镜像站使…

Swagger 3.0 与 Springboot 集成

springboot版本:2.3.12.RELEASE swagger版本:3.0.0 1&#xff1a;pom文件添加如下代码: <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version>&…

离线安装 Docker + Docker registry(Docker仓库)

概述 通过私有yum仓库安装docker 具体内容&#xff1a; 1. 构建私有镜像仓库 2. 准备rpm包 3. 安装部署 4. ansible 安装 卸载 5. shell 脚本安装 卸载 6. 网页连接 7. 构建docker 私有仓库 1. 构建私有镜像仓库 构建简单的yum私有仓库请参考如下文档 http://t.csdn.cn/22bmb…

用chatGPT写chatGPT教学方案

最近从机器学习的原理、数学推理、主流模型架构、chatGPT的Prompt策略&#xff0c;也实际体验了各种AIGC工具&#xff0c;算是较为系统的学习了chatGPT的前世今身&#xff0c;想着有很多人应该跟我一样&#xff0c;没有编程基础、也没有成为chatGPT专家的希望&#xff0c;仅仅通…

LeetCode_Day3 | 反转链表/移除链表的元素/设计个链表

LeetCode_链表 203.移除链表元素1. 题目描述2. 直接使用原表删除1. 思路2. 代码实现 3. 使用虚拟头节点删除1. 思路2. 代码实现 707.设计链表1.题目描述2.单链表&#xff1a;虚拟头节点设计1. 思路2. 代码实现及部分逻辑解释3. 需要注意的点 206.反转链表1.题目描述2. 双指针法…

BIO阻塞模型

作者&#xff1a;V7 博客&#xff1a;https://www.jvmstack.cn 一碗鸡汤 少年辛苦终身事&#xff0c;莫向光阴惰寸功。 —— 杜荀鹤 同步阻塞IO 在介绍阻塞和非阻塞之前先说明一下同步和异步。我们可以将同步和异步看做是发起IO请求的两种方式。同步IO指的是用户空间&…

项目美术部门敏捷开发流程及工具

前言 在项目开发中&#xff0c;针对美术部门的特性和工作风格&#xff0c;合理使用多种工具进行项目管理和进度控制非常必要。如果项目执行中有异地协作、美术内/外包、多项目并行的情况&#xff0c;正确的规划和管理的优势会更加凸显。 而合理使用整套的综合管理工具会让项目…

Day969.如何拆分代码 -遗留系统现代化实战

如何拆分代码 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于如何拆分代码的内容。 当完成了项目的战略设计&#xff0c;大体设计出目标架构&#xff0c;又根据系统的现状&#xff0c;决定采用“战术分叉”的方式进行微服务拆分之后&#xff0c;接下来的难点就变成…

用GPT-4 写2022年天津高考作文能得多少分?

正文共 792 字&#xff0c;阅读大约需要 3 分钟 学生必备技巧&#xff0c;您将在3分钟后获得以下超能力&#xff1a; 积累作文素材 Beezy评级 &#xff1a;B级 *经过简单的寻找&#xff0c; 大部分人能立刻掌握。主要节省时间。 推荐人 | Kim 编辑者 | Linda ●图片由Lexica …

vue工程搭建

1&#xff1a;查看vue及npm版本&#xff1a; 2&#xff1a;执行npm init nuxt-app <project-name>语句&#xff1a; 若出现npm ERR! code ENOLOCAL 请执行如下语句&#xff1a; npm cache verify npm cache clean --force npm i -g npm npm install -g cnpm --regis…

数据库(mysql语句)循环语句

例题1&#xff1a; 20到50之间能被5除余1的所有自然数的和 EDECLARE i int DECLARE s int SET s0 SET i20 白WHILE i <50 BEGIN IF(i%51) SET s s i SET ii1 END PRINT20到50之间能被5除余1的所有自然数的和是cast(s as varchar(20)) 例题2&#xff1a; 实现如下图 代码…