2.2、 新增用户
2.2.1、为用户列表页面的新增用户按钮添加链接
打开 views-admin 目录下的 user.art 文件,给新增用户按钮添加连接:
<a href="/admin/user-edit" class="btn btn-primary new">新增用户</a>
2.2.2、添加一个连接对应的路由,在路由处理函数中渲染新增用户模板
打开 route 目录下 admin.js 文件,新增 user-edit 路由:
// 创建用户编辑页面路由
admin.get('/user-edit', require('./admin/user-edit'));
在 route-admin 目录下新建 user-edit.js 文件:
module.exports = (req, res) => {
res.render('admin/user-edit.art');
}
打开浏览器刷新,点击新增用户,发现:可以跳转到表单页了。
2.2.3、为新增用户表单指定请求地址、请求方式、为表单项添加name属性
打开 views-admin 目录下 user-edit.art 文件:
<form class="form-container" method="post" action="/admin/user-edit">
<input name="username" type="text" class="form-control" placeholder="请输入用户名">
<input name="email" type="email" class="form-control" placeholder="请输入邮箱地址">
<input name="password" type="password" class="form-control" placeholder="请输入密码">
<select name="role" class="form-control">
<option value="normal">普通用户</option>
<option value="admin">超级管理员</option>
</select>
<select name="state" class="form-control">
<option value="0">启用</option>
<option value="1">禁用</option>
</select>
2.2.4、增加实现添加用户的功能路由
打开 route 目录下 admin.js 文件:
// 创建实现添加用户功能
admin.post('/user-edit', require('./admin/user-edit-fn'));
在 route-admin 目录下新建 user-edit-fn.js 文件:
module.exports = (req, res) => {
res.send('ok');
}
回到浏览器刷新表单页面,点击提交按钮,可以看到:
说明路由创建成功了。
2.2.5、接收到客户端传递过来的请求参数
继续编辑 user-edit-fn.js 文件:
module.exports = (req, res) => {
// 输出请求参数
res.send(req.body)
}
刷新浏览器,随便输入一些信息,点击提交。可以看到:接收到的请求参数
项目包含的知识点:Joi
JavaScript 对象的规则描述语言和验证器。实际上就是验证 JavaScript 格式的。
下载安装:
npm install joi
示例代码:
const Joi = require('joi');
// 定义验证规则
const schema = {
username: Joi.string().alphanum().min(3).max(30).required().error(new Error('错误信息')),
password: Joi.string().regex(/^[a-zA-Z0-9](3, 30)$/),
access_token: [Joi.string(), Joi.number()],
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email()
};
// 使用规则验证对象(第1个参数:要验证对象,第2个参数:验证的规则)
Joi.validate({ username: 'abc', birthyear: 1994}, schema);
● alphanum() 表示 username 属性只能是字母字符串或者是数字字符串,不能包含特殊字符(比如_、$等)
● required() 表示 username 属性是必选属性
● error() 方法指定错误信息
● integer() 表示必须是整数
● valid() 表示合法值
在命令行工具中输入:npm install joi 安装第三方模块。
例子:新建 joi.js 文件:
// 引入 joi 模块
const Joi = require('joi');
// 定义对象的验证规则
const schema = {
username = Joi.string().alphanum().min(2).max(5)
};
async function run() {
try {
// 实施验证(第1个参数:要验证对象,第2个参数:验证的规则)
await Joi.validate({ username: 'abc'}, schema);
} catch (err) {
console.log(err.message);
}
console.log('验证通过');
}
run();
在命令行工具中输入:nodemon joi.js ,可以看到:
把 username 改为 a,在看下结果:报错信息
这个英文的错误提示信息,对用户来说不够友好,我们可以自定义错误信息:
username: Joi.string().alphanum().min(2).max(5).error(new Error('username 属性没有通过验证'))
回到命令行工具可以看到:
注意:使用 Joi.validate()实施验证,这个方法返回 promise 对象,可以在后面使用 then 或者 catch 捕获错误信息;也可以通过异步函数的方式来验证对象,但是错误信息要通过 try catch 来捕获。
2.2.6、对请求参数的格式进行验证
打开 user-edit-fn.js 文件,引入 joi 模块:
// 引入 joi 模块
const Joi = require('joi');
// 引入 joi 模块
const Joi = require('joi');
module.exports = async (req, res) => {
// 定义对象的验证规则
const schema = {
username: Joi.string().alphanum().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,30}$/).required().error(new Error('密码格式不符合验证规则')),
role: Joi.string().valid('normal', 'admin').required().error(new Error('角色值非法')),
state: Joi.number().valid(0, 1).required().error(new Error('状态值非法'))
};
try {
// 实施验证
await Joi.validate(req.body, schema);
}catch (err) {
// 验证没有通过
// 重定向回用户添加页面
// res.redirect('/admin/user-edit?message=' + err.message);
res.redirect(`/admin/user-edit?message=${err.message}`);
}
res.send(req.body)
}
打开 route-admin 目录下的 user-edite.js 文件,添加代码:
module.exports = (req, res) => {
const {message} = req.query;
return res.render('admin/user-edit.art', {message: message});
}
在打开 views-admin 目录下的 user-edit.art 文件,把错误信息改为 message :
<p class="tips">{{message}}</p>
回到浏览器,重新登录后添加用户,直接点提交按钮,可以看到错误信息提示:
2.2.7、验证当前要注册的邮箱地址是否已经注册过
打开 route-admin 目录下 user-edit-fn.js 文件,导入用户集合:
// 导入用户集合构造函数
const { User } = require('../../model/user');
// 根据邮箱地址查询用户是否存在
const user = await User.findOne({email: req.body.email})
// 如果用户已经存在,那么邮箱地址已经被占用
if (user) {
return res.redirect(`/admin/user-edit?message=邮箱地址已经被占用`);
}
回到浏览器重新登录,提交已经存在的用户信息,发现结果:
2.2.8、对密码进行加密处理
打开 route-admin 目录下 user-edit-fn.js 文件,导入 bcrypt 模块:
// 导入 bcrypt 模块
const bcrypt = require('bcrypt');
// 对密码进行加密处理
//生成随机字符串
const salt = await bcrypt.genSalt(10);
// 进行加密
const password = await bcrypt.hash(req.body.password, salt);
req.body.password = password;
res.send(req.body.password);
在浏览器重新提交,可以看到结果:说明密码加密成功
2.2.9、将用户信息添加到数据库中
继续编辑 user-edit-fn.js 文件:
// 将新用户信息添加到数据库中
await User.create(req.body);
2.2.10、重定向页面到用户列表页面
继续编辑 user-edit-fn.js 文件:
// 将页面重定向到用户列表页
res.redirect('/admin/user');
回到浏览器,重新添加新用户,成功后跳回到用户列表页。打开 Compass 软件,可以看到:
user-edit-fn.js 文件完整代码:
// 引入 joi 模块
const Joi = require('joi');
// 导入 bcrypt 模块
const bcrypt = require('bcrypt');
// 导入用户集合构造函数
const { User } = require('../../model/user');
module.exports = async (req, res) => {
// 定义对象的验证规则
const schema = {
username: Joi.string().alphanum().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,30}$/).required().error(new Error('密码格式不符合验证规则')),
role: Joi.string().valid('normal', 'admin').required().error(new Error('角色值非法')),
state: Joi.number().valid(0, 1).required().error(new Error('状态值非法'))
};
try {
// 实施验证(第1个参数:要验证对象,第2个参数:验证的规则)
await Joi.validate(req.body, schema);
}catch (err) {
// 验证没有通过
// 重定向回用户添加页面
// res.redirect('/admin/user-edit?message=' + err.message);
return res.redirect(`/admin/user-edit?message=${err.message}`);
}
// 根据邮箱地址查询用户是否存在
const user = await User.findOne({email: req.body.email})
// 如果用户已经存在,那么邮箱地址已经被占用
if (user) {
return res.redirect(`/admin/user-edit?message=邮箱地址已经被占用`);
}
// 对密码进行加密处理
//生成随机字符串
const salt = await bcrypt.genSalt(10);
// 进行加密
const password = await bcrypt.hash(req.body.password, salt);
// 替换密码
req.body.password = password;
// 将新用户信息添加到数据库中
await User.create(req.body);
// 将页面重定向到用户列表页
res.redirect('/admin/user');
}
代码优化1:验证用户信息
分离请求参数验证的代码,在 model 目录下 user.js 文件中都是对用户数据操作相关的代码 ,所以可以放在这里。
user.js 文件
// 引入 joi 模块
const Joi = require('joi');
// 验证用户信息
const validateUser = user => {
// 定义对象的验证规则
const schema = {
username: Joi.string().alphanum().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,30}$/).required().error(new Error('密码格式不符合验证规则')),
role: Joi.string().valid('normal', 'admin').required().error(new Error('角色值非法')),
state: Joi.number().valid(0, 1).required().error(new Error('状态值非法'))
};
// 实施验证(第1个参数:要验证对象,第2个参数:验证的规则)
return Joi.validate(user, schema);
}
// 将用户信息集合作为模块成员进行导出
module.exports = {
User,
validateUser
}
回到 user-edit-fn.js 中引用:
// 导入用户集合及验证用户
const { User, validateUser } = require('../../model/user');
module.exports = async (req, res) => {
try {
await validateUser(req.body);
}
。。。
回到浏览器中验证下,ok,没问题,功能都正常。
代码优化2:错误处理
错误处理中间件是一个集中处理错误的地方。
打开 app.js 文件,定义错误处理中间件,把重定向的代码粘过来:
// 错误处理中间件
app.use((err, req, res, next) => {
res.redirect(`/admin/user-edit?message=${err.message}`);
})
但是这里的路由和参数不可能写死,所以在 user-edit-fn.js 中添加 next:
module.exports = async (req, res, next) => {
try {
await validateUser(req.body);
}catch (err) {
// 验证没有通过
// 重定向回用户添加页面
// return res.redirect(`/admin/user-edit?message=${err.message}`);
return next()
}
next() 只能传递一个参数,并且是字符串类型,而我们需要传递两个参数。所以需要传递一个参数,用对象的形式,然后再转换为字符串类型。JSON.stringify() 将对象数据类型转换为字符串数据类型
所以要修改为:
try {
await validateUser(req.body);
}catch (err) {
// 验证没有通过
// 重定向回用户添加页面
// return res.redirect(`/admin/user-edit?message=${err.message}`);
// JSON.stringify() 将对象数据类型转换为字符串数据类型
return next(JSON.stringify({path: '/admin/user-edit', message: err.message}))
}
回到 app.js 文件修改错误处理中间件代码:
// 错误处理中间件
app.use((err, req, res, next) => {
// 框字符串类型转换为对象类型 JSON.parse()
const result = JSON.parse(err);
res.redirect(`${result.path}?message=${result.message}`);
})
回到浏览器中验证下:错误提示没问题
下面把 user-edit-fn.js 文件中的另一个错误处理信息也修改下:
if (user) {
// return res.redirect(`/admin/user-edit?message=邮箱地址已经被占用`);
return next(JSON.stringify({path: '/admin/user-edit', message: '邮箱地址已经被占用'}))
}
2.3、 用户列表
2.3.1、访问用户列表页时,在对应的路由处理函数中,将所有的用户信息从数据库总查询出来。
打开 route-admin 目录下的 userPage.js 文件:
// 导入用户集合构造函数
const { User } = require('../../model/user');
module.exports = async (req, res) => {
// 将用户信息从数据库中查询处理
let users = await User.find({})
res.render('admin/user.art', { users: users })
}
2.3.2、将查询中来的数据渲染到用户列表中
打开 views-admin 目录下的 user.art 文件:
<tbody>
{{each users}}
<tr>
<!-- @表示原文输出 -->
<td>{{@$value._id}}</td>
<td>{{$value.username}}</td>
<td>{{$value.email}}</td>
<td>{{$value.role == 'admin' ? '超级管理员' : '普通用户'}}</td>
<td>{{$value.state == 0 ? '启用' : '禁用'}}</td>
<td>
<a href="user-edit.html" class="glyphicon glyphicon-edit"></a>
<i class="glyphicon glyphicon-remove" data-toggle="modal" data-target=".confirm-modal"></i>
</td>
</tr>
{{/each}}
</tbody>
在浏览器刷新页面,可以看到:用户列表
还有找到1个用户,这里要改为动态数据:
<span>找到{{users.length}}个用户</span>
刷新浏览器: