一、接收微信客戶端發(fā)送的請求并對微信客戶端響應(yīng)
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后,需要根據(jù)用戶的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. 當(dāng)微信服務(wù)器發(fā)來消息后,需要驗證是否是服務(wù)器發(fā)來的消息
/**
* 檢查簽名時間合法性
*/
function checkSignature(params, token){
//1. 將token、timestamp、nonce三個參數(shù)進行字典序排序
//2. 將三個參數(shù)字符串拼接成一個字符串進行sha1加密
//3. 開發(fā)者獲得加密后的字符串可與signature對比,標(biāo)識該請求來源于微信
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格式發(fā)送個客戶端,利用tmpl模板轉(zhuǎn)化
/**
*msg,為響應(yīng)的消息名,msg為用戶發(fā)來的消息
*/
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.調(diào)用上邊四步的方法,給web端用戶廣播消息,并給微信客戶端響應(yīng)消息
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)){
//如果簽名不對,結(jié)束請求并返回
response.end('signature fail');
return;
}
if(request.method == "GET"){
//如果請求是GET,返回echostr用于通過服務(wù)器有效校驗
response.end(params.echostr);
}else{
//否則是微信給開發(fā)者服務(wù)器的POST請求
var postdata = "";
request.addListener("data",function(postchunk){
postdata += postchunk;
});
//獲取到了POST數(shù)據(jù)
request.addListener("end",function(){
//將xml字符串轉(zhuǎn)化為文json
var parseString = require('xml2js').parseString;
//將用戶消息xml字符串轉(zhuǎn)化為文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, '消息發(fā)送成功!');
response.end(res);
});
}
server.listen(config.wxPort);
二、web客戶端請求數(shù)據(jù),服務(wù)器響應(yīng)一個頁面
1.服務(wù)器響應(yīng)請求, 標(biāo)準(zhǔn) 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);
});
//設(shè)置默認訪問路徑
app.use(function(req,res){
res.sendFile(path.join(__dirname,'./static/index.html'))
});
2.響應(yīng)的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網(wǎng)頁和服務(wù)器進行websocket的通信
1.web網(wǎng)頁客戶端利用websocket監(jiān)聽和發(fā)送消息
var myApp=angular.module("myApp",[]);
myApp.factory('ioService',function($rootScope){
var socket=io.connect('www.lmem.site:9903');
console.log("connect is istablish");
return {
//監(jiān)聽事
on:function(eventName,callback){
//監(jiān)聽事件
socket.on(eventName,function(){
var args=arguments;
//調(diào)用回調(diào)函數(shù),并且更新整個整個應(yīng)用狀態(tài)
$rootScope.$apply(function(){ //默認情況是不會觸發(fā)事件循環(huán)的
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.服務(wù)端響應(yīng)消息
**module.exports=io; 的作用是和響應(yīng)微信客戶端的服務(wù)端共享一個socket連接
//接收用戶發(fā)送的請求
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){
//用戶發(fā)來消息
io.messages.push(message);
console.log(message);
//socket.broadcast.emit('message',message);//客戶端跟新消息
io.sockets.emit('message',message);//客戶端跟新消息
});
});
module.exports=io;
其他
1.配置參數(shù)
module.exports = {
appID: 'wxacdacbf56dfcf310',
appSecret: '5e07dbd623559dfed741996ed4b4bd77',
webPort: 9901,
wxPort: 9902,
sockPort:9903,
token:'weixinproject'
};

Paste_Image.png
注意事項
1.io.sockets.emit('event',data) 是向所有客戶端發(fā)送數(shù)據(jù)
sockets.broadcast.emit('event',data) 是向除了自己的客戶端發(fā)送數(shù)據(jù)
2.該程序總共開啟了三個端口,9901負責(zé)響應(yīng)web請求,9902響應(yīng)微信客戶端請求,9903負責(zé)websocket請求