Files
sionrui/.cursor/rules/backend.mdc
2025-11-18 23:30:31 +08:00

469 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` 进行校验
- 提供默认值和说明文档