CREATE TABLE `my_db_01`.`ev_articles` (
`Id` int(0) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL COMMENT '文章標(biāo)題',
`content` text NOT NULL COMMENT '文章內(nèi)容',
`cover_img` varchar(255) NOT NULL COMMENT '封面圖片',
`pub_date` varchar(255) NOT NULL COMMENT '文章發(fā)表日期',
`state` varchar(255) NOT NULL COMMENT '文章發(fā)布狀態(tài)',
`is_delete` tinyint(255) NOT NULL DEFAULT 0 COMMENT '0未刪除,1相反',
`cate_id` int(0) NOT NULL COMMENT '分類id',
`author_id` int(0) NOT NULL COMMENT '作者id',
PRIMARY KEY (`Id`),
UNIQUE INDEX(`Id`)
) COMMENT = '文章表';
/router/article.js
路由模塊,并初始化如下的代碼結(jié)構(gòu):// 導(dǎo)入 express
const express = require('express')
// 創(chuàng)建路由對象
const router = express.Router()
// 發(fā)布新文章
router.post('/add', (req, res) => {
res.send('ok')
})
// 向外共享路由對象
module.exports = router
app.js
中導(dǎo)入并使用文章的路由模塊:// 導(dǎo)入并使用文章路由模塊
const articleRouter = require('./router/article')
// 為文章的路由掛載統(tǒng)一的訪問前綴 /my/article
app.use('/my/article', articleRouter)
/router_handler/article.js
路由處理函數(shù)模塊,并初始化如下的代碼結(jié)構(gòu):// 發(fā)布新文章的處理函數(shù)
exports.addArticle = (req, res) => {
res.send('ok')
}
/router/article.js
中的代碼如下:const express = require('express')
const router = express.Router()
// 導(dǎo)入文章的路由處理函數(shù)模塊
const article_handler = require('../router_handler/article')
// 發(fā)布新文章
router.post('/add', article_handler.addArticle)
module.exports = router
注意:使用
express.urlencoded()
中間件無法解析multipart/form-data
格式的請求體數(shù)據(jù)。南澗網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站建設(shè)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)從2013年創(chuàng)立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。
當(dāng)前項(xiàng)目,推薦使用 multer 來解析
multipart/form-data
格式的表單數(shù)據(jù)。https://www.npmjs.com/package/multer
multer
:npm i multer@1.4.2
/router_handler/article.js
模塊中導(dǎo)入并配置 multer
:// 導(dǎo)入解析 formdata 格式表單數(shù)據(jù)的包
const multer = require('multer')
// 導(dǎo)入處理路徑的核心模塊
const path = require('path')
// 創(chuàng)建 multer 的實(shí)例對象,通過 dest 屬性指定文件的存放路徑
const upload = multer({ dest: path.join(__dirname, '../uploads') })
發(fā)布新文章
的路由如下:// 發(fā)布新文章的路由
// upload.single() 是一個(gè)局部生效的中間件,用來解析 FormData 格式的表單數(shù)據(jù)
// 將文件類型的數(shù)據(jù),解析并掛載到 req.file 屬性中
// 將文本類型的數(shù)據(jù),解析并掛載到 req.body 屬性中
router.post('/add', upload.single('cover_img'), article_handler.addArticle)
/router_handler/article.js
模塊中的 addArticle
處理函數(shù)中,將 multer
解析出來的數(shù)據(jù)進(jìn)行打?。?/li>
// 發(fā)布新文章的處理函數(shù)
exports.addArticle = (req, res) => {
console.log(req.body) // 文本類型的數(shù)據(jù)
console.log('--------分割線----------')
console.log(req.file) // 文件類型的數(shù)據(jù)
res.send('ok')
}
在認(rèn)證條件下輸入cover_img,查看返回值
實(shí)現(xiàn)思路:通過 express-joi 自動(dòng)驗(yàn)證 req.body 中的文本數(shù)據(jù);通過 if 判斷手動(dòng)驗(yàn)證 req.file 中的文件數(shù)據(jù);
/schema/article.js
驗(yàn)證規(guī)則模塊,并初始化如下的代碼結(jié)構(gòu):// 導(dǎo)入定義驗(yàn)證規(guī)則的模塊
const joi = require('joi')
// 定義 標(biāo)題、分類Id、內(nèi)容、發(fā)布狀態(tài) 的驗(yàn)證規(guī)則
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('已發(fā)布', '草稿').required()
// 驗(yàn)證規(guī)則對象 - 發(fā)布文章
exports.add_article_schema = {
body: {
title,
cate_id,
content,
state,
},
}
/router/article.js
模塊中,導(dǎo)入需要的驗(yàn)證規(guī)則對象,并在路由中使用:// 導(dǎo)入驗(yàn)證數(shù)據(jù)的中間件
const expressJoi = require('@escook/express-joi')
// 導(dǎo)入文章的驗(yàn)證模塊
const { add_article_schema } = require('../schema/article')
// 發(fā)布新文章的路由
// 注意:在當(dāng)前的路由中,先后使用了兩個(gè)中間件:
// 先使用 multer 解析表單數(shù)據(jù)
// 再使用 expressJoi 對解析的表單數(shù)據(jù)進(jìn)行驗(yàn)證
router.post('/add', upload.single('cover_img'), expressJoi(add_article_schema), article_handler.addArticle)
/router_handler/article.js
模塊中的 addArticle
處理函數(shù)中,通過 if
判斷客戶端是否提交了 封面圖片
:// 發(fā)布新文章的處理函數(shù)
exports.addArticle = (req, res) => {
// 手動(dòng)判斷是否上傳了文章封面
if (!req.file || req.file.fieldname !== 'cover_img') return res.cc('文章封面是必選參數(shù)!')
// TODO:表單數(shù)據(jù)合法,繼續(xù)后面的處理流程...
})
以下的內(nèi)容放到/router_handler/article.js
的處理函數(shù)中
// 導(dǎo)入處理路徑的 path 核心模塊
const path = require('path')
//注意:放入到處理函數(shù)內(nèi)
const articleInfo = {
// 標(biāo)題、內(nèi)容、狀態(tài)、所屬的分類Id
...req.body,
// 文章封面在服務(wù)器端的存放路徑
cover_img: path.join('/uploads', req.file.filename),
// 文章發(fā)布時(shí)間
pub_date: new Date(),
// 文章作者的Id
author_id: req.auth.id,
}
const sql = `insert into ev_articles set ?`
db.query()
執(zhí)行發(fā)布文章的 SQL 語句:// 導(dǎo)入數(shù)據(jù)庫操作模塊
const db = require('../db/index')
// 執(zhí)行 SQL 語句
db.query(sql, articleInfo, (err, results) => {
// 執(zhí)行 SQL 語句失敗
if (err) return res.cc(err)
// 執(zhí)行 SQL 語句成功,但是影響行數(shù)不等于 1
if (results.affectedRows !== 1) return res.cc('發(fā)布文章失敗!')
// 發(fā)布文章成功
res.cc('發(fā)布文章成功', 0)
})
app.js
中,使用 express.static()
中間件,將 uploads
目錄中的圖片托管為靜態(tài)資源:// 托管靜態(tài)資源文件
app.use('/uploads', express.static('./uploads'))
我的踩坑點(diǎn):
multipart/form-data
的格式提交數(shù)據(jù)是因?yàn)?code>cover_img不承認(rèn)提交網(wǎng)絡(luò)圖片鏈接的形式,而是要選擇本地文件,不然是無法讀取到文件類型的,點(diǎn)擊File
即可,postman
的我不清楚。articleInfo
對象中的req.user
一定要改成req.auth
,不然它會(huì)一直提示Cannot read properties of undefined (reading 'id')
的錯(cuò)誤,現(xiàn)在就可以看到我提交成功了。/router_handler/article.js
//文章列表顯示處理函數(shù)
exports.getArticle = (req, res) => {
const sql = 'select * from ev_articles where is_delete=0 order by id asc'
db.query(sql, (err, results) => {
// 1. 執(zhí)行 SQL 語句失敗
if (err) return res.cc(err)
// 2. 執(zhí)行 SQL 語句成功
res.send({
status: 0,
message: '獲取文章列表成功!',
data: results,
})
})
// res.send('ok')
}
/router/article.js
// 獲取文章的列表數(shù)據(jù)
router.get('/list', article_handler.getArticle)
/router/article.js
//根據(jù)ID刪除文章的路由
router.get('/delete/:Id', expressJoi(delete_schema), article_handler.deleteById)
/router_handler/article.js
// 刪除文章分類的處理函數(shù)
exports.deleteById = (req, res) => {
const sql = `update ev_articles set is_delete=1 where Id=?`
db.query(sql, req.params.Id, (err, results) => {
// 執(zhí)行 SQL 語句失敗
if (err) return res.cc(err)
// SQL 語句執(zhí)行成功,但是影響行數(shù)不等于 1
if (results.affectedRows !== 1) return res.cc('刪除文章失敗!')
// 刪除文章分類成功
res.cc('刪除文章成功!', 0)
})
}
/schema/article.js
// 定義 文章Id 的校驗(yàn)規(guī)則
const Id = joi.number().integer().min(1).required()
// 校驗(yàn)規(guī)則對象 - 刪除分類
exports.delete_schema = {
params: {
Id,
},
}
注意大事件項(xiàng)目的art_list
文件的這個(gè)Id不要改成了id,因?yàn)閿?shù)據(jù)庫里面寫的也是Id
/router/article.js
//根據(jù)Id查詢文章
router.get(
'/:Id',
expressJoi(get_articleById_schema),
article_handler.getArticleById
)
/router_handler/article.js
//根據(jù)Id查詢文章的處理函數(shù)
exports.getArticleById = (req, res) => {
const sql = `select * from ev_articles where Id=?`
db.query(sql, req.params.Id, (err, results) => {
// 執(zhí)行 SQL 語句失敗
if (err) return res.cc(err)
// SQL 語句執(zhí)行成功,但是沒有查詢到任何數(shù)據(jù)
if (results.length !== 1) return res.cc('獲取文章數(shù)據(jù)失??!')
// 把數(shù)據(jù)響應(yīng)給客戶端
res.send({
success: true,
status: 0,
message: '獲取文章數(shù)據(jù)成功!',
data: results[0],
})
})
}
/schema/article.js
// 校驗(yàn)規(guī)則對象 - 根據(jù) id 獲取文章
exports.get_articleById_schema = {
params: {
Id,
},
}
/router/article.js
//更新文章的路由
router.post(
'/edit',
uploads.single('cover_img'),
expressJoi(update_article_schema),
article_handler.editArticle
)
/router_handler/article.js
//更新文章的處理函數(shù)
exports.editArticle = (req, res) => {
//先查詢有沒有這個(gè)文章有沒有名稱撞車
const sqlStr = `select * from ev_articles where Id != ? and title = ?`
//執(zhí)行查重操作
db.query(sqlStr, [req.body.Id, req.body.title], (err, results) => {
if (err) return res.cc(err)
if (results[0]) {
return res.cc('文章標(biāo)題不能重復(fù)!')
}
if (!req.file || req.file.fieldname !== 'cover_img')
return res.cc('文章封面是必選參數(shù)!')
// 證明數(shù)據(jù)都是合法的,可以進(jìn)行后續(xù)業(yè)務(wù)邏輯的處理
// 處理文章的信息對象
const articleInfo = {
// 標(biāo)題、內(nèi)容、發(fā)布狀態(tài)、所屬分類的Id
...req.body,
// 文章封面的存放路徑
cover_img: path.join('/uploads', req.file.filename),
// 文章的發(fā)布時(shí)間
pub_date: new Date(),
// 文章作者的Id
author_id: req.auth.id,
}
const sql = `update ev_articles set ? where Id=?`
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)
})
})
}
/schema/article.js
// 校驗(yàn)規(guī)則對象 — 更新文章
exports.update_article_schema = {
body: {
Id,
title,
cate_id,
content,
state,
},
}