文章目录
初始化
创建项目
- 初始化包管理配置文件
npm init -y
- 运行如下的命令,安装特定版本的
express
- 在项目根目录中新建
app.js
作为整个项目的入口文件,并初始化如下的代码
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// write your code here...
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(3007, function () {
console.log('api server running at http://127.0.0.1:3007')
})
配置cors跨域
- 运行如下的命令,安装
cors
中间件npm i cors@2.8.5
- 在
app.js
中导入并配置cors
中间件
// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件
app.use(cors())
配置解析表单数据的中间件
只能解析application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }))
初始化路由相关的文件夹
- 在项目根目录中,新建
router
文件夹,用来存放所有的路由
模块 - 在项目根目录中,新建
router_handler
文件夹,用来存放所有的路由处理函数模块
初始化用户路由模块
- 在
router
文件夹中,新建user.js
文件,作为用户的路由模块,并初始化代码如下:
const express = require('express')
// 创建路由对象
const router = express.Router()
// 注册新用户
router.post('/reguser', (req, res) => {
res.send('reguser OK')
})
// 登录
router.post('/login', (req, res) => {
res.send('login OK')
})
// 将路由对象共享出去
module.exports = router
- 在
app.js
中,导入并使用用户路由模块
// 导入并注册用户路由模块
const userRouter = require('./router/user')
app.use('/user', userRouter)
抽离用户路由模块中的处理函数
为了保证 路由模块
的纯粹性,所有的 路由处理函数
,必须抽离到对应的 路由处理函数模块
中
- 在router_handle中的user.js中定义路由处理函数
exports.register = (req,resp)=>{
resp.send("发送成功")
console.log('注册成功');
}
exports.login =(req,resp)=>{
resp.send("登录成功!")
}
- 在router中的user.js中使用定义的处理函数模块
const express = require("express")
const router = express.Router()
const userHandle = require("../router_handle/user.js")
router.get("/register",userHandle.register)
router.post("/login",userHandle.login)
module.exports = router
安装配置mysql
- 使用npm安装mysql模块
npm install mysql
- 配置mysql模块
const mysql = require("mysql")
const db = mysql.createPool({
host:'127.0.0.1',
user:'root',
password:'root',
database:'node_demo'
})
// 向外共享 db 数据库连接对象
module.exports = db
登录注册
初始化数据表
- 新增user表用来存放用户信息
- 检查表单数据是否合法
- 获取表单数据
res.body
需要安装body-parser中间件,并且需要在引入路由之前引用
//app.js
var bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
//router_handle/user.js
exports.register = (req, resp) => {
// 接收表单数据
const userinfo = req.body
// 判断数据是否合法
if (!userinfo.username || !userinfo.password) {
return res.send({
status: 500,
message: '用户名或密码不能为空!'
})
}
}
- 验证用户名是否存在
// 检测用户名是否被占用
db.query('select * from user where username = ?',userinfo.username,function(err,result){
if(err){
return res.send({ code:500,msg:err.message})
}
if(result.length > 0){
return res.send({ code:-1,msg:"用户名已被占用,请更换其他用户名!"})
}
res.send("新增成功")
})
- 对密码进行加密,在当前项目中,使用
bcryptjs
对用户密码进行加密,优点:
- 加密之后的密码,无法被逆向破解
- 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
运行如下命令,安装指定版本的 bcryptjs
npm i bcryptjs@2.4.3
在 /router_handler/user.js
中,导入 bcryptjs
const bcrypt = require('bcryptjs')
在注册用户的处理函数中,确认用户名可用之后,调用 bcrypt.hashSync(明文密码, 随机盐的长度)
方法,对用户的密码进行加密处理
// 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
优化res.send()代码
处理函数中,需要多次调用 res.send()
向客户端响应 处理失败
的结果,为了简化代码,可以手动封装一个 res.cc() 函数,需要定义在路由之前
app.use(function(req,res,next){
res.cc = function (err,code = 500){
res.send({
code,
msg:err instanceof Error ? err.message : err
})
}
next()
})
此时,注册代码简化为
exports.register = (req, res) => {
// 接收表单数据
const userinfo = req.body
// 判断数据是否合法
if (!userinfo.username || !userinfo.password) {
return res.cc('用户名或密码不能为空!')
}
// 检测用户名是否被占用
db.query('select * from user where username = ?',userinfo.username,function(err,result){
if(err){
return res.cc(err)
}
if(result.length > 0){
return res.cc("用户名已被占用,请更换其他用户名!",-1)
}
})
//对用户密码进行加密
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
userinfo['nickName'] = '新用户'
userinfo['userPic'] = ''
// 插入新用户
db.query("insert into user set ?",userinfo,function(err,result){
if(err){
return res.cc(err)
}
if(result.affectedRows != 1){
return res.cc("注册用户失败,请稍后重试!")
}
return res.cc("新增用户成功",200)
})
}
优化表单数据验证
单纯的使用 if...else...
的形式对数据合法性进行验证,效率低下、出错率高、维护性差。因此,推荐使用第三方数据验证模块,来降低出错率、提高验证的效率与可维护性。
- 安装
@hapi/joi
包,为表单中携带的每个数据项,定义验证规则:
npm install @hapi/joi@17.1.0
- 安装
@escook/express-joi
中间件,来实现自动对表单数据进行验证的功能:
npm i @escook/express-joi
- 新建
/schema/user.js
用户信息验证规则模块,并初始化代码如下:
const Joi = require('@hapi/joi')
const joi = require('@hapi/joi')
/**
* string() 值必须是字符串
* alphanum() 值只能是包含 a-zA-Z0-9 的字符串
* min(length) 最小长度
* max(length) 最大长度
* required() 值是必填项,不能为 undefined
* pattern(正则表达式) 值必须符合正则表达式的规则
*/
// 用户名的验证规则
const username = joi.string().alphanum().min(1).max(10).required()
// 密码的验证规则
const password = joi
.string()
.pattern(/^[\S]{6,12}$/)
.required()
// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
// 表示需要对 req.body 中的数据进行验证
body: {
username,
password,
repassword:Joi.ref('password')
},
}
-
修改
/router/user.js
中的代码如下:// 1. 导入验证表单数据的中间件 const expressJoi = require('@escook/express-joi') // 2. 导入需要的验证规则对象 const { reg_login_schema } = require('../schema/user') router.post("/register",expressJoi(reg_login_schema),userHandle.register)
-
在
app.js
的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端:
// 错误中间件
app.use(function (err, req, res, next) {
// 数据验证失败
if (err instanceof joi.ValidationError) return res.cc(err)
// 未知错误
res.cc(err)
})
登录判断用户输入的密码是否正确
调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码)
方法比较密码是否一致,返回值为布尔类型
if(!bcrypt.compareSync(userinfo.password,result[0].password)){
return res.cc('密码错误,请重新输入','-1')
}
生成 JWT 的 Token 字符串
在生成 Token 字符串的时候,一定要剔除 密码 和 头像 的值
// 剔除完毕之后,user 中只保留了用户的 id, username, nickname, email 这四个属性的值
const user = { ...results[0], password: '', user_pic: '' }
知识链接—扩展运算符(…)
对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
let bar = { a: 1, b: 2 };
let baz = { …bar }; // { a: 1, b: 2 }如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉
let bar = {a: 1, b: 2};
let baz = {…bar, …{a:2, b: 4}}; // {a: 2, b: 4}如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
-
运行如下的命令,安装生成 Token 字符串的包
npm i jsonwebtoken@8.5.1
-
在
/router_handler/user.js
模块的头部区域,导入jsonwebtoken
包:
// 用这个包来生成 Token 字符串
const jwt = require('jsonwebtoken')
- 创建
config.js
文件,并向外共享 加密 和 还原 Token 的jwtSecretKey
字符串
module.exports = {
jwtSecretKey: 'abcdefg. ^_^',
}
- 将用户信息对象加密成 Token 字符串
// 导入配置文件
const config = require('../config')
// 生成 Token 字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: '10h', // token 有效期为 10 个小时
})
- 将生成的 Token 字符串响应给客户端
res.send({
status: 200,
message: '登录成功!',
// 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀
token: 'Bearer ' + tokenStr,
})