#koa2wechat
简单的微信公众平台接入中间件(koa2)
#####环境依赖
node v4.4.4+
npm v3.10.6
babel v6.14.0
#####实现功能
- 根据配置自动接入公众号
- 默认自动回复文本消息
- 支持自定义规则,进行自动回复(包括文本,图片,音乐,小视频,视频,图文类型)
#####快速开始
1.安装
npm install koa2wechat
2.引入模块(模块导出{Wechat,WeConnector,WeHandler,WeReply},下文详细介绍)
import Koa from 'koa2'
import {WeConnector,WeHandler} from './src/index'
3.引入配置文件
import {weconfig} from './config'
配置文件示例: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"
}
}
export const {weconfig} = config
export default config
4.装载中间件、启动服务
const app = new Koa()
app
.use(WeConnector(weconfig.token))
.use(WeHandler(null))
app.listen(3000)
此时公众号会默认自动回复文本消息,若要自定义规则需要将上面的null替换为根据业务逻辑写出的handler
至此,之用到了4个模块中的2个模块,实际{WeConnector,WeHandler}这两个模块是koa2中间件
剩下的{Wechat,WeReply}则是用于操作微信后台的工具类
5.根据业务逻辑自定义handler,要特别注意的是,handler一定要返回一个Promise或者它是一个Promise
例如:/src/handler/defaultHandler.js
import {WeReply} from '../index'
const welcomeMsg = 'hello from koa2wechat'
let defaultHandler = (xml)=>{
let {FromUserName,ToUserName} = xml
let weReply = new WeReply()
let meta = {from:ToUserName,to:FromUserName,ts:new Date().getTime()}
let textRpl = {
meta:meta,
type:"text",
content:welcomeMsg
}
let rpl = weReply.genXML(textRpl)
return Promise.resolve(rpl)
}
export default defaultHandler
推荐方式
随着业务逻辑的复杂度增加,可以考虑使用一个handler来分发各个事件的handler来实现响应。比如实现一个handler
例如:src/handler/handler.js 配合上文的defaultHandler
import defaultHandler from './defaultHandler'
let handler = (xml)=>{
let {MsgType} = xml
if(!MsgType) return
switch(MsgType){
case 'event':
case 'text':
case 'image':
case 'voice':
case 'video':
case 'shortvideo':
case 'location':
case 'link':
default:
return defaultHandler(xml)
}
}
export {handler}
export default handler
然后再将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(type,mediaPath)
downloadTemp(mediaId,directory,isVideo=false)
uploadNews(articles)
uploadPerm(type,mediaPath,videoDesc)
downloadPerm(type,mediaId,directory)
removePerm(mediaId)
updateNews(mediaId,index,article)
getPermCount()
batchGetPerm(type,offset,count)
使用方法:
let wechatapi = new WechatApi(weconfig)
wechatapi
.uploadTemp('thumb',__dirname + '/../../test/logo.png')
.then(response=>console.log(response.data))
.catch(e=>{console.log(e)})
wechatapi
.uploadTemp('voice',__dirname + '/../../material/voice.amr')
.then(response=>console.log(response.data))
.catch(e=>{console.log(e)})
wechatapi
.uploadTemp('video',__dirname + '/../../material/gf.mp4')
.then(response=>console.log(response.data))
.catch(e=>{console.log(e)})
wechatapi
.downloadTemp(mediaId,__dirname + '/../../material',true)
.then(filename=>{
console.log(`==>${filename} downloaded!`)
})
.catch(e=>console.log(e))
let 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.uploadNews(articles)
.then(response=>{
console.log(response.data)
})
.catch(e=>console.log(e))
wechatapi
.uploadPerm('thumb',__dirname + '/../../test/logo.png')
.then(response=>console.log(response.data))
.catch(e=>{console.log(e)})
wechatapi
.uploadPerm('image',__dirname + '/../../test/logo.png')
.then(response=>console.log(response.data))
.catch(e=>{console.log(e)})
wechatapi
.uploadPerm('video',__dirname + '/../../material/gf.mp4',videoDesc)
.then(response=>console.log(response.data))
.catch(e=>{console.log(e)})
wechatapi
.uploadPerm('voice',__dirname + '/../../material/voice.amr')
.then(response=>console.log(response.data))
.catch(e=>{console.log(e)})
let mediaId = 'LnFqDNEdJXP8Mt8lfcrKcsQ7QsxUDgVa_23horR0Hi0'
wechatapi.downloadPerm('news',mediaId,'./')
.then(response=>{
if(response.data)
console.log(response.data)
else
console.log(response)
})
.catch(e=>console.log(e))
let mediaId = 'LnFqDNEdJXP8Mt8lfcrKckzvFfJcIuHQNWv039vuqZA'
wechatapi.downloadPerm('video',mediaId,__dirname + '/../../material')
.then(response=>{
if(response.data)
console.log(response.data)
else
console.log(response)
})
.catch(e=>console.log(e))
let mediaId = 'LnFqDNEdJXP8Mt8lfcrKctnwaBVhh1Zq7VoRaBEOZN8'
wechatapi.downloadPerm('thumb',mediaId,__dirname + '/../../material')
.then(response=>{
if(response.data)
console.log(response.data)
else
console.log(response)
})
.catch(e=>console.log(e))
wechatapi.removePerm("123456")
.then(msg=>console.log(msg))
.catch(e=>console.log(e))
let mediaId = 'LnFqDNEdJXP8Mt8lfcrKcsQ7QsxUDgVa_23horR0Hi0'
let index = 0
let article = {
"title": "这里是更新后的标题",
"thumb_media_id": 'LnFqDNEdJXP8Mt8lfcrKcipoGRdbxawd5iF-4CoBjRk',
"author": "徐涌盛",
"digest": "",
"show_cover_pic": 1,
"content": "这里是内容",
"content_source_url": "https://github.com/chux0519"
}
wechatapi.updateNews(mediaId,index,article)
.then(msg=>console.log(msg))
.catch(e=>console.log(e))
{ voice_count: 0, video_count: 4, image_count: 5, news_count: 1 }
wechatapi.getPermCount()
.then(response=>console.log(response.data))
.catch(e=>console.log(e))
wechatapi.batchGetPerm("image",0,5)
.then(response=>console.log(response.data))
.catch(e=>console.log(e))