后端开发手册
本文最后更新于:10 个月前
Spring 应用程序编码和命名规范
SpringBoot项目目录结构是有命名规范的,编码和命名反映了对应模块的功能。
一、规范的意义和作用
- 编码规范可以最大限度的提高团队开发的合作效率
- 编码规范可以尽可能的减少一个软件的维护成本 , 并且几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护
- 编码规范可以改善软件的可读性,可以让开发人员尽快而彻底地理解新的代码
- 规范性编码还可以让开发人员养成好的编码习惯,甚至锻炼出更加严谨的思维
二、代码仓库规范
(一)公共组件
- 公共组件通常指 Java 库,提供特定问题的处理程序包
- 公共组件仓库地址:https://git.company.com/java-library-group
- 公共组件的坐标命名规范
- 分组编号:
com.company.library 固定取值 - 组件名称:
name name 根据组件名称定义 - 组件版本:
x.y.z x.y.z 根据组件实际版本情况定义
- 分组编号:
(二)服务组件
- 服务组件通常指可以独立部署,运行,维护的服务程序包
- 服务组件仓库地址:https://git.company.com/server-microservice-group
- 应用组件的坐标命名规范
- 分组编号:
com.company.server固定取值 - 组件名称:
name name 根据组件名称定义 - 组件版本:
x.y.z x.y.z 根据组件实际版本情况定义
- 分组编号:
三、开发环境规范
- 开发环境:JDK8+
- 开发工具:IntelliJ IDEA 2017(安装 Lombok Plugin)
- 构建工具:Maven3.x
- 代码管理工具:Git /TortoiseGit
四、项目结构规范
(一)简述
一个项目对应代码仓库中的一个仓库,项目结构是指一个基于Maven创建的项目目录结构。公共组件项目,通常会创建一个Maven普通项目。服务组件项目,通常会创建一个Maven聚合项目,并在聚合项目目录下创建多个继承Maven聚合项目的Maven模块,它们一起作为服务组件项目的组成部分。
(二)项目名
- 要求
- 英文名称,作为仓库,项目,项目根目录,组件(公共组件,服务组件)的名称
- 中文名称,用于代码仓库的描述
- 项目名称和代码仓库的名称保持一致
- 定义
- 项目名称通常由团队负责人确定
- 示例
- 项目中文名:人脸数据仓库
- 项目英文名:data-warehouse-face
- 项目目录名:data-warehouse-face
- 项目仓库地址:https://git.company.com/server-microservice-group/data-warehouse-face.git
- 初始版本:1.0.0
- 示例是一个服务组件,根据上面定义的信息确定该服务组件的 Maven 坐标命名:
<groupId>com.company.server</groupId> <artifactId>data-warehouse-face</artifactId> <version>1.0.0</version>
(三)模块命名
- 要求示例
- 模块名称:{项目名称}-{模块名称} 模块名称简洁体现职责
- 模块名字作为模块组件的名称
- 人脸数据仓库的数据接入模块名称:data-warehouse-face-access
(四)项目目录
- 项目目录遵循 Maven 约定目录格式
(五)源码目录
- 源码目录指:{项目目录}/src/main/
- 打包定义目录:src/main/assembly
- 代码目录:src/main/java
- 资源目录:src/main/resources 文档目录:src/main/docs
- /db:数据库脚本归档
- /data:内部依赖数据归档
- 脚本目录:src/main/bin
- run-manage.sh 运行管理脚本(通过参数 start, stop, status, help info 控制程序运行)
- sh:服务组件启动脚本
- sh:服务组件停止脚本
五、编码规范
(一)包规范
- 项目基本包:com.company.{项目英文名(较长时适当简化)}.{模块名(可选)}
- config:配置类
- startup:与服务启动相关的类
- client:提供客户端实现的相关类
- common:公共类,定义常量类,组件
- entity: 数据库相关的实体类
- model:数据模型类(参数模型,数据传输模型等)
- control:控制层接口
- service: 服务层
- dao:数据库访问层
POJO
POJO(Plain Old Java Object)是指普通的 Java 对象,没有任何特殊的注解或功能。POJO 通常用于表示数据,例如用户、商品等。
Entity
Entity 是指数据库中的表对应的对象。Entity 通常具有以下特性:
- 具有对应表中的所有字段。
- 具有默认的构造方法和 getter/setter 方法。
- 具有 equals()、hashCode() 和 toString() 方法。
Model
其属性字段可能不与 entity 一一对应,Model 是一个高度优化组合或者精简后的一个用于在 View 层展示数据的对象。(可能为多个 entity 的某些属性组合,也可能为单一 entity 的精简,具体结合业务需求来决定。)
根据实际开发中来看,model 作为包命名,包内一般写与前端交互的 response 和 request,根据业务需要的数据将 entity 中一个或多个字段数据封装成 res 和 req。
VO
VO(View Object,一般为响应数据模型):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
VO 与 DTO 都是展示层与服务层之间传递数据的对象,那为什么区分成两个呢,参考论坛老哥——对于绝大部分的应用场景来说,DTO 和 VO 的属性值基本是一致的,而且他们通常都是 POJO,因此没必要多此一举,但不要忘记这是实现层面的思维,对于设计层面来说,概念上还是应该存在 VO 和 DTO,因为两者有着本质的区别,DTO 代表服务层需要接收的数据和返回的数据,而 VO 代表展示层需要显示的数据。
VO(Value Object)是指用于传输数据的对象。VO 通常具有以下特性:
- 只包含业务数据,不包含业务逻辑。
- 通常是简单的对象,没有太多的字段。
DTO
DTO(数据传输对象,一般为请求数据模型)封装的数据面向表现层(UI),Model 封装的数据面向业务逻辑层(service)。开发中模型改了但是 UI 没变,则只需要改变 model,而不需要改其他。
DTO(Data Transfer Object)是指用于传输数据的对象。DTO 通常具有以下特性:
- 与 VO 类似,只包含业务数据,不包含业务逻辑。
- 通常是复杂的对象,具有较多的字段。
在用户修改密码的 controller 中,我们可能只需要拿到用户名、用户密码和用户修改新密码,即可对数据库进行操作,则我们把用户密码和用户修改新密码封装为一个 dto 类型,只拿到自己需要的类型
Domain
Domain 是指业务领域中的对象。Domain 通常具有以下特性:
- 可以是 Entity、Model、VO 或 DTO 的组合。
- 可以包含业务逻辑。
区别
POJO、Entity、Model、VO、Dto、Domain 的区别如下表所示:
特性 | POJO | Entity | Model | VO | DTO | Domain |
---|---|---|---|---|---|---|
作用 | 表示数据 | 表示数据库中的表 | 表示业务数据 | 传输数据 | 传输数据 | 业务领域中的对象 |
特性 | 没有特殊的注解或功能 | 具有对应表中的所有字段,具有默认的构造方法和 getter/setter 方法,具有 equals()、hashCode() 和 toString() 方法 | 具有 Entity 的所有特性,可以包含业务逻辑 | 只包含业务数据,不包含业务逻辑 | 与 VO 类似,只包含业务数据,不包含业务逻辑 | 可以是 Entity、Model、VO 或 DTO 的组合,可以包含业务逻辑 |
使用场景 | 用于表示数据,例如用户、商品等 | 用于表示数据库中的表,通常用于持久化数据 | 用于表示业务数据,例如用户、商品等 | 用于传输数据,例如用于前端与后端的通信 | 用于传输数据,例如用于前端与后端的通信 | 用于表示业务领域中的对象,例如订单、商品等 |
(二)日志记录
- 统一使用 SLF4j 接口
(三)异常处理
- 运行时异常:通过参数检查等方式避免或抛出运行时异常,日志记录
- 检查异常:检查异常需要捕获,处理,日志记录
(四)接口定义
- 原则
- 接口地址定义表明用意
- 接口地址定义清晰,简洁,无歧义
- 同一个服务组件的接口定义具有一致性
- 格式
- 控制类的顶层地址格式:/{顶层分类名},例如:/library 人员库相关接口的顶层地址
- 接口定义使用 Swagger 的 API 注解说明
- 标注完整的请求信息,请求方法,请求地址,参数可选性,接口描述
- 请求方式
- GET URL-Params
- POST Form-Data
- POST RequestBody(JSON 格式)
- POST Mulitpart
- 响应方式
- 统一的响应模型
(五)辅助工具
- 字符串处理:apache common-lang3
- 时间日期处理:joda-time
- JSON 处理:Gson,Fastjson
- 集合扩展工具:guava
- 文件和流处理:commons-io
- 编解码:commons-codec
- 建议:尽可能使用开源组件
(六)代码注释
- 类、接口、枚举顶层注释
- 接口方法注释
- 静态方法注释
- 公开方法注释
- 类的属性字段注释
- 常量注释
- 不限于以上
六、代码控制规范
(一)拉取原则
- 强制
- 每日开始工作拉取
- 约定
- 提交之前拉取
(二)提交原则
- 强制
- 提交代码必须构建成功(比如:编译,打包成共)
- 提交代码必须完整(比如:少提文件)
- 提交代码必须忽略到本地临时文件(比如:target, logs, .idea, *.iml,dist 等)
- 约定
- 完成一个功能提交
- 修改一个 Bug 修改提交
- 解决冲突提交
- 每日结束工作提交
(三)提交注释
- 强制
- 中文填写注释
- 注释反映本次提交变更情况
- 约定
- 注释描述添加前缀,前缀如下
- [创建] 通常在项目创建时使用
- [新增]
- [修改]
- [删除]
- [修复-number] 修复 Bug 使用,number 是 Bug 编号
七、构建规范
(一)公共组件构建规范
- 构建输出组件包
- 构建输出组件源码包
- 构建发布到公司私有仓库
(二)服务组件构建规范
- 服务组件包命名:{组件名称}-{版本号}-bin.zip
- 构建输出到工程根目录下的 dist/{组件名称}-{yyyyMMddHH}目录
数据库设计
数据库设计流程:
- 根据业务逻辑画出 E-R 图(即实体关系图):找出业务逻辑中有多少个实体,以及他们之间的联系(一对一、一对多、多对多)。
图例说明
- 实体:矩形
- 关系:菱形
- 属性:椭圆/圆边矩形
- 将 E-R 图转换为关系模式(E-R 图转表):重难点是如何体现一对多关系,而多对多关系又要如何处理,将 E-R 图进行两两关系转换,当出现冲突时保留属性复杂完整的。
- 一对多
- 两张表:一对多中一的一方只包含自己的属性,多的一方除了含自己的属性还要包含一的一方的主键和交叉属性(eg:
user(一)、order(多,包含user的id和交叉属性)
)。 - 三张表:同多对多进行转换设计
- 两张表:一对多中一的一方只包含自己的属性,多的一方除了含自己的属性还要包含一的一方的主键和交叉属性(eg:
- 多对多:两张各自实体关系表,外加一张含有各自表主键和交叉属性的表(eg:
user、role、user_role
)
- 一对多
==具体方法==:
- 首先为各个实体添加自己固有属性字段
- 根据 E-R 图寻找实体的一对一和多对一关系,为该关系添加关联表 id 主键字段
- 添加多对多表,并为之添加固有属性字段和交叉关联属性字段
外键一般用于一对一和一对多关系中,一对一中外键存在于任意一个表中都可以,一对多中外键存在于多的一方,且外键为一的一方的主键或更多字段。
- 将数据库规范化:根据第一、二、三范式进行进一步完善,可能需要拆分字段、拆表(增加表)、添加主键等。
商品表中商品分类不满足原子性因此拆分出商品分类表。
数据库设计尽量遵循三大范式,三大范式如下:
第一范式(N1):列的原子性,属性不可分割,即每个属性都是不可分割的原子项。(实体的属性即表中的列)
第二范式(N2)
- 满足第一范式;
- 第二范式需要确保数据表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言),每张表只描述一件事情;
- 消除部分依赖,要求一张表中的每一列都完全依赖于主键(针对于组合主键),也就是不会出现某一列只和部分主键相关。
除满足 N1 外,数据库的表中不存在非关键字段对任一候选关键字段对任一候选关键字段的部分函数依赖。比如视频中的 petstore 的 E-R 图中商品与订单是多对多的关系,为什么订单表中没包含商品信息,商品表中也没有包含订单信息。因为这样就不满足第二范式了。
第三范式(N3)
- 满足第一范式和第二范式;
- 第三范式需要确保数据表中的每一列数据和主键直接相关,而不能间接相关;
- 消除传递依赖,要求一张表中的每一列都和主键是直接依赖的,不是间接依赖。
除了满足 N3 外,非主键列必须直接依赖于主键,不存在传递依赖。视频中的例子:订单表包含了用户号,那么如果加入用户名,密码,会怎么样呢,用户号依赖于订单号,但用户名和密码是直接依赖于用户号,而间接依赖于订单号的。那么就不满足第三范式了。
个人开发经验
设计规范
单体项目
pom.xml 中:
- parent 标签中设置 spring-boot-parent 和版本
微服务项目
主项目 pom.xml 文件中:
- properties 标签中对所用到的全部依赖进行版本设置。
- dependencyManagement 标签中导入 spring-cloud-dependencies、spring-boot-dependencies、spring-cloud-alibaba-dependencies 依赖,type 为 pom,scope 为 import
- 如果有私人仓库,设置 repositories 和 pluginRepositories 标签添加私人 maven 仓库
- build 标签中设置打包插件
结构规范
[!TIP] 设计技巧
- 核心公共模块:微服务将公共组件和配置抽象到一个模块中集中配置管理,提高可维护、可扩展性;
- 业务模块:将各个业务按照一定的粒度进行拆分解耦为不同业务模块,被其他业务模块调用的模块需要额外添加 api 接口模块集中管理 rpc/feign 接口和数据传输对象,否则就只需要业务 service 模块;
- 网关模块:对全局业务统一调度管理,对用户发来的请求进行负载均衡根据网关路由尽可能分配到负载压力较小的服务器上提供可靠高可用服务。
- 项目总结构如下所示:
- 主项目:全局配置依赖管理
- gateway 网关子项目:统一配置网关
- 实际业务子项目:子项目配置依赖管理
- 子项目 api:供其他微服务 rpc/feign 调用接口模块(也可以单独抽取出来)
- 子项目 service:实际业务功能模块
- 项目核心子项目(xxx-common):包含全局公共组件和配置
util
:工具组件(JwtUtils、UserHolder···)enums
:枚举类型(ErrorCodeEnum···)constant
:全局常量(AmqConsts、ApiRouterConsts、CacheConsts、SystemConfigConsts···)json
:json 序列化和反序列化器配置(@JsonCOmponent)config
:包含全局公共配置(接口文档配置、springSecurity 配置、跨域配置)annotation
:公共注解aspect
:公共切片(日志切片)exception
:公共异常(自定义业务异常、全局异常捕获处理)filter
:公共过滤器(防止 XSS 攻击的过滤器)interceptor
:公共拦截器(token 解析拦截器)wrapper
:公共装饰器(XSS 过滤处理)domain
:公共业务、视图类- vo:公共视图类
- req:请求视图基本类(分页请求基类···)
- resp:响应视图基本类(HttpRest 响应基类、分页响应基类)
- bo:业务对象类
- vo:公共视图类
- 项目业务 API 模块(xxx-api)根包名下的综合结构如下:
domain
:数据库相关对象model
:模型对象(dto等)dto
:数据传输对象
service
:rpc/feign 业务接口
- 项目业务模块(xxx-service)根包名下的综合结构如下:
config
:配置controller
:控制器mapper
:数据库操作接口dubbo
:dubbo接口实现类service
:业务功能接口- impl:业务功能实现类
- xxxService.java:业务接口
domain
:实体、视图对象- entity:数据库表 ORM 映射实体
- vo:前端视图对象
- req:请求视图对象
- resp:响应视图对象
- bo:业务对象
util
:业务工具类constant
:业务常量类enums
:业务枚举类annotation
:业务注解类aspect
:业务切片类exception
:业务自定义异常interceptor
:业务拦截器manager
:业务管理类- mq:AMQP 消息管理类
- cache:缓存管理类
- ·······
命名规范
- 包名全部小写,POJO 类后缀名全部大写,其他类、接口、枚举、记录类型全部首字母大写并遵循驼峰命名原则;
- 方法、属性、局部变量全部遵循驼峰命名原则;
- 常量和记录类型的属性全部大写以下划线分隔;
代码调试
在IDEA中进行断点调试的方法,包括行断点、详细断点(源断点:不挂起的行断点)、方法断点(接口跳转实现类)、异常断点(全局捕获)和字段断点(读写监控)。其中详细断点可以提供当前断点所在行的详细信息,包括类和方法的签名。异常断点可以在出现异常时自动停顿并查看异常信息,可以在断点视图中添加各种异常进行全局捕获。字段断点可以用于监控字段的读写变化。