项目结构及开发规范

WARNING

阅读本小节前,请确保你已经完成了上一节的内容,当然 你非常熟悉 koa 的开发也可直接阅读本小节

项目结构

koa 是一个优美的微框架。这既是一件好事——你可以按照自己的习惯和想法来组织你的项 目,不过对于团队来说这可能是一件坏事——团队每个人都有自己的喜好,这会使整体项目 的结构很混乱。因此我们提供了 starter 模板项目,它是我们团队从诸多项目开发中提 炼而来的一种规范,它不仅仅是结构,风格还有诸多细节,你会在后续逐渐了解到。

app
├── api // api层
│   ├── cms // 关于cms的api
│   │   ├── admin.js
│   │   ├── log.js
│   │   ├── test.js
│   │   └── user.js
│   └── v1 // 普通api
│       └── book.js
├── app.js // 创建koa实例及应用扩展
├── config // 配置文件目录
│   ├── secure.js // 普通配置文件
│   └── setting.js // 安全性配置文件
├── libs // 其它类库
│   ├── errCode.js // 异常类库
│   └── util.js // 助手函数
├── models // 模型层
│   ├── book.js
├── dao // 数据库操作
│   ├── admin.js
│   ├── log.js
│   └── user.js
├── plugins // 插件目录
├── starter.js  // 程序的启动文件
└── validators // 校验层
    ├── admin.js // 校验器模块
    ├── book.js
    ├── common.js
    ├── log.js
    └── user.js

上面是 starter 项目的整体结构,开发时我们强烈建议你遵循如下规范开发,在前期你肯 定会不适应,但慢慢地你会爱上它。

  • app/api 文件夹中开发 API,并将不同版本,不同类型的 API 分开,如:v1 代表 第一版本的 API,v2 代表第二版本,cms 代表属于 cms 的 API。
  • 将程序的配置文件放在 app/config 文件夹下,并着重区分 secure(安全性配置)setting(普通性配置)。配置更详细内容参考配置
  • 将可重用的类库放在 app/libs 文件夹下。
  • 将数据模型放在 app/models 文件夹下。
  • 将开发的插件放在 app/plugins 文件夹下。
  • 将校验类放在 app/validators 文件夹下。

TIP

在你自己的实际开发中你可能不需要dao这一层,对于简单的模型操作,我们建议你直接 在视图函数中操作,而对于复杂的操作,我们建议你为每一类的 router 封装一个 dao。

dao 全称 data access object,主要是负责数据对对象的操作,实际上它也属于模型 层,属于 MVC 中的 M 层。

开发规范

API 规范

koa 的 API 开发规范是一个很棘手的问题,目前社区中知名的基于 koa 二次的开发的 think.js 与 egg.js 都为 API 的开发引入了 Controller 这个概念。但 koa 官方以及它 的生态大多都是以匿名函数的形式来进行 API 的开发的。koa-router 是目前很流行的路由 管理库,它也遵循了匿名函数这一方式。所以 Lin 仍然是选择了与 koa 官方文档一致的做 法,通过匿名函数来进行 API 的开发,从而降低学习成本。

Lin 是一套高可用的 CMS 系统,因为权限的控制是必须的,但是 koa-router 本身并不能 满足我们的需求,因此我们在 koa-router 的基础上扩展除了 LinRouter 这一概念,它 100%兼容了 koa-router 的开发,并且也提供了相应的方式进行权限的配置与管理。

一般的,我们推荐你在一类 API 中新建一个 router(如 Book 这一类,它负责与图书相关 的 API)。如下:

const { LinRouter } = require("lin-mizar");

const bookApi = new LinRouter({
  prefix: "/v1/book"
});

bookApi.get("/:id", async ctx => {
  ctx.json({
    msg: "hello, I am a book"
  });
});

如果你熟悉 koa 和 koa-router,你会发现这几乎与 koa 的标准开发方式一样。新建 router 时,你需传入红图的前缀prefix,如/v1/book,而后红图会自己在访问的 url 中加入/v1/book前缀。当然你可以查看 koa-router 的文档,获得更多的初始化参数。

数据库模型规范

koa 本身并非对数据库做出支持,Lin 通过集成sequelize这个 orm 库来进行数据访问, 如果你不熟悉,请先阅读官方文档

由于 Lin 在核心库中便已经依赖于数据库的操作,因此我们将初始化的Sequelize实例置 放在了核心库中,你可以通过如下方式拿到这个实例:

const { db } = require("lin-mizar/lin/db");
// 使用Sequelize实例
await db.query(...)

异常处理规范

提起异常,大多时候我们都并不想碰见,因为它经常会与程序 crash 一起出现。但它确实 又是程序中不可或缺的一部分,在 Lin 中我们默认集成了全局异常处理机制。因此不论你 程序出现何种异常,都将会返回固定格式的提示信息给前端。对于前端来说,这是非常友好 的一种交互。

在项目开发中我们强力推荐,甚至可以说是要求你在开发的过程中,关于某一类的异常 一定要通过继承HttpException的方式来自定义,这会让前后端的交互更加友好。

当然,当你每自定义一个异常后,别忘记在根目录下的code.md中记录相关异常的 error_code 和 msg,方便前端查阅和团队协作。

数据校验规范

我们强烈建议你为每个有数据校验的接口定义一个相应的校验类。Lin 整合 了validator.js这个好用的校验库,并提供了一个基础的校验类LinValidator来方便参 数的校验。 validator.js 包含了很多的校验函数,你可以查 看官方文档

class BookSearchValidator extends LinValidator {
  constructor() {
    super();
    this.q = new Rule("isNotEmpty", "必须传入搜索关键字");
  }
}

如上,我们定义了一个图书搜索的校验类,在BookSearchValidator类中定义了一个字 段q,并且传入了 q 的校验规则为 isNotEmpty,表示 q 不可为空。该字段会对前端传 入的数据做出校验,若传入的数据中不存在q字段,则会返回前端一个错误信息,错误信 息为必须传入搜索关键字

到这里,你或许未发现校验类的可取之处,因为这个简单的校验直接在视图函数中实现,或 许更为直接和简单。

但是,一旦我们的数据变多,且校验规则变得复杂,如下:

class RegisterValidator extends LinValidator {
  constructor() {
    super();
    this.nickname = [
      new Rule("isNotEmpty", "昵称不可为空"),
      new Rule("length", "昵称长度必须在2~10之间", 2, 10)
    ];
    this.group_id = new Rule("isInt", "分组id必须是整数,且大于0", {
      min: 1
    });
    this.email = [
      new Rule("isOptional"),
      new Rule("isEmail", "电子邮箱不符合规范,请输入正确的邮箱")
    ];
    this.password = [
      new Rule(
        "matches",
        "密码长度必须在6~22位之间,包含字符、数字和 _ ",
        /^[A-Za-z0-9_*&$#@]{6,22}$/
      )
    ];
    this.confirm_password = [
      new Rule(this.passwordCheck.bind(this), "两次密码输入不一致")
    ];
  }

  passwordCheck(val) {
    if (!this.data.password || !this.data.confirm_password) {
      return false;
    }
    let ok = this.data.password === this.data.confirm_password;
    return ok;
  }
}

可以发现,当我们需要校验的参数变得复杂时,一个专注于校验的类可以让我们的代码变得 更易维护,提升代码整体的可读性。

Lin 的校验器十分灵活,详细内容的请参考校验器这一节。

配置规范

在我们的 starter 项目中,统一把项目的配置文件放在了app/config文件夹下。当然, 我们也强烈建议你如此做。不仅如此,由于 Node.js 的特点你必须导出每一个配置项。

"use strict";

module.exports = {
  apiDir: "app/api"
};

如上我们导出了 apiDir 这个配置项,Lin 会自动将配置加载到 config 中,如果你需要扩 展配置,请直接在module.exports中添加其他的配置项,详细内容的请参 考配置这一节。

其他规范

模块的使用

由于 js 的不成熟性,在市面上 js module 充斥着各种各样的解决方案,其中最为广泛的 便是 es6 module 和 commonjs。

node.js 默认支持并强制使用 commonjs 的机制,虽然它也试验性的支持 es6 module,但 这毕竟是试验性的。lin-cms-koa 作为一个 node.js 项目并不想使用 babel 或者 typescript 来增加门槛,我们希望 lin-cms-koa 可以直接在 node.js 环境下使用和部署 ,因此lin-cms-koa是一个符合 commonjs 规范的项目。

commonjs 的模块导入十分简介和方便,这里便不赘述,我们需要规范的是模块的导出。

commonjs 支持两种模块的导出,如下:

单项导出

exports.bookApi = bookApi;

嵌套项导出

module.exports = { bookApi };

lin-cms-koa 建议使用嵌套项导出的方式来导出每一个模块,当你需要扩展的时候只需要 在{}中加入另一项即可,如下:

module.exports = { bookApi, book };

这样对于一个模块我们可以很快的找到它的所有导出项,在可读性和维护性上大大提高。

小结

到此,我们介绍了项目的结构和开发规范。本小节注重的不是项目的开发实现与细节,而是 项目的整体与规范,对于很多人来说,去适应一个规范会觉得不舒服,但对于团队来说,这 是一件必须的事。最后,送大家一句话——越规矩,越自由。

    上次更新: 4/21/2019, 9:59:32 AM