• 手把手教你如何采用服务商模式实现微信支付



    背景

    小程序盛行时代,一般的企业中都都会包含多个小程序,而大部分的小程序通常都需要实现支付功能,本文将针对服务商模式进行微信支付进行详细讲解。

    微信支付的模式


    一般企业选用是服务商或者为渠道商模式,但是成为渠道商需要相关流量支撑才能申请,本文以服务商模式进行讲解。


    咨询微信支付客服关于服务商的有些问题:
    1.服务商和特约只能有一个,需要注销特约商户后申请成为服务商
    2.服务商不能单独收款,只能给特约商户进行收款
    3.服务商可以设置分账抽成微信会自动完成分账
    4.服务商下特约商户收款会直接将钱打到特约商户下
    5.直连或者特约可以成为注册另一个服务商下的特约(根据风险适时调整)

    一、前期准备

    1.注册服务商

    服务商申请需要通过已做过公司认证的公众号,登录公司的微信服务号,在【微信支付】>【服务商申请】,直接跟着官方引导一步步操作即可,具体申请流程如下:
    在这里插入图片描述
    说明:所以企业需要申请公共号,才能申请注册服务商。

    2.服务商入驻

    通过服务商来开发的系统来帮助商户微信支付,首先需要完成商户号在服务商号中的入驻过程。服务商注册成功后,进入微信支付平台,登录服务商,进行商户入驻。一般商户入驻有两种,具体如下:

    • 页面入驻
    • 调用API方式入驻

    页面入驻

    在这里插入图片描述
    入驻可以看作是商户生成商户号的同时与服务商形成绑定关系。具体可以参考微信公众号中按流程指引一步步操作就行。
    在这里插入图片描述

    说明:商户入驻完成后,此商户才能用于微信支付。

    申请证书

    商户号入驻成功后,需要申请API证书。
    在这里插入图片描述
    说明:按照官方文档申请证书,设置密钥,设置好密钥后一定要在安全的前提下记住,之后只能重置不能查看。

    重要参数说明

    • appid:服务商Appid
    • mchId:服务商的商户id
    • mchKey:证书的序列号
    • subAppId:子商户小程序Appid
    • subOpenId:子商户小程序用户的openId
    • subMchId:子商户的商户id

    二、子商户支付

    1.下单流程图

    在这里插入图片描述
    注意:下单流程和直连商户一样,但是接口和参数略有不同。直接参考合作伙伴平台Api

    2.代码实现

    服务商模式的微信支付的具体实现方案,本文采用的是Spring Boot集成weixin-java-pay来实现微信支付。
    微信服务商JSAPI下单官方文档 https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_1.shtml
    weixin-java-pay文档:http://binary.ac.cn/weixin-java-pay-javadoc/

    踩坑:mchId和appid必须是服务商的而且是必填,如果服务商要给特约下单的appid是特约的appid也要写服务商的,subappid才可以写特约商户的。如果服务商给特约下答案appid是服务商的那么subappid可以不填。

    1.引入依赖

    <dependency>
         <groupId>com.github.binarywang</groupId>
        <artifactId>weixin-java-pay</artifactId>
        <version>4.5.0</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.支付配置

    将证书放到resources下
    在这里插入图片描述

    # 微信支付配置
    wx:
      pay:
        appId: wx1xxxx #服务商微信公众号或者小程序等的appid
        subAppId: wxc0xxxxx #特约商户微信公众号或者小程序等的appid
        mchId: 160000000 #服务商微信支付商户号
        apiV3Key: 7xxxxxxxxxxxxxx #apiV3秘钥
        certSerialNo: 2CCCCCCCCCCCA  #证书号
        privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
        privateCertPath: classpath:cert/apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径
        notifyUrl: https://www.xxx.com/prod-api/anonymous/wx/notify/order #回调地址
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.相关配置类

    说明:读取微信支付的配置信息

    package com.ruoyi.xyhj.config;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    /**
     * wxpay pay properties.
     *
     * @author Binary Wang
     */
    @Data
    @ConfigurationProperties(prefix = "wx.pay")
    public class WxPayProperties {
      /**
       * 设置微信公众号或者小程序等的appid
       */
      private String appId;
    
      /**
       * 设置微信公众号或者小程序等的appid
       */
      private String subAppId;
    
      /**
       * 微信支付商户号
       */
      private String mchId;
    
      /**
       * 微信支付商户V3密钥
       */
      private String apiV3Key;
    
      /**
       * 证书号
       */
      private String certSerialNo;
    
      /**
       * apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
       */
      private String privateKeyPath;
    
      /**
       * apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径
       */
      private String privateCertPath;
    
      /**
       * 回调地址
       */
      private String notifyUrl;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    说明:将微信支付的相关参数设置到wxjava中的cofig中。

    package com.ruoyi.xyhj.config;
    
    import com.github.binarywang.wxpay.config.WxPayConfig;
    import com.github.binarywang.wxpay.service.WxPayService;
    import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
    import lombok.AllArgsConstructor;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author Binary Wang
     */
    @Configuration
    @ConditionalOnClass(WxPayService.class)
    @EnableConfigurationProperties(WxPayProperties.class)
    @AllArgsConstructor
    public class WxPayConfiguration {
      private WxPayProperties properties;
    
      @Bean("wxPayService")
      @ConditionalOnMissingBean
      public WxPayService wxService() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
        payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
        payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiV3Key()));
        payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));
        payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
        payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
        payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
        payConfig.setTradeType("JSAPI");
        payConfig.setSignType("MD5");
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
      }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    4.业务实现类

    @RestController
    @RequestMapping("/anonymous")
    @Slf4j
    public class TelAnonymousController
    {
        
        @Autowired
        private WxPayService wxPayService; 
            
        //发起支付
        @PostMapping("/wx/order/create")
        public WxUnifiedOrderVo createOrder(){
            //发起V3 服务商发起支付
            EcommerceServiceImpl ecommerceService=new EcommerceServiceImpl(wxPayService);
         // 1. 创建请求对象
            PartnerTransactionsRequest orderRequest=new PartnerTransactionsRequest();
            // 2. 根据订单系统传过来的订单信息组装支付参数,创建支付订单
            //服务商应用ID
            orderRequest.setSpAppid(wxPayProperties.getAppId());
            //服务商户号
            orderRequest.setSpMchid(wxPayProperties.getMchId());
            //子商户号
            orderRequest.setSubMchid(dealerMerchant.getMchid());
            //子商户/二级商户应用ID
            orderRequest.setSubAppid(wxPayProperties.getSubAppId());
            //商品描述
            orderRequest.setDescription("商品描述");
            //商户订单号
            orderRequest.setOutTradeNo("订单id");
            //设置交易结束时间为24小时
            orderRequest.setTimeExpire(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(new Date()));
            //通知地址
            orderRequest.setNotifyUrl(wxPayProperties.getNotifyUrl());
            //订单金额
            PartnerTransactionsRequest.Amount amount = new PartnerTransactionsRequest.Amount();
            amount.setTotal(BaseWxPayRequest.yuanToFen("订单金额"));
            orderRequest.setAmount(amount);
            //支付者
            PartnerTransactionsRequest.Payer payer = new PartnerTransactionsRequest.Payer();
            payer.setSubOpenid("支付者的openid");
            orderRequest.setPayer(payer);
            //发起下单请求
            TransactionsResult partner = ecommerceService.partner(TradeTypeEnum.JSAPI, partnerTransactionsRequest);
            //生成签名
            WxUnifiedOrderVo tokenJSAPI = WechatSignUtil.getTokenJSAPI(wxPayProperties.getSubAppId(), partner.getPrepayId(), wxPayProperties.getPrivateKeyPath());
            tokenJSAPI.setOrderId(order.getId());
            return tokenJSAPI;
        }
    		
    		//支付回调
    	@ApiOperation(value = "支付回调通知处理")
        @PostMapping("/wx/notify/order")
        public void parseOrderNotifyResult(@RequestBody String resultData) throws WxPayException {
            log.info("回调:{}",resultData);
            EcommerceServiceImpl ecommerceService = new EcommerceServiceImpl(wxPayService);
            PartnerTransactionsNotifyResult notifyResult = ecommerceService.parsePartnerNotifyResult(resultData, null);
            //此处解析到了回调信息
            log.info("回调:{}",notifyResult.getResult());
          //业务逻辑
        }
      
        		//支付回调
    	@ApiOperation(value = "主动查询支付信息")
        @PostMapping("/wx/order/select")
        public void wxSelectOrderStatus() throws WxPayException {
             //构建ecommerceService
            EcommerceServiceImpl ecommerceService = new EcommerceServiceImpl(wxPayService);
            //构建PartnerTransactionsQueryRequest对象
            PartnerTransactionsQueryRequest queryRequest = new PartnerTransactionsQueryRequest();
            queryRequest.setOutTradeNo("订单ID");
            queryRequest.setSpMchid("服务商ID");
            queryRequest.setSubMchid("特约商户ID");
            PartnerTransactionsResult partnerTransactionsResult=new PartnerTransactionsResult();
            try {
                //普通查询订单API
                partnerTransactionsResult = ecommerceService.queryPartnerTransactions(queryRequest);
            } catch (WxPayException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    说明:实现微信创建订单、支付回调、查询订单接口。

    5. 工具类

    WxUnifiedOrderVo

    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    @Data
    @ApiModel
    public class WxUnifiedOrderVo {
         /**
          * appid
          */
         @ApiModelProperty("appId")
         private String appId;
         /**
          * 时间戳
          */
         @ApiModelProperty("时间戳")
         private String timeStamp;
         /**
          * 随机字符串
          */
         @ApiModelProperty("随机字符串")
         private String nonceStr;
         /**
          * 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
          */
         @ApiModelProperty("小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***")
         private String packageStr;
         /**
          * 签名类型,默认为RSA,仅支持RSA。
          */
         @ApiModelProperty("签名类型,默认为RSA,仅支持RSA。")
         private String signType;
         /**
          * 签名
          */
         @ApiModelProperty("签名")
         private String paySign;
         /**
          * 订单id
          */
         @ApiModelProperty("订单id")
         private Long orderId;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    WechatSignUtil

    package com.ruoyi.xyhj.utils;
    
    import com.ruoyi.xyhj.domain.vo.WxUnifiedOrderVo;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.*;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.util.Base64;
    import java.util.HashMap;
    import java.util.UUID;
    
    public class WechatSignUtil {
    
        /**
         * 参考网站 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
         * 计算签名值
         *
         * @param appId
         * @param prepay_id
         * @return
         * @throws IOException
         * @throws SignatureException
         * @throws NoSuchAlgorithmException
         * @throws InvalidKeyException
         */
        public static WxUnifiedOrderVo getTokenJSAPI(String appId, String prepay_id, String privateKey) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
            // 获取随机字符串
            String nonceStr = getNonceStr();
            // 获取微信小程序支付package
            String packagestr = "prepay_id=" + prepay_id;
            long timestamp = System.currentTimeMillis() / 1000;
            //签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值
            String message = buildMessageTwo(appId, timestamp, nonceStr, packagestr);
            //获取对应的签名
            String signature = sign(message.getBytes("utf-8"),privateKey);
            // 组装返回
            WxUnifiedOrderVo vo = new WxUnifiedOrderVo();
            vo.setAppId(appId);
            vo.setTimeStamp(String.valueOf(timestamp));
            vo.setNonceStr(nonceStr);
            vo.setPackageStr(packagestr);
            vo.setSignType("RSA");
            vo.setPaySign(signature);
            return vo;
        }
    
        /**
         * 生成随机数
         * @return
         */
        public static String getNonceStr(){
            return UUID.randomUUID().toString()
                    .replaceAll("-", "")
                    .substring(0, 32);
        }
        /**
         * 拼接参数
         *
         * @return
         */
    
        public static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
            return appId + "\n"
                    + timestamp + "\n"
                    + nonceStr + "\n"
                    + packag + "\n";
        }
        /**
         * 生成签名
         *
         * @return
         */
        public static String sign(byte[] message,String privateKey) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
            Signature sign = Signature.getInstance("SHA256withRSA"); //SHA256withRSA
            sign.initSign(getPrivateKey(privateKey));
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        }
        /**
         * 获取私钥
         * @param filename 私钥文件路径  (required)
         * @return 私钥对象
         */
        public static PrivateKey getPrivateKey(String filename) throws IOException {
            System.out.println("filename:" + filename);
            filename = filename.replace("classpath:", "");
            WechatSignUtil wechatSignUtil = new WechatSignUtil();
            InputStream resourceAsStream = wechatSignUtil.getClass().getClassLoader().getResourceAsStream(filename);
            byte[] bytes = new byte[0];
            bytes = new byte[resourceAsStream.available()];
            resourceAsStream.read(bytes);
            String content = new String(bytes);
    //        String content = new String(Files.readAllBytes(Paths.get(resource.getPath())), "utf-8");
            try {
                String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                        .replace("-----END PRIVATE KEY-----", "")
                        .replaceAll("\\s+", "");
    
                KeyFactory kf = KeyFactory.getInstance("RSA");
                return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("当前Java环境不支持RSA", e);
            } catch (InvalidKeySpecException e) {
                throw new RuntimeException("无效的密钥格式");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    三、服务商进件

    官方文档 https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter11_1_1.shtml

    1.特约商户进件流程、时序图

    • 流程图
      在这里插入图片描述
    • 时序图
      在这里插入图片描述
    • 申请单状态如下
      在这里插入图片描述

    2.代码实现

      @Operation(summary = "提交申请单")
        @GetMapping("/createApply")
        public R<String> createApply(@RequestParam(required = false) String applymentId) throws WxPayException {
            WxPayApplyment4SubCreateRequest request = new WxPayApplyment4SubCreateRequest();
            // 主体资料:主体类型、是否是金融机构、营业执照、登记证书、组织机构代码证、单位证明函照片、经营者/法人身份证件、最终受益人信息列表(UBO)、小微辅助证明材料(subjectType为小微商户时必填)
            WxPayApplyment4SubCreateRequest.SubjectInfo subjectInfo = WxPayApplyment4SubCreateRequest.SubjectInfo.builder().build()
                    .setFinanceInstitution(false)
                    .setBusinessLicenseInfo(null);// 省略.......
            request.setSubjectInfo(subjectInfo);
            // 补充材料
            WxPayApplyment4SubCreateRequest.AdditionInfo additionInfo=new WxPayApplyment4SubCreateRequest.AdditionInfo();
            additionInfo.setBusinessAdditionMsg("补充说明");
            additionInfo.setBusinessAdditionPics(null) ;// 补充材料
            additionInfo.setLegalPersonCommitment("法人开户承诺函");
            additionInfo.setLegalPersonVideo("法人开户意愿视频");
            request.setAdditionInfo(additionInfo);
            // 结算银行账户
            WxPayApplyment4SubCreateRequest.BankAccountInfo bankAccountInfo=new WxPayApplyment4SubCreateRequest.BankAccountInfo();
            bankAccountInfo.setBankAccountType(BankAccountTypeEnum.BANK_ACCOUNT_TYPE_CORPORATE); // 账户类型:对公银行账户
            bankAccountInfo.setAccountName("开户名称");  // 开户名称
            bankAccountInfo.setAccountBank("开户银行");
            bankAccountInfo.setBankAddressCode("开户银行省市编码");
            bankAccountInfo.setBankBranchId("开户银行联行号");
            bankAccountInfo.setBankName("开户银行全称(含支行)");
            bankAccountInfo.setAccountNumber("银行账号");
            request.setBankAccountInfo(bankAccountInfo);
            // 业务申请编号
            request.setBusinessCode("业务申请编号");
            // 经营资料
            request.setBusinessInfo(null); // 省略.......
            // 超级管理员信息
            request.setContactInfo(null);// 省略.......
            // 结算规则
            request.setSettlementInfo(null);// 省略.......
    
            // 调用微信API
            Applyment4SubService applyment4SubService=new Applyment4SubServiceImpl(wxPayService);
            WxPayApplymentCreateResult apply = applyment4SubService.createApply(request);
            String applyMentId = apply.getApplymentId(); // 返回申请单ID
            return R.success(applyMentId);
        }
        
        @Operation(summary = "通过申请单号查询申请状态")
        @GetMapping("/queryApply")
        public R<ApplymentStateQueryResult> queryApply(@RequestParam(required = true) String applymentId) throws WxPayException {
            // 调用API 查询申请状态
            Applyment4SubService applyment4SubService=new Applyment4SubServiceImpl(wxPayService);
            ApplymentStateQueryResult result = applyment4SubService.queryApplyStatusByApplymentId(applymentId);
            return R.success(result);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
  • 相关阅读:
    Unity WIFI 无线打包至手机
    <图像处理> Fast角点检测
    搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)
    likely和unlikely的用法
    CSDN Markdown 编辑器的目录与页内跳转功能
    调用线程的run()和start()方法有什么不同呢?
    [Java]快速入门二叉树,手撕相关面试题
    store redux在项目中的应用
    Spring MVC - 相关内容3
    微信小程序使用 scss
  • 原文地址:https://blog.csdn.net/qq_43548590/article/details/134282232