后台管理系统,简单来说就是对各种数据进行维护的一个平台或者说是CRUD也行。因为涉及到数据,所以保护数据安全就显得尤为重要。下面就来简单介绍下我之前做的一个后台管理系统的实现过程中遇到的问题及关键点做一下总结。
一、架构设计
开发模式:前后端分离(rmvc) 前端(FE): RMVC , 后端:RM (V) C
技术栈:es6 + webpack+jquery +Nodejs + Express + MongoDB + handlebar
二、搭建框架
使用第三方的ui库,根据业务功能需求做了对应的调整,效果图:
三、用户相关功能技术点
1、用户的数据安全处理
数据都存储在mongodb数据库中,密码存储使用了第三方的插件 crypto-js,对注册时的密码进行了加密存储,保护用户的数据安全,如下图:
当然了,用之前先引入一下(学node的应该很清楚)
加密和解密我提前封装成一个方法,直接调的。
const crypto = require('crypto-js');
对传入的密码进行加密:
ciphertext(pwd){
return crypto.AES.encrypt(pwd, 'secret key 123').toString()
}
用户登录的时候,解密比对:
originalText(newpwd){
return crypto.AES.decrypt(newpwd, 'secret key 123').toString(crypto.enc.Utf8);
}
2、维持用户的登录状态(就是当用户登录的时候,刷新界面用户依然保持登录状态)
为了实现这个效果,使用了第三方的中间件 cookie-session辅助。它是以cookie为基础的session中间件。具体的使用流程如下:
用户注册————>如果注册成功,添加req.session.username=注册用户名;(这里username只是一个属性名,自己定即可)
用户登录————>如果登录成功,添加req.session.username=登录用户名;
这里还需要定义一个方法,用来判断是否有req.session.username这个属性,假设叫做islogin
当用户登录后去刷新页面,如果islogin返回的结果code为1(这个code是后台返回的一个标识,表示存在req.session.username),同时req.session.username也会传回来,拿到这个数据,后面只需要重新渲染登录显示区域的页面,即可实现维护登录的一个状态。
四、职位管理相关技术点
职位管理主要的功能就是CRDU,都是通过调用后台提前设计好的接口进行操作的,这里主要说一下翻页和职位信息添加两个功能。
1 后端实现翻页功能
平常我们实现一个翻页可能采用的是前端页面实现,这样有一些问题,每次你都需要提前将所有的数据都需要通过ajax请求回来,然后在去做分页,如果数据量一旦非常大,将会很卡(半天加载不出来);为了规避这个问题,采用了后端分页,每次只请求每一页需要展示的数据量。具体实现如下:
前端在点击页码的时候需要传递几个信息(keyword关键字,pagesize每页展示信息数量,pageno页码,这里采用的是get请求),后端接收到这个信息,通过req.query解析出来,根据这个数据去数据库查询,返回查询的数据,渲染到页面,这样整个界面就显得比较流畅。
2 职位信息的添加
在添加过程中由于存在图片信息,不能像处理普通数据一样,直接修改,通过post提交到后台处理。这里有个关键点,需要给form表单添加一个属性
enctype=“multipart/form-data”;由于需要处理图片信息,在后台使用了multer中间件,其它信息的存储就没啥问题了
五、安全性
1 如何预防csrf(跨站请求伪造)攻击
这里使用了第三方插件jsonwebtoken ,简单来说就是:在用户登录的时候,后台生成一个token,向header里面添加一个自定义的字段并带上它,用户登录后取出这个token,在responseHeader中,后面每次请求的时候带到后台,在后台进行比对,如何没问题就能正常访问,如果不匹配,就返回一个401(非法访问)
这里简单描述下:
流程:
1 后端通过res.set(‘X-ACCESS-TOKEN’, token) 设置token到响应头中
2 前端登录的时候通过下面的方式取出token值
let token = rs.xhr.getResponseHeader("x-access-token")取出token,
3 将token值存到localStorage中
if (token) {
localStorage.setItem('token', token);
}
4 前端入口文件设置一个全局的ajax参数,在请求之前,将这个token绑定到请求中
$.ajaxSetup({
beforeSend(xhr, setting) {
let token = localStorage.getItem('token');
xhr.setRequestHeader('x-access-token', token);
}
})
5 后端新建一个中间件,用来处理比对工作
let token = req.get('x-access-token');来接收token
加密:用到的插件 jsonwebtoken
_createJWT(username) {
//非对称
let privateKey = fs.readFileSync(path.resolve(__dirname, '../key/rsa_private_key.pem'))
let token = jwt.sign(username, privateKey, { algorithm: 'RS256' });
//对称
// let token = jwt.sign(username, 'lagouadmin')
// console.log('token:', token);
return token;
}
//得到token
let token = this._createJWT(user["username"])
解密:
//对称
// let rs = jwt.verify(token, 'lagouadmin')
//非对称
let publicKey = fs.readFileSync(path.resolve(__dirname, '../key/rsa_public_key.pem'));
let rs = jwt.verify(token, publicKey, { algorithm: 'RS256' })
2 未登录用户不予查看站内的职位信息
在点击职位管理功能选项时,会进行一个后台校对,查看是否存在req.session.username这个属性,如果存在说明用户已经登录就可以正常查看,否则说明未登录,返回401非法访问的提示,采用的是中间件,所以不会继续执行下面的代码了
const jwt = require('jsonwebtoken');
function auth(req, res, next) {
if (req.session.username) {
next();
}else{
res.send({
code: 401,
message: "非法访问"
})
}
}
module.exports = auth;
六、实时通信
本系统采用了socket.io实时通信技术,主要是使用较为方便,能够自动重连,基本支持所有的浏览器。
使用它的主要目的是为了将用户操作过程的信息进行一个记录,比如职位信息更新或者添加职位成功后,会自动向页面发送一条信息,效果如下:
主要代码实现:
后台的www文件
var server = http.createServer(app);
var io= require('socket.io')(server);
// 设置一个全局的io属性,方便跨页面访问
global.socketio = io;
// 连接状态
io.on('connection',function(client){
console.log('connection.......')
})
发送信息使用emit
socketio.emit('message',{
code:2,
message:"职位信息添加成功"
})
前端页面订阅消息及连接后台服务
startSocket() {
let i=0;
let html='';
// 服务器的地址
var socket = io("http://localhost:5300");
socket.on('connect', function () {
console.log('connect ...')
})
// 订阅消息
socket.on('message', function (msg) {
// 做一些界面的渲染效果
i++;
$('#posNum').html(i);
$('#pos-header').html(`你有${i}条新消息`);
html += msg.message+'<br>';
$('#posUpdatamsg').html(html);
})
}