1.0 前言

我们在实际的开发过程中,肯定会对项目目录结构,开发规范做一些约定的设计,这样项目看起来会比较舒服,别人参与进来的时候,也会比较清晰,而且项目越是复杂,项目目录规范确定下来,清晰,程序才会健壮。以下是笔者在结合实际开发和社区实践梳理出来。

2.0 目录结构

  1. project
  2. └── app
  3. ├── config # 环境配置相关
  4. ├── base.env.js
  5. ├── dev.env.js
  6. ├── prod.env.js
  7. └── qa.env.js
  8. └── index.js
  9. ├── db # 数据库配置相关
  10. └── mysql.js
  11. ├── log4j # 日志配置相关
  12. └── logger.js
  13. ├── controllers # 操作层 执行服务端模板渲染,json接口返回数据,页面跳转
  14. ├── admin.js
  15. ├── index.js
  16. ├── user.js
  17. └── work.js
  18. ├── services # 业务层 实现数据层model到操作层controller的耦合封装
  19. └── user.js
  20. ├── models # 数据模型层 执行数据操作
  21. └── user.js
  22. ├── routers # 路由层 控制路由
  23. └── index.js
  24. |———————page # 页面跳转
  25. └── index.js
  26. |———————api # ajax api 相关
  27. └── v1.js
  28. └── bin
  29. └── www # 项目启动相关
  30. └── views # 服务端模板代码
  31. ├── index.ejs
  32. └── work.ejs
  33. └── static # 静态资源相关
  34. └── app.js
  35. └── package.json

接下里我们详细解释下细分下目录结构的设计。

3.0 MVC 结构设计

在后端代码目录结构中,我们常常采用的是 MVC 结构设计
image.png

  • Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
  • View(视图) - 视图代表模型包含的数据的可视化。
  • Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

把项目分为这样的 3 层,好处就是目录清晰,试想,随着项目代码越来越多,如果没有做好目录划分,代码可维护性是特别差的,接下来我们把我们之前的用户管理改造成符合 MVC 设计的结构。

那现在有一个问题出现了,假设我们有一个业务场景是注册场景,需要先检测手机号,微信号是否已经注册了,注册了的话,假设已经注册了,需要踢出登录用户,然后走注册逻辑,注册完成后,需要缓存用户信息,创建页面调用 api 的 token,如果把这些代码都写到 Controller 层,这个代码会非常多。

所以我们一般都是在 Controller 和 View 层之间,再创建一个 Service 层,专门负责处理这些业务逻辑,保持Controller 的整洁,Controller 层做参数的校验,组合多个 Service 层。而且由于我们现在都是进行的前后端分离开发,用 Ajax 交互,这样 View 层 实际用的非常少了,后端主要是暴露 API 给前端调用,所以模型稍作改动。
如上,我们就变成了这种设计,Sevice 层 来通过 Model 操作数据库,组装 Controller 层需要的数据,然后Controller 返回给页面,总的来说就是我们不要直接在 Controller 操作数据库,把具体的业务逻辑放到 Service 层里面去

接下来我们把之前的用户操作 改造下
步骤一:创建 Model 层
Model 层我们已经创建好了,就是 app/models 下面的 user.js。

  1. const { Sequelize, DataTypes, Model } = require('sequelize')
  2. const { getConnection } = require('../db/mysql')
  3. class User extends Model {
  4. }
  5. User.init(
  6. {
  7. id: {
  8. type: DataTypes.INTEGER({ length: 11, unsigned: true }),
  9. autoIncrement: true,
  10. primaryKey: true,
  11. allowNull: false,
  12. comment: 'id',
  13. },
  14. name: {
  15. type: DataTypes.STRING,
  16. allowNull: false,
  17. comment: '名称',
  18. },
  19. email: {
  20. type: DataTypes.STRING,
  21. allowNull: false,
  22. comment: '邮箱',
  23. },
  24. imgUrl: {
  25. type: DataTypes.STRING,
  26. allowNull: false,
  27. comment: '图片url',
  28. },
  29. }, {
  30. sequelize: getConnection(),
  31. modelName: 'User',
  32. tableName: 'user',
  33. underscored: true, // 转下划线
  34. comment: '用户表',
  35. freezeTableName: true,
  36. timestamps: true,
  37. })
  38. module.exports = User

步骤二:创建 Service 层

  1. // app/services/user.js
  2. const User = require('../models/user')
  3. async function findAll() {
  4. return User.findAll()
  5. }
  6. async function findById(id) {
  7. return User.findByPk(id)
  8. }
  9. async function add(name, email, imgUrl) {
  10. return User.create({ name, email, imgUrl })
  11. }
  12. async function remove(id) {
  13. const data = await User.destroy(id)
  14. return data
  15. }
  16. module.exports = {
  17. findAll,
  18. findById,
  19. add,
  20. remove
  21. }

步骤二:创建 Controller 层

  1. const {
  2. findAll,
  3. findById,
  4. add,
  5. remove
  6. } = require('../services/user')
  7. async function list(ctx) {
  8. const data = await findAll()
  9. ctx.body = {
  10. data: data,
  11. success: true
  12. }
  13. }
  14. async function detail(ctx) {
  15. const id = ctx.params.id
  16. if(!id) {
  17. ctx.body = {
  18. messge: "参数ID不能为空",
  19. success: true
  20. }
  21. return
  22. }
  23. const data = await findById(id)
  24. ctx.body = {
  25. data: data[0],
  26. success: true
  27. }
  28. }
  29. async function add(ctx) {
  30. const { path } = ctx.request.files.file
  31. const { name, email } = ctx.request.body // 获取 request body 字段
  32. const imgUrl = path.split("/static")[1]
  33. if(!name || !email || !imgUrl) {
  34. ctx.body = {
  35. messge: "参数错误",
  36. success: true
  37. }
  38. return
  39. }
  40. const data = await add({ name, email, imgUrl })
  41. ctx.body = {
  42. data: data,
  43. success: true,
  44. }
  45. }
  46. async function remove(params) {
  47. const id = ctx.params.id
  48. const data = await remove(id)
  49. ctx.body = {
  50. data: data[0],
  51. success: true
  52. }
  53. }
  54. module.exports = {
  55. detail,
  56. list,
  57. add,
  58. remove
  59. }

在 Controller 层我们做参数校验 ,组合 Service 层。这样后,在前端页面我们只需要用 Ajax 调用对应的 api 就可以完成整个流程了。

假设没有用前后端分离,使用的是模版引擎,那就对应的 view 层就放在 app/views 目录下。

4.0 静态资源目录

一般我们在 static 目录下放置静态资源相关的,对应 js,图片都单独放到对应目录下面去。
image.png
这样归类就清晰了,以后就知道去哪找对应的资源。

5.0 环境配置相关

开发,测试,生产,不同环境有不同的配置文件,这里我们就需要不同的配置文件,这些信息我们放到 config 目录下面去。

  1. project
  2. └── app
  3. ├── config # 环境配置相关
  4. ├── base.env.js
  5. ├── dev.env.js
  6. ├── prod.env.js
  7. └── qa.env.js
  8. └── index.js

6.0 bin 目录

bin 是放置启动项目相关。

7.0 小结

这节我们学习了 基于 MVC 的结构改造后的,后端项目接口设计,以及各个目录一般放置什么配置文件的使用,使用约定好的配置,可以大大降低 代码耦合,而且结构清晰也是构建大型应用的基石,大家可以对着 Demo 本地跑起来看看,Demo 地址