Files
sionrui/.cursor/rules/backend.mdc

469 lines
14 KiB
Plaintext
Raw Normal View History

2025-11-13 01:06:28 +08:00
---
description: RuoYi Spring Boot 后端开发最佳实践与规范
globs: **/*.java, **/*.xml, **/*.yaml, **/*.yml
---
# RuoYi Spring Boot 后端开发规范
## 项目架构
2025-11-16 22:11:15 +08:00
## 代码规划
- 代码简洁易于人类阅读
2025-11-13 01:06:28 +08:00
### 模块结构
- `yudao-dependencies`: Maven 依赖版本统一管理
- `yudao-framework`: 框架拓展组件(技术组件)
- `yudao-server`: 服务启动模块
- `yudao-module-*`: 业务模块(如 system、member、ai 等)
### 分层架构
- **Controller 层**: 接收请求,参数校验,调用 Service
- **Service 层**: 业务逻辑处理,事务管理
- **Mapper 层**: 数据访问,使用 MyBatis Plus
- **VO 层**: 视图对象,用于前后端交互
- **DO 层**: 数据对象,对应数据库表
2025-11-16 22:09:41 +08:00
## 目录结构规范
### 模块目录结构
业务模块的标准目录结构如下:
```
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/` 包下
### 目录结构示例
2025-11-18 23:30:31 +08:00
#### 示例 2模块
2025-11-16 22:09:41 +08:00
```
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. **统一性**:同一模块内保持结构一致
2025-11-18 23:30:31 +08:00
2. **简洁性**:使用 `mapper/`,结构清晰
3. **可选性**:省略 `dataobject/` 包
2025-11-16 22:09:41 +08:00
4. **可扩展性**:预留扩展空间,便于后续功能扩展
2025-11-13 01:06:28 +08:00
## Controller 层规范
2025-11-16 22:09:41 +08:00
2025-11-13 01:06:28 +08:00
### 注解使用
- 使用 `@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`
2025-11-16 22:09:41 +08:00
- 用户端:`/api`
2025-11-13 01:06:28 +08:00
- 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` 进行校验
- 提供默认值和说明文档