这篇文章中有些内容像字段校验,项目结构等部分在另一篇文章中有写到:Node.js: express + MySQL的使用_掉头发类型的选手的博客-CSDN博客
1,注册需要将新用户的账号和密码写入数据库,账号可以直接写入数据库,但密码一般不会直接存入到数据库中,会将密码加密后存入数据库中,能够提高账号的安全性。
2,在登录的时候再将密码通过同样的方式进行加密,与数据库中的存储的密码进行比对,相同的话则登录成功。
3,实现
(1),先在数据库中创建一个表用来存储信息,我创建的这个表名是 user_login
(2),密码加密用到一个包 bcryptjs ,这个包可以对密码进行加密,用npm将它安装到项目中。
(3),目录结构,在路由模块,处理函数模块,字段校验模块下都新建一个文件,用来完成这部分内容。
app.js
// 引入express
const express = require("express");
// 创建服务器的实例对象
const app = express();
// 引入校验规则的包,在定义错误级别的中间件时会用到
const joi = require("joi");
// 配置解析表单数据的中间件 内置中间件,只能解析application/x-www-form-urlencoded格式的数据
app.use(express.urlencoded({ extended: false }));
// 封装res.send() ,必须在路由之前封装
/**
* 不管是输出正确的数据还是错误的信息,每次都需要写这么多东西
* res.send({ state: 0, message: "查询成功", data: result })
* 封装之后不需要写这么多
*/
app.use((req, res, next) => {
// 定义一个输出的函数
res.output = function (err, status = 1, data) {
res.send({
status,
message: err instanceof Error ? err.message : err,
data,
});
};
next();
});
// 导入并使用登录,注册的路由模块
const loginRouter = require("./router/login");
app.use(loginRouter);
// 导入并使用路由模块
const inforRouter = require("./router/userInfor");
app.use(inforRouter);
// 在所有路由下面调用错误级别的中间件
app.use((err, req, res, next) => {
// 验证失败导致的错误
if (err instanceof joi.ValidationError) return res.output(err);
// // 身份认证失败后的错误
// if(err.name === 'UnauthorizedError') return res.cc('身份认证失败!')
// 未知的错误
res.output(err);
next();
});
// 启动服务器
app.listen(3007, () => {
console.log("Server running at http://127.0.0.1:3007");
});
因为app.js是整个项目的入口文件,所有所有的路由都应该在这个文件中导入并使用。
router文件夹下的login.js文件
const express = require("express");
const router = express.Router();
// 导入校验规则的中间价
const expressJoi = require("@escook/express-joi");
// 引入规则
const { login_rules } = require("../schema/login");
// 引入处理函数
const login_handler = require("../router_handler/login");
// 注册
router.post("/register", expressJoi(login_rules), login_handler.userRegister);
// 将路由导出
module.exports = router;
和上一篇内容一样,这个文件中注册路由,并从router_handler中导入处理函数,导入校验规则的中间件和规则,最后将路由导出,在app.js文件中使用。
router_handler文件夹下的login.js文件 (处理函数)
// 导入数据库模块
const db = require("../db/index");
// 引入对密码进行加密的包
const bcryptjs = require("bcryptjs");
// 注册处理函数并导出
exports.userRegister = (req, res) => {
// 先取到从客户端传来的值
let { username, password } = req.body;
/**
* 注册的时候用户名是唯一的,不能与其他人的用户名一样
* 在将信息写入数据库之前,要先检查用户名是否被占用
*/
// 查询有无相同的用户名
const sql = "select username from user_login where username=?";
// 执行查找sql语句
db.query(sql, username, (err, results) => {
// sql语句执行失败
if (err) return res.output(err);
// 执行成功,如果有数据,则说明有相同的用户名
if (results.length === 1) return res.output("用户名被占用");
// 执行成功,用户名没被占用
console.log('加密之前', password);
// 对密码进行加密,第二个参数可以提高密码的安全性,几也行
password = bcryptjs.hashSync(password, 10);
console.log('加密过后', password);
// 定义新语句,增加用户
const sqlStr = "insert into user_login set ?";
// 执行增加sql语句
db.query(sqlStr, { username, password }, (err, results) => {
// 执行sql语句失败
if (err) return res.output(err);
// 执行sql语句成功 但影响行数不为1
if (results.affectedRows !== 1) return res.output("用户注册失败!");
// 执行成功,影响行数为1
res.output("注册成功!");
});
});
};
(1),先获取到从客户端传来要注册的用户名和密码,解构用户名和密码的时候要用let,因为在下面的操作中需要给密码加密,修改了变量。用const,重新定义一个值也行。
(2),在注册的过程中分为三步,第一步先将用户名在数据库中查询有没有相同的值,用户名不能相同,存在相同的话返回用户名被占用。
(3),如果没有相同的用户名和密码,则可以使用,将用户设定的密码进行加密,使用bcryptjs包进行加密。使用hashSync()方法,第一个参数是原密码,第二个参数可以提高密码的安全性,是一个数字。
(4),加密过后可以在终端输出查看加密后的密码,可以将数据存储到数据库中,使用insert语句,若数据库语句执行成功,但影响行数不为1,也属于失败。
schema文件夹下的login.js文件(校验规则,在router目录下的login.js文件中使用)
// 导入校验规则的包
const joi = require("joi");
// 确定规则
const username = joi.string().alphanum().min(1).max(16).required();
const password = joi.string().pattern(/^[\S]{6,12}$/).required();
// 导出规则
exports.login_rules = {
body: {
username,
password,
},
};
都写好后用postman测试一下,先测试校验规则能不能用,输一个错的,报错密码不符合正则。
然后把密码换成符合校验的:
注册成功,在终端看一下加密之前和之后的密码,数据库中也有了一条新数据。
之后再点击一次send,这次是进行注册的时候数据库中已有数据,并且用户名被占用。
报错,用户名被占用。
实现登录,会用到一个包 jsonwebtoken ,这个包能够生成token。
文件结构:
这次多了一个全局的配置文件,config.js,这个文件设置生成token时的加密方式的密钥,和token的持续时间,密钥和时间直接写在文件中也可以,但加密需要用到,解密也需要用到,为了方便将他抽离出来。密钥可以随便设置。
config.js文件
// 全局的配置文件
module.exports = {
/**
* 设置token加密和解密用到的密钥
*/
jwtSecretKey: 'c^_^h',
/**
* 设置token的有效期
*/
expiresIn: '10h',
}
router文件夹下的login.js文件(在这个文件中将路由配置好)
const express = require("express");
const router = express.Router();
// 导入校验规则的中间价
const expressJoi = require("@escook/express-joi");
// 引入规则
const { login_rules } = require("../schema/login");
// 引入处理函数
const login_handler = require("../router_handler/login");
// 注册
router.post("/register", expressJoi(login_rules), login_handler.userRegister);
// 登录,登录的时候字段也是相同的规则
router.post("/login", expressJoi(login_rules), login_handler.userLogin);
// 将路由导出
module.exports = router;
注册和登录都用到了用户名和密码,这两个的规则是相同的。
router_handler文件夹下的login.js文件(登录的处理函数)
// 导入数据库模块
const db = require("../db/index");
// 引入对密码进行加密的包
const bcryptjs = require("bcryptjs");
// 导入生成token的包
const jwt = require("jsonwebtoken");
// 导入全局的配置文件,密文
const config = require("../config");
// 注册处理函数
exports.userRegister = (req, res) => {
// 先取到从客户端传来的值
let { username, password } = req.body;
/**
* 注册的时候用户名是唯一的,不能与其他人的用户名一样
* 在将信息写入数据库之前,要先检查用户名是否被占用
*/
// 查询有无相同的用户名
const sql = "select username from user_login where username=?";
// 执行查找sql语句
db.query(sql, username, (err, results) => {
// sql语句执行失败
if (err) return res.output(err);
// 执行成功,如果有数据,则说明有相同的用户名
if (results.length === 1) return res.output("用户名被占用");
// 执行成功,用户名没被占用
// 定义新语句,增加用户
const sqlStr = "insert into user_login set ?";
console.log("加密之前", password);
// 对密码进行加密,第二个参数可以提高密码的安全性,几也行
password = bcryptjs.hashSync(password, 10);
console.log("加密过后", password);
// 执行增加sql语句
db.query(sqlStr, { username, password }, (err, results) => {
// 执行sql语句失败
if (err) return res.output(err);
// 执行sql语句成功 但影响行数不为1
if (results.affectedRows !== 1) return res.output("用户注册失败!");
// 执行成功,影响行数为1
res.output("注册成功!");
});
});
};
// 登录处理函数
exports.userLogin = (req, res) => {
// 接收表单数据
const { username, password } = req.body;
// 先查找用户名是否在数据库中,定义sql语句
const sql = "select * from user_login where username=?";
// 执行语句
db.query(sql, username, (err, results) => {
if (err) return res.output(err);
// 语句执行成功,但没有相应的username
if (results.length !== 1) return res.output("登录失败");
// 语句执行成功,也有相应的username
// 进行密码的比较
// 前面是客户端的密码,后面是数据库中存储经过加密的密码
const compareResult = bcryptjs.compareSync(password, results[0].password);
// 会返回true或false
if (!compareResult) {
return res.output("密码错误,登录失败!");
}
// 密码比对正确,在服务端生成token字段
// 获取到用户的信息,剔除掉密码,生成token
const user = { ...results[0], password: "" };
// 对用户的信息进行加密,生成token字符串,参数2和参数3可以直接写,也可以抽出去
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: config.expiresIn,
});
// 调用res.send将token响应给客户端
res.output("登录成功", 0, "Bearer " + tokenStr);
});
};
(1),先接收客户端传来的用户名和密码,然后在数据库中寻找是否有对应的用户名,如果没有,就是用户名不对,登录失败。
(2),如果有对应的用户名,要比对密码是否相同,比对密码还是用到 bcryptjs 包,用到其中compareSync()方法,这个方法有两个参数,第一个是从客户端传来的密码,第二个参数是在数据库中存储经过加密的密码,根据结果会返回true或false,如果不相同,登录失败。
(3),如果密码相同登录成功,要生成token返回客户端,用到 jsonwebtoken 包,sign()方法,它根据用户的信息生成token,用户信息一般是会将密码去掉的。第二个参数是密钥,第三个参数是时间,有多长时间的有效期,最后将token返回到客户端。
(4),token客户端不能直接用,需要在前面加 'Bearer ',返回的时候将这个加上,前端就能直接用了。
登录成功。
项目的功能分为登录内和登录外的功能,比如一个博客系统,在没有登录时,你可以查看里面的文章,但只有登录之后,才能发表文章。
登录内的接口要在请求时在请求头里上送token,进行身份认证。只在app.js里有修改,其他文件不变。
app.js
// 引入express
const express = require("express");
// 创建服务器的实例对象
const app = express();
// 引入校验规则的包,在定义错误级别的中间件时会用到
const joi = require("joi");
// 配置解析表单数据的中间件 内置中间件,只能解析application/x-www-form-urlencoded格式的数据
app.use(express.urlencoded({ extended: false }));
// 封装res.send() ,必须在路由之前封装
/**
* 不管是输出正确的数据还是错误的信息,每次都需要写这么多东西
* res.send({ state: 0, message: "查询成功", data: result })
* 封装之后不需要写这么多
*/
app.use((req, res, next) => {
// 定义一个输出的函数
res.output = function (err, status = 1, data) {
res.send({
status,
message: err instanceof Error ? err.message : err,
data,
});
};
next();
});
// 在路由之前配置解析token的中间件
const { expressjwt: jwt } = require("express-jwt");
// 解析token需要token的密钥
const config = require("./config");
// 定义中间件,需要哪个密钥解析,.unless指定哪些接口不需要进行token身份认证
app.use(
jwt({ secret: config.jwtSecretKey, algorithms: ["HS256"] }).unless({
path: [/^\/api/],
})
);
// 导入并使用登录,注册的路由模块
const loginRouter = require("./router/login");
app.use("/api", loginRouter);
// 导入并使用路由模块
const inforRouter = require("./router/userInfor");
app.use(inforRouter);
// 在所有路由下面调用错误级别的中间件
app.use((err, req, res, next) => {
// 验证失败导致的错误
if (err instanceof joi.ValidationError) return res.output(err);
// 未知的错误
res.output(err);
next();
});
// 启动服务器
app.listen(3007, () => {
console.log("Server running at http://127.0.0.1:3007");
});
(1),在请求头里带了token,接口请求时要解析token,解析token需要一个包 express-jwt ,还需要和生成token时相同的密钥。
(2),在没有定义解析token的中间件时,请求这时所有的路由还是不需要token的,定义了之后,所有路由就都需要token了。unless()可以指定哪些路由不需要token。algorithms属性,设置jwt的算法,具体了解可以看 express-jwt 的文档。
(3),app.use("/api", loginRouter);使用路由时,前面加‘/api',在请求时,请求路径前也需要加 '/api',登录注册的时候不需要上送token。
未上送token,报错。
上送token,查询成功。
express:框架
mysql:数据库
@escook/express-joi:自动对表单数据进行验证
joi:字段规则
bcryptjs:对密码进行加密处理
jsonwebtoken:生成token
express-jwt:请求头上送token后解析token
可以到下面链接获取文章中的代码。
链接: https://pan.baidu.com/s/1t7bX0Nv3kggyf7IFzEffcA 提取码: 0000