记录创建一个node项目的全过程
截止今天,终于要把在b站学习的《黑马程序员》的nodejs全套视频看完了,学习总是需要有输出的吧。所以今天记录创建一个node项目的全过程。废话不多说,下面进入正题。
项目初始化
新建项目
创建一个
api_server
的文件夹1
mkdir api_server
通过
npm
初始化一个express
工程1
2
3
4
5# 进入到api_server根目录下
npm init -y
# 在api_server根目录安装express
npm i express编写
app.js
配置web服务器在
api_server
根目录下创建app.js
文件1
2
3
4
5
6
7
8
9// 第一步 导入express模块
let express = require('express');
// 第二步 创建express的服务器实例
let app = express();
// 第三步 调用app.listen方法 指定端口号并启动web服务器
let port = 3008
app.listen(port, () => {
console.log("api server is runnning at http://localhost:" + port)
})
配置cors
跨域
为了支持项目可以跨域访问,我们需要再项目里安装并配置cors
跨域中间件
安装
cors
1
npm i cors
在
app.js
配置全局中间件1
2
3
4
5
6
7
8
9
10
11
12
13
14// 第一步 导入express模块
let express = require('express');
// 第二步 创建express的服务器实例
let app = express();
// 配置cors跨域中间件
let cors = require('cors');
app.use(cors())
// 第三步 调用app.listen方法 指定端口号并启动web服务器
let port = 3008
app.listen(port, () => {
console.log("api server is runnning at http://localhost:" + port)
})
配置解析表单数据的中间件
express
提供了一个只解析application/x-www-form-unlencoded
的中间件
在
app.js
配置解析表单数据的中间件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 第一步 导入express模块
let express = require('express');
// 第二步 创建express的服务器实例
let app = express();
// 配置cors跨域中间件
let cors = require('cors');
app.use(cors())
// 配置解析表单数据的中间件
app.use(express.urlencoded({extended: false}))
// 第三步 调用app.listen方法 指定端口号并启动web服务器
let port = 3008
app.listen(port, () => {
console.log("api server is runnning at http://localhost:" + port)
})
初始化路由相关的文件夹
在
api_server
根目录下,新建**router
文件夹,用来存放所有的路由模块**路由模块中,只存放客户端的请求与处理函数之间的映射关系
在
api_server
根目录下,新建router_handler
文件夹,用来存放所有的路由处理函数模块
路由处理函数模块中,专门负责存放每个路由对应的处理函数
初始化用户路由模块
在
router
文件夹中,新建user.js
文件,作为用户的路由模块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let express = require('express');
// 创建路由对象
let router = express.Router();
// 注册新用户
router.post('/register', (req, res) => {
res.send('register ok')
})
// 登录
router.post('/login', (req, res) => {
res.send('login success')
})
// 将路由共享出去
module.exports = router在
app.js
中,导入并使用用户路由模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14// ...省略以上代码
// 配置解析表单数据的中间件
app.use(express.urlencoded({extended: false}))
// 导入并使用用户路由模块
let userRouter = require("./router/user");
app.use('/api', userRouter)
// 第三步 调用app.listen方法 指定端口号并启动web服务器
let port = 3008
app.listen(port, () => {
console.log("api server is runnning at http://localhost:" + port)
})
抽离用户路由模块中的处理函数
为了保证路由模块
的纯粹,所有的路由处理函数
,必须抽离到对应的路由处理函数模块
中
在
router_handler
目录下创建user.js
,并且将登录和注册的路由处理函数
共享出去1
2
3
4
5
6
7
8
9// 注册用户的处理函数
exports.register = (req, res) => {
res.send('register ok')
}
// 用户登录的处理函数
exports.login = (req, res) => {
res.send('login ok')
}改造
router/user.js
的代码1
2
3
4
5
6
7
8
9
10
11
12
13let express = require('express');
// 创建路由对象
let router = express.Router();
let userHandler = require("../router_handler/user");
// 注册新用户
router.post('/register', userHandler.register)
// 登录
router.post('/login', userHandler.login)
// 将路由共享出去
module.exports = router
注册登录
创建用户表
以下为用户信息表ev_users
的DDL
1 |
|
安装并配置mysql模块
安装
mysql
模块1
2# 在api_server根目录下
npm i mysql配置mysql链接对象
在api_server根目录下新建一个
db
目录,创建一个index.js
文件,用来创建一个mysql的链接对象1
2
3
4
5
6
7
8
9
10
11
12
13// 导入mysql模块
let mysql = require('mysql');
// 创建
let db = mysql.createPool({
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '123456',
database: 'api_server'
});
// 向外共享db数据库连接对象
module.exports = db
注册功能
检测表单数据是否合法
判断前端传过来的用户名和密码是否为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 注册用户的处理函数
exports.register = (req, res) => {
// 获取表单数据
let userinfo = req.body;
// 判断数据是否合法
if (!userinfo.username || !userinfo.password) {
return res.send({
status: 1,
message: "用户名或密码不能为空!"
})
}
// res.send('register ok')
}
// ...省略检测用户名是否被占用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// 引入数据库操作模块
let db = require("../db/index");
// 注册用户的处理函数
exports.register = (req, res) => {
// 获取表单数据
let userinfo = req.body;
// 判断数据是否合法
if (!userinfo.username || !userinfo.password) {
return res.send({
status: 1,
message: "用户名或密码不能为空!"
})
}
// 定义sql语句
let sql = "select * from ev_users where username = ?"
// 判断用户名是否可用
db.query(sql, [userinfo.username], function (err, results) {
// 查询失败的话
if (err) {
return res.send({
status: 1,
message: err.message
})
}
if (results.length > 0) {
return res.send({
status: 1,
message: "用户名被占用,请更换其他用户名!"
})
}
})
res.send('register ok')
}
// ...省略对密码进行加密处理
众所皆知,在我们的系统中,为了保证安全性,我们不会将明文的密码存入数据库,正常我们都会选择加密后存储
安装
bcryptjs
组件对用户密码进行加密bcryptjs加密后的密码无法被逆向破解
bcryptjs对同一明文密码进行加密,所得结果各不相同
1
npm i bcryptjs
在
/router_handler/user.js
导入bcryptjs
1
let bcrypt = require('bcryptjs');
对用户密码进行加密
1
2// 对用户密码进行bcrypt加密,返回的是加密之后的字符串 hashSync(明文密码,随机盐的长度)方法 进行加密处理
userinfo.password = bcrypt.hashSync(userinfo.password, 10);
插入新用户
编写插入新用户的sql以及业务处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14// ...省略
// 对用户密码进行bcrypt加密,返回的是加密之后的字符串 hashSync(明文密码,随机盐的长度)方法 进行加密处理
userinfo.password = bcrypt.hashSync(userinfo.password, 10);
// 添加新用户
let addSql = 'insert into ev_users set ?'
db.query(addSql, {username: userinfo.username, password: userinfo.password}, function (err, results) {
if (err) return res.send({status: 1, message: err.message})
// sql执行成功 但影响行数不为1
if (results.affectedRows !== 1) {
return res.send({status: 1, message: "注册用户失败,请稍后再试!"})
}
// 注册成功
return res.send({status: 0, message: '注册成功!'})
})
优化代码
优化
res.send()
代码(统一响应体)在
app.js
中,所有路由之前,生命一个全局中间件,为res对象挂载一个res.cc()
函数注意事项:一定要在所有路由之前,否则会失效
1
2
3
4
5
6
7
8
9
10
11// 统一响应体的中间件
app.use(function (req, res, next) {
// 默认状态是1 为错误的情况
res.cc = function (err, status = 1) {
res.send({
status,
message: err instanceof Error ? err.message : err
})
}
next()
})改造代码(片段)如下
1
2
3
4
5
6
7
8
9
10
11// 改造前
if (!userinfo.username || !userinfo.password) {
return res.send({
status: 1,
message: "用户名或密码不能为空!"
})
}
// 改造后
if (!userinfo.username || !userinfo.password) {
return res.cc('用户名或密码不能为空!')
}优化表单数据验证
表单验证原则:前端验证为辅,后端验证为主。对于后端而言,前端传递过来的数据永远是不可信的状态
现状
单纯使用
if..else...
的形式进行数据合法性验证,效率底下、出错率高、维护性差。改进方案
采用第三方数据验证模块来降低出错率、提高验证的效率和可维护性,让后端开发专注于业务逻辑的处理。
安装
@hapi/joi
包,为表单中携带的每个数据项,定义验证规则1
npm install [email protected]
安装@escook/express-joi中间件,来实现自动对表单数据进行验证的功能
1
npm install @escook/express-joi
新建
/schema/user.js
用户验证规则模块1
2
3
4
5
6
7
8
9
10
11
12
13
14let joi = require('joi');
// 用户名的验证规则
let username = joi.string().alphanum().min(1).max(10).required();
// 密码的验证规则
let password = joi.string().pattern(/^[\S]{6,15}$/).required()
// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
body: {
username,
password
}
}改造
/router/user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let express = require('express');
// 创建路由对象
let router = express.Router();
let userHandler = require("../router_handler/user");
// 1.导入验证表单数据的中间件
let expressJoi = require('@escook/express-joi');
// 2.导入需要的验证规则对象
let {reg_login_schema} = require('../schema/user');
// 注册新用户 插入参数校验的局部中间件
router.post('/register', expressJoi(reg_login_schema), userHandler.register)
// 将路由共享出去
module.exports = router在
app.js
的路由之后,定义全局异常处理中间件1
2
3
4
5
6
7
8// ...省略
// 在路由之后 定义全局异常处理中间件
app.use(function (err, req, res, next) {
// 验证失败导致的错误
if (err instanceof joi.ValidationError) res.cc(err)
// 未知的错误
res.cc(err)
})
登录
检测登录表单数据是否合法
在
router/user.js
改造路由代码,添加参数校验的局部中间件1
2// 登录
router.post('/login', expressJoi(reg_login_schema), userHandler.login)根据用户名查询用户的数据
在
router_handler/user.js
改造login函数1
2
3
4
5
6
7
8
9
10
11
12
13// 用户登录的处理函数
exports.login = (req, res) => {
let userinfo = req.body
let sql = 'select * from ev_users where username = ?'
db.query(sql, userinfo.username, function (err, results) {
// 执行sql失败
if (err) return res.cc(err)
// 执行sql成功,但数量不等于1
if (results.length !== 1) return res.cc(err)
// TODO 判断用户输入的登录密码是否和数据库中的密码一致
})
res.send('login ok')
}判断用户输入的密码是否正确
核心思路:调用
bcrypt.compareSync(用户提交的密码,数据库中的加密密码)
方法比较密码是否一致。true则一致在
router_handler/user.js
改造login函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 用户登录的处理函数
exports.login = (req, res) => {
let userinfo = req.body
let sql = 'select * from ev_users where username = ?'
db.query(sql, userinfo.username, function (err, results) {
// 执行sql失败
if (err) return res.cc(err)
// 执行sql成功,但数量不等于1
if (results.length !== 1) return res.cc('登录失败!')
let compareResult = bcrypt.compareSync(userinfo.password, results[0].password);
if (!compareResult) return res.cc('登录失败!')
// TODO 在服务端生成Token的字符串
res.send('login ok')
})
}生成
JWT
的Token
字符串注意事项:在生成Token字符串的时候,一定要剔除密码和头像的值
通过ES6的高级语法,快速提出
密码
和头像
的值1
2// 剔除用户敏感信息(ES6写法)
let user = {...results[0], password: '', user_pic: ''}安装生成Token的包
1
npm i jsonwebtoken
在
/router_handler/user.js
模块的头部区域,导入jsonwebtoken
包1
let jwt = require('jsonwebtoken');
创建
config.js
文件,并向外共享加密和还原Token的jwtSecretKey
的字符串在
api_server
根目录下新建一个config.js
全局配置文件1
2
3
4
5
6
7// 这是一个全局配置文件
module.exports = {
// 加密和还原Token的`jwtSecretKey`的字符串
jwtSecretKey: 'gcoder 009M ~~',
// token过期时间
expiresIn: '1h'
}将用户信息对象加密成token字符串
1
2// 生成token字符串
let tokenStr = jwt.sign(user, config.jwtSecretKey, {expiresIn: config.expiresIn});将生成的token字符串响应给客户端
1
2
3
4
5
6// 返回token
res.send({
status: 0,
message: '登录成功!',
token: 'Bearer ' + tokenStr
})
配置解析
Token
的中间件安装解析Token的中间件
1
npm i express-jwt
在
app.js
中注册路由之前,配置解析Token的中间件1
2
3
4// 导入全局配置文件
let config = require('./config');
// 解析token的中间件
let {expressjwt: jwt, UnauthorizedError} = require('express-jwt');在
app.js
中的错误级别中间件
里面,捕获并处理Token认证失败后的错误1
2// 身份认证失败导致的错误
if (err instanceof UnauthorizedError) return res.cc('身份认证失败!')
写在最后
以上就是关于用node.js搭建一个express后端项目的记录了。希望大家能够从中学习到点东西~