NodeJS——大彙總(一)(只須要使用這些東西,就能處理80%以上業務需求,全網最全node解決方案,吐血整理)

1、前言

本文目標

本文是博主總結了以前的本身在作的不少個項目的一些知識點,固然我在這裏不會過多的講解業務的流程,而是創建一個小demon,旨在幫助你們去更加高效 更加便捷的生成本身的node後臺接口項目,本文底部提供了一個 藍圖,歡迎你們下載,start,實際上,這樣的一套思路打下來,基本上就已經創建手擼了一個nodejs框架出來了。大多數框架基本上都是這樣構建出來的,底層的Node 第二層的KOA 或者express,第三層就是各類第三方包的加持。前端

注意:本文略長,我分了兩個章節node

本文寫了一個功能比較齊全的博客後臺管理系統,用來演示這些工具的使用,源代碼已經分章節的放在了github之中,連接在文章底部laravel

望周知

歡迎各位大牛指教,若有不足望諒解,這裏只是提供了一個從express過渡到其它框架的文章,實際上,這篇文章所介紹的工具,也僅僅是工具啦,若是是真實開發項目,咱們可能更加青睞於選擇一個成熟穩定的框架,好比AdonisJS(Node版的laravel) ,NestJS(Node版的spring),EggJS.....,我更推薦NestJS,博主後期會出一些Nest教學博文,歡迎關注git

至於選擇Nest緣由以下程序員

2、特別提示

總體的架構思路

  1. 忌諱

不少時候你們作爲 高技術人才(程序猿單身狗),最忌諱的事情就是什麼都是還不清楚的狀況下就去,吧唧的敲代碼,就從我的的經驗來談,思路這種東西真的很是很是的重要github

  1. 從更高的層次來看架構的設計

通常來說,咱們能夠從兩個角度來看架構的設計,一個是數據,一個http報文(res,req)web

  • 數據
    咱們看看若是從數據的扭轉角度,也就是說,咱們站在數據的角度,看看總體的web架構應該如何作纔是相對比較合理的.

第一步,咱們拿到一個需求,要作的第一件的事情就是分析數據創建模型
第二步,仔細的分析數據的扭轉(以下這裏假設了這樣的一種)spring

用戶點點擊文章的時候,咱們能進行數據的聯合查詢,而且把查詢的數據返回給回去mongodb

  • 報文
    從報文的角度,看總體的架構,這裏實際上也很是的簡單,就是看看咱們的報文到底通過了什麼加工到底獲得了什麼樣的數據,看看req,res經歷了什麼,就能夠很好的把握 整個的後臺的API設計架構,

  1. 結合

開發後臺的時候,對於一個有追求的工程師來講,兩者的完美結合纔是咱們不變的追求,數據庫

更快,更高效,更穩定

數據庫建模約定

咱們嚴格約定:Aritcle (庫) => (對應的接口)articles

咱們這裏有一些約定是必需要遵照的,我認爲在工做中,若是遵照這些規範,能夠方便後續的各類業務的操做

約定

  • 約定1

嚴格要求數據庫是單數並且首字母的大寫形式

  • 約定2

嚴格要求請求的api接口是小寫的複數形式

  • 好比
Aritcle (庫)  => (對應的接口)articles

實操

好了,有了前面的約定還有理論,如今咱們來實操

  1. 模型
    需求:我但願創建一個博客網站,博客網站目前有以下的數據,他們的數據模型圖以下(爲了方便咱們使用Native的模型設計,可是實際上咱們這裏仍是使用MongoDB數據庫)

以上咱們詳細的說明了各個數據之間的關聯操做

  1. 代碼實現
    工程目錄以下

具體的代碼實現,這裏講解了如何在mongoose中進行多表(集合)關聯

  • 廣告模型
    /model/Ad.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
const schema = new mongoose.Schema({
    username:{type:String},
    passowrd:{type:String}

})
  • 文章模型
    /mode/Article.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
const schema = new mongoose.Schema({
    iamge:{type:String},
    name:{type:String},
    body:{type:String},
})
  • 分類模型
    /mode/Category.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
const schema = new mongoose.Schema({
    body:{type:String},
    isPublic:{type:Boolean}
})

他們的模型在這個文件夾下

REST風格約定

咱們所有使用REST風格接口

REST全稱是Representational State Transfer,中文意思是表述(編者注:一般譯爲表徵)性狀態轉移

大白話說就是一種API接口編寫的規範,固然了這裏不詳細的展開敘述,咱們來看看有用的

下面的代碼就用到了一些經常使用的RES風格

請不要關注具體的業務邏輯,咱們的總店是請求的接口的編寫

// 單一個的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. 假設咱們刪除成功了

咱們返回這樣的數據

res.status(200).send({ message: '刪除成功' })
  1. 假設咱們刪除失敗了
// 程序設計的一個概念:中斷條件
   if (!user) {
        return res.status(400).send({ message: '刪除失敗' })
    }
  1. 假設咱們須要權限
if (!user) {
        return res.status(400).send({ message: '用戶不存在' })
    }

以上res.status(400).send({ message: '用戶不存在' })就是咱們的約定

中間件約定方案

中間件約定方案:咱們約定一個規則去搭建咱們的中間件

  • 假設有這樣的一種狀況,咱們有一個接口要處理一項很是複雜的業務,使用了很是多的中間件,那麼我該如何處理呢,

假設咱們有一個訪問文章詳情的接口,獲取的這個數據,須要有文章詳情body,文章的tabs,上一篇 下一篇是否存在(也就是判斷數據庫中,文章以前是否還有文章)

// 文章詳情頁,不要關注具體的業務,我這裏想表達的是。若是是多箇中間件,咱們就用【】括起來,並且咱們嚴格要求全部中間件處理以後若是有接口都必須放在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一下,捕獲全局的錯誤

// 錯誤處理中間件,統一的處理咱們http-assart拋出的錯誤
    app.use(async (err,req,res,next)=>{

        // 具體的捕獲到信息是err中,再服務器爲了排查錯誤,咱們打印出來
        
        consel.log(err)

        res.status(500).send({
            message:'服務器除問題了~~~請等待修復'
        })
        
    })

以上就是咱們的第一部分的所有內容

至此咱們項目的文件夾以下

一款很是好用的REST測試插件

這裏介紹了一個很是好用的接口測試工具RESTClinet
/.http

@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

3、進入正題

跨域的解決發方案

cros模塊的使用

咱們使用一個cros,

const cors = require('cors')
app.use(cors())

靜態資源的解決方案

express就行了

咱們使用一個express就能解決了

// 文件上傳的文件夾模塊配置,同時也是靜態資源的處理,
app.use('/uploads', express.static(__dirname + '/uploads')) //靜態路由

post請求處理方案

對於post的解決方案很是的簡單,咱們只須要使用express爲咱們提供的一些工具就行了

// 如下兩個專門用來處理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

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

require('./plugin/db')(app)
  1. 假設有一個接口要求查詢數據那麼能夠這樣,使用mongoose的ORM方法
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
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');
})
  1. 子路由CRUD接口邏輯所在
    /router/admin/index.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)
  1. 測試結果

REST測試文件以下

@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>這是咱們的測試內容/h1>",
    "hot":522
}

### 刪
DELETE {{uri}}/articles/5eca1161017fa61840905206

### 改,僅僅是更改一部分,
PUT {{uri}}/articles/5eca1161017fa61840905206
Content-Type: application/json

{
    "category":""
    "title":"測試標題2",
    "body":"<h1>這是咱們的測試內容/h1>",
    "hot":522
}


### 查
GET {{uri}}/articles

### 指定的查
GET {{uri}}/articles/5eca1161017fa61840905206

通用的抽象封裝

inflection

咱們發現,若是是這裏只是指定的一個資源(表-集合)的CRUD,若是說咱們有不少的資源,那麼咱們是不太可能一個一個去複製這些CRUD代碼,所以,咱們想的事情是封裝,封裝成統一的CRUD接口

咱們的思路很是的清晰也很是的簡單,在請求地址中,把資源獲取出來,而後去查對應的資源模塊就行了,這裏咱們須要來回顧一下,咱們以前的接口API規則還有資源命名的規則,articles====> Article,因此,這個命名規則在這裏就用得上了,咱們須要使用一個模塊來處理大小寫首字母的轉化,還有單數複數的轉換inflection

  1. 咱們抽離一箇中間件,放在要通用的CRUD資源請求中
    /middleware/resouce.js
// 咱們但願中間件能夠配置,這樣咱們就能夠高階函數
module.exports = Option=>{
    return async(req, res, next) => {
        const inflection = require('inflection')

        //轉化成單數大寫的字符串形式
        let moldeName = inflection.classify(req.params.resource)
        console.log(moldeName); //categorys ===> Category
        //注意這裏的關聯查詢populate方法,裏面放的就是一個要被關聯的字段
        req.Model = require(`../model/${moldeName}`)
        req.modelNmae = moldeName
        next()
    }
}

/router/admin/index.js

app.use('/api/rest/:resource', resourceMiddelWeare(), router)
  1. 在其餘的資源中把固定寫死的資源表,替換成一個動態的表
    /router/admin/index.js
// 單一個的post不帶參數就是表示----> 增 (往資源裏面增長些什麼)
    router.post('/', async(req, res) => {
      
            const model = await req.Model.create(req.body)
            res.send(model)
       
    })

    // 單一個get不帶參數表示-------> 查 (把資源裏的都查出來)
    router.get('/', async(req, res) => {

        const queryOptions = {}
        if (req.modelName === 'Category') {
            queryOptions.populate = 'parent'
        }
        const items = await req.Model.find().setOptions(queryOptions).limit(10)
        res.send(items)
    })

    //get帶參數表示-------> 指定條件的查
    router.get('/:id', async(req, res) => {
        //咱們的req.orane裏面就又東
        console.log(req.params.id);
        const items = await req.Model.findById(req.params.id)
        res.send(items)
    })

    // put帶參數表示-------> 更新某個指定的資源數據
    router.put('/:id', async(req, res) => {
        const items = await req.Model.findByIdAndUpdate(req.params.id, req.body)
        res.send(items)
    })

    // deldete帶參數表示------> 刪除指定的資源數據
    router.delete('/:id', async(req, res) => {
        await req.Model.findByIdAndDelete(req.params.id, req.body)
        res.send({
            sucees: true
        })
    })

以上就是咱們的一個通用的CRUD接口的編寫方式了

相關文章
相關標籤/搜索