小程序支付升级:实现微信支付V3接口接入

news2025/1/19 20:33:16

文章目录

  • 用户付款流程
  • 业务流程讲解
  • 接入前准备
  • 快速接入
    • 1、引入开发库
    • 2、配置参数
    • 3、初始化商户配置
    • 4、微信支付对接
    • 5、支付回调-支付通知API

相较于 v2 版本,v3 版本的接口文档在阅读上可能显得相对凌乱。它的组织结构可能不太清晰,难以快速理解整个流程。但是,一旦我们对基本流程有了大致了解,我们可以利用 wechatpay-java 来简化开发过程(一把梭哈)。

项目托关于gitee:springboot-wechat_pay

相关api文档:小程序支付API列表

微信公众平台:微信公众平台

用户付款流程

如图1,用户通过分享或扫描二维码进入商户小程序,用户选择购买,完成选购流程。

步骤2:如图3,调起微信支付控件,用户开始输入支付密码。

imgimgimg
图1 打开商户小程序图2 请求微信支付图3 调起微信支付控件

如图4,密码验证通过,支付成功。商户后台得到支付成功的通知。

步骤4:如图5,返回商户小程序,显示购买成功。

步骤5:如图6,微信支付公众号下发支付凭证。

img 图4 请求支付成功img 图5 返回商户小程序img
图4 请求支付成功图5 返回商户小程序图6 下发支付凭证

业务流程讲解

业务流程图如下:
在这里插入图片描述
重点步骤说明:

步骤4:用户下单发起支付,商户可通过JSAPI下单创建支付订单。

步骤9:商户小程序内使用小程序调起支付API(wx.requestPayment)发起微信支付,详见小程序API文档

步骤16:用户支付成功后,商户可接收到微信支付支付结果通知支付通知API。

步骤21:商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。

接入前准备

详细操作流程参考官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml#part-1

  • Java 1.8+。
  • 成为微信支付商户。
  • 商户 API 证书:指由商户申请的,包含证书序列号、商户的商户号、公司名称、公钥信息的证书。
  • 商户 API 私钥:商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。
  • APIv3 密钥:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了 AES-256-GCM 加密。APIv3 密钥是加密时使用的对称密钥。

最终我们需要获取到以下信息:

  • appId:微信公众号或者小程序等的appId(登陆 微信公众平台,设置 -> 基本设置 -> 账号信息 -> AppID)
  • merchantId:微信支付商户号(首先关联商户号,功能 -> 微信支付 -> 商户号管理 -> 已关联商户号)
  • privateKeyPath:商户API私钥(商户 API 证书根据文档获取,将apiclient_key.pem文件复制至项目路径下。该文件在resource下路径)
  • merchantSerialNumber:商户证书序列号(登陆 商户平台,账户中心 -> API安全 -> API证书管理 -> 查看证书号)
  • apiV3key:商户APIv3密钥(登陆 商户平台,账户中心 -> API安全的页面 设置该密钥,请求才能通过微信支付的签名校验)
  • payNotifyUrl:支付回调通知地址(本项目的回调接口)

快速接入

项目结构如下,以及需要注意点
在这里插入图片描述

1、引入开发库

Gradle

implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.10'

Maven

<dependency>
  <groupId>com.github.wechatpay-apiv3</groupId>
  <artifactId>wechatpay-java</artifactId>
  <version>0.2.10</version>
</dependency>

2、配置参数

yaml配置文件:

wechat:
  pay:
    # 微信公众号或者小程序等的appId
    appId: XXXX
    # 微信支付商户号
    merchantId: XXXX
    # 商户API私钥
    privateKeyPath: /apiclient_key.pem
    # 商户证书序列号
    merchantSerialNumber: XXXXX
    # 商户APIv3密钥
    apiV3key: XXXXX
    # 支付回调通知地址
    payNotifyUrl: https://XXXXXXXXXXX/api/v1/wechat/pay/callback

微信支付配置类

package com.gw.pay.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Description: 微信支付配置类
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 10:27
 */
@Data
@Component
@ConfigurationProperties(prefix = "wechat.pay")
public class WechatPayProperties {
    /**
     * 微信公众号或者小程序等的appId
     */
    private String appId;
    /**
     * 微信支付商户号
     */
    private String merchantId;
    /**
     * 商户API私钥
     */
    private String privateKeyPath;
    /**
     * 商户证书序列号
     */
    private String merchantSerialNumber;
    /**
     * 商户APIv3密钥
     */
    private String apiV3key;
    /**
     * 支付回调通知地址
     */
    private String payNotifyUrl;
}

3、初始化商户配置

package com.gw.pay.config;

import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


/**
 * Description: 初始化 具有自动下载并更新平台证书能力的RSA配置类并托关于spring
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 10:50
 */
@Slf4j
@Configuration
public class WechatPayAutoCertificateConfig {

    @Autowired
    private WechatPayProperties properties;

    @Autowired
    private ResourceLoader resourceLoader;

    private static final String CLASS_PATH = "classpath:";


    /**
     * 初始化商户配置
     * @return RSAAutoCertificateConfig
     */
    @Bean
    public RSAAutoCertificateConfig rsaAutoCertificateConfig() throws IOException {
        String privatePath = CLASS_PATH + properties.getPrivateKeyPath();
        Resource resourcePrivate = resourceLoader.getResource(privatePath);
        String privateKey = inputStreamToString(resourcePrivate.getInputStream());
        log.info("==========加载微信私钥配置:{}", privateKey);
        RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(properties.getMerchantId())
                .privateKey(privateKey)
                .merchantSerialNumber(properties.getMerchantSerialNumber())
                .apiV3Key(properties.getApiV3key())
                .build();
        return config;
    }

    /**
     * 读取私钥文件,将文件流读取成string
     *
     * @param inputStream
     * @return
     * @throws IOException
     */
    public String inputStreamToString(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
        reader.close();
        return stringBuilder.toString();
    }

}

4、微信支付对接

在接口中定义了创建订单、根据商户订单号查询订单、关闭订单三类核心方法,几乎满足最基本的微信支付对接~

package com.gw.pay.external;

import com.gw.pay.data.CreateOrder;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.model.Transaction;

/**
 * Description: 微信支付对接
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 10:57
 */
public interface WechatPayExternalService {
    /**
     * 创建订单
     * @param createOrder 创建订单请求体
     * @return预支付交易会话标识
     */
    PrepayResponse createOrder(CreateOrder createOrder);

    /**
     * 根据商户订单号查询订单
     *
     * @param outTradeNo 对外贸易号(即本业务中的订单号)
     * @return 事项
     */
    Transaction queryOrderByOutTradeNo(String outTradeNo);

    /**
     * 根据商户订单号查询订单(根据回话id,回调中返回)
     *
     * @param transactionId 商户订单号
     * @return 事项
     */
    Transaction queryOrderByTransactionId(String transactionId);

    /**
     * 关闭订单
     *
     * @param outTradeNo 对外贸易号(即本业务中的订单号)
     */
    void closeOrder(String outTradeNo);
}

具体实现如下:

package com.gw.pay.external.impl;

import com.gw.pay.config.WechatPayProperties;
import com.gw.pay.data.CreateOrder;
import com.gw.pay.external.WechatPayExternalService;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * Description: 微信支付对接
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 10:58
 */
@Slf4j
@Service
public class WechatPayExternalServiceImpl implements WechatPayExternalService {

    @Resource
    private RSAAutoCertificateConfig rsaAutoCertificateConfig;

    @Resource
    private WechatPayProperties properties;

    @Override
    public PrepayResponse createOrder(CreateOrder createOrder) {
        PrepayRequest request = new PrepayRequest();
        request.setAppid(properties.getAppId());
        request.setMchid(properties.getMerchantId());
        request.setDescription(createOrder.getOrderTitle());
        request.setOutTradeNo(createOrder.getOrderId());
        request.setNotifyUrl(properties.getPayNotifyUrl());
        Amount amount = new Amount();
        amount.setTotal(createOrder.getAmountTotal());
        request.setAmount(amount);
        Payer payer = new Payer();
        payer.setOpenid(createOrder.getOpenid());
        request.setPayer(payer);
        PrepayResponse result;
        try {
            JsapiService service = new JsapiService.Builder().config(rsaAutoCertificateConfig).build();
            result = service.prepay(request);
        } catch (HttpException e) {
            log.error("微信下单发送HTTP请求失败,错误信息:{}", e.getHttpRequest());
            throw new RuntimeException("微信下单发送HTTP请求失败", e);
        } catch (ServiceException e) {
            // 服务返回状态小于200或大于等于300,例如500
            log.error("微信下单服务状态错误,错误信息:{}", e.getErrorMessage());
            throw new RuntimeException("微信下单服务状态错误", e);
        } catch (MalformedMessageException e) {
            // 服务返回成功,返回体类型不合法,或者解析返回体失败
            log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
            throw new RuntimeException("服务返回成功,返回体类型不合法,或者解析返回体失败", e);
        }
        return result;
    }

    @Override
    public Transaction queryOrderByOutTradeNo(String outTradeNo) {
        QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
        queryRequest.setMchid(properties.getMerchantId());
        queryRequest.setOutTradeNo(outTradeNo);
        Transaction result;
        try {
            JsapiService service = new JsapiService.Builder().config(rsaAutoCertificateConfig).build();
            result = service.queryOrderByOutTradeNo(queryRequest);
        } catch (ServiceException e) {
            log.error("订单查询失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
            throw new RuntimeException("订单查询失败", e);
        }
        return result;
    }

    @Override
    public Transaction queryOrderByTransactionId(String transactionId) {
        QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
        queryRequest.setMchid(properties.getMerchantId());
        queryRequest.setTransactionId(transactionId);
        Transaction result;
        try {
            JsapiService service = new JsapiService.Builder().config(rsaAutoCertificateConfig).build();
            result = service.queryOrderById(queryRequest);
        } catch (ServiceException e) {
            log.error("订单查询失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
            throw new RuntimeException("订单查询失败", e);
        }
        return result;
    }

    @Override
    public void closeOrder(String outTradeNo) {
        CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
        closeOrderRequest.setMchid(properties.getMerchantId());
        closeOrderRequest.setOutTradeNo(outTradeNo);
        try {
            JsapiService service = new JsapiService.Builder().config(rsaAutoCertificateConfig).build();
            service.closeOrder(closeOrderRequest);
        } catch (ServiceException e) {
            log.error("订单关闭失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
            throw new RuntimeException("订单关闭失败", e);
        }
    }
}

其中使用到的创建订单实体类:

/**
 * Description: 创建订单
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 17:06
 */
@Data
public class CreateOrder {
    /**
     * 订单号
     */
    private String orderId;
    /**
     * 订单标题
     */
    private String orderTitle;
    /**
     * 订单总金额,单位为分
     */
    private Integer amountTotal;
    /**
     * 用户在商户appid下的唯一标识
     */
    private String openid;
}

自测一下

package com.gw.pay;

import com.alibaba.fastjson.JSONObject;
import com.gw.pay.data.CreateOrder;
import com.gw.pay.external.WechatPayExternalService;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootWechatPayApplicationTests {

    @Resource
    private WechatPayExternalService wechatPayExternalService;

    @Test
    void createOrder() {
        CreateOrder createOrder = new CreateOrder();
        createOrder.setOrderId("100000001");
        createOrder.setOrderTitle("商机直租会员续费");
        createOrder.setAmountTotal(1);
        createOrder.setOpenid("oKwQd5MtFfgnXyLBp7vC6Pe3HAJQ");
        PrepayResponse prepayResponse = wechatPayExternalService.createOrder(createOrder);
        System.out.println(JSONObject.toJSONString(prepayResponse));
    }

    @Test
    void queryOrder() {
        Transaction result = wechatPayExternalService.queryOrderByOutTradeNo("100000001");
        System.out.println(JSONObject.toJSONString(result));
        if (Transaction.TradeStateEnum.SUCCESS.equals(result.getTradeState())) {
            System.out.println("支付成功");
        } else {
            System.out.println("支付失败");
        }
    }

    @Test
    void closeOrder() {
        wechatPayExternalService.closeOrder("100000001");
    }

}

5、支付回调-支付通知API

微信支付通过支付通知接口将用户支付成功消息通知给商户,文档地址:pay.weixin.qq.com/wiki/doc/ap…

注意

  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。
package com.gw.pay.controller;

import com.alibaba.fastjson.JSONObject;
import com.gw.pay.utils.HttpServletUtils;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.app.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Description: 微信支付回调接口
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 11:18
 */
@RequestMapping("/api/v1/wechat/pay")
@Slf4j
public class WechatPayCallbackController {

    @Resource
    private RSAAutoCertificateConfig rsaAutoCertificateConfig;

    /**
     * 回调接口
     *
     * @param request
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/callback")
    public synchronized String callback(HttpServletRequest request) throws IOException {
        log.info("------收到支付通知------");
        // 请求头Wechatpay-Signature
        String signature = request.getHeader("Wechatpay-Signature");
        // 请求头Wechatpay-nonce
        String nonce = request.getHeader("Wechatpay-Nonce");
        // 请求头Wechatpay-Timestamp
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        // 微信支付证书序列号
        String serial = request.getHeader("Wechatpay-Serial");
        // 签名方式
        String signType = request.getHeader("Wechatpay-Signature-Type");
        // 构造 RequestParam
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(signType)
                .body(HttpServletUtils.getRequestBody(request))
                .build();

        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
        // 以支付通知回调为例,验签、解密并转换成 Transaction
        log.info("验签参数:{}", requestParam);
        Transaction transaction = parser.parse(requestParam, Transaction.class);
        log.info("验签成功!-支付回调结果:{}", transaction.toString());

        Map<String, String> returnMap = new HashMap<>(2);
        returnMap.put("code", "FAIL");
        returnMap.put("message", "失败");
        if (Transaction.TradeStateEnum.SUCCESS != transaction.getTradeState()) {
            log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", transaction.getOutTradeNo(), transaction.getTransactionId());
            return JSONObject.toJSONString(returnMap);
        }
        //todo 修改订单前,建议主动请求微信查询订单是否支付成功,防止恶意post
        //todo 修改订单信息
        returnMap.put("code", "SUCCESS");
        returnMap.put("message", "成功");
        return JSONObject.toJSONString(returnMap);
    }

}

使用到的网络工具:

package com.gw.pay.utils;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * Description: 网络工具
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/6 11:23
 */
public class HttpServletUtils {
    /**
     * 获取请求体
     *
     * @param request
     * @return
     * @throws IOException
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream = null;
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常!");
        } finally {
            reader.close();
        }
        return sb.toString();
    }
}

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

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

相关文章

【Go语言实战】(25) 分布式算法 MapReduce

MapReduce 写在前面 身为大数据专业的学生&#xff0c;其实大学我也多多少少接触过mapreduce&#xff0c;但是当时觉得这玩意太老了&#xff0c;觉得这和php一样会被时代淘汰。只能说当时确实太年轻了&#xff0c;没有好好珍惜那时候的学习资源… 现在回过头来看mapreduce&a…

聊聊分布式架构——RPC通信原理

目录 RPC通信的基本原理 RPC结构 手撸简陋版RPC 知识点梳理 1.Socket套接字通信机制 2.通信过程的序列化与反序列化 3.动态代理 4.反射 思维流程梳理 码起来 服务端时序图 服务端—Api与Provider模块 客户端时序图 RPC通信的基本原理 RPC&#xff08;Remote Proc…

【算法练习Day13】二叉树的层序遍历翻转二叉树对称二叉树

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 二叉树的层序遍历翻转二叉树…

安装Ubuntu提示:系统找不到指定的文件。

今天我删除Ubuntu后重新下载&#xff0c;发现报错&#xff0c;错误信息如下&#xff1a; 这是因为系统没有卸载干净而导致的。 解决办法&#xff1a; 第一步&#xff1a; ##查询当前已安装的系统 wsl.exe --list --all 执行结果&#xff1a; 第二步&#xff1a; ##注销当前…

【GSEP202303 C++】1级 长方形面积

[GSEP202303 一级] 长方形面积 题目描述 小明刚刚学习了如何计算长方形面积。他发现&#xff0c;如果一个长方形的长和宽都是整数&#xff0c;它的面积一定也是整数。现在&#xff0c;小明想知道如果给定长方形的面积&#xff0c;有多少种可能的长方形&#xff0c;满足长和宽…

BF算法详解(JAVA语言实现)

目录 BF算法的介绍 图解 JAVA语言实现 BF算法的时间复杂度 BF算法的介绍 BF算法&#xff0c;即暴力(Brute Force)算法&#xff0c;是普通的模式匹配算法&#xff0c;BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配&#xff0c;若相等&#xff0c;则继…

C++设计模式-桥接(Bridge)

目录 C设计模式-桥接&#xff08;Bridge&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-桥接&#xff08;Bridge&#xff09; 一、意图 将抽象部分与它的实现部分分离&#xff0c;使它们都可以独立地变化。 二、适用性 你不希望在抽象和它…

[笔记] Microsoft Windows网络编程《三》网际协议

文章目录 前言3.1 IPv43.1.1 寻址3.1.1.1 单播3.1.1.2 多播(组播)3.1.1.3 广播 3.1.2 IPv4 管理协议&#xff08;ARP&#xff0c;ICMP&#xff0c;IGMP&#xff09;ARPICMPIGMP 3.1.3 Winsock 中的IPv4 寻址 3.2 IPv63.2.1 寻址3.2.1.1 单播链接——本地地址站点——本地地址&a…

ipa文件怎么把应用上架到苹果ios系统下载的App Store商城

注册为苹果开发者&#xff1a;首先&#xff0c;您需要注册为苹果开发者。前往苹果开发者网站&#xff08;https://developer.apple.com/&#xff09;&#xff0c;点击"Enroll"按钮&#xff0c;并按照相关步骤注册和付费&#xff08;开发者账号需要年度费用&#xff0…

【Java 进阶篇】使用 JDBCTemplate 执行 DQL 语句详解

在前面的文章中&#xff0c;我们已经学习了如何使用 Spring 的 JDBCTemplate 执行 DML&#xff08;Data Manipulation Language&#xff09;操作&#xff0c;包括插入、更新和删除操作。现在&#xff0c;让我们来深入了解如何使用 JDBCTemplate 执行 DQL&#xff08;Data Query…

SpringCloud Alibaba - Seata 四种分布式事务解决方案(TCC、Saga)+ 实践部署(下)

目录 一、Seata 分布式解决方案 1.1、TCC 模式 1.1.1、TCC 模式理论 对比 TCC 和 AT 模式的一致性和隔离性 TC 的工作模型 1.2.2、TCC 模式优缺点 1.2.3、TCC 模式注意事项&#xff1a;空回滚 1.2.4、TCC 模式注意事项&#xff1a;业务悬挂 1.2.5、实现 TCC 模式 案例…

MySQL数据库基础回顾与复习一

MySQL数据库 一、原理定义概念 定义 数据库(Database)是按照数据结构来组织、存储和管理数据的建立在计算机存储设备上的仓库 数据库是长期储存在计算机内、有组织的、可共享的数据集合 分类&#xff1a; &#xff08;1&#xff09;非结构化数据&#xff1a; 数据相对来讲没…

Spring Cloud Gateway网关中各个过滤器的作用与介绍

文章目录 1. Route To Request URL Filter&#xff08;路由过滤器&#xff09;2. Gateway Filter&#xff08;全局过滤器&#xff09;3. Pre Filter&#xff08;前置过滤器&#xff09;4. Post Filter&#xff08;后置过滤器&#xff09;5. Error Filter&#xff08;错误过滤器…

【刷题笔记10.6】LeetCode:汉明距离

LeetCode&#xff1a;汉明距离 一、题目描述 两个整数之间的汉明距离是指这两个数字对应二进制位不同的位置的数目。 给你两个整数x 和 y&#xff0c;计算并返回他们之间的汉明距离。 二、分析及代码实现 对于汉明距离问题我们其实可以将其转换为&#xff1a;计算x 和 y按…

U盘作为启动盘安装苹果OS X操作系统

如何制作 macOS USB启动盘&#xff1f;如何创建可引导的 macOS 安装器&#xff1f;接下来就为大家带来可引导的苹果电脑 macOS 系统U盘启动盘制作教程。U盘是我们在工作和生活中的好帮手&#xff0c;能储存和传递数据文件&#xff0c;重要的是&#xff0c;U盘还可以制作成苹果电…

leetcode - 365周赛

一&#xff0c;2873.有序三元组中的最大值 I ​ 该题的数据范围小&#xff0c;直接遍历&#xff1a; class Solution {public long maximumTripletValue(int[] nums) {int n nums.length;long ans 0;for(int i0; i<n-2; i){for(int ji1; j<n-1; j){for(int kj1; k<…

矩阵键盘的扫描原理与基础应用

基础知识 原理图 首先需要先将 J5 跳帽放到1和2之间。 表示选择的是矩阵键盘。 简化原理图 扫描原理&#xff1a; 以左上角按键为例。 先向 R1 输出低电平&#xff0c;向 R2&#xff0c;R3&#xff0c;R4 输出高电平。 再然后向 C1&#xff0c;C2&#xff0c;C3&#xff…

在Linux中软链接和硬链接的区别是什么?

2023年10月6日&#xff0c;周五晚上 目录 软链接(SymbolicLink):硬链接(HardLink):区别: 软链接(SymbolicLink): 软链接本身只是一个指向其他文件或目录的指针,不占用任何磁盘空间。软链接的修改或删除不会影响原文件。软链接可以指向不同文件系统中的文件。 硬链接(HardLink…

Cookie和Session详解以及结合生成登录效果

目录 引言 1.Cookie中的数据从哪来数据长啥样&#xff1f; 2.Cookie有什么作用&#xff1f; 3.cookie与session的工作关联&#xff1f; 4.Cookie到哪去&#xff1f; 5.Cookie如何存&#xff1f; 6.Session 7.Cookie与Session的关联与区别 8.通过代码理解 8.1 相关代码 8.2…

c++学习之 继承的方式

在C中&#xff0c;继承方式&#xff08;或继承访问权限&#xff09;有三种&#xff1a;public、protected 和 private&#xff0c;它们决定了派生类&#xff08;子类&#xff09;对基类&#xff08;父类&#xff09;成员的访问权限&#xff0c;它们之间的区别如下&#xff1a; …