跳到主要内容

微信公众号前后端开发流程梳理

· 9 分钟阅读

公众号后台及服务端校验配置

开发前准备

第一步:填写服务器配置

在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。

image.png

注意IP白名单配置,微信服务端开发要把服务器的IP配到此名单中,大多数微信开放接口需要依赖微信返回的access_token(这个跟js接口安全域名不一样)

第二步:验证微信服务器消息

imagebef69.png
微信服务器会对这个接口发送请求,会携带一个echostr的字段,如果服务端的校验接口 /wx/verify 返回这个串的值微信平台就会校验通过,配置成功。

以下是服务端的校验规则及js代码:

const TOKEN = 'justwe7'
const checkSignature = (params, token) => {
// 1. 将token (自己设置的代码中与微信后台都需要指定) 、timestamp(时间戳)、nonce(随机数)三个参数进行字典排序
const ArrData = [token, params.timestamp, params.nonce].sort().join('');
// 2. 将上面三个字符串拼接成一个字符串再进行sha1加密
var sha1 = require('crypto').createHash('sha1');
sha1.update(ArrData);
// 3. 将加密后的字符串与signature进行对比,若成功,返回echostr
return sha1.digest('hex') == params.signature;
}
/* 校验接口 */
exports.verify = async (ctx) => {
/* 微信发出的请求大概是这种格式:/wx/verify?signature=033cb075f32849acf5aa20c72c210546fc68f93d&echostr=7268271941968435903&timestamp=1608882842&nonce=668904465 */
const txParams = URI.parseQuery(new URI(ctx.request.url).query())
/* 假如是只是想体验。。。直接返回微信所携带的echostr即可,如果是真实的项目 还是要严格校验一下的 */
if (checkSignature(txParams, TOKEN)) {
ctx.body = txParams.echostr
} else {
ctx.body = '翻滚吧,牛宝宝!'
}
}

第三步:依据接口文档实现业务逻辑

第二步完成后就可以开发微信公众号的相关代码了。一定要先做好前置准备再去写demo,否则各种调不通

测试号的相关配置

在公众号后台,【开发】- 【开发者工具】-【公众平台测试账号】可以申请微信测试号(因公众号很多接口权限是不允许个人主体调用的,比如分享卡片定制),测试号更可以满足学习的场景

客户端请求的安全域名配置

【公众号设置】-【功能设置】-【JS接口安全域名】中添加生产环境页面的域名,否则会报错: invalid ip 1.03.15.140 ipv6 ::ffff:1.23.18.140, not in whitelist rid: 6131fb92-369bb6a4-667f2b81

测试号配置安全域名为本地回源IP就好:127.0.0.1:3000å

微信开放接口注册及使用

功能开发

(一)服务器代码开发(wx.config参数获取)

需要将“JS接口安全域名”(IP白名单)中将本地IP(服务器公网)配置好,然后进行服务端代码开发

文章中wx.api方法有过封装,所有的接口请求都会以https://api.weixin.qq.com作为baseUrl,且会将微信接口返回的响应体JSON.parse处理。

  1. 拿公众号appIdsecret获取 access_token

    access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。官方文档

    // appId secret从公众号后台得到
    const { access_token } = wx.api(`/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${secret}`)
  2. access_token 获取 jsapi_ticket

    const { ticket: jsapi_ticket } = wx.api('/cgi-bin/ticket/getticket?access_token=' + access_token + '&type=jsapi')
  3. 拿到 jsapi_ticket 生成出前端所需要的 wx.config 参数

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

    exports.wxconfig = async (ctx) => {
    // 此处省略1.2步的代码
    const nonceStr = uuidv4().replace(/-/g, '') // 生成随机字符串
    const timestamp = ~~(+new Date() / 1000)
    const url = ctx.request.query.url
    const signature = sha1(`jsapi_ticket=${jsapi_ticket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${url}`)

    const result = { code: 200, message: '', data: { appId, timestamp, // 必填,生成签名的时间戳 nonceStr, // 必填,生成签名的随机串 signature // 必填,签名 } } ctx.body = result }


(二)客户端代码开发(wx.config注入)

  1. 首先引入微信jssdk文件(支持npm引入): <script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>

  2. 引入sdk后会在window下挂载一个wx全局对象

  3. 通过config接口注入权限验证配置,实例化客户端的微信jssdk。
    以下为调用微信的提供的分享卡片定制的接口demo:

    // 调用服务端生成的权限配置
    $.ajax({
    type: 'get',
    url: '/wx/wxconfig?url=' + location.href, // 注意location.href 不包含当前链接中 # 及其后面部分
    success: function (response) {
    initWx(response.data)
    }
    });

    function initWx (params) {
    wx.config({
    debug: !true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: params.appId, // 必填,公众号的唯一标识
    timestamp: params.timestamp, // 必填,生成签名的时间戳
    nonceStr: params.nonceStr, // 必填,生成签名的随机串
    signature: params.signature,// 必填,签名
    openTagList:['wx-open-launch-weapp'], // 填入打开小程序的开放标签名
    jsApiList: [
    'updateAppMessageShareData',
    ... // 把需要用到的接口填入
    ]
    });
    wx.ready(function (res) {
    wx.updateAppMessageShareData({
    title: '今晚打产品', // 分享标题
    desc: '计划通', // 分享描述
    link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
    imgUrl: '', // 分享图标
    success: function () {
    // 设置成功
    }
    })
    });
    wx.error(function (err) {
    console.log('失败', err)
    });
    }

微信网页授权

(官方文档)[https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html]如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑 redirect_uri 需要在微信管理后台配置才可以正常使用: 用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。

  • 正式公众号:开发 -> 接口权限 -> 网页服务 -> 网页授权 -> 网页授权获取用户基本信息 -> 修改。新增目标的域名(仅支持域名)
  • 测试号:网页服务 -> 网页授权 -> 网页授权获取用户基本信息 -> 修改。新增目标的域名(支持IP)

此功能需要有公网的IP或者域名才能进行模拟

  1. 引导用户进入授权页面同意授权,获取code。用户同意授权后会携带 codestate 自动跳转至传入的 redirect_uri
    // 此接口为同步接口 访问 http://127.0.0.1:3000/wx/auth
    // 下方demo代码会执行跳转到/wx/redirect (假设定义的url是http://45.xx.xx.xxx/wx.html, 最终重定向结果为: http://45.xx.xx.xxx/wx.html?code=0714i60009q2mM1Pih000pBwFJ34i60-&state=666)
    exports.oauth = async ctx => {
    const url = encodeURIComponent('http://' + ctx.request.header.host + '/wx/redirect')
    const state = 666
    // const url = encodeURIComponent('http://45.xx.xx.xxx/wx.html') // 本地开发可以将url写死为远端的正确业务域名(然后query中会被拼装参数 wx.html?code=011AAZFa16leHB0bgaJa11h8ze2AAZFx&state=666 拿code去请求 /wx/redirect 接口
    // const scope = 'snsapi_base' // (不弹出授权页面,直接跳转,只能获取用户openid
    const scope = 'snsapi_userinfo' // (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息 )
    ctx.redirect(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${url}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`)
    }
    image982ce.png
  1. 通过code换取网页授权access_token(与基础支持中的access_token不同)

    exports.redirect = async ctx => {
    const code = URI.parseQuery(new URI(ctx.request.url).query()).code
    // 1. 注意这个 access_token,跟jssdk那个demo中的是不同的
    const userInfo = await wx.api(`/sns/oauth2/access_token?appid=${appId}&secret=${secret}&code=${code}&grant_type=authorization_code`)
    .then(res => {
    /*
    "access_token":"ACCESS_TOKEN",
    "expires_in":7200,
    "refresh_token":"REFRESH_TOKEN", // *可以用来替代access_token获取 /sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
    "openid":"OPENID",
    "scope":"SCOPE"
    */
    return res
    })
    .then((res) => {
    // 2. 拉取用户信息(需第一步scope为 snsapi_userinfo)
    return wx.api(`/sns/userinfo?access_token=${res.access_token}&openid=${res.openid}&lang=zh_CN`)
    })

    if (userInfo && userInfo.openid) {
    ctx.body = userInfo // 方便查看,将用户信息作为内容返回到页面中
    } else {
    ctx.body = {
    msg: '授权失败'
    }
    }
    }

    效果: imageaa312.png

问题记录

业务域名、JS接口安全域名、网页授权域名?

1、业务域名:在业务域名内的网页,微信方就会默认为友好网页,避免网页输入框弹出安全提示,从而提高用户体验;

2、JS接口安全域名:是用来为微信JS-SDK服务的,通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的功能;

3、网页授权域名:是用来为微信公众号请求用户网页授权服务的, 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

小程序开发unionid解密值为空

在回调中调用 wx.login 登录,可能会刷新登录态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用 checkSession 进行登录态检查,避免 login 刷新登录态。

https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.checkSession.html

errmsg: 'invalid ip xx, not in whitelist rid: xx'

/cgi-bin/token 调用返回出错

登录微信公众 -> 开发 / 基本配置 -> 在右侧将上述报出的IP地址添加到"IP白名单"中即可