SpringBoot集成微信支付JSAPIV3保姆教程

来源:https://www.cnblogs.com/yanpeng19940119/archive/2023/09/11/17693895.html
-Advertisement-
Play Games

前言 最近為一個公眾號h5商城接入了微信支付功能,查找資料過程中踩了很多坑,以此文章記錄一下和大家分享 前期準備 公眾號認證 微信支付功能需要開通企業號併進行資質認證,費用一年300,且需企業營業執照等信息,對公賬戶打款驗證 登錄微信公眾平臺https://mp.weixin.qq.com/,創建服 ...


前言

最近為一個公眾號h5商城接入了微信支付功能,查找資料過程中踩了很多坑,以此文章記錄一下和大家分享

前期準備

公眾號認證

微信支付功能需要開通企業號併進行資質認證,費用一年300,且需企業營業執照等信息,對公賬戶打款驗證

登錄微信公眾平臺https://mp.weixin.qq.com/,創建服務號

如果已有服務號掃碼登錄後點擊公眾號頭像選擇認證詳情菜單

商戶開通

點擊公眾號左側微信支付菜單,選擇右側關聯商戶按鈕,如果沒有商戶按指引申請

參數獲取

公眾號參數

點擊左側基本配置菜單,記錄右側的應用ID(appid

商戶參數

點擊公眾號左側微信支付菜單,滑動到已關聯商戶號,點擊查看按鈕

進入商戶後,選擇產品中心,左側開發配置,記錄商戶號(mchId

進入商戶後,選擇賬戶中心,左側API安全,按照指引獲取APIV3密鑰(apiV3Key),API證書的序列號(merchantSerialNumber)和私鑰文件apiclient_key.pem

參數配置

外網映射

在微信支付本地調試時需要用到外網映射工具,這裡推薦NATAPP:https://natapp.cn/(非廣)

一個月帶備案功能變數名稱的映射隧道12元,我們需要兩個,一個映射公眾號菜單頁面,一個映射後端介面

公眾號參數

進入公眾點擊左側自定義菜單,右側點擊添加菜單,輸入外網映射後的菜單地址

如果你是新手,需要進行網頁授權認證獲取用戶openid,那你還需要進行網頁授權功能變數名稱的設置

點左側介面許可權菜單,修改右側的網頁授權用戶信息獲取

進入後設置JS介面安全功能變數名稱,會需要將一個txt認證文件放置到你的靜態頁面目錄,參照指引即可

商戶參數

進入商戶後,選擇產品中心,左側我的產品,進入JSAPI支付

點擊產品設置,在支付配置模塊,添加支付授權目錄(後端介面和前端網頁都添加)

支付對接

參數聲明

wechartpay:
  # 公眾號id
  appId: xxx
  # 公眾號中微信支付綁定的商戶的商戶號
  mchId: xxxx
  # 商戶apiV3Keyz密鑰
  apiV3Key: xxxx
  #商戶證書序列號
  merchantSerialNumber: xxxx
  # 支付回調地址
  v3PayNotifyUrl: http://xxxxxx/wechatpay/pay_notify
  # 退款回調地址
  v3BackNotifyUrl: http://xxxxx/wechatpay/back_notify
	@Value("${wechartpay.appId}")
    private String appId;
    @Value("${wechartpay.mchId}")
    private String mchId;
    @Value("${wechartpay.apiV3Key}")
    private String apiV3Key;
    @Value("${wechartpay.merchantSerialNumber}")
    private String merchantSerialNumber;
    @Value("${wechartpay.v3PayNotifyUrl}")
    private String v3PayNotifyUrl;
    @Value("${wechartpay.v3BackNotifyUrl}")
    private String v3BackNotifyUrl;

	public static RSAAutoCertificateConfig config = null;
    public static JsapiServiceExtension service = null;
    public static RefundService backService = null;

	private void initPayConfig() {
        initConfig();
        // 構建service
        if (service == null) {
            service = new JsapiServiceExtension.Builder().config(config).build();
        }
    }

    private void initBackConfig() {
        initConfig();
        // 構建service
        if (backService == null) {
            backService = new RefundService.Builder().config(config).build();
        }
    }

    private void initConfig() {
        String filePath = getFilePath("apiclient_key.pem");
        if (config == null) {
            config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(mchId)
                    .privateKeyFromPath(filePath)
                    .merchantSerialNumber(merchantSerialNumber)
                    .apiV3Key(apiV3Key)
                    .build();
        }
    }

    public RSAAutoCertificateConfig getConfig() {
        initConfig();
        return config;
    }

    public static String getFilePath(String classFilePath) {
        String filePath = "";
        try {
            String templateFilePath = "tempfiles/classpathfile/";
            File tempDir = new File(templateFilePath);
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }
            String[] filePathList = classFilePath.split("/");
            String checkFilePath = "tempfiles/classpathfile";
            for (String item : filePathList) {
                checkFilePath += "/" + item;
            }
            File tempFile = new File(checkFilePath);
            if (tempFile.exists()) {
                filePath = checkFilePath;
            } else {
                //解析
                ClassPathResource classPathResource = new ClassPathResource(classFilePath);
                InputStream inputStream = classPathResource.getInputStream();
                checkFilePath = "tempfiles/classpathfile";
                for (int i = 0; i < filePathList.length; i++) {
                    checkFilePath += "/" + filePathList[i];
                    if (i == filePathList.length - 1) {
                        //文件
                        File file = new File(checkFilePath);
                        if (!file.exists()) {
                            FileUtils.copyInputStreamToFile(inputStream, file);
                        }
                    } else {
                        //目錄
                        tempDir = new File(checkFilePath);
                        if (!tempDir.exists()) {
                            tempDir.mkdirs();
                        }
                    }
                }
                inputStream.close();
                filePath = checkFilePath;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return filePath;
    }

將apiclient_key.pem私鑰文件拷貝到resources文件夾根目錄

Maven引用

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.11</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.8.0</version>
        </dependency>

用戶授權

為了測試流程的完整性,這裡簡單描述下如何通過網頁授權獲取用戶的openid,幾個參數定義如下

var appid = "xxxx";
var appsecret = "xxxx";
redirect_uri = encodeURIComponent("http://xxxx/xxx.html");
response_type = "code";
scope = "snsapi_userinfo";
  • 發起授權請求

    function getCodeUrl() {
        var url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + redirect_uri + "&response_type=" + response_type + "&scope=" + scope + "#wechat_redirect";
        return url
    }
    

    跳轉到開始授權頁面,會跳轉到redirect_uri這個頁面,url參數攜帶授權code

  • 用戶同意授權

    function getAuthUrl(code) {
        var url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + appsecret + "&code=" + code + "&grant_type=authorization_code";
        return url
    }
    

    根據code生成正式授權url,需要用戶手動點擊同意,使用get方式請求該url成功後會返回openid

    var url = getAuthUrl(code);
    $.get(url, function (data, status) {
       var result = JSON.parse(data)
       if (!result.errcode) {
           var openid = result.openid;
        }
    });
    

不使用用戶授權流程也能簡單的獲取到用戶openid進行測試,如果該用戶關註了公眾號,選擇公眾號左側的用戶管理菜單,點擊用戶跳轉到與該用戶的聊天界面,url參數中的tofakeid就是用戶的openid

支付準備

根據用戶的openid,訂單號,訂單金額,訂單說明四個參數進行支付前的參數準備,會返回如下參數

公眾號ID(appId)

時間戳(timeStamp)

隨機串(nonceStr)

打包值(packageVal)

微信簽名方式(signType)

微信簽名(paySign)

這裡的orderID指業務中生成的訂單號,最大32位,由數字和字母組成,支付金額最終要轉轉換成已分為單位

    @PostMapping("/prepay")
    public Object prepay(@RequestBody Map<String, Object> params) throws Exception {
        String openId = "xxxx";
        String orderID = String.valueOf(params.get("orderID"));
        BigDecimal payAmount = new BigDecimal(String.valueOf(params.get("payAmount")));
        String payDes = "支付測試";
        return paySDK.getPreparePayInfo(openId,orderID,payAmount,payDes);
    }
	//支付前的準備參數,供前端調用
    public PrepayWithRequestPaymentResponse getPreparePayInfo(String openid, String orderID, BigDecimal payAmount, String payDes) {
        initPayConfig();
        //元轉換為分
        Integer amountInteger = (payAmount.multiply(new BigDecimal(100))).intValue();
        //組裝預約支付的實體
        // request.setXxx(val)設置所需參數,具體參數可見Request定義
        PrepayRequest request = new PrepayRequest();
        //計算金額
        Amount amount = new Amount();
        amount.setTotal(amountInteger);
        amount.setCurrency("CNY");
        request.setAmount(amount);
        //公眾號appId
        request.setAppid(appId);
        //商戶號
        request.setMchid(mchId);
        //支付者信息
        Payer payer = new Payer();
        payer.setOpenid(openid);
        request.setPayer(payer);
        //描述
        request.setDescription(payDes);
        //微信回調地址,需要是https://開頭的,必須外網可以正常訪問
        //本地測試可以使用內網穿透工具,網上很多的
        request.setNotifyUrl(v3PayNotifyUrl);
        //訂單號
        request.setOutTradeNo(orderID);
        // 加密
        PrepayWithRequestPaymentResponse payment = service.prepayWithRequestPayment(request);
        //預設加密類型為RSA
        payment.setSignType("MD5");
        payment.setAppId(appId);
        return payment;
    }

支付拉起

在微信環境調用支付準備介面獲取參數後,使用WeixinJSBridge.invoke方法發起微信支付

function submitWeChatPay(orderID,payAmount,callback) {
    if (typeof WeixinJSBridge != "undefined") {
        var param = {
            orderID:orderID,
            payAmount:payAmount
        }
        httpPost(JSON.stringify(param),"http://xxxx/wechatpay/prepay",function (data, status) {
            var param = {
                "appId": data.appId,    
                "timeStamp": data.timeStamp,  
                "nonceStr": data.nonceStr,    
                "package": data.packageVal,
                "signType": data.signType,    
                "paySign": data.paySign
            }
            WeixinJSBridge.invoke(
                'getBrandWCPayRequest', param,callback);
        })
    } else {
        alert("非微信環境")
    }
}

支付回調

支付回調地址是在支付準備階段傳遞的,在用戶付款完成後會自動調用該介面,傳遞支付訂單的相關信息

    @PostMapping("/pay_notify")
    public void pay_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //獲取報文
        String body = getRequestBody(request);
        //隨機串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        //微信傳遞過來的簽名
        String signature = request.getHeader("Wechatpay-Signature");
        //證書序列號(微信平臺)
        String serialNo = request.getHeader("Wechatpay-Serial");
        //時間戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        InputStream is = null;
        try {
            is = request.getInputStream();
            // 構造 RequestParam
            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonceStr)
                    .signature(signature)
                    .timestamp(timestamp)
                    .body(body)
                    .build();
            // 如果已經初始化了 RSAAutoCertificateConfig,可以直接使用  config
            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(paySDK.getConfig());
            // 驗簽、解密並轉換成 Transaction
            Transaction transaction = parser.parse(requestParam, Transaction.class);
            //記錄日誌信息
            Transaction.TradeStateEnum state = transaction.getTradeState();
            String orderNo = transaction.getOutTradeNo();
            System.out.println("訂單號:" + orderNo);
            if (state == Transaction.TradeStateEnum.SUCCESS) {
                System.out.println("支付成功");
                //TODO------
                //根據自己的需求處理相應的業務邏輯,非同步

                //通知微信回調成功
                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
            } else {
                System.out.println("微信回調失敗,JsapiPayController.payNotify.transaction:" + transaction.toString());
                //通知微信回調失敗
                response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            is.close();
        }
    }

訂單查詢

除了支付回調的非同步通知,我們還需要通過定時任務主動去查詢支付信息來保證業務訂單支付狀態的正確

    @PostMapping("/pay_check")
    public Object pay_check(@RequestBody Map<String, Object> params) throws Exception {
        String orderID = String.valueOf(params.get("orderID"));
        com.wechat.pay.java.service.payments.model.Transaction transaction = paySDK.getPayOrderInfo(orderID);
        com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum state = transaction.getTradeState();
        if (state == com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.SUCCESS) {
            return Result.okResult().add("obj", transaction);
        } else {
            return Result.errorResult().add("obj", transaction);
        }
    }
    //獲取訂單支付結果信息
    public com.wechat.pay.java.service.payments.model.Transaction getPayOrderInfo(String orderID) {
        initPayConfig();
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setMchid(mchId);
        request.setOutTradeNo(orderID);
        com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(request);
        return transaction;
    }

退款申請

退款申請需要業務訂單號和微信支付號,所以我們這裡先通過查詢訂單信息得到transactionId,你也可以冗餘記錄在表中

退款支持全款退款和部分退款,部分退款對應的場景就是同一個訂單買了多個商品,只退款了其中一個

這裡只發起退款申請,具體的退款處理進度通知由退款回調完成

    @PostMapping("/back")
    public Object back(@RequestBody Map<String, Object> params) throws Exception {
        String orderID = String.valueOf(params.get("orderID"));
        String backID = String.valueOf(params.get("backID"));
        BigDecimal backAmount = new BigDecimal(String.valueOf(params.get("backAmount")));
        paySDK.applyRefund(orderID,backID,backAmount);
        return Result.okResult();
    }
    //申請退款
    public void applyRefund(String orderID, String backID,BigDecimal backAmount) {
        initPayConfig();
        initBackConfig();
        QueryOrderByOutTradeNoRequest payRequest = new QueryOrderByOutTradeNoRequest();
        payRequest.setMchid(mchId);
        payRequest.setOutTradeNo(orderID);
        com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(payRequest);


        CreateRequest request = new CreateRequest();
        request.setTransactionId(transaction.getTransactionId());
        request.setNotifyUrl(v3BackNotifyUrl);
        request.setOutTradeNo(transaction.getOutTradeNo());
        request.setOutRefundNo(backID);
        request.setReason("測試退款");
        AmountReq amountReq = new AmountReq();
        amountReq.setCurrency(transaction.getAmount().getCurrency());
        amountReq.setTotal(Long.parseLong((transaction.getAmount().getTotal().toString())));
        amountReq.setRefund( (backAmount.multiply(new BigDecimal(100))).longValue());
        request.setAmount(amountReq);
        backService.create(request);
    }

退款回調

退款回調在申請退款後自動調用該介面,由於退款需要一定的處理時間,所以回調通知一般顯示的狀態為處理中(PROCESSING)可以在此回調更新訂單退款的處理狀態

    @PostMapping("/back_notify")
    public void back_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //獲取報文
        String body = getRequestBody(request);
        //隨機串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        //微信傳遞過來的簽名
        String signature = request.getHeader("Wechatpay-Signature");
        //證書序列號(微信平臺)
        String serialNo = request.getHeader("Wechatpay-Serial");
        //時間戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        InputStream is = null;
        try {
            is = request.getInputStream();
            // 構造 RequestParam
            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonceStr)
                    .signature(signature)
                    .timestamp(timestamp)
                    .body(body)
                    .build();
            // 如果已經初始化了 RSAAutoCertificateConfig,可以直接使用  config
            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(paySDK.getConfig());
            // 驗簽、解密並轉換成 Transaction
            Refund refund = parser.parse(requestParam, Refund.class);
            //記錄日誌信息
            Status state = refund.getStatus();
            String orderID = refund.getOutTradeNo();
            String backID = refund.getOutRefundNo();
            System.out.println("訂單ID:" + orderID);
            System.out.println("退款ID:" + backID);
            if (state == Status.PROCESSING) {
                //TODO------
                //根據自己的需求處理相應的業務邏輯,非同步

                //通知微信回調成功
                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
                System.out.println("退款處理中");
            } else if (state == Status.SUCCESS) {
                //TODO------
                //根據自己的需求處理相應的業務邏輯,非同步

                //通知微信回調成功
                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
                System.out.println("退款完成");
            } else {
                System.out.println("微信回調失敗,JsapiPayController.Refund:" + state.toString());
                //通知微信回調失敗
                response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            is.close();
        }
    }

退款查詢

除了退款回調的非同步通知,我們還需要通過定時任務主動去查詢退款信息來保證業務訂單退款狀態的正確

 @PostMapping("/back_check")
    public Object back_check(@RequestBody Map<String, Object> params) throws Exception {
        String backID = String.valueOf(params.get("backID"));
        Refund refund = paySDK.getRefundOrderInfo(backID);
        if (refund.getStatus() == Status.SUCCESS) {
            return Result.okResult().add("obj", refund);
        }if (refund.getStatus() == Status.PROCESSING) {
            return Result.okResult().setCode(2).setMsg("退款處理中").add("obj", refund);
        } else {
            return Result.errorResult().add("obj", refund);
        }
    }
    //獲取訂單退款結果信息
    public Refund getRefundOrderInfo(String backID){
        initBackConfig();
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.setOutRefundNo(backID);
        return backService.queryByOutRefundNo(request);
    }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 很早之前,就寫過一篇與原生嵌套相關的文章 -- CSS 即將支持嵌套,SASS/LESS 等預處理器已無用武之地?,彼時 CSS 原生嵌套還處於工作草案 Working Draft (WD) 階段,而今天(2023-09-02),CSS 原生嵌套 Nesting 終於成為了既定的規範! CSS 原生 ...
  • 淺拷貝 當我們想要複製一段數據的時候嗎,我們就會用到拷貝;拷貝數據又分為了淺拷貝和深拷貝,淺拷貝指複製對象或數組的頂層結構,如果對象或數組中有引用類型的屬性值,複製的是引用(地址)而非值;而深拷貝則是遞歸複製完整的對象或數組,包括嵌套的子對象或子數組,生成一個全新的對象,新對象和原對象的引用地址不同 ...
  • 前言 大家好,我是 god23bin,今天我們來聊一聊 Spring 框架中的 Bean 作用域(Scope)。 什麼是 Bean 的作用域? 我們在以 XML 作為配置元數據的情況下,進行 Bean 的定義,是這樣的: <bean id="vehicle" class="cn.god23bin.d ...
  • 1 ★★★ 例1 : 判斷集合是否為空: 2 CollectionUtils.isEmpty(null); //控制台列印:true 3 CollectionUtils.isEmpty(new ArrayList());//控制台列印:true 4 CollectionUtils.isEmpty({ ...
  • 使用<property>標簽的value屬性配置原始數據類型和ref屬性配置對象引用的方式來定義Bean配置文件。這兩種情況都涉及將單一值傳遞給Bean。那麼如果您想傳遞多個值,例如Java集合類型,如List、Set、Map和Properties怎麼辦?為了處理這種情況,Spring提供了四種類型 ...
  • 裝飾器 裝飾器的簡易版本 import time def index(): time.sleep(3) print('from index') def home(): print('from home') def func(): print('from func') def outer(func_n ...
  • 數據來源:House Prices - Advanced Regression Techniques 參考文獻: Comprehensive data exploration with Python 1. 導入數據 import pandas as pd import warnings warnin ...
  • SpringBoot-Learning系列之Kafka整合 本系列是一個獨立的SpringBoot學習系列,本著 What Why How 的思想去整合Java開發領域各種組件。 消息系統 主要應用場景 流量消峰(秒殺 搶購)、應用解耦(核心業務與非核心業務之間的解耦) 非同步處理、順序處理 實時數據 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...