最佳mock解决方案mongo-expresser源码解析

236 阅读3分钟

源码地址

git clone https://github.com/ouyangsuo/mongo-expresser.git
git clone https://gitee.com/steveouyang/mongo-expresser.git

入口文件app.js

  • 创建expresss实例并挂载到配置的指定端口
  • 配置四大常用中间件:json读取、表单读取、二进制文件读取、静态资源
  • /user前缀的请求派发给userRouter,实现登录与注册
  • /file前缀的请求派发给fileRouter,实现文件上传
  • project.config.json中配置的每个业务模块,生成一个对应的xxRouter,并派发过去
// 引入express
const express = require("express")

// 上传中间件(可以读取二进制数据的请求体了)
const multer = require("multer")

const path = require("path")
const fs = require("fs")

/* 引入核心类【路由生成器RouterGenerator】 */
const RouterGenerator = require("./views/RouterGenerator")

/* 导入子模块路由处理器 */
// 用户路由器,提供了注册+登录功能
const userRouter = require("./views/userRouter")

// 文件路由器,提供了上传功能
const fileRouter = require("./views/fileRouter")

// 导入端口配置 + 路由模块的鉴权中间件配置
const { port,routes } = require("./config")

// 创建应用实例
const app = express()

/* 配置中间件 */
// 从请求体中读取json
app.use(express.json())

// 从请求体中国读取form
app.use(express.urlencoded({extended:false}))

// 从请求体中读取二进制文件数据
// temp=临时文件存储目录
// file=上传表单中携带文件的input的name
// <input type="file" name="file" />
app.use(multer({dest:path.resolve("temp")}).array("file"))

// 静态资源目录为public目录
app.use(express.static(path.resolve("public")))

/* 派发请求给路由器【中间件】 */
// 根据前缀派发路由给具体的路由处理器
app.use("/user",userRouter)
app.use("/file",fileRouter)

/* 
遍历路由表配置 
将/film开头的路由派发给自动生成的"filmRouter"
将/city开头的路由派发给自动生成的"cityRouter"
*/
routes.forEach(
    ({name,middlewares})=>{
        // 为每个业务模块 自动配置xxRouter
        // app.use("/film",filmRouter)
        app.use(`/${name}`,RouterGenerator.from(name,middlewares).generate())
    }
)

// 将应用实例app挂载监听在指定端口
const server = app.listen(
    port,
    ()=>{
        const host = server.address().address
        const port = server.address().port
        console.log("listening at %s:%s",host,port);
    }
)

核心类RouterGenerator(xxRouter生成器)

  • 该框架的核心类
  • 使用方式
app.use(`/film`,RouterGenerator.from("film",middlewares).generate())
  • 提前导入所有中间件,并建立速查表
/* 导入鉴权中间件 */
const loginCheck = require("../middlewares/loginCheck");
const adminCheck = require("../middlewares/adminCheck");

/* 中间件速查表 */
const middlewares = {
  loginCheck,
  adminCheck,
};
  • from方法创建一个RouterGenerator实例,并在上面挂载模块名字+6大接口的中间件列表
  • 无非是根据用户配置的【中间件名称列表】映射为【中间件实体列表】
  /* 静态方法中的this为当前类 */
  static from(collectionName, middlewares = null) {
    // this.collectionName = jsonTemplateName

    return Object.assign(
      // 创建一个空实例,在实例的地址中覆盖一些新配置
      new RouterGenerator(), //实例

      /* 在上述地址中覆盖配置 */
      {
        //实例.collectionName = "file"
        collectionName, 

        /* 
        实例.middlewares = {
          // 给单个添加声明好中间件
          create:[adminCheck,loginCheck]

          ...其它接口及其中间件列表
        }
        */
        middlewares: this.applyMiddlewares(middlewares),
      }
    );
  }

  /* 
  _middlewares定义了每个CRUD接口的中间件列表
  {
      "create": ["adminCheck"],
      "createMany": ["adminCheck"],
      "retrieve": [],
      "retrieveMany": [],
      "update": ["adminCheck"],
      "delete": ["adminCheck"]
  }
  将上面的对象中的中间件名称映射为真正的中间件实体
  {
      "create": [adminCheck],
      "createMany": [adminCheck],
      "retrieve": [],
      "retrieveMany": [],
      "update": [adminCheck],
      "delete": [adminCheck]
  }
  */
  static applyMiddlewares(_middlewares) {
    for (let key in _middlewares) {

      // ["loginCheck","adminCheck",...]
      let names = _middlewares[key];
      // console.log("names=",names);

      // 将名字映射为中间件 配置回去
      _middlewares[key] = names.map(
        // 中间件名字 => 真正的中间件
        (name) => middlewares[name]
      );
    }

    // {loginCheck,adminCheck,...}
    return _middlewares;
  }
  • 生成xxRouter:创建router对象 + 配置6大接口(每个接口都自带一个中间件列表)+ 返回该router
  • 每个接口的实际处理逻辑:直接调用提前封装好的【数据库引擎层接口】实现数据操作
  /* 实例方法:生成xxRouter */
  generate() {
    // 创建filmRouter
    const router = express.Router();

    /* 在filmRouter身上添加了CRUD接口*6 */

    // POST /film/0 = 添加单个film
    // create: [loginCheck,adminCheck,...],
    router.post(
      // /film/0路由
      "/0", 

      // 一堆中间件
      ...this.middlewares?.create, 

      // 真正的业务处理器
      async (req, resp) => {
        // View层直接调用MVCD中的Database引擎层 将请求体数据数据插入数据库
        const result = await doCreate(this.collectionName, req.body);

        // 数据库的操作结果直接返回客户端
        resp.json(result);
      }
    );

    /* POST /film/-1 = 批量插入film */
    router.post("/-1", ...this.middlewares?.createMany, async (req, resp) => {
      const result = await doCreateMany(this.collectionName, req.body);
      resp.json(result);
    });

    /* DELETE /film/:id = 删除单个film */
    router.delete("/:id", ...this.middlewares?.delete, async (req, resp) => {
      const result = await doDelete(req.params.id, this.collectionName);
      resp.json(result);
    });

    /* PUT /film/:id = 修改单个film */
    router.put("/:id", ...this.middlewares?.update, async (req, resp) => {
      const result = await doUpdate(
        req.params.id,
        req.body,
        this.collectionName
      );
      resp.json(result);
    });

    /* GET /film/0 = 查询所有film */
    router.get("/0", ...this.middlewares?.retrieveMany, async (req, resp) => {
      const result = await doRetrieve(this.collectionName, {});
      resp.json(result);
    });

    /* GET /film/:id = 查询单个film详情 */
    router.get("/:id", ...this.middlewares?.retrieve, async (req, resp) => {
      const result = await doRetrieve(this.collectionName, {
        _id: new ObjectId(req.params.id),
      });
      resp.json(result);
    });

    // 返回配置好了CURD接口的filmRouter
    return router;
  }