koa2微信公众号开发及JS-SDK的使用

微信公众号项目介绍

当前只对实现思路进行说明,不作基础介绍!详细信息查看微信官方文档git地址

实现功能

  1. 新关注自动回复;

  2. 被动回复;

  3. 推送消息;

  4. 自定义菜单;

  5. js-sdk使用;

  6. 网页授权获取用户信息

koa2环境搭建

  1. koa-generator的安装

    cnpm install -g koa-generator
    
  2. koa2项目建立

    koa2 -e wechat
    

使用飞鸽内网穿透

飞鸽

使用比较简单就不作详细介绍了!

接入微信公众平台开发

编写reply中间件;新建reply文件夹,进入文件夹新建index文件!

安装所需依赖:

cnpm i -S sha1

在app.js入口文件中路由配置前引入中间件!

...
const reply = require("./reply")

...

//接收处理所有消息
app.use(reply());

// routes
app.use(index.routes(), index.allowedMethods())

...

验证消息的确来自微信服务器

微信服务器会发送两种类型的消息给开发者服务器.

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。

  1. GET请求

    -验证服务器的有效性

    1)将token、timestamp、nonce三个参数进行字典序排序

    2)将三个参数字符串拼接成一个字符串进行sha1加密

    3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

  2. POST请求

    -微信服务器会将用户发送的数据以post请求的方式转发到开发者服务器上

const config = require("../config/config")
const sha1 = require("sha1");

module.exports = () => {
    return async(ctx, next) => {
        const { signature, timestamp, nonce, echostr } = ctx.query;
        const token = config.wechat.token;

        let str = [token, timestamp, nonce].sort().join('');
        const sha = sha1(str);

        if (ctx.method === "GET" && sha === signature) {
            //如果一样说明消息来自于微信服务器,返回echostr给微信服务器
            ctx.body = echostr;

        } else if (ctx.method === "POST" && sha === signature) {

        } else {
            await next();
            // ctx.body = "Failed"
        }
    }
}

在测试号中进行配置,若无误会提示配置成功!

360截图167401157264113_看图王.png

继续处理post请求,并实现回复消息

安装依赖raw-body和xml2js:

cnpm i -S raw-body xml2js

处理POST请求,具体实现方法移步源码查看!


...
const getRawBody = require("raw-body");
const { parseXML, formatMessage, tpl2xml } = require("../utils/tool")
const reply = require("./reply")

...

const data = await getRawBody(ctx.req, {
    length: ctx.length,
    limit: "1mb",
    encoding: ctx.charset
})
const content = await parseXML(data);
// console.log(content);
const message = formatMessage(content.xml);
// console.log(message);
let replyBody = await reply(message);
// console.log(replyBody);
//生成xml数据
let xml = tpl2xml(replyBody, message);
// console.log(xml)
ctx.status = 200;
ctx.type = 'application/xml';

ctx.body = xml;

...

reply.js

/**
 * 处理用户发送的消息和内容,返回不同的内容给用户
 */
module.exports = async(message) => {
    let replyBody = "您在说什么,我听不懂!";

    if (message.MsgType === "text") {
        let content = message.Content;
        if (content === "热门") {
            replyBody = "热门";
        } else if (content === "2") {
            replyBody = "落地成盒";
        } else if (content.match("爱")) {
            replyBody = "我爱你~";
        }
    } else if (message.MsgType === "voice") {
        options.msgType = "voice";
        options.mediaId = message.MediaId;
        console.log(message.Recognition);
    } else if (message.MsgType === "event") {
        if (message.Event === "subscribe") {
            replyBody = "欢迎您的关注~\n" +
                "回复 首页 查看电影预告片页面\n" +
                "回复 热门 查看最新热门电影\n" +
                "回复 文本 查看指定电影信息\n" +
                "回复 语音 查看指定电影信息\n" +
                "也可以点击下面的菜单按钮来了解其它信息~";
        } else if (message.Event === "unsubscribe") {
            console.log("用户取消关注了!")
        } else if (message.Event === "CLICK") {
            replyBody = "您可以按照以下提示来进行操作~\n" +
                "回复 首页 查看电影预告片页面\n" +
                "回复 热门 查看最新热门电影\n" +
                "回复 文本 查看指定电影信息\n" +
                "回复 语音 查看指定电影信息\n" +
                "也可以点击下面的菜单按钮来了解其它信息~";
        }
    }

    return replyBody;
}

自定义菜单及微信JS-SDK分享接口实例

根目录新建wechat文件夹,进入文件夹创建menu.js和wechat.js文件。

wechat.js封装了access_token、jsapi_ticket、创建和删除菜单!

自定义菜单

/*routes/index.js*/

...
// 创建实例对象
const Wechat = require("../wechat/wechat")
const wechatApi = new Wechat();

//menu.js文件重新配置菜单
router.get('/updateMenu', async(ctx, next) => {
    let result = await wechatApi.createMenu(menu);
    ctx.body = result
})

...

JSSDK使用步骤

  • 步骤一:绑定域名

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

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

  • 步骤三:通过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接口列表
    });
    
  • 步骤四:通过ready接口处理成功验证

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

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

创建jssdk路由权限签名

/*routes/index.js*/

...
const { appID } = require("../config/config").wechat;
...
//用于JS-SDK使用权限签名算法
router.get('/jssdk', async(ctx, next) => {
    /* JS-SDK使用权限(签名算法)
          签名生成规则如下:参与签名的字段包括noncestr(随机字符串),
          有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。
    */
    //获取传入的url
    let url = ctx.query.url;
    const { ticket } = await wechatApi.fetchTicket();
    const nonceStr = Math.random().toString().split(".")[1];
    const timestamp = Date.now();
    const arr = [`jsapi_ticket=${ticket}`, `noncestr=${nonceStr}`, `timestamp=${timestamp}`, `url=${url}`];
    const str = arr.sort().join("&");
    const signature = sha1(str);

    ctx.body = {
        appId: appID,
        signature,
        nonceStr,
        timestamp
    }
})
...

前端使用

<!-- /*public/test.html*/ -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://libs.cdnjs.net/zepto/1.2.0/zepto.min.js"></script>
    <script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
</head>

<body>
    <div>123</div>
    <script>
        $(function() {
            let shareUrl = location.href.split('#')[0];
            $.ajax({
                url: "http://caorui.max.svipss.top/jssdk",
                data: {
                    url: shareUrl
                },
                success: function(data) {
                    wx.config({
                        //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                        appId: data.appId, // 必填,公众号的唯一标识
                        timestamp: data.timestamp, // 必填,生成签名的时间戳
                        nonceStr: data.nonceStr, // 必填,生成签名的随机串
                        signature: data.signature, // 必填,签名
                        jsApiList: [ // 必填,需要使用的JS接口列表
                            "updateAppMessageShareData",
                            "updateTimelineShareData"
                        ]
                    });

                    wx.ready(function() {
                        wx.updateAppMessageShareData({
                            title: '分享测试12dsd', // 分享标题
                            desc: '分享描述cgngn', // 分享描述
                            link: shareUrl, // 分享链接
                            imgUrl: '分享图标', // 分享图标
                            success: function() {
                                // 用户确认分享后执行的回调函数
                            }
                        });
                        wx.updateTimelineShareData({
                            title: '分享测试12fsf', // 分享标题
                            link: shareUrl, // 分享链接
                            imgUrl: '分享图标', // 分享图标
                            success: function() {
                                // 用户确认分享后执行的回调函数
                            }
                        });
                        wx.error(function(res) {
                            alert(res.errMsg); // 正式环境记得关闭啊!!!!
                        });
                    })
                }
            })
        })
    </script>
</body>

</html>

网页授权获取用户信息

微信官方文档

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

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

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

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

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

在routes/index.js添加路由

//微信网页授权获取code
router.get("/oauth", async(ctx, next) => {
    let redirect_uri = `http%3a%2f%2fcaorui.max.svipss.top/oauth.html`;
    ctx.redirect(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE&connect_redirect=1#wechat_redirect`)
})

//获取授权后的用户信息
router.get("/getUserInfo", async(ctx, next) => {
    //获取code值
    let code = ctx.query.code;
    if (!code) {
        ctx.redirect('http://caorui.max.svipss.top/oauth')
    }
    let result = await wechatApi.getOauthAccessToken(code);
    let data = await wechatApi.getOauthUserinfo(result.access_token, result.openid);

    ctx.body = data;
})

通过code获取AccessToken 和 获取授权后的用户资料 的方法查看wechat.js文件!

在public/oauth.html中使用:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://libs.cdnjs.net/zepto/1.2.0/zepto.min.js"></script>
    <script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
</head>

<body>
    <div>
        <button id="oauth">获取用户信息</button>
        <div id="userInfo"></div>
    </div>
    <script>
        $(function() {
            //获取url参数
            function getQueryVariable(variable) {
                var query = window.location.search.substring(1);
                var vars = query.split("&");
                for (var i = 0; i < vars.length; i++) {
                    var pair = vars[i].split("=");
                    if (pair[0] == variable) {
                        return pair[1];
                    }
                }
                return (false);
            }

            var userinfo = JSON.parse(localStorage.getItem("userinfo"));

            // console.log(userinfo)

            if (userinfo) {
                $("#oauth").hide();
                var html = `
                            <image src="${userinfo.headimgurl}"/>
                            <h2>你已经登录</h2>
                            `;
                $("#userInfo").html(html);
                return;
            }

            $("#oauth").on("click", function() {
                location.href = "http://caorui.max.svipss.top/oauth";
            })

            var code = getQueryVariable("code");
            if (code) {
                $.ajax({
                    url: "http://caorui.max.svipss.top/getUserInfo",
                    data: {
                        code
                    },
                    success: function(data) {
                        $("#oauth").hide();
                        // console.log(data)
                        localStorage.setItem("userinfo", JSON.stringify(data));
                        var html = `
                                <image src="${data.headimgurl}"/>
                                <p>nickname:${data.nickname}</p>
                                <p>country:${data.country}</p>
                                <p>province:${data.province}</p>
                                <p>city:${data.city}</p>
                                <p>openid:${data.openid}</p>
                            `
                        $("#userInfo").html(html)
                    }
                })
            }


        })
    </script>
</body>

</html>

结果如下:

360截图17321128447023.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269