koa2wechat
简单的微信公众平台接入中间件(koa2)
环境依赖
node v4.4.4+ npm v3.10.6 babel v6.14.0
实现功能
- 根据配置自动接入公众号
- 默认自动回复文本消息
- 支持自定义规则,进行自动回复(包括文本,图片,音乐,小视频,视频,图文类型)
快速开始
1.安装
npm install koa2wechat
2.引入模块(模块导出{Wechat,WeConnector,WeHandler,WeReply},下文详细介绍)
// 这两个是koa2中间件
3.引入配置文件
// 或者 import config from './config' // let weconfig = config.weconfig
配置文件示例:config.example.js
let config = weconfig: local_token_path:__dirname + '/src/token.txt' appid:"your app id" secret:"your secret" token:"your token for encrypt" const weconfig = config
4.装载中间件、启动服务
const app = app // WeConnector可以使用 用户自定义的token 接入微信平台 // 接入后,WeHandler 处理用户请求 这里的参数是根据业务逻辑编写的代码,具体看下面的介绍 app
此时公众号会默认自动回复文本消息,若要自定义规则需要将上面的null替换为根据业务逻辑写出的handler 至此,之用到了4个模块中的2个模块,实际{WeConnector,WeHandler}这两个模块是koa2中间件 剩下的{Wechat,WeReply}则是用于操作微信后台的工具类
5.根据业务逻辑自定义handler,要特别注意的是,handler一定要返回一个Promise或者它是一个Promise 例如:/src/handler/defaultHandler.js
// WeReply 是一个类,用于创建各类返回信息,生成对应的xml字符串 const welcomeMsg = 'hello from koa2wechat' // 返回一个Promise对象 let { // 获取来源FromUserName,在回复信息中将其设置为目的地 `to` // 获取我们公众号的标识ToUserName,在回复信息中将其设置为发送地 `from` let FromUserNameToUserName = xml // 这里可以插入需要执行的业务逻辑代码,比如判断消息来源是谁,他回复了什么类型的数据之类的 // 实例化weReply let weReply = // 生成回复将会使用到的meta信息,包括 本机`from`, 目标`to`, 时间戳`ts`(时间戳非必选项) let meta = from:ToUserNameto:FromUserNamets: // 回复文本类型的数据,要构造选项`type` 和 回复内容`content` 以及上面获取的 meta 信息 // 更多类型的数据在下面会提供 let textRpl = meta:meta type:"text" content:welcomeMsg // 传入回复选项,生成相应的xml字符串 let rpl = weReply // 把Promise 返回回去 return Promise }
推荐方式 随着业务逻辑的复杂度增加,可以考虑使用一个handler来分发各个事件的handler来实现响应。比如实现一个handler 例如:src/handler/handler.js 配合上文的defaultHandler
/** * 示例 handler * @param * 当接收到文本信息的时候,xml为: * { * ToUserName:"", * FromUserName:"", * CreateTime:12345678, * MsgType:"text", * Content:"this is a test", * MsgId:1234567890123456 * } * 接收到图片信息的时候为: * { * ToUserName:"", * FromUserName:"", * CreateTime:12345678, * MsgType:"image", * PicUrl:"", * MediaId:"", * MsgId:1234567890123456 * } * 其他类型同理,具体信息参考 http://mp.weixin.qq.com/wiki/17/f298879f8fb29ab98b2f2971d42552fd.html * @return */ let { let MsgType = xml if!MsgType return }
然后再将handler引入,将第4步中的null替换为handler即可 其实,到这里为止,我们一直没有用到Wechat对象,是因为,微信公众号有不同的类型 参考:官方文档 (未认证订阅号 微信认证订阅号 未认证服务号 微信认证服务号) 不同类型的号,有不同的权限,譬如对素材的管理,用户的管理,等等。 所有的操作都涉及到access_token,而Wechat类的核心就是要维护access_token, 现在的Wechat类,只简单实现了对access_token的维护, 因此有需要的开发者可以自行实现在这个基础之上,自己需要的功能,譬如素材上传,对象分组等等。
6.示例程序在这里:
结构说明:
koa2wechat
├── config.example.js(config示例)
├── lib
│ ├── api.js
│ ├── handler
│ │ ├── defaultHandler.js
│ │ └── handler.js
│ ├── index.js
│ ├── wechat
│ │ ├── apitest.js
│ │ ├── Exception.js
│ │ ├── Loader.js
│ │ ├── Reply.js
│ │ ├── WechatApi.js
│ │ ├── Wechat.js
│ │ ├── WeConnector.js
│ │ ├── WeHandler.js
│ │ └── WeReply.js
│ └── xml
│ ├── formatter.js
│ ├── parser.js
│ ├── templates
│ │ ├── imageTpl.js
│ │ ├── musicTpl.js
│ │ ├── newsItemTpl.js
│ │ ├── newsTpl.js
│ │ ├── textTpl.js
│ │ ├── videoTpl.js
│ │ └── voiceTpl.js
│ └── templates.js
├── package.json
├── readme.md
├── server.js(模拟server)
├── src
│ ├── api.js(api接口,暂时只添加了素材接口)
│ ├── handler
│ │ ├── defaultHandler.js
│ │ └── handler.js
│ ├── index.js(主文件)
│ ├── wechat
│ │ ├── Exception.js(异常类)
│ │ ├── token.txt(存储access_token)
│ │ ├── WechatApi.js(类,暂时只实现了素材管理)
│ │ ├── Wechat.js(基类,只负责access_token的维护)
│ │ ├── WeConnector.js(中间件,只负责接入微信server)
│ │ ├── WeHandler.js(中间件,负责业务逻辑)
│ │ └── WeReply.js(类,用于创建各类回复信息)
│ └── xml
│ ├── formatter.js(扁平化object)
│ ├── parser.js(解析xml至object)
│ ├── templates(回复模板)
│ │ ├── imageTpl.js
│ │ ├── musicTpl.js
│ │ ├── newsItemTpl.js
│ │ ├── newsTpl.js
│ │ ├── textTpl.js
│ │ ├── videoTpl.js
│ │ └── voiceTpl.js
│ └── templates.js(模板入口)
└── test(测试/未完成)
├── mocha.opts
├── ReplyBuilder.js
└── templates.js
更新
2016-09-20-------------------增加新类WechatApi(实现素材上传下载接口) 导出接口:
/** * [uploadTemp 用于上传临时素材] * @param {[string]} type [媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)] * @param {[string]} mediaPath [文件全路径,例如‘../../test.jpg’] * @return {[Promise]} [返回一个网络请求Promise] */ /** * [downloadTemp 下载临时素材] * @param {[string]} mediaId [参数] * @param {[string]} directory [存储素材目录] * @return {[promise]} [成功将resolve已下载的素材名] */ /** * [uploadNews 上传永久素材--图文] * @param {[object]} articles 详细字段参考:http://mp.weixin.qq.com/wiki/10/10ea5a44870f53d79449290dfd43d006.html * 例如 { * "title": TITLE, * "thumb_media_id": THUMB_MEDIA_ID, * "author": AUTHOR, * "digest": DIGEST, * "show_cover_pic": SHOW_COVER_PIC(0 / 1), * "content": CONTENT, * "content_source_url": CONTENT_SOURCE_URL * } * @return {[Promise]} [axios Post] */ /** * [uploadPerm 上传除了图文类型的其他永久素材] * @param {[string]} type [媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)] * @param {[string]} mediaPath [目标文件路径] * @param {[object]} videoDesc [只有上传类型为‘video’才需要,例如: * { * "title":VIDEO_TITLE, * "introduction":INTRODUCTION * } * @return {[Promise]} [axios POST] */ /** * [downloadPerm 下载永久素材] * @param {[string]} type [如果要获取图文,则填写(news),获取视频填写(video),获取图片(image)、语音(voice)和缩略图(thumb)] * @param {[string]} mediaId [media_id] * @param {[存储在本地的目录]} directory [图文直接返回字符串,不会写入本地] * @return {[Promise]} [获取图文最终返回json,其他多媒体类型最终返回文件名] */ /** * [removePerm 删除永久素材] * @param {[string]} mediaId [media_id] * @return {[Promise]} [最终返回json{status:true,item:form},status代表是否删除成功。item为post的内容,如果不成功可以缓存下来] */ /** * [updateNews 更新永久图文素材] 具体参数意义请参考:http://mp.weixin.qq.com/wiki/10/c7bad9a463db20ff8ccefeedeef51f9e.html * @param {[string]} mediaId [media_id] * @param {[int]} index [index] * @param {[object]} article [article] * @return {[type]} [最终返回json{status:true,item:form},status代表是否删除成功。item为post的内容,如果不成功可以缓存下来] */ /** * [getPermCount 获取永久素材类型和个数] * @return {[Promise]} 最终返回json,形如 * { * "voice_count":COUNT, * "video_count":COUNT, * "image_count":COUNT, * "news_count":COUNT * } */ /** * [batchGetPerm 批量获取某类型的永久素材信息] * @param {[string]} type [素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news)] * @param {[int]} offset [从全部素材的该偏移位置开始返回,0表示从第一个素材 返回] * @param {[int]} count [返回素材的数量,取值在1到20之间] * @return {[Promise]} [最终返回参数请参考:http://mp.weixin.qq.com/wiki/15/8386c11b7bc4cdd1499c572bfe2e95b3.html] */
使用方法:
// weconfig 和 新建Wechat对象的weconfig格式相同let wechatapi = weconfig // 上传临时素材-thumbwechatapi // 上传临时素材 voice// server error 猜想并不支持amr格式wechatapi // 上传临时素材 videowechatapi // 下载临时素材// 视频只支持http协议wechatapi // 上传永久素材-news 注意 articles 长度<=8let thumb_media_id = 'LnFqDNEdJXP8Mt8lfcrKcipoGRdbxawd5iF-4CoBjRk'let articles = "title": 'logo' "thumb_media_id": thumb_media_id "author": "徐涌盛" "digest": "digest" "show_cover_pic": 1 "content": "这里是内容1" "content_source_url": "https://github.com/chux0519" "title": 'logo2' "thumb_media_id": thumb_media_id "author": "徐涌盛" "digest": "digest2" "show_cover_pic": 1 "content": "这里是内容2" "content_source_url": "https://github.com/chux0519" wechatapi // 上传永久素材-thumbwechatapi // 上传永久素材-imagewechatapi // 上传永久素材-videowechatapi // 上传永久素材-voicewechatapi // 下载永久素材-articleslet mediaId = 'LnFqDNEdJXP8Mt8lfcrKcsQ7QsxUDgVa_23horR0Hi0'wechatapi // 下载永久素材-videolet mediaId = 'LnFqDNEdJXP8Mt8lfcrKckzvFfJcIuHQNWv039vuqZA'wechatapi // 下载永久素材-image// 测试号测试时有问题,文件会莫名其妙大几百b// curl和axios下载的结果相同,初步认为是微信服务器/或者是测试号的原因let mediaId = 'LnFqDNEdJXP8Mt8lfcrKctnwaBVhh1Zq7VoRaBEOZN8' wechatapi // 删除永久素材wechatapi // 更改永久图文消息 let mediaId = 'LnFqDNEdJXP8Mt8lfcrKcsQ7QsxUDgVa_23horR0Hi0'// 不能改变已经放上去图文素材的长度 index<lengthlet index = 0let article = "title": "这里是更新后的标题" "thumb_media_id": 'LnFqDNEdJXP8Mt8lfcrKcipoGRdbxawd5iF-4CoBjRk' "author": "徐涌盛" "digest": "" "show_cover_pic": 1 "content": "这里是内容" "content_source_url": "https://github.com/chux0519" wechatapi // 获取永久素材条数 voice_count: 0 video_count: 4 image_count: 5 news_count: 1 wechatapi // 批量获取永久素材wechatapi