Express - API接口1

赵智
2023-12-01

B站视频: https://www.bilibili.com/video/BV1xz4y1X7cE MongoDB 数据库笔记

教你如何使用 node(Express) + mongoose + mongoDB 创建属于自己的 API 接口

1. mongoose

mongoose是nodeJS提供连接 mongodb的一个库. 此外还有mongoskin, mongodb(mongodb官方出品). 本人,还是比较青睐mongoose的, 因为他遵循的是一种, 模板式方法, 能够对你输入的数据进行自动处理. 有兴趣的同学可以去Mongoose官网看看.

  • Schemas

    用来约束MongoDb文档数据(那些字段必须的,那些可选的)

    中文网: http://www.mongoosejs.net/docs/guide.html

  • model

    一个模型对应一个集合,通过模型管理集合中的数据

2. mongoose 的安装

npm init -y 
npm install mongoose --save

yarn add mongoose

3. mongoose 的使用

// 导入mongoose
const mongoose = require('mongoose');
const user = 'goods';
const password = 'goods';
const database = 'mall';
const url = 'mongodb://'+ user + ':' + password + '@localhost:27017/' + database;
const options = {useNewUrlParser: true,useUnifiedTopology: true}
// 连接数据库
const db = mongoose.createConnection(url,options,err => {
  if(err) {
    console.log("---------------------------------");
    console.log("数据库连接失败");
    console.log("---------------------------------");
  } else {
    console.log("---------------------------------");
    console.log("数据库连接成功");
    console.log("---------------------------------");
  }
})
// 定义一个Schema,作为对数据库集合的各个字段类型的规范定义,
// 此外,在添加数据时,如果添加的数据字段没有在Schema中定义,那么该字段数据不会被添加到集合里
const userSchema = mongoose.Schema({
  uname: {
    type: String,
    default: "user_" + new Date().getTime()
  },
  age: {type: Number},
  sex: {type: String}
})

// 设置数据模型(声明是哪个集合,限制字段个数和字段类型)
const User = db.model('user', userSchema)
//增
let user = {
    uname: 'a',
    age: 18,
    sex: '男'
}

new User(user).save()
  .then(res => {
    console.log("res: ",res);
    return res;
  })
  .catch(err => {
    console.log("err: ",err);
    return false;
  })
//查
User.find({}).limit(10)
  .then(res => {
    console.log("res: ",res);
    return res;
  })
  .catch(err => {
    console.log("err: ",err);
    return false;
  })

4. 接口

API,英文全称Application Programming Interface,翻译为“应用程序编程接口”。是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。——百度百科

  • 推荐写法

    {
        meta: {
            msg: '提示信息'
            status: '状态码'
        },
        data: {
            ...
        }
    }
    

5. 接口开发规范

6. 接口测试工具

7. 接口开发 (express)

7.1 express 使用

基于 Node.js 平台,快速、开放、极简的 Web 开发框架

  • 安装

    npm install express --save
    npm install body-parser
    

    yarn add express
    yarn add body-parser
    
  • express使用

    // 1. 引入express模块
    const express = require('express');
    
    // 2. 创建app对象,通过语法expres, 底层原理http模块的createServer
    const app = express();
    
    // 3. 路由
    
    // 导入body-parser用于post方法
    var bodyParser = require('body-parser');
    app.use(bodyParser.json()); // 添加json解析
    app.use(bodyParser.urlencoded({extended: false}));
    
    app.get('/',(request,response) => response.send('hellow'))
    
    // 4. 启动服务监听
    app.listen(3000,() => {
      console.log('http://localhost:3000');
    })
    
    

    启动服务器

    node app.js
    

    访问: http://localhost:3000/

  • 中间件

8. 实战接口开发

req.query

主要获得get请求链接上的参数信息,也就是?后面的参数

例如:127.0.0.1:3000/index?id=110,这种情况下,这种方式是获取客户端get方式传递过来的值,也就是?后面的参数,通过使用req.query.id就可以获得110

req.body

获取请求中存放在body中的参数信息,用的比较多的是获取post请求中body参数信息

例如:127.0.0.1:300/index,然后post了一个id=120的值,这种方式是获取客户端post过来的数据,通过使用req.body.id就可以获得120

  • 创建下面的文件夹和文件(先安装好mongoose,express)

    ├─node_modules
    │
    ├─public
    │
    └─src
    │   │  
    │   ├─controller
    │   │      userr.js
    │   │      
    │   ├─moudels
    │   │      user.js 
    │   │
    │   ├─routes
    │   │ 	   user.js 
    │   │ 
    │   │  app.js
    │
    │  package-lock.json
    │  package.json       
    
  • app.js

    主入口文件

    // 引入express模块
    const express = require('express');
    const bodyParser = require('body-parser');
    // 设置端口
    const port = 3000;
    // 创建app对象,通过语法expres, 底层原理http模块的createServer
    const app = express();
    
    // 导入body-parser中间件,用于post方法
    app.use(bodyParser.json()); // 添加json解析
    app.use(bodyParser.urlencoded({ extended: false }));
    
    //设置跨域访问
    app.all('*', (req, res, next) => {
      res.header('Access-Control-Allow-Origin', '*');
      res.header('Access-Control-Allow-Headers', 'X-Requested-With');
      res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
      res.header('X-Powered-By', ' 3.2.1');
      next();
    });
    
    //处理静态目录,访问静态文件
    app.use('/static', express.static('public'));
    // 用户路由
    app.use('/api/user', require('./routes/user.js'));
    
    // 资源不存在(写在所有路由的最后)
    app.get('*', (req, res, next) => {
      res.json({
        meta: {
          status: 404,
          msg: '所请求的资源不存在或不可用',
        },
        data: null,
      });
    });
    
    // 启动服务监听
    app.listen(port, () => {
      console.log('http://localhost:3000');
    });
    
  • routes/user.js

    用于分发路由

    const express = require('express');
    const router = express.Router();
    
    //process.cwd(): 返回Node.js进程的当前工作目录,就是node命令运行的那个文件夹的路径
    const userController = require(process.cwd() + '/src/controller/user.js');
    
    router.post('/login', (req, res, next) => userController.userLogin(req, res, next));
    router.post('/register', (req, res, next) => userController.userRegister(req, res, next));
    
    module.exports = router;
    
  • controller/user.js

    实现逻辑部分

    // 导入数据库模型
    const userMoudles = require(process.cwd() + '/src/moudles/user.js');
    
    /**
     * 用户注册
     */
    const userRegister = async (req, res, next) => {
      // 1. 拿到请求过来的数据
      let user = req.body;
      // 2. 验证数据是否正确
      if (Object.keys(user).length == 0) {
        return res.json({
          meta: {
            status: 400,
            msg: '参数为空',
          },
          data: null,
        });
      }
      if (!(user.userName && user.passWord && user.cellularPhone)) {
        return res.json({
          meta: {
            status: 400,
            msg: '参数错误',
          },
          data: null,
        });
      }
      // 3. 调用操作数据库的方法
      let b1 = await isUserNameUnique(user.userName);
      if (!b1) {
        return res.json({
          meta: {
            status: 400,
            msg: 'userName重复',
          },
          data: null,
        });
      }
      let b2 = await isCellularPhoneUnique(user.cellularPhone);
      if (!b2) {
        return res.json({
          meta: {
            status: 400,
            msg: 'cellularPhone重复',
          },
          data: null,
        });
      }
      let rs = await userMoudles.userRegister({
        userBaseInfo: user,
    	//...
      });
      // 4. 返回信息
      if (rs) {
        return res.json({
          meta: {
            status: 200,
            msg: '注册成功',
          },
          data: null,
        });
      } else {
        return res.json({
          meta: {
            status: 500,
            msg: '服务器错误,注册失败',
          },
          data: null,
        });
      }
    };
    
    /**
     * 用户登录
     */
    const userLogin = async (req, res, next) => {
      // 1. 拿到请求过来的数据
      let user = req.body;
      // 2. 验证数据是否正确
      if (Object.keys(user).length == 0) {
        return res.json({
          meta: {
            status: 400,
            msg: '参数为空',
          },
          data: null,
        });
      }
      if (!(user.userName && user.passWord)) {
        return res.json({
          meta: {
            status: 400,
            msg: '参数错误',
          },
          data: null,
        });
      }
      // 3. 调用操作数据库的方法
      let rs = await userMoudles.userLogin(user.userName, user.passWord);
      //这里 rs 返回的是一个数组
      // 4. 返回信息
      if (rs) {  //返回的不是一个数组,是null,那么意味着系统错误
        rs = rs[0];
        if (rs) {  //返回的数组为空,没有查询到任何信息,那么就是用户名或密码错误.
           return res.json({
              meta: {
                status: 200,
                msg: '登录成功',
              },
              data: null,
            });
        } else {
          return res.json({
            meta: {
              status: 400,
              msg: '用户名或密码错误',
            },
            data: null,
          });
        }
      } else {
        return res.json({
          meta: {
            status: 500,
            msg: '系统错误,登录失败',
          },
          data: null,
        });
      }
    };
    
    const isUserNameUnique = async (userName) => {
      let num = await userMoudles.countUserName(userName);
      return num == 0;
    };
    const isCellularPhoneUnique = async (cellularPhone) => {
      let num = await userMoudles.countCellularPhone(cellularPhone);
      return num == 0;
    };
    
    module.exports = {
      userRegister,
      userLogin,
    };
    
  • moudels/user.js

    连接数据库, 获取数据

    // 导入数据库连接对象
    const { mongoose, db } = require(process.cwd() + '/src/utils/db.js'); //es6 对象的解构
    
    // 定义一个Schema,作为对数据库集合的各个字段类型的规范定义,
    // 此外,在添加数据时,如果添加的数据字段没有在Schema中定义,那么该字段数据不会被添加到集合里
    const UserSchema = mongoose.Schema({
      userBaseInfo: { type: Object },
      //...
    });
    
    // 设置数据模型(声明是哪个集合,限制字段个数和字段类型)
    const User = db.model('user', UserSchema);
    
    // 创建实例操作(CURD)
    const userRegister = (user) => {
      return new User(user)
        .save()
        .then((res) => {
          console.log('insertUser: success');
          // console.log(res);
          return res;
        })
        .catch((err) => {
          console.log('insertUser: fail\n', err);
          return false;
        })
        .finally(() => {
          // db.close();
        });
    };
    
    const countUserName = (userName) => {
      return User.find({ 'userBaseInfo.userName': userName })
        .countDocuments()
        .then((res) => {
          console.log('isUserNameUnique: success', res);
          return res;
        })
        .catch((err) => {
          console.log('isUserNameUnique: fail\n', err);
          return false;
        })
        .finally(() => {
          // db.close();
        });
    };
    const countCellularPhone = (cellularPhone) => {
      return User.find({ 'userBaseInfo.cellularPhone': cellularPhone })
        .countDocuments()
        .then((res) => {
          console.log('isCellularPhoneUnique: success', res);
          return res;
        })
        .catch((err) => {
          console.log('isCellularPhoneUnique: fail\n', err);
          return false;
        })
        .finally(() => {
          // db.close();
        });
    };
    
    const userLogin = (userName, passWord) => {
      return User.find({ 
          'userBaseInfo.userName': userName, 
          'userBaseInfo.passWord': passWord 
      })
        .then((res) => {
          console.log('userLogin: success');
          // console.log(res);
          return res;
        })
        .catch((err) => {
          console.log('userLogin: fail\n', err);
          return false;
        })
        .finally(() => {
          // db.close();
        });
    };
    
    
    module.exports = {
      userRegister,
      userLogin,
      countUserName,
      countCellularPhone,
    };
    
  • utils/db.js

    把连接数据库的部分代码封装起来

    // 导入mongoose
    const mongoose = require('mongoose');
    // 连接用户信息
    const user = 'goods';
    const password = 'goods';
    const database = 'mall';
    const url = 'mongodb://' + user + ':' + password + '@localhost:27017/' + database;
    const options = { useNewUrlParser: true, useUnifiedTopology: true };
    
    // 连接数据库
    const db = mongoose.createConnection(url, options, (err) => {
      if (err) {
        console.log('---------------------------------');
        console.log('数据库连接失败', err);
        console.log('---------------------------------');
      } else {
        console.log('---------------------------------');
        console.log('数据库连接成功');
        console.log('---------------------------------');
      }
    });
    mongoose.set('useCreateIndex', true);
    
    module.exports = {
      mongoose,
      db,
    };
    

启动服务器

node app.js

用接口测试工具测试接口

9. 实战接口文档(apiDoc)

  • 接口文档

    接口的说明书

  • apiDoc(https://apidocjs.com/

    node.js 中的一个模块,可以快速生成接口文档

    前提: 写接口的时候写好注释

  • 使用

    1. 下载模块,后期通过命令基于注释生成文档(仅一次)

      npm install apidoc -g
      
    2. 在项目根目录创建 apidoc.json 文件(仅一次)

      {
        "name": "example",
        "version": "0.1.0",
        "description": "apiDoc basic example",
        "title": "Custom apiDoc browser title",
        "url" : "https://api.github.com/v1"
      }
      
      

      我的 apidoc.json 文件

      {
        "name": "接口",
        "version": "1.1.0",
        "description": "一个简单的接口",
        "title": "Custom apiDoc browser title",
        "url": "http://localhost:3000"
      }
      
    3. 去写注释(N次)

      /**
       * @api {get} /user/:id Request User information
       * @apiName GetUser
       * @apiGroup User
       *
       * @apiParam {Number} id Users unique ID.
       *
       * @apiSuccess {String} firstname Firstname of the User.
       * @apiSuccess {String} lastname  Lastname of the User.
       */
      
      @api {method} path [title]
        只有使用@api标注的注释块才会在解析之后生成文档,title会被解析为导航菜单(@apiGroup)下的小菜单
        method可以有空格,如{POST GET}
      @apiGroup name
        分组名称,被解析为导航栏菜单
      @apiName name
        接口名称,在同一个@apiGroup下,名称相同的@api通过@apiVersion区分,否者后面@api会覆盖前面定义的@api
      @apiDescription text
        接口描述,支持html语法
      @apiVersion verison
        接口版本,major.minor.patch的形式
        
      @apiIgnore [hint]
        apidoc会忽略使用@apiIgnore标注的接口,hint为描述
      @apiSampleRequest url
        接口测试地址以供测试,发送请求时,@api method必须为POST/GET等其中一种
      
      @apiDefine name [title] [description]
        定义一个注释块(不包含@api),配合@apiUse使用可以引入注释块
        在@apiDefine内部不可以使用@apiUse
      @apiUse name
        引入一个@apiDefine的注释块
      
      @apiParam [(group)] [{type}] [field=defaultValue] [description]
      @apiHeader [(group)] [{type}] [field=defaultValue] [description]
      @apiError [(group)] [{type}] field [description]
      @apiSuccess [(group)] [{type}] field [description]
        用法基本类似,分别描述请求参数、头部,响应错误和成功
        group表示参数的分组,type表示类型(不能有空格),入参可以定义默认值(不能有空格)
      @apiParamExample [{type}] [title] example
      @apiHeaderExample [{type}] [title] example
      @apiErrorExample [{type}] [title] example
      @apiSuccessExample [{type}] [title] example
        用法完全一致,但是type表示的是example的语言类型
        example书写成什么样就会解析成什么样,所以最好是书写的时候注意格式化,(许多编辑器都有列模式,可以使用列模式快速对代码添加*号)
        
      @apiPermission name
        name必须独一无二,描述@api的访问权限,如admin/anyone
      
    4. 生成文档

      apidoc -i ./接口注释目录 -o ./接口文档存放目录
      

      例:

      apidoc -i controller/ -o apido
      
 类似资料: