申请微信测试账号
- 修改本地 host 文件:windows 路径
C:\Windows\System32\drivers\etc
,将本地 host 改为任意域名,比如tumbleweed.top
- 登录微信公众平台, 进入
开发-开发者工具-公众平台测试账号
申请测试账号 - 在
JS接口安全域名修改
中配置自定义的本地域名tumbleweed.top
- 关注测试号二维码 (如果多人开发,都要关注这个测试账号)
安装手机代理软件 (废弃不用:直接使用内网穿透)
由于我们的域名是自定义的假域名,并不存在,所以需要将手机的网络代理到电脑上,由电脑端向微信服务器发送请求
安装 charles 并测试 (废弃不用:直接使用内网穿透)
charles 是一款收费软件,下载安装之后,在 proxy-setting
中查看 http 代理端口,默认是 8888
,将手机和电脑接入统一局域网中,打开手机 WiFi 连接,配置手动代理,输入电脑的 IP 地址和代理端口 8888
在电脑上启动本地调试服务,注意要运行在 80 端口下面,微信 sdk 只支持(http 80 或者 https 443),比如运行在 http://tumbleweed.top
,如果使用的是 vue 开发,需要在vue.config.js
中配置以下字段
devServer: {
disableHostCheck: true,
port:80,
host:'tumbleweed.top'
}
在手机上访问 http://tumbleweed.top
,如果能访问成功,则表明代理软件已经配置成功(注意 charles 会弹出确认代理连接操作弹框)
服务端代码
const request = require('request')
const axios = require('axios')
const sha1 = require('js-sha1') // 引入sha1加密算法,需要使用sha1算法生成签名
const Koa = require('koa')
var cors = require('koa2-cors')
const serve = require("koa-static");
const {appId, appSecret} = require('./wechatSetting')
const app = new Koa()
const Router = require('@koa/router')
const router = new Router()
const bodyParser = require('koa-bodyparser')
app.use(bodyParser())
app.use(serve("./static"));
app.use(cors())
app.use(router.routes()).use(router.allowedMethods())
// 测试接口
router.get('/wechat/hello', async (ctx) => {
ctx.body = 'Hello World'
})
// 微信 SDK 初始化
router.post('/wechat/sdkInit', async (ctx, next) => {
ctx.body = ctx.request.body
const url = ctx.body.url // 初始化jsdk的页面url,如果是单页应用记得截掉url的#部分
let access_token = ''
let ticket = ''
console.log(url)
try {
let step1Res = await axios.get(
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`
)
if (step1Res.status === 200) {
access_token = step1Res.data.access_token
}
let step2Res = await axios.get(
`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=jsapi`
)
if (step2Res.status === 200) {
ticket = step2Res.data.ticket
}
const createNonceStr = () => Math.random().toString(36).substr(2, 15)
const createTimeStamp = () => parseInt(new Date().getTime() / 1000) + ''
const calcSignature = (ticket, nonceStr, ts, url) => {
let str = `jsapi_ticket=${ticket}&noncestr=${nonceStr}×tamp=${ts}&url=${url}`
return sha1(str) //使用sha1加密算法
}
const nonceStr = createNonceStr() // 随机字符串
const timestamp = createTimeStamp() // 时间戳
const signature = calcSignature(ticket, nonceStr, timestamp, url) // 通过sha1算法得到签名
ctx.response.status = 200
const res = {
result: {
nonceStr: nonceStr,
timestamp: timestamp,
signature: signature,
appId,
code: 0,
},
code: 0,
message: '签名获取成功',
}
ctx.response.body = res
} catch (e) {
console.log(e)
}
})
// 微信授权登录接口测试
router.get('/wechat/sendCode', async (ctx) => {
const query = ctx.request.query
const code = query.code
try {
const {data} = await getAccessCodeFromCode(code)
const {access_token, openid} = data
const {data: userInfo} = await getUserInfoFromWechat(access_token, openid)
ctx.response.status = 200
const res = {
result: userInfo,
code: 0,
message: '用户信息获取成功',
}
ctx.response.body = res
} catch (error) {
console.error(error)
}
})
// 小程序获取openid
router.post('/wechat/getOpenid', async (ctx) => {
console.log("post /wechat/getOpenid")
const query = ctx.request.query
const code = query.code
try {
const {data} = await getAccessCodeFromCodeByMiniProgram(code)
console.log(111111, data)
const {session_key, openid} = data
ctx.response.status = 200
const res = {
result: {session_key,openid},
code: 0,
message: 'openid 获取成功',
}
ctx.response.body = res
console.log("response:", res)
} catch (error) {
console.error(error)
}
})
// 微信公众平台调用接口
function getAccessCodeFromCode(CODE) {
const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${appSecret}&code=${CODE}&grant_type=authorization_code`
return axios.get(url)
}
// 小程序登录调用接口 获取openid
function getAccessCodeFromCodeByMiniProgram(CODE) {
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${CODE}&grant_type=authorization_code`
return axios.get(url)
}
function validateToken(ACCESS_TOKEN, OPENID) {
const url = `https://api.weixin.qq.com/sns/auth?access_token=${ACCESS_TOKEN}&openid=${OPENID}`
return axios.get(url)
}
function refreshToken(REFRESH_TOKEN) {
const url = `https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=${appId}&grant_type=refresh_token&refresh_token=${REFRESH_TOKEN}`
return axios.get(url)
}
function getUserInfoFromWechat(ACCESS_TOKEN, OPENID) {
const url = `https://api.weixin.qq.com/sns/userinfo?access_token=${ACCESS_TOKEN}&openid=${OPENID}&lang=zh_CN`
return axios.get(url)
}
app.listen(5000, () => {
console.log("app is running in port 5000")
})
前端代码
TIP
所有需要使用 JS-SDK 的页面必须先注入配置信息,否则将无法调用.(同一个 url 仅需调用一次,对于变化 url 的 SPA 的 web app 可在每次 url 变化时进行调用),url(当前网页的 URL,不包含#及其后面部分)
以微信扫一扫功能为例,每个需要调用扫一扫的页面需要先初始化 SDK,调用后台生成签名接口时需要传入前页面的 url
<template>
<div @click="menuClick">扫一扫</div>
</template>
<script>
import WX from "weixin-js-sdk"
import {getWXconfig} from "@/src/api/wx.js"
export default {
data() {
return {};
},
methods: {
async initWx() {
let url = window.location.href.split("#")[0]
let res = await getWXconfig({url}) // 获取 appId timestamp nonceStr signature
this.configWX(res)
},
menuClick() {
this.initWx()
},
startScanCode() {
WX.scanQRCode({
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType: ['qrCode'], // 可以指定扫二维码还是一维码,默认二者都有
success: async function (res) {
// 扫一扫返回的结果
console.log(res)
}
})
},
configWX(data) {
let that = this
WX.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: data.appId, // 必填,公众号的唯一标识
timestamp: data.timestamp, // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature, // 必填,签名
jsApiList: [ // 必填,需要使用的JS接口列表
'checkJsApi',
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone',
'hideMenuItems',
'showMenuItems',
'hideAllNonBaseMenuItem',
'showAllNonBaseMenuItem',
'translateVoice',
'startRecord',
'stopRecord',
'onVoiceRecordEnd',
'playVoice',
'onVoicePlayEnd',
'pauseVoice',
'stopVoice',
'uploadVoice',
'downloadVoice',
'chooseImage',
'previewImage',
'uploadImage',
'downloadImage',
'getNetworkType',
'openLocation',
'getLocation',
'hideOptionMenu',
'showOptionMenu',
'closeWindow',
'scanQRCode',
'chooseWXPay',
'openProductSpecificView',
'addCard',
'chooseCard',
'openCard'
]
});
/**wx.error可以返回微信config配置是否成功*/
WX.error(function (res) {
console.log("WX----error", arguments)
});
WX.ready(function () {
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
console.log("WX----ready", arguments)
that.startScanCode()
});
},
}
};
</script>
线上部署配置
JS 接口安全域名
设置需要调用 js-sdk 的 H5 页面
微信 IP 白名单设置
通过开发者 ID 及密码调用获取 access_token 接口时,需要设置访问来源 IP 为白名单(初始化 js-sdk 的时候需要后台服务器与微信服务器进行交互,获取 access_token,需要添加后台服务器 ip 至微信公众号白名单中)
目的:为了提高公众平台开发者接口调用的安全性,避免一旦开发者 ID 和密码泄露后给帐号造成损失。对调用“获取 access_token”接口增加 IP 白名单校验:只有将 IP 地址设置为公众号的 IP 白名单,才能成功调用该接口
参考链接
# Start a tunnel,将内网 3000 端口映射到外网
ngrok http 3000
# 如果需要提供网页服务 需要设置 token
ngrok config add-authtoken <token>
# 前后端分离需要配置 nginx 将前后端都代理到 3000 端口
server {
listen 3000;
server_name localhost;
location /wechat {
proxy_pass http://localhost:5000; # 后端接口
}
location / {
proxy_pass http://localhost:4000; # 前端页面
}
}