├── .gitignore ├── README.md ├── app.js ├── bin └── www ├── io ├── io.js ├── ioHelper.js └── messageTpye.js ├── package.json ├── public ├── images │ ├── demo.png │ ├── icon-close.png │ └── repeat.jpg ├── javascripts │ └── notify.js └── stylesheets │ └── style.css ├── routes ├── index.js └── users.js ├── utils └── redis.js └── views ├── error.ejs └── index.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .gitignore 3 | .idea/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 基于Nodejs websocket socket.io的消息转发系统 message pusher written in nodejs based on socket.io 2 | ============== 3 | 4 | 消息实时推送,支持在线用户数实时统计。基于[Socket.IO](https://socket.io/)开发,使用websocket推送数据,当浏览器不支持websocket时自动切换comet推送数据。 5 | 6 | 支持Linux,mac,windows等环境部署。 7 | 8 | 9 | 10 | 效果截图 11 | ====== 12 | ![node-msg-sender-demo](http://112.74.81.224:3000/images/demo.png) 13 | 14 | 线上demo 15 | ====== 16 | 17 | http://112.74.81.224:3000/ 18 | 19 | 可以通过url:http://112.74.81.224:3000/sendMsg/?type=private&uid=1504936989000&content=消息内容 向当前用户发送消息 20 | 21 | 可以通过url:http://112.74.81.224:3000/sendMsg/?type=public&content=消息内容 向所有在线用户推送消息 22 | 23 | uid为接收消息的uid,如果不传递则向所有人推送消息   24 | content 为消息内容 25 | 26 | 注:可以通过php或者其它语言的curl功能实现后台推送 27 | 28 | 下载安装 29 | ====== 30 | 1、git clone https://github.com/gytai/node-websocket-msg-sender.git 31 | 32 | 2、npm install 33 | 34 | 3、apt-get install redis-server 35 | 36 | 4、redis-server 37 | 38 | 后端服务启动停止,先安装PM2(Advanced Node.js process manager,http://pm2.keymetrics.io/) 39 | ====== 40 | ### 启动服务 41 | pm2 start bin/www --name msg-sender 42 | 43 | ### 停止服务 44 | pm2 stop msg-sender 45 | 46 | Web前端代码类似: 47 | ==== 48 | ```javascript 49 | // 引入前端文件 50 | 51 | 67 | ``` 68 | 69 | 其他客户端 70 | ==== 71 | 根据websocket协议即可。具体参考websocket协议。 72 | 73 | 74 | Nodejs后端调用api向任意用户推送数据 75 | ==== 76 | ```javascript 77 | var type = req.query.type || msgType.public; 78 | var content = req.query.content || 'none'; 79 | var uid = req.query.uid; 80 | 81 | switch (type){ 82 | case msgType.public: 83 | ioSvc.serverBroadcastMsg(content); 84 | break; 85 | case msgType.private: 86 | if(!uid){ 87 | return res.send({code:400,msg:'uid参数必传'}); 88 | } 89 | ioSvc.serverToPrivateMsg(uid,content); 90 | break; 91 | } 92 | ``` 93 | 94 | Http 发送数据,可以配置跨站发送(需要设置跨域放行)。例如安卓或者IOS等其他客户端也可以方便的发送消息。 95 | ==== 96 | 可以通过url:http://localhost:3000/sendMsg/?type=private&uid=1504936989000&content=消息内容 向当前用户发送消息 97 | 98 | 可以通过url:http://localhost:3000/sendMsg/?type=public&content=消息内容 向所有在线用户推送消息 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var app = express(); 9 | 10 | var index = require('./routes/index'); 11 | var users = require('./routes/users'); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'ejs'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', index); 26 | app.use('/users', users); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handler 36 | app.use(function(err, req, res, next) { 37 | // set locals, only providing error in development 38 | res.locals.message = err.message; 39 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 40 | 41 | // render the error page 42 | res.status(err.status || 500); 43 | res.render('error'); 44 | }); 45 | 46 | module.exports = app; 47 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('nodemessage:server'); 9 | var http = require('http'); 10 | var ioSvc = require('../io/io'); 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | //??Socket.IO 25 | var io = require('socket.io')(server); 26 | ioSvc.ioServer(io); 27 | 28 | /** 29 | * Listen on provided port, on all network interfaces. 30 | */ 31 | 32 | server.listen(port); 33 | server.on('error', onError); 34 | server.on('listening', onListening); 35 | 36 | /** 37 | * Normalize a port into a number, string, or false. 38 | */ 39 | 40 | function normalizePort(val) { 41 | var port = parseInt(val, 10); 42 | 43 | if (isNaN(port)) { 44 | // named pipe 45 | return val; 46 | } 47 | 48 | if (port >= 0) { 49 | // port number 50 | return port; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | /** 57 | * Event listener for HTTP server "error" event. 58 | */ 59 | 60 | function onError(error) { 61 | if (error.syscall !== 'listen') { 62 | throw error; 63 | } 64 | 65 | var bind = typeof port === 'string' 66 | ? 'Pipe ' + port 67 | : 'Port ' + port; 68 | 69 | // handle specific listen errors with friendly messages 70 | switch (error.code) { 71 | case 'EACCES': 72 | console.error(bind + ' requires elevated privileges'); 73 | process.exit(1); 74 | break; 75 | case 'EADDRINUSE': 76 | console.error(bind + ' is already in use'); 77 | process.exit(1); 78 | break; 79 | default: 80 | throw error; 81 | } 82 | } 83 | 84 | /** 85 | * Event listener for HTTP server "listening" event. 86 | */ 87 | 88 | function onListening() { 89 | var addr = server.address(); 90 | var bind = typeof addr === 'string' 91 | ? 'pipe ' + addr 92 | : 'port ' + addr.port; 93 | debug('Listening on ' + bind); 94 | } 95 | -------------------------------------------------------------------------------- /io/io.js: -------------------------------------------------------------------------------- 1 | /* 2 | *介绍:socket.io 功能封装 3 | *作者:TaiGuangYin 4 | *时间:2017-09-09 5 | * */ 6 | var redis = require('../utils/redis'); 7 | var msgType = require('./messageTpye'); 8 | var ioSvc = require('./ioHelper').ioSvc; 9 | 10 | //服务端连接 11 | function ioServer(io) { 12 | 13 | var _self = this; 14 | ioSvc.setInstance(io); 15 | 16 | //初始化连接人数 17 | redis.set('online_count',0,null,function (err,ret) { 18 | if(err){ 19 | console.error(err); 20 | } 21 | }); 22 | 23 | io.on('connection', function (socket) { 24 | console.log('SocketIO有新的连接!'); 25 | 26 | _self.updateOnlieCount(true); 27 | 28 | //用户与Socket进行绑定 29 | socket.on('login', function (uid) { 30 | console.log(uid+'登录成功'); 31 | redis.set(uid,socket.id,null,function (err,ret) { 32 | if(err){ 33 | console.error(err); 34 | } 35 | }); 36 | redis.set(socket.id,uid,null,function (err,ret) { 37 | if(err){ 38 | console.error(err); 39 | } 40 | }); 41 | }); 42 | 43 | //断开事件 44 | socket.on('disconnect', function() { 45 | console.log("与服务其断开"); 46 | _self.updateOnlieCount(false); 47 | redis.get(socket.id,function (err,val) { 48 | if(err){ 49 | console.error(err); 50 | } 51 | redis.del(socket.id,function (err,ret) { 52 | if(err){ 53 | console.error(err); 54 | } 55 | 56 | }); 57 | redis.del(val,function (err,ret) { 58 | if(err){ 59 | console.error(err); 60 | } 61 | }); 62 | }); 63 | }); 64 | 65 | //重连事件 66 | socket.on('reconnect', function() { 67 | console.log("重新连接到服务器"); 68 | }); 69 | 70 | //监听客户端发送的信息,实现消息转发到各个其他客户端 71 | socket.on('message',function(msg){ 72 | if(msg.type == msgType.messageType.public){ 73 | socket.broadcast.emit("message",msg.content); 74 | }else if(msg.type == msgType.messageType.private){ 75 | var uid = msg.uid; 76 | redis.get(uid,function (err,sid) { 77 | if(err){ 78 | console.error(err); 79 | } 80 | if(sid){ 81 | //给指定的客户端发送消息 82 | io.sockets.socket(sid).emit('message', msg.content); 83 | } 84 | }); 85 | } 86 | 87 | }); 88 | }); 89 | 90 | this.updateOnlieCount = function (isConnect) { 91 | //记录在线客户连接数 92 | redis.get('online_count',function (err,val) { 93 | if(err){ 94 | console.error(err); 95 | } 96 | if(!val){ 97 | val = 0; 98 | } 99 | if(typeof val == 'string'){ 100 | val = parseInt(val); 101 | } 102 | if(isConnect){ 103 | val += 1; 104 | }else{ 105 | val -= 1; 106 | if(val<=0){ 107 | val = 0; 108 | } 109 | } 110 | 111 | console.log('当前在线人数:'+val); 112 | io.sockets.emit('update_online_count', { online_count: val }); 113 | 114 | redis.set('online_count',val,null,function (err,ret) { 115 | if(err){ 116 | console.error(err); 117 | } 118 | }); 119 | }); 120 | }; 121 | 122 | } 123 | 124 | 125 | //模块导出 126 | exports.ioServer = ioServer; -------------------------------------------------------------------------------- /io/ioHelper.js: -------------------------------------------------------------------------------- 1 | var redis = require('../utils/redis'); 2 | 3 | var ioSvc = {}; 4 | ioSvc.io = null; 5 | 6 | //初始化实例 7 | ioSvc.setInstance = function (io) { 8 | this.io = io; 9 | }; 10 | 11 | ioSvc.getInstance =function () { 12 | return this.io; 13 | }; 14 | 15 | //服务器给所有客户端广播消息 16 | ioSvc.serverBroadcastMsg = function (data) { 17 | console.log('发送广播消息'); 18 | console.log(data); 19 | this.io.sockets.emit('message',data); 20 | }; 21 | 22 | //服务端给指定用户发消息 23 | ioSvc.serverToPrivateMsg = function (uid,data) { 24 | console.log('发送私人消息'); 25 | console.log(data); 26 | redis.get(uid,function (err,sid) { 27 | if(err){ 28 | console.error(err); 29 | } 30 | if(sid){ 31 | //给指定的客户端发送消息 32 | this.io.sockets.socket(sid).emit('message',data); 33 | } 34 | }); 35 | }; 36 | 37 | exports.ioSvc = ioSvc; -------------------------------------------------------------------------------- /io/messageTpye.js: -------------------------------------------------------------------------------- 1 | const messageType= { 2 | 'public':'public', 3 | 'private':'private' 4 | }; 5 | 6 | exports.messageType = messageType; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodemessage", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.17.1", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.3", 12 | "ejs": "~2.5.6", 13 | "express": "~4.15.2", 14 | "morgan": "~1.8.1", 15 | "serve-favicon": "~2.4.2", 16 | "socket.io": "^2.0.3", 17 | "socket.io-client": "^2.0.3", 18 | "redis": "^2.8.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/node-websocket-msg-sender/3256674041c2a9c5a7c83077e82e345b366f848c/public/images/demo.png -------------------------------------------------------------------------------- /public/images/icon-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/node-websocket-msg-sender/3256674041c2a9c5a7c83077e82e345b366f848c/public/images/icon-close.png -------------------------------------------------------------------------------- /public/images/repeat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/node-websocket-msg-sender/3256674041c2a9c5a7c83077e82e345b366f848c/public/images/repeat.jpg -------------------------------------------------------------------------------- /public/javascripts/notify.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.extend({ 3 | notify: function (options) { 4 | var settings = $.extend({ type: 'sticky', speed: 500, onDemandButtonHeight: 35 }, options); 5 | return this.each(function () { 6 | var wrapper = $(this); 7 | var ondemandBtn = $('.ondemand-button'); 8 | var dh = -35; 9 | var w = wrapper.outerWidth() - ondemandBtn.outerWidth(); 10 | ondemandBtn.css('left', w).css('margin-top', dh + "px" ); 11 | var h = -wrapper.outerHeight(); 12 | wrapper.addClass(settings.type).css('margin-top', h).addClass('visible').removeClass('hide'); 13 | if (settings.type != 'ondemand') { 14 | wrapper.stop(true, false).animate({ marginTop: 0 }, settings.speed); 15 | } 16 | else { 17 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); 18 | } 19 | 20 | var closeBtn = $('.close', wrapper); 21 | closeBtn.click(function () { 22 | if (settings.type == 'ondemand') { 23 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { 24 | wrapper.removeClass('visible').addClass('hide'); 25 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); 26 | }); 27 | } 28 | else { 29 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { 30 | wrapper.removeClass('visible').addClass('hide'); 31 | }); 32 | } 33 | }); 34 | if (settings.type == 'floated') { 35 | $(document).scroll(function (e) { 36 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); 37 | }).resize(function (e) { 38 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); 39 | }); 40 | } 41 | else if (settings.type == 'ondemand') { 42 | ondemandBtn.click(function () { 43 | $(this).animate({ marginTop: dh }, settings.speed, function () { 44 | wrapper.removeClass('hide').addClass('visible').animate({ marginTop: 0 }, settings.speed, function () { 45 | 46 | }); 47 | }) 48 | }); 49 | } 50 | 51 | }); 52 | 53 | } 54 | }); 55 | })(jQuery); 56 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | body { 3 | margin:0px; padding:0px; 4 | font-family: Arial, Helvetica, sans-serif; 5 | background:url(/images/repeat.jpg); 6 | font-size:15px; 7 | color:#000; 8 | } 9 | ul{list-style:none; margin:0px; padding:0px; margin-top:20px;} 10 | li{padding-bottom:20px;} 11 | .sticky p, .floated p, .fixed p, .ondemand p{ float:left; padding:0px; margin:0px; margin-left:10px; line-height:45px; color:#fff; font-size:12px;} 12 | .sticky a, .floated a, .fixed a, .ondemand a{ float:right; margin:13px 10px 0px 0px; } 13 | img{border:0px;} 14 | .wrapper{padding:20px;} 15 | 16 | .sticky { 17 | 18 | position:fixed; 19 | top:0; 20 | left:0; 21 | z-index:1000; 22 | width:100%; 23 | border-bottom:3px solid #fff !important; 24 | 25 | background: #91BD09; /* Old browsers */ 26 | background: -moz-linear-gradient(top, #91BD09 0%, #91BD09 100%); /* FF3.6+ */ 27 | 28 | /* FireFox 3.6 */ 29 | /* Safari4+, Chrome */ 30 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#91BD09', endColorstr='#91BD09')"; 31 | -pie-background: linear-gradient(#91BD09, #91BD09 100%); 32 | behavior: url(PIE.htc); 33 | -moz-box-shadow: 1px 1px 7px #676767; 34 | -webkit-box-shadow: 1px 1px 7px #676767; 35 | box-shadow: 1px 1px 7px #676767; 36 | height: 45px; 37 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #91BD09),color-stop(1, #91BD09));/* IE6,IE7 */ 38 | /* IE8 */ 39 | /* Firefox F3.5+ */ 40 | /* Safari3.0+, Chrome */ 41 | } 42 | 43 | 44 | .floated { 45 | 46 | position:absolute; 47 | top:0; 48 | left:0; 49 | z-index:1000; 50 | width:100%; 51 | border-bottom:3px solid #fff !important; 52 | background: #0e59ae; /* Old browsers */ 53 | background: -moz-linear-gradient(top, #0e59ae 0%, #0e59ae 100%); /* FF3.6+ */ 54 | 55 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#0E59AE', endColorstr='#0E59AE')"; 56 | 57 | -moz-box-shadow: 1px 1px 7px #676767; 58 | -webkit-box-shadow: 1px 1px 7px #676767; 59 | box-shadow: 1px 1px 7px #676767; 60 | height: 45px; 61 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #0E59AE),color-stop(1, #0E59AE));/* IE6,IE7 */ 62 | -pie-background: linear-gradient(#0E59AE, #0E59AE 100%); 63 | behavior: url(PIE.htc); 64 | } 65 | 66 | 67 | .fixed { 68 | position:absolute; 69 | top:0; 70 | left:0; 71 | width:100%; 72 | border-bottom:3px solid #fff !important; 73 | 74 | background: #660099; /* Old browsers */ 75 | background: -moz-linear-gradient(top, #660099 0%, #660099 100%); /* FF3.6+ */ 76 | 77 | /* FireFox 3.6 */ 78 | /* Safari4+, Chrome */ 79 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#660099', endColorstr='#660099')"; 80 | -pie-background: linear-gradient(#660099, #660099 100%); 81 | behavior: url(PIE.htc); 82 | -moz-box-shadow: 1px 1px 7px #676767; 83 | -webkit-box-shadow: 1px 1px 7px #676767; 84 | box-shadow: 1px 1px 7px #676767; 85 | height: 45px; 86 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #660099),color-stop(1, #660099));/* IE6,IE7 */ 87 | /* IE8 */ 88 | /* Firefox F3.5+ */ 89 | /* Safari3.0+, Chrome */ 90 | } 91 | 92 | .ondemand { 93 | 94 | width:100%; 95 | border-bottom:3px solid #fff !important; 96 | position:absolute; 97 | top:0; 98 | left:0; 99 | z-index:1000; 100 | 101 | background: #CC0000; /* Old browsers */ 102 | background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */ 103 | 104 | /* FireFox 3.6 */ 105 | /* Safari4+, Chrome */ 106 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')"; 107 | -pie-background: linear-gradient(#CC0000, #CC0000 100%); 108 | behavior: url(PIE.htc); 109 | -moz-box-shadow: 1px 1px 7px #676767; 110 | -webkit-box-shadow: 1px 1px 7px #676767; 111 | box-shadow: 1px 1px 7px #676767; 112 | height: 45px; 113 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */ 114 | /* IE8 */ 115 | /* Firefox F3.5+ */ 116 | /* Safari3.0+, Chrome */ 117 | } 118 | .ondemand-button 119 | { 120 | width:40px !important; 121 | height:40px; 122 | float:right !important; 123 | z-index:999; 124 | position:absolute; 125 | margin-right:100px!important; 126 | } 127 | 128 | 129 | 130 | 131 | 132 | #footer{width:100%; margin:0 auto; font-size:12px; color:#0E59AE; height:30px; margin-top:200px;border-top:1px solid #CCC;padding:18px;} 133 | .hide{display:none;} 134 | 135 | 136 | /* Buttons */ 137 | 138 | .round.button { 139 | -moz-border-radius: 15px; 140 | -webkit-border-radius: 15px; 141 | border-radius: 15px; 142 | background-image: url(button-images/round-button-overlay.png); 143 | border: 1px solid rgba(0, 0, 0, 0.25); 144 | font-size: 13px; 145 | padding: 0; 146 | } 147 | 148 | .button { 149 | -moz-border-radius: 5px; 150 | -webkit-border-radius: 5px; 151 | border-radius: 5px; 152 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); 153 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); 154 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); 155 | background: #222; 156 | border: 1px solid rgba(0, 0, 0, 0.25); 157 | color: white !important; 158 | cursor: pointer; 159 | display: inline-block; 160 | font-size: 13px; 161 | font-weight: bold; 162 | line-height: 1; 163 | overflow: visible; 164 | padding: 5px 15px 6px; 165 | position: relative; 166 | text-decoration: none; 167 | text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); 168 | width: auto; 169 | text-align: center; 170 | } 171 | 172 | .round.button span { 173 | -moz-border-radius: 14px; 174 | -webkit-border-radius: 14px; 175 | border-radius: 14px; 176 | display: block; 177 | line-height: 1; 178 | padding: 4px 15px 6px; 179 | } 180 | 181 | 182 | .green.button { 183 | background-color:#91BD09; 184 | } 185 | .green.button:hover { 186 | background-color:#749A02; 187 | } 188 | .green.button:active { 189 | background-color:#a4d50b; 190 | } 191 | .blue.button { 192 | background-color:#0E59AE; 193 | } 194 | .blue.button:hover { 195 | background-color:#063468; 196 | } 197 | .blue.button:active { 198 | background-color:#1169cc; 199 | } 200 | .purple.button { 201 | background-color:#660099; 202 | } 203 | .purple.button:hover { 204 | background-color:#330066; 205 | } 206 | .purple.button:active { 207 | background-color:#7f02bd; 208 | } 209 | 210 | .red.button { 211 | background-color:#CC0000; 212 | } 213 | .red.button:hover { 214 | background-color:#990000; 215 | } 216 | .red.button:active { 217 | background-color:#ea0202; 218 | } 219 | .close 220 | {} 221 | 222 | .show{ 223 | 224 | background: #CC0000; /* Old browsers */ 225 | background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */ 226 | 227 | 228 | /* FireFox 3.6 */ 229 | /* Safari4+, Chrome */ 230 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')"; 231 | -pie-background: linear-gradient(#CC0000, #CC0000 100%); 232 | behavior: url(PIE.htc); 233 | -moz-box-shadow: 1px 1px 7px #676767; 234 | -webkit-box-shadow: 1px 1px 7px #676767; 235 | box-shadow: 1px 1px 7px #676767; 236 | height: 35px; 237 | float: right; 238 | width: 30px; 239 | overflow:hidden; 240 | /*margin-top: 0px !important;*/ 241 | margin-right: 10px !important; 242 | text-align: center; 243 | background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */ 244 | /* IE8 */ 245 | /* Firefox F3.5+ */ 246 | /* Safari3.0+, Chrome */ 247 | /* Opera 10.5, IE 9.0 */ 248 | 249 | 250 | } 251 | 252 | .show img{margin-top:10px;} 253 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var ioSvc = require('../io/ioHelper').ioSvc; 4 | var msgType = require('../io/messageTpye').messageType; 5 | 6 | /* GET home page. */ 7 | router.get('/', function(req, res, next) { 8 | res.render('index', { title: 'Node-Msg-Sender' }); 9 | }); 10 | 11 | router.get('/sendMsg', function(req, res, next) { 12 | var type = req.query.type || msgType.public; 13 | var content = req.query.content || 'none'; 14 | var uid = req.query.uid; 15 | 16 | switch (type){ 17 | case msgType.public: 18 | ioSvc.serverBroadcastMsg(content); 19 | break; 20 | case msgType.private: 21 | if(!uid){ 22 | return res.send({code:400,msg:'uid参数必传'}); 23 | } 24 | ioSvc.serverToPrivateMsg(uid,content); 25 | break; 26 | } 27 | return res.send({code:200,msg:'发送成功'}); 28 | 29 | }); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /utils/redis.js: -------------------------------------------------------------------------------- 1 | var redisSvc = {}; 2 | var redis = require("redis"); 3 | 4 | if(!client){ 5 | var client = redis.createClient(); 6 | } 7 | 8 | client.on("error", function (err) { 9 | console.log("Redis Error :" , err); 10 | client = null; 11 | }); 12 | 13 | client.on('connect', function(){ 14 | console.log('Redis连接成功.'); 15 | }); 16 | 17 | /** 18 | * 添加string类型的数据 19 | * @param key 键 20 | * @params value 值 21 | * @params expire (过期时间,单位秒;可为空,为空表示不过期) 22 | * @param callBack(err,result) 23 | */ 24 | redisSvc.set = function(key, value, expire, callback){ 25 | 26 | client.set(key, value, function(err, result){ 27 | 28 | if (err) { 29 | console.log(err); 30 | callback(err,null); 31 | return; 32 | } 33 | 34 | if (!isNaN(expire) && expire > 0) { 35 | client.expire(key, parseInt(expire)); 36 | } 37 | 38 | callback(null,result) 39 | }) 40 | }; 41 | 42 | /** 43 | * 查询string类型的数据 44 | * @param key 键 45 | * @param callBack(err,result) 46 | */ 47 | redisSvc.get = function(key, callback){ 48 | 49 | client.get(key, function(err,result){ 50 | 51 | if (err) { 52 | console.log(err); 53 | callback(err,null); 54 | return; 55 | } 56 | 57 | callback(null,result); 58 | }); 59 | }; 60 | 61 | /* 62 | *删除String 类型的key 63 | * @param key 键 64 | * @param callBack(err,result) 65 | */ 66 | redisSvc.del = function(key, callback){ 67 | 68 | client.del(key, function(err,result){ 69 | 70 | if (err) { 71 | console.log(err); 72 | callback(err,null); 73 | return; 74 | } 75 | 76 | callback(null,result); 77 | }); 78 | }; 79 | 80 | 81 | module.exports = redisSvc; -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

13 | 14 |
15 |
16 |
17 |

介绍:

18 | 这是一个消息推送系统,基于Socket.IO开发。


19 |

支持以下特性:

20 | 30 |

测试:

31 | 当前用户uid:
32 | 可以通过url:http://:3000/sendMsg/?type=private&uid=&content=消息内容 向当前用户发送消息
33 | 可以通过url:http://:3000/sendMsg/?type=public&content=消息内容 向所有在线用户推送消息
34 | 43 |
44 |
45 | 49 | 50 | 51 | 52 | 72 | 73 | --------------------------------------------------------------------------------