1.0 前言
我们在实际的开发过程中,肯定会对项目目录结构,开发规范做一些约定的设计,这样项目看起来会比较舒服,别人参与进来的时候,也会比较清晰,而且项目越是复杂,项目目录规范确定下来,清晰,程序才会健壮。以下是笔者在结合实际开发和社区实践梳理出来。
2.0 目录结构
project
└── app
├── config # 环境配置相关
│ ├── base.env.js
│ ├── dev.env.js
│ ├── prod.env.js
│ └── qa.env.js
│ └── index.js
├── db # 数据库配置相关
│ └── mysql.js
├── log4j # 日志配置相关
│ └── logger.js
├── controllers # 操作层 执行服务端模板渲染,json接口返回数据,页面跳转
│ ├── admin.js
│ ├── index.js
│ ├── user.js
│ └── work.js
├── services # 业务层 实现数据层model到操作层controller的耦合封装
│ └── user.js
├── models # 数据模型层 执行数据操作
│ └── user.js
├── routers # 路由层 控制路由
│ └── index.js
|———————page # 页面跳转
│ └── index.js
|———————api # ajax api 相关
│ └── v1.js
└── bin
└── www # 项目启动相关
└── views # 服务端模板代码
├── index.ejs
└── work.ejs
└── static # 静态资源相关
└── app.js
└── package.json
3.0 MVC 结构设计
在后端代码目录结构中,我们常常采用的是 MVC 结构设计
- 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。
const { Sequelize, DataTypes, Model } = require('sequelize')
const { getConnection } = require('../db/mysql')
class User extends Model {
}
User.init(
{
id: {
type: DataTypes.INTEGER({ length: 11, unsigned: true }),
autoIncrement: true,
primaryKey: true,
allowNull: false,
comment: 'id',
},
name: {
type: DataTypes.STRING,
allowNull: false,
comment: '名称',
},
email: {
type: DataTypes.STRING,
allowNull: false,
comment: '邮箱',
},
imgUrl: {
type: DataTypes.STRING,
allowNull: false,
comment: '图片url',
},
}, {
sequelize: getConnection(),
modelName: 'User',
tableName: 'user',
underscored: true, // 转下划线
comment: '用户表',
freezeTableName: true,
timestamps: true,
})
module.exports = User
步骤二:创建 Service 层
// app/services/user.js
const User = require('../models/user')
async function findAll() {
return User.findAll()
}
async function findById(id) {
return User.findByPk(id)
}
async function add(name, email, imgUrl) {
return User.create({ name, email, imgUrl })
}
async function remove(id) {
const data = await User.destroy(id)
return data
}
module.exports = {
findAll,
findById,
add,
remove
}
步骤二:创建 Controller 层
const {
findAll,
findById,
add,
remove
} = require('../services/user')
async function list(ctx) {
const data = await findAll()
ctx.body = {
data: data,
success: true
}
}
async function detail(ctx) {
const id = ctx.params.id
if(!id) {
ctx.body = {
messge: "参数ID不能为空",
success: true
}
return
}
const data = await findById(id)
ctx.body = {
data: data[0],
success: true
}
}
async function add(ctx) {
const { path } = ctx.request.files.file
const { name, email } = ctx.request.body // 获取 request body 字段
const imgUrl = path.split("/static")[1]
if(!name || !email || !imgUrl) {
ctx.body = {
messge: "参数错误",
success: true
}
return
}
const data = await add({ name, email, imgUrl })
ctx.body = {
data: data,
success: true,
}
}
async function remove(params) {
const id = ctx.params.id
const data = await remove(id)
ctx.body = {
data: data[0],
success: true
}
}
module.exports = {
detail,
list,
add,
remove
}
在 Controller 层我们做参数校验 ,组合 Service 层。这样后,在前端页面我们只需要用 Ajax 调用对应的 api 就可以完成整个流程了。
假设没有用前后端分离,使用的是模版引擎,那就对应的 view 层就放在 app/views 目录下。
4.0 静态资源目录
一般我们在 static 目录下放置静态资源相关的,对应 js,图片都单独放到对应目录下面去。
这样归类就清晰了,以后就知道去哪找对应的资源。
5.0 环境配置相关
开发,测试,生产,不同环境有不同的配置文件,这里我们就需要不同的配置文件,这些信息我们放到 config 目录下面去。
project
└── app
├── config # 环境配置相关
│ ├── base.env.js
│ ├── dev.env.js
│ ├── prod.env.js
│ └── qa.env.js
│ └── index.js
6.0 bin 目录
bin 是放置启动项目相关。
7.0 小结
这节我们学习了 基于 MVC 的结构改造后的,后端项目接口设计,以及各个目录一般放置什么配置文件的使用,使用约定好的配置,可以大大降低 代码耦合,而且结构清晰也是构建大型应用的基石,大家可以对着 Demo 本地跑起来看看,Demo 地址