基于nodejs的微信+web聊天

一、接收微信客戶端發(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請求

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評論 19 139
  • API定義規(guī)范 本規(guī)范設(shè)計基于如下使用場景: 請求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請求頻率非常高,建議使用雙通...
    有涯逐無涯閱讀 2,922評論 0 6
  • 點擊查看原文 Web SDK 開發(fā)手冊 SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個完善的 IM 系統(tǒng)...
    layjoy閱讀 14,301評論 0 15
  • 湖面微朦霧 晨光稀松影 稚子竹臺坐 愿者上鉤來
    回亦閱讀 295評論 0 1
  • 一直沒有追劇的習(xí)慣,卻因為《我的前半生》開啟了追劇生活,到點準(zhǔn)時看劇,跟著跌宕起伏劇情而大喜大悲是常有的事,為什么...
    暖暖_bd79閱讀 444評論 0 0

友情鏈接更多精彩內(nèi)容