--- description: RuoYi Spring Boot 后端开发最佳实践与规范 globs: **/*.java, **/*.xml, **/*.yaml, **/*.yml --- # RuoYi Spring Boot 后端开发规范 ## 项目架构 ## 代码规划 - 代码简洁易于人类阅读 ### 模块结构 - `yudao-dependencies`: Maven 依赖版本统一管理 - `yudao-framework`: 框架拓展组件(技术组件) - `yudao-server`: 服务启动模块 - `yudao-module-*`: 业务模块(如 system、member、ai 等) ### 分层架构 - **Controller 层**: 接收请求,参数校验,调用 Service - **Service 层**: 业务逻辑处理,事务管理 - **Mapper 层**: 数据访问,使用 MyBatis Plus - **VO 层**: 视图对象,用于前后端交互 - **DO 层**: 数据对象,对应数据库表 ## 目录结构规范 ### 模块目录结构 业务模块的标准目录结构如下: ``` yudao-module-{模块名}/ └── src/main/java/cn/iocoder/yudao/module/{模块名}/ ├── controller/ # Controller 层 │ └── {Xxx}Controller.java ├── service/ # Service 层 │ ├── {Xxx}Service.java # Service 接口 │ ├── {Xxx}ServiceImpl.java # Service 实现类 │ └── {Xxx}Util.java # Service 工具类(可选) ├── mapper/ # Mapper 层 │ └── {Xxx}Mapper.java ├── dataobject/ # DO 对象(可选) │ └── {Xxx}DO.java ├── vo/ # VO 对象 │ ├── {Xxx}SaveReqVO.java │ ├── {Xxx}PageReqVO.java │ ├── {Xxx}UpdateReqVO.java │ └── {Xxx}RespVO.java ├── enums/ # 枚举类(可选) │ └── {Xxx}Enum.java └── mq/ # 消息队列(可选) └── consumer/ └── {Xxx}Consumer.java ``` ### 目录结构说明 - **mapper/**: Mapper 接口,继承 `BaseMapperX` - **dataobject/**: DO 对象(可选),继承 `BaseDO` 或 `TenantBaseDO` - 如果模块没有 DO 对象,可以省略 `dataobject/` 包 ### 包命名规范 #### Controller 包 - Controller 类名:`{Xxx}Controller` 或 `App{Xxx}Controller` - 直接放在 `controller/` 包下 #### Service 包 - Service 接口:`{Xxx}Service.java` - Service 实现:`{Xxx}ServiceImpl.java` - Service 工具类:`{Xxx}Util.java` 或 `{Xxx}Helper.java` #### Mapper 包 - Mapper 接口:`mapper/{Xxx}Mapper.java` - Mapper 接口继承 `BaseMapperX` #### DO 包 - DO 对象:`dataobject/{Xxx}DO.java`(可选) - DO 类名:`{Xxx}DO.java` - 继承 `BaseDO` 或 `TenantBaseDO` #### VO 包 - Request VO: `{Xxx}SaveReqVO`、`{Xxx}PageReqVO`、`{Xxx}UpdateReqVO` - Response VO: `{Xxx}RespVO` - App VO: `App{Xxx}ReqVO`、`App{Xxx}RespVO` - 直接放在 `vo/` 包下 ### 目录结构示例 #### 示例 2:模块 ``` file/ ├── controller/ │ ├── AppTikUserFileController.java │ ├── AppTikFileGroupController.java │ └── AppTikTestController.java ├── service/ │ ├── TikUserFileService.java │ ├── TikUserFileServiceImpl.java │ ├── TikFileGroupService.java │ └── TikFileGroupServiceImpl.java ├── mapper/ │ ├── TikUserFileMapper.java │ └── TikFileGroupMapper.java ├── vo/ │ ├── AppTikUserFilePageReqVO.java │ ├── AppTikUserFileRespVO.java │ └── AppTikFileGroupCreateReqVO.java └── enums/ └── TikFileCategoryEnum.java ``` ### 目录结构原则 1. **统一性**:同一模块内保持结构一致 2. **简洁性**:使用 `mapper/`,结构清晰 3. **可选性**:省略 `dataobject/` 包 4. **可扩展性**:预留扩展空间,便于后续功能扩展 ## Controller 层规范 ### 注解使用 - 使用 `@RestController` 而非 `@Controller` - 使用 `@RequestMapping` 定义基础路径 - 使用 `@Tag` 定义 Swagger 文档标签 - 使用 `@Operation` 定义接口说明 - 使用 `@Parameter` 定义参数说明 - 使用 `@Valid` 或 `@Validated` 进行参数校验 ### 权限控制 - 管理后台接口使用 `@PreAuthorize("@ss.hasPermission('module:resource:action')")` - 用户端接口通过 `getLoginUserId()` 获取当前用户,确保数据隔离 - 使用 `@PermitAll` 标记允许匿名访问的接口 ### 返回值规范 - 统一使用 `CommonResult` 包装返回值 - 使用 `success()` 静态方法返回成功结果 - 异常由全局异常处理器统一处理 ### 代码示例 ```java @Tag(name = "管理后台 - 用户提示词") @RestController @RequestMapping("/ai/user-prompt") @Validated public class UserPromptController { @Resource private UserPromptService userPromptService; @PostMapping("/create") @Operation(summary = "创建用户提示词") @PreAuthorize("@ss.hasPermission('ai:user-prompt:create')") public CommonResult createUserPrompt(@Valid @RequestBody UserPromptSaveReqVO createReqVO) { return success(userPromptService.createUserPrompt(createReqVO)); } } ``` ## Service 层规范 ### 接口与实现 - Service 接口定义在 `service` 包下 - Service 实现类使用 `ServiceImpl` 后缀,实现对应接口 - 使用 `@Service` 注解标记 - 使用 `@Validated` 启用参数校验 ### 事务管理 - 涉及数据库写操作的方法使用 `@Transactional(rollbackFor = Exception.class)` - 查询方法不需要事务注解 - 避免在 Service 方法中捕获异常后不抛出,导致事务无法回滚 ### 业务逻辑 - Service 层处理核心业务逻辑 - 使用 `BeanUtils.toBean()` 进行对象转换 - 使用 `validateXxxExists()` 方法校验数据存在性 - 使用 `ServiceExceptionUtil.exception()` 抛出业务异常 ### 代码示例 ```java @Service @Validated public class UserPromptServiceImpl implements UserPromptService { @Resource private UserPromptMapper userPromptMapper; @Override @Transactional(rollbackFor = Exception.class) public Long createUserPrompt(UserPromptSaveReqVO createReqVO) { // 1. 校验 // 2. 转换 UserPromptDO userPrompt = BeanUtils.toBean(createReqVO, UserPromptDO.class); // 3. 插入 userPromptMapper.insert(userPrompt); // 4. 返回 return userPrompt.getId(); } } ``` ## Mapper 层规范 ### 继承规范 - Mapper 接口继承 `BaseMapperX`,而非 `BaseMapper` - `BaseMapperX` 提供了更强大的查询能力 ### 方法命名 - 查询方法使用 `select` 前缀 - 插入方法使用 `insert` 前缀 - 更新方法使用 `update` 前缀 - 删除方法使用 `delete` 前缀 ### 分页查询 - 使用 `selectPage(PageReqVO pageReqVO)` 进行分页查询 - 使用 `LambdaQueryWrapperX` 构建查询条件 ### 代码示例 ```java @Mapper public interface UserPromptMapper extends BaseMapperX { default PageResult selectPage(UserPromptPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(UserPromptDO::getName, reqVO.getName()) .eqIfPresent(UserPromptDO::getCategory, reqVO.getCategory()) .betweenIfPresent(UserPromptDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(UserPromptDO::getId)); } } ``` ## VO 对象规范 ### 命名规范 - Request VO: `XxxSaveReqVO`、`XxxPageReqVO`、`XxxUpdateReqVO` - Response VO: `XxxRespVO` - App VO: `AppXxxReqVO`、`AppXxxRespVO` ### 字段注解 - 使用 `@Schema` 定义字段说明和示例 - 使用 `@NotNull`、`@NotEmpty`、`@NotBlank` 等校验注解 - 使用 `requiredMode = Schema.RequiredMode.REQUIRED` 标记必填字段 ### 对象转换 - Controller 层使用 `BeanUtils.toBean()` 进行 DO 到 VO 的转换 - Service 层使用 `BeanUtils.toBean()` 进行 VO 到 DO 的转换 - 复杂转换使用 MapStruct 或手动转换 ### 代码示例 ```java @Schema(description = "管理后台 - 用户提示词创建 Request VO") @Data public class UserPromptSaveReqVO { @Schema(description = "提示词名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发助手") @NotBlank(message = "提示词名称不能为空") private String name; @Schema(description = "提示词内容", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank(message = "提示词内容不能为空") private String content; } ``` ## DO 对象规范 ### 继承规范 - 普通 DO 继承 `BaseDO`,包含 `id`、`createTime`、`updateTime`、`creator`、`updater`、`deleted` - 需要多租户的 DO 继承 `TenantBaseDO`,额外包含 `tenantId` - 使用 `@TableName` 指定表名 ### 字段规范 - 使用 `@TableId(type = IdType.AUTO)` 指定主键策略 - 使用 `@TableLogic` 标记逻辑删除字段 - 字段名使用驼峰命名,对应数据库下划线命名 ### 代码示例 ```java @TableName("ai_user_prompt") @Data @EqualsAndHashCode(callSuper = true) public class UserPromptDO extends TenantBaseDO { @TableId(type = IdType.AUTO) private Long id; private String name; private String content; private String category; } ``` ## 异常处理规范 ### 异常定义 - 业务异常使用 `ServiceException`,通过 `ServiceExceptionUtil.exception()` 创建 - 异常码定义在 `ErrorCodeConstants` 中 - 使用全局异常处理器统一处理 ### 异常码规范 - 格式:`MODULE_RESOURCE_ACTION_ERROR` - 例如:`USER_PROMPT_NOT_EXISTS`、`USER_PROMPT_NAME_DUPLICATE` ### 代码示例 ```java // 定义异常码 public interface ErrorCodeConstants { ErrorCode USER_PROMPT_NOT_EXISTS = new ErrorCode(1_010_000_001, "用户提示词不存在"); } // 使用异常 if (userPrompt == null) { throw exception(USER_PROMPT_NOT_EXISTS); } ``` ## 多租户规范 ### DO 继承 - 需要多租户的数据表,DO 继承 `TenantBaseDO` - 框架自动注入 `tenantId`,无需手动设置 ### 数据隔离 - Mapper 查询时,框架自动添加租户条件 - 跨租户操作需要特殊处理 ### 代码示例 ```java // DO 继承 TenantBaseDO public class UserPromptDO extends TenantBaseDO { // tenantId 自动注入,无需手动定义 } // Service 中无需关心租户,框架自动处理 public UserPromptDO getUserPrompt(Long id) { return userPromptMapper.selectById(id); // 自动添加租户条件 } ``` ## 权限控制规范 ### 权限标识 - 格式:`模块:资源:操作` - 例如:`ai:user-prompt:create`、`ai:user-prompt:query`、`ai:user-prompt:update`、`ai:user-prompt:delete` ### 权限注解 - 使用 `@PreAuthorize("@ss.hasPermission('module:resource:action')")` - 查询操作使用 `query`,创建使用 `create`,更新使用 `update`,删除使用 `delete` ## API 路径规范 ### 路径前缀 - 管理后台:`/admin-api` - 用户端:`/api` - Controller 路径:`/模块/资源`,如 `/ai/user-prompt` ### HTTP 方法 - GET: 查询操作 - POST: 创建操作 - PUT: 更新操作 - DELETE: 删除操作 ### 接口路径 - 创建:`POST /模块/资源/create` - 更新:`PUT /模块/资源/update` - 删除:`DELETE /模块/资源/delete` - 查询单个:`GET /模块/资源/get?id=xxx` - 分页查询:`GET /模块/资源/page` - 导出:`GET /模块/资源/export-excel` ## 代码质量规范 ### 命名规范 - 类名使用大驼峰(PascalCase) - 方法名和变量名使用小驼峰(camelCase) - 常量使用大写下划线(UPPER_SNAKE_CASE) - 包名全小写,使用点分隔 ### 注释规范 - 类和方法必须有 JavaDoc 注释 - 复杂业务逻辑添加行内注释 - 使用 `@author` 标记作者 ### 代码格式 - 使用 4 个空格缩进 - 每行代码不超过 120 个字符 - 方法参数过多时换行对齐 - 使用 IDE 格式化快捷键统一格式 ### 导入规范 - 使用静态导入简化代码:`import static ...` - 避免使用 `.*` 通配符导入 - 导入顺序:Java 标准库 → 第三方库 → 项目内部 ## 性能优化规范 ### 数据库查询 - 避免 N+1 查询问题,使用批量查询 - 合理使用索引,避免全表扫描 - 分页查询必须限制每页数量 ### 缓存使用 - 热点数据使用 Redis 缓存 - 缓存 key 使用统一前缀:`模块:资源:id` - 注意缓存更新和失效策略 ### 事务优化 - 查询方法不使用事务 - 事务范围尽可能小 - 避免在事务中进行远程调用 ## 安全规范 ### 参数校验 - 所有用户输入必须校验 - 使用 `@Valid` 和 JSR-303 注解 - 敏感操作进行二次校验 ### SQL 注入防护 - 使用 MyBatis Plus 的参数化查询 - 禁止拼接 SQL 语句 - 使用 `LambdaQueryWrapperX` 构建查询条件 ### 权限校验 - 所有接口必须进行权限校验 - 数据操作前校验数据归属 - 敏感操作记录操作日志 ## 测试规范 ### 单元测试 - Service 层方法编写单元测试 - 使用 Mockito 模拟依赖 - 测试覆盖率不低于 70% ### 集成测试 - 关键业务流程编写集成测试 - 使用 `@SpringBootTest` 进行集成测试 - 测试数据使用独立的测试数据库 ## 日志规范 ### 日志级别 - ERROR: 系统错误,需要立即处理 - WARN: 警告信息,需要关注 - INFO: 关键业务流程日志 - DEBUG: 调试信息,生产环境关闭 ### 日志格式 - 使用 SLF4J + Logback - 日志包含:时间、级别、线程、类名、消息 - 关键操作记录操作日志(使用 `@ApiAccessLog`) ## 配置管理 ### 配置文件 - 使用 `application.yaml` 作为主配置 - 使用 `application-{profile}.yaml` 作为环境配置 - 敏感信息使用环境变量或配置中心 ### 配置类 - 使用 `@ConfigurationProperties` 绑定配置 - 配置类使用 `@Validated` 进行校验 - 提供默认值和说明文档