This commit is contained in:
LittleBoy 2019-09-28 21:25:24 +08:00
parent 33316c9112
commit 894bed05b1
15 changed files with 1749 additions and 282 deletions

4
app.js
View File

@ -17,12 +17,16 @@ app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public // uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
// 日志
app.use(logger('dev')); app.use(logger('dev'));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }));
// cookie
app.use(cookieParser()); app.use(cookieParser());
// 设置静态资源
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
//绑定路由
app.use('/', index); app.use('/', index);
app.use('/users', users); app.use('/users', users);
app.use('/message', message); app.use('/message', message);

View File

@ -3,7 +3,6 @@
/** /**
* Module dependencies. * Module dependencies.
*/ */
var app = require('../app'); var app = require('../app');
var debug = require('debug')('kefu:server'); var debug = require('debug')('kefu:server');
var http = require('http'); var http = require('http');
@ -12,7 +11,6 @@ var ioSvc = require('../io/io');
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.
*/ */
var port = normalizePort(process.env.PORT || '9010'); var port = normalizePort(process.env.PORT || '9010');
app.set('port', port); app.set('port', port);
@ -28,7 +26,9 @@ ioSvc.ioServer(io);
* Listen on provided port, on all network interfaces. * Listen on provided port, on all network interfaces.
*/ */
server.listen(port); server.listen(port,()=>{
console.log("server is running,detail see http://localhost:%s",port);
});
server.on('error', onError); server.on('error', onError);
server.on('listening', onListening); server.on('listening', onListening);

View File

@ -43,7 +43,7 @@ function ioServer(io) {
console.log(uid + '登录成功'); console.log(uid + '登录成功');
//通知用户上线 //通知用户上线
if(uid != AppConfig.KEFUUUID){ if(uid != AppConfig.KEFUUUID){ // 通知给管理员
redis.get(AppConfig.KEFUUUID,function (err,sid) { redis.get(AppConfig.KEFUUUID,function (err,sid) {
if(err){ if(err){
console.error(err); console.error(err);

View File

@ -1,11 +1,14 @@
var mongoose = require('../mongoose').mongoose; var mongoose = require('../utils/mongoose').mongoose;
var crypto = require('crypto'); var crypto = require('crypto');
var Schema = mongoose.Schema; var Schema = mongoose.Schema;
// 管理员模型
var UsersSchema = new Schema({ var UsersSchema = new Schema({
username : { type:String }, username: {type: String,index: true},
password: {type: String}, password: {type: String},
nickname: {type: String},
description: {type: String,default:'欢迎咨询'},
time: {type: Date, default: Date.now} time: {type: Date, default: Date.now}
}); });
@ -43,5 +46,24 @@ function reset_psw(username,psw_old,psw_new,callback) {
}); });
} }
function findByUserName(username){
return new Promise((resolve ,reject)=>{
UsersModel.findOne({username},(err,res)=>{
if(err) reject(err)
else if(!res) resolve(null)
else{
resolve(res);
}
});
})
}
function updateInfo(username, nickname, description) {
return new Promise((resovle, reject) => {
UsersModel.findOneAndUpdate({username}, {nickname, description}, resovle);
})
}
exports.updateInfo = updateInfo;
exports.findByUserName = findByUserName;
exports.login = login; exports.login = login;
exports.reset_psw = reset_psw; exports.reset_psw = reset_psw;

View File

@ -11,12 +11,14 @@
"debug": "~2.6.3", "debug": "~2.6.3",
"ejs": "~2.5.6", "ejs": "~2.5.6",
"express": "~4.15.2", "express": "~4.15.2",
"express-session": "^1.16.2",
"lib-qqwry": "^1.3.1",
"mongoose": "^4.12.5",
"morgan": "~1.8.1", "morgan": "~1.8.1",
"qiniu": "^7.1.1",
"redis": "^2.8.0",
"serve-favicon": "~2.4.2", "serve-favicon": "~2.4.2",
"socket.io": "~2.0.4", "socket.io": "~2.0.4",
"socket.io-client":"^2.0.4", "socket.io-client": "^2.0.4"
"redis":"^2.8.0",
"mongoose":"^4.12.5",
"qiniu":"^7.1.1"
} }
} }

View File

@ -6,10 +6,11 @@ layui.use(['layer', 'form', 'jquery'], function () {
var currentUUID = ''; var currentUUID = '';
var uuid = ''; var uuid = '';
// 创建socket连接
var socket = io.connect('http://' + document.domain + ':9010', { var socket = io.connect('http://' + document.domain + ':9010', {
"transports":['websocket', 'polling'] "transports": ['websocket', 'polling'],
autoConnect: false
}); });
var uuids = []; var uuids = [];
var online_num = 0; var online_num = 0;
@ -44,12 +45,14 @@ layui.use(['layer', 'form', 'jquery'], function () {
div.scrollTop = div.scrollHeight; div.scrollTop = div.scrollHeight;
} }
// 添加新的节点
function insert_section(uid) { function insert_section(uid) {
var html = '<section class="user-section" style="display:none;" id="section-' + uid + '"></section>'; var html = '<section class="user-section" style="display:none;" id="section-' + uid + '"></section>';
$(".message-container").append(html); $(".message-container").append(html);
get_message(uid); get_message(uid);
} }
// 添加管理员消息
function insert_agent_html(msg) { function insert_agent_html(msg) {
var time = dateFormat(); var time = dateFormat();
if (msg.time) { if (msg.time) {
@ -80,7 +83,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
$('#section-' + msg.uid).append(html); $('#section-' + msg.uid).append(html);
} }
// 添加客户端消息
function insert_client_html(msg) { function insert_client_html(msg) {
var time = dateFormat(); var time = dateFormat();
if (msg.time) { if (msg.time) {
@ -110,6 +113,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
$('#section-' + msg.uid).append(html); $('#section-' + msg.uid).append(html);
} }
function insert_user_html(id, name) { function insert_user_html(id, name) {
var html = '<div class="user-info layui-row" id="' + id + '">\n' + var html = '<div class="user-info layui-row" id="' + id + '">\n' +
' <div class="layui-col-xs3 user-avatar">\n' + ' <div class="layui-col-xs3 user-avatar">\n' +
@ -121,6 +125,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
$('.chat-user').append(html); $('.chat-user').append(html);
} }
//设置消息状态
function msg_sender_status(status) { function msg_sender_status(status) {
if (status) { if (status) {
$(".btnMsgSend").removeClass("layui-btn-disabled"); $(".btnMsgSend").removeClass("layui-btn-disabled");
@ -133,6 +138,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
} }
} }
//消息提醒
function msg_notification(msg) { function msg_notification(msg) {
$(".chat-user #" + msg.uid + " .msg-tips").show(); $(".chat-user #" + msg.uid + " .msg-tips").show();
if (window.Notification && Notification.permission !== "denied") { if (window.Notification && Notification.permission !== "denied") {
@ -142,6 +148,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
} }
} }
//设置 在线状态(人数)
function update_online_status() { function update_online_status() {
var num = uuids.length; var num = uuids.length;
if (online_num > num) { if (online_num > num) {
@ -163,6 +170,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
"chat_type": 'text' "chat_type": 'text'
}; };
socket.emit('message', msg_sender); socket.emit('message', msg_sender);
//将发送的消息添加到页面中
insert_agent_html(msg_sender); insert_agent_html(msg_sender);
scrollToBottom(); scrollToBottom();
$("#msg-send-textarea").val(''); $("#msg-send-textarea").val('');
@ -216,76 +224,11 @@ layui.use(['layer', 'form', 'jquery'], function () {
}); });
} }
//发送消息
$(".btnMsgSend").click(function () { $(".btnMsgSend").click(function () {
msg_send(); msg_send();
}); });
$(".picture-upload").click(function () {
var uploader = Qiniu.uploader({
runtimes: 'html5,flash,html4', // 上传模式,依次退化
browse_button: 'pickfiles', // 上传选择的点选按钮,必需
uptoken_url: '/uptoken', // Ajax请求uptoken的Url强烈建议设置服务端提供
get_new_uptoken: false, // 设置上传文件的时候是否每次都重新获取新的uptoken
domain: 'http://kefuimg.chinameyer.com/', // bucket域名下载资源时用到必需
container: 'btn-uploader', // 上传区域DOM ID默认是browser_button的父元素
max_file_size: '10mb', // 最大文件体积限制
flash_swf_url: 'path/of/plupload/Moxie.swf', //引入flash相对路径
max_retries: 3, // 上传失败最大重试次数
dragdrop: false, // 开启可拖曳上传
drop_element: 'btn-uploader', // 拖曳上传区域元素的ID拖曳文件或文件夹后可触发上传
chunk_size: '4mb', // 分块上传时,每块的体积
auto_start: true, // 选择文件后自动上传,若关闭需要自己绑定事件触发上传
unique_names: true,
filters : {
max_file_size : '10mb',
prevent_duplicates: true,
// Specify what files to browse for
mime_types: [
{title : "Image files", extensions : "jpg,gif,png,bmp"}, // 限定jpg,gif,png后缀上传
]
},
init: {
'FilesAdded': function(up, files) {
plupload.each(files, function(file) {
// 文件添加进队列后,处理相关的事情
});
},
'BeforeUpload': function(up, file) {
// 每个文件上传前,处理相关的事情
},
'UploadProgress': function(up, file) {
// 每个文件上传时,处理相关的事情
},
'FileUploaded': function(up, file, info) {
// 查看简单反馈
var domain = up.getOption('domain');
var res = JSON.parse(info);
var sourceLink = domain +"/"+ res.key;
var msg_sender = {
"type":'private',
"uid":currentUUID,
"content":'图片消息',
"from_uid":uuid,
"chat_type":'image',
"image":sourceLink
};
socket.emit('message', msg_sender);
insert_agent_html(msg_sender);
scrollToBottom();
},
'Error': function(up, err, errTip) {
//上传出错时,处理相关的事情
$.toast("上传失败");
},
'UploadComplete': function() {
//队列文件处理完毕后,处理相关的事情
}
}
});
});
$(".emoji-list li").click(function () { $(".emoji-list li").click(function () {
var content = $("#msg-send-textarea").val(); var content = $("#msg-send-textarea").val();
$("#msg-send-textarea").val(content + " " + $(this).html() + " "); $("#msg-send-textarea").val(content + " " + $(this).html() + " ");
@ -299,7 +242,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
//连接服务器 //连接服务器
socket.on('connect', function () { socket.on('connect', function () {
console.log('连接成功...'); console.log('连接成功...');
uuid = 'chat-kefu-admin'; uuid = 'chat-admin-' + data.username;
var ip = $("#keleyivisitorip").html(); var ip = $("#keleyivisitorip").html();
var msg = { var msg = {
"uid": uuid, "uid": uuid,
@ -369,6 +312,34 @@ layui.use(['layer', 'form', 'jquery'], function () {
$(".chat-user #" + uid + " .msg-tips").hide(); $(".chat-user #" + uid + " .msg-tips").hide();
}); });
const showLoginView = () => {
layer.prompt({
formType: 0,
title: '请先输入工号进行登录',
btnAlign: 'c',
btn: [' 登录 '],
closeBtn: 0,
shade :0.7,
value: ''
}, function (username, index, elem) {
$.post('/admin/login', {username}, (res) => {
if (res.code != 200) {
layer.msg(res.message);
return;
}
layer.msg('登录成功');
layer.closeAll();
console.log(res);
// location.reload();
}, 'json').fail(() => {
layer.msg('登录异常请重试')
})
});
}
if (!data.username) {
showLoginView();
} else {
init(); init();
get_users(); get_users();
}
}); });

View File

@ -2,14 +2,25 @@ var express = require('express');
var router = express.Router(); var router = express.Router();
var AppConfig = require('../config'); var AppConfig = require('../config');
var qiniu = require('qiniu'); var qiniu = require('qiniu');
var msgModel = require('../model/message');
var userModel = require('./../model/users');
router.get('/', function(req, res, next) { const getViewAdmin = (req) => {
res.render('./server/index');
});
router.get('/admin', function(req, res, next) { let data = {
res.render('./server/index'); username: null
}); }
if (req.cookies && req.cookies.username) {
data.username = req.cookies.username
}
return data;
};
const adminIndexHandler = (req, res, next) => {
let data = getViewAdmin(req)
res.render('./server/index', data);
}
router.get('/', adminIndexHandler);
router.get('/admin', adminIndexHandler);
router.get('/client', function (req, res, next) { router.get('/client', function (req, res, next) {
res.render('./client/index'); res.render('./client/index');
@ -20,9 +31,61 @@ router.get('/admin/users', function(req, res, next) {
}); });
router.get('/admin/setup', function (req, res, next) { router.get('/admin/setup', function (req, res, next) {
res.render('./server/setup'); let data = getViewAdmin(req)
res.render('./server/setup', data);
}); });
router.post('/admin/update', async (req, res, next) => {
// userModel.updateInfo()
let data = req.body;
let cookie = req.cookies;
if (!cookie.username) {
res.send({code: 403, message: '工号格式不正确'});
return;
}
if (!data.nickname) {
res.send({code: 500, message: '昵称不能为空'});
return;
}
try {
await userModel.findByUserName(cookie.username);
await userModel.updateInfo(cookie.username, data.nickname, data.description)
res.send({code: 200});
} catch (e) {
res.send({code: 500, message: e.toString()});
}
});
router.post('/admin/login', async function (req, res, next) {
console.log('/admin/login');
let data = req.body;
if (!data.username) {
res.send({code: 201, message: '工号格式不正确'})
return;
}
try {
let user = await userModel.findByUserName(data.username);
if (user) {
let maxAge = 60 * 1000 * 60 * 24 * 30,httpOnly = true;
res.cookie('username', user.username, {httpOnly,httpOnly}) // 该处是设置 cookie 与 httpOnly
res.cookie('token', user._id, {httpOnly,httpOnly}) // 该处是设置 cookie 与 httpOnly
// req.cookies.set('username', user.username);
// req.cookies.set('token', user._id);
res.send({
code: 200, user: {
token: user._id,
username: user.username
}
});
} else {
res.send({code: 403, message: '工号不存在或者格式不正确'})
}
} catch (e) {
console.log('err', e);
res.send({code: 500, message: e.toString()});
}
});
// 七牛上传
router.get('/uptoken', function (req, res, next) { router.get('/uptoken', function (req, res, next) {
var mac = new qiniu.auth.digest.Mac(AppConfig.QINIU.accessKey, AppConfig.QINIU.secretKey); var mac = new qiniu.auth.digest.Mac(AppConfig.QINIU.accessKey, AppConfig.QINIU.secretKey);
var options = { var options = {

8
test.js Normal file
View File

@ -0,0 +1,8 @@
var libqqwry = require('lib-qqwry');
var qqwry = libqqwry() //初始化IP库解析器
qqwry.speed(); //启用急速模式;
var ipInfo = qqwry.searchIP("202.103.102.10"); //查询IP信息
console.log(ipInfo)
ipInfo = qqwry.searchIP("127.0.0.1"); //查询IP信息
console.log(ipInfo)

View File

@ -1,4 +1,7 @@
var http = require('http'); var http = require('http');
var libqqwry = require('lib-qqwry');
var qqwry = libqqwry() //初始化IP库解析器
qqwry.speed(); //启用急速模式;
function getClientIp(req) { function getClientIp(req) {
return req.headers['x-forwarded-for'] || return req.headers['x-forwarded-for'] ||
@ -8,22 +11,20 @@ function getClientIp(req) {
}; };
function getIpLocation(ip, callback) { function getIpLocation(ip, callback) {
http.get('http://ip.taobao.com/service/getIpInfo.php?ip='+ip,function(req,res){ return setTimeout(() => {
var html=''; try {
req.on('data',function(data){ let data = qqwry.searchIP(ip)
html+=data; if (!data) {
}); callback(new Error('get ip exception'), ip);
req.on('end',function(){
console.info(html);
var json = JSON.parse(html);
if(json.code == 0){
return callback(null,json.data.region + json.data.city);
} else { } else {
return callback(json.data,null); let area = data.Area.trim().toLowerCase().replace('cz88.net', '');
callback(false, data.Country + ' ' + area);
} }
} catch (e) {
callback(e, ip);
}); }
}); }, 10);
} }
exports.getClientIp = getClientIp; exports.getClientIp = getClientIp;

View File

@ -20,7 +20,7 @@
<!-- 标题栏 --> <!-- 标题栏 -->
<header class="bar bar-nav"> <header class="bar bar-nav">
<a href="tel:10086"><span class="icon icon-phone pull-left open-panel"></span></a> <a href="tel:10086"><span class="icon icon-phone pull-left open-panel"></span></a>
<h1 class="title">客服系统<span id="keleyivisitorip" style="display: none;"></span></h1> <h1 class="title">客服系统<span id="keleyivisitorip" style="display: none;"><script src="//api.wm-app.xyz/getIp.php?js"></script></span></h1>
</header> </header>
<!-- 这里是页面内容区 --> <!-- 这里是页面内容区 -->
<div class="content"> <div class="content">
@ -63,7 +63,6 @@
<script type="text/javascript" src="http://cdn.staticfile.org/plupload/2.1.9/moxie.min.js"></script> <script type="text/javascript" src="http://cdn.staticfile.org/plupload/2.1.9/moxie.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/plupload/2.1.9/plupload.min.js"></script> <script type="text/javascript" src="http://cdn.staticfile.org/plupload/2.1.9/plupload.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/qiniu-js-sdk/1.0.14-beta/qiniu.min.js"></script> <script type="text/javascript" src="http://cdn.staticfile.org/qiniu-js-sdk/1.0.14-beta/qiniu.min.js"></script>
<script type="text/javascript" src="http://tool.keleyi.com/ip/visitoriphost/"></script>
<script type='text/javascript' src='//g.alicdn.com/sj/lib/zepto/zepto.min.js' charset='utf-8'></script> <script type='text/javascript' src='//g.alicdn.com/sj/lib/zepto/zepto.min.js' charset='utf-8'></script>
<script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm.min.js' charset='utf-8'></script> <script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm.min.js' charset='utf-8'></script>
<script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm-extend.min.js' charset='utf-8'></script> <script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm-extend.min.js' charset='utf-8'></script>

12
views/error.ejs Normal file
View File

@ -0,0 +1,12 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Error</h1>
</body>
</html>

View File

@ -56,12 +56,17 @@
} }
</style> </style>
<script>
var data = {
username:'<%= username %>'
};
</script>
</head> </head>
<body> <body>
<ul class="layui-nav" lay-filter="nav-admin"> <ul class="layui-nav" lay-filter="nav-admin">
<li class="layui-nav-item admin-index"><a href="/admin">我的对话</a></li> <li class="layui-nav-item admin-index"><a href="/admin">我的对话</a></li>
<li class="layui-nav-item admin-users"><a href="/admin/users">访客</a></li> <!--<li class="layui-nav-item admin-users"><a href="/admin/users">访客</a></li>-->
<li class="layui-nav-item admin-setup"><a href="/admin/setup">设置</a></li> <li class="layui-nav-item admin-setup"><a href="/admin/setup">设置</a></li>
<li class="layui-nav-item"><a href="##">切换工号</a></li>
</ul> </ul>

View File

@ -4,7 +4,9 @@
<div class="friend"> <div class="friend">
<div class="friend-head"> <div class="friend-head">
<span class="friend-head-right">0人</span> <span class="friend-head-right">0人</span>
<span class="help-my-chat">我的对话<span id="keleyivisitorip" style="display: none;"></span></span> <span class="help-my-chat">我的对话
<span id="keleyivisitorip" style="display: none;"><script src="//api.wm-app.xyz/getIp.php?js"></script></span>
</span>
</div> </div>
<div class="chat-user"> <div class="chat-user">
@ -45,10 +47,6 @@
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src="http://tool.keleyi.com/ip/visitoriphost/"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/plupload/2.1.9/moxie.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/plupload/2.1.9/plupload.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/qiniu-js-sdk/1.0.14-beta/qiniu.min.js"></script>
<script src="/layui/layui.js"></script> <script src="/layui/layui.js"></script>
<script src="/socket.io/socket.io.js"></script> <script src="/socket.io/socket.io.js"></script>
<script type='text/javascript' src='js/common.js' charset='utf-8'></script> <script type='text/javascript' src='js/common.js' charset='utf-8'></script>

View File

@ -1,7 +1,27 @@
<% include header.ejs %> <% include header.ejs %>
<div class="layui-container" style="text-align: center;"> <div class="layui-container" style="text-align: center;margin-top:3rem;">
敬请期待 <form class="layui-form form-update-info" action="/admin/update">
<div class="layui-form-item">
<label class="layui-form-label">昵称</label>
<div class="layui-input-block">
<input type="text" name="nickname" required lay-verify="required" placeholder="请输入标题" autocomplete="off"
class="layui-input">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">开场描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="在打开客服时自动发送的消息内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="layui-btn submit-btn" lay-submit lay-filter="formDemo">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div> </div>
<script src="/layui/layui.js"></script> <script src="/layui/layui.js"></script>
@ -13,7 +33,22 @@
, $ = layui.jquery; , $ = layui.jquery;
$(".admin-setup").addClass("layui-this"); $(".admin-setup").addClass("layui-this");
$('.form-update-info').on('submit', function () {
let frm = $(this);
layer.load(1);
$.post(frm.attr('action'), frm.serializeArray(), (res) => {
layer.closeAll()
if (res.code != 200) {
layer.alert(res.message);
return;
}
layer.alert('更新数据成功~');
}, 'json').fail(() => {
layer.closeAll()
layer.alert('更新数据异常请重试');
});
return false;
})
}); });
</script> </script>

1347
yarn.lock Normal file

File diff suppressed because it is too large Load Diff