支付系统设计三:渠道网关设计06-业务处理

news2025/1/20 21:54:57

文章目录

  • 前言
  • 一、业务服务工厂
  • 二、业务处理服务
    • 1. 业务处理服务
    • 2. 业务处理抽象服务
    • 3. 流量控制
    • 4. 报文提交
      • 4.1 获取交易的服务端通讯列表
      • 4.2 循环请求支付渠道
        • 4.2.1 报文组装
        • 4.2.2 报文发送
          • 4.2.2.1 协议处理器获取
          • 4.2.2.2 构建通讯客户端
          • 4.2.2.3 发送请求
          • 4.2.2.4 响应报文读取
    • 5. 报文解析
      • 5.1 获取待解析报文
      • 5.2 获取报文解析器
      • 5.3 进行报文解析
    • 6. 响应码解析
  • 总结


前言

在这里插入图片描述
前面做了那么多工作,上送报文解析、参数校验、服务端渠道获取、参数补全、持久化工作等,下面开始请求支付渠道了,本篇将继续分解业务处理部分的逻辑实现。

businessServiceFactory.getBusinessService(context.getServerTransCode()).execute(context);

一、业务服务工厂

transCode+BusinessServiceImpl从业务服务工厂中获取到业务服务Service。

/**
 * @author Kkk
 * @Describe: 业务服务工厂类
 */
@Component
public class BusinessServiceFactory {
    private static final Logger logger = LoggerFactory.getLogger(BusinessServiceFactory.class);

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

    @Autowired
    private Map<String, BusinessService> businessServiceMap;

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

在这里插入图片描述

二、业务处理服务

1. 业务处理服务

/**
 * @author Kkk
 * @Describe: 单笔代扣业务服务
 */
@Service
public class DeductBusinessServiceImpl extends AbstractBusinessService {

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

2. 业务处理抽象服务

/**
 * @author Kkk
 * @Describe: 业务处理抽象服务
 */
@Service
public abstract class AbstractBusinessService implements BusinessService {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 报文传输组件
     */
    @Autowired
    private MessageTransport messageTransport;

    /**
     * 流量控制服务
     */
    @Autowired
    private RateLimitService rateLimitService;

    /**
     * 发送支付渠道(进行限流控制)
     * @param context
     */
    protected void submitWithfluxControl(PayGwContext context) {
        MessageDescription messageDescription = context.getMessageDescription();
        Map<String, Object> data = messageDescription.getDatas();
        String instTransCode = StringUtils.valueOf(data.get(PayGwConstant.INST_TRANS_CODE));
        List<String> transCodeList = getRateLimitTransCodeList();
        if (transCodeList.contains(instTransCode)) {
            String instCode = StringUtils.valueOf(data.get(PayGwConstant.INST_CODE));
            String instTransId = StringUtils.valueOf(data.get(PayGwConstant.INST_TRANS_ID));

            boolean hasRateLimit = rateLimitService.hasRateLimit(instCode, instTransId);
            if (hasRateLimit) {
                acceptAndQueue(context);
                return;
            }
        }
        submit(context);
    }

    /**
     * 发送支付渠道(服务端渠道通讯)
     * @param context
     */
    protected void submit(PayGwContext context) {
        String transCode = context.getServerTransCode();
        LoggerUtil.info(logger, "交易({})-请求支付渠道-开始", transCode);
        messageTransport.submit(context);
        LoggerUtil.info(logger, "交易({})-请求支付渠道-结束", transCode);
    }

    /**
     * 发送支付渠道(指定客户端通讯或者服务端通讯)
     * @param context
     * @param csFlagEnum
     */
    protected void submit(PayGwContext context, CsFlagEnum csFlagEnum) {
        String transCode = context.getServerTransCode();
        LoggerUtil.info(logger, "交易({})-请求支付渠道-开始", transCode);
        messageTransport.submit(context, csFlagEnum);
        LoggerUtil.info(logger, "交易({})-请求支付渠道-结束", transCode);
    }

    /**
     * 获取进行流量控制的transCode
     * @return
     */
    protected List<String> getRateLimitTransCodeList() {
        List<String> transCodeList = Lists.newArrayList();
        transCodeList.add(TransactionEnum.DEDUCT.getCode());
        transCodeList.add(TransactionEnum.DEDUCT_ASY.getCode());
        transCodeList.add(TransactionEnum.DEPUTE.getCode());
        transCodeList.add(TransactionEnum.DEPUTE_ASY.getCode());
        // ... ....
        return transCodeList;
    }

    /**
     * 交易登记受理幷用于限流排队
     * @param context
     */
    protected void acceptAndQueue(PayGwContext context) {
        LoggerUtil.info(logger, "交易({})-登记受理并排队-开始", context.getClientTransCode());
        Map<String, Object> data = context.getMessageDescription().getDatas();
        data.put(PayGwConstant.ORG_PROCESS_STATUS, ProcessStatusEnum.PROCESSING.getCode());//处理状态:处理中
        data.put(PayGwConstant.PAYGW_PROCESS_STATUS, ProcessStatusEnum.QUEUEING.getCode());//处理状态:已受理并排队
        data.put(PayGwConstant.PAYGW_TRANS_STATUS, TransStatusEnum.PROCESS.getCode());//交易状态:处理中
        data.put(PayGwConstant.PAYGW_RESP_CODE, SystemErrorCode.PROCESS.getCode());//支付网关响应码
        data.put(PayGwConstant.PAYGW_RESP_MSG, SystemErrorCode.PROCESS.getMessage());//支付网关响应信息:交易已受理
        LoggerUtil.info(logger, "交易({})-登记受理并排队-结束", context.getClientTransCode());
    }
}

3. 流量控制

在请求支付渠道之前,先判断是否配置流量控制,如果配置了流量控制,则将processStatus置为QUEUEING排队中,后面处理会通过Redis实现流量控制。
在这里插入图片描述
此处就不再展开,后期有时间单独介绍渠道侧流量控制的实现。

4. 报文提交

和支付渠道通讯过程报文组装客户端获取、通讯证书等加载比较复杂,这里简要介绍下实现脉络,后期专门再介绍这块具体实现。

4.1 获取交易的服务端通讯列表

    /**
     * 获取服务端通讯列表
     * @param context
     * @param csFlagEnum
     * @return
     */
    public List<CommunicationEntity> getServerCommunicationEntityList(PayGwContext context, CsFlagEnum csFlagEnum) {
        MessageDescription messageDescription = context.getMessageDescription();
        //区分客户端和服务端调用,获取交易的服务端通讯
        List<CommunicationEntity> serverCommunicationEntityList = null;
        if (csFlagEnum == CsFlagEnum.SERVER) {//服务端机构
            messageDescription.setInstCode(context.getServerInstCode());
            messageDescription.setTransCode(context.getServerTransCode());
            messageDescription.setTransId(context.getServerTransId());
            PayInstitution payInstitution = messageDescription.getServerPayInstitution();
            serverCommunicationEntityList = conmmunicationCacheManager.getByType(payInstitution.getInstTransTypeEntityByTransId(context.getServerTransId()).getId(), csFlagEnum.getCode());
        } else {//客户端机构
            messageDescription.setInstCode(context.getClientInstCode());
            messageDescription.setTransCode(context.getClientTransCode());
            messageDescription.setTransId(context.getClientTransId());
            PayInstitution payInstitution = messageDescription.getClientPayInstitution();
            serverCommunicationEntityList = conmmunicationCacheManager.getByType(payInstitution.getInstTransTypeEntityByTransCode(context.getClientTransCode()).get(0).getId(), csFlagEnum.getCode());
        }
        AssertUtils.isNotEmpty(serverCommunicationEntityList, SystemErrorCode.UNFOUND_CONFIG, new Object[]{"通讯"});
        return serverCommunicationEntityList;
    }

获取交易的服务端通讯列表之前MessageDescription 属性:
在这里插入图片描述
获取交易的服务端通讯列表之后MessageDescription 属性:
在这里插入图片描述
即将服务端支付渠道信息作为处理的目标:当前处理的通讯设置为服务端支付渠道通讯信息。

4.2 循环请求支付渠道

   /**
    * 循环请求支付渠道
    * @param context
    * @param serverCommunicationEntityList
    */
   public void submit(PayGwContext context, List<CommunicationEntity> serverCommunicationEntityList) {
       //1、循环请求支付渠道
       MessageDescription messageDescription = context.getMessageDescription();
       for (Iterator<CommunicationEntity> iterator = serverCommunicationEntityList.iterator(); iterator.hasNext(); ) {
           CommunicationEntity communicationEntity = iterator.next();
           messageDescription.setCommunicationEntity(communicationEntity);
           try {
               submitCommunication(context);
               if (iterator.hasNext()) {
                   if (isShortCut(messageDescription)) {
                       LoggerUtil.error(logger, "前置通讯交易状态不是成功-结束此交易的所有通讯");
                       break;
                   }
               }
           } catch (PayGwException e) {
               if (StringUtils.equals(COMMUNICATION_EXCEPTION.getCode(), e.getErrorCode())) {
                   if (iterator.hasNext()) {
                       LoggerUtil.error(logger, "前置通讯异常", e);
                       throw new PayGwException(SystemErrorCode.FAIL, e);
                   }
               }
               throw e;
           } catch (Exception e) {
               throw e;
           }
       }
   }

4.2.1 报文组装

使用报文组装引擎进行请求报文的组装工作,并将组装好的报文设置到MessageDescription serverRequestMessageEnvelope(发送到服务端的请求报文)中。

  @Override
  public MessageEnvelope messageAssemble(MessageTemplate messageTemplate, PayGwContext payGwContext) {
      MessageEnvelope messageEnvelope = new MessageEnvelope();
      //初始化Velocity和Template  将对应交易所需要的 velocity 模板(头,尾,补充模板等)与上下文中的参数 放到 velocitycontext 中
      VelocityContext context = velocityContextHelper.fillContext(messageTemplate, payGwContext);
      //报文体组装
      if (StringUtils.isNotBlank(messageTemplate.getMainTemplate())) {
          String messageBody = VelocityUtil.evaluate(context, messageTemplate.getMainTemplate());
          messageEnvelope.setContent(messageBody);
      }

      //报文头组装
      String headerTemplate = messageTemplate.getHeaderTemplate();
      if (StringUtils.isNotBlank(headerTemplate)) {
          String headerPrototype = VelocityUtil.evaluate(context, headerTemplate);
          Map<String, String> headers = MapUtils.covertText2MapByRule(headerPrototype);
          messageEnvelope.setExtraContent(headers);
      }
      return messageEnvelope;
  }

组装的请求渠道报文前4位为报文掩码,在发送前判断报文掩码是否需要发送支付渠道,有些交易类型不需要发送支付渠道,只需要给前端业务系统组装成一个JSON或者HTML表单之类的响应。

0000:其中第一位标识是否需要发送支付渠道(0-不需要发送 1-需要发送)
第一位:0-不需要发送;1-需要发送
第二位:0-报文内容需要trim();1-报文内容不需要trim()
第三位:预留
第四位:预留

4.2.2 报文发送

4.2.2.1 协议处理器获取

根据配置的和支付渠道交互的通讯协议类型从Client和Server的管理工厂中获取协议处理器进行处理。

        ProtocolTypeEnum protocolTypeEnum = ProtocolTypeEnum.getByCode(communicationEntity.getProtocolType());
        ClientManager clientManager = ServiceManagerFactory.getClientManager(protocolTypeEnum);

在这里插入图片描述
在这里插入图片描述
此时paygw属于客户端,所以获取Client端,如使用http协议将获取到对应的HttpClientManager处理类。

4.2.2.2 构建通讯客户端
 @Override
 public void messageSend(PayGwContext context) {
     MessageDescription messageDescription = context.getMessageDescription();
     CommunicationEntity communicationEntity = messageDescription.getCommunicationEntity();

     //1、通过支付机构号、交易类型为key进行客户端获取;
     PayInstitution payInstitution = messageDescription.getServerPayInstitution();

     if (StringUtils.isBlank(payInstitution.getId()) || StringUtils.isBlank(messageDescription.getTransCode())) {
         throw new PayGwException(SystemErrorCode.OPTION_VALIDATE_ERROR, new Object[]{"通讯key->支付机构ID或交易码"});
     }

     //key默认->支付机构+交易码+商户号
     StringBuilder key = new StringBuilder(StringUtils.valueOf(payInstitution.getId()));
     key.append(StringUtils.valueOf(messageDescription.getTransCode()));

     Map<String, Object> datas = messageDescription.getDatas();

     //兼容多个商户号-多个通讯证书,key值后+instAcctNo
     if (StringUtils.isNotBlank(datas.get(INST_ACCT_NO))) {
         key.append(StringUtils.valueOf(datas.get(INST_ACCT_NO)));
     }

     //2、获取HttpClient,如果获取不到进行初始化,初始化后加入缓存;
     HttpClient httpClient = null;
     httpClient = getHttpClient(key.toString(), communicationEntity, ProtocolTypeEnum.getByCode(communicationEntity.getProtocolType()), datas);
     context.addParam(ParamType.HTTP_CLIENT, httpClient);

     //3、生成调用参数;
     //根据GET/POST创建不同的请求(HttpGet/HttpPost)
     HttpUriRequest request = createHttpUriRequest(communicationEntity);
     context.addParam(ParamType.HTTP_METHOD, request);

     //4、获取协议处理客户端服务进行服务处理;
     httpClientService.hand(context);
 }
4.2.2.3 发送请求
  @Override
  protected void messageSend(PayGwContext context) {
      HttpClient httpClient = context.getHttpClient();
      HttpUriRequest httpMethod = context.getHttpMethod();
      HttpResponse httpResponse = null;
      try {
          httpResponse = httpClient.execute(httpMethod);
      } catch (IOException e) {
          throw new PayGwException(SystemErrorCode.COMMUNICATION_EXCEPTION, e);
      }
      context.addParam(ParamType.HTTP_CLIENT_RESPONSE, httpResponse);
  }
4.2.2.4 响应报文读取
  @Override
  public void convertInMessage(PayGwContext context) {
      MessageDescription messageDescription = context.getMessageDescription();
      messageDescription.setProcessPhase(ProcessPhaseEnum.SERVER_RESPONSE_RECEIVE);
      HttpResponse httpResponse = context.getHttpResponse();
      
      MessageEnvelope messageEnvelope = new MessageEnvelope();
      Object content = null;
      try {
          CommunicationEntity communicationEntity = messageDescription.getCommunicationEntity();
          MessageFormatEnum messageFormat = communicationEntity.getReceiveMessageFormat();
          messageEnvelope.setMessageFormat(messageFormat);
          if (messageFormat == MessageFormatEnum.BYTE) {
              content = EntityUtils.toByteArray(httpResponse.getEntity());
          } else if (messageFormat == MessageFormatEnum.FILE) {
              content = readResponse(httpResponse, communicationEntity);
          } else {
              String charset = EncodeEnum.getByCode(communicationEntity.getReceiveTransformEncode()).getMessage();
              content = EntityUtils.toString(httpResponse.getEntity(), charset);
          }
          messageEnvelope.setContent(content);
      } catch (IOException e) {
          throw new PayGwException(SystemErrorCode.COMMUNICATION_EXCEPTION, e);
      }
      messageDescription.setServerResponseMessageEnvelope(messageEnvelope);
  }

根据配置的通讯报文类型、编码进行响应报文的读取,并构建MessageEnvelope 赋值填充到MessageDescription的serverResponseMessageEnvelope,此时的MessageDescription对象:
在这里插入图片描述

5. 报文解析

将不同支付渠道的同一交易类型的响应报文解析为系统所需要的同一格式对象。

  /**
   * 消息解析
   * @param context
   */
  @Override
  public void messageParse(PayGwContext context) {
      MessageDescription messageDescription = context.getMessageDescription();
      //1.获取待解析报文
      Object parseMessage = getParseMessage(messageDescription);
      LoggerUtil.info(logger, "交易-开始报文解析");

      //2.获取报文解析器(加载groovy脚本,有可能是默认实现的java类,脚本统一实现MessageParser接口)
      MessageParser messageParser = findParser(context);

      //3.进行报文解析
      Object obj = messageParser.parse(context, parseMessage);
      messageDescription.putDatas(BeanUtils.beanToMap(obj));
  }

5.1 获取待解析报文

 public Object getParseMessage(MessageDescription messageDescription) {
     Object message = null;
     ProcessPhaseEnum processPhase = messageDescription.getProcessPhase();
     switch (processPhase) {
         case CLIENT_REQUEST_RECEIVE://客户端请求(支付核心请求或支付渠道异步通知)
             message = messageDescription.getClientRequestMessageEnvelope().getContent();
             break;
         case SERVER_RESPONSE_RECEIVE://服务端响应
             message = messageDescription.getServerResponseMessageEnvelope().getContent();
             break;
         default:
             LoggerUtil.warn(logger, "processPhase = {} 没有加 switch.", processPhase);
             throw new PayGwException(SystemErrorCode.SYSTEM_ERROR);
     }
     return message;
 }

5.2 获取报文解析器

 /**
  * 获取报文解析器
  * @param context
  * @return
  */
 private MessageParser findParser(PayGwContext context) {
     String parserName = getParserName(context);
     MessageParser messageParser = groovyScriptCache.getMessageParser(parserName);
     if (messageParser == null) {
         LoggerUtil.error(logger, "未找到交易的报文解析器({})", parserName);
         throw new PayGwException(SystemErrorCode.SYSTEM_ERROR);
     }
     return messageParser;
 }

5.3 进行报文解析

  Object obj = messageParser.parse(context, parseMessage);
  messageDescription.putDatas(BeanUtils.beanToMap(obj));

如下在解析报文中将单笔交易解析为如下对象,以统一后面的业务处理。

/**
 * @author Kkk
 * @Describe: 单笔报文解析结果
 */
public class MessageParserResult {
    /**
     * 支付网关原处理状态
     */
    private String orgProcessStatus;
    /**
     * 支付网关处理状态
     */
    private String processStatus;
    /**
     * 支付网关交易状态
     */
    private String transStatus;
    /**
     * 支付网关响应码
     */
    private String respCode;
    /**
     * 支付网关响应信息
     */
    private String respMsg;
    /**
     * 支付渠道请求流水号
     */
    private String instReqNo;
    /**
     * 支付渠道返回流水号
     */
    private String instRespNo;
    /**
     * 支付渠道交易状态
     */
    private String instTransStatus;
    /**
     * 支付渠道交易金额
     */
    private BigDecimal instTransAmount;
    /**
     * 支付渠道响应码
     */
    private String instRespCode;
    /**
     * 支付渠道响应码
     */
    private String instRespMsg;
    /**
     * 支付渠道交易日期
     */
    private Date instTransDate;
    /**
     * 支付渠道交易完成时间
     */
    private Date instTransTime;

    /**
     * 扩展1
     */
    private String extend1;

    /**
     * 扩展2
     */
    private String extend2;

   //... ...
}

6. 响应码解析

使用响应信息解析器将不同支付渠道响应的响应码转换为平台统一的响应码。

respInfoResolver.resolve(messageDescription.getInstCode(), messageDescription.getTransId(), messageDescription.getDatas());

在这里插入图片描述
到此就完成了和服务端支付渠道的第一次通讯过程,由于有的交易类型涉及到多次通讯,所以在for循环通讯中进行上一次通讯判断,前置通讯交易状态不是成功-结束此交易的所有通讯。


总结

本篇主要介绍了paygw和支付渠道的通讯,在通讯之前先判断是否进行流量控制,如果配置了限流则不再和支付渠道进行通讯,等待后续处理入队走异步处理。如果未配置流量控制,则根据通讯配置构建通讯客户端,进行通讯,并将渠道响应报文进行解析,转化为系统统一格式对象,然后对渠道响应码进行转换。后续处理就是交易表数据更新,构建客户端渠道响应报文,具体实现见后文。

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

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

相关文章

【rust】| 06——语言特性 | 所有权

系列文章目录 【rust】| 00——开发环境搭建 【rust】| 01——编译并运行第一个rust程序 【rust】| 02——语法基础 | 变量(不可变?)和常量 【rust】| 03——语法基础 | 数据类型 【rust】| 04——语法基础 | 函数 【rust】| 05——语法基础 | 流程控制 【rust】| 06——语言特…

论文/文章/课设 不会写 后端的实现方式?来试试这个吧!

起因 有朋友用了云开发&#xff0c;但是不太会写&#xff0c;老师也不太理解&#xff0c;就来询问我该怎么写&#xff08;不要苛责老师古板&#xff0c;他们确实不一定能够立刻接受新东西&#xff09; 用的不是云开发的也适用以下思路 思路 我们把后端开发类比成拧螺丝&…

非煤矿山电子封条 yolov7

非煤矿山电子封条通过yolov7python网络模型技术&#xff0c;非煤矿山电子封条可以对矿山主副井口、风井口、车辆出入口和调度室等全天候不间断实时分析预警&#xff0c;发现人员违规行为及异常设备状态立即告警。YOLOv7 的发展方向与当前主流的实时目标检测器不同&#xff0c;研…

GitSVN区别及选型

1、结论先行 git更适用于纯代码仓库&#xff0c;优势在于分支管理svn则擅长于文件管理&#xff0c;优势在于目录级权限控制 2、版本管理发展历程 3、Git&SVN为何而生 Git出生于2005年&#xff0c;是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版…

LeetCode 63 不同路径 II

题目&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。现在考虑网格中有障碍物。那么从左…

一款超级简洁的个人博客系统搭建教程(附源码)

开发环境 IDEA、jdk1.8、mysql8.33 开发框架 springboot 1.首先&#xff0c;确保已安装 Git 和 IntelliJ IDEA。如果你还没有安装 Git&#xff0c;请前往官网下载并安装 Git。 2.打开 IntelliJ IDEA&#xff0c;选择 “File” -> “New” -> “Project from Version Con…

Java面试知识点(全)- Java面试基础部分三

[Java面试知识点(全) 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 ThreadPoolExecutor 如果不了解这个类&#xff0c;应该了解前面提到的ExecutorService&#xff0c;开一个自己的线程池非常方便&#xff1a; Execut…

桂院导航小程序 静态项目 二次开发教程

Gitee代码仓库&#xff1a;桂院导航小程序 先 假装 大伙都成功安装了静态项目&#xff0c;并能在 微信开发者工具 和 手机 上正确运行。 接着就是 将项目 改成自己的学校。 代码里的注释我就不说明了&#xff0c;有提到 我的学校 的文字都改成你自己的就行 1. 全局 app.json…

吴恩达教你写提示词 ChatGPT prompt engineering

文章目录 吴恩达教你写提示词 ChatGPT prompt engineering1. 关键提示&#xff08;prompt&#xff09;原则1. 基础2. 编写明确和具体的提示词3. 给模型时间“思考”4. 模型的限制5. 迭代式提示&#xff08;prompt&#xff09;开发过程 2. 提示&#xff08;prompt&#xff09;一…

Ubuntu下通过Wine安装LTSpice 17.1.8

LTSpice LTSpice 是常用的电路模拟软件, 但是只有 Windows 版本和 Mac 版本, 在 Linux 下需要用 Wine 运行. 以下说明如何在 Ubuntu 下安装最新的 LTSpice 17.1.8 安装 下载 LTSpice 安装文件 下载地址: https://www.analog.com/en/design-center/design-tools-and-calcula…

51单片机也可以移植RTOS

说起RTOS移植&#xff0c;我们首先会想到32位单片机。 那么51单片机可以移植RTOS吗&#xff1f; 我的答案是&#xff0c;只要资源够用&#xff08;ROM空间、RAM空间&#xff09;&#xff0c;可以移植。 前提是你对RTOS的实现原理非常清楚&#xff0c;并且可以自己完成移植工作…

国内免费cdn汇总2023最新

内容分发网络简称CDN&#xff0c;其原理大概是将网站内容分发至加速节点&#xff0c;让用户从就近的服务器节点上获取内容&#xff0c;从而提高网站的访问加载速度。大部分服务商&#xff08;如阿里云&#xff0c;腾讯云&#xff0c;京东云等&#xff09;的CDN服务是按使用量收…

在Linux开发板上安装HomeAssistant

1. 什么是Home Assistant Home Assistant 使用 Python3 开发的&#xff0c;是一个完整的 UI 管理的家庭自动化生态系统&#xff0c;它运行 Home Assistant Core、Home Assistant Supervisor 和附加组件。它预装在 Home Assistant OS 上&#xff0c;当然也可以安装在任何 Linux…

Android ConstraintLayout 使用入门

ConstraintLayout是Android中一个非常强大的布局管理器&#xff0c;它可以帮助我们快速创建复杂的布局&#xff0c;并且具有很好的性能和可扩展性。在本文中&#xff0c;我将从面试的角度&#xff0c;详细讲解ConstraintLayout的概念、特点、使用方法和示例。 概念 Constraint…

力扣19删除链表的倒数第 N 个结点:思路分析+图文全解+方法总结(快慢指针法递归法)+深入思考

文章目录 第一部分&#xff1a;题目描述第二部分&#xff1a;代码实现2.1 快慢指针法2.2 递归 第一部分&#xff1a;题目描述 &#x1f3e0; 链接&#xff1a;19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; ⭐ 难度&#xff1a;中等 第二部分&#…

Java【网络编程2】 详解基于 UDP 协议的 Socket API, 逐行代码解析如何网络编程

文章目录 前言一、认识 Socket(套接字), TCP 协议和 UDP 协议1, 什么是 Socket(套接字)2, 浅谈 TCP 协议和 UDP 协议的区别和特点 二、基于 UDP 协议的 Socket API1, DatagramSocket 类2, DatagramPacket 类 三、逐行代码解析网络编程1, 逐行解析客户端1.1, 核心成员方法 start…

(转载)从0开始学matlab(第2天)—MATLAB 变量的初始化

当变量初始化时&#xff0c;MATLAB 将会自动建立变量。有三种方式初始化 MATLAB 中的变量&#xff1a; 1 &#xff0e;用赋值语句初始化变量 2 &#xff0e;用 input 函数从键盘输入初始化变量 3 &#xff0e;从文件读取一个数据 前两种方法我们在这里讨论&#xff0c…

Linux -- 进阶 Web服务器 虚拟主机 -- 基于 域名

基于域名的虚拟主机 &#xff1a; 原理 &#xff1a; # 当服务器无法给每个网站都分配一个独立的 IP 地址时&#xff0c;可以通过用户请求的域 名实现不同域名传输不同的网页数据。 域名解析 &#xff1a; 功能 &#xff1a; 域名<>IP &#xff08; 就是 …

E4A影视APP源码电视盒子酷点TV版4.5 后端对接苹果cms

E4A影视APP源码电视盒子酷点TV版4.5 后端对接苹果cms 内带演示apk是为了方便大家调试&#xff0c;必应搜索醉里技术博客http://202271.xyz/?tv 此版带会员功能,对接的是 如意验证1.71版苹果cms后端APP 电视TV4.5版, 会员功能带注册邀请,绑定邮箱,邮箱找回密码,修改新密码,卡…

docker-compose 集成Jenkins部署,打包,发布

前言 需要提前准备的条件 1.git 2.Maven环境 3.Docker环境 4.JDK环境(Centos7.4自带JDK环境去除&#xff0c;重装的JDK) 5.Nodejs #前端发布使用一、 jenkins部署 下载镜像 #查询镜像 docker search jenkins#下载镜像 docker pull jenkins/jenkins编写docker-compose.yml …