1、初始化
1.1创建项目
1.新建 api_server
文件夹作为项目根目录,并在项目根目录中运行如下的命令,初始化包管理配置文件:
3.在项目根目录中新建 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')
})
1.2 配置 cors 跨域
npm i cors@2.8.5
// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件
app.use(cors())
1.3 配置解析表单数据的中间件
通过如下的代码,配置解析 application/x–www–form–urlencoded 格式的表单数据的中间件:
app.use(express.urlencoded({ extended: false }))
1.4 初始化路由相关的文件夹
1.在项目根目录中,新建 router 文件夹,用来存放所有的路由模块
2.在项目根目录中,新建router_handler 文件夹,用来存放所有的 路由处理函数模块
3.在项目根目录中,新建 schema 文件夹,用来存放表单验证规则模块
1.5 初始化 config.js 配置文件
module.exports = {
jwtSecretKey: 'itheima No1. ^_^', // 加密字符串
expiration_time: '10h' // token 有效期为 10 个小时
}
1.6 初始化 db 数据库配置文件
在项目根目录下创建 db 文件夹,里面创建index.js 文件
// 导入 mysql 模块 npm i mysql@2.18.1
const mysql = require('mysql')
// 创建数据库连接对象
const db = mysql.createPool({
host: 'localhost',
user: 'root', // 本地
// user: 'my_db_01', // 线上
password: '207080',
database: 'my_db_01',
})
// 检测 mysql 模块能否正常工作
// 调用 db.query() 函数,指定要执行的 SQL 语句,通过回调函数拿到执行的结果:
db.query('select 1', (err, results) => {
if (err) return console.log(err.message);
// 只要能打印出 [ RowDataPacket { '1': 1 } ]的结果,就能证明数据库链接正常
console.log(results);
})
// 向外共享 db 数据库连接对象
module.exports = db
1.7 在根目录创建 public 文件夹,用来存放静态数据
最终的项目结构
2、接口api实现
用户注册与登录的接口(使用 jwt 做前后端身份验证)
1.在 router 文件夹中,新建 user.js 文件,作为用户的路由模块,并初始化代码如下:
// 用户登录/注册的路由模块
const express = require('express')
// 创建路由对象
const router = express.Router()
// 导入用户路由处理函数模块
const userHandler = require('../router_handler/user')
// 1. 导入验证表单数据的中间件(自动验证) npm i @escook/express-joi
const expressJoi = require('@escook/express-joi')
// 2. 导入需要的验证规则对象
const { reg_login_schema } = require('../schema/user')
// 注册新用户
// 3. 在注册新用户的路由中,声明局部中间件,对当前请求中携带的数据进行验证
// 3.1 数据验证通过后,会把这次请求流转给后面的路由处理函数
// 3.2 数据验证失败后,终止后续代码的执行,并抛出一个全局的 Error 错误,进入全局错误级别中间件中进行处理
router.post('/reguser', expressJoi(reg_login_schema), userHandler.regUser)
// 登录
router.post('/login', expressJoi(reg_login_schema), userHandler.login)
// 将路由对象共享出去
module.exports = router
2.在 /router_handler/user.js 中,使用 exports 对象,分别向外共享如下两个 路由处理函数 :
/**
* 在这里定义和用户相关的路由处理函数,供 /router/user.js 模块进行调用
*/
// 导入 mySql 数据库的链接模块
const db = require('../db/index.js')
// 导入对密码进行加密处理的第三方中间件 npm i bcryptjs@2.4.3
const bcrypt = require('bcryptjs')
// 用这个包来生成 Token 字符串
const jwt = require('jsonwebtoken')
// 导入配置文件
const config = require('../config')
// 注册用户的处理函数
exports.regUser = (req, res) => {
// 1.判断用户名和密码是否为空
// 接收表单数据
const userinfo = req.body
// 2.检测用户名是否被占用
// 定义 SQL 语句:
const sql = `select * from ev_users where username=?`
// 执行 SQL 语句并根据结果判断用户名是否被占用:
db.query(sql, [userinfo.username], function (err, results) {
// 执行 SQL 语句失败
if (err) {
return res.cc(err.message)
}
// 用户名被占用
if (results.length > 0) {
return res.cc('用户名被占用,请更换其他用户名!')
}
// TODO: 用户名可用,继续后续流程...
// 调用 bcrypt.hashSync(明文密码, 随机盐的长度) 方法,对用户的密码进行加密处理:
// 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
// 向 my_db_01 数据库的 ev_users 表插入新用户
const user = { username: userinfo.username, password: userinfo.password }
const sql = 'insert into ev_users set ?'
db.query(sql, user, (err, results) => {
// 执行 SQL 语句失败
if (err) {
return res.cc(err.message)
}
// SQL 语句执行成功,但影响的行数不为 1
if (results.affectedRows !== 1) {
return res.cc('注册新用户失败,请稍后重试!')
}
return res.cc('注册成功!', 0, 201)
})
})
}
// 登录的处理函数
exports.login = (req, res) => {
// 1.接收表单数据:
const userinfo = req.body
// 2.定义 SQL 语句
const sql = `select * from ev_users where username=?`
// 3.执行 SQL 语句,查询用户的数据:
db.query(sql, userinfo.username, function (err, results) {
// 执行 SQL 语句失败
if (err) return res.cc(err)
// 执行 SQL 语句成功,但是查询到数据条数不等于 1
if (results.length !== 1) return res.cc('登录失败,用户不存在!')
// TODO:判断用户输入的登录密码是否和数据库中的密码一致
// 核心实现思路:调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码) 方法比较密码是否一致
// 返回值是布尔值(true 一致、false 不一致)
// 拿着用户输入的密码,和数据库中存储的密码进行对比
const compareResult = bcrypt.compareSync(userinfo.password, results[0].password)
// const compareResult = userinfo.password == results[0].password
// 如果对比的结果等于 false, 则证明用户输入的密码错误
if (!compareResult) {
return res.cc('登录失败,密码错误!')
}
// TODO:登录成功,生成 Token 字符串
// 核心注意点:在生成 Token 字符串的时候,一定要剔除 密码 和 头像 的值
// 剔除完毕之后,user 中只保留了用户的 id, username, nickname, email 这四个属性的值
const user = { ...results[0], password: '', user_pic: '' }
// 运行如下的命令,安装生成 Token 字符串的包:
// npm i jsonwebtoken@8.5.1
// 生成 Token 字符串
// 参数1:用户的信息对象
// 参数2:加密的秘钥
// 参数3:配置对象,可以配置当前 token 的有效期
// 记住:千万不要把密码加密到 token 字符中
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: config.expiration_time, // token 有效期
})
res.send({
status: 0,
msg: '登录成功!',
// 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀
token: 'Bearer ' + tokenStr,
})
})
}
// 用户信息验证模块
// 使用第三方的中间件验证表单数据
/*
安装 @hapi/joi 包,为表单中携带的每个数据项,定义验证规则:
npm install @hapi/joi@17.1.0 // @hapi/joi 版本已弃用,
npm install joi
安装 @escook/express-joi 中间件,来实现自动对表单数据进行验证的功能:
npm i @escook/express-joi
*/
const joi = require('joi')
/**
* string() 值必须是字符串 默认情况不支持空字符串 Joi.string().allow('') 支持空字符串
* alphanum() 值只能是包含 a-zA-Z0-9 的字符串
* min(length) 最小长度
* max(length) 最大长度
* required() 值是必填项,不能为 undefined
* pattern(正则表达式) 值必须符合正则表达式的规则
* number()值必须是一个数字
* email()值必须是邮箱格式
*/
// 用户名的验证规则
const username = joi.string().alphanum().min(1).max(10).required()
// 密码的验证规则 6~12位 [S]表示,非空白就匹配 [s] 只要出现空白就匹配
// /^[S]{6,12}$/ = 以非空白的元素开头和结尾数量6-12
const password = joi
.string()
.pattern(/^[S]{6,12}$/)
.required()
// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
// 表示需要对 req.body 中的数据进行验证
body: {
username,
password,
},
}
// 定义 id, nickname, emial 的验证规则
const id = joi.number().integer().min(1).required()
const nickname = joi.string().required()
const email = joi.string().email().required()
// 验证规则对象 - 更新用户基本信息
exports.update_userinfo_schema = {
body: {
id,
nickname,
email,
},
}
// 验证规则对象 - 重置密码
exports.update_password_schema = {
body: {
// 使用 password 这个规则,验证 req.body.oldPwd 的值
oldPwd: password,
// 使用 joi.not(joi.ref('oldPwd')).concat(password) 规则,验证 req.body.newPwd 的值
// 解读:
// 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内使用 multer 来解析数据自带验证
// 头像图片的验证规则
// dataUri() 指的是如下格式的字符串数据:
// data:image/png;base64,VE9PTUFOWVNFQ1JFVFM=
// const avatar = joi.string().dataUri().required()
// 验证规则对象 - 更新头像
// exports.update_avatar_schema = {
// body: {
// avatar,
// },
// }
更新用户信息的接口(包含文件上传)
1.在 router 文件夹中,新建 userinfo.js 文件,作为用户的路由模块,并初始化代码如下:
// 用户信息的路由模块
// 导入 express
const express = require('express')
const router = express.Router()
// 导入用户信息的处理函数模块
const userinfo_handler = require('../router_handler/userinfo')
// 导入验证数据合法性的中间件
const expressJoi = require('@escook/express-joi')
// 导入解析 formdata 格式表单数据的包
const multer = require("multer")
// 导入处理路径的核心模块
const path = require('path')
// 导入需要的验证规则对象
const { update_userinfo_schema, update_password_schema } = require('../schema/user')
// 获取用户的基本信息
router.get('/userinfo', userinfo_handler.getUserInfo)
// 更新用户的基本信息
router.put('/userinfo', expressJoi(update_userinfo_schema), userinfo_handler.updateUserInfo)
// 重置密码的路由
router.patch('/updatepwd', expressJoi(update_password_schema), userinfo_handler.updatePassword)
// 更新用户头像的路由
// 注意:使用 express.urlencoded() 中间件无法解析 multipart/form-data 格式的请求体数据。
// npm i multer@1.4.2
// 创建 multer 的实例对象,通过 dest 属性指定文件的存放路径
const upload = multer({ dest: path.join(__dirname, '../public/image/head_portrait') })
// upload.single() 是一个局部生效的中间件,用来解析 FormData 格式的表单数据
// 将文件类型的数据(avatar),解析并挂载到 req.files 属性中 files是数组
// 将其他文本类型的数据,解析并挂载到 req.body 属性中
// array 有两个参数 表单name属性值(前端的文件对象名要和这里的相同) 最多上传文件的个数
router.post('/update/avatar', upload.array('avatar', 1), userinfo_handler.updateAvatar)
// 或
// 将文件类型的数据,解析并挂载到 req.file 属性中 file是对象
// 将其他文本类型的数据,解析并挂载到 req.body 属性中
// router.post('/update/avatar', upload.single('avatar'), userinfo_handler.updateAvatar)
module.exports = router
2.在 /router_handler/userinfo.js 中,使用 exports 对象,向外共享路由处理函数 :
/**
* 在这里定义和用户信息相关的路由处理函数,供 /router/userinfo.js 模块进行调用
*/
// 导入数据库操作模块
const db = require('../db/index')
// 导入文件操作模块
const fs = require('fs')
// 导入处理路径的 path 核心模块
const path = require('path')
// 导入对密码进行加密处理的第三方中间件 npm i bcryptjs@2.4.3
const bcrypt = require('bcryptjs')
// 获取用户基本信息的处理函数
exports.getUserInfo = (req, res) => {
// 根据用户的 id,查询用户的基本信息
// 注意:为了防止用户的密码泄露,需要排除 password 字段
const sql = `select id, username, nickname, email, user_pic,role_id,role_name,is_enabled from ev_users where id=?`
// 调用 db.query() 执行 SQL 语句:
// 注意:req 对象上的 user 属性,是 Token 解析成功,express-jwt 中间件帮我们挂载上去的
db.query(sql, req.user.id, (err, results) => {
// 1. 执行 SQL 语句失败
if (err) return res.cc(err)
// 2. 执行 SQL 语句成功,但是查询到的数据条数不等于 1
if (results.length !== 1) return res.cc('获取用户信息失败!')
// 3. 将用户信息响应给客户端
res.send({
status: 0,
msg: '获取用户基本信息成功!',
data: results[0],
})
})
}
// 更新用户基本信息的处理函数
exports.updateUserInfo = (req, res) => {
const sql = `update ev_users set ? where id=?`
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('修改用户基本信息失败!')
// 修改用户信息成功
return res.cc('修改用户基本信息成功!', 0)
})
}
// 重置密码的处理函数
exports.updatePassword = (req, res) => {
// 定义根据 id 查询用户数据的 SQL 语句
const sql = `select * from ev_users where id=?`
// 执行 SQL 语句查询用户是否存在
db.query(sql, req.user.id, (err, results) => {
// 执行 SQL 语句失败
if (err) return res.cc(err)
// 检查指定 id 的用户是否存在
if (results.length !== 1) return res.cc('用户不存在!')
// TODO:判断提交的旧密码是否正确
// 在头部区域导入 bcryptjs 后,(因为数据库中的密码是加密过的)
// 即可使用 bcrypt.compareSync(提交的密码,数据库中的密码) 方法验证密码是否正确
// compareSync() 函数的返回值为布尔值,true 表示密码正确,false 表示密码错误
// 判断提交的旧密码是否正确
const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password)
if (!compareResult) return res.cc('原密码错误!')
// 对新密码进行 bcrypt 加密之后,更新到数据库中:
// 定义更新用户密码的 SQL 语句
const sql = `update ev_users set password=? where id=?`
// 对新密码进行 bcrypt 加密处理
const newPwd = bcrypt.hashSync(req.body.newPwd, 10)
console.log(req.body.newPwd);
// 执行 SQL 语句,根据 id 更新用户的密码
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.updateAvatar = (req, res) => {
// req.files/req.file 是上传成功文件的信息对象 (.array req.files .single('avatar') req.file)
// req.body 上传文件时所携带的其他文本表单数据
let file = req.files[0]
// 手动判断是否上传了头像
if (!file || file.fieldname !== 'avatar') return res.cc('头像是必选参数!')
// Date.now() 返回自 1970 年 1 月 1 日 00:00:00 (UTC) 到当前时间的毫秒数。类型为 number,
// toString() 方法返回 string 类型。
// file.originalname 文件的名称包含了格式 如: '人èx84¸.PNG'
// 这里写相对路径
// 定义新的文件名
// let Url = 'public/image/head_portrait/' + Date.now().toString() + '_' + req.file.originalname
let Url = path.join('public/image/head_portrait/', file.originalname)// 覆盖重复的文件
// fs.rename():重命名文件或文件夹 同步
// fs.renameSync(oldPath,newPath) 异步 oldPath 原路径 newPath 新路径
fs.renameSync(path.join('./public/image/head_portrait/', file.filename), Url) // 设置文件名
// 图片访问路径
const imgUrl = `/${Url}`
const sql = 'update ev_users set user_pic=? where id=?'
// 调用 db.query() 执行 SQL 语句,更新对应用户的头像:
// req.body.avatar 是一个base(贝s)64字符串,可以直接用img 标签展示
db.query(sql, [imgUrl, 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)
res.send({
status: 0,
msg: '更新头像成功',
imgUrl: imgUrl
})
})
}
表单验证已经写在上面注册与登录的文件中,他们共用 schema/user.js 文件
带分页和模糊查询-获取文章列表
1.在 router 文件夹中,新建 article.js 文件,作为用户的路由模块,并初始化代码如下:
// 导入 express
const express = require('express')
// 创建路由对象
const router = express.Router()
// 导入验证数据的中间件(自动验证)
const expressJoi = require('@escook/express-joi')
// 导入获取文章列表的验证模块
const { getArticleList_schema, } = require('../schema/article')
// 导入文章的路由处理函数模块
const article_handler = require('../router_handler/article')
// 获取-文章列表
router.post('/list', expressJoi(getArticleList_schema), article_handler.getArticleList)
module.exports = router
2.在 /router_handler/article.js 中,使用 exports 对象,向外共享路由处理函数 :
// 导入数据库操作模块
const db = require('../db/index')
/**
* 带分页和模糊查询-获取文章列表
* */
exports.getArticleList = (req, res) => {
const body = req.body;
let sql = 'select * from ev_articles where is_delete<>1 ' // 查询列表所有的数据
let sql_b = 'select count(*) as total from ev_articles where is_delete<>1' // 查询结果总数据条数
let content = [] // 实参
let isMore = false //是否有多个查询参数
let total = 0 // 总数
db.query(sql_b, (err, results) => {
if (err) return res.cc(err)
total = results[0].total
})
if (body.model.cate_id) {
/** like 用法:
* 使用like一般都需要指定某列数据应用like查询,它的语法如下:
* select * from 表名 where 列名 like 要查询的数据
* select * from 表名 where 列名 like '%abc%' 查询列值包含abc的
* select * from 表名 where 列名 like 'abc%' 查询列值以abc开头的
* select * from 表名 where 列名 like '%abc' 查询列值以abc结尾的
* select * from 表名 where 列名 like 'ab%c' 查询列值以ab开头,c结尾的
* % 占位符表示0个或多个字符,可放在查询条件的任意位置
*/
sql += 'and cate_id like ? ';
content.push('%' + body.model.cate_id + '%') // 模糊查询 cate_id
isMore = true;
}
if (body.model.state) {
if (isMore) {//true代表有多个参数
sql += 'and state like ? ' // and 是与两个条件都必须满足,or是或的关系
} else {
sql += 'and state like ? '
}
content.push('%' + body.model.state + '%') // 模糊查询 state
}
if (body.current) { //开启分页
let current = body.current;//当前页码
let pageSize = body.size;//一页展示多少条数据
sql += 'limit ?,?' // ? 开始查询的索引 , ? 要查多少条数据
// parseInt 将字符串转为整数
content.push((current - 1) * pageSize, parseInt(pageSize))
}
db.query(sql, content, (err, results) => {
// 执行 SQL 语句失败
if (err) return res.cc(err)
// SQL 语句执行成功,但是没有查询到任何数据
if (results.length === 0) return res.cc('文章列表数据为空,请更筛选条件重试~', 0)
res.json({
status: 0,
msg: '获取文章列表数据成功!',
total: total,
data: results
})
})
}
3.在/schema/article.js 中,写入表单验证规则
// 导入定义验证规则的模块
const joi = require('joi')
/**
* string() 值必须是字符串 默认情况不支持空字符串 Joi.string().allow('') 支持空字符串
* alphanum() 值只能是包含 a-zA-Z0-9 的字符串
* min(length) 最小长度
* max(length) 最大长度
* required() 值是必填项,不能为 undefined
* pattern(正则表达式) 值必须符合正则表达式的规则
* number()值必须是一个数字
* email()值必须是邮箱格式
* integer() 整数
* valid() 有效值
*/
const current = joi.number().integer().required()
const size = joi.number().integer().required()
const cate_idR = joi.required()
const stateR = joi.string().valid('已发布', '草稿', '').required()
//校验规则对象 - 获取 - 文章列表
exports.getArticleList_schema = {
body: {
current,
size,
model: {
cate_id: cate_idR,
state: stateR
}
}
}
原文地址:https://blog.csdn.net/weixin_43263566/article/details/126991448
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_22028.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!