四时宝库

程序员的知识宝库

微信公众号开发(客户端和node服务端)

申请测试号

测试号配置:

https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

//config.js 
module.exports = {
    appid:'asfasfasfas',
    appsecret:'asfasfasfasfasg',
    token:'asfasgag'
}
这是乱写的;到时用你自己的就可以了

服务端API调用:

第一步:获取access tokens
    获取access tokens   access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
        1、建议公众号开发者使用中控服务器统一获取和刷新access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务;
        2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器可对外继续输出的老access_token,此时公众平台后台会保证在5分钟内,新老access_token都可用,这保证了第三方业务的平滑过渡;
        3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。

const tokenCache = {
    access_token:'',
    updateTime:Date.now(),
    expires_in:7200
}

// 服务端获取access token 储存在redis 或者 数据库  两小时失效 应该有中控服务器同意刷新
router.get('/getTokens',async ctx => {
    const wxDomain =  `https://api.weixin.qq.com`
    const path = `/cgi-bin/token`
    const param = `?grant_type=client_credential&appid=${conf.appid}&secret=${conf.appsecret}`
    const url = wxDomain + path + param
    const res = await axios.get(url)
    Object.assign(tokenCache,res.data,{
        updateTime:Date.now()
    })
    ctx.body = res.data
})


第二部:服务端入参access token的各种操作

具体的接口见 官网: https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
获取用户列表
router.get('/getFollowers',async ctx => {
    const url = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${tokenCache.access_token}`
    const res = await axios.get(url)
    console.log('getFollowers:',res)
    ctx.body = res.data
})

co-wechat-api(简化开发流程)

const { ServerToken,ClientToken } = require('./mongoose')

const WechatAPI = require('co-wechat-api')
const api = new WechatAPI(
    conf.appid,
    conf.appsecret,
    // 取Token
    async () => await ServerToken.findOne(),
    // 存Token
    async token => await ServerToken.updateOne({}, token, { upsert: true })
)
router.get('/getFollowers', async ctx => {
    let res = await api.getFollowers()
    res = await api.batchGetUsers(res.data.openid, 'zh_CN')
    ctx.body = res
})

客服消息 - (你问我答)

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html 见官网


co-wechat-oauth(简化开发流程

const wechat = require('co-wechat')
router.all('/wechat', wechat(conf).middleware(
    async message => {
        console.log('wechat:', message)
        return 'Hello World ' + message.Content
    }
))

原理解析(源码)

外网:http 80端口 https 443端口

const Koa = require('koa')
const Router = require('koa-router')
const static = require('koa-static')
const xml2js = require('xml2js')
const app = new Koa()
const url = require('url')
const conf = require('./conf')

const crypto = require('crypto')
const xmlParser = require('koa-xml-body')
app.use(xmlParser())

const router = new Router()
app.use(static(__dirname + '/'))


// 验证 (我们认证微信,返回  echostr)
router.get('/wechat', ctx => {
    console.log('微信认证...', ctx.url)
    const {
        query
    } = url.parse(ctx.url, true)
    const {
        signature, // 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
        timestamp, // 时间戳
        nonce, // 随机数
        echostr // 随机字符串
    } = query

    console.log('wechat', query)
    // 将 token timestamp nonce 三个参数进行字典序排序并用sha1加密

    let str = [conf.token, timestamp, nonce].sort().join('');
    console.log('str', str)
    let strSha1 = crypto.createHash('sha1').update(str).digest('hex');

    console.log(`自己加密后的字符串为:${strSha1}`);
    console.log(`微信传入的加密字符串为:${signature}`);
    console.log(`两者比较结果为:${signature == strSha1}`);

    // 签名对比,相同则按照微信要求返回echostr参数值
    if (signature == strSha1) {
        ctx.body = echostr
    } else {
        ctx.body = "你不是微信"
    }
})



// 接受信息(用户的输入信息)
router.post('/wechat', ctx => {
    const {
        xml: msg
    } = ctx.request.body
    console.log('Receive:', msg)
    const builder = new xml2js.Builder()
    const result = builder.buildObject({
        xml: {
            ToUserName: msg.FromUserName,
            FromUserName: msg.ToUserName,
            CreateTime: Date.now(),
            MsgType: msg.MsgType,
            Content: 'Hello ' + msg.Content
        }
    })
    ctx.body = result
})

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

客户端使用

Oauth2.0 的认证(网页授权)

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后 台服务器,与"服务提供商"的认证服务器进行互动。




https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 网页授权的官方文档(具体步骤)

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html jssdk 的使用

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

关于网页授权回调域名的说明

1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请添加 http:// 等协议头;

2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com 无法进行OAuth2.0鉴权

3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可

关于网页授权的两种scope的区别说明

1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)

2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。

关于网页授权access_token和普通access_token的区别

1、微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息;

2、其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。

关于UnionID机制

1、请注意,网页授权获取用户基本信息也遵循UnionID机制。即如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。

2、UnionID机制的作用说明:如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为同一用户,对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号),unionid是相同的。

关于特殊场景下的静默授权

1、上面已经提到,对于以snsapi_base为scope的网页授权,就静默授权的,用户无感知;

2、对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。

具体而言,网页授权流程分为四步:

1、引导用户进入授权页面同意授权,获取code

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

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

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

JSSDK使用步骤

步骤一:绑定域名

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。

备注:登录后可在“开发者中心”查看对应的接口权限。

步骤二:引入JS文件

在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.6.0.js

如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js/jweixin-1.6.0.js (支持https)。

备注:支持使用 AMD/CMD 标准模块加载方法加载

步骤三:通过config接口注入权限验证配置

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

wx.config({

  debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。

  appId: '', // 必填,公众号的唯一标识

  timestamp: , // 必填,生成签名的时间戳

  nonceStr: '', // 必填,生成签名的随机串

  signature: '',// 必填,签名

  jsApiList: [] // 必填,需要使用的JS接口列表

})

签名算法见文末的附录1,所有JS接口列表见文末的附录2

步骤四:通过ready接口处理成功验证

wx.ready(function(){

  // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。});

步骤五:通过error接口处理失败验证

wx.error(function(res){

  // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。});

具体步骤可以见 官网 流程很详细

co-wechat-oauth(简化开发流程)

  <div id="app">
        <cube-button @click='getTokens'>getTokens</cube-button>
        <cube-button @click='getFollowers'>getFollowers</cube-button>
        <cube-button @click='auth'>微信登录</cube-button>
        <cube-button @click='getUser'>获取用户信息</cube-button>
        <cube-button @click='getJSConfig'>获取JSSKConfig</cube-button>
    </div>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                value: 'input'
            },

            methods: {
                async getTokens() {
                    const res = await axios.get('/getTokens')
                    console.log('res:', res)
                },
                async getFollowers() {
                    const res = await axios.get('/getFollowers')
                    console.log('res', res)
                },
                async auth() {  // 这一步仅仅是为了获得用户的openid
                    window.location.href = '/wxAuthorize' 
                },
                async getUser() {  // 拿到openid 去后端请求用户信息
                    const qs = Qs.parse(window.location.search.substr(1))
                    console.log(qs)
                    const res = await axios.get('/getUser', {
                        params: {
                            openid: qs.openid
                        }
                    })
                    console.log('User', res.data)
                },
                async getJSConfig() {  // 仅仅是配置jssdk
                     //返回值
                    console.log('wx', wx)
                    const res = await axios.get('/getJSConfig', {
                        params: {
                            url: window.location.href
                        }
                    })
                    console.log('res....', res.data)
                    res.data.jsApiList = ['onMenuShareTimeline', 'onMenuShareAppMessage']
                    wx.config(res.data);
                    wx.ready(function () {
                        console.log('wx.ready......')
                    })
                    wx.getNetworkType({
                        success: function (res) {
                            // 返回网络类型2g,3g,4g,wifi
                            var networkType = res.networkType;
                            console.log('getNetworkType...', networkType)
                        }
                    })
                }
            },
//服务端
const OAuth = require('co-wechat-oauth')
const oauth = new OAuth(conf.appid, conf.appsecret,
    async function (openid) {
        return await ClientToken.getToken(openid)
    },
    async function (openid, token) {
        return await ClientToken.setToken(openid, token)
    }
)

/**
* 生成用户URL
*/
router.get('/wxAuthorize', async (ctx, next) => {
    const state = ctx.query.id
    console.log('ctx...' + ctx.href)
    let redirectUrl = ctx.href
    redirectUrl = redirectUrl.replace('wxAuthorize', 'wxCallback')
    const scope = 'snsapi_userinfo'
    //客户端访问 接口 这个包 请求微信,并且成功够访问改回调
    const url = oauth.getAuthorizeURL(redirectUrl, state, scope)
    console.log('url:' + url)
    ctx.redirect(url)  // 浏览器会相应 跳转到这个地址
})

/**
* 用户回调方法
*/
router.get('/wxCallback', async ctx => {
    const code = ctx.query.code
    console.log('wxCallback code', code)
    //拿取code 获取token  openid
    const token = await oauth.getAccessToken(code)
    const accessToken = token.data.access_token
    const openid = token.data.openid
    console.log('accessToken', accessToken)
    console.log('openid', openid)
    ctx.redirect('/?openid=' + openid)   // 浏览器会相应 跳转到这个地址 (前端获得了当前用户的openid)
})


/**
* 获取用户信息
*/
router.get('/getUser', async ctx => {
    const openid = ctx.query.openid
    const userInfo = await oauth.getUser(openid) 
    ctx.body = userInfo
})


/**
* 获取JSConfig
*/
router.get('/getJsConfig',async ctx => {
    console.log('getJSSDK...',ctx.query)
    const res = await api.getJsConfig(ctx.query)
    ctx.body = res
})

希望有所帮助启发

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接