NodeJS——大彙總(一)(只需要使用這些東西,就能處理80%以上業務需求,全網最全node解決方案,吐血整理)
阿新 • • 發佈:2020-05-28
# 一、前言
## 本文目標
> 本文是博主總結了之前的自己在做的很多個專案的一些知識點,當然我在這裡不會過多的講解業務的流程,而是建立一個小demon,旨在幫助大家去更加高效 更加便捷的生成自己的node後臺介面專案,本文底部提供了一個 藍圖,歡迎大家下載,start,實際上,這樣的一套思路打下來,基本上就已經建立手擼了一個nodejs框架出來了。大多數框架基本上都是這樣構建出來的,底層的Node 第二層的KOA 或者express,第三層就是各種第三方包的加持。
**注意:本文略長,我分了兩個章節**
本文寫了一個功能比較齊全的部落格後臺管理系統,用來演示這些工具的使用,原始碼已經分章節的放在了github之中,連結在文章底部
## 望周知
> 歡迎各位大牛指教,如有不足望諒解,這裡只是提供了一個從express過渡到其它框架的文章,實際上,這篇文章所介紹的工具,也僅僅是工具啦,如果是真實開發專案,我們可能更加青睞於選擇一個成熟穩定的框架,比如AdonisJS(Node版的laravel) ,NestJS(Node版的spring),EggJS.....,我更推薦NestJS,博主後期會出一些Nest教學博文,歡迎關注
至於選擇Nest原因如下
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527210937617-1213529871.png)
# 二、特別提示
## 整體的架構思路
1. 忌諱
> 很多時候大家做為 高技術人才(程式猿單身狗),最忌諱的事情就是什麼都是還不清楚的情況下就去,吧唧的敲程式碼,就從個人的經驗來談,思路這種東西真的非常非常的重要
2. 從更高的層次來看架構的設計
> 一般來講,我們可以從兩個角度來看架構的設計,一個是資料,一個http報文(res,req)
- 資料
我們看看如果從資料的扭轉角度,也就是說,我們站在資料的角度,看看整體的web架構應該如何做才是相對比較合理的.
第一步,我們拿到一個需求,要做的第一件的事情就是分析資料建立模型
第二步,仔細的分析資料的扭轉(如下這裡假設了這樣的一種)
使用者點點選文章的時候,我們能進行資料的聯合查詢,並且把查詢的資料返回給回去
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527210953796-1353840917.png)
- 報文
從報文的角度,看整體的架構,這裡實際上也非常的簡單,就是看看我們的報文到底經過了什麼加工到底得到了什麼樣的資料,看看req,res經歷了什麼,就可以很好的把握 整個的後臺的API設計架構,
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527211012456-482097519.png)
3. 結合
> 開發後臺的時候,對於一個有追求的工程師來說,二者的完美結合才是我們不變的追求,
*更快,更高效,更穩定*
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527211020555-1924499944.png)
## 資料庫建模約定
**我們嚴格約定:Aritcle (庫) => (對應的介面)articles**
> 我們這裡有一些約定是必須要遵守的,我認為在工作中,如果遵守這些規範,可以方便後續的各種業務的操作
### 約定
- 約定1
> 嚴格要求資料庫是單數而且首字母的大寫形式
- 約定2
> 嚴格要求請求的api介面是小寫的複數形式
- 比如
```js
Aritcle (庫) => (對應的介面)articles
```
### 實操
> 好了,有了前面的約定還有理論,現在我們來實操
1. 模型
需求:我希望建立一個部落格網站,部落格網站目前有如下的資料,他們的資料模型圖如下(為了方便我們使用Native的模型設計,但是實際上我們這裡還是使用MongoDB資料庫)
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527211035586-288696807.png)
*以上我們詳細的說明了各個資料之間的關聯操作*
2. 程式碼實現
工程目錄如下
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527211045132-320087029.png)
具體的程式碼實現,這裡講解了如何在mongoose中進行多表(集合)關聯
- 廣告模型
/model/Ad.js
```js
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
name:{type:String},
thumbnails:{type:String},
url:{type:String}
})
module.exports = mongoose.model('Ad',schema)
```
*以下的程式碼大多都是大同小異,我們只列出來Schema規則*
- 管理員模型
/mode/AdminUser.js
```js
const schema = new mongoose.Schema({
username:{type:String},
passowrd:{type:String}
})
```
- 文章模型
/mode/Article.js
```js
const schema = new mongoose.Schema({
title:{type:String},
thumbnails:{type:String},
body:{type:String},
hot:{type:Number},
// 建立時間與更新時間
createTime: {
type: Date,
default: Date.now
},
updateTime: {
type: Date,
default: Date.now
}
// 一篇文章可能同屬於多個分類之下
category:[{type:mongoose.SchemaTypes.ObjectId,ref:'Category'}],
},{
versionKey: false,//這個是表示是否自動的生成__v預設的ture表示生成
// 這個就能做到自動管理時間了,非常的方面
timestamps: { createdAt: 'createTime', updatedAt: 'updateTime' }
})
```
- 欄目模型
/mode/Book.js
```js
const schema = new mongoose.Schema({
iamge:{type:String},
name:{type:String},
body:{type:String},
})
```
- 分類模型
/mode/Category.js
```js
const schema = new mongoose.Schema({
title:{type:String},
thumbanils:{type:String},
//父分類,一篇文章,我們假設一個文章能有一個父分類,一個欄目(書籍)
parent:{type:mongoose.SchemaTypes.ObjectId,ref:'Category'},
book:{type:mongoose.SchemaTypes.ObjectId,ref:'Book'}
})
```
- 評論模型
/mode/Comment.js
```js
const schema = new mongoose.Schema({
body:{type:String},
isPublic:{type:Boolean}
})
```
*他們的模型在這個資料夾下*
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527211107114-334594347.png)
## REST風格約定
**我們全部使用REST風格介面**
> REST全稱是Representational State Transfer,中文意思是表述(編者注:通常譯為表徵)性狀態轉移
> 大白話說就是一種API介面編寫的規範,當然了這裡不詳細的展開敘述,我們來看看有用的
*下面的程式碼就用到了一些常用的RES風格*
> 請不要關注具體的業務邏輯,我們的總店是請求的介面的編寫
```js
// 單一個的post不帶引數就是表示----> 增 (往資源裡面增加些什麼)
router.post('/api/articles', async(req, res) => {
const model = await Article.create(req.body)
// console.log(Article);
res.send(model)
})
// 單一個get不帶引數表示-------> 查 (把資源裡的都查出來)
router.get('/api/articles', async(req, res) => {
const queryOptions = {}
if (Article.modelName === 'Category') {
queryOptions.populate = 'parent'
}
const items = await Article.find().setOptions(queryOptions).limit(10)
res.send(items)
})
//get帶引數表示-------> 指定條件的查
router.get('/api/articles/:id', async(req, res) => {
//我們的req.orane裡面就又東
console.log(req.params.id);
const items = await Article.findById(req.params.id)
res.send(items)
})
// put帶引數表示-------> 更新某個指定的資源資料
router.put('/api/articles/:id', async(req, res) => {
const items = await Article.findByIdAndUpdate(req.params.id, req.body)
res.send(items)
})
// deldete帶引數表示------> 刪除指定的資源資料
router.delete('/api/articles/:id', async(req, res) => {
await Article.findByIdAndDelete(req.params.id, req.body)
res.send({
sucees: true
})
})
```
## message風格約定方案
**我們約定,返回資訊的格式res.status(200).send({ message: '刪除成功' })**
> 我們都知道,再有些情況下,我們的得到的一些結果是差不太多的,有時候,我們希望得到一些格式上統一的資料,這樣就能大大的簡化前端的操作。做為一名優秀的有節操的後臺程式設計師,我們應該與前端約定一些資料的統一返回格式,這樣就能大大的加快,大大的簡化專案的開發
> 比如我習慣把一些操作的資料統一一個格式發出去
*注意:我指的統一,是指沒有實際的資料庫訊息返回的時候,如果有資料,就老老實實返回對應的資料就好了*
1. 假設我們刪除成功了
> 我們返回這樣的資料
```js
res.status(200).send({ message: '刪除成功' })
```
2. 假設我們刪除失敗了
```js
// 程式設計的一個概念:中斷條件
if (!user) {
return res.status(400).send({ message: '刪除失敗' })
}
```
3. 假設我們需要許可權
```js
if (!user) {
return res.status(400).send({ message: '使用者不存在' })
}
```
*以上res.status(400).send({ message: '使用者不存在' })就是我們的約定*
## 中介軟體約定方案
**中介軟體約定方案:我們約定一個規則去搭建我們的中介軟體**
- 假設有這樣的一種情況,我們有一個介面要處理一項非常複雜的業務,使用了非常多的中介軟體,那麼我該如何處理呢,
> 假設我們有一個訪問文章詳情的介面,獲取的這個資料,需要有文章詳情body,文章的tabs,上一篇 下一篇是否存在(也就是判斷資料庫中,文章之前是否還有文章)
```js
// 文章詳情頁,不要關注具體的業務,我這裡想表達的是。如果是多箇中間件,我們就用【】括起來,而且我們嚴格要求所有中介軟體處理之後如果有介面都必須放在req上,這樣我們後續就可以非常方便的拿中介軟體處理的資料了,req物件,再整個node中,還有一個角色(第三方),可以用來做資料的扭轉的工具
articleApp.get('/:id',
[article.getArticleById,
article.getTabs,
article.getPrev,
article.getNext,
category.getList,
auth.getUser],
(req, res) => {
let { article, categories, tabs, prev, next, user } = req
res.send(
{
res:{
// 如果key和value一樣我們可以忽略掉
article:article,
categories:categories,
tabs,
prev,
next,
user
}
}
)
})
```
### 重要的一個話題,錯誤處理中介軟體
> 我們程式執行的時候,可能回報錯,但是我們希望給使用者友好的提示,而不是直接給除報錯資訊,那麼我們可以這樣的來做,定義一個統一的錯誤處理中介軟體
注意啊,由於是整體的錯誤處理中介軟體,於是我們把整個東西放在main中的app下就好了全域性的use一下,捕獲全域性的錯誤
```js
// 錯誤處理中介軟體,統一的處理我們http-assart丟擲的錯誤
app.use(async (err,req,res,next)=>{
// 具體的捕獲到資訊是err中,再伺服器為了排查錯誤,我們打印出來
consel.log(err)
res.status(500).send({
message:'伺服器除問題了~~~請等待修復'
})
})
```
**以上就是我們的第一部分的全部內容**
> 至此我們專案的資料夾如下
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527211122162-1544405854.png)
## 一款非常好用的REST測試外掛
> 這裡介紹了一個非常好用的介面測試工具RESTClinet
/.http
```js
@uri = http://127.0.0.1:3333/api
### 介面測試
GET {{uri}}/test
### 獲取JSON資料
GET {{uri}}/getjson
### 後去六位數驗證碼
GET {{uri}}/getcode
###### 正式的對資料庫操作 #########
### 驗證使用者是否存在
GET {{uri}}/validataName/bmlaoli
### 增:====> 實現使用者註冊
POST {{uri}}/doRegister
Content-Type: application/json
{
"name":"123123",
"gender":"男",
"isDelete":"true"
}
### 刪:====> 根據id進行資料庫的某一項刪除
DELETE {{uri}}/deletes/9
### 改:====> 根據id修改某個資料的具體的值
PATCH {{uri}}/changedata/7
Content-Type: application/json
{
"name":"李仕增",
"gender":"男",
"isDelete":"true"
}
### 查: =====> 獲取最真實的資料
GET {{uri}}/getalldata
### 生成指定的表裡面的項
GET {{uri}}/createTable
```
# 三、進入正題
## 跨域的解決發方案
**cros模組的使用**
> 我們使用一個cros,
```js
const cors = require('cors')
app.use(cors())
```
## 靜態資源的解決方案
**express就好了**
> 我們使用一個express就能解決了
```js
// 檔案上傳的資料夾模組配置,同時也是靜態資源的處理,
app.use('/uploads', express.static(__dirname + '/uploads')) //靜態路由
```
## post請求處理方案
> 對於post的解決方案非常的簡單,我們只需要使用express為我們提供的一些工具就好了
```js
// 以下兩個專門用來處理application/x-www-form-urlencoded,application/json格式的post請求
app.uer(express.urlencoded({extended:true}))
app.use(express.json())
```
## 資料庫解決方案
*講解要點:model操作,connet’,popuerlate查詢語句*
1. 基礎知識
> 這裡我們使用的MongoDB資料庫。我們只需要建立模型之後拿到資料表(集合)的操作模型就可以了,模型我們之前是已經定義過的,非常的簡單,我們只需要建立連結,並且拿來操作就好了
/plugin/db.js
```js
module.exports = app => {
// 使用app有一個好處就是這些項我們都是可以配置的,這個app實際上你寫成option也沒問題
const mongoose = require("mongoose")
mongoose.connect('mongodb://127.0.0.1:27017/Commet-Tools', {
useNewUrlParser: true,
useUnifiedTopology: true
})
}
```
/index.js
```js
require('./plugin/db')(app)
```
2. 假設有一個介面要求查詢資料那麼可以這樣,使用mongoose的ORM方法
```js
router.post('/api/articles', async(req, res) => {
const model = await req.Model.create(req.body)
// console.log(req.Model);
res.send(model)
})
```
## CRUD解決方案
### CRUD業務邏輯
> 這裡我們主要使用
> 我們看看我們目前的專案目錄結構,再看看我們的CRUD業務邏輯程式碼
1. 入口
/index.js
```js
const express = require('express')
const app = express()
// POST解決方案
app.uer(express.urlencoded({extended:true}))
app.use(express.json())
require('./plugin/db')(app)
require('./route/admin/index')(app)
app.listen(3000,()=>{
console.log('http://localhost:3000');
})
```
2. 子路由CRUD介面邏輯所在
/router/admin/index.js
```js
// 單一個的post不帶引數就是表示----> 增 (往資源裡面增加些什麼)
router.post('/api/articles', async(req, res) => {
const model = await Article.create(req.body)
// console.log(Article);
res.send(model)
})
// 單一個get不帶引數表示-------> 查 (把資源裡的都查出來)
router.get('/api/articles', async(req, res) => {
const queryOptions = {}
if (Article.modelName === 'Category') {
queryOptions.populate = 'parent'
}
const items = await Article.find().setOptions(queryOptions).limit(10)
res.send(items)
})
//get帶引數表示-------> 指定條件的查
router.get('/api/articles/:id', async(req, res) => {
//我們的req.orane裡面就又東
console.log(req.params.id);
const items = await Article.findById(req.params.id)
res.send(items)
})
// put帶引數表示-------> 更新某個指定的資源資料
router.put('/api/articles/:id', async(req, res) => {
const items = await Article.findByIdAndUpdate(req.params.id, req.body)
res.send(items)
})
// deldete帶引數表示------> 刪除指定的資源資料
router.delete('/api/articles/:id', async(req, res) => {
await Article.findByIdAndDelete(req.params.id, req.body)
res.send({
sucees: true
})
})
// 使用router 這一步一定不能少
app.use('/api',router)
```
3. 測試結果
![](https://img2020.cnblogs.com/blog/1547034/202005/1547034-20200527211135442-751993356.png)
REST測試檔案如下
```js
@uri = http://localhost:3001/api
### 測試
GET {{uri}}/test
### 增
POST {{uri}}/articles
Content-Type: application/json
{
"title":"測試標題3",
"thumbnails":"http://www.mongoing.com/wp-content/uploads/2016/01/MongoDB-%E6%A8%A1%E5%BC%8F%E8%AE%BE%E8%AE%A1%E8%BF%9B%E9%98%B6%E6%A1%88%E4%BE%8B_%E9%A1%B5%E9%9D%A2_35.png",
"body":"這是我們的測試內容/h1>",
"hot":522
}
### 刪
DELETE {{uri}}/articles/5eca1161017fa61840905206
### 改,僅僅是更改一部分,
PUT {{uri}}/articles/5eca1161017fa61840905206
Content-Type: application/json
{
"category":""
"title":"測試標題2",
"body":"