一、接收微信客户端发送的请求并对微信客户端响应
1.由于请求用户信息需要token,所以需要两小时申请一次
var request = require('request');
var fs = require('fs');
/**
*获得请求用的token
*/
function getToken(appID, appSecret){
return new Promise(function(resolve, reject){
var token;
//先看是否有token缓存,这里选择用文件缓存,可以用其他的持久存储作为缓存
if(fs.existsSync('token.dat')){
token = JSON.parse(fs.readFileSync('token.dat'));
}
//如果没有缓存或者过期
if(!token || token.timeout < Date.now()){
request('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='+appID+'&secret=' + appSecret, function(err, res, data){
var result = JSON.parse(data);
result.timeout = Date.now() + 7000000;
//更新token并缓存
//因为access_token的有效期是7200秒,每天可以取2000次
//所以差不多缓存7000秒左右肯定是够了
fs.writeFileSync('token.dat', JSON.stringify(result));
resolve(result);
});
}else{
resolve(token);
}
});
}
module.exports = {getToken: getToken};
2.在第一步获得了token后,需要根据用户的openid获得用户信息
var appID = require('../config').appID;
var appSecret = require('../config').appSecret;
var getToken = require('./token').getToken;
var request = require('request');
/*
由openID获取用户的信息
*/
function getUserInfo(openID){
console.log("openID"+openID);
return getToken(appID, appSecret).then(function(res){
var token = res.access_token;
//请求用户信息
return new Promise(function(resolve, reject){
request('https://api.weixin.qq.com/cgi-bin/user/info?access_token='+token+'&openid='+openID+'&lang=zh_CN', function(err, res, data){
resolve(JSON.parse(data));
});
});
}).catch(function(err){
console.log(err);
});
}
3. 当微信服务器发来消息后,需要验证是否是服务器发来的消息
/**
* 检查签名时间合法性
*/
function checkSignature(params, token){
//1. 将token、timestamp、nonce三个参数进行字典序排序
//2. 将三个参数字符串拼接成一个字符串进行sha1加密
//3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
var key = [token, params.timestamp, params.nonce].sort().join('');
var sha1 = require('crypto').createHash('sha1');
sha1.update(key);
return sha1.digest('hex') == params.signature;
}
module.exports=checkSignature;
4.需要把消息封装成xml格式发送个客户端,利用tmpl模板转化
/**
*msg,为响应的消息名,msg为用户发来的消息
*/
function replyText(msg, replyText){
//将要返回的消息通过一个简单的tmpl模板(npm install tmpl)返回微信
var tmpl = require('tmpl');
var replyTmpl = '<xml>' +
'<ToUserName><![CDATA[{toUser}]]></ToUserName>' +
'<FromUserName><![CDATA[{fromUser}]]></FromUserName>' +
'<CreateTime><![CDATA[{time}]]></CreateTime>' +
'<MsgType><![CDATA[{type}]]></MsgType>' +
'<Content><![CDATA[{content}]]></Content>' +
'</xml>';
return tmpl(replyTmpl, {
toUser: msg.xml.FromUserName[0],
fromUser: msg.xml.ToUserName[0],
type: 'text',
time: Date.now(),
content: replyText
});
}
module.exports = {
replyText: replyText
};
5.调用上边四步的方法,给web端用户广播消息,并给微信客户端响应消息
var http = require('http');
var qs = require('qs');
var config=require('../config');
var checkSignature=require('./check');
var io=require('../service/wxapp');
var getUserInfo = require('./userInfo').getUserInfo;
var replyText = require('./reply').replyText;
var server = http.createServer(function (request, response) {
//解析URL中的query部分,用qs模块(npm install qs)将query解析成json
var query = require('url').parse(request.url).query;
var params = qs.parse(query);
//1.
if(!checkSignature(params, config.token)){
//如果签名不对,结束请求并返回
response.end('signature fail');
return;
}
if(request.method == "GET"){
//如果请求是GET,返回echostr用于通过服务器有效校验
response.end(params.echostr);
}else{
//否则是微信给开发者服务器的POST请求
var postdata = "";
request.addListener("data",function(postchunk){
postdata += postchunk;
});
//获取到了POST数据
request.addListener("end",function(){
//将xml字符串转化为文json
var parseString = require('xml2js').parseString;
//将用户消息xml字符串转化为文json
parseString(postdata, function (err, result) {
if(err){
console.log(err);
} else {
textMessage(result,response);
}
});
});
}
});
function textMessage(result,response){
//由openID获得用户信息
getUserInfo(result.xml.FromUserName[0])
.then(function(userInfo){
//获得用户信息,合并到消息中
console.log("ddddddddddddddddddddddddd")
result.user = userInfo;
//将消息通过websocket广播
io.messages.push(result.xml.Content[0]);
io.sockets.emit("message",result.xml.Content[0]);
var res = replyText(result, '消息发送成功!');
response.end(res);
});
}
server.listen(config.wxPort);
二、web客户端请求数据,服务器响应一个页面
1.服务器响应请求, 标准 express写法
var express=require('express');
var path=require('path');
var fs=require('fs');
var request=require('request');
var config=require('./config');
var bodyParser=require('body-parser');
var cookieParser=require('cookie-parser');
var session=require('express-session');
var app=express();
app.use(express.static(path.join(__dirname,'static')));
app.use(cookieParser('rwreport'));
app.use(bodyParser.json());
app.use(session({
secret:'rwreport',
resave:true,
saveUninitialized:false,
cookie:{
maxAge:1000*60*60*12
}
}));
var server =app.listen(config.webPort,function(){
console.log('listen is on port '+config.webPort);
});
//设置默认访问路径
app.use(function(req,res){
res.sendFile(path.join(__dirname,'./static/index.html'))
});
2.响应的index界面
<!DOCTYPE html>
<html>
<head>
<base href="/">
<meta charset="UTF-8">
<title>chat</title>
<link rel="stylesheet" type="text/css" href="/bower_components/bootstrap/dist/css/bootstrap.css">
<!--奇怪此处不需要添加js -->
<script type="text/javascript" src="/bower_components/socket.io.client/dist/socket.io-1.3.5.js" ></script>
<!-- 添加bootstrap依赖 -->
<script type="text/javascript" src="/bower_components/jquery/dist/jquery.js"></script>
<script type="text/javascript" src="/bower_components/bootstrap/dist/js/bootstrap.js"></script>
<!-- 添加angularjs依赖 -->
<script type="text/javascript" src="/bower_components/angular/angular.js"></script>
<script type="text/javascript" src="/index.js"></script>
<style type="text/css">
.item{
width: 100%;
height: 40px;
line-height: 40px;
}
</style>
</head>
<body ng-app="myApp">
<div class="container" ng-controller="ShowMessage">
<input type="text" style="width: 200px;height: 40px; line-height: 40px" ng-model="message"> </input>
<a href="#" class="btn btn-primary btn-large" ng-click="submitInfo()" >提交</a>
<div class="item" ng-repeat="message in messages track by $index">
{{message}}
</div>
</div>
</body>
</html>
三、web网页和服务器进行websocket的通信
1.web网页客户端利用websocket监听和发送消息
var myApp=angular.module("myApp",[]);
myApp.factory('ioService',function($rootScope){
var socket=io.connect('www.lmem.site:9903');
console.log("connect is istablish");
return {
//监听事
on:function(eventName,callback){
//监听事件
socket.on(eventName,function(){
var args=arguments;
//调用回调函数,并且更新整个整个应用状态
$rootScope.$apply(function(){ //默认情况是不会触发事件循环的
callback.apply(socket,args);
});
})
},
emit:function(eventName,data,callback){
socket.emit(eventName,data,function(){
var args=arguments;
$rootScope.$apply(function(){
if(callback){
callback.apply(socket,args);
}
})
})
}
}
})
myApp.controller("ShowMessage",function($scope,ioService){
$scope.messages=[]
ioService.emit('messages');
ioService.on('messages',function(messages){
$scope.messages=messages;
});
ioService.on('message',function(message){
console.log(message);
$scope.messages.push(message);
});
$scope.submitInfo=function(){
if($scope.message){
ioService.emit('message',$scope.message);
}
}
});
2.服务端响应消息
**module.exports=io; 的作用是和响应微信客户端的服务端共享一个socket连接
//接收用户发送的请求
var config=require('../config')
var io=require('socket.io').listen(config.sockPort);
console.log("socket port is "+config.sockPort)
io.messages=[];
io.sockets.on('connection',function(socket){
//用户请求消息
socket.on('messages',function(){
socket.emit('messages',io.messages);
});
socket.on('message',function(message){
//用户发来消息
io.messages.push(message);
console.log(message);
//socket.broadcast.emit('message',message);//客户端跟新消息
io.sockets.emit('message',message);//客户端跟新消息
});
});
module.exports=io;
其他
1.配置参数
module.exports = {
appID: 'wxacdacbf56dfcf310',
appSecret: '5e07dbd623559dfed741996ed4b4bd77',
webPort: 9901,
wxPort: 9902,
sockPort:9903,
token:'weixinproject'
};
Paste_Image.png
注意事项
1.io.sockets.emit('event',data) 是向所有客户端发送数据
sockets.broadcast.emit('event',data) 是向除了自己的客户端发送数据
2.该程序总共开启了三个端口,9901负责响应web请求,9902响应微信客户端请求,9903负责websocket请求