一、后台管理系统布局模板
1、后台应用设置
因为后台可以认为是一个单独的应用,在express中可以启用子应用这个概念。在app.js中添加:
const backend = express();
//设置后台应用的模板引擎
backend.set('view engine', 'html');
//设置后台模板的位置
backend.set('views', path.join(__dirname, 'views', 'console'));
//设置后台模板布局模板及碎片(组件)模板位置
backend.engine('html', handlebars({extname:'.html', layoutsDir : 'views/console/layouts', partialsDir:'views/console/partials'}));
//使用中间件指定到backend应用(即当请求为console时,为后台应用)
app.use('/console', backend);
//添加后台的路由
router(app, backend);
2、路由设置:
把libs/router.js中修改为:
const index = require('../routes/index.js');
const consoleIndex = require('../routes/console/index.js');
module.exports = function(app, backend){
//前台的路由分发
app.get('/', index);
//后台的路由分发
backend.get('/', consoleIndex);
};
3、后台首页控制器:
在文件routes/console/index.js中写入:
const index = {};
//定义index模块的index方法
index.index = function(req, res){
res.render('index/index', {title:'控制台'});
};
module.exports = index;
注:
- 因为在入口文件中指定了后台模板的文件位置,所以在赋值给模板的时候不用带console,所以当前模板为views/console/index/index.html
- module.exports表示导出的模块
4、碎片模板(组件模板):
一些公共模板,如公共头部和公共底部等,很多模板引擎中可以使用include包含。
如views/console/partials/head.html
,放公共的css文件
<meta charset="utf-8">
<link href="/static/css/bootstrap/boostrap.css" rel="stylesheet">
<link href="/static/css/layout.css">
如views/console/partials/foot.html
,放公共的js文件
<script type="/static/js/jquery.min.js"></script>
<script type="/static/js/bootstrap.min.js"></script>
5、布局模板
文件为views/console/layouts/layout.html
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
{{> head}}
<link href="/static/css/compiled/index.css" rel="stylesheet">
</head>
<body>
<div class="navbar">公共头部内容</div>
<div class="sidebar">左侧导航</div>
<div class="content">
{{{body}}}
</div>
</body>
</html>
- {{> head}}表示包含head这个碎片文件(类似于其他模板中的include)
- {{{body}}}表示具体模板继承中被替换的部分的内容,为啥是{{{呢,因为{{}}不能包含html,而
{{{
可以包含html
二、数据库操作封装
1、数据库操作需要依赖node的mysql模块:
npm install mysql --save
2、使用node操作数据库的ORM模块Sequelize:(当然也可以不使用,直接使用mysql模块)
npm install sequelize --save
sequelize官方文档地址:http://docs.sequelizejs.com/en/v3/
3、定义应用配置。
在libs/conf.js中定义配置
module.exports = {
database : {
host : 'localhost',
port : 3306,
dbname : 'node_blog',
user : 'shixinke',
password : 'nodeblog',
charset : 'utf8',
pool : {
max : 5,
min : 0,
idle : 10000
}
}
};
4、建立自定义数据库操作基类db.js
文件为libs/db.js:
const db = {};
const Sequelize = require('sequelize');
const conf = require('./conf.js').database;
db.Sequelize = Sequelize;
db.db = new Sequelize(conf.dbname, conf.user, conf.password, {host : conf.host, port : conf.port, pool : conf.pool});
module.exports = db;
注:
- db.Sequelize为Sequelize这个模块(后面定义模型的时候需要用到)
- db.db为数据库操作对象
三、登录功能实现
1、定义用户模型:
数据库表结构:
CREATE TABLE `blog_user` (
`uid` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`account` char(20) NOT NULL COMMENT '账号',
`password` char(32) NOT NULL COMMENT '密码',
`email` varchar(30) NOT NULL COMMENT '邮箱',
`nickname` varchar(30) NOT NULL DEFAULT '' COMMENT '昵称',
`avatar` varchar(200) NOT NULL DEFAULT '' COMMENT '头像',
`status` enum('ENABLE','DISABLED','UNCHECKED','LOCKED') NOT NULL DEFAULT 'UNCHECKED' COMMENT '状态',
`last_login_time` timestamp NULL DEFAULT NULL COMMENT '上次登录时间',
`last_login_ip` char(16) DEFAULT NULL,
`create_time` timestamp NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`uid`),
UNIQUE KEY `idx_account` (`account`),
KEY `idx_nickname` (`nickname`),
KEY `idx_email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=200001 DEFAULT CHARSET=utf8 COMMENT='用户表';
文件为models/user.js
const userModel = {};
//加载数据库操作类
const db = require('../libs/db.js');
//定义表模型(要定义字段名及其数据类型)
const User = db.db.define('user', {
uid:db.Sequelize.INTEGER,
account: db.Sequelize.STRING,
password: db.Sequelize.STRING,
nickname: db.Sequelize.STRING,
email: db.Sequelize.STRING,
avatar: db.Sequelize.STRING,
status: db.Sequelize.STRING,
last_login_time: db.Sequelize.DATE,
last_login_ip: db.Sequelize.STRING,
create_time: db.Sequelize.DATE
}, {
tableName:'blog_user',
timestamps:true,
createdAt : 'create_time',
updateAt : false,
deletedAt : false
});
userModel.checkLogin = async function(account, password){
let result = await User.findOne({where:{account : account}});
if (result && result.uid) {
if (result.password != password) {
return {error : '密码不正确', data : false};
}
if (result.status != 'ENABLED') {
return {error : '该账号尚未启用', data : false};
}
return {error : null, data : result};
} else {
return {error : '该账号不存在', data : false};
}
};
module.exports = userModel;
注:
- sequelize.define()一般有三个参数,第一个参数为模型名,第二个参数为各字段定义及字段类型定义的对象,第三个参数是表相关的属性
- tableNmae表示数据库表的名称
- timestamps指定插入、更新和删除的时候是否记录操作时间
- createdAt表示记录插入时间的字段名(如果不指定或不设置为false,默认为createdAt)
- updatedAt表示修改插入的字段名(如果不指定或不设置为false,默认为updatedAt)
- deletedAt表示删除插入的字段名(如果不指定或不设置为false,默认为deletedAt)
特别提醒:
- async和await是node.js7的关键词,并且要启用才可以,在启动时加上:node —harmony-async-await app.js。
- aysnc和await是成对出现的,在函数内部调用await则,函数必须声明为async
2、在入口文件中添加必要中间件
(1)添加bodyParser中间件
bodyParser用于解析客户端请求的body中的内容,内部使用JSON编码处理,url编码处理以及对于文件的上传处理.
安装body-parser
npm install body-parser --save
在入口文件中启用body-parser中间件
const bodyParser = require('body-parser');
//解析json请求数据
app.use(bodyParser.json());
//解析urlencoded过的数据
app.use(bodyParser.urlencoded({extened : true}));
(2)添加session中间件
安装express-session中间件
npm install express-session
在入口文件中加入session
const session = require('express-session');
app.use(session({secret : 'shixinke', resave : true, saveUninitialized : false, cookie : {secure : false}}));
- secret:表示用于cookie加密的字符串
- resave是指每次请求都重新设置session cookie
- saveUninitialized: 是指无论有没有session cookie,每次请求都设置个session cookie
- cookie设置cookie相关的信息(secure:false表示不是https应用)
3、添加一些公共的函数
在文件libs/helper.js中:
(1)md5加密函数:
const conf = require('./conf.js');
const crypto = require('crypto');
helper.md5 = function(str, saltKey){
let salt;
salt = saltKey ? saltKey : conf.security.salt;
let md5sum = crypto.createHash('md5');
md5sum.update(str+salt);
str = md5sum.digest('hex');
return str;
};
(2)判断某个元素是否在数组中:
helper.inArray = function(ele, arr){
let isArr = false;
if (arr && typeof arr == 'object' && arr.constructor === Array) {
isArr = true;
}
if (!isArr) {
return false;
}
let len = arr.length;
for(let i=0;i < len; i++){
if(ele == arr[i] ){
return true;
}
}
return false;
};
4、在路由(控制器)中实现登录
文件为routes/console/login.js
const login = {};
const userModel = require('../../models/user.js');
const helper = require('../../libs/helper.js');
login.index = function(req, res){
res.render('login/index', {title : '管理登录', layout : false});
};
login.checkLogin = async function(req, res){
let result = {};
//接收请求参数
let account = req.body.account;
let password = req.body.password;
//简单判断参数的合法性
if (!account || account == '') {
res.json({code : 5001, message : '请输入账号'});
}
if (!password || password == '') {
res.json({code : 5001, message : '请输入密码'});
}
password = helper.md5(password);
try {
result = await userModel.checkLogin(account, password);
} catch (e) {
result.error = '未找到数据';
result.data = false;
}
if (result.error) {
res.json({code : 5003, message : result.error});
} else {
let userInfo = {};
userInfo = result.data.dataValues;
//存入到session中
req.session.user = userInfo;
res.json(200, message : '登录成功', data:{url : '/console/index'}});
}
};
5、登录权限控制判断
如果在每个后台路由中判断是否登录的话,可能功能有点重复,现在可以使用中间件来实现,不过这种方式是否合理,还值得商榷。
(1)在配置中增加一个是不需要判断登录的路径,这些路径不用判断是事登录
module.exports = {
security : {
withoutLogin : ['/login', '/register', '/login/checkLogin']
}
}
(2)在入口文件中判断中,加入登录权限控制
backend.use(function(req, res, next){
//获取session信息
let userInfo = req.session.user;
//判断session是否存在
if ((!userInfo || !userInfo.uid) && !helper.inArray(req.path, conf.security.withoutLogin)) {
res.redirect('/console/index');
} else {
next();
}
});
app.use('/console', backend);
6、成果展示: