Node教程——API接口开发(Node版的CRUD通用接口的搭建)(MangoDB+Express_Version2)

2020-05-19

1. 概述

时间跨度有点大,之前就跟大家唠嗑过一些知识点,也开启了一个Node书写一个后台api项目的开始,出于各种原因,迟迟没有更新博文。现在我把这个坑填上,如果你还有阅读过我之前的文章,我建议你先阅读一下

博文地址https://www.cnblogs.com/BM-laoli/p/12708342.html

项目Git地址 https://github.com/BM-laoli/Node-api-Design

在接下来的项目中,我将尽量的以工作时的状态,或者说完全按照公司工作的标准流程,来进行开发,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 设计数据库并且完成连接

这里我们主要干的一个事情就是设计两个集合(表),并且拿到他们的操作对象

  1. 新建一个分支init-database,
git checkout -b init-basese

这里我们新建一个分支,并且配置一个忽略项文件
.gitignore

.DS_Store
node_modules
npm-debug.log
package-lock.json

以上的代码表示我们在git上提交东西的时候可以忽略掉node_module,等文件夹/文件

  1. 在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 )
  1. 提交给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 )

测试结果如下