【微信小程序】H5页面调用JSSDK中chooseImage多选图片接口

项目场景:

【微信小程序】H5页面调用JSSDK中chooseImage多选图片接口
本文章是利用 [微信公众平台的接口测试号] 介绍JSSDK接口如何配置及调用样例


准备工作

  • 需要注册一个接口测试号(也可以在正式的公众号平台上操作)
    https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
  • 获取appIDappsecret(注册完接口测试号就有)
  • 需要公网IP域名:用于配置JS接口安全域名

原理解释

微信原生解释文档:
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
ps:这篇开发文档对我这种新手真的很不友好…

大概意思就是:

1.前端

携带前端H5页面的url参数向后端请求下述若干信息,完成config配置

url:需要在微信公众平台的接口测试号中配置到JS接口安全域名
不需要写https:// 直接就是example.com
好像要求是https,不能是http,但本人没有测试,看其他文章这样说就改了
至于域名啥的就自己去搞吧

2.后端:

通过后端代码,拿着appIDappsecret向微信请求到AccessToken
然后还是用后端代码,拿着刚获取的AccessToken向微信请求到jsapi_ticket
然后还是在后端,就可以用一堆参数加密生成签名signature(提供签名校验工具)
校验网址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
前端通过接口向后端请求到appID、appsecret、signature等参数就可以了
(官方提供了后端示例代码
示例代码链接:https://www.weixinsxy.com/jssdk/sample.zip

JS-SDK说明文档——签名生成规则如下:
1、参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。
2、对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。
3、这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。

另附:接入微信公众平台开发的指南说明

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html


具体代码:

前端我在Vs Code中用Live Server插件在本地运行
后端直接写个启动类就行了
然后用某壳做个内网穿透就可以在本地临时测试了

有些细节内容,我放在注释里了,比如:7200秒的缓存,Token的含义

前端代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>测试页面</title>
    <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <h1>选择图片</h1>
    <button id="chooseImage">选择图片</button>
    <div id="result"></div>

    <script>
        $(document).ready(function() {
            const pageUrl = window.location.href;
            // 请求后端获取微信配置
            $.ajax({
                url: 'https://xxxxxxxxxx/getWXconfig',
                method: 'POST',
                data: {
                    url: pageUrl // 当前页面的 URL
                },
                success: function(data) {
                    // 初始化 JSSDK
                    wx.config({
                        debug: true, // 开启调试
                        appId: data.appId,
                        timestamp: data.timestamp,
                        nonceStr: data.noncestr,
                        signature: data.signature,
                        jsApiList: ['chooseImage'] // 需要使用的 JS API 列表
                    });

                    // 调用选择图片接口
                    wx.ready(function() {
                        $('#chooseImage').on('click', function() {
                            wx.chooseImage({
                                count: 5, // 默认9
                                sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
                                sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
                                success: function(res) {
                                    const localIds = res.localIds; // 返回选定照片的本地ID
                                    $('#result').html('<p>选择的图片ID: ' + localIds.join(', ') + '</p>');
                                },
                                fail: function(err) {
                                    console.error('选择图片失败', err);
                                }
                            });
                        });
                    });

                    wx.error(function(res) {
                        console.error('wx.config 失败', res);
                    });
                },
                error: function(xhr, status, error) {
                    console.error('请求失败', error);
                }
            });
        });
    </script>
</body>
</html>

后端(服务器端):

启动类

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

核心代码

package com.example.demo;

import org.json.JSONObject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@RestController
public class wxGetShareData {

    private static String appid = "wx0x0x0x0x0x0x0x";
    private static String secret = "abcdefghijklmn1234567890";
    private static String token = "token123"; // 自定义的Token,与页面token对应

    // 缓存 Access Token 和 Ticket
    // Access_Token (有效期7200秒,开发者必须在自己的服务全局缓存access_token)
    private static Map<String, String> tokenCache = new ConcurrentHashMap<>();
    private static Map<String, Long> tokenExpiry = new HashMap<>();
    private static final long TOKEN_EXPIRY_TIME = 7000 * 1000;


    // 获取微信accessToken的静态方法
    public static String getAccessToken() {

        String cachedToken = tokenCache.get("accessToken");
        Long expiryTime = tokenExpiry.get("accessTokenExpiry");

        // 如果缓存存在且未过期,直接返回缓存的accessToken
        if (cachedToken != null && expiryTime != null && System.currentTimeMillis() < expiryTime) {
            return cachedToken;
        }

        String requestUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid
                + "&secret=" + secret;

        try {
            // 创建 URL 对象
            URL url = new URL(requestUrl);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            con.setRequestProperty("Content-Type", "application/json");
            con.setRequestProperty("Accept", "application/json");

            // 读取响应
            int responseCode = con.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
                String inputLine;
                StringBuilder response = new StringBuilder();
                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                in.close();

                // 解析 JSON 响应
                JSONObject data = new JSONObject(response.toString());
                String accessToken = data.getString("access_token");

                // 缓存 accessToken 和过期时间
                tokenCache.put("accessToken", accessToken);
                tokenExpiry.put("accessTokenExpiry", System.currentTimeMillis() + TOKEN_EXPIRY_TIME);

                return accessToken;

            } else {
                System.out.println("GET request failed. Response Code: " + responseCode);
            }

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

        return null;
    }

    public static String getTicket() {

        String accessToken = getAccessToken();
        if (accessToken == null) {
            System.out.println("Access token is null");
            return null;
        }

        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=" + accessToken;
        try {
            URL object = new URL(url);
            HttpURLConnection con = (HttpURLConnection) object.openConnection();
            con.setRequestMethod("GET");
            con.setRequestProperty("Content-Type", "application/json");
            con.setRequestProperty("Accept", "application/json");

            StringBuilder sb = new StringBuilder();
            int httpResult = con.getResponseCode();
            if (httpResult == HttpURLConnection.HTTP_OK) {
                BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
                String line;
                while ((line = br.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                br.close();

                JSONObject data = new JSONObject(sb.toString());
                return data.getString("ticket");

            } else {
                System.out.println("GET request failed: " + con.getResponseMessage());
            }

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

        return null;
    }
// 	签名算法
    public static String signString(String jsapi_ticket, String url, String nonce_str, String timestamp) {
        Map<String, String> ret = sign(jsapi_ticket, url, nonce_str, timestamp);
        return ret.get("signature");
    }
// 	签名算法中的string1的顺序
    public static Map<String, String> sign(String jsapi_ticket, String url, String nonce_str, String timestamp) {
        Map<String, String> ret = new HashMap<>();

        String string1;
        String signature = "";

        // 注意这里参数名必须全部小写,且必须有序
        string1 = "jsapi_ticket=" + jsapi_ticket +
                "&noncestr=" + nonce_str +
                "&timestamp=" + timestamp +
                "&url=" + url;
        System.out.println("string1:"+string1);

        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private boolean checkSignature(String signature, String timestamp, String nonce) {
        String[] params = {token, timestamp, nonce};
        Arrays.sort(params); // 字典序排序
        StringBuilder sb = new StringBuilder();
        for (String param : params) {
            sb.append(param);
        }
        String temp = sha1(sb.toString());
        return temp.equals(signature);
    }
// 	签名算法中的加密
    private String sha1(String input) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            byte[] hash = digest.digest(input.getBytes("UTF-8"));
            return byteToHex(hash);
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

// 调用接口
    @RequestMapping(value = "/getWXconfig", method = {RequestMethod.POST, RequestMethod.GET})
    public void getWechatConfig(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 添加 CORS 头部
        // 这段代码是我在运行的时候报错(CORS(跨源资源共享) 错误):Access to XMLHttpRequest at 'http://xxxxxxxxxxxx/getWXconfig' from origin 'http://8578g29f45.vicp.fun' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
        response.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有来源
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); // 允许的请求方法
        response.setHeader("Access-Control-Allow-Headers", "Content-Type"); // 允许的请求头

       // 验证请求的合法性
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
//      这段代码是Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
//		测试时发现忽略掉也行
//        if (request.getMethod().equalsIgnoreCase("GET")) {
//            if (checkSignature(signature, timestamp, nonce)) {
//                response.getWriter().write(echostr); // 返回echostr,接入成功
//            } else {
//                response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid signature");
//            }
//            return;
//        }

        // POST 请求处理
        System.out.println("------------------------------");
        System.out.println(LocalDateTime.now());
        System.out.println("开始配置wxconfig");
        Map<String, Object> map = new HashMap<>();
        String jsapi_ticket = getTicket();
        if (jsapi_ticket != null) System.out.println("已获取ticket");
        System.out.println("jsapi_ticket:" + jsapi_ticket);
        timestamp = String.valueOf(new Date().getTime() / 1000); // 获取当前时间戳(秒)
        System.out.println("timestamp:" + timestamp);
        String noncestr = UUID.randomUUID().toString(); // 生成随机字符串
        System.out.println("noncestr:" + noncestr);
        String url = request.getParameter("url"); // 获取请求参数中的url
        System.out.println("url:" + url);

        if (url == null || url.isEmpty()) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "URL parameter is missing");
            return;
        }

        // 生成签名
        signature = signString(jsapi_ticket, url, noncestr, timestamp);
        System.out.println("signature:" + signature);
        // 设置返回的参数
        map.put("jsapi_ticket", jsapi_ticket);
        map.put("timestamp", timestamp);
        map.put("noncestr", noncestr);
        map.put("signature", signature);
        map.put("appId", appid); 

        // 设置响应类型
        response.setContentType("application/json");
        response.getWriter().write(new JSONObject(map).toString()); // 返回JSON格式
    }


}


参考文章

https://blog.csdn.net/qq_41108402/article/details/105971724
https://blog.csdn.net/qq_38366657/article/details/123295493
https://huaweicloud.csdn.net/64e5d7ca6ffa502025760442.html
https://blog.csdn.net/chmod_R_755/article/details/76091774 (这篇文章启发很大)
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值