基于node的第三方插件express开发的后台管理系统
1. 初始化 1.1. 创建项目
新建 api_server
文件夹为项目根目录,并在根目录中运行如下的命令,初始化包管理配置文件
运行如下命令,安装 express
在根目录中新建 app.js
作为整个项目的入口文件,并初始化
1 2 3 4 5 6 7 8 9 const express = require ("express" );const app = express ();app.listen (3007 ,()=> { console .log ("api server running at http://127.0.0:3007" ); })
1.2. 配置cors跨域
运行如下命令,安装 cors
中间件
在 app.js
中导入配置 cors
中间件
1 2 3 4 const cors = require ("cors" );app.use (cors ())
1.3. 配置解析表单数据的中间件
通过如下的代码,配置解析 application/x-www-form-urlencoded
格式的表单数据的中间件
1 app.use (express.urlencoded ({ extended : false }))
1.4. 初始化路由相关的文件夹
在项目根目录中,新建 router
文件夹,用来存放所有的 路由
模块
在项目根目录中,新建 router_handler
文件夹,用来存放所有的 路由处理函数模块
1.5. 初始化用户路由模块
在 router
文件夹中,新建 user.js
文件,作为用户的路由模块,并初始化代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const express = require ("express" );const router = express.Router ();router.post ("/register" ,(req,res )=> { res.send ("register ok" ); }); router.post ("/login" ,(req,res )=> { res.send ("login ok" ); }) module .exports =router;
在 app.js 中,导入并使用 用户路由模块 :
1 2 3 const userRouter = require ("./router/user" );app.use ("/use" ,userRouter);
1.6. 抽离用户路由模块中的处理函数
目的:为了保证 路由模块 的纯粹性,所有的 路由处理函数 ,必须抽离到对应的 路由处理函数 模块 中
在 /router_handler/user.js
中,使用 exports 对象,分别向外共享如下两个路由处理函 数
:
1 2 3 4 5 6 7 8 9 10 11 12 13 exports .useReg = (req,res )=> { console .log ("Register Ok" ); } exports .login = (req,res )=> { console .log ("login Ok" ); }
将 /router/user.js
中的代码修改为如下结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const express = require ("express" );const router = express.Router ();const useHandler = require ("../router_handler/user" )router.post ("/register" ,useHandler.useReg ); router.post ("/login" ,useHandler.login ) module .exports =router;
2. 登录注册 2.1. 新建 ev_users 表
在 my_db_01
数据库中,新建 ev_users
表如下:
1 2 3 4 5 6 7 8 9 10 11 12 create database my_db_01 default character set utf8;use my_db_01; create table ev_users( id int primary key auto_increment, username varchar (255 ) not null , password varchar (255 ) not null , nickname varchar (255 ), email varchar (255 ), user_pic text )
2.2. 安装并配置 mysql 模块
在 API 接口项目中,需要安装并配置 mysql
这个第三方模块,来连接和操作 MySQL 数据库
运行如下命令,安装 mysql
模块:
在项目根目录中新建 /db/index.js
文件,在此自定义模块中创建数据库的连接对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const mysql = require ('mysql' )const connection = db.createConnection ({ host : '127.0.0.1' , user : 'root' , port : "3306" , password : 'admin123' , database : 'my_db_01' , }) connection.connect (); module .exports = connection;
2.3. 注册 2.3.1. 实现步骤
检测表单数据是否合法
检测用户名是否被占用
对密码进行加密处理
插入新用户
2.3.2. 检测表单数据是否合法
判断用户名和密码是否为空
1 2 3 4 5 6 const userinfo = req.body if (!userinfo.username || !userinfo.password ) { return res.send ({ status : 1 , message : '用户名或密码不能为空!' }) }
2.3.3. 检测用户名是否被占用
导入数据库操作模块:
1 const db = require ('../db/index' )
定义 SQL 语句:
1 const sql = 'select * from ev_users where username = ?' ;
执行 SQL 语句并根据结果判断用户名是否被占用:
1 2 3 4 5 6 7 8 9 10 11 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 : '用户名被占用,请更换其他用户名!' }) } })
2.3.4. 对密码进行加密处理
为了保证密码的安全性,不建议在数据库以 明文
的形式保存用户密码,推荐对密码进行 加密存储
在当前项目中,使用 bcryptjs
对用户密码进行加密,优点:
加密之后的密码,无法被逆向破解
同一明文密码多次加密,得到的加密结果各不相同 ,保证了安全性
运行如下命令,安装指定版本的 bcryptjs
:
在 /router_handler/user.js
中,导入 bcryptjs
:
1 const bcrypt = require ('bcryptjs' )
在注册用户的处理函数中,确认用户名可用之后,调用 bcrypt.hashSync(明文密码, 随机盐的长度)
方法,对用户的密码进行加密处理:
1 2 userinfo.password = bcrypt.hashSync (userinfo.password , 10 )
2.3.5. 插入新用户
定义插入用户的 SQL 语句:
1 const sql = 'insert into ev_users set ?'
调用 db.query()
执行 SQL 语句,插入新用户:
1 2 3 4 5 6 7 8 9 10 db.query (sql, { username : userinfo.username , password : userinfo.password }, function (err, results ) { if (err) return res.send ({ status : 1 , message : err.message }) if (results.affectedRows !== 1 ) { return res.send ({ status : 1 , message : '注册用户失败,请稍后再试!' }) } res.send ({ status : 0 , message : '注册成功!' }) })
2.4. 优化 res.send() 代码
在处理函数中,需要多次调用 res.send()
向客户端响应 处理失败
的结果,为了简化代码,可以手动封装一个 res.cc() 函数
在 app.js
中,所有路由之前,声明一个全局中间件,为 res 对象挂载一个 res.cc()
函数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 app.use (function (req, res, next ) { res.cc = function (err, status = 1 ) { res.send ({ status, message : err instanceof Error ? err.message : err, }) } next () })
2.5. 优化表单数据验证
表单验证的原则:前端验证为辅,后端验证为主,后端永远不要相信 前端提交过来的任何内容
在实际开发中,前后端都需要对表单的数据进行合法性的验证,而且,后端做为数据合法性验证的最后一个关口 ,在拦截非法数据方面,起到了至关重要的作用。
单纯的使用 if...else...
的形式对数据合法性进行验证,效率低下、出错率高、维护性差。因此,推荐使用第三方数据验证模块 ,来降低出错率、提高验证的效率与可维护性,让后端程序员把更多的精力放在核心业务逻辑的处理上 。
安装 joi
包,为表单中携带的每个数据项,定义验证规则:
安装 @escook/express-joi
中间件,来实现自动对表单数据进行验证的功能:
1 npm i @escook/express-joi
新建 /schema/user.js
用户信息验证规则模块,并初始化代码如下:
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 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
中的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const express = require ('express' )const router = express.Router ()const userHandler = require ('../router_handler/user' )const expressJoi = require ('@escook/express-joi' )const { reg_login_schema } = require ('../schema/user' )router.post ('/reguser' , expressJoi (reg_login_schema), userHandler.regUser ) router.post ('/login' , userHandler.login ) module .exports = router
在 app.js
的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端:
1 2 3 4 5 6 7 8 9 const joi = require ('joi' )app.use (function (err, req, res, next ) { if (err instanceof joi.ValidationError ) return res.cc (err) res.cc (err) })
2.6. 登录 2.6.1. 实现步骤
检测表单数据是否合法
根据用户名查询用户的数据
判断用户输入的密码是否正确
生成 JWT 的 Token 字符串
2.6.2. 检测登录表单的数据是否合法
将 /router/user.js
中 登录
的路由代码修改如下:
1 2 router.post ('/login' , expressJoi (reg_login_schema), userHandler.login )
2.6.3. 根据用户名查询用户的数据
接收表单数据:
1 const userinfo = req.body
定义 SQL 语句:
1 const sql = `select * from ev_users where username=?`
执行 SQL 语句,查询用户的数据:
1 2 3 4 5 6 7 db.query (sql, userinfo.username , function (err, results ) { if (err) return res.cc (err) if (results.length !== 1 ) return res.cc ('登录失败!' ) })
2.6.4. 判断用户输入的密码是否正确
核心实现思路:调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码)
方法比较密码是否一致
返回值是布尔值(true 一致、false 不一致)
具体的实现代码如下:
1 2 3 4 5 6 7 8 9 const compareResult = bcrypt.compareSync (userinfo.password , results[0 ].password )if (!compareResult) { return res.cc ('登录失败!' ) }
2.6.5. 生成 JWT 的 Token 字符串
核心注意点:在生成 Token 字符串的时候,一定要剔除 密码 和 头像 的值
通过 ES6 的高级语法,快速剔除 密码
和 头像
的值:
1 2 const user = { ...results[0 ], password : '' , user_pic : '' }
运行如下的命令,安装生成 Token 字符串的包:
在 /router_handler/user.js
模块的头部区域,导入 jsonwebtoken
包:
1 2 const jwt = require ('jsonwebtoken' )
创建 config.js
文件,并向外共享 加密 和 还原 Token 的 jwtSecretKey
字符串:
1 2 3 module .exports = { jwtSecretKey : 'itheima No1. ^_^' }
将用户信息对象加密成 Token 字符串:
1 2 3 4 5 6 7 const config = require ('../config' )const tokenStr = jwt.sign (user, config.jwtSecretKey , { expiresIn : '10h' })
将生成的 Token 字符串响应给客户端:
1 2 3 4 5 6 res.send ({ status : 0 , message : '登录成功!' , token : 'Bearer ' + tokenStr, })
2.6.6. 配置解析 Token 的中间件
运行如下的命令,安装解析 Token 的中间件:
在 app.js
中注册路由之前,配置解析 Token 的中间件:
1 2 3 4 5 6 7 8 9 10 11 const config = require ('./config' )const expressJWT = require ('express-jwt' )app.use (expressJWT.expressjwt ({ secret :config.jwtSecretKey , algorithms : ['HS256' ] }).unless ({path :[/^\/api\// ] }));
在 app.js
中的 错误级别中间件
里面,捕获并处理 Token 认证失败后的错误:
1 2 3 4 5 6 7 8 9 app.use (function (err, req, res, next ) { if (err.name === 'UnauthorizedError' ) return res.cc ('身份认证失败!' ) })
2.6.7. 测试
3. 个人中心 3.1. 获取用户的基本信息 3.1.1. 实现步骤
初始化 路由 模块
初始化 路由处理函数 模块
获取用户的基本信息
3.1.2. 初始化路由模块
创建 /router/userinfo.js
路由模块,并初始化如下的代码结构:
1 2 3 4 5 6 7 8 9 10 11 12 const express = require ('express' )const router = express.Router ()router.get ('/userinfo' , (req, res ) => { res.send ('ok' ) }) module .exports = router
在 app.js
中导入并使用个人中心的路由模块:
1 2 3 4 const userinfoRouter = require ('./router/userinfo' )app.use ('/my' , userinfoRouter)
3.1.3. 初始化路由处理函数模块
创建 /router_handler/userinfo.js
路由处理函数模块,并初始化如下的代码结构:
1 2 3 4 exports .getUserInfo = (req, res ) => { res.send ('ok' ) }
修改 /router/userinfo.js
中的代码如下:
1 2 3 4 5 6 7 8 9 10 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.4. 获取用户的基本信息
在 /router_handler/userinfo.js
头部导入数据库操作模块:
1 2 const db = require ('../db/index' )
定义 SQL 语句:
1 2 3 const sql = 'select id, username, nickname, email, user_pic from ev_users where id=?'
调用 db.query()
执行 SQL 语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 db.query (sql, 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. 更新用户的基本信息 3.2.1. 实现步骤
定义路由和处理函数
验证表单数据
实现更新用户基本信息的功能
3.2.2. 定义路由和处理函数
在 /router/userinfo.js
模块中,新增 更新用户基本信息
的路由:
1 2 router.post ('/userinfo' , userinfo_handler.updateUserInfo )
在 /router_handler/userinfo.js
模块中,定义并向外共享 更新用户基本信息
的路由处理函数:
1 2 3 4 exports .updateUserInfo = (req, res ) => { res.send ('ok' ) }
3.2.3. 验证表单数据
在 /schema/user.js
验证规则模块中,定义 id
,nickname
,email
的验证规则如下:
1 2 3 4 const id = joi.number ().integer ().min (1 ).required()const nickname = joi.string ().required()const email = joi.string ().email ().required()
并使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 7 8 exports .update_userinfo_schema = { body : { id, nickname, email, }, }
在 /router/userinfo.js
模块中,导入验证数据合法性的中间件:
1 2 const expressJoi = require ('@escook/express-joi' )
在 /router/userinfo.js
模块中,导入需要的验证规则对象:
1 2 const { update_userinfo_schema } = require ('../schema/user' )
在 /router/userinfo.js
模块中,修改 更新用户的基本信息
的路由如下:
1 2 router.post ('/userinfo' , expressJoi (update_userinfo_schema), userinfo_handler.updateUserInfo )
3.2.4. 实现更新用户基本信息的功能
定义待执行的 SQL 语句:
1 const sql = 'update ev_users set ? where id=?'
调用 db.query()
执行 SQL 语句并传参:
1 2 3 4 5 6 7 8 9 10 db.query (sql, [req.body , req.auth .id ], (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('修改用户基本信息失败!' ) return res.cc ('修改用户基本信息成功!' , 0 ) })
3.3. 重置密码 3.3.1. 实现步骤
定义路由和处理函数
验证表单数据
实现重置密码的功能
3.3.2. 定义路由和处理函数
在 /router/userinfo.js
模块中,新增 重置密码
的路由:
1 2 router.post ('/updatepwd' , userinfo_handler.updatePassword )
在 /router_handler/userinfo.js
模块中,定义并向外共享 重置密码
的路由处理函数:
1 2 3 4 exports .updatePassword = (req, res ) => { res.send ('ok' ) }
3.3.3. 验证表单数据
核心验证思路:旧密码与新密码,必须符合密码的验证规则,并且新密码不能与旧密码一致!
在 /schema/user.js
模块中,使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 7 8 9 10 11 12 13 exports .update_password_schema = { body : { oldPwd : password, newPwd : joi.not (joi.ref ('oldPwd' )).concat (password), }, }
在 /router/userinfo.js
模块中,导入需要的验证规则对象:
1 2 const { update_userinfo_schema, update_password_schema } = require ('../schema/user' )
并在 重置密码的路由
中,使用 update_password_schema
规则验证表单的数据,示例代码如下:
1 router.post ('/updatepwd' , expressJoi (update_password_schema), userinfo_handler.updatePassword )
3.3.4. 实现重置密码的功能
根据 id
查询用户是否存在:
1 2 3 4 5 6 7 8 9 10 11 12 13 const sql = 'select * from ev_users where id=?' db.query (sql, req.user .id , (err, results ) => { if (err) return res.cc (err) if (results.length !== 1 ) return res.cc ('用户不存在!' ) })
判断提交的 旧密码 是否正确:
1 2 3 4 5 6 7 8 const bcrypt = require ('bcryptjs' )const compareResult = bcrypt.compareSync (req.body .oldPwd , results[0 ].password )if (!compareResult) return res.cc ('原密码错误!' )
对新密码进行 bcrypt
加密之后,更新到数据库中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const sql = `update ev_users set password=? where id=?` const newPwd = bcrypt.hashSync (req.body .newPwd , 10 )db.query (sql, [newPwd, req.auth .id ], (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('更新密码失败!' ) res.cc ('更新密码成功!' , 0 ) })
3.4. 更新用户头像 3.4.1. 实现步骤
定义路由和处理函数
验证表单数据
实现更新用户头像的功能
3.4.2. 定义路由和处理函数
在 /router/userinfo.js
模块中,新增 更新用户头像
的路由:
1 2 router.post ('/update/avatar' , userinfo_handler.updateAvatar )
在 /router_handler/userinfo.js
模块中,定义并向外共享 更新用户头像
的路由处理函数:
1 2 3 4 exports .updateAvatar = (req, res ) => { res.send ('ok' ) }
3.4.3. 验证表单数据
在 /schema/user.js
验证规则模块中,定义 avatar
的验证规则如下:
1 2 3 const avatar = joi.string ().dataUri ().required()
并使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 exports .update_avatar_schema = { body : { avatar, }, }
在 /router/userinfo.js
模块中,导入需要的验证规则对象:
1 const { update_avatar_schema } = require ('../schema/user' )
在 /router/userinfo.js
模块中,修改 更新用户头像
的路由如下:
1 router.post ('/update/avatar' , expressJoi (update_avatar_schema), userinfo_handler.updateAvatar )
3.4.4. 实现更新用户头像的功能
定义更新用户头像的 SQL 语句:
1 const sql = 'update ev_users set user_pic=? where id=?'
调用 db.query()
执行 SQL 语句,更新对应用户的头像:
1 2 3 4 5 6 7 8 9 10 db.query (sql, [req.body .avatar , req.user .id ], (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('更新头像失败!' ) return res.cc ('更新头像成功!' , 0 ) })
4. 文章分类管理 4.1. 新建 ev_article_cate 表 4.1.1. 创建表结构 1 2 3 4 5 6 create table ev_article_cate( id int primary key auto_increment, name varchar (255 ) not null , alias varchar (255 ) not null , is_delete tinyint(1 ) default 0 )
4.1.2. 新增两条初始数据 1 2 insert into ev_article_cate(name,alias) values ('科技' ,'keji' )insert into ev_article_cate(name,alias) values ('历史' ,'lishi' )
4.2. 获取文章分类列表 4.2.1. 实现步骤
初始化路由模块
初始化路由处理函数模块
获取文章分类列表数据
4.2.2. 初始化路由模块
创建 /router/artcate.js
路由模块,并初始化如下的代码结构:
1 2 3 4 5 6 7 8 9 10 11 12 const express = require ('express' )const router = express.Router ()router.get ('/cates' , (req, res ) => { res.send ('ok' ) }) module .exports = router
在 app.js
中导入并使用文章分类的路由模块:
1 2 3 4 const artCateRouter = require ('./router/artcate' )app.use ('/my/article' , artCateRouter)
4.2.3. 初始化路由处理函数模块
创建 /router_handler/artcate.js
路由处理函数模块,并初始化如下的代码结构:
1 2 3 4 exports .getArticleCates = (req, res ) => { res.send ('ok' ) }
修改 /router/artcate.js
中的代码如下:
1 2 3 4 5 6 7 8 9 10 const express = require ('express' )const router = express.Router ()const artcate_handler = require ('../router_handler/artcate' )router.get ('/cates' , artcate_handler.getArticleCates ) module .exports = router
4.2.4. 获取文章分类列表数据
在 /router_handler/artcate.js
头部导入数据库操作模块:
1 2 const db = require ('../db/index' )
定义 SQL 语句:
1 2 3 const sql = 'select * from ev_article_cate where is_delete=0 order by id asc'
调用 db.query()
执行 SQL 语句:
1 2 3 4 5 6 7 8 9 10 11 db.query (sql, (err, results ) => { if (err) return res.cc (err) res.send ({ status : 0 , message : '获取文章分类列表成功!' , data : results, }) })
4.3. 新增文章分类 4.3.1. 实现步骤
定义路由和处理函数
验证表单数据
查询 分类名称
与 分类别名
是否被占用
实现新增文章分类的功能
4.3.2. 定义路由和处理函数
在 /router/artcate.js
模块中,添加 新增文章分类
的路由:
1 2 router.post ('/addcates' , artcate_handler.addArticleCates )
在 /router_handler/artcate.js
模块中,定义并向外共享 新增文章分类
的路由处理函数:
1 2 3 4 exports .addArticleCates = (req, res ) => { res.send ('ok' ) }
4.3.3. 验证表单数据
创建 /schema/artcate.js
文章分类数据验证模块,并定义如下的验证规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const joi = require ('joi' )const name = joi.string ().required()const alias = joi.string ().alphanum ().required()exports .add_cate_schema = { body : { name, alias }, }
在 /router/artcate.js
模块中,使用 add_cate_schema
对数据进行验证:
1 2 3 4 5 6 7 const expressJoi = require ('@escook/express-joi' )const { add_cate_schema } = require ('../schema/artcate' )router.post ('/addcates' , expressJoi (add_cate_schema), artcate_handler.addArticleCates )
4.3.4. 查询分类名称与别名是否被占用
定义查重的 SQL 语句:
1 2 const sql = "select * from ev_article_cate where name=? or alias=?" ;
调用 db.query()
执行查重的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 db.query (sql, [req.body .name , req.body .alias ], (err, results ) => { if (err) return res.cc (err) if (results.length === 2 ) return res.cc ('分类名称与别名被占用,请更换后重试!' ) if (results.length === 1 && results[0 ].name === req.body .name ) return res.cc ('分类名称被占用,请更换后重试!' ) if (results.length === 1 && results[0 ].alias === req.body .alias ) return res.cc ('分类别名被占用,请更换后重试!' ) })
4.3.5. 实现新增文章分类的功能
定义新增文章分类的 SQL 语句:
1 const sql = "insert into ev_article_cate set ?"
调用 db.query()
执行新增文章分类的 SQL 语句:
1 2 3 4 5 6 7 8 9 10 db.query (sql, req.body , (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('新增文章分类失败!' ) res.cc ('新增文章分类成功!' , 0 ) })
4.4. 根据 id 删除文章分类 4.4.1. 实现步骤
定义路由和处理函数
验证表单数据
实现删除文章分类的功能
4.4.2. 定义路由和处理函数
在 /router/artcate.js
模块中,添加 删除文章分类
的路由:
1 2 router.get ('/deletecate/:id' , artcate_handler.deleteCateById )
在 /router_handler/artcate.js
模块中,定义并向外共享 删除文章分类
的路由处理函数:
1 2 3 4 exports .deleteCateById = (req, res ) => { res.send ('ok' ) }
4.4.3. 验证表单数据
在 /schema/artcate.js
验证规则模块中,定义 id 的验证规则如下:
1 2 const id = joi.number ().integer ().min (1 ).required()
并使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 exports .delete_cate_schema = { params : { id } }
在 /router/artcate.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 const { delete_cate_schema } = require ('../schema/artcate' )router.get ('/deletecate/:id' , expressJoi (delete_cate_schema), artcate_handler.deleteCateById )
4.4.4. 实现删除文章分类的功能
定义删除文章分类的 SQL 语句:
1 const sql = 'update ev_article_cate set is_delete=1 where id=?'
调用 db.query()
执行删除文章分类的 SQL 语句:
1 2 3 4 5 6 7 8 9 10 db.query (sql, req.params .id , (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('删除文章分类失败!' ) res.cc ('删除文章分类成功!' , 0 ) })
4.5. 根据 id 获取文章分类数据 4.5.1. 实现步骤
定义路由和处理函数
验证表单数据
实现获取文章分类的功能
4.5.2. 定义路由和处理函数
在 /router/artcate.js
模块中,添加 根据 id 获取文章分类
的路由:
1 router.get ('/cates/:id' , artcate_handler.getCateById )
在 /router_handler/artcate.js
模块中,定义并向外共享 根据 Id 获取文章分类
的路由处理函数:
1 2 3 4 exports .getCateById = (req, res ) => { res.send ('ok' ) }
4.5.3. 验证表单数据
在 /schema/artcate.js
验证规则模块中,使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 exports .get_cate_schema = { params : { id } }
在 /router/artcate.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 const { get_cate_schema } = require ('../schema/artcate' )router.get ('/cates/:id' , expressJoi (get_cate_schema), artcate_handler.getCateById )
4.5.4. 实现获取文章分类的功能
定义根据 Id 获取文章分类的 SQL 语句:
1 const sql = 'select * from ev_article_cate where id=?'
调用 db.query()
执行 SQL 语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 db.query (sql, req.params .id , (err, results ) => { if (err) return res.cc (err) if (results.length !== 1 ) return res.cc ('获取文章分类数据失败!' ) res.send ({ status : 0 , message : '获取文章分类数据成功!' , data : results[0 ], }) })
4.6. 根据 id 更新文章分类数据 4.6.1. 实现步骤
定义路由和处理函数
验证表单数据
查询 分类名称
与 分类别名
是否被占用
实现更新文章分类的功能
4.6.2. 定义路由和处理函数
在 /router/article.js
模块中,添加 更新文章分类
的路由:
1 2 router.post ('/updatecate' , artcate_handler.updateCateById )
在 /router_handler/artcate.js
模块中,定义并向外共享 更新文章分类
的路由处理函数:
1 2 3 4 exports .updateCateById = (req, res ) => { res.send ('ok' ) }
4.6.3. 验证表单数据
在 /schema/artcate.js
验证规则模块中,使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 7 8 exports .update_cate_schema = { body : { id, name, alias } }
在 /router/artcate.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 const { update_cate_schema } = require ('../schema/artcate' )router.post ('/updatecate' , expressJoi (update_cate_schema), artcate_handler.updateCateById )
4.6.4. 查询分类名称与别名是否被占用
定义查重的 SQL 语句:
1 2 3 const sql = 'select * from ev_article_cate where id<>? and (name=? or alias=?)'
调用 db.query()
执行查重的操作:
1 2 3 4 5 6 7 8 9 10 11 12 db.query (sql, [req.body .id , req.body .name , req.body .alias ], (err, results ) => { if (err) return res.cc (err) if (results.length === 2 ) return res.cc ('分类名称与别名被占用,请更换后重试!' ) if (results.length === 1 && results[0 ].name === req.body .name ) return res.cc ('分类名称被占用,请更换后重试!' ) if (results.length === 1 && results[0 ].alias === req.body .alias ) return res.cc ('分类别名被占用,请更换后重试!' ) })
4.6.5. 实现更新文章分类的功能
定义更新文章分类的 SQL 语句:
1 const sql = 'update ev_article_cate set ? where id=?'
调用 db.query()
执行 SQL 语句:
1 2 3 4 5 6 7 8 9 10 db.query (sql, [req.body , req.body .id ], (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('更新文章分类失败!' ) res.cc ('更新文章分类成功!' , 0 ) })
5. 文章管理 5.1. 新建 ev_articles 表 1 2 3 4 5 6 7 8 9 10 11 create table ev_articles( id int primary key auto_increment, title varchar (255 ) not null , content longtext not null , cover_img varchar (255 ) not null , pub_date varchar (255 ) not null , state varchar (255 ) not null , is_delete tinyint(1 ) default 0 , cate_id int not null , author_id int )
5.2. 发布新文章 5.2.1. 实现步骤
初始化路由模块
初始化路由处理函数模块
使用 multer 解析表单数据
验证表单数据
实现发布文章的功能
5.2.2. 初始化路由模块
创建 /router/article.js
路由模块,并初始化如下的代码结构:
1 2 3 4 5 6 7 8 9 10 11 12 const express = require ('express' )const router = express.Router ()router.post ('/add' , (req, res ) => { res.send ('ok' ) }) module .exports = router
在 app.js
中导入并使用文章的路由模块:
1 2 3 4 const articleRouter = require ('./router/article' )app.use ('/my/article' , articleRouter)
5.2.3. 初始化路由处理函数模块
创建 /router_handler/article.js
路由处理函数模块,并初始化如下的代码结构:
1 2 3 4 exports .addArticle = (req, res ) => { res.send ('ok' ) }
修改 /router/article.js
中的代码如下:
1 2 3 4 5 6 7 8 9 10 const express = require ('express' )const router = express.Router ()const articleHandler = require ('../router_handler/article' )router.post ('/add' , articleHandler.addArticle ) module .exports = router
5.2.4. 使用 multer 解析表单数据
注意:使用 express.urlencoded()
中间件无法解析 multipart/form-data
格式的请求体数据。
当前项目,推荐使用 multer 来解析 multipart/form-data
格式的表单数据。https://www.npmjs.com/package/multer
运行如下的终端命令,在项目中安装 multer
:
在 /router_handler/article.js
模块中导入并配置 multer
:
1 2 3 4 5 6 7 const multer = require ('multer' )const path = require ('path' )const upload = multer ({ dest : path.join (__dirname, '../uploads' ) })
修改 发布新文章
的路由如下:
1 2 3 4 5 router.post ('/add' , upload.single ('cover_img' ), article_handler.addArticle )
在 /router_handler/article.js
模块中的 addArticle
处理函数中,将 multer
解析出来的数据进行打印:
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 exports .addArticle = (req, res ) => { console .log (req.body ) console .log ('--------分割线----------' ) res.send ('ok' ) })
5.2.5. 验证表单数据
实现思路:通过 express-joi 自动验证 req.body 中的文本数据;通过 if 判断手动验证 req.file 中的文件数据;
创建 /schema/article.js
验证规则模块,并初始化如下的代码结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const joi = require ('@hapi/joi' )const title = joi.string ().required()const cate_id = joi.number ().integer ().min (1 ).required()const content = joi.string ().required().allow ('' )const state = joi.string ().valid ('已发布' , '草稿' ).required()exports .add_article_schema = { body : { title, cate_id, content, state } }
在 /router/article.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 6 7 8 9 10 const expressJoi = require ('@escook/express-joi' )const { add_article_schema } = require ('../schema/article' )router.post ('/add' , upload.single ('cover_img' ), expressJoi (add_article_schema), article_handler.addArticle )
在 /router_handler/article.js
模块中的 addArticle
处理函数中,通过 if
判断客户端是否提交了 封面图片
:
1 2 3 4 5 6 7 exports .addArticle = (req, res ) => { if (!req.file || req.file .fieldname !== 'cover_img' ) return res.cc ('文章封面是必选参数!' ) })
5.2.6. 实现发布文章的功能 插曲:
安装时间格式模块
整理要插入数据库的文章信息对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 const path = require ('path' )const articleInfo = { ...req.body , cover_img : path.join ('/uploads' , req.file .filename ), pub_date : moment ().format ("YYYY-MM-DD HH:mm:ss" ), author_id : req.auth .id }
定义发布文章的 SQL 语句:
1 const sql = 'insert into ev_articles set ?'
调用 db.query()
执行发布文章的 SQL 语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const db = require ('../db/index' )db.query (sql, articleInfo, (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('发布文章失败!' ) res.cc ('发布文章成功' , 0 ) })
在 app.js
中,使用 express.static()
中间件,将 uploads
目录中的图片托管为静态资源:
1 2 app.use ('/uploads' , express.static ('./uploads' ))
5.3. 获取文章列表数据 5.3.1. 实现步骤
初始化路由模块
初始化路由处理函数模块
获取文章分类列表数据
5.3.2. 定义路由和处理函数
在 /router/article.js
路由模块,添加 获取文章的列表数据
路由:
1 2 router.get ('/list' , articleHandler.getArticleList );
在 /router_handler/article.js
模块中,定义并向外共享 获取文章列表
的路由处理函数:
1 2 3 4 exports .getArticleList = (req, res ) => { res.send ('ok' ); }
5.3.3. 验证表单数据
在 /schema/article.js
验证规则模块中,使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 7 8 9 10 11 12 13 exports .get_article_schema = { query :{ pagenum : joi.number ().integer ().min (1 ).required(), pagesize : joi.number ().integer ().min (1 ).required(), cate_id : joi.number ().integer ().min (1 ), state : joi.string ().valid ("已发布" ,"草稿" ) } }
在 /router/article.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 const { get_article_schema } = require ("../schema/article" );router.get ("/list" ,expressJoi (get_article_schema),articleHandler.getArticleList );
5.3.4. 获取文章分类列表数据
在 /router_handler/artcile.js
中定义 SQL 语句:
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 37 38 39 40 41 42 const obj = req.query ;const pagenum = obj.pagenum ;const pagesize = obj.pagesize ;const num = pagesize*(pagenum-1 );var articleInfo = [num,pagesize];var sql = "select " + "a.id, title, pub_date, state, b.name as cate_name " + "from ev_articles as a " + "left outer join ev_article_cate as b " + "on a.cate_id = b.id " + "where a.is_delete=0 " ; var countsql = "select * from ev_articles where is_delete=0" ;if (obj.cate_id ){ articleInfo.unshift (obj.cate_id ); sql += " and a.cate_id=?" ; countsql+=" and cate_id=?" ; } if (obj.state && !obj.cate_id ){ articleInfo.unshift (obj.state ); sql += " and a.state=?" ; countsql += " and state=?" ; } if (obj.state && obj.cate_id ){ articleInfo=[obj.cate_id ,obj.state ,num,pagesize]; sql += " and a.state=?" ; countsql += " and state=?" ; } sql+=" limit ?,?" ;
调用 db.query()
执行 SQL 语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 db.query (sql,articleInfo,(err,results )=> { if (err){ return res.cc (err); } articleInfo.pop (); articleInfo.pop (); db.query (countsql,articleInfo,(err,result )=> { if (err){ return res.cc (err); } res.send ({ status : 0 , message : '获取文章列表成功!' , data : results, total : result.length }) }); })
5.4. 根据id删除文章数据 5.4.1. 实现步骤
定义路由和处理函数
验证表单数据
实现删除文章分类的功能
5.4.2. 定义路由和处理函数
在 /router/article.js
模块中,添加 `删除文章 的路由:
1 2 router.get ('/delete/:id' , articleHandler.deleteArticleById )
在 /router_handler/article.js
模块中,定义并向外共享 删除文章
的路由处理函数:
1 2 3 4 exports .deleteArticleById = (req, res ) => { res.send ('ok' ) }
5.4.3. 验证表单数据
在 /schema/artcate.js
验证规则模块中,定义 id 的验证规则如下:
1 2 const id = joi.number ().integer ().min (1 ).required()
并使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 exports .delete_article_schema = { params : { id } }
在 /router/artcate.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 const { delete_article_schema } = require ('../schema/article' )router.get ('/delete/:id' , expressJoi (delete_article_schema), articleHandler.deleteArticleById )
5.4.4. 实现删除文章的功能
定义删除文章分类的 SQL 语句:
1 const sql = 'update ev_articles set is_delete=1 where id=?'
调用 db.query()
执行删除文章分类的 SQL 语句:
1 2 3 4 5 6 7 8 9 10 db.query (sql, req.params .id , (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('删除文章失败!' ) res.cc ('删除文章成功!' , 0 ) })
5.5. 根据id获取文章详情 5.5.1. 实现步骤
定义路由和处理函数
验证表单数据
实现获取文章详情的功能
5.5.2. 定义路由和处理函数
在 /router/article.js
模块中,添加 根据 id 获取文章详情
的路由:
1 router.get ('/:id' , artcate_handler.getArticleById )
在 /router_handler/article.js
模块中,定义并向外共享 根据 id 获取文章详情
的路由处理函数:
1 2 3 4 exports .getArticleById = (req, res ) => { res.send ('ok' ) }
5.5.3. 验证表单数据
在 /schema/article.js
验证规则模块中,使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 exports .get_article_id_schema = { params : { id } }
在 /router/artcate.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 const { get_article_id_schema } = require ('../schema/article' )router.get ('/:id' , expressJoi (get_article_id_schema), articleHandler.getArticleById )
5.5.4. 实现获取文章的功能
定义根据 id 获取文章分类的 SQL 语句:
1 const sql = 'select * from ev_articles where id=?'
调用 db.query()
执行 SQL 语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 db.query (sql, req.params .id , (err, results ) => { if (err) return res.cc (err) if (results.length !== 1 ) return res.cc ('获取文章分类数据失败!' ) res.send ({ status : 0 , message : '获取文章分类数据成功!' , data : results[0 ], }) })
5.6. 根据id更新文章信息 5.6.1. 实现步骤
定义路由和处理函数
验证表单数据
查询 文章名称
是否被占用
实现更新文章的功能
5.6.2. 定义路由和处理函数
在 /router/article.js
模块中,添加 `更新文章 的路由:
1 2 router.post ('/edit' , articleHandler.updateArticleById )
在 /router_handler/article.js
模块中,定义并向外共享 更新文章
的路由处理函数:
1 2 3 4 exports .updateArticleById = (req, res ) => { res.send ('ok' ) }
5.6.3. 验证表单数据
在 /schema/artcate.js
验证规则模块中,使用 exports
向外共享如下的 验证规则对象
:
1 2 3 4 5 6 7 8 9 10 exports .update_article_schema = { body : { id, title, cate_id, content, state } }
在 /router/artcate.js
模块中,导入需要的验证规则对象,并在路由中使用:
1 2 3 4 5 const { update_article_schema } = require ('../schema/article' )router.post ('/edit' , expressJoi (update_article_schema), articleHandler.updateArticleById )
在 /router_handler/article.js
模块中的 updateArticleById
处理函数中,通过 if
判断客户端是否提交了 封面图片
:
1 2 3 4 5 6 7 exports .updateArticleById = (req, res ) => { if (!req.file || req.file .fieldname !== 'cover_img' ) return res.cc ('文章封面是必选参数!' ) })
5.6.4. 查询文章名称是否被占用
定义查重的 SQL 语句:
1 2 3 const sql = 'select * from ev_article_cate where id<>? and title=?'
调用 db.query()
执行查重的操作:
1 2 3 4 5 6 7 8 9 10 db.query (sql, [req.body .id , req.body .title ], (err, results ) => { if (err) return res.cc (err) if (results.length === 1 ) return res.cc ('文章名称被占用,请更换后重试!' ) })
5.6.5. 实现更新文章分类的功能
整理要更新数据库的文章信息对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 const path = require ('path' )const articleInfo = { ...req.body , cover_img : path.join ('/uploads' , req.file .filename ), pub_date : moment ().format ("YYYY-MM-DD HH:mm:ss" ), author_id : req.auth .id }
定义更新文章分类的 SQL 语句:
1 const sql = 'update ev_articles set ? where id=?'
调用 db.query()
执行 SQL 语句:
1 2 3 4 5 6 7 8 9 10 db.query (sql, [articleInfo, req.body .id ], (err, results ) => { if (err) return res.cc (err) if (results.affectedRows !== 1 ) return res.cc ('修改文章失败!' ) res.cc ('修改文章成功!' , 0 ) })
6. 部署上线 6.1. 将数据库数据转移到服务器 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 create database my_db_01 default character set utf8;use my_db_01; create table ev_users( id int primary key auto_increment, username varchar (255 ) not null , password varchar (255 ) not null , nickname varchar (255 ), email varchar (255 ), user_pic text ) select * from ev_users;create table ev_article_cate( id int primary key auto_increment, name varchar (255 ) not null , alias varchar (255 ) not null , is_delete tinyint(1 ) default 0 ) select * from ev_article_cate where is_delete= 1 and (name= '科技' or alias= 'keji' )select * from ev_article_cate;create table ev_articles( id int primary key auto_increment, title varchar (255 ) not null , content longtext not null , cover_img varchar (255 ) not null , pub_date varchar (255 ) not null , state varchar (255 ) not null , is_delete tinyint(1 ) default 0 , cate_id int not null , author_id int ) select * from ev_articles order by cast (pub_date as datetime) desc ;select a.id, title, pub_date, state, b.name as cate_name from ev_articles as aleft outer join ev_article_cate as bon a.cate_id = b.id where a.is_delete= 0 and a.cate_id= 3 and a.state= '草稿' order by cast (pub_date as datetime) desc limit 0 ,10 ; select count (* ) from ev_articles where is_delete= 0
6.2. 修改数据库连接信息 在 /db/index.js
1 2 3 4 5 6 7 8 const connection = db.createConnection ({ host : "{ip}" , user : "{用户名}" , port : "3306" , password : "{密码}" , database : "my_db_01" })
6.3. 安装证书 在阿里云中下载 Nginx对应证书信息
SSL证书
下载的文件含有 xx.pem 和 xx.key,将文件放入 ssl
文件夹
在 app.js
中让项目启动 https
1 2 3 4 5 6 7 8 9 const https = require ("https" );const fs = require ("fs" );var option = { key : fs.readFileSync ('./ssl/xx.key' ), cert : fs.readFileSync ('./ssl/xx.pem' ) } https.createServer (option,app).listen (3007 ,()=> { console .log ("api server running at post 3007" ); });
6.4. 转移文件 用 Transmit
软件将整个项目上传至服务器
6.5. 重新启动 使用Postman
访问 https://域名:3007/api/login