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)
注解要求:
1@TableName("表名")2@KeySequence("表名_seq") // 如果使用序列3@Data4@EqualsAndHashCode(callSuper = true)5@ToString(callSuper = true)6@Builder7@NoArgsConstructor8@AllArgsConstructor字段规范:
- 主键使用
@TableId注解 - 字段名使用驼峰命名,对应数据库下划线命名
- 使用包装类型 (Long, Integer, Boolean) 而非基本类型
- 金额字段使用
BigDecimal类型 - 时间字段使用
LocalDateTime类型 - 布尔字段命名:
is{属性名}或直接使用状态名
示例:
1/**2 * CRM 产品执行阶段 DO3 *4 * @author 芋道源码5 */6@TableName("crm_product_stage")7@KeySequence("crm_product_stage_seq")8@Data9@EqualsAndHashCode(callSuper = true)10@ToString(callSuper = true)11@Builder12@NoArgsConstructor13@AllArgsConstructor14public class CrmProductStageDO extends BaseDO {1516 /**17 * 阶段ID18 */19 @TableId20 private Long id;2122 /**23 * 产品分类ID24 */25 private Long categoryId;2627 /**28 * 阶段名称29 */30 private String stageName;3132 /**33 * 阶段顺序34 */35 private Integer stageOrder;3637 /**38 * 是否必填39 */40 private Boolean isRequired;4142}1.2 VO (View Object) - 视图对象
位置: controller/admin/{模块名}/vo/{子模块}/
分类:
- ReqVO (Request VO): 接收前端请求参数
{业务名}SaveReqVO: 创建和更新请求{业务名}PageReqVO: 分页查询请求{业务名}UpdateStatusReqVO: 状态更新请求{业务名}TransferReqVO: 转移请求
- RespVO (Response VO): 返回给前端的数据
{业务名}RespVO: 详情和列表响应
注解要求:
1@Schema(description = "管理后台 - {业务描述} Request/Response VO")2@DataReqVO 额外注解:
- 字段校验:
@NotNull,@NotEmpty,@NotBlank - 分页继承:
extends PageParam
RespVO 额外注解:
- Excel 导出:
@ExcelIgnoreUnannotated+@ExcelProperty("列名") - 字段说明:
@Schema(description = "字段说明", example = "示例值")
示例:
1/**2 * 管理后台 - CRM 商机 Response VO3 */4@Schema(description = "管理后台 - CRM 商机 Response VO")5@Data6@ExcelIgnoreUnannotated7public class CrmBusinessRespVO {89 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)10 @ExcelProperty("编号")11 private Long id;12 13 @Schema(description = "商机名称")14 @ExcelProperty("商机名称")15 private String name;1617}1.3 DTO (Data Transfer Object) - 数据传输对象
位置: api/{模块名}/dto/
用途: 跨模块、跨服务调用时的数据传输
命名规范: {业务名}RespDTO 或 {业务名}ReqDTO
注解要求:
1@Data2. Controller 层规范
位置: controller/admin/{模块名}/
命名规范: {业务名}Controller
类注解:
1@Tag(name = "管理后台 - {业务描述}")2@RestController3@RequestMapping("/{模块}/{业务}")4@Validated方法规范:
2.1 创建接口
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 更新接口
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 删除接口
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 查询接口
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 分页查询接口
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 导出接口
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()
示例:
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
类注解:
1@Service2@Validated依赖注入:
- 使用
@Resource注入依赖 - Mapper 注入命名:
{业务名}Mapper - 其他 Service 注入: 如有循环依赖使用
@Lazy
事务注解:
1@Transactional(rollbackFor = Exception.class)日志记录注解:
1@LogRecord(type = CRM_{业务}_TYPE, 2 subType = CRM_{业务}_{操作}_SUB_TYPE, 3 bizNo = "{{#id}}", 4 success = CRM_{业务}_{操作}_SUCCESS)权限注解:
1@CrmPermission(bizType = CrmBizTypeEnum.CRM_{业务}, 2 bizId = "#id", 3 level = CrmPermissionLevelEnum.WRITE)实现规范:
4.1 创建方法
1@Override2@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 更新方法
1@Override2@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 删除方法
1@Override2@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 查询方法
1@Override2@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", 3 level = CrmPermissionLevelEnum.READ)4public CrmBusinessDO getBusiness(Long id) {5 return businessMapper.selectById(id);6}78@Override9public PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) {10 return businessMapper.selectPage(pageReqVO, userId);11}4.5 校验方法
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}89private 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
接口定义:
1@Mapper2public 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{条件} - 更新:
updateById或updateBatch - 删除:
deleteById或deleteByIds
查询构造器规范:
- 使用
LambdaQueryWrapperX构建查询条件 - 条件方法:
eqIfPresent: 等于(参数不为空时)likeIfPresent: 模糊查询(参数不为空时)betweenIfPresent: 范围查询(参数不为空时)inIfPresent: IN 查询(参数不为空时)geIfPresent: 大于等于(参数不为空时)leIfPresent: 小于等于(参数不为空时)
- 排序:
orderByDesc或orderByAsc
Mapper 层规范要点:
- 继承
BaseMapperX<DO> - 使用
@Mapper注解 - 复杂查询使用
default方法实现 - 避免在 Mapper 中写业务逻辑
- 使用 Lambda 方式引用字段,避免硬编码字符串
6. 通用规范
6.0 阿里 P3C 代码格式规范(强制)
6.0.1 类格式规范
- 类注释必须包含类的用途说明和作者信息
- 注解顺序:类注解 → 类定义
- 类注解之间不空行
- 类注解与类定义之间不空行
- 类定义的左大括号
{不换行
1/**2 * CRM 产品执行阶段 DO3 *4 * @author 芋道源码5 */6@TableName("crm_product_stage")7@KeySequence("crm_product_stage_seq")8@Data9@EqualsAndHashCode(callSuper = true)10@ToString(callSuper = true)11@Builder12@NoArgsConstructor13@AllArgsConstructor14public class CrmProductStageDO extends BaseDO {15 // 类体16}6.0.2 字段格式规范
- 每个字段必须有 Javadoc 注释
- 字段注释使用
/** */格式 - 字段注解与字段声明之间不空行
- 字段之间用空行分隔
- 字段注释在字段注解之前
1/**2 * 阶段ID3 */4@TableId5private Long id;67/**8 * 产品分类ID9 */10private Long categoryId;1112/**13 * 阶段名称14 */15private String stageName;6.0.3 方法格式规范
- 方法之间用空行分隔
- 方法注释使用 Javadoc 格式
- 方法注解与方法声明之间不空行
- 方法左大括号
{不换行 - 方法体内逻辑块之间用空行分隔
1/**2 * 创建商机3 *4 * @param createReqVO 创建请求VO5 * @param userId 用户ID6 * @return 商机ID7 */8@Override9@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 等)与左括号之间有空格
- 左大括号前有空格
- 方法参数之间的逗号后有空格
1// 正确2if (condition) {3 doSomething();4}56for (int i = 0; i < 10; i++) {7 process(i);8}910// 错误11if(condition){12 doSomething();13}1415for(int i=0;i<10;i++){16 process(i);17}6.0.5 大括号规范
- 左大括号不换行
- 右大括号换行
- if/else/for/while/do 语句必须使用大括号,即使只有一行
1// 正确2if (condition) {3 doSomething();4}56// 错误7if (condition) 8{9 doSomething();10}1112// 错误 - 缺少大括号13if (condition) doSomething();6.0.6 空行规范
- 类内成员之间用空行分隔(字段、方法)
- 方法内逻辑块之间用空行分隔
- 不同类型的 import 之间用空行分隔
- 类注释与 package 之间有空行
- import 与类定义之间有空行
6.0.7 注释规范
- 类、类属性、类方法必须使用 Javadoc 注释
- 方法内部单行注释使用
//,多行注释使用/* */ - 所有的抽象方法必须用 Javadoc 注释
- 所有的枚举类型字段必须有注释
1/**2 * 校验商机是否存在3 *4 * @param id 商机ID5 * @return 商机DO6 */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 结尾
1// 类名2public class CrmBusinessServiceImpl { }34// 方法名5public void createBusiness() { }67// 常量8public static final String DEFAULT_STATUS = "ACTIVE";910// 包名11package com.laby.boot.module.crm.service.business;6.0.9 其他格式规范
- 单行字符数限制不超过 120 个
- 方法参数过多时,每个参数占一行,与第一个参数对齐
- 链式调用超过 3 个时换行,点号与下文一起换行
- 数组初始化可以写在一行,也可以每个元素一行
1// 方法参数换行2public void createBusiness(CrmBusinessSaveReqVO createReqVO,3 Long userId,4 String remark,5 LocalDateTime createTime) {6 // 方法体7}89// 链式调用换行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
注解要求:
1@Getter2@AllArgsConstructor枚举规范:
- 每个枚举值必须有 Javadoc 注释
- 枚举字段必须有注释
- 枚举值之间用空行分隔
- 提供静态方法用于枚举值查找
- 实现 IntArrayValuable 接口(如果需要)
示例:
1/**2 * CRM 测试订单状态枚举3 *4 * @author Kiro5 */6@Getter7@AllArgsConstructor8public enum CrmTestOrderStatusEnum implements IntArrayValuable {910 /**11 * 待支付12 */13 WAIT_PAY(1, "待支付"),1415 /**16 * 已支付17 */18 PAID(2, "已支付"),1920 /**21 * 已取消22 */23 CANCELLED(3, "已取消");2425 /**26 * 状态值27 */28 private final Integer status;2930 /**31 * 状态名称32 */33 private final String name;3435 /**36 * 所有状态值数组37 */38 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmTestOrderStatusEnum::getStatus).toArray();3940 /**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 }4950 /**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 }6061 @Override62 public int[] array() {63 return ARRAYS;64 }6566}7.2 枚举使用规范
- 使用枚举代替魔法值
- 状态、类型等固定值使用枚举
- 枚举值不要使用 ordinal() 方法
- 枚举比较使用 == 而不是 equals()
8. 常量类规范
8.1 常量类定义规范
位置: constants/{模块名}/
命名规范: {业务名}Constants
常量规范:
- 常量类必须是 final 类或接口
- 常量名全部大写,单词间用下划线分隔
- 每个常量必须有 Javadoc 注释
- 相关常量分组,组之间用空行分隔
- 提供私有构造函数防止实例化(如果是类)
示例:
1/**2 * CRM 测试订单常量类3 *4 * @author Kiro5 */6public class CrmTestOrderConstants {78 /**9 * 订单编号前缀10 */11 public static final String ORDER_NO_PREFIX = "ORD";1213 /**14 * 订单编号长度15 */16 public static final int ORDER_NO_LENGTH = 20;1718 /**19 * 默认订单状态 - 待支付20 */21 public static final Integer DEFAULT_ORDER_STATUS = 1;2223 /**24 * 订单金额最小值25 */26 public static final String MIN_ORDER_AMOUNT = "0.01";2728 /**29 * 订单金额最大值30 */31 public static final String MAX_ORDER_AMOUNT = "999999999.99";3233 /**34 * Redis Key 前缀 - 订单锁35 */36 public static final String REDIS_KEY_ORDER_LOCK = "crm:test-order:lock:";3738 /**39 * Redis Key 过期时间(秒)- 订单锁40 */41 public static final long REDIS_EXPIRE_ORDER_LOCK = 60L;4243 /**44 * 私有构造函数,防止实例化45 */46 private CrmTestOrderConstants() {47 throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");48 }4950}8.2 错误码常量规范
位置: enums/
命名规范: ErrorCodeConstants
错误码规范:
- 使用接口定义错误码
- 每个错误码必须有注释说明
- 错误码按模块分组
- 错误码区间在类注释中说明
- 错误码格式:
new ErrorCode(错误码, "错误信息")
示例:
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 Kiro8 */9public interface ErrorCodeConstants {1011 // ========== 测试订单模块 1-008-100-000 ==========1213 /**14 * 测试订单不存在15 */16 ErrorCode TEST_ORDER_NOT_EXISTS = new ErrorCode(1_008_100_001, "测试订单不存在");1718 /**19 * 测试订单编号重复20 */21 ErrorCode TEST_ORDER_NO_DUPLICATE = new ErrorCode(1_008_100_002, "测试订单编号已存在");2223 /**24 * 测试订单状态不允许修改25 */26 ErrorCode TEST_ORDER_STATUS_NOT_ALLOW_UPDATE = new ErrorCode(1_008_100_003, "当前订单状态不允许修改");2728}8.3 日志常量规范
位置: enums/
命名规范: LogRecordConstants
日志常量规范:
- 使用类定义日志常量
- 每个常量必须有注释
- 日志常量按模块分组
- 提供私有构造函数防止实例化
示例:
1/**2 * CRM 操作日志常量类3 *4 * @author Kiro5 */6public class LogRecordConstants {78 // ========== 测试订单模块 ==========910 /**11 * 测试订单日志类型12 */13 public static final String CRM_TEST_ORDER_TYPE = "CRM 测试订单";1415 /**16 * 测试订单创建子类型17 */18 public static final String CRM_TEST_ORDER_CREATE_SUB_TYPE = "创建测试订单";1920 /**21 * 测试订单创建成功日志模板22 */23 public static final String CRM_TEST_ORDER_CREATE_SUCCESS = "创建了测试订单【{{#testOrder.orderNo}}】";2425 /**26 * 私有构造函数,防止实例化27 */28 private LogRecordConstants() {29 throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");30 }3132}9. Controller 方法注释规范
9.1 CRUD 方法注释模板
创建方法:
1/**2 * 创建{业务名称}3 *4 * @param createReqVO 创建请求VO5 * @return {业务}ID6 */更新方法:
1/**2 * 更新{业务名称}3 *4 * @param updateReqVO 更新请求VO5 * @return 是否成功6 */删除方法:
1/**2 * 删除{业务名称}3 *4 * @param id {业务}ID5 * @return 是否成功6 */查询单个方法:
1/**2 * 获得{业务名称}详情3 *4 * @param id {业务}ID5 * @return {业务}详情6 */分页查询方法:
1/**2 * 获得{业务名称}分页列表3 *4 * @param pageVO 分页查询条件5 * @return 分页结果6 */导出方法:
1/**2 * 导出{业务名称} Excel3 *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: 必须是未来的时间
自定义校验消息:
1@NotBlank(message = "订单编号不能为空")2private String orderNo;34@DecimalMin(value = "0.01", message = "订单金额必须大于0.01")5@DecimalMax(value = "999999999.99", message = "订单金额不能超过999999999.99")6private BigDecimal orderAmount;78@Size(max = 500, message = "备注长度不能超过500个字符")9private String remark;分组校验:
1public interface CreateGroup {}2public interface UpdateGroup {}34public 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}1112// Controller 中使用13@PostMapping("/create")14public CommonResult<Long> create(@Validated(CreateGroup.class) @RequestBody CrmTestOrderSaveReqVO createReqVO) {15 // ...16}12.2 分页查询优化规范
分页参数规范:
1@Schema(description = "管理后台 - CRM 测试订单分页查询 Request VO")2@Data3@EqualsAndHashCode(callSuper = true)4@ToString(callSuper = true)5public class CrmTestOrderPageReqVO extends PageParam {67 @Schema(description = "页码", example = "1")8 @Min(value = 1, message = "页码最小值为 1")9 private Integer pageNo = 1;1011 @Schema(description = "每页条数", example = "10")12 @Min(value = 1, message = "每页条数最小值为 1")13 @Max(value = 100, message = "每页条数最大值为 100")14 private Integer pageSize = 10;1516}深分页优化:
- 避免使用
OFFSET进行深分页 - 使用游标分页(基于 ID 或时间戳)
- 限制最大页码或总数
12.3 批量操作规范
批量删除:
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 异步任务规范
异步方法定义:
1/**2 * 异步导出测试订单3 *4 * @param exportReqVO 导出查询条件5 * @param userId 用户ID6 */7@Async8@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 缓存使用规范
缓存注解使用:
1/**2 * 获取测试订单(带缓存)3 *4 * @param id 订单ID5 * @return 订单DO6 */7@Cacheable(value = "testOrder", key = "#id", unless = "#result == null")8public CrmTestOrderDO getTestOrderWithCache(Long id) {9 return testOrderMapper.selectById(id);10}1112/**13 * 更新测试订单(清除缓存)14 *15 * @param updateReqVO 更新请求VO16 */17@CacheEvict(value = "testOrder", key = "#updateReqVO.id")18public void updateTestOrder(CrmTestOrderSaveReqVO updateReqVO) {19 // 更新逻辑20}缓存规范要点:
- 缓存 Key 设计要有业务含义
- 设置合理的过期时间
- 更新/删除操作要清除相关缓存
- 避免缓存穿透、击穿、雪崩
12.6 性能优化规范
N+1 查询优化:
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}1213// 正确示例 - 批量查询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 注入防护:
1// 正确 - 使用参数化查询2default List<CrmTestOrderDO> selectByOrderNo(String orderNo) {3 return selectList(new LambdaQueryWrapperX<CrmTestOrderDO>()4 .eq(CrmTestOrderDO::getOrderNo, orderNo));5}67// 错误 - 字符串拼接(存在 SQL 注入风险)8// 不要这样写!XSS 防护:
- 前端输入进行 HTML 转义
- 使用
@SafeHtml注解校验 - 富文本内容使用白名单过滤
敏感信息处理:
1/**2 * 用户手机号(脱敏)3 */4@Schema(description = "用户手机号", example = "138****5678")5private String phone;67// 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: 调试信息,开发环境使用
日志内容规范:
1// 正确 - 包含关键信息2log.info("创建订单成功,订单ID:{},订单编号:{},用户ID:{}", 3 orderId, orderNo, userId);45// 正确 - 异常日志包含上下文6log.error("创建订单失败,订单编号:{},用户ID:{},错误信息:{}", 7 orderNo, userId, e.getMessage(), e);89// 错误 - 信息不足10log.info("创建订单成功");1112// 错误 - 没有记录异常堆栈13log.error("创建订单失败:" + e.getMessage());日志规范要点:
- 不要在循环中打印日志
- 敏感信息不要记录到日志
- 使用占位符而不是字符串拼接
- 异常日志必须包含堆栈信息
12.9 代码复用规范
工具类提取:
1/**2 * 订单工具类3 *4 * @author Kiro5 */6public class OrderUtils {78 /**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 }1718 /**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 }3132 /**33 * 私有构造函数,防止实例化34 */35 private OrderUtils() {36 throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");37 }3839}通用方法抽取:
- 相同逻辑出现 3 次以上,考虑抽取为方法
- 工具类方法必须是静态方法
- 避免过度封装,保持代码可读性
12.10 单元测试规范
测试类命名:
- 测试类命名:
{被测试类名}Test - 测试方法命名:
test{方法名}_{场景描述}
测试方法示例:
1/**2 * CRM 测试订单 Service 测试类3 *4 * @author Kiro5 */6@SpringBootTest7public class CrmTestOrderServiceTest {89 @Resource10 private CrmTestOrderService testOrderService;1112 /**13 * 测试创建订单 - 正常场景14 */15 @Test16 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"));2223 // 2. 执行测试24 Long orderId = testOrderService.createTestOrder(createReqVO, 1L);2526 // 3. 断言结果27 assertNotNull(orderId);28 assertTrue(orderId > 0);29 }3031 /**32 * 测试创建订单 - 订单编号重复33 */34 @Test35 public void testCreateTestOrder_duplicateOrderNo() {36 // 测试逻辑37 assertThrows(ServiceException.class, () -> {38 // 执行会抛出异常的代码39 });40 }4142}测试规范要点:
- 每个公共方法都应该有对应的测试
- 测试要覆盖正常场景和异常场景
- 使用
@Transactional保证测试数据回滚 - 测试方法之间不要有依赖关系
13. 代码审查要点
注意:
- 本规范是强制性的,所有新代码必须遵循此规范
- 严格遵循阿里巴巴 Java 开发手册(P3C)的代码格式规范
- 代码审查时将严格检查是否符合规范
- 特别注意:
- 类注解之间不空行、字段之间要空行
- 所有成员必须有 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 分层架构开发的所有方面,从代码格式、命名规范、注释规范,到性能优化、安全规范、测试规范等。严格遵循本规范可以保证代码质量、提高开发效率、降低维护成本。
评论区 / Comments