1. 概述
時間跨度有點大,之前就跟大家嘮嗑過一些知識點,也開啟了一個Node書寫一個后台api項目的開始,出於各種原因,遲遲沒有更新博文。現在我把這個坑填上,如果你還有閱讀過我之前的文章,我建議你先閱讀一下
博文地址https://www.cnblogs.com/BM-laoli/p/12708342.html
項目Git地址 https://github.com/BM-laoli/Node-api-Design 所有代碼已經分章節的放在了github供需要的朋友參考
在接下來的項目中,我將盡量的以工作時的狀態,或者說完全按照公司工作的標准流程,來進行開發,git上分章節,創建分支都是必須學的基本技能,
2. 初始化(github相關)
我們這里假設一種情況,就是你進到一家公司,公司新的項目還沒有開始,目前是做的舊的項目,(假設舊的項目在github上),突然你的上司跟你說,你今天做一個對某項目(這里假設舊是我們的design)做一個CRUD接口,然后你上司丟給你一個gihub地址,那你怎么辦?蒙圈了???,你這個時候去問人....就有點傻逼了,因為你這都不會,你還來公司干啥?干坐着?
好了毒雞湯灌輸完畢,接下里我們就來開始我們的項目
2.1. 設計架構的更新
- 首先我們看看我們的之前的設計圖,再看看我們現在的總體架構圖
2.2 看看我們接下需要做哪些事情,
我們希望寫幾個接口對兩個數據表進行操作這兩個表是 article 還有user
- 關於架構的設計模式還有一些規則,
大佬不要噴我,這里就說一下,我的個人觀點,項目的架構,基本上都是MVC,既然是MVC那么這里就得非常的明確的分出來,(如果你學習過后台的語言比如php或者java)那么這個應該能非常好理解,在node中的架構是這樣的,
我們站在http發送的報文角度,看看我們的的報文從客戶端發到我們node服務器,經歷了哪些事情
--> 進入路由 -----> 路由接受到之后做業務處理,這里就是(中間件)業務處理的邏輯就是在中間件做的-------> 假設你的http報文要請求數據庫的一些字段,那么久需要用到model層<------數據庫的dao層(在node中你可能使用的別人大神給你寫好的dao層比如mongoose,或則Knex.js等,總而言之這些東西都是為model服務的) ------>中間件拋出數據next到我們的路由上------->路由通過res.send把數據丟出去,提示:在node中我們最常用的就是把傳出去的數據整成json格式,如果你還不會,別着急我們一點點來學
2.3 克隆項目下來
git clone https://github.com/BM-laoli/Node-api-Design.git
3. 進入業務之前的說明
3.1 想清楚再敲代碼,別一上來就吧唧敲一頓
我們看看啊,我們的大體的流程,理解整個的流程,丟代碼的書寫非常的重要,這樣你就能清晰的知道自己到底在干嘛了.
還有一點需要說明的是:這里我們遵守這樣的一個命名規范:數據模型是大寫單數,路由參數是小寫復數比如
Article ====> articles
3.2 接口測試工具
我們使用一個vs上的一個插件這個插件是下面的樣子,在項目中,我講一點點的教大家使用它
4. 進入業務
4.1 設計數據庫並且完成連接
這里我們主要干的一個事情就是設計兩個集合(表),並且拿到他們的操作對象
- 新建一個分支init-database,
git checkout -b init-basese
這里我們新建一個分支,並且配置一個忽略項文件
.gitignore
.DS_Store
node_modules
npm-debug.log
package-lock.json
以上的代碼表示我們在git上提交東西的時候可以忽略掉node_module,等文件夾/文件
- 在molde下設計一個article表(集合),它的規則(schema)如下
/model/Article
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
title:{ type:String },
body:{type:String}
})
module.exports = mongoose.model( 'Article',schema )
- 提交給git分支,並且合並一下代碼待maseter上
git push origin init-basese
4.2 實現丟article表的增刪改查(CRUD)
首先我們依然是git出來一個分支,我們在CRUD分支下進行操作
git checkout -b CRUD
注意,這里我在原來的基礎上做了一些修改,因為使用restClinet的時候有些字段不能讀取出來,所有我修改了下面的代碼
- 在main下 我把原來的index接口改掉了,改造成了下面的這樣的格式,然后在home.index.js中也稍微做了一下修改
/main.js
require('./API/home/index')(admin)
實際上這樣的寫法等價於。
main.use(require('./API/home/index'))
/home/index.js
module.exports = admin => {
const express = require('express')
const router = express.Router({})
const Article = require('../../../../model/Article')
//測試路由
router.get('/test',(req,res)=>{
res.send('ok')
})
//這句use一定要加
admin.use('/CRUD',router)
}
- 在路由驗證器的東西上,我改掉了header.token。因為我在發生restClint的時候,不走token,而是加在了Authorization字段中
/Mideleware/loginPash.js
let token = req.headers.authorization;
- 測試結果,注意這這里我使用了restClint插件,通過下面的圖,你應該可以非常清晰的看到如何使用這個插件。非常方便的做接口測試
接下里就是正式的增刪改查業務流程了
特別說明:這里我們編寫的接口要求符合REST風格,至於說明是REST風格請去百度,它看起來就是下面的這個樣子
更具REST風格來說。PUT表示更新,POST表示增加,DELET表示刪除,GET分為帶參數還有不帶參數的情況,不帶參數表示全查,待參數表示查指定內容
@uri = http://localhost:3000/main/CRUD
@toke = eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiNWU5MTIwMTViOWI0NmYzZmE4Y2MzMjUzIiwiZXhwIjoxNTg5ODU0NTI5LCJpYXQiOjE1ODk4NTI3Mjl9.U0N1bZLUBiy2fmz1NqAF-tTelE7uO8Xld4HHFeBsbq7iY5rSNMByYUyxhC1k8ug9n_8XzChL81ruiFuwxIcOg7DXk_hdrnvpUgwwtjIPjn4kfWWVEsZGm456fo3L5cHfppOTWH5NQAtKEd7xwrhqPrEPsBUwAc7pKEAFDaGuFOc
###
GET http://localhost:3000/main/test
### 進行token校驗拿到我們的token,只有拿到token之后才允許進入下面的請求環節
POST http://localhost:3000/main/login
Content-Type: application/json
{
"email":"18376621755@163.com",
"password":"123456"
}
### 我們需要攜帶token才能發起其它的請求
GET {{uri}}/test
Authorization: {{toke}}
### 增
POST {{uri}}/Article/
Content-Type: application/json
Authorization:{{toke}}
{
"title":"分類測試3",
"body":"<h1>你好我是標題2</h1>"
}
### 刪
DELETE {{uri}}/Article/5ec330fe21d3d97d5054e5bf
Authorization: {{toke}}
### 改
PUT {{uri}}/Article/5ec32f60cd080550a4e2fba8
Content-Type: application/json
Authorization:{{toke}}
{
"title":"修改測試--222",
"body":"<h1>你好我是標題2</h1>"
}
### 查
GET {{uri}}/Article
Authorization:{{toke}}
### 查 根據id查
### 特別小心由於你在后台使用了express的動態路由,所以你不必加id=xxx
GET {{uri}}/Article/5ec32f60cd080550a4e2fba8
Content-Type: application/json
Authorization:{{toke}}
/home/index.js
module.exports = admin => {
const express = require('express')
const router = express.Router({
mergeParams: true //合並所有的url,如果你合並那么那個動態的resoulce你是拿不到的
})
const Article = require('../../../../model/Article')
//測試路由
router.get('/test',(req,res)=>{
res.send('ok')
})
//增
router.post('/Article', async (req, res) => {
const model = await Article.create(req.body)
res.send(model)
})
//查
router.get('/Article', async(req, res,next) => {
const items = await Article.find().limit(10)
res.send(items)
next()
})
//根據id查
router.get('/Article/:id', async(req, res) => {
console.log('asdasdasd');
console.log(req.params.id);
const items = await Article.findById(req.params.id)
res.send(items)
})
//更新(改)
router.put('/Article/:id', async(req, res) => {
const items = await Article.findByIdAndUpdate(req.params.id, req.body)
res.send(items)
})
//根據id參數(刪)
router.delete('/Article/:id', async(req, res) => {
await Article.findByIdAndDelete(req.params.id, req.body)
res.send({
sucees: true
})
})
admin.use('/CRUD',router)
}
4.3 使用RESTClinet進行接口測試
- 測試結果如下:完美通過
4.4 收尾上傳分支到git
git push origin CRUD
4.4 抽離成一個通用的CRUD
現在老板又給我嗎要求了,我們需要對一個名字叫做product集合(表),進行增刪改查,那么問題來了?我們難道又要寫一個與上代碼類似的接口嗎?
答案是非必要的,我們可以把上面的接口改成通用的CRUD,這樣我們就能很方便的使用它了,有新的需求來的時候,只需要修改數據模型就好了
- 首先,我們在路由的地方做一層封裝
這里呢,我們就使用到了,之前的命名約定規范,(命名規范非常的重要!!!),我們使用一個第三方插件,實現大小寫以及單復數的轉換,然后根據轉換之后的結果去找對應的 數據模型就好了
module.exports = admin => {
const express = require('express')
const inflection = require('inflection')
const router = express.Router({
mergeParams: true //合並所有的url,如果你合並那么那個動態的resoulce你是拿不到的
})
++++
//起一個到哪個台的資源就好了,注意我們的中間件
admin.use('/CRUD/rest/:resource', async(req, res, next) => {
//轉化成單數大寫的字符串形式
let moldeName = inflection.classify(req.params.resource)
console.log(moldeName); //categorys ===> Category
//注意這里的關聯查詢populate方法,里面放的就是一個要被關聯的字段
req.Model = require(`../../../../model/${moldeName}`)
req.modelNmae = moldeName
next()
}, router)
}
設置好一層用來的過濾中間件之后,那么我們的CRUD就變成了一個通用的接口了,后續的操作API只需要變化接口的名字,變化數據庫模型就好了,非常的方便,
//增
router.post('/', async (req, res) => {
const model = await req.Model.create(req.body)
res.send(model)
})
//查
router.get('/', async(req, res,next) => {
const items = await req.Model.find().limit(10)
res.send(items)
next()
})
//根據id查
router.get('/:id', async(req, res) => {
console.log('asdasdasd');
console.log(req.params.id);
const items = await req.Model.findById(req.params.id)
res.send(items)
})
//更新(改)
router.put('/:id', async(req, res) => {
const items = await req.Model.findByIdAndUpdate(req.params.id, req.body)
res.send(items)
})
//根據id參數(刪)
router.delete('/:id', async(req, res) => {
await req.Model.findByIdAndDelete(req.params.id, req.body)
res.send({
sucees: true
})
})
接下來我們新建一個product模型進行測試
/model/Prodcut
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
product_name:{ type:String },
category:{type:String}
})
module.exports = mongoose.model( 'Product',schema )
測試結果如下