469 lines
14 KiB
Plaintext
469 lines
14 KiB
Plaintext
---
|
||
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<T>`
|
||
- **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<T>`
|
||
|
||
#### 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<T>` 包装返回值
|
||
- 使用 `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<Long> 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<T>`,而非 `BaseMapper<T>`
|
||
- `BaseMapperX` 提供了更强大的查询能力
|
||
|
||
### 方法命名
|
||
- 查询方法使用 `select` 前缀
|
||
- 插入方法使用 `insert` 前缀
|
||
- 更新方法使用 `update` 前缀
|
||
- 删除方法使用 `delete` 前缀
|
||
|
||
### 分页查询
|
||
- 使用 `selectPage(PageReqVO pageReqVO)` 进行分页查询
|
||
- 使用 `LambdaQueryWrapperX` 构建查询条件
|
||
|
||
### 代码示例
|
||
```java
|
||
@Mapper
|
||
public interface UserPromptMapper extends BaseMapperX<UserPromptDO> {
|
||
|
||
default PageResult<UserPromptDO> selectPage(UserPromptPageReqVO reqVO) {
|
||
return selectPage(reqVO, new LambdaQueryWrapperX<UserPromptDO>()
|
||
.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` 进行校验
|
||
- 提供默认值和说明文档
|