1.初始化
1.1创建项目
- 新建
api_server文件夹作为项目根目录,并运行如下命名,创建包管理文件
npm init -y
- 运行如下命名,安装
express:
npm i express
- 在跟项目中创建app.js作为项目入口,并初始化如下代码
//导入express模块
const express=require('express')
//创建expres的服务实例
const app=express()
//调用app.listen方法,指定端口号并启动服务
app.listen(80,()=>{
console.log('http://127.0.0.1')
})
1.2配置cors跨域
- 运行如下命令,安装
cors中间件:
npm i cors
- 在
app.js中导入并配置cors中间件:
//导入cors模块
const cors=require('cors')
//将cors注册为全局中间件
app.use(cors())
1.3配置解析表单数据中间件
- 通过如下代码,配置解析application/x-www-form-urlencoded格式的表单数据的中间件:
app.use(express.urlencoded({extended:false}))
1.4初始化路由相关文件夹
-
在项目根目录下新建
router文件夹,用来存放所有路由模块 -
在项目根目录下新建
router_handler文件夹,用来存放所有的路由处理函数
1.5初始化用户路由模块
- 在
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('/api',userRouter)
1.6抽离用户模块中的处理函数
- 目的:为了保证
路由模块的纯粹性,所有的路由处理函数,必须抽离到对应的路由处理函数模块中
- 在
/router_handler/user.js中,使用exports对象,分别向外共享如下两个路由处理函数
//注册的处理函数
exports.regUser=(req,res)=>{
res.send('reguser OK')
}
//登录的处理函数
exports.login=(req,res)=>{
res.send('login OK')
}
- 将
/router/user.js中的代码修改成如下结构
const express= require('express')
const router=express.Router()
//导入用户路由处理函数对应的模块
const user_handler=require('../router_handler/user')
//注册
router.post('/reguser',user_handler.regUser)
//登录
router.post('/login',user_handler.login)
module.exports=router
2.登录与注册
2.1创建ev_user表
2.2安装并配置mysql模块
- 运行如下命令,安装mysql
npm i mysql
- 在项目中新建
/db/index.js文件,在此自定义模板中创建数据库的链接对象
const mysql= require('mysql')
const db=mysql.createPool({
host:'127.0.0.1',
user:'root',
password:'',
database:'my_db_01'
})
module.exports=db
2.3注册
- 检测表单数据是否合法
- 检测用户名是否占用
- 对密码进行加密处理
- 插入新用户
2.3.1检测表单数据是否合法
- 判断用户名和密码是否为空
//获取客户端提交到服务器的信息
const userInfo=req.body
//对表单数据进行合法验证
if(!userInfo.username||!userInfo.password){
return res.send({status:1,message:'用户名或密码不合法'})
}
2.3.2检测用户名是否占用
- 导入数据库模块
const db=require('../db/index')
- 定义SQL语句
const sqlStr='select * from ev_user where username=?'
- 执行SQL语句并根据结果判断用户名是否被占用
db.query(sqlStr,userInfo.username,(err,results)=>{
if(err){
return res.send({status:1,message:err.message})
}
if(results.length>0){
return res.send({status:1,message:'用户被占用,请使用其他用户名'})
}
})
2.3.3对密码进行加密处理
使用bcrypt.js对用户密码进行加密,优点
- 加密之后密码,无法被逆向破解
- 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
- 运行如下命令,安装
bcryptjs
npm i bcryptjs
- 在
/router_handler/user.js中,导入bcryptjs:
const bcrypt=require('bcryptjs')
- 在注册用户的处理函数中,确认用户名可用之后,调用bcrypt.hashSync(明文密码,随机值的长度)方法,对用户的密码进行加密处理。
//调用bcrypt.hashSync()对密码进行加密
userInfo.password=bcrypt.hashSync(userInfo.password,10)
2.3.4插入用户
- 定义一个插入用户的SQL语句
//定义插入用户的SQL语句
const sql='insert into ev_user set ?'
2.使用db.query()执行SQL语句,插入新用户:
//调用db.query()执行sql
db.query(sql,{username:userInfo.username,password:userInfo.password},(err,results)=>{
if(err){
return res.send({status:1,message:err.message})
}
//判断影响函数是否为1
if(results.affectedRows!==1){
return res.send({status:1,message:'注册用户失败,请稍后再试!'})
}
//注册用户成功
return res.send({status:0,message:'注册用户成功!'})
})
2.4优化res.send()代码
- 在app.js中,所有路由之前,声明一个中间件,为res对象挂载一个res.cc()函数
//一定要在路由之前,封装res.cc函数
app.use((req,res,next)=>{
//status默认值为1,表示失败情况
//err的值,可能是一个错误的对象,也有可能是一个错误的描述字符串
res.cc=function(err,status=1){
res.send({
status,
message:err instanceof Error?err.message:err
})
}
next()
})
2.5优化表单数据验证
- 安装
joi包,为表单中携带的每个数据项,定义验证规则:
npm i joi
- 安装
@escook/express-joi中间件,来实现自动对表单数据进行验证功能:
npm i @escook/express-joi
- 新建
/schema/user.js用户信息验证模块,并初始化代码如下:
//导入定义验证规则的包
const joi=require('joi')
//定义用户名和密码的验证规则
const username=joi.string().alphanum().min(1).max(10).required()
const password=joi.string().pattern(/^[\S]{6,12}$/).required()
//定义验证注册和登录表单数据规则对象
exports.reg_login_schema={
body:{
username,
password
}
}
- 修改
/router/user.js中的代码如下:
const express= require('express')
const router=express.Router()
//导入用户路由处理函数对应的模块
const user_handler=require('../router_handler/user')
//1.导入验证数据的中间件
const expressJoi=require('@escook/express-joi')
//2.导入需要的验证规则对象
const {reg_login_schema}= require('../schema/user')
//注册
router.post('/reguser',expressJoi(reg_login_schema),user_handler.regUser)
//登录
router.post('/login',user_handler.login)
module.exports=router
5.在app.js的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端:
const joi= require('joi')
//定义错误级别的中间件
app.use((err,req,res,next)=>{
//验证失败导致的错误
if(err instanceof joi.ValidationError) return res.cc(err)
//未知错误
res.cc(err)
})
2.6登录
实现步骤
- 检测表单数据是否合法
- 根据用户名查询用户信息
- 判断用户输入的密码是否正确
- 生成JWT的Token
2.6.1检测表单数据是否合法
1.将/router/user.js中登录的路由代码修改如下
router.post('/login',expressJoi(reg_login_schema),user_handler.login)
2.6.2根据用户名查询用户的数据
- 接受表单数据:
const userInfo=req.body
- 定义SQL语句:
const sqlStr='select * from ev_user where username=?'
- 执行SQL语句,查询用户的数据
db.query(sqlStr,userInfo.username,(err,results)=>{
if(err) return res.cc(err)
if(results.length!==1) return res.cc('用户名不存在')
//TODO:判断用户输入的登录密码是否和数据库中的密码一致
})
2.6.3判断用户输入的密码是否正确
核心思路:调用调用bcrypt.compareSync(用户提交的密码,数据库中的密码)方法比较密码是否一致。返回true一致,false不一致。
具体实现代码如下
//判断密码是否正确
const compareResult= bcrypt.compareSync(userInfo.password,results[0].password)
if(!compareResult) return res.cc('登录失败!')
//TODO:登录成功,生成Token字符串
2.6.4生成JWT的Token字符串
注意:在生成Token字符串的时候,一定要剔除密码和头像值
- 通过ES6的高级语法,快速剔除密码和头像
//登录成功,生成Token字符串
const user={...results[0],password:'',user_pic:''}
- 运行如下命令,安装生成Token字符串的包:
npm i jsonwebtoken
3.在/router_handler/user.js模块的头部区域,导入jsonwebtoken包
//用这个包生成Token字符串
const jwt=require('jsonwebtoken')
- 创建
config.js文件,并向外共享加密和还原Token的jwtSecreKey字符串:
module.exports={
jwtSecretKey:'zyt',//加密解密的秘钥
expiresIn:'10h'//token有效期为10个小时
}
- 将用户信息对象加密成Token字符串:
const config=require('../config')
const token=jwt.sign(user,config.jwtSecretKey,{
expiresIn:config.expiresIn
})
- 将生成的Token字符串相应给客户端:
res.send({
status:0,
message:'登录成功',
//为方便客户端使用token,在服务器端直接拼接上Bearer前缀
token:'Bearer '+token
})
2.7配置解析Token中间件
- 运行如下命令,安装解析Token的中间件:
npm i express-jwt@7.7.1
- 在
app.js中注册路由之前,配置解析Token中间件:
//一定要在路由之前配置解析Token的中间件
const { expressjwt: jwt }=require('express-jwt')
const config=require('./config')
app.use(jwt({secret:config.jwtSecretKey,algorithms: ["HS256"]}).unless({path:[/^\/api/]}))
- 在
app.js中的错误级别中间件里面,捕获并处理Token身份认证
//定义错误级别的中间件
app.use((err,req,res,next)=>{
//其他验证失败导致的错误
//...
//捕获身份认证失败的错误
if(err.name==='UnauthorizedError') return res.cc('身份认证失败')
//未知错误
res.cc(err)
})
3个人中心
3.1获取用户的基本信息
实现步骤
- 初始化路由模块
- 初始化路由处理函数模块
- 获取用户的基本信息
3.1.1初始化路由模块
/router/userinfo.js路由模块,并初始化如下代码结构:
const express= require('express')
const router=express.Router()
router.get('/userinfo',(req,res)=>{
res.send('ok')
})
module.exports=router
- 在
app.js中导入并使用个人中心的路由模块
//导入并使用用户信息路由模块
const userinfoRouter=require('./router/userinfo')
//注意:以/my开头的接口,都是有权限的接口,需要进行Token身份认证
app.use('/my',userinfoRouter)
3.1.2初始化路由处理函数模块
- 创建
/router_handler/userinfo.js路由处理函数模块,并初始化如下代码结构:
exports.getUserInfo=(req,res)=>{
res.send('ok')
}
2.修改/router/userinfo.js中的代码:
const express= require('express')
const router=express.Router()
//导入用户信息处理函数
const userinfo_handler=require('../router_handler/userinfo')
//获取用户的基本信息
router.get('/userinfo',userinfo_handler.getUserInfo)
module.exports=router
3.1.3获取用户基本信息
- 在
/router_handler/userinfo.js头部导入数据库:
//导入数据库
const db= require('../db/index')
- 定义SQL语句:
//注意:为防止用户密码泄露,需要排除password字段
const sqlStr='select id,username,nickname,email,user_pic from ev_user where id=?'
- 调用db.query()执行SQL语句:
//注意req对象上有auth属性,是Token解析成功,express-jwt中间件帮我们挂载上去了
db.query(sqlStr,req.auth.id,(err,results)=>{
if(err) return res.cc(err)
if(results.length!==1) return res.cc('获取用户信息失败')
//将用户信息相应给客户端
res.send({
status:0,
message:'获取用户信息成功',
data:results[0]
})
})
3.2.1定义路由和处理函数
- 在
/router/userinfo.js模块中,新增更新用户基本信息的路由
//更新用户的基本信息
router.post('/userinfo',userinfo_handler.updataUserInfo)
- 在
/router_handler/userinfo.js模块中,定义并向外共享更新用户基本信息的路由处理函数:
//更新用户基本信息的处理函数
exports.updataUserInfo=(req,res)=>{
res.send('ok')
}
3.2.2验证表单数据
- 在
/schema/user.js验证规则模块中,定义id,nickname,email,的验证规则如下:
//定义id,nickname,email的验证规则
const id=joi.number().integer().min(1).required()
const nickname=joi.string().required()
const email=joi.string().email().required()
- 使用
exports向外共享如下验证规则对象
//验证规则对向-更新用户基本信息
exports.update_userinfo_schema={
//需要对req.body里面的数据进行验证
body:{
id,
nickname,
email
}
}
- 在
/router/userinfo.js模块,导入验证数据合法性中间件
//导入验证数据的中间件
const expressJoi=require('@escook/express-joi')
- 在
/router/userinfo.js模块,导入需要的验证规则对象:
//导入需要的验证规则对象
const {update_userinfo_schema}= require('../schema/user')
- 在
/router/userinfo.js模块,修改更新用户基本信息路由
//更新用户的基本信息
router.post('/userinfo',expressJoi(update_userinfo_schema),userinfo_handler.updataUserInfo)
3.2.3实现更新用户基本信息功能
- 定义待执行的SQL语句:
const sqlStr='update ev_user set ? where id=?'
- 调用db.query()执行SQL语句并传参:
db.query(sqlStr,[req.body,req.body.id],(err,results)=>{
if(err) return res.cc(err)
if(results.affectedRows!==1) res.cc('更新用户基本信息失败')
//成功
res.cc('更新用户成功!',0)
})
3.3重置密码
实现步骤:
- 定义一个路由和处理函数
- 验证表单数据
- 实现重置密码的功能
3.3.1定义一个路由和处理函数
- 在
/router/userinfo.js模块中,新增重置密码的路由:
router.post('/updatepwd',userinfo_handler.updatePassword)
- 在
/router_handler/userinfo.js模块中,定义并向外共享重置密码的路由处理函数
exports.updatePassword=(req,res)=>{
res.send('ok')
}
3.3.2验证表单数据
旧密码与新密码,必须符合密码规范,并且新密码不能与旧密码一致!
/schema/user,js模块中,使用exports向外共享如下的验证规则对象
//验证规则对象-重置密码
exports.update_password_schema={
//需要对req.body里面的数据进行验证
body:{
oldPwd:password,
// 1. joi.ref('oldPwd') 表示 newPwd 的值必须和 oldPwd 的值保持一致
// 2. joi.not(joi.ref('oldPwd')) 表示 newPwd 的值不能等于 oldPwd 的值
// 3. .concat() 用于合并 joi.not(joi.ref('oldPwd')) 和 password 这两条验证规则
newPwd:joi.not(joi.ref('oldPwd')).concat(password)
}
}
- 在
/router/userinfo.js模块,导入需要的验证规则对象:
//导入需要的验证规则对象
const {update_userinfo_schema,update_password_schema}= require('../schema/user')
- 并在重置密码路由中,使用
update_password_schema规则验证表单数据
//密码重置
router.post('/updatepwd',expressJoi(update_password_schema),userinfo_handler.updatePassword)
3.3.3实现重置密码的功能
- 根据id查询用户是否存在
exports.updatePassword=(req,res)=>{
const sqlStr='select * from ev_user where id=?'
db.query(sqlStr,req.auth.id,(err,results)=>{
if(err) return res.cc(err)
if(results.length!==1) return res.cc('用户不存在')
//TODO判断用户输入的旧密码对否正确
})
}
- 判断旧密码是否存在
const bcryptjs= require('bcryptjs')
//判断用户输入的旧密码对否正确
const compareResult= bcryptjs.compareSync(req.body.oldPwd,results[0].password)
if(!compareResult) return res.cc('原密码错误')
- 对新密码加密,放入数据库中
const sqlStr='update ev_user set password=? where id=?'
//对新密码加密
const newPwd=bcryptjs.hashSync(req.body.newPwd,10)
db.query(sqlStr,[newPwd,req.auth.id],(err,results)=>{
if(err) return res.cc(err)
if(results.affectedRows!==1) return res.cc('修改密码失败')
res.cc('修改密码成功',0)
})
- 学习视频地址:www.bilibili.com/video/BV1a3…
- 代码地址:gitee.com/zyt91666/no…