基于nodejs的微信+web聊天

一、接收微信客户端发送的请求并对微信客户端响应

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请求

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,841评论 18 139
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,582评论 0 6
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,877评论 0 15
  • 湖面微朦雾 晨光稀松影 稚子竹台坐 愿者上钩来
    回亦阅读 262评论 0 1
  • 一直没有追剧的习惯,却因为《我的前半生》开启了追剧生活,到点准时看剧,跟着跌宕起伏剧情而大喜大悲是常有的事,为什么...
    暖暖_bd79阅读 369评论 0 0