字节笔记本字节笔记本

2024年微信公众平台H5 JSSDK开发流程

2023-12-14

2024年微信公众平台H5 JSSDK开发流程包括前提准备、测试账号申请、FRP内网穿透配置、Nginx代理管理、前后端测试脚本编写等步骤。

前提准备

  1. 已备案域名
  2. VPS 服务器一台
  3. 已认证公众号或者使用测试帐号

微信公众平台接口测试帐号申请

微信公众平台接口测试帐号无需公众帐号、快速申请接口测试号

可以直接体验和测试公众平台所有高级接口

申请地址:

https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

FRP一键安装脚本

由于JSSDK需要使用公网测试环境, 同时又需要在本地进行开发,所以我们考虑使用FRP内网穿透来实现

使用 FRP(Fast Reverse Proxy)来通过域名访问本地端口是一种常见的内网穿透技术

FRP 可以在没有公网 IP 的情况下,通过外部服务器将互联网流量转发到您的本地服务器

具体的服务端安装一键脚本如下:

bash <(curl -fsSL https://raw.githubusercontent.com/Slotheve/Shells/main/frp.sh)

以下是配置 FRP 实现此功能的基本步骤:

  • 域名修改其 DNS 记录指向公网 IP 的服务器
  • 公网 IP 的服务器,用作 FRP 的服务器端(frps)。
  • 本地计算机,用作 FRP 的客户端(frpc)。

配置服务端

使用一键脚本安装完后, 将自动创建Frps默认的配置方件, 其路径为 /etc/frp/frp.ini

内容如下:

[common]
# PORT
bind_addr = 0.0.0.0
bind_port = 7000
kcp_bind_port = 7000
vhost_http_port = 7001
subdomain_host = songya.top
# LOG
log_file = ./frp.log
log_level = info
log_max_days = 3
disable_log_color = false
detailed_errors_to_client = true
# AUTH
authentication_method = token
authenticate_heartbeats = false
authenticate_new_work_conns = false
token = [设置的密码]
# LIMIT
max_pool_count = 5
max_ports_per_client = 0

这里 bind_port 是 FRP 本身服务的监听端口

vhost_http_port 是用于 HTTP 流量的虚拟主机端口

启动 FRP 服务

systemctl start frp

重启 FRP 服务器(frps)

systemctl restart frp

检查服务状态

可以检查服务的状态来确保它正在运行:

systemctl status frp

这些命令将停止当前运行的 FRP 服务,并立即重新启动它。如果对配置文件做了更改,这些更改将在重启后生效。

配置 FRP 客户端(frpc)

  1. 安装 FRP:在您的本地计算机上下载并解压 FRP。

  2. 配置 frpc.ini:创建一个 frpc.ini 配置文件,指定服务器地址和需要转发的本地服务。例如:

    [common]
    server_addr = x.x.229.202
    server_port = 7000
    token = [服务端设置的密码]
    
    [wechat]
    type = http
    local_ip = 127.0.0.1
    local_port = 8080
    subdomain = wechat
    
    [go-jssdk]
    type = http
    local_ip = 127.0.0.1
    local_port = 3000
    subdomain = gochat
    
    

    这里 server_addrserver_port 是指向您的 FRP 服务器,web 是一个随意命名的 section,用于定义通过 wechat.songya.top:7001 访问本地端口 8080 的转发规则。

  3. 启动 FRP 客户端

    ./frpc -c frpc.ini
    

实现80/443端口的转发

nginx-proxy-manager 是一个基于 Node.js 的应用程序,它提供了一个易于使用的界面来管理 Nginx 代理服务器的配置。

nginx-proxy-manager 让用户可以方便地通过网页界面创建、编辑或删除代理规则,而不必直接编辑 Nginx 配置文件。

主要功能

  • 图形界面:提供一个用户友好的图形界面,用于管理 Nginx 的反向代理、负载均衡等功能。
  • SSL 证书管理:可以轻松地为代理的域名配置和续订 Let's Encrypt SSL 证书。
  • 访问控制:支持基本的访问控制,如密码保护、白名单和黑名单。
  • 404 页面管理:用户可以为未找到的页面自定义 404 响应。
  • 代理规则:允许用户创建重定向、阻止某些路由等代理规则。
  • 多用户支持:可以配置多个用户账号,为每个用户分配不同的管理权限。

安装和使用

nginx-proxy-manager 通常通过 Docker 来安装。以下是使用 Docker 安装的大致步骤:

# 下载并运行 nginx-proxy-manager
docker run -d \
  --name=nginx-proxy-manager \
  -p 80:80 \
  -p 81:81 \
  -p 443:443 \
  -v /path/to/config.json:/app/config/production.json \
  -v /path/to/data:/data \
  -v /path/to/letsencrypt:/etc/letsencrypt \
  jlesage/nginx-proxy-manager

这里,80 端口和 443 端口分别用于 HTTP 和 HTTPS 通信,而 81 端口用于访问 nginx-proxy-manager 的管理界面。

安装完成后,用户可以通过访问主机上的 81 端口来配置 Nginx 代理规则。

如何我们想直接使用用 80 443 端口访问本地的应用

例如将 wx.songya.top proxy转发到 http://wechat.songya.top:7001

需要配置反向代理 NPM具体的配置路径为:

Edit Proxy Host >Advanced > Custom Nginx Configuration:

增加配置内容如下:

location / {
      proxy_pass  http://wechat.songya.top:7001;
      add_header X-Example-Header "Value";
}

完成上面的配置后我们就可以在本地启动前后端应用了

前端测试脚本

因为目前jssdk无法直接调用分享图文,需要使用生成二维码形式引导用户长按扫描识别当前地址的二维码才能正常分享

<html lang="en">
<head>
    <meta charset="utf-8">
    <title>jssdk</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
</head>

<body>
<button class="btn btn_primary" id="checkJsApi">checkJsApi</button>

<div id="qrcode" style="width: 100px; height: 100px; margin-top: 15px;"></div>


</body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/zepto.min.js"></script>
<script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>


<script>
    const url = window.location.href
    $.get(`/sign?url=${url}`, function (res) {
        wx.config({
            debug: true, // 开启调试模式
            appId: "wx660b843f17b014c5", // 必填,公众号的唯一标识
            timestamp: res.timestamp, // 必填,生成签名的时间戳
            nonceStr: res.noncestr, // 必填,生成签名的随机串
            signature: res.signature,// 必填,签名,见附录1
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
        });

        wx.ready(function() {
            const wxConfig = {
                title: "分享标题",
                desc: "分享描述",
                imgUrl: "https://www.baidu.com/img/flexible/logo/pc/result.png",
                link: url,
                success: function() {
                    // 设置成功
                    console.log("设置成功***");
                }
            }
            wx.updateAppMessageShareData(wxConfig);
            wx.updateTimelineShareData(wxConfig);
        });
    })

    // 生成当前URL二维码的功能
    new QRCode(document.getElementById("qrcode"), {
        text: window.location.href,
        width: 100,
        height: 100
    });

</script>

<script>

    wx.error(function(res){
        console.log(JSON.stringify(res))
    })
    wx.ready(function () {
        // 判断当前版本是否支持指定 JS 接口,支持批量判断
        document.querySelector('#checkJsApi').onclick = function () {
            wx.checkJsApi({
                jsApiList: [
                    "updateAppMessageShareData"
                ],
                success: function (res) {
                    alert(JSON.stringify(res));
                }
            });
        };
    })
</script>
</html>

请注意,根据官方文档原有的 wx.onMenuShareTimeline、wx.onMenuShareAppMessage、wx.onMenuShareQQ、wx.onMenuShareQZone 接口,即将废弃。请尽快迁移使用客户端6.7.2及JSSDK 1.4.0以上版本支持的 wx.updateAppMessageShareData、wx.updateTimelineShareData接口。

后端测试脚本

使用koa2作为后端脚本, 其它语言可以参照下面的编码逻辑自行进行修改

const Koa = require('koa');
const Router = require('@koa/router');
const s = require('koa-static');
const request = require('koa2-request');
const cache = require('memory-cache');
const sha1 = require('sha1');
const appid = 'wx660b843f17b014c5';
const secret = 'cd3b0b0b0b0b0b0b0b0b0b0b0b0b0b0b';
const app = new Koa();
const router = new Router();

app.use(s(__dirname + '/web')); // 提供前面的index.html静态服务

router.get('/sign', async (ctx) => {
    const url = ctx.request.query.url;
    console.log(url)
    let noncestr = "123456",
        timestamp = Math.floor(Date.now() / 1000), // 精确到秒
        jsapi_ticket;

    if (cache.get('ticket')) {
        jsapi_ticket = cache.get('ticket');
        ctx.body = {
            noncestr: noncestr,
            timestamp: timestamp,
            url: url,
            jsapi_ticket: jsapi_ticket,
            signature: sha1(`jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`)
        };
    } else {
        let response = await request(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}$&secret=${secret}`);
        if (response.statusCode === 200) {
            const tokenMap = JSON.parse(response.body);
            console.log("tokenMap 1:", tokenMap);
            response = await request(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${tokenMap.access_token}&type=jsapi`);
            if (response.statusCode === 200) {
                ticketMap = JSON.parse(response.body);
                console.log("ticketMap 2:", ticketMap);
                cache.put('ticket', ticketMap.ticket, (1000 * 60 * 60 * 24)); // 加入缓存
                ctx.body = {
                    noncestr: noncestr,
                    timestamp: timestamp,
                    url: url,
                    jsapi_ticket: ticketMap.ticket,
                    signature: sha1(`jsapi_ticket=${ticketMap.ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`)
                };
            }
        }
    }
});

app.use(router.routes()).use(router.allowedMethods());

const server = app.listen(3000, () => {
    const host = server.address().address;
    const port = server.address().port;
    console.log(`Example app listening at http://${host}:${port}`);
});