Skip to main content

Java 分层架构开发规范

概述

本规范定义了 Java 项目中各层的代码标准,包括 VO、DO、DTO、Controller、Service、ServiceImpl、Mapper(DAO) 层的命名、结构和实现规范。

1. 数据对象层规范

1.1 DO (Data Object) - 数据库实体对象

位置: dal/dataobject/{模块名}/

命名规范:

  • 类名: {业务名}DO,例如 CrmBusinessDO
  • 表名注解: @TableName("{表名}")
  • 继承: extends BaseDO (包含通用字段如 createTime, updateTime, creator, updater)

注解要求:

java
1@TableName("表名")
2@KeySequence("表名_seq") // 如果使用序列
3@Data
4@EqualsAndHashCode(callSuper = true)
5@ToString(callSuper = true)
6@Builder
7@NoArgsConstructor
8@AllArgsConstructor

字段规范:

  • 主键使用 @TableId 注解
  • 字段名使用驼峰命名,对应数据库下划线命名
  • 使用包装类型 (Long, Integer, Boolean) 而非基本类型
  • 金额字段使用 BigDecimal 类型
  • 时间字段使用 LocalDateTime 类型
  • 布尔字段命名: is{属性名} 或直接使用状态名

示例:

java
1/**
2 * CRM 产品执行阶段 DO
3 *
4 * @author 芋道源码
5 */
6@TableName("crm_product_stage")
7@KeySequence("crm_product_stage_seq")
8@Data
9@EqualsAndHashCode(callSuper = true)
10@ToString(callSuper = true)
11@Builder
12@NoArgsConstructor
13@AllArgsConstructor
14public class CrmProductStageDO extends BaseDO {
15
16 /**
17 * 阶段ID
18 */
19 @TableId
20 private Long id;
21
22 /**
23 * 产品分类ID
24 */
25 private Long categoryId;
26
27 /**
28 * 阶段名称
29 */
30 private String stageName;
31
32 /**
33 * 阶段顺序
34 */
35 private Integer stageOrder;
36
37 /**
38 * 是否必填
39 */
40 private Boolean isRequired;
41
42}

1.2 VO (View Object) - 视图对象

位置: controller/admin/{模块名}/vo/{子模块}/

分类:

  • ReqVO (Request VO): 接收前端请求参数
    • {业务名}SaveReqVO: 创建和更新请求
    • {业务名}PageReqVO: 分页查询请求
    • {业务名}UpdateStatusReqVO: 状态更新请求
    • {业务名}TransferReqVO: 转移请求
  • RespVO (Response VO): 返回给前端的数据
    • {业务名}RespVO: 详情和列表响应

注解要求:

java
1@Schema(description = "管理后台 - {业务描述} Request/Response VO")
2@Data

ReqVO 额外注解:

  • 字段校验: @NotNull, @NotEmpty, @NotBlank
  • 分页继承: extends PageParam

RespVO 额外注解:

  • Excel 导出: @ExcelIgnoreUnannotated + @ExcelProperty("列名")
  • 字段说明: @Schema(description = "字段说明", example = "示例值")

示例:

java
1/**
2 * 管理后台 - CRM 商机 Response VO
3 */
4@Schema(description = "管理后台 - CRM 商机 Response VO")
5@Data
6@ExcelIgnoreUnannotated
7public class CrmBusinessRespVO {
8
9 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)
10 @ExcelProperty("编号")
11 private Long id;
12
13 @Schema(description = "商机名称")
14 @ExcelProperty("商机名称")
15 private String name;
16
17}

1.3 DTO (Data Transfer Object) - 数据传输对象

位置: api/{模块名}/dto/

用途: 跨模块、跨服务调用时的数据传输

命名规范: {业务名}RespDTO{业务名}ReqDTO

注解要求:

java
1@Data

2. Controller 层规范

位置: controller/admin/{模块名}/

命名规范: {业务名}Controller

类注解:

java
1@Tag(name = "管理后台 - {业务描述}")
2@RestController
3@RequestMapping("/{模块}/{业务}")
4@Validated

方法规范:

2.1 创建接口

java
1@PostMapping("/create")
2@Operation(summary = "创建{业务}")
3@PreAuthorize("@ss.hasPermission('{模块}:{业务}:create')")
4public CommonResult<Long> create{业务}(@Valid @RequestBody {业务}SaveReqVO createReqVO) {
5 return success({业务}Service.create{业务}(createReqVO, getLoginUserId()));
6}

2.2 更新接口

java
1@PutMapping("/update")
2@Operation(summary = "更新{业务}")
3@PreAuthorize("@ss.hasPermission('{模块}:{业务}:update')")
4public CommonResult<Boolean> update{业务}(@Valid @RequestBody {业务}SaveReqVO updateReqVO) {
5 {业务}Service.update{业务}(updateReqVO);
6 return success(true);
7}

2.3 删除接口

java
1@DeleteMapping("/delete")
2@Operation(summary = "删除{业务}")
3@Parameter(name = "id", description = "编号", required = true)
4@PreAuthorize("@ss.hasPermission('{模块}:{业务}:delete')")
5public CommonResult<Boolean> delete{业务}(@RequestParam("id") Long id) {
6 {业务}Service.delete{业务}(id);
7 return success(true);
8}

2.4 查询接口

java
1@GetMapping("/get")
2@Operation(summary = "获得{业务}")
3@Parameter(name = "id", description = "编号", required = true)
4@PreAuthorize("@ss.hasPermission('{模块}:{业务}:query')")
5public CommonResult<{业务}RespVO> get{业务}(@RequestParam("id") Long id) {
6 {业务}DO {业务} = {业务}Service.get{业务}(id);
7 return success(BeanUtils.toBean({业务}, {业务}RespVO.class));
8}

2.5 分页查询接口

java
1@GetMapping("/page")
2@Operation(summary = "获得{业务}分页")
3@PreAuthorize("@ss.hasPermission('{模块}:{业务}:query')")
4public CommonResult<PageResult<{业务}RespVO>> get{业务}Page(@Valid {业务}PageReqVO pageVO) {
5 PageResult<{业务}DO> pageResult = {业务}Service.get{业务}Page(pageVO, getLoginUserId());
6 return success(new PageResult<>(convertList(pageResult.getList()), pageResult.getTotal()));
7}

2.6 导出接口

java
1@GetMapping("/export-excel")
2@Operation(summary = "导出{业务} Excel")
3@PreAuthorize("@ss.hasPermission('{模块}:{业务}:export')")
4@ApiAccessLog(operateType = EXPORT)
5public void export{业务}Excel(@Valid {业务}PageReqVO exportReqVO,
6 HttpServletResponse response) throws IOException {
7 exportReqVO.setPageSize(PAGE_SIZE_NONE);
8 List<{业务}DO> list = {业务}Service.get{业务}Page(exportReqVO, getLoginUserId()).getList();
9 ExcelUtils.write(response, "{业务}.xls", "数据", {业务}RespVO.class,
10 BeanUtils.toBean(list, {业务}RespVO.class));
11}

Controller 层规范要点:

  • 只负责参数接收、权限校验、结果返回
  • 不包含业务逻辑
  • 不包含数据组装逻辑(数据组装应在 Service 层完成)
  • 统一返回 CommonResult<T>
  • 使用 @Valid 进行参数校验
  • 使用 @PreAuthorize 进行权限控制
  • Controller 方法应该尽可能简洁,通常只有 1-3 行代码

3. Service 接口层规范

位置: service/{模块名}/

命名规范: {业务名}Service

方法命名规范:

  • 创建: create{业务}()
  • 更新: update{业务}()
  • 删除: delete{业务}()
  • 查询单个: get{业务}()
  • 查询列表: get{业务}List()
  • 查询分页: get{业务}Page()
  • 校验: validate{业务}()
  • 统计: get{业务}Count()

示例:

java
1public interface CrmBusinessService {
2
3 /**
4 * 创建商机
5 */
6 Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId);
7
8 /**
9 * 更新商机
10 */
11 void updateBusiness(CrmBusinessSaveReqVO updateReqVO);
12
13 /**
14 * 删除商机
15 */
16 void deleteBusiness(Long id);
17
18 /**
19 * 获得商机
20 */
21 CrmBusinessDO getBusiness(Long id);
22
23 /**
24 * 获得商机分页
25 */
26 PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId);
27}

4. ServiceImpl 实现层规范

位置: service/{模块名}/

命名规范: {业务名}ServiceImpl

类注解:

java
1@Service
2@Validated

依赖注入:

  • 使用 @Resource 注入依赖
  • Mapper 注入命名: {业务名}Mapper
  • 其他 Service 注入: 如有循环依赖使用 @Lazy

事务注解:

java
1@Transactional(rollbackFor = Exception.class)

日志记录注解:

java
1@LogRecord(type = CRM_{业务}_TYPE,
2 subType = CRM_{业务}_{操作}_SUB_TYPE,
3 bizNo = "{{#id}}",
4 success = CRM_{业务}_{操作}_SUCCESS)

权限注解:

java
1@CrmPermission(bizType = CrmBizTypeEnum.CRM_{业务},
2 bizId = "#id",
3 level = CrmPermissionLevelEnum.WRITE)

实现规范:

4.1 创建方法

java
1@Override
2@Transactional(rollbackFor = Exception.class)
3@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE,
4 bizNo = "{{#business.id}}", success = CRM_BUSINESS_CREATE_SUCCESS)
5public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
6 // 1. 数据校验
7 validateRelationDataExists(createReqVO);
8
9 // 2. 数据转换和插入
10 CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class);
11 businessMapper.insert(business);
12
13 // 3. 关联数据处理
14 // ...
15
16 // 4. 记录操作日志
17 LogRecordContext.putVariable("business", business);
18
19 return business.getId();
20}

4.2 更新方法

java
1@Override
2@Transactional(rollbackFor = Exception.class)
3@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE,
4 bizNo = "{{#updateReqVO.id}}", success = CRM_BUSINESS_UPDATE_SUCCESS)
5@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id",
6 level = CrmPermissionLevelEnum.WRITE)
7public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) {
8 // 1. 校验存在
9 CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
10
11 // 2. 数据校验
12 validateRelationDataExists(updateReqVO);
13
14 // 3. 更新数据
15 CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
16 businessMapper.updateById(updateObj);
17
18 // 4. 记录操作日志
19 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT,
20 BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
21}

4.3 删除方法

java
1@Override
2@Transactional(rollbackFor = Exception.class)
3@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_DELETE_SUB_TYPE,
4 bizNo = "{{#id}}", success = CRM_BUSINESS_DELETE_SUCCESS)
5@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id",
6 level = CrmPermissionLevelEnum.OWNER)
7public void deleteBusiness(Long id) {
8 // 1. 校验存在
9 CrmBusinessDO business = validateBusinessExists(id);
10
11 // 2. 校验关联数据
12 validateRelatedData(id);
13
14 // 3. 删除数据
15 businessMapper.deleteById(id);
16
17 // 4. 删除关联数据
18 // ...
19
20 // 5. 记录操作日志
21 LogRecordContext.putVariable("businessName", business.getName());
22}

4.4 查询方法

java
1@Override
2@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id",
3 level = CrmPermissionLevelEnum.READ)
4public CrmBusinessDO getBusiness(Long id) {
5 return businessMapper.selectById(id);
6}
7
8@Override
9public PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
10 return businessMapper.selectPage(pageReqVO, userId);
11}

4.5 校验方法

java
1private CrmBusinessDO validateBusinessExists(Long id) {
2 CrmBusinessDO business = businessMapper.selectById(id);
3 if (business == null) {
4 throw exception(BUSINESS_NOT_EXISTS);
5 }
6 return business;
7}
8
9private void validateRelationDataExists(CrmBusinessSaveReqVO saveReqVO) {
10 if (saveReqVO.getCustomerId() != null) {
11 customerService.validateCustomer(saveReqVO.getCustomerId());
12 }
13 // 其他关联数据校验...
14}

ServiceImpl 层规范要点:

  • 包含核心业务逻辑
  • 负责数据校验、转换、持久化
  • 负责数据组装(如关联查询、VO 组装)
  • 处理事务边界
  • 记录操作日志
  • 校验方法使用 private 修饰
  • 数据组装方法使用 private 修饰
  • 异常使用 throw exception(错误码) 抛出
  • 使用 BeanUtils.toBean() 进行对象转换
  • 集合操作使用 CollUtil 工具类

数据组装规范:

  • 查询详情时,Service 返回 RespVO 而不是 DO
  • 分页查询时,Service 返回 PageResult<RespVO> 而不是 PageResult<DO>
  • 数据组装逻辑统一在 Service 层的 private 方法中完成
  • 避免 N+1 查询,使用批量查询优化性能

5. Mapper (DAO) 层规范

位置: dal/mysql/{模块名}/

命名规范: {业务名}Mapper

接口定义:

java
1@Mapper
2public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
3
4 default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO reqVO, Long userId) {
5 return selectPage(reqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
6 .likeIfPresent(CrmBusinessDO::getName, reqVO.getName())
7 .eqIfPresent(CrmBusinessDO::getCustomerId, reqVO.getCustomerId())
8 .orderByDesc(CrmBusinessDO::getId));
9 }
10
11 default List<CrmBusinessDO> selectListByCustomerId(Long customerId) {
12 return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
13 .eq(CrmBusinessDO::getCustomerId, customerId));
14 }
15}

方法命名规范:

  • 查询单个: select{条件}selectById
  • 查询列表: selectList{条件}
  • 查询分页: selectPage{条件}
  • 统计: selectCount{条件}
  • 更新: updateByIdupdateBatch
  • 删除: deleteByIddeleteByIds

查询构造器规范:

  • 使用 LambdaQueryWrapperX 构建查询条件
  • 条件方法:
    • eqIfPresent: 等于(参数不为空时)
    • likeIfPresent: 模糊查询(参数不为空时)
    • betweenIfPresent: 范围查询(参数不为空时)
    • inIfPresent: IN 查询(参数不为空时)
    • geIfPresent: 大于等于(参数不为空时)
    • leIfPresent: 小于等于(参数不为空时)
  • 排序: orderByDescorderByAsc

Mapper 层规范要点:

  • 继承 BaseMapperX<DO>
  • 使用 @Mapper 注解
  • 复杂查询使用 default 方法实现
  • 避免在 Mapper 中写业务逻辑
  • 使用 Lambda 方式引用字段,避免硬编码字符串

6. 通用规范

6.0 阿里 P3C 代码格式规范(强制)

6.0.1 类格式规范

  • 类注释必须包含类的用途说明和作者信息
  • 注解顺序:类注解 → 类定义
  • 类注解之间不空行
  • 类注解与类定义之间不空行
  • 类定义的左大括号 { 不换行
java
1/**
2 * CRM 产品执行阶段 DO
3 *
4 * @author 芋道源码
5 */
6@TableName("crm_product_stage")
7@KeySequence("crm_product_stage_seq")
8@Data
9@EqualsAndHashCode(callSuper = true)
10@ToString(callSuper = true)
11@Builder
12@NoArgsConstructor
13@AllArgsConstructor
14public class CrmProductStageDO extends BaseDO {
15 // 类体
16}

6.0.2 字段格式规范

  • 每个字段必须有 Javadoc 注释
  • 字段注释使用 /** */ 格式
  • 字段注解与字段声明之间不空行
  • 字段之间用空行分隔
  • 字段注释在字段注解之前
java
1/**
2 * 阶段ID
3 */
4@TableId
5private Long id;
6
7/**
8 * 产品分类ID
9 */
10private Long categoryId;
11
12/**
13 * 阶段名称
14 */
15private String stageName;

6.0.3 方法格式规范

  • 方法之间用空行分隔
  • 方法注释使用 Javadoc 格式
  • 方法注解与方法声明之间不空行
  • 方法左大括号 { 不换行
  • 方法体内逻辑块之间用空行分隔
java
1/**
2 * 创建商机
3 *
4 * @param createReqVO 创建请求VO
5 * @param userId 用户ID
6 * @return 商机ID
7 */
8@Override
9@Transactional(rollbackFor = Exception.class)
10public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
11 // 1. 数据校验
12 validateRelationDataExists(createReqVO);
13
14 // 2. 数据转换和插入
15 CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class);
16 businessMapper.insert(business);
17
18 // 3. 返回结果
19 return business.getId();
20}

6.0.4 缩进和空格规范

  • 使用 4 个空格缩进,禁止使用 Tab
  • 运算符两侧必须有空格
  • 关键字(if、for、while 等)与左括号之间有空格
  • 左大括号前有空格
  • 方法参数之间的逗号后有空格
java
1// 正确
2if (condition) {
3 doSomething();
4}
5
6for (int i = 0; i < 10; i++) {
7 process(i);
8}
9
10// 错误
11if(condition){
12 doSomething();
13}
14
15for(int i=0;i<10;i++){
16 process(i);
17}

6.0.5 大括号规范

  • 左大括号不换行
  • 右大括号换行
  • if/else/for/while/do 语句必须使用大括号,即使只有一行
java
1// 正确
2if (condition) {
3 doSomething();
4}
5
6// 错误
7if (condition)
8{
9 doSomething();
10}
11
12// 错误 - 缺少大括号
13if (condition) doSomething();

6.0.6 空行规范

  • 类内成员之间用空行分隔(字段、方法)
  • 方法内逻辑块之间用空行分隔
  • 不同类型的 import 之间用空行分隔
  • 类注释与 package 之间有空行
  • import 与类定义之间有空行

6.0.7 注释规范

  • 类、类属性、类方法必须使用 Javadoc 注释
  • 方法内部单行注释使用 //,多行注释使用 /* */
  • 所有的抽象方法必须用 Javadoc 注释
  • 所有的枚举类型字段必须有注释
java
1/**
2 * 校验商机是否存在
3 *
4 * @param id 商机ID
5 * @return 商机DO
6 */
7private CrmBusinessDO validateBusinessExists(Long id) {
8 // 查询商机
9 CrmBusinessDO business = businessMapper.selectById(id);
10
11 // 校验是否存在
12 if (business == null) {
13 throw exception(BUSINESS_NOT_EXISTS);
14 }
15
16 return business;
17}

6.0.8 命名规范

  • 类名使用 UpperCamelCase 风格
  • 方法名、参数名、成员变量、局部变量使用 lowerCamelCase 风格
  • 常量命名全部大写,单词间用下划线隔开
  • 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词
  • 抽象类命名使用 Abstract 或 Base 开头
  • 异常类命名使用 Exception 结尾
  • 测试类命名以它要测试的类的名称开始,以 Test 结尾
java
1// 类名
2public class CrmBusinessServiceImpl { }
3
4// 方法名
5public void createBusiness() { }
6
7// 常量
8public static final String DEFAULT_STATUS = "ACTIVE";
9
10// 包名
11package com.laby.boot.module.crm.service.business;

6.0.9 其他格式规范

  • 单行字符数限制不超过 120 个
  • 方法参数过多时,每个参数占一行,与第一个参数对齐
  • 链式调用超过 3 个时换行,点号与下文一起换行
  • 数组初始化可以写在一行,也可以每个元素一行
java
1// 方法参数换行
2public void createBusiness(CrmBusinessSaveReqVO createReqVO,
3 Long userId,
4 String remark,
5 LocalDateTime createTime) {
6 // 方法体
7}
8
9// 链式调用换行
10return selectPage(reqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
11 .likeIfPresent(CrmBusinessDO::getName, reqVO.getName())
12 .eqIfPresent(CrmBusinessDO::getCustomerId, reqVO.getCustomerId())
13 .orderByDesc(CrmBusinessDO::getId));

6.1 包导入规范

  • 按顺序导入: Java 标准库 → 第三方库 → 项目内部包
  • 避免使用 import *
  • 使用 IDE 自动优化导入

6.2 异常处理规范

  • 使用 ServiceExceptionUtil.exception(错误码) 抛出业务异常
  • 错误码定义在 ErrorCodeConstants
  • 不捕获不处理的异常

6.3 日志规范

  • 使用 @LogRecord 注解记录操作日志
  • 使用 LogRecordContext.putVariable() 记录上下文变量
  • 日志类型和子类型定义在 LogRecordConstants

6.4 权限规范

  • Controller 层使用 @PreAuthorize("@ss.hasPermission('{权限标识}')")
  • Service 层使用 @CrmPermission 进行数据权限控制
  • 权限级别: READ(读), WRITE(写), OWNER(拥有者)

6.5 事务规范

  • 涉及多表操作使用 @Transactional(rollbackFor = Exception.class)
  • 只在 Service 层使用事务注解
  • 避免事务嵌套和长事务

6.6 对象转换规范

  • 使用 BeanUtils.toBean() 进行单对象转换
  • 使用 BeanUtils.toBean(list, TargetClass.class) 进行列表转换
  • 使用 convertList() 进行自定义转换
  • 使用 convertSet() 提取集合中的某个字段

6.7 集合操作规范

  • 判空: CollUtil.isEmpty()CollUtil.isNotEmpty()
  • 转换: convertList(), convertSet(), convertMap()
  • 过滤: filterList()
  • 求和: getSumValue()

6.8 金额计算规范

  • 使用 MoneyUtils.priceMultiply() 进行金额乘法
  • 使用 MoneyUtils.priceMultiplyPercent() 进行百分比计算
  • 金额字段统一使用 BigDecimal 类型

6.9 时间处理规范

  • 使用 LocalDateTime 类型
  • 不使用 Date 类型
  • 时间比较使用 isBefore(), isAfter(), isEqual()

6.10 注释规范

  • 类注释: 说明类的用途和作者
  • 方法注释: 说明方法功能、参数、返回值
  • 复杂逻辑添加行内注释
  • 使用中文注释

7. 枚举类规范

7.1 枚举定义规范

位置: enums/{模块名}/

命名规范: {业务名}Enum

注解要求:

java
1@Getter
2@AllArgsConstructor

枚举规范:

  • 每个枚举值必须有 Javadoc 注释
  • 枚举字段必须有注释
  • 枚举值之间用空行分隔
  • 提供静态方法用于枚举值查找
  • 实现 IntArrayValuable 接口(如果需要)

示例:

java
1/**
2 * CRM 测试订单状态枚举
3 *
4 * @author Kiro
5 */
6@Getter
7@AllArgsConstructor
8public enum CrmTestOrderStatusEnum implements IntArrayValuable {
9
10 /**
11 * 待支付
12 */
13 WAIT_PAY(1, "待支付"),
14
15 /**
16 * 已支付
17 */
18 PAID(2, "已支付"),
19
20 /**
21 * 已取消
22 */
23 CANCELLED(3, "已取消");
24
25 /**
26 * 状态值
27 */
28 private final Integer status;
29
30 /**
31 * 状态名称
32 */
33 private final String name;
34
35 /**
36 * 所有状态值数组
37 */
38 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmTestOrderStatusEnum::getStatus).toArray();
39
40 /**
41 * 根据状态值获取枚举
42 *
43 * @param status 状态值
44 * @return 枚举对象
45 */
46 public static CrmTestOrderStatusEnum valueOf(Integer status) {
47 return ArrayUtil.firstMatch(o -> o.getStatus().equals(status), values());
48 }
49
50 /**
51 * 根据状态值获取状态名称
52 *
53 * @param status 状态值
54 * @return 状态名称
55 */
56 public static String getNameByStatus(Integer status) {
57 CrmTestOrderStatusEnum statusEnum = valueOf(status);
58 return statusEnum != null ? statusEnum.getName() : "";
59 }
60
61 @Override
62 public int[] array() {
63 return ARRAYS;
64 }
65
66}

7.2 枚举使用规范

  • 使用枚举代替魔法值
  • 状态、类型等固定值使用枚举
  • 枚举值不要使用 ordinal() 方法
  • 枚举比较使用 == 而不是 equals()

8. 常量类规范

8.1 常量类定义规范

位置: constants/{模块名}/

命名规范: {业务名}Constants

常量规范:

  • 常量类必须是 final 类或接口
  • 常量名全部大写,单词间用下划线分隔
  • 每个常量必须有 Javadoc 注释
  • 相关常量分组,组之间用空行分隔
  • 提供私有构造函数防止实例化(如果是类)

示例:

java
1/**
2 * CRM 测试订单常量类
3 *
4 * @author Kiro
5 */
6public class CrmTestOrderConstants {
7
8 /**
9 * 订单编号前缀
10 */
11 public static final String ORDER_NO_PREFIX = "ORD";
12
13 /**
14 * 订单编号长度
15 */
16 public static final int ORDER_NO_LENGTH = 20;
17
18 /**
19 * 默认订单状态 - 待支付
20 */
21 public static final Integer DEFAULT_ORDER_STATUS = 1;
22
23 /**
24 * 订单金额最小值
25 */
26 public static final String MIN_ORDER_AMOUNT = "0.01";
27
28 /**
29 * 订单金额最大值
30 */
31 public static final String MAX_ORDER_AMOUNT = "999999999.99";
32
33 /**
34 * Redis Key 前缀 - 订单锁
35 */
36 public static final String REDIS_KEY_ORDER_LOCK = "crm:test-order:lock:";
37
38 /**
39 * Redis Key 过期时间(秒)- 订单锁
40 */
41 public static final long REDIS_EXPIRE_ORDER_LOCK = 60L;
42
43 /**
44 * 私有构造函数,防止实例化
45 */
46 private CrmTestOrderConstants() {
47 throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
48 }
49
50}

8.2 错误码常量规范

位置: enums/

命名规范: ErrorCodeConstants

错误码规范:

  • 使用接口定义错误码
  • 每个错误码必须有注释说明
  • 错误码按模块分组
  • 错误码区间在类注释中说明
  • 错误码格式: new ErrorCode(错误码, "错误信息")

示例:

java
1/**
2 * CRM 错误码常量类
3 *
4 * CRM 系统错误码区间:[1-008-000-000 ~ 1-008-999-999]
5 * 测试订单模块错误码区间:[1-008-100-000 ~ 1-008-100-999]
6 *
7 * @author Kiro
8 */
9public interface ErrorCodeConstants {
10
11 // ========== 测试订单模块 1-008-100-000 ==========
12
13 /**
14 * 测试订单不存在
15 */
16 ErrorCode TEST_ORDER_NOT_EXISTS = new ErrorCode(1_008_100_001, "测试订单不存在");
17
18 /**
19 * 测试订单编号重复
20 */
21 ErrorCode TEST_ORDER_NO_DUPLICATE = new ErrorCode(1_008_100_002, "测试订单编号已存在");
22
23 /**
24 * 测试订单状态不允许修改
25 */
26 ErrorCode TEST_ORDER_STATUS_NOT_ALLOW_UPDATE = new ErrorCode(1_008_100_003, "当前订单状态不允许修改");
27
28}

8.3 日志常量规范

位置: enums/

命名规范: LogRecordConstants

日志常量规范:

  • 使用类定义日志常量
  • 每个常量必须有注释
  • 日志常量按模块分组
  • 提供私有构造函数防止实例化

示例:

java
1/**
2 * CRM 操作日志常量类
3 *
4 * @author Kiro
5 */
6public class LogRecordConstants {
7
8 // ========== 测试订单模块 ==========
9
10 /**
11 * 测试订单日志类型
12 */
13 public static final String CRM_TEST_ORDER_TYPE = "CRM 测试订单";
14
15 /**
16 * 测试订单创建子类型
17 */
18 public static final String CRM_TEST_ORDER_CREATE_SUB_TYPE = "创建测试订单";
19
20 /**
21 * 测试订单创建成功日志模板
22 */
23 public static final String CRM_TEST_ORDER_CREATE_SUCCESS = "创建了测试订单【{{#testOrder.orderNo}}】";
24
25 /**
26 * 私有构造函数,防止实例化
27 */
28 private LogRecordConstants() {
29 throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
30 }
31
32}

9. Controller 方法注释规范

9.1 CRUD 方法注释模板

创建方法:

java
1/**
2 * 创建{业务名称}
3 *
4 * @param createReqVO 创建请求VO
5 * @return {业务}ID
6 */

更新方法:

java
1/**
2 * 更新{业务名称}
3 *
4 * @param updateReqVO 更新请求VO
5 * @return 是否成功
6 */

删除方法:

java
1/**
2 * 删除{业务名称}
3 *
4 * @param id {业务}ID
5 * @return 是否成功
6 */

查询单个方法:

java
1/**
2 * 获得{业务名称}详情
3 *
4 * @param id {业务}ID
5 * @return {业务}详情
6 */

分页查询方法:

java
1/**
2 * 获得{业务名称}分页列表
3 *
4 * @param pageVO 分页查询条件
5 * @return 分页结果
6 */

导出方法:

java
1/**
2 * 导出{业务名称} Excel
3 *
4 * @param exportReqVO 导出查询条件
5 * @param response HTTP响应
6 * @throws IOException IO异常
7 */

10. 代码检查清单

创建新功能时检查:

  • 类是否有完整的 Javadoc 注释(包含类说明和 @author)
  • 所有字段是否有 Javadoc 注释
  • 所有公共方法是否有 Javadoc 注释(包含 @param 和 @return)
  • Controller 的 CRUD 方法是否有完整注释
  • 枚举值是否有注释
  • 枚举字段是否有注释
  • 常量是否有注释
  • 静态方法是否有注释
  • 私有方法是否有注释
  • 类注解之间是否没有空行
  • 类注解与类定义之间是否没有空行
  • 字段之间是否有空行分隔
  • 枚举值之间是否有空行分隔
  • 常量分组之间是否有空行分隔
  • 字段注解与字段声明之间是否没有空行
  • 方法之间是否有空行分隔
  • 左大括号是否不换行
  • if/for/while 是否都使用了大括号
  • 缩进是否使用 4 个空格(不使用 Tab)
  • 运算符两侧是否有空格
  • 单行字符数是否不超过 120 个
  • 枚举是否使用 @Getter 和 @AllArgsConstructor 注解
  • 常量类是否提供了私有构造函数
  • 错误码是否有注释和错误码区间说明
  • 日志常量是否有注释
  • DO 是否继承 BaseDO
  • DO 是否添加了 @TableName 注解
  • VO 是否添加了 @Schema 注解和字段说明
  • ReqVO 是否添加了参数校验注解
  • Controller 方法是否添加了权限注解
  • Controller 方法是否添加了 @Operation 注解
  • Service 接口方法是否有注释
  • ServiceImpl 是否添加了 @Service 和 @Validated 注解
  • 涉及多表操作是否添加了 @Transactional 注解
  • 是否添加了操作日志 @LogRecord 注解
  • 是否进行了数据校验
  • 是否使用了正确的异常抛出方式
  • Mapper 是否继承了 BaseMapperX
  • Mapper 是否添加了 @Mapper 注解
  • 是否使用了 BeanUtils 进行对象转换
  • 金额字段是否使用了 BigDecimal 类型
  • 时间字段是否使用了 LocalDateTime 类型

11. 示例代码模板

完整的 CRUD 示例

参考 test-example 目录中的完整示例代码:

  • CrmTestOrderDO.java - DO 层示例
  • CrmTestOrderSaveReqVO.java - 请求 VO 示例
  • CrmTestOrderRespVO.java - 响应 VO 示例
  • CrmTestOrderPageReqVO.java - 分页查询 VO 示例
  • CrmTestOrderMapper.java - Mapper 层示例
  • CrmTestOrderService.java - Service 接口示例
  • CrmTestOrderServiceImpl.java - Service 实现示例
  • CrmTestOrderController.java - Controller 层示例
  • CrmTestOrderStatusEnum.java - 枚举类示例
  • CrmTestOrderConstants.java - 常量类示例
  • ErrorCodeConstants.java - 错误码常量示例
  • LogRecordConstants.java - 日志常量示例

12. 高级规范与最佳实践

12.1 参数校验规范

基础校验注解:

  • @NotNull: 不能为 null(可以为空字符串)
  • @NotEmpty: 不能为 null 且长度/大小必须大于 0(用于字符串、集合、数组)
  • @NotBlank: 不能为 null 且去除空格后长度必须大于 0(仅用于字符串)
  • @Size(min, max): 字符串、集合、数组的大小范围
  • @Length(min, max): 字符串长度范围
  • @Min(value): 数值最小值
  • @Max(value): 数值最大值
  • @DecimalMin(value): BigDecimal 最小值
  • @DecimalMax(value): BigDecimal 最大值
  • @Pattern(regexp): 正则表达式校验
  • @Email: 邮箱格式校验
  • @Past: 必须是过去的时间
  • @Future: 必须是未来的时间

自定义校验消息:

java
1@NotBlank(message = "订单编号不能为空")
2private String orderNo;
3
4@DecimalMin(value = "0.01", message = "订单金额必须大于0.01")
5@DecimalMax(value = "999999999.99", message = "订单金额不能超过999999999.99")
6private BigDecimal orderAmount;
7
8@Size(max = 500, message = "备注长度不能超过500个字符")
9private String remark;

分组校验:

java
1public interface CreateGroup {}
2public interface UpdateGroup {}
3
4public class CrmTestOrderSaveReqVO {
5 @NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空")
6 private Long id;
7
8 @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "订单编号不能为空")
9 private String orderNo;
10}
11
12// Controller 中使用
13@PostMapping("/create")
14public CommonResult<Long> create(@Validated(CreateGroup.class) @RequestBody CrmTestOrderSaveReqVO createReqVO) {
15 // ...
16}

12.2 分页查询优化规范

分页参数规范:

java
1@Schema(description = "管理后台 - CRM 测试订单分页查询 Request VO")
2@Data
3@EqualsAndHashCode(callSuper = true)
4@ToString(callSuper = true)
5public class CrmTestOrderPageReqVO extends PageParam {
6
7 @Schema(description = "页码", example = "1")
8 @Min(value = 1, message = "页码最小值为 1")
9 private Integer pageNo = 1;
10
11 @Schema(description = "每页条数", example = "10")
12 @Min(value = 1, message = "每页条数最小值为 1")
13 @Max(value = 100, message = "每页条数最大值为 100")
14 private Integer pageSize = 10;
15
16}

深分页优化:

  • 避免使用 OFFSET 进行深分页
  • 使用游标分页(基于 ID 或时间戳)
  • 限制最大页码或总数

12.3 批量操作规范

批量删除:

java
1/**
2 * 批量删除测试订单
3 *
4 * @param ids 订单ID列表
5 * @return 是否成功
6 */
7@DeleteMapping("/batch-delete")
8@Operation(summary = "批量删除测试订单")
9@PreAuthorize("@ss.hasPermission('crm:test-order:delete')")
10public CommonResult<Boolean> batchDeleteTestOrder(@RequestParam("ids") List<Long> ids) {
11 // 限制批量操作数量
12 if (CollUtil.isEmpty(ids) || ids.size() > 100) {
13 throw exception(BATCH_DELETE_SIZE_EXCEED);
14 }
15 testOrderService.batchDeleteTestOrder(ids);
16 return success(true);
17}

批量操作规范要点:

  • 限制批量操作的数量(建议不超过 100 条)
  • 使用事务保证原子性
  • 提供批量操作进度反馈(异步任务)
  • 记录批量操作日志

12.4 异步任务规范

异步方法定义:

java
1/**
2 * 异步导出测试订单
3 *
4 * @param exportReqVO 导出查询条件
5 * @param userId 用户ID
6 */
7@Async
8@Transactional(rollbackFor = Exception.class)
9public void asyncExportTestOrder(CrmTestOrderPageReqVO exportReqVO, Long userId) {
10 try {
11 // 1. 查询数据
12 List<CrmTestOrderDO> list = testOrderMapper.selectPage(exportReqVO).getList();
13
14 // 2. 生成文件
15 String filePath = generateExcelFile(list);
16
17 // 3. 通知用户
18 notifyUserExportComplete(userId, filePath);
19 } catch (Exception e) {
20 log.error("异步导出订单失败", e);
21 notifyUserExportFailed(userId, e.getMessage());
22 }
23}

异步任务规范要点:

  • 使用 @Async 注解标记异步方法
  • 异步方法必须在不同的类中调用(避免自调用失效)
  • 异步任务必须有异常处理和日志记录
  • 长时间任务提供进度反馈机制

12.5 缓存使用规范

缓存注解使用:

java
1/**
2 * 获取测试订单(带缓存)
3 *
4 * @param id 订单ID
5 * @return 订单DO
6 */
7@Cacheable(value = "testOrder", key = "#id", unless = "#result == null")
8public CrmTestOrderDO getTestOrderWithCache(Long id) {
9 return testOrderMapper.selectById(id);
10}
11
12/**
13 * 更新测试订单(清除缓存)
14 *
15 * @param updateReqVO 更新请求VO
16 */
17@CacheEvict(value = "testOrder", key = "#updateReqVO.id")
18public void updateTestOrder(CrmTestOrderSaveReqVO updateReqVO) {
19 // 更新逻辑
20}

缓存规范要点:

  • 缓存 Key 设计要有业务含义
  • 设置合理的过期时间
  • 更新/删除操作要清除相关缓存
  • 避免缓存穿透、击穿、雪崩

12.6 性能优化规范

N+1 查询优化:

java
1// 错误示例 - N+1 查询
2public List<CrmTestOrderRespVO> getTestOrderList(List<Long> ids) {
3 List<CrmTestOrderDO> orders = testOrderMapper.selectByIds(ids);
4 return orders.stream().map(order -> {
5 // 每次循环都查询一次数据库
6 CrmCustomerDO customer = customerService.getCustomer(order.getCustomerId());
7 CrmTestOrderRespVO vo = BeanUtils.toBean(order, CrmTestOrderRespVO.class);
8 vo.setCustomerName(customer.getName());
9 return vo;
10 }).collect(Collectors.toList());
11}
12
13// 正确示例 - 批量查询
14public List<CrmTestOrderRespVO> getTestOrderList(List<Long> ids) {
15 List<CrmTestOrderDO> orders = testOrderMapper.selectByIds(ids);
16
17 // 批量查询客户信息
18 Set<Long> customerIds = convertSet(orders, CrmTestOrderDO::getCustomerId);
19 Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(customerIds);
20
21 return BeanUtils.toBean(orders, CrmTestOrderRespVO.class, vo -> {
22 MapUtils.findAndThen(customerMap, vo.getCustomerId(),
23 customer -> vo.setCustomerName(customer.getName()));
24 });
25}

性能优化要点:

  • 避免 N+1 查询,使用批量查询
  • 合理使用索引
  • 避免全表扫描
  • 大数据量操作使用分批处理
  • 使用连接池复用数据库连接

12.7 安全规范

SQL 注入防护:

java
1// 正确 - 使用参数化查询
2default List<CrmTestOrderDO> selectByOrderNo(String orderNo) {
3 return selectList(new LambdaQueryWrapperX<CrmTestOrderDO>()
4 .eq(CrmTestOrderDO::getOrderNo, orderNo));
5}
6
7// 错误 - 字符串拼接(存在 SQL 注入风险)
8// 不要这样写!

XSS 防护:

  • 前端输入进行 HTML 转义
  • 使用 @SafeHtml 注解校验
  • 富文本内容使用白名单过滤

敏感信息处理:

java
1/**
2 * 用户手机号(脱敏)
3 */
4@Schema(description = "用户手机号", example = "138****5678")
5private String phone;
6
7// Service 层脱敏处理
8public String maskPhone(String phone) {
9 if (StrUtil.isBlank(phone) || phone.length() < 11) {
10 return phone;
11 }
12 return phone.substring(0, 3) + "****" + phone.substring(7);
13}

12.8 日志规范

日志级别使用:

  • ERROR: 系统错误,需要立即处理
  • WARN: 警告信息,可能存在问题
  • INFO: 重要业务流程信息
  • DEBUG: 调试信息,开发环境使用

日志内容规范:

java
1// 正确 - 包含关键信息
2log.info("创建订单成功,订单ID:{},订单编号:{},用户ID:{}",
3 orderId, orderNo, userId);
4
5// 正确 - 异常日志包含上下文
6log.error("创建订单失败,订单编号:{},用户ID:{},错误信息:{}",
7 orderNo, userId, e.getMessage(), e);
8
9// 错误 - 信息不足
10log.info("创建订单成功");
11
12// 错误 - 没有记录异常堆栈
13log.error("创建订单失败:" + e.getMessage());

日志规范要点:

  • 不要在循环中打印日志
  • 敏感信息不要记录到日志
  • 使用占位符而不是字符串拼接
  • 异常日志必须包含堆栈信息

12.9 代码复用规范

工具类提取:

java
1/**
2 * 订单工具类
3 *
4 * @author Kiro
5 */
6public class OrderUtils {
7
8 /**
9 * 生成订单编号
10 *
11 * @return 订单编号
12 */
13 public static String generateOrderNo() {
14 return ORDER_NO_PREFIX + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss")
15 + RandomUtil.randomNumbers(6);
16 }
17
18 /**
19 * 计算订单折扣金额
20 *
21 * @param totalAmount 总金额
22 * @param discountPercent 折扣百分比
23 * @return 折扣金额
24 */
25 public static BigDecimal calculateDiscountAmount(BigDecimal totalAmount, BigDecimal discountPercent) {
26 if (totalAmount == null || discountPercent == null) {
27 return BigDecimal.ZERO;
28 }
29 return MoneyUtils.priceMultiplyPercent(totalAmount, discountPercent);
30 }
31
32 /**
33 * 私有构造函数,防止实例化
34 */
35 private OrderUtils() {
36 throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
37 }
38
39}

通用方法抽取:

  • 相同逻辑出现 3 次以上,考虑抽取为方法
  • 工具类方法必须是静态方法
  • 避免过度封装,保持代码可读性

12.10 单元测试规范

测试类命名:

  • 测试类命名: {被测试类名}Test
  • 测试方法命名: test{方法名}_{场景描述}

测试方法示例:

java
1/**
2 * CRM 测试订单 Service 测试类
3 *
4 * @author Kiro
5 */
6@SpringBootTest
7public class CrmTestOrderServiceTest {
8
9 @Resource
10 private CrmTestOrderService testOrderService;
11
12 /**
13 * 测试创建订单 - 正常场景
14 */
15 @Test
16 public void testCreateTestOrder_success() {
17 // 1. 准备测试数据
18 CrmTestOrderSaveReqVO createReqVO = new CrmTestOrderSaveReqVO();
19 createReqVO.setOrderNo("ORD20240101001");
20 createReqVO.setCustomerId(1L);
21 createReqVO.setOrderAmount(new BigDecimal("1000.00"));
22
23 // 2. 执行测试
24 Long orderId = testOrderService.createTestOrder(createReqVO, 1L);
25
26 // 3. 断言结果
27 assertNotNull(orderId);
28 assertTrue(orderId > 0);
29 }
30
31 /**
32 * 测试创建订单 - 订单编号重复
33 */
34 @Test
35 public void testCreateTestOrder_duplicateOrderNo() {
36 // 测试逻辑
37 assertThrows(ServiceException.class, () -> {
38 // 执行会抛出异常的代码
39 });
40 }
41
42}

测试规范要点:

  • 每个公共方法都应该有对应的测试
  • 测试要覆盖正常场景和异常场景
  • 使用 @Transactional 保证测试数据回滚
  • 测试方法之间不要有依赖关系

13. 代码审查要点

注意:

  1. 本规范是强制性的,所有新代码必须遵循此规范
  2. 严格遵循阿里巴巴 Java 开发手册(P3C)的代码格式规范
  3. 代码审查时将严格检查是否符合规范
  4. 特别注意:
    • 类注解之间不空行、字段之间要空行
    • 所有成员必须有 Javadoc 注释(包括 Controller 方法、枚举值、常量)
    • 枚举值之间要空行分隔
    • 常量类必须提供私有构造函数防止实例化
    • 错误码必须有注释和区间说明
    • 使用枚举代替魔法值

13. 代码审查要点

13.1 功能性审查

  • 代码是否实现了需求的所有功能
  • 边界条件是否处理正确
  • 异常情况是否有适当的处理
  • 是否有遗漏的业务逻辑

13.2 安全性审查

  • 是否存在 SQL 注入风险
  • 是否存在 XSS 攻击风险
  • 敏感信息是否做了脱敏处理
  • 权限校验是否完整
  • 是否有越权访问的风险

13.3 性能审查

  • 是否存在 N+1 查询问题
  • 是否有不必要的数据库查询
  • 循环中是否有数据库操作
  • 是否合理使用了缓存
  • 大数据量操作是否分批处理

13.4 代码质量审查

  • 代码是否符合命名规范
  • 代码是否有适当的注释
  • 是否有重复代码需要抽取
  • 方法是否过长(建议不超过 50 行)
  • 类是否职责单一

13.5 可维护性审查

  • 代码逻辑是否清晰易懂
  • 是否使用了合适的设计模式
  • 是否有硬编码需要提取为常量
  • 是否有魔法值需要使用枚举
  • 日志是否完整且有意义

14. 常见问题与解决方案

14.1 循环依赖问题

问题: Service 之间相互依赖导致启动失败

解决方案: 使用 @Lazy 延迟加载

14.2 事务失效问题

问题: 事务注解不生效

常见原因: 方法不是 public、同类内部调用、异常被捕获

14.3 BigDecimal 精度问题

问题: 金额计算精度丢失

解决方案: 使用 String 构造 BigDecimal,不使用 double

14.4 空指针异常问题

问题: NullPointerException 频繁出现

解决方案: 使用 Optional、工具类判空、提供默认值

15. 版本控制规范

15.1 Git 提交规范

提交信息格式: <type>(<scope>): <subject>

Type 类型: feat、fix、docs、style、refactor、perf、test、chore

15.2 分支管理规范

分支类型: master、develop、feature/xxx、bugfix/xxx、hotfix/xxx、release/xxx


规范总结: 本规范涵盖了 Java 分层架构开发的所有方面,从代码格式、命名规范、注释规范,到性能优化、安全规范、测试规范等。严格遵循本规范可以保证代码质量、提高开发效率、降低维护成本。

forum

评论区 / Comments