微信公众号开发

基于有了openid才能进行后续操作的功能,可以采用发布订阅模式进行通知;如果用户拒绝授权,可以在需要授权的操作之前多次让用户进行授权,否则无法进行下一步操作,直到用户愿意授权拿到授权信息为止。

1、解决微信公众号网页授权只能设置一个回调域名的问题(获取code时,跨域了)

https://github.com/HADB/GetWeixinCode

2、ngrok内网穿透 本地调试

下载ngrok,打开终端,进入ngork目录,执行./ngrok http <端口号>
进入微信公众号测试号后台,配置授权域名,

此时需要开启一个服务器,用来接收微信服务器发送的信息,启动服务node server.js

此处用koa2框架调试,server.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Koa = require('koa2');
const sha1 = require('sha1');
const app = new Koa()

const config = {
appId: 'wxc4b8f409cda275c3',
appSecret: '9901c6818704b41e93f95c237d01cdac',
token: 'test'
}

app.use(async ctx => {
console.log('访问来了。')
console.log(ctx.query)
const signature = ctx.query.signature,
timestamp = ctx.query.timestamp,
nonce = ctx.query.nonce,
token = config.token;

const str = [timestamp, nonce, token].sort().join('');
const result = sha1(str)
console.log('str:', result)
if(signature === result) {
return ctx.body = ctx.query.echostr;
}
})

app.listen(8080)

3、redirect_uri域名与后台配置不一致

此处要配置,且不能有http://

踩坑:
scope: snsapi_userinfo 拉取用户信息。
会调起用户授权页面,用户可以选择拒绝,由于redirectUri经过编码了,这时候再点击授权时经过编码的redirectUri可能造成不一致,所以要先解码再进行编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
getCodeByWx (appid, scope = 'snsapi_base', type = 0) {
let search = location.search
let hash = location.hash
if (search) {
if (search.indexOf('code') >= 0) {
search = search.replace(/\?code.*end/, '')
}
redirectUri += search
}
if (hash) {
redirectUri += hash
}
const sharp = redirectUri.match(/#\//g)
if (sharp && sharp.length > 1) {
redirectUri = redirectUri.replace(/#\//g, '') + '#/'
}
redirectUri = encodeURIComponent(decodeURIComponent(redirectUri))
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${type}end#wechat_redirect`
window.location.replace(url)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function wxAuthorize () {
const code = getParamsFn('code')
let state = getParamsFn('state')
state = state && state.substr(0, 1)
const appId = getStorage('appId')
const openId = getStorage('openId')
if (!appId) {
handleGetAppId2Code()
.then(appId => {
// 获取到appId后,判断是否有openId
if (!openId) {
getCodeByWx(appId, 'snsapi_userinfo', 0)
}
})
} else {
if (!openId) {
if (code) {
getUserInfo(code)
} else {
getCodeByWx(appId, 'snsapi_userinfo', 0)
}
}
}

4、授权流程

auth.jpg

网页授权流程分为四步:

1、引导用户进入授权页面同意授权,获取code (前端获取,并发送给后台)

2、通过code换取网页授权access_token(与基础支持中的access_token不同)

3、如果需要,开发者可以刷新网页授权access_token,避免过期

4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

5、微信支付

需要去微信公众号后台配置支付目录,支付目录得是**://**/index.html,vue构建的单页面,hash模式需要在 # 前面用 ?将其参数化,否则无法匹配微信支付目录,ios会弹出提示,但是安卓调支付,闪一下就没了。

1
2
3
4
5
6
7
let href = location.href
let wenhaoIndex = href.indexOf('?')
let jinhaoIndex = href.indexOf('#')
if (wenhaoIndex < 0 || (wenhaoIndex > jinhaoIndex)) {
href = href.replace('#', '?#')
window.location.replace(href)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//从后台拿到签名串后开始调支付
wxPay(JSON.parse(params), (res) => {
// 支付回调
if (res && res.err_msg === 'get_brand_wcpay_request:ok') {
state.orderId = data.order_id
}
console.log('支付回调', res)
resolve(res)
})

function wxPay (params, callback) {
if (typeof WeixinJSBridge === 'undefined') {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false)
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady)
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady)
}
} else {
onBridgeReady(params, callback)
}
}

function onBridgeReady (params, callback) {
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
params,
function (res) {
// console.log('WeixinJSBridge res.err_msg', res.err_msg)
if (callback && typeof callback === 'function') {
callback(res)
}
})
}

6、微信分享

npm install weixin-js-sdk -S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import wx from 'weixin-js-sdk';
function initSdk({ appid, timestamp, noncestr, signature, jsApiList }) { // 从后端获取
sdk.config({
debug: process.env.VUE_APP_ENV !== 'production',
appId: appid,
timestamp: timestamp,
nonceStr: noncestr,
signature: signature,
jsApiList: jsApiList
});
}


import { getTicket } from '@/utils/lib/wechat/helper'; // 签名接口
import { initSdk } from '@/utils/lib/wechat/config';
import sdk from 'weixin-js-sdk';

// 接口清单
const JS_API_LIST = ['onMenuShareAppMessage', 'onMenuShareTimeline'];

// 消息分享
function onMenuShareAppMessage(config) {
const { title, desc, link, imgUrl } = config;
sdk.onMenuShareAppMessage({ title, desc, link, imgUrl });
}

// 朋友圈分享
function onMenuShareTimeline(config) {
const { title, link, imgUrl } = config;
sdk.onMenuShareTimeline({ title, link, imgUrl });
}

export function share(wechatShareConfig) {
if (!wechatShareConfig.link) return false;

// 签名验证
getTicket(wechatShareConfig.link).then(res => {
// 初始化 `jssdk`
initSdk({
appid: res.appid,
timestamp: res.timestamp,
noncestr: res.noncestr,
signature: res.signature,
jsApiList: JS_API_LIST
});

sdk.ready(() => {
// 初始化目标接口
onMenuShareAppMessage(wechatShareConfig);
onMenuShareTimeline(wechatShareConfig);
});
});
}