网站怎么接入微信扫码支付?

这篇具有很好参考价值的文章主要介绍了网站怎么接入微信扫码支付?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

第01章-准备工作

1、微信支付产品介绍

参考资料:产品中心 - 微信支付商户平台 (qq.com)

付款码支付、JSAPI支付、小程序支付、Native支付、APP支付、刷脸支付

1.1、付款码支付

用户展示微信钱包内的“付款码”给商家,商家扫描后直接完成支付,适用于线下面对面收银的场景。

1.2、JSAPI支付

  • 线下场所:商户展示一个支付二维码,用户使用微信扫描二维码后,输入需要支付的金额,完成支付。
  • 公众号场景:用户在微信内进入商家公众号,打开某个页面,选择某个产品,完成支付。
  • PC网站场景:在网站中展示二维码,用户使用微信扫描二维码,输入需要支付的金额,完成支付。

特点:用户在客户端输入支付金额

1.3、小程序支付

在微信小程序平台内实现支付的功能。

1.4、Native支付

Native支付是指商户展示支付二维码,用户再用微信“扫一扫”完成支付的模式。这种方式适用于PC网站。

特点:商家预先指定支付金额

1.5、APP支付

商户通过在移动端独立的APP应用程序中集成微信支付模块,完成支付。

1.6、刷脸支付

用户在刷脸设备前通过摄像头刷脸、识别身份后进行的一种支付方式。

2、接口版本

微信支付企业主流的API版本有v2和v3,课程中我们使用微信支付APIv3。

V2和V3的比较

相比较而言,APIv2比APIv3安全性更高,但是APIv2中有一些功能在APIv3中尚未完整实现,具体参考如下API字典页面:API字典概览 | 微信支付商户平台文档中心 (qq.com)

3、接入指引

3.1、获取开发参数

如果需要独立申请和开通微信支付功能,可以参考如下官方文档。开通微信支付后,才能获取相关的开发参数以及商户公钥和商户私钥文件。

参考资料:微信支付接入指引 - 微信支付商户平台 (qq.com)

3.2、配置开发参数

service-order服务的resources目录中创建wxpay.properties

这个文件定义了在“接入指引”的步骤中我们提前准备的微信支付相关的参数,例如商户号、APPID、API秘钥等等

# 微信支付相关参数
wxpay:
  mch-id: 1558950191 #商户号
  mch-serial-no: 34345964330B66427E0D3D28826C4993C77E631F # 商户API证书序列号
  private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商户私钥文件
  api-v3-key: UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B # APIv3密钥
  appid: wx74862e0dfcf69954 # APPID
  notify-url: https://7d92-115-171-63-135.ngrok.io/api/order/wxpay/payment/notify # 接收支付结果通知地址
  notify-refund-url: http://agxnyzl04y90.ngrok.xiaomiqiu123.top/api/order/wxpay/refunds/notify # 接收退款结果通知地址

3.3、复制商户私钥

将商户私钥文件复制到配置文件指定的目录下:

资料:资料>微信支付>商户证书>apiclient_key.pem

private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商户私钥文件

3.4、证书密钥使用说明(了解)

参考文档:APIv3证书与密钥使用说明

一个完整的请求和响应的流程:

  • 商户使用商户私钥对请求进行签名,发送给微信支付平台,平台使用商户公钥进行签名验证。
  • 微信支付平台使用平台私钥对响应进行签名,商户使用微信支付平台公钥对响应进行验签。

第02章-订单支付

1、微信支付平台证书的获取

1.1、引入SDK

参考文档:SDK&工具

我们可以使用官方提供的 SDK wechatpay-java

在service-order微服务中添加依赖:

<!--微信支付APIv3-->
<dependency>
  <groupId>com.github.wechatpay-apiv3</groupId>
  <artifactId>wechatpay-java</artifactId>
  <version>0.2.6</version>
</dependency>

1.2、读取支付参数

在config 包中 创建 WxPayConfig.java

package com.atguigu.syt.order.config;

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;
    
    // 接收支付结果通知地址
    private String notifyUrl;
    
    // 接收退款结果通知地址
    private String notifyRefundUrl;
   
}

1.3、自动更新微信支付平台证书

在 API 请求过程中,客户端需使用微信支付平台证书,验证服务器应答的真实性和完整性。我们使用自动更新平台证书的配置类 RSAAutoCertificateConfig。每个商户号只能创建一个 RSAAutoCertificateConfig

WxPayConfig中添加如下方法:

/**
     * 获取微信支付配置对象
     * @return
     */
@Bean
public RSAAutoCertificateConfig getConfig(){

    return new RSAAutoCertificateConfig.Builder()
        .merchantId(mchId)
        .privateKeyFromPath(privateKeyPath)
        .merchantSerialNumber(mchSerialNo)
        .apiV3Key(apiV3Key)
        .build();
}

RSAAutoCertificateConfig 通过 RSAAutoCertificateProvider 自动下载微信支付平台证书。 同时,RSAAutoCertificateProvider 会启动一个后台线程,定时更新证书(目前设计为60分钟),以实现证书过期时的新老证书平滑切换。

常见错误:引入商户私钥后如果项目无法启动,则需要升级JDK版本,并重新配置idea编译和运行环境到最新版本的JDK。建议升级到1.8.0_300以上

img

2、生成支付二维码

2.1、Native支付流程

参考文档:业务流程时序图

2.2、Controller

在service-order中创建FrontWXPayController

package com.atguigu.syt.order.controller.front;

@Api(tags = "微信支付接口")
@RestController
@RequestMapping("/front/order/wxpay")
public class FrontWXPayController {

    @Resource
    private WxPayService wxPayService;

    @Resource
    private AuthContextHolder authContextHolder;

    @ApiOperation("获取支付二维码url")
    @ApiImplicitParam(name = "outTradeNo",value = "订单号", required = true)
    @GetMapping("/auth/nativePay/{outTradeNo}")
    public Result<String> nativePay(@PathVariable String outTradeNo, HttpServletRequest request, HttpServletResponse response) {

        //校验用户登录状态
        authContextHolder.checkAuth(request, response);

        String codeUrl = wxPayService.createNative(outTradeNo);
        return Result.ok(codeUrl);
    }
}

2.3、Service

SDK参考代码:wechatpay-java/NativePayServiceExample.java at main · wechatpay-apiv3/wechatpay-java · GitHub

具体代码示例如下:

Native下单API参数参考:Native下单API

接口:OrderInfoService

/**
 * 根据订单号获取订单
 * @param outTradeNo
 * @return
 */
OrderInfo selectByOutTradeNo(String outTradeNo);

实现:OrderInfoServiceImpl

@Override
public OrderInfo selectByOutTradeNo(String outTradeNo) {
    LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);
    return baseMapper.selectOne(queryWrapper);
}

接口:WxPayService

package com.atguigu.syt.order.service;

public interface WxPayService {

    /**
     * 获取支付二维码utl
     * @param outTradeNo
     * @return
     */
    String createNative(String outTradeNo);
}

实现:WxPayServiceImpl

package com.atguigu.syt.order.service.impl;

@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Resource
    private RSAAutoCertificateConfig rsaAutoCertificateConfig;

    @Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private OrderInfoService orderInfoService;

    @Override
    public String createNative(String outTradeNo) {

        // 初始化服务
        NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();

        // 调用接口
        try {

            //获取订单
            OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);

            PrepayRequest request = new PrepayRequest();
            // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
            request.setAppid(wxPayConfig.getAppid());
            request.setMchid(wxPayConfig.getMchId());

            request.setDescription(orderInfo.getTitle());
            request.setOutTradeNo(outTradeNo);
            request.setNotifyUrl(wxPayConfig.getNotifyUrl());

            Amount amount = new Amount();
            //amount.setTotal(orderInfo.getAmount().multiply(new BigDecimal(100)).intValue());
            amount.setTotal(1);//1分钱
            request.setAmount(amount);
            // 调用接口
            PrepayResponse prepayResponse = service.prepay(request);

            return prepayResponse.getCodeUrl();

        } catch (HttpException e) { // 发送HTTP请求失败
            // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义
            log.error(e.getHttpRequest().toString());
            throw new GuiguException(ResultCodeEnum.FAIL);
        } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
            // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
            log.error(e.getResponseBody());
            throw new GuiguException(ResultCodeEnum.FAIL);
        } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
            // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义
            log.error(e.getMessage());
            throw new GuiguException(ResultCodeEnum.FAIL);
        }
    }
}

可见,使用 SDK 并不需要计算请求签名和验证应答签名。

3、前端整合

3.1、api

创建api/wxpay.js

import request from '~/utils/request'
export default {
  //获取支付二维码url
  nativePay(outTradeNo) {
    return request({
      url: `/front/order/wxpay/auth/nativePay/${outTradeNo}`,
      method: 'get'
    })
  },
}

3.2、修改show.vue

引入api

import wxpayApi from '~/api/wxpay'

添加data数据

codeUrl: null, //微信支付二维码
isPayShow: false, //不显示登录二维码组件
payText: '支付'

修改按钮

将
<div class="v-button" @click="pay()">支付</div>
修改为
<div class="v-button" @click="pay()">{{payText}}</div>

添加方法

//支付
pay() {
    //防止重复提交
    if(this.isPayShow) return
    this.isPayShow = true
    this.payText = '支付中.....'

    //显示二维码
    wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {
        this.codeUrl = response.data
        this.dialogPayVisible = true
    })
},

//关闭对话框
closeDialog(){
    //恢复支付按钮
    this.isPayShow = false
    this.payText = '支付'
}

用二维码组件替换img图片

<img src="二维码链接" alt="" />

替换成

<qriously :value="codeUrl" :size="220"/>

第03章-查询支付结果

1、支付查单

参考文档:微信支付查单接口

商户可以主动调用微信支付查单接口,同步订单状态。

调用查询订单接口,如果支付成功则更新订单状态添加交易记录,如果支付尚未成功则轮询查单

1.1、更新订单状态

OrderInfoService接口

/**
     * 根据订单号更新订单状态
     * @param outTradeNo
     * @param status
     */
void updateStatus(String outTradeNo, Integer status);

OrderInfoServiceImpl实现

@Override
public void updateStatus(String outTradeNo, Integer status) {

    LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);

    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderStatus(status);
    baseMapper.update(orderInfo, queryWrapper);
}

1.2、添加交易记录

PaymentInfoService接口

/**
     * 保存交易记录
     * @param orderInfo
     * @param transaction
     */
void savePaymentInfo(OrderInfo orderInfo, Transaction transaction);

实现:PaymentInfoServiceImpl

@Override
public void savePaymentInfo(OrderInfo orderInfo, Transaction transaction) {

    PaymentInfo paymentInfo = new PaymentInfo();
    paymentInfo.setOrderId(orderInfo.getId());
    paymentInfo.setPaymentType(PaymentTypeEnum.WEIXIN.getStatus());
    paymentInfo.setOutTradeNo(orderInfo.getOutTradeNo());//数据库字段的长度改成32
    paymentInfo.setSubject(orderInfo.getTitle());
    paymentInfo.setTotalAmount(orderInfo.getAmount());
    paymentInfo.setPaymentStatus(PaymentStatusEnum.PAID.getStatus());
    paymentInfo.setTradeNo(transaction.getTransactionId());
    paymentInfo.setCallbackTime(new Date());
    paymentInfo.setCallbackContent(transaction.toString());
    baseMapper.insert(paymentInfo);
}

1.3、查单Controller

FrontWXPayController

@ApiOperation("查询支付状态")
@ApiImplicitParam(name = "outTradeNo",value = "订单id", required = true)
@GetMapping("/queryPayStatus/{outTradeNo}")
public Result queryPayStatus(@PathVariable String outTradeNo) {
    //调用查询接口
    boolean success = wxPayService.queryPayStatus(outTradeNo);
    if (success) {
        return Result.ok().message("支付成功");
    }
    return Result.ok().message("支付中").code(250);
}

1.4、查单Service

接口:WxPayService

/**
     * 查询订单支付状态
     * @param outTradeNo
     * @return
     */
boolean queryPayStatus(String outTradeNo);

实现:WxPayServiceImpl

@Resource
private PaymentInfoService paymentInfoService;

@Override
public boolean queryPayStatus(String outTradeNo) {

    // 初始化服务
    NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();

    // 调用接口
    try {

        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
        request.setOutTradeNo(outTradeNo);
        request.setMchid(wxPayConfig.getMchId());

        // 调用接口
        Transaction transaction = service.queryOrderByOutTradeNo(request);
        Transaction.TradeStateEnum tradeState = transaction.getTradeState();

        //支付成功
        if(tradeState.equals(Transaction.TradeStateEnum.SUCCESS)){

            OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);

            //更新订单状态
            orderInfoService.updateStatus(outTradeNo, OrderStatusEnum.PAID.getStatus());
            //记录支付日志
            paymentInfoService.savePaymentInfo(orderInfo, transaction);

            //通知医院修改订单状态
            //组装参数
            HashMap<String, Object> paramsMap = new HashMap<>();
            paramsMap.put("hoscode", orderInfo.getHoscode());
            paramsMap.put("hosOrderId", orderInfo.getHosOrderId());
            paramsMap.put("timestamp", HttpRequestHelper.getTimestamp());

            paramsMap.put("sign", HttpRequestHelper.getSign(paramsMap, "8af52af00baf6aec434109fc17164aae"));
            //发送请求
            JSONObject jsonResult = HttpRequestHelper.sendRequest(paramsMap, "http://localhost:9998/order/updatePayStatus");
            //解析响应
            if(jsonResult.getInteger("code") != 200) {
                log.error("查单失败,"
                          + "code:" + jsonResult.getInteger("code")
                          + ",message:" + jsonResult.getString("message")
                         );
                throw new GuiguException(ResultCodeEnum.FAIL.getCode(), jsonResult.getString("message"));
            }

            //返回支付结果
            return true;//支付成功
        }

        return false;//支付中

    } catch (HttpException e) { // 发送HTTP请求失败
        // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义
        log.error(e.getHttpRequest().toString());
        throw new GuiguException(ResultCodeEnum.FAIL);
    } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
        // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
        log.error(e.getResponseBody());
        throw new GuiguException(ResultCodeEnum.FAIL);
    } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
        // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义
        log.error(e.getMessage());
        throw new GuiguException(ResultCodeEnum.FAIL);
    }
}

2、前端整合

2.1、修改request.js

utils/request.js的响应拦截器中增加对250状态的判断

}else if (response.data.code !== 200) {
    
修改为

}else if (response.data.code !== 200 && response.data.code !== 250) {

2.2、api

在api/wxpay.js中添加的方法

//查询订单
queryPayStatus(outTradeNo) {
    return request({
        url: `/front/order/wxpay/queryPayStatus/${outTradeNo}`,
        method: 'get'
    })
},

3.3、show.vue轮询查单

js中修改pay方法:每隔3秒查单

添加queryPayStatus方法

完善closeDialog方法:停止定时器

//发起支付
pay(){
  if(this.isPayShow) return
  this.isPayShow = true
  this.payText = '支付中.....'
    
  wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {
    this.codeUrl = response.data
    this.dialogPayVisible = true
      
    //每隔3秒查单
    this.timer = setInterval(()=>{
      console.log('轮询查单:' + new Date())
      this.queryPayStatus()
    }, 3000)
  })
},
    
//查询订单状态
queryPayStatus(){
  wxpayApi.queryPayStatus(this.orderInfo.outTradeNo).then(response => {
    if(response.code == 250){
        return
    }
    //清空定时器
    clearInterval(this.timer)
    window.location.reload()
  })
},

//关闭对话框
closeDialog(){
    //停止定时器
    clearInterval(this.timer)
    //恢复支付按钮
    this.isPayShow = false
    this.payText = '支付'
}

3、查单应答超时

3.1、测试应答超时

//应答超时
//设置响应超时,支付成功后没有及时响应,可能继续轮询查单,此时数据库会记录多余的支付日志
try {
    TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}


//返回支付结果
return true;//支付成功

3.2、处理重复通知

支付成功后,判断订单状态:文章来源地址https://www.toymoban.com/news/detail-492663.html

OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);
//处理支付成功后重复查单
//保证接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的
Integer orderStatus = orderInfo.getOrderStatus();
if (OrderStatusEnum.UNPAID.getStatus().intValue() != orderStatus.intValue()) {
    return true;//支付成功、关单、。。。
}

//更新订单状态
//记录支付日志
......

到了这里,关于网站怎么接入微信扫码支付?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Java后台实现网站微信扫码登录功能,获取用户openid,及微信用户信息(小程序码方案),关联微信小程序(个人主体小程序也可以)

    Java后台实现网站微信扫码登录功能,获取用户openid,及微信用户信息(小程序码方案),关联微信小程序(个人主体小程序也可以)

    目录 前言 下面展示操作流程 注册微信小程序 通过后台获取小程序码 前端处理 时序图理解 方案实现步骤 前言 很多业务场景之下我们需要实现  微信扫码登录  的需求,如: 同步网站与小程序的用户数据 。 需要获取用户微信相关基本信息,如头像、id等 实例:小程序上的

    2024年02月02日
    浏览(17)
  • 使用Spring Boot Security 实现多认证 手机号登录 微信扫码登录 微信扫码注册

    使用Spring Boot Security 实现多认证 手机号登录 微信扫码登录 微信扫码注册

    Spring Boot 3.x Spring Security 5.7 Spring Redis MyBatis plus 前端 Vue 公司 最近有个新项目 使用单点登录 sso 百度了一圈 也没怎么找到微信扫码注册的功能于是自己写 需求就是 手机 + 密码登录 微信扫码登录 微信扫码注册 微信二维码 登录 和注册二合一 具体实现 稍后我会说 本教程将指导

    2024年04月10日
    浏览(15)
  • 微信扫码登陆流程

    以下为几类型微信登录的功能说明(基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统): 序号 类型 授权域/接口 用户侧使用流程 接入流程 1 App 接入微信SDK,并调用snsapi_userinfo (1)在App内选择使用微信登录 (2)拉起微信客户端,打开用户授权页,完成登录授权 (1)注

    2024年02月09日
    浏览(15)
  • JAVA微信扫码登录

    JAVA微信扫码登录

    本帖有两种微信扫码的方式。根据测试公众号进行测试,所以不用担心没有公众号。 因为涉及到微信回调,所以要走本地的话需要进行内网穿透,本贴不包含内网穿透教学,很简单,请自行百度。 测试号地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 扫码登录后,进

    2024年02月16日
    浏览(20)
  • 手把手教你接入网站微信支付

    手把手教你接入网站微信支付

    小摊小贩们在线下交易一般无需接入微信支付,只需要向别人出示自己的付款码就行。为什么?因为一手交钱,一手交货,你不付钱,人家不会给你商品。 但在网上,要实现无人值守,别人扫码付了款,平台得自动知道,然后再发货。所以必须接入微信支付。 微信支付必须

    2024年02月07日
    浏览(13)
  • 微信扫码跳转微信小程序

    微信扫码跳转微信小程序

    一:首先声明为什么需要这样做         项目中需要在后台管理页面进行扫码支付,其他人弄了微信小程序支付,所以就需要挑战小程序进行支付,在跳转的时候需要参数例如订单编号等 二:跳转小程序的方法有多种         接口调用凭证 | 微信开放文档          具体可

    2024年02月11日
    浏览(45)
  • springboot实现微信扫码登录

    springboot实现微信扫码登录

    目录 1,注册微信开发者账号,创建一个应用,获取AppID和AppSecret 2,在Spring Boot项目中引入微信SDK依赖  3,在Spring Boot配置文件中配置AppID和AppSecret 4,创建一个Controller,处理微信登录请求。 5,在启动类中配置WxMpService的Bean 6,在页面中提供微信登录按钮,点击后跳转到授权

    2024年02月04日
    浏览(11)
  • 微信扫码进入小程序特定页面

    微信扫码进入小程序特定页面

    小程序配置 开发 - 开发管理 - 开发设置-普通链接二维码打开小程序 配置好的截图 如下:二维码规则建议是自己的域名 + /mini/ 功能页面 pages/index/index 是为了方便跳转其他页面 记得把校验文件发给后端 web 端处理 二维码格式为: 二维码规则/功能页/你想跳转的页面 小程序处

    2024年01月22日
    浏览(8)
  • 微信扫码跳转到微信小程序指定页面

    微信扫码跳转到微信小程序指定页面

    用户想通过在微信上扫描实验室二维码直接进入小程序申请加入实验室 1、首先我们需要在微信公众平台的开发管理——开发设置,找到(扫普通链接二维码打开小程序),点击添加,会出现下面的页面,配置好之后点击保存就行了 填写页配置点击保存之后要再发布 2、在微

    2024年02月16日
    浏览(48)
  • 微信扫码跳转小程序并传参

    微信扫码跳转小程序并传参

    微信公众平台 扫码登录小程序的后台 开发》开发管理》开发设置》扫普通链接二维码打开小程序》添加 这里我用一个aa.txt来替代。把他放到test文件夹下 添加一个路由转发 访问页面验证 这里新增一个测试连接 https://我的域名/test?aa=333 然后使用二维码在线生成工具生成一个

    2024年02月12日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包