通过process.env.环境变量名 来读取这个系统变量名的值
例如:我们添加环境变量NODE_ENV值为development来表示开发环境
if(process.env.NODE_ENV=='development'){
console.log('开发环境');
}else{
console.log('生产环境');
}
不使用 config 来配置数据库的连接 ,密码容易暴露,不安全
mongoose.connect('mongodb://xibing_G:xibing@localhost:27017/blog', { useNewUrlParser: true, useUnifiedTopology: true })
第三方模块:config 用于根据环境(开发环境or生产环境)来切换不同的代码
插件要求:项目根目录下创建config文件夹,并在内部建立以下文件:
development.json:如果是开发环境,将会执行这里面的,参数也将从这里获取
production.json:如果是生产环境,将会执行这里面的,参数将从这个文件中获取
default.json:对应的开发or生产环境文件中没有所需数据,将会来这里查询获取,默认环境
custom-environment-variables.json:在这个文件中映射环境变量和应用配置的关系
【文件名一定不能有错误,不然就会有错误或者config功能使用无效】
使用模块内部提供的get方法获取配置信息,运行时config会按名称进行查找,读取其值
例如:
建立系统变量APP_PWD变量值为123456(将数据库的连接密码写到了系统变量中,相对安全)
custom-environment-variables.json代码:会根据APP_PASSWORD查询系统变量对应的值给pwd
{
"db":{
"pwd":"APP_PASSWORD"
}
}
development.json代码:
{
"title":"博客管理系统-开发环境",
"db":{
"user":"xibing_G",
"host":"localhost",
"port":"27017",
"name":"blog"
}
}
使用config来配置数据库的连接(connect.js代码)
// 连接数据库,引入mongoose第三方模块
const mongoose = require('mongoose');
// 导入config模块,获取config的json文件中所配置的信息
const config=require('config');
// 避免一个警告的提示
mongoose.set('useCreateIndex', true);
// 连接数据库
mongoose.connect(`mongodb://${config.get('db.user')}:${config.get('db.pwd')}@${config.get('db.host')}:${config.get('db.port')}/${config.get('db.name')}`, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('数据库连接成功'))
.catch((err) => console.log('数据库连接失败' + err))
第三方模块:morgan 是express模块的中间件函数,来获取客户端向服务器端发送的请求信息
包括:请求方式、请求地址、请求状态码、请求时间等
morgan()函数可以将获取到的请求信息在控制台中输出【morgan('dev'),参数dev为固定写法】
const morgan=require('morgan');
app.use(morgan('dev'));
配置session 设置cookie的缓存有效时间
session中间的两个参数如果不填写会有警告提示,但也算不上报错
app.use(session({resave: false, //添加 resave 选项
saveUninitialized: false,
//添加 saveUninitialized,当用户没有登陆的时候不执行默认的保存cookie的行为
secret:'secret key',
// 设置cookie的过期时间,单位是毫秒,下面限制为1天的写法
cookie:{
maxAge:24*60*60*1000
}
}));
处理 post 请求参数(非json类型的)
false表示使用querystring处理请求参数(官方推荐);true表示使用第三方qs模块处理
app.use(bodyParser.urlencoded({extended:false}));
配置 express 模板
这里的path是引用了系统模块path,目的使用其join来做路径拼接
// 告诉express框架模板所在位置(views为固定参数)
app.set('views', path.join(__dirname, 'views'));
// 告诉express框架模板的默认后缀(view engine为固定参数)
app.set('view engine', 'art');
// 告诉express框架渲染后缀为art时,所用的模板引擎是什么
app.engine('art', require('express-art-template'));
// 开放静态资源文件
app.use(express.static(path.join(__dirname, 'public')))
配置 art-template 模板引擎的全局时间日期格式化
借助第三方模块:dateformat
// 导入art-template模板引擎
const template=require('art-template');
// 导入dateformat第三方模块来对全局进行时间格式化
const dateFormat=require('dateformat');
// 向模板内部导入dateFormat变量,便于全局配置时间格式
template.defaults.imports.dateFormat=dateFormat;
配置完成后,由于是全局,所以所有的模板文件中都可以使用dataFormat来格式化日期/时间
dateFormat(date,'yyyy-mm-dd');
两个参数:1、日期对象;2、目标格式 ;返回格式化后的日期
公共数据为避免每次从数据库中进行查询的麻烦,可以存放到 express实例的 locals 下
let user=await User.findOne({email:email});
req.app.locals.userInfo=user;
在添加用户的信息时使用第三方模块 Joi 进行数据格式的验证
// 验证用户信息
const validateUser=(user)=>{
// 建立Joi验证对象(数据库有哪些字段需要验证就填写哪些验证名,并在其后编写验证规则)
const schema=Joi.object({
username:Joi.string().min(2).max(12).required().error(new Error('用户名不符合规则')),
email:Joi.string().email().required().error(new Error('邮箱不符合规则')),
password:Joi.string().regex(/^[a-zA-Z0-9]{3,20}$/).required().error(new Error('密码不符合验证规则')),
role:Joi.string().valid('normal','admin').required().error(new Error('角色不符合验证规则')),
//只能传入这两个值中的数据,其他数据不通过,类似于枚举
state:Joi.number().valid(0,1).required().error(new Error('状态值非法'))
});
// 建立的Joi验证对象下会有validateAsync()来对传入的数据进行验证,return返回这个验证结果
return schema.validateAsync(user);
}
添加用户时调用验证方法 validateUser()
// 实施验证
try{
// user信息通过post请求方式传递,采用req.body来获取参数(body功能源于'body-parser'模块)
await validateUser(req.body);
}catch(err){
// 验证没有通过那重定向到添加用户页面
return next(JSON.stringify({path:'/admin/user-edit',message:err.message}));
}
对于 next() 错误处理中间件 的完善 借助 JSON.parse 和 JSON.stringify
(验证用户信息出错)完善前:
return res.next(`/admin/user-edit?message=${err.message}`);
(错误处理中间件)完善前:
// 错误处理中间件
app.use((err,req,res,next)=>{
// 将字符串再转回对象JSON.parse()
const result = JSON.parse(err);
res.redirect(`${result.path}?message=${result.message}`);
})
( 验证用户信息出错)完善后:
// next方法只能传递一个参数,并且是字符串类型
// 由于重定向语句用到了 重定向地址 和 携带参数 这两个变化的量
// 所以需要传递两个参数,将两个参数放到对象中,再将对象转为字符串传递到next中
return next(JSON.stringify({path:'/admin/user-edit',message:err.message}));
(错误处理中间件)完善后:
// 错误处理中间件
app.use((err,req,res,next)=>{
// 将字符串再转回对象JSON.parse()
const result = JSON.parse(err);
// 对重定向代码进行改进,比如在user-modify中除了path、message还有传入id
// path是必要的,但是后面的参数需要灵活变动
// 新建数组用于存放后面的参数
let params=[];
for(let attr in result){
// 又因为path在result中,所以我们把path进行刨除
if(attr!=path){
//attr+'='+result.[attr];相当于message='密码比对失败'
params.push(attr+'='+result[attr]);
}
}
// 多个参数中间需要用&隔开,所以借助数组的join方法,对每一项都用&进行分隔连接
res.redirect(`${result.path}?${params.join('&')}`);
})
密码加密 第三方模块 bcryptjs (相比bcrypt功能相同,还不用下载安装一些依赖)
// 引入加密模块
const bcrypt=require('bcryptjs');
// 对密码进行加密,生成随机字符串,默认是10,越高复杂度越高
const salt=await bcrypt.genSalt(10);
// 把加密后的密码赋值给password
const password=await bcrypt.hash(req.body.password,salt);
// 用加密后的密码对请求中的密码进行覆盖
req.body.password=password;
// 将用户信息添加到数据库中(引入User集合,使用create对用户进行创建)
await User.create(req.body);
bycryptjs的compare加密比对 由于插入数据库中的时候的密码是被加密后的,
所以当用户登陆时还需要借助bcryptjs对用户输入的密码和数据库中的密码进行比对
const isValid=await bcrypt.compare(password,user.password);
// 参数1:用户输入的密码,参数2:数据库中的被加密的密码,返回值为布尔值
登陆拦截
拦截1:还没有登陆的用户直接跳过登陆页面去访问其他地址(路由),重定向到登陆页面
拦截2:普通用户知道管理员的管理页面地址,登陆后修改请求地址访问管理页面
重定向到普通用户的首页
// 登陆拦截
const guard= (req,res,next)=>{
// 如果请求地址不是login并且用户名没有被定义
if(req.url!='/login' && !req.session.username){
res.redirect('/admin/login');
}else{
// 说明用户名已被定义,也就表明是登陆状态
if(req.session.role=='normal'){
// 如果是普通用户就阻止程序向下执行
return res.redirect('/home/');
}
// 如果中间没有被拦截,就说明既登陆了也不是普通用户,那就是管理员,放行路由
next();
}
}
// 开放guard供引用本文件的地方调用
module.exports=guard;
拦截的调用(放在项目的入口文件中,匹配路由之前,一定要在匹配路由前做出拦截)
use匹配到一个路由后,若没有next将终止匹配,并做出响应,所以要放置匹配其他路由之前
// 在路径匹配之前做拦截,判断用户的登陆状态
// 如果还没有登陆就直接请求到用户列表页面,用户列表中请求的session的userInfo中的username将呈现没有被定义的状态,也就会报错
// 所以先来拦截路径,如果是登陆状态再放行,也就使用到了中间件,写到了中间件文件中,这里做了引用
app.use('/admin',require('./middleware/loginGuard'));
用于 退出 的同步功能(删除session、清空模板中的用户信息、重定向到登陆页面)
module.exports=(req,res)=>{
// 删除session
req.session.destroy(function(){
// 删除cookie
res.clearCookie('connect.sid');
// 清除模板中的用户信息
req.app.locals.userInfo=null;
// 重定向到用户登陆页面
res.redirect('/admin/login');
});
};