项目结构目录如下
1.初始化
1.1创建项目
1.新建api_server文件,初始化包配置管理配置文件
npm init -y
2.安装特定版本的express
npm i express@4.17.1
3.在根目录中新建app.js作为 整个项目的入口文件,并初始化如下:
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8080, () => {
console.log('api server running at http://127.0.0.1:8080')
})
1.2 配置cors跨域
1.安装cors
npm i cors@2.8.5
2.在app.js中导入并配置cors中间件
//导入并配置cors中间件
const cors = require('cors')
//注册为全局可用的中间件
app.use(cors())
1.3配置解析表单数据
1.配置解析表单数据的中间件
app.use(express.urlencoded({extended:false}))
1.4初始化路由
1.新建router文件夹,用来存放路由模块。
2.在根目录中,新建router_handler文件夹,用来存放所有路由处理函数模块。
初始化路由模块:
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.5抽离用户路由模块中的处理函数
在router_handle/user.js中,使用exports对象,分别向外暴露如下两个路由处理函数
//注册处理函数
exports.regUser = (req, res) => {
res.send('reguser ok')
}
//登录处理函数
exports.login = (req, res) => {
res.send('login ok')
}
并且在router/user.js中进行调用
//导入用户路由处理函数对应的模块
const user_handler = require('../router_handler/user')
//注册新用户
router.post('/reguser', user_handler.regUser)
//登录
router.post('/login', user_handler.login)
2.注册登录
2.1新建ev_user表
1.在my_db_01中新建ev_user表
2.安装配置mysql模块
npm i mysql@2.18.1
在根目录中新建/db/index.js文件,在此定义模板中创建数据库的连接对象
//导入mysql模块
const mysql = require('mysql')
//创建数据库连接对象
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'admin',
database: 'my_db_01'
})
//向外共享db数据库连接对象
module.exports = db
2.3注册
2.3.1检测表单数据是否合法
//注册处理函数
exports.regUser = (req, res) => {
//获取客户端提交到服务器的用户信息
const userinfo = req.body
//对表单数据进行合法校验(为空)
if (!userinfo.username || !userinfo.password)
return res.send({ status: 1, message: '用户名或密码不合法' })
console.log(userinfo);
res.send('reguser ok')
}
2.3.2检测用户名是否被占用
1.导入数据库操作模块
//导入数据库操作模块
const db = require('../db/index')
2.SQL语句(用户名是否被占用)
//定义SQL语句,查询用户名是否被占用
const sqlStr = 'select * from ev_users where username=?'
db.query(sqlStr, userinfo.username, (err, results) => {
//执行SQL语句失败
if (err) {
return res.send({ status: 1, message: err.message })
}
//判断用户名是否被占用
if (results.length > 0) {
return results.send({ status: 1, message: '用户名被占用 ' })
}
//用户名可以使用
})
2.3.3 对密码进行加密处理
加密中后,无法被逆向破解
同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
安装:
npm i bcryptjs@2.4.3
导入
//导入加密bcryptjs
const bcrypt=require('bcryptjs')
确认用户名之后,对密码进行加密处理
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
加密前后效果对比:
2.3.4插入新用户
代码:
//定义插入新用户的插入语句
const sql = 'insert into ev_users set ?'
//调用db.query()执行SQL语句
db.query(sql, { username: userinfo.username, password: userinfo.password }, (err, results) => {
//判断SQL语句是否执行成功
if (err)
return res.send({ status: 1, message: err.message })
//判断影响行数是否为1
if (results.affectedRows !== 1)
return res.send({ status: 1, message: '注册新用户失败,请稍后再试' })
//注册用户成功
res.send({ status: 0, message: '注册成功!' })
})
遇到问题:
Field ‘id’ doesn’t have a default value
解决方案:
原因是因为mysql的中没有将主键设置为自增,所以在增加元素时获取生成主键时出现异常,打开Navicat,点击表右键->设计表 到下面的页面 选择自动递增,保存即可
解决后成功注册
2.4 优化res.send()代码
因为在处理函数中,需要多次调用res.send()向客户端响应处理失败的结果,于是可以手动封装一个res.cc函数
1.在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优化表单验证
1.安装@hapi/joi包,为表单中携带的每个数据项,定义验证规则
npm install joi
2.安装@escook/express-joi中间件,实现自动对表单数据进行验证的功能
npm i @escook/express-joi
3.新建/schema/user.js用户信息验证规则模块,并初始化代码如下
//导入定义验证规则的包
const joi = require('@hapi/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
}
}
2.6登录
1.检验表单数据是否合法
2.根据用户名查询用户的数据
3.判断用户输入的密码是否正确
4.生成JWT的token字符串
2.6.1检验用户提交的表单数据是否正确
注册用户和登录使用的是同一种校验规则:
//登录处理函数
exports.login = (req, res) => {
//接收表单数据
const userinfo = req.body
//定义SQL数据
const sql = 'select * from ev_users where username=?'
//执行SQL语句,根据用户名查询用户的信息
db.query(sql, userinfo.username, (err, results) => {
//执行SQL语句失败
if (err) return res.cc(err)
//执行SQL语句成功,但是获取数据条数不是1
if (results.length !== 1)
return res.cc('登录失败')
//判断密码是否正确
})
res.end('login ok')
}
2.6.3服务器端生成jwt字符串
1.通过ES6高级语法,快速剔除用户的密码和头像的值
//在服务器端生成Token的字符串,后面跟进的两项被踢出
const user = {...results[0] ,password:'',user_pic:''}
console.log(user);
2.运行如下的命令,安装Token字符串的包
npm i jsonwebtoken@8.5.1
3.在/router_handler/user.js导入这个包
//导入生成Token的包
const jwt = require('jsonwebtoken')
4.在根目录新建文件夹config.js,写入下面代码。建议秘钥有效时间写得久一点,不然很快就失效了,需要重新生成,比较麻烦。
///这是一个全局的配置文件
module.exports = {
//加密和解密Token的秘钥
jwtSecretKey: 'itheima',
//token的有效期
expiresIn: '100h'
}
5.在路由函数管理文件/router_handler/user.js中导入
//导入全局的配置文件
const config = require('../config')
6.把用户信息对象加密成Token字符串
//对用户的信息进行加密,生成Token字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, { expiresIn: config.expiresIn })
// //调用res.send()将Token响应给客户端
// console.log(tokenStr);
res.send({
status: 0,
message: '登录成功',
token: 'Bearer ' + tokenStr
})
用户登录成功就会生成token字符串
2.7配置token中间件
1.安装Token中间件
npm i express-jwt@5.3.3
2.注册中间件,在app.js入口文件中注册路由之前,配置解析Token的中间件
//一定在路由之前配置解析Token中间件
const expressJWT = require('express-jwt')
const config = require('./config')
app.use(expressJWT({ secret: config.jwtSecretKey }).unless({ path: [/^\/api/] }))
//导入并注册路由模块
const userRouter = require('./router/user')
3.在app.js入口文件中的错误中间件里面,捕获并且处理Token认证失败后的错误
//定义错误级别中间件
app.use((err, req, res, next) => {
//验证失败导致的错误
if (err instanceof Joi.ValidationError) return res.cc(err)
//身份认证失败后的错误
if (err.name === 'UnauthorizedError') return res.cc('身份认证失败')
//未知的错误
return res.cc(err)
})
如果出现报错情况: jwt expired
解决方案:重新进行登录操作,会生成新的token字符串作为请求头信息,以这个新的请求头发送请求,可以生效
3.个人中心
3.1获取用户信息
1.初始化路由模块
2.初始化路由处理函数模块
3.获取用户的基本信息
3.1.1初始化路由模块
1.创建/router/userinfo.js模块
//导入express
const express = require('express')
//创建路由对象
const router = express.Router()
//挂载路由
//获取用户的基本信息
router.get('/userinfo', (req, res) => {
res.send('ok')
})
//向外共享路由对象
module.exports = router
2.在app.js入口文件中导入个人中心路由
//导入并使用用户信息的路由模块
const userinfoRouter = require('./router/userinfo')
app.use('/my', userinfoRouter)
由于个人信息这个接口设置了权限,需要在请求头里面加入一个认证的字段,value里面是前面生成的token字段
路由成功运行!
3.1.2初始化路由处理函数模块
1.在/router_handler新建文件userinfo.js路由处理函数模块,并初始化
exports.getUserInfo = (req, res) => {
res.send('ok')
}
2./router/userinfo.js代码
//导入express
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获取用户的基本信息
1.导入数据库
const db = require('../db/index')
2.定义SQL语句
//根据用户的id,查询用户的基本信息
//防止用户的密码泄漏,需要排除password字段
const sql = 'select id,username,nickname,email,user_pic from ev_users where id=?'
3.调用db.qurey()执行语句
//定义SQL语句
//根据用户的id,查询用户的基本信息
//防止用户的密码泄漏,需要排除password字段
const sql = 'select id,username,nickname,email,user_pic from ev_users where id=?'
//调用db.query()执行SQL语句
db.query(sql, req.user.id, (err, results) => {
//执行SQL语句失败
if (err) return res.cc(err)
//执行SQL语句成功,但是查询的结果可能为空
if (results.length !== 1) return res.cc('获取用户信息失败')
//用户信息获取成功
res.send({ status: 0, message: '获取用户信息成功', data: results[0] })
})
3.2更新用户的基本信息
1.定义路由和处理函数
2.验证表单数据
3.实现更新用户基本信息的功能
3.2.1
1.在/router/userinfo.js文件中,新增更新用户信息的接口
router.post('/userinfo', userinfo_handler.updateUserInfo)
2.在/router_handler/userinfo.js文件中,定义向外共享
exports.updateUserInfo = (req, res) => {
res.send('ok')
}
3.2.2验证表单数据
1.在/schema/user.js验证规则模块中,定义id,nickname,email的验证规则
const id = joi.number().integer().min().required()
const nickname = joi.string().required()
const email = joi.string().email().required()
2.向外共享
exports.update_userinfo_schema = {
body: {
id,
nickname,
email
}
}
3.在router/userinfo.js模块中,导入验证数据合法性的中间件
//导入验证数据的中间件
const expressJoi = require('@escook/express-joi')
//2.导入需要的验证规则
const { update_userinfo_schema } = require('../schema/user')
router.post('/userinfo', expressJoi(update_userinfo_schema), userinfo_handler.updateUserInfo)
3.2.3实现更新用户基本信息的功能
修改router_handler/userinfo.js代码
//更新用户基本信息的处理函数
exports.updateUserInfo = (req, res) => {
//定义执行的SQL语句
const sql = 'update ev_users set ? where id=?'
//调用db.query执行SQL语句并传递参数
db.query(sql, [req.body, req.body.id], (err, results) => {
//执行SQL语句失败辽
if (err) return res.cc(err)
//执行SQL语句成功,但是影响行数不等于1
if (results.affectedRows !== 1) return res.cc('更新用户基本信息失败')
//成功
res.cc('更新用户信息成功', 0)
})
}
3.3重置密码
1.定义路由和处理函数
2.验证表单数据
3.实现重置密码的功能
3.3.1定义路由和处理函数
1.在/router/userinfo.js模块中,新增重置密码的路由
router.post('/updatepwd', userinfo_handler.updatePassword)
2.在/router_handler/userinfo.js模块,定义并向外共享重置密码的路由处理函数
exports.updatePassword = (req, res) => {
res.send('ok')
}
3.3.2验证表单数据
1.在/schema/user.js模块,使用exports向外共享
exports.update_password_schema = {
body: {
oldPwd: password,
//joi.ref 表示新旧一致
//2.joi.not表示newPwd的值不能等于旧密码的值
//3..concat表示合并新的和旧的密码验证规则
newPwd: joi.not(joi.ref('oldPwd')).concat(password)
}
}
2.在router/userinfo.js
//导入需要的验证规则对象
const { update_userinfo_schema, update_password_schema } = require('../schema/user')
router.post('/updatepwd', expressJoi(update_password_schema), userinfo_handler.updatePassword)
3.3.3重置密码的功能
1.根据id查询用户是否存在
//更新用户密码的处理函数
exports.updatePassword = (req, res) => {
//根据id查询用户的信息
const sql = 'select * from ev_users where id=?'
//执行根据id查询用户信息的SQL语句
db.query(sql, req.user.id, (err, results) => {
//执行SQL语句失败辽
if (err) return res.cc(err)
//执行SQL语句成功,但是影响行数不等于1
if (results.length !== 1) return res.cc('更新用户基本信息失败')
//成功
res.cc('ok')
})
}
2.判断提交的旧密码是否正确
const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password)
if (compareResult) return res.cc('旧密码错误')
3.对新密码进行bcrypt加密之后,更新到数据库中
//更新数据库中的密码
//定义更新密码的SQL语句
const sql = 'update ev_users set password=? where id=?'
//对新密码进行加密处理
const newPwd = bcrypt.hashSync(req.body.newPwd, 10)
//调用db.query()执行SQL语句
db.query(sql, [newPwd, req.user.id], (err, results) => {
//执行SQL语句失败辽
if (err) return res.cc(err)
//执行SQL语句成功,但是影响行数不等于1
if (results.affectedRows !== 1) return res.cc('更新用户基本信息失败')
//成功
res.cc('更新用户信息成功', 0)
})
总代码:
//更新用户密码的处理函数
exports.updatePassword = (req, res) => {
//根据id查询用户的信息
const sql = 'select * from ev_users where id=?'
//执行根据id查询用户信息的SQL语句
db.query(sql, req.user.id, (err, results) => {
//执行SQL语句失败辽
if (err) return res.cc(err)
//执行SQL语句成功,但是影响行数不等于1
if (results.length !== 1) return res.cc('更新用户基本信息失败')
//成功
//判断密码是否正确
const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password)
if (compareResult) return res.cc('旧密码错误')
//更新数据库中的密码
//定义更新密码的SQL语句
const sql = 'update ev_users set password=? where id=?'
//对新密码进行加密处理
const newPwd = bcrypt.hashSync(req.body.newPwd, 10)
//调用db.query()执行SQL语句
db.query(sql, [newPwd, req.user.id], (err, results) => {
//执行SQL语句失败辽
if (err) return res.cc(err)
//执行SQL语句成功,但是影响行数不等于1
if (results.affectedRows !== 1) return res.cc('更新用户基本信息失败')
//成功
res.cc('更新用户信息成功', 0)
})
})
}
3.4 用户头像信息更新
3.4.1定义路由和处理函数
1.在/router/userinfo.js模块中,新增更换用户头像路由
router.post('/update/avatar', userinfo_handler.updateAvatar)
2./router_handler/userinfo.js模块中,定义向外共享更新头像的路由处理函数
//更新用户头像的处理函数
exports.updateAvatar = (req, res) => {
res.send('ok')
}
3.4.2验证表单数据
1./schema/user.js,定义验证规则如下:
const avatar = joi.string().dataUri().required() //更新头像
//验证规则对象--更换头像
exports.update_avatar_schema = {
body: {
avatar
}
}
2./router/userinfo.js.导入需要的的验证规则对象
//更换头像验证规则对象
const { update_avatar_schema } = require('../schema/user')
//更换头像路由
router.post('/update/avatar', expressJoi(update_avatar_schema), userinfo_handler.updateAvatar)
3.4.3实现更新用户头像的功能
//更新用户头像的处理函数
exports.updateAvatar = (req, res) => {
const sql = 'update ev_users set user_pic=? where id=?'
db.query(sql, [req.body.avatar, req.user.id], (err, results) => {
//执行SQL语句失败辽
if (err) return res.cc(err)
//执行SQL语句成功,但是影响行数不等于1
if (results.affectedRows !== 1) return res.cc('更新头像失败')
//成功
return res.cc('更新头像成功', 0)
})
}