📋 主要变更: - ✅ 补充完整的7个业务模块表命名规范 - ✅ 新增后勤管理模块:hq_ 前缀 - ✅ 明确系统公共模块:common_ 前缀 - ✅ 提供完整的命名示例和实体类映射规则 - ✅ 更新变更记录文档 🎯 覆盖模块: - 后勤管理(hq_)、人事管理(rs_)、财务管理(cw_) - 学工管理(xg_)、招生就业(zsjy_)、安全保卫(aqbw_) - 系统公共(common_) 📚 文件变更: - .cursor/cursorrules-backend.md - 数据库表命名规范 - docs/变更管理/code-adjust-record-2025-01-23-wuzilong.md - 变更记录 - 各业务模块工程文件和前端结构文件 Co-authored-by: Cursor AI <cursor@cursor.sh>
1982 lines
60 KiB
Markdown
1982 lines
60 KiB
Markdown
# Cursor Rules - 后端开发规范
|
||
|
||
## 适用范围
|
||
|
||
- **项目目录**: `backend/` (基于 RuoYi-Vue-Plus + Spring Boot 3.4.6)
|
||
- **Java 版本**: JDK 17/21
|
||
- **后端服务**: http://localhost:8080
|
||
|
||
---
|
||
|
||
# 后端开发规范 (backend/)
|
||
|
||
## 1. 技术栈规范
|
||
|
||
### 核心技术
|
||
|
||
- **框架**: Spring Boot 3.4.6
|
||
- **Java 版本**: JDK 17/21
|
||
- **Web 容器**: Undertow
|
||
- **ORM 框架**: MyBatis-Plus 3.5.12
|
||
- **数据库**: MySQL/Oracle/PostgreSQL/SQLServer (多数据库支持)
|
||
- **缓存**: Redis 5-7 + Redisson 3.45.1
|
||
- **权限认证**: Sa-Token 1.42.0 + JWT
|
||
- **任务调度**: SnailJob 1.5.0
|
||
- **工作流**: Warm-Flow 1.7.3
|
||
|
||
## 2. 项目结构规范
|
||
|
||
### 后端模块结构
|
||
|
||
```
|
||
backend/
|
||
├── ruoyi-admin/ # 管理后台启动模块
|
||
├── ruoyi-common/ # 公共模块组
|
||
│ ├── ruoyi-common-core/ # 核心模块
|
||
│ ├── ruoyi-common-mybatis/ # MyBatis配置
|
||
│ ├── ruoyi-common-redis/ # Redis配置
|
||
│ ├── ruoyi-common-satoken/ # Sa-Token配置
|
||
│ └── ... (其他公共模块)
|
||
├── ruoyi-modules/ # 业务模块组
|
||
│ ├── ruoyi-system/ # 系统管理模块
|
||
│ ├── ruoyi-demo/ # 示例模块
|
||
│ ├── ruoyi-hq/ # 后勤管理模块 (hq)
|
||
│ ├── ruoyi-rs/ # 人事管理模块 (rs)
|
||
│ ├── ruoyi-cw/ # 财务管理模块 (cw)
|
||
│ ├── ruoyi-aqbw/ # 安全保卫管理模块 (aqbw)
|
||
│ ├── ruoyi-zsjy/ # 招生就业管理模块 (zsjy)
|
||
│ ├── ruoyi-xg/ # 学工管理模块 (xg)
|
||
│ └── ... (其他业务模块)
|
||
├── ruoyi-extend/ # 扩展模块组
|
||
└── script/ # 脚本和SQL
|
||
```
|
||
|
||
## 🔴 模块工程管理规范(强制性,2025-01-23新增)
|
||
|
||
### 📦 模块工程创建规范
|
||
|
||
**⚠️ 基于ruoyi-hq工程实践制定,后续所有新模块必须遵循此规范!**
|
||
|
||
#### 🏗️ 模块命名和位置规范
|
||
|
||
**模块位置**: `backend/ruoyi-modules/ruoyi-{模块简称}/`
|
||
|
||
**已规划模块列表**:
|
||
- ✅ `ruoyi-hq` - 后勤管理模块 (已完成)
|
||
- 🚧 `ruoyi-rs` - 人事管理模块
|
||
- 🚧 `ruoyi-cw` - 财务管理模块
|
||
- 🚧 `ruoyi-aqbw` - 安全保卫管理模块
|
||
- 🚧 `ruoyi-zsjy` - 招生就业管理模块
|
||
- 🚧 `ruoyi-xg` - 学工管理模块
|
||
|
||
#### 📋 Maven配置规范
|
||
|
||
**pom.xml标准模板**:
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||
<parent>
|
||
<artifactId>ruoyi-modules</artifactId>
|
||
<groupId>org.dromara</groupId>
|
||
<version>${revision}</version>
|
||
</parent>
|
||
<modelVersion>4.0.0</modelVersion>
|
||
|
||
<artifactId>ruoyi-{模块简称}</artifactId>
|
||
<description>
|
||
{模块名称}管理模块
|
||
</description>
|
||
|
||
<dependencies>
|
||
<!-- 通用工具 -->
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-core</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-doc</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-log</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-security</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-web</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-excel</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-idempotent</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-tenant</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-social</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-ratelimiter</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-common-mail</artifactId>
|
||
</dependency>
|
||
|
||
<!-- 系统模块依赖 -->
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-system</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-job</artifactId>
|
||
</dependency>
|
||
|
||
<!-- 代码生成-->
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-generator</artifactId>
|
||
</dependency>
|
||
|
||
<!-- demo模块 -->
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-demo</artifactId>
|
||
</dependency>
|
||
|
||
<!-- 工作流模块 -->
|
||
<dependency>
|
||
<groupId>org.dromara</groupId>
|
||
<artifactId>ruoyi-workflow</artifactId>
|
||
</dependency>
|
||
</dependencies>
|
||
</project>
|
||
```
|
||
|
||
#### 🏛️ 包结构规范
|
||
|
||
**Java包结构**: `org.dromara.{模块简称}`
|
||
|
||
**标准目录结构**:
|
||
```
|
||
ruoyi-{模块简称}/
|
||
├── pom.xml
|
||
└── src/main/
|
||
├── java/org/dromara/{模块简称}/
|
||
│ ├── controller/ # REST API控制器层
|
||
│ ├── service/ # 业务服务接口层
|
||
│ │ └── impl/ # 业务服务实现层
|
||
│ ├── mapper/ # 数据访问层接口
|
||
│ └── domain/ # 数据对象层
|
||
│ ├── [Entity].java # 实体类 (继承TenantEntity)
|
||
│ ├── bo/ # 业务对象 (Business Object)
|
||
│ └── vo/ # 视图对象 (View Object)
|
||
└── resources/
|
||
└── mapper/ # MyBatis XML映射文件
|
||
```
|
||
|
||
#### 🎯 代码规范要求
|
||
|
||
**Entity实体类**:
|
||
```java
|
||
@Data
|
||
@EqualsAndHashCode(callSuper = true)
|
||
@TableName("表名")
|
||
public class EntityName extends TenantEntity {
|
||
@Serial
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
@TableId(value = "id")
|
||
private Long id;
|
||
|
||
// 业务字段...
|
||
}
|
||
```
|
||
|
||
**Service接口**:
|
||
```java
|
||
public interface IEntityService {
|
||
EntityVo queryById(Long id);
|
||
TableDataInfo<EntityVo> queryPageList(EntityBo bo, PageQuery pageQuery);
|
||
List<EntityVo> queryList(EntityBo bo);
|
||
Boolean insertByBo(EntityBo bo);
|
||
Boolean updateByBo(EntityBo bo);
|
||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||
}
|
||
```
|
||
|
||
**Controller控制器**:
|
||
```java
|
||
@Validated
|
||
@RequiredArgsConstructor
|
||
@RestController
|
||
@RequestMapping("/{模块简称}/entity")
|
||
public class EntityController extends BaseController {
|
||
|
||
private final IEntityService entityService;
|
||
|
||
@SaCheckPermission("{模块简称}:entity:list")
|
||
@GetMapping("/list")
|
||
public TableDataInfo<EntityVo> list(EntityBo bo, PageQuery pageQuery) {
|
||
return entityService.queryPageList(bo, pageQuery);
|
||
}
|
||
|
||
// 其他标准接口...
|
||
}
|
||
```
|
||
|
||
**Mapper接口**:
|
||
```java
|
||
public interface EntityMapper extends BaseMapperPlus<Entity, EntityVo> {
|
||
// 自定义查询方法...
|
||
}
|
||
```
|
||
|
||
#### 🔐 权限命名规范
|
||
|
||
**权限编码格式**: `{模块简称}:{功能}:{操作}`
|
||
|
||
**示例**:
|
||
- `hq:repair:list` - 后勤模块报修列表查询权限
|
||
- `rs:employee:add` - 人事模块员工新增权限
|
||
- `cw:budget:export` - 财务模块预算导出权限
|
||
|
||
#### 📚 模块开发最佳实践
|
||
|
||
**1. 遵循ruoyi-hq标准模式**:
|
||
- 继承TenantEntity支持多租户
|
||
- 使用@SaCheckPermission进行权限控制
|
||
- 遵循TableDataInfo分页响应格式
|
||
- 使用MapstructUtils进行对象转换
|
||
|
||
**2. 代码质量要求**:
|
||
- 使用Lombok减少样板代码
|
||
- 完整的中文注释和文档
|
||
- 统一的异常处理机制
|
||
- 完善的参数校验
|
||
|
||
**3. 模块间解耦**:
|
||
- 模块间通过Service接口交互
|
||
- 避免直接依赖其他模块的内部实现
|
||
- 共用功能提取到common模块
|
||
|
||
**4. 数据库设计**:
|
||
- 必须遵循数据库规范基础字段
|
||
- 支持逻辑删除和多租户
|
||
- 合理的索引设计
|
||
- 完整的数据字典配置
|
||
|
||
#### 🚨 模块创建检查清单
|
||
|
||
**创建新模块前必须检查**:
|
||
- [ ] 模块名称符合规范?(`ruoyi-{简称}`)
|
||
- [ ] Maven配置是否正确?
|
||
- [ ] 包结构是否遵循标准?
|
||
- [ ] 是否包含完整的分层架构?
|
||
- [ ] 权限编码是否符合规范?
|
||
- [ ] 是否遵循ruoyi-hq的最佳实践?
|
||
- [ ] 数据库设计是否符合规范?
|
||
- [ ] 是否添加了相关文档?
|
||
|
||
## 3. 开发规范
|
||
|
||
### 代码规范
|
||
|
||
- **严格遵守**: Alibaba Java 开发手册
|
||
- **命名规范**:
|
||
- 类名: PascalCase (如: UserController)
|
||
- 方法名: camelCase (如: getUserList)
|
||
- 常量: UPPER_SNAKE_CASE (如: MAX_RETRY_COUNT)
|
||
- 包名: 全小写 (如: org.dromara.system)
|
||
|
||
### 🔴 数据库规范(强制性)
|
||
|
||
- **⚠️ 超级重要**: 所有数据库操作必须严格遵循 `docs/设计文档/database/mysql-database-standard.md` 规范
|
||
- **现有数据库模型**: 参考 `docs/设计文档/database/hzdyjs_xg.sql` 中的完整表结构
|
||
- **环境配置**: 开发环境数据库信息见 `docs/设计文档/database/database_msg.md`
|
||
|
||
#### 🚨 强制性核心规范
|
||
|
||
1. **所有业务表必须包含标准基础字段**
|
||
2. **统一使用雪花 ID (bigint(20)) 作为主键**
|
||
3. **强制使用逻辑删除,禁止物理删除**
|
||
4. **所有表必须支持多租户**
|
||
5. **完整的中文注释是必须的**
|
||
6. **禁止使用数据库外键约束**,关联关系通过应用层维护
|
||
|
||
#### 🔴 表命名规范(强制性)
|
||
|
||
**⚠️ 超级重要:所有数据库表必须按业务模块使用统一前缀命名!**
|
||
|
||
**模块表名前缀规范**:
|
||
- **后勤管理**:`hq_` 开头 (如:`hq_repair`, `hq_maintenance`)
|
||
- **人事管理**:`rs_` 开头 (如:`rs_employee`, `rs_department`)
|
||
- **财务管理**:`cw_` 开头 (如:`cw_budget`, `cw_expense`)
|
||
- **学工管理**:`xg_` 开头 (如:`xg_student`, `xg_dormitory`)
|
||
- **招生就业**:`zsjy_` 开头 (如:`zsjy_application`, `zsjy_job`)
|
||
- **安全保卫**:`aqbw_` 开头 (如:`aqbw_security`, `aqbw_patrol`)
|
||
- **系统公共**:`common_` 开头 (如:`common_dict`, `common_config`)
|
||
|
||
**命名示例**:
|
||
```sql
|
||
-- 后勤管理模块表
|
||
CREATE TABLE `hq_repair` (
|
||
`id` bigint(20) NOT NULL COMMENT '报修ID',
|
||
-- 其他字段...
|
||
) COMMENT='报修管理表';
|
||
|
||
-- 人事管理模块表
|
||
CREATE TABLE `rs_employee` (
|
||
`id` bigint(20) NOT NULL COMMENT '员工ID',
|
||
-- 其他字段...
|
||
) COMMENT='员工信息表';
|
||
|
||
-- 财务管理模块表
|
||
CREATE TABLE `cw_budget` (
|
||
`id` bigint(20) NOT NULL COMMENT '预算ID',
|
||
-- 其他字段...
|
||
) COMMENT='预算管理表';
|
||
|
||
-- 学工管理模块表
|
||
CREATE TABLE `xg_student` (
|
||
`id` bigint(20) NOT NULL COMMENT '学生ID',
|
||
-- 其他字段...
|
||
) COMMENT='学生信息表';
|
||
|
||
-- 招生就业模块表
|
||
CREATE TABLE `zsjy_application` (
|
||
`id` bigint(20) NOT NULL COMMENT '申请ID',
|
||
-- 其他字段...
|
||
) COMMENT='招生申请表';
|
||
|
||
-- 安全保卫模块表
|
||
CREATE TABLE `aqbw_security` (
|
||
`id` bigint(20) NOT NULL COMMENT '安全记录ID',
|
||
-- 其他字段...
|
||
) COMMENT='安全管理表';
|
||
|
||
-- 系统公共模块表
|
||
CREATE TABLE `common_dict` (
|
||
`id` bigint(20) NOT NULL COMMENT '字典ID',
|
||
-- 其他字段...
|
||
) COMMENT='数据字典表';
|
||
```
|
||
|
||
**强制性要求**:
|
||
- ✅ 新建表时必须使用对应模块前缀
|
||
- ✅ 表名采用下划线命名法(snake_case)
|
||
- ✅ 表名必须是英文,禁止使用中文或拼音
|
||
- ✅ 表注释必须用中文清楚说明表的用途
|
||
- ❌ 禁止跨模块混用前缀(如人事模块不能创建 cw_ 开头的表)
|
||
- ❌ 禁止使用无前缀的表名(系统核心表除外)
|
||
|
||
**实体类命名对应**:
|
||
```java
|
||
// 表名: hq_repair -> 实体类: HqRepair
|
||
@TableName("hq_repair")
|
||
public class HqRepair extends TenantEntity {
|
||
// ...
|
||
}
|
||
|
||
// 表名: rs_employee -> 实体类: RsEmployee
|
||
@TableName("rs_employee")
|
||
public class RsEmployee extends TenantEntity {
|
||
// ...
|
||
}
|
||
|
||
// 表名: cw_budget -> 实体类: CwBudget
|
||
@TableName("cw_budget")
|
||
public class CwBudget extends TenantEntity {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### 🔴 强制性基础字段模板
|
||
|
||
```sql
|
||
-- 所有业务表必须包含以下字段(顺序和类型必须完全一致)
|
||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)',
|
||
|
||
-- 业务字段在此处添加
|
||
|
||
PRIMARY KEY (`id`) USING BTREE,
|
||
KEY `idx_tenant_id` (`tenant_id`),
|
||
KEY `idx_deleted` (`deleted`),
|
||
KEY `idx_create_time` (`create_time`)
|
||
```
|
||
|
||
#### 🔴 AI 开发强制要求
|
||
|
||
当使用 AI 进行数据库开发时:
|
||
|
||
1. **必须先阅读** `docs/设计文档/database/mysql-database-standard.md` 规范
|
||
2. **必须使用标准模板**,不得随意修改基础字段
|
||
3. **必须保持与现有表结构的一致性**
|
||
4. **建表后必须立即更新** `hzdyjs_xg.sql` 文件
|
||
5. **必须在变更记录中记录** 数据库变更
|
||
|
||
### 🔴 API 接口规范(强制性)
|
||
|
||
**⚠️ 超级重要:所有 API 接口必须严格遵循以下统一规范!**
|
||
|
||
#### 🔐 请求头规范(强制性)
|
||
|
||
**所有需要认证的接口必须在请求头中包含 Token,格式如下:**
|
||
|
||
```json
|
||
{
|
||
"Content-Type": "application/json;charset=utf-8",
|
||
"Authorization": "Bearer {access_token}",
|
||
"clientid": "e5cd7e4891bf95d1d19206ce24a7b32e",
|
||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||
"referer": "http://localhost/login?redirect=%252Findex"
|
||
}
|
||
```
|
||
|
||
**请求头字段说明**:
|
||
|
||
- **Authorization**: `Bearer ` + Token 值(⚠️ 注意 Bearer 后面有一个空格)
|
||
- **clientid**: 客户端 ID,用于权限验证
|
||
- **Content-Type**: 请求内容类型,必须为 application/json;charset=utf-8
|
||
- **Accept-Language**: 语言设置,用于国际化
|
||
- **referer**: 来源页面,用于安全验证
|
||
|
||
**Token 格式强制要求**:
|
||
|
||
- ✅ 正确:`"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."`
|
||
- ❌ 错误:`"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."`(缺少 Bearer 前缀)
|
||
- ❌ 错误:`"Authorization": "Bearer eyJ0eXAi..."`(Bearer 后面没有空格)
|
||
|
||
#### 📊 统一响应格式(必须遵循)
|
||
|
||
**基础响应格式(分页列表)**:
|
||
|
||
```json
|
||
{
|
||
"total": 7,
|
||
"rows": [],
|
||
"code": 200,
|
||
"msg": "查询成功"
|
||
}
|
||
```
|
||
|
||
#### 🎯 响应格式详细说明
|
||
|
||
**1. 分页列表响应**
|
||
|
||
```json
|
||
{
|
||
"total": 100,
|
||
"rows": [
|
||
{
|
||
"id": 1,
|
||
"name": "示例数据1",
|
||
"createTime": "2025-06-30 12:00:00"
|
||
},
|
||
{
|
||
"id": 2,
|
||
"name": "示例数据2",
|
||
"createTime": "2025-06-30 12:01:00"
|
||
}
|
||
],
|
||
"code": 200,
|
||
"msg": "查询成功"
|
||
}
|
||
```
|
||
|
||
**2. 非分页列表响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "查询成功",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"name": "示例数据1"
|
||
},
|
||
{
|
||
"id": 2,
|
||
"name": "示例数据2"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**3. 单条数据响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"data": {
|
||
"id": 1,
|
||
"name": "示例数据",
|
||
"createTime": "2025-06-30 12:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
**4. 操作成功响应(无数据)**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
**5. 错误响应**
|
||
|
||
```json
|
||
{
|
||
"code": 400,
|
||
"msg": "参数错误:用户名不能为空",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
#### 📋 状态码规范
|
||
|
||
**成功状态码**:
|
||
|
||
- `200` - 操作成功
|
||
- `201` - 创建成功
|
||
|
||
**客户端错误(4xx)**:
|
||
|
||
- `400` - 请求参数错误
|
||
- `401` - 未认证/认证失败
|
||
- `403` - 无权限访问
|
||
- `404` - 资源不存在
|
||
- `409` - 数据冲突
|
||
|
||
**服务端错误(5xx)**:
|
||
|
||
- `500` - 服务器内部错误
|
||
- `502` - 网关错误
|
||
- `503` - 服务不可用
|
||
|
||
#### 🚨 接口开发强制要求
|
||
|
||
**1. Controller 返回格式**
|
||
|
||
```java
|
||
// ✅ 正确 - 分页接口直接返回TableDataInfo
|
||
@GetMapping("/list")
|
||
public TableDataInfo<UserVo> list(UserBo bo, PageQuery pageQuery) {
|
||
TableDataInfo<UserVo> result = userService.queryPageList(bo, pageQuery);
|
||
return result;
|
||
}
|
||
|
||
// ✅ 正确 - 非分页接口使用R类型包装
|
||
@GetMapping("/options")
|
||
public R<List<UserVo>> options() {
|
||
List<UserVo> list = userService.selectUserOptions();
|
||
return R.ok(list);
|
||
}
|
||
|
||
// ❌ 错误 - 分页接口使用R类型包装
|
||
@GetMapping("/list")
|
||
public R<TableDataInfo<UserVo>> list(UserBo bo, PageQuery pageQuery) {
|
||
TableDataInfo<UserVo> result = userService.queryPageList(bo, pageQuery);
|
||
return R.ok(result); // 这会产生多层嵌套
|
||
}
|
||
```
|
||
|
||
**2. 分页接口规范**
|
||
|
||
```java
|
||
// 分页查询直接返回 TableDataInfo 数据
|
||
@GetMapping("/list")
|
||
public TableDataInfo<UserVo> list(UserBo bo, PageQuery pageQuery) {
|
||
TableDataInfo<UserVo> result = userService.queryPageList(bo, pageQuery);
|
||
return result;
|
||
}
|
||
|
||
// 返回格式:
|
||
// {
|
||
// "total": 100,
|
||
// "rows": [...],
|
||
// "code": 200,
|
||
// "msg": "查询成功"
|
||
// }
|
||
```
|
||
|
||
**3. 非分页列表接口**
|
||
|
||
```java
|
||
// 非分页列表查询(下拉选项等)
|
||
@GetMapping("/options")
|
||
public R<List<UserVo>> options() {
|
||
List<UserVo> list = userService.selectUserOptions();
|
||
return R.ok(list);
|
||
}
|
||
|
||
// 返回格式:
|
||
// {
|
||
// "code": 200,
|
||
// "msg": "查询成功",
|
||
// "data": [...]
|
||
// }
|
||
```
|
||
|
||
**4. 单条数据接口**
|
||
|
||
```java
|
||
// 单条数据查询
|
||
@GetMapping("/{id}")
|
||
public R<UserVo> getInfo(@PathVariable Long id) {
|
||
UserVo user = userService.queryById(id);
|
||
return R.ok(user);
|
||
}
|
||
|
||
// 返回格式:
|
||
// {
|
||
// "code": 200,
|
||
// "msg": "操作成功",
|
||
// "data": {...}
|
||
// }
|
||
```
|
||
|
||
**5. 操作类接口**
|
||
|
||
```java
|
||
// 新增/修改/删除操作
|
||
@PostMapping
|
||
public R<Void> add(@RequestBody UserBo bo) {
|
||
boolean result = userService.insertByBo(bo);
|
||
return result ? R.ok() : R.fail("操作失败");
|
||
}
|
||
|
||
// 成功返回格式:
|
||
// {
|
||
// "code": 200,
|
||
// "msg": "操作成功",
|
||
// "data": null
|
||
// }
|
||
|
||
// 失败返回格式:
|
||
// {
|
||
// "code": 500,
|
||
// "msg": "操作失败",
|
||
// "data": null
|
||
// }
|
||
```
|
||
|
||
#### 🔍 接口命名规范
|
||
|
||
**RESTful 风格**:
|
||
|
||
- `GET /api/users` - 查询用户列表
|
||
- `GET /api/users/{id}` - 查询单个用户
|
||
- `POST /api/users` - 新增用户
|
||
- `PUT /api/users` - 修改用户(传完整对象)
|
||
- `PATCH /api/users/{id}` - 部分更新用户
|
||
- `DELETE /api/users/{id}` - 删除用户
|
||
|
||
**特殊功能接口**:
|
||
|
||
- `GET /api/users/options` - 获取用户下拉选项
|
||
- `GET /api/users/statistics` - 获取用户统计数据
|
||
- `POST /api/users/batch` - 批量操作用户
|
||
- `POST /api/users/{id}/enable` - 启用用户
|
||
- `POST /api/users/{id}/disable` - 禁用用户
|
||
|
||
#### 🛡️ 权限控制规范
|
||
|
||
```java
|
||
// 每个接口必须添加权限注解
|
||
@SaCheckPermission("system:user:list")
|
||
@GetMapping("/list")
|
||
public R<TableDataInfo<UserVo>> list(UserBo bo, PageQuery pageQuery) {
|
||
// 接口实现
|
||
}
|
||
```
|
||
|
||
#### 📝 参数验证规范
|
||
|
||
```java
|
||
// 使用Bean Validation进行参数校验
|
||
@PostMapping
|
||
public R<Void> add(@Validated(AddGroup.class) @RequestBody UserBo bo) {
|
||
// 接口实现
|
||
}
|
||
|
||
// VO类中的验证注解
|
||
public class UserBo {
|
||
@NotBlank(message = "用户名不能为空", groups = {AddGroup.class, EditGroup.class})
|
||
@Size(max = 30, message = "用户名长度不能超过30个字符")
|
||
private String userName;
|
||
|
||
@NotBlank(message = "手机号不能为空", groups = {AddGroup.class, EditGroup.class})
|
||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||
private String phone;
|
||
}
|
||
```
|
||
|
||
#### 🚨 接口开发检查清单
|
||
|
||
**每个新接口必须检查**:
|
||
|
||
- [ ] 是否在请求头中正确使用了 Bearer Token 格式?
|
||
- [ ] 分页接口是否直接返回 TableDataInfo 格式?
|
||
- [ ] 非分页接口是否使用 R 类型包装返回结果?
|
||
- [ ] 是否遵循了统一的响应格式规范?
|
||
- [ ] 是否添加了@SaCheckPermission 权限注解?
|
||
- [ ] 是否添加了@Operation 接口描述?
|
||
- [ ] 是否进行了参数校验?
|
||
- [ ] 是否符合 RESTful 命名规范?
|
||
- [ ] 是否包含了必要的请求头(clientid、Content-Type 等)?
|
||
- [ ] 是否在 API 文档中记录了接口信息?
|
||
|
||
#### 💥 违规后果
|
||
|
||
**如果发现接口不符合统一规范,必须立即修改!不符合规范的接口不允许合并到主分支。**
|
||
|
||
### 🔴 API 开发强制性工作流程
|
||
|
||
#### 💡 基于场地预约 API 开发实践的标准流程
|
||
|
||
**⚠️ 本流程基于场地预约管理系统的成功开发经验制定,必须严格遵循!**
|
||
|
||
#### 步骤 1:需求分析和文档创建
|
||
|
||
**在编写任何代码之前必须完成**:
|
||
|
||
- 📋 **创建需求文档**:`docs/需求文档/[模块名]/` 详细的业务需求说明
|
||
- 🏗️ **业务模块文档**:`docs/业务模块/[模块名]/README.md` 技术架构说明
|
||
- 🗄️ **数据库设计**:完整的表结构、字典数据、权限配置设计
|
||
|
||
**参考示例**:
|
||
|
||
- `docs/需求文档/后勤场地预约需求/场地预约系统需求说明.md`
|
||
- `docs/业务模块/venue-reservation/README.md`
|
||
|
||
#### 步骤 2:数据库初始化(必须先行)
|
||
|
||
**在编写业务代码之前必须完成数据库设计**:
|
||
|
||
- 🗄️ **数据表创建**:`docs/设计文档/database/[模块名]-module.sql`
|
||
- 📊 **字典数据配置**:`docs/设计文档/database/[模块名]-data.sql`
|
||
- 🔐 **菜单权限配置**:`docs/设计文档/database/[模块名]-menu.sql`
|
||
- 🧪 **示例数据插入**:便于开发和测试
|
||
|
||
**参考示例**:
|
||
|
||
```sql
|
||
-- 04-venue-reservation-module.sql (数据表)
|
||
-- 04-venue-reservation-data.sql (字典数据)
|
||
-- 04-venue-reservation-menu.sql (菜单权限)
|
||
```
|
||
|
||
#### 步骤 3:后端模块开发
|
||
|
||
**完整的 ruoyi 模块开发结构**:
|
||
|
||
```
|
||
ruoyi-[模块名]/
|
||
├── pom.xml # 模块依赖配置
|
||
├── src/main/java/org/dromara/[模块名]/
|
||
│ ├── domain/ # 实体类
|
||
│ │ ├── [Entity].java # 数据库实体(继承TenantEntity)
|
||
│ │ ├── bo/[EntityBo].java # 业务对象(含验证注解)
|
||
│ │ └── vo/[EntityVo].java # 视图对象(含Excel注解)
|
||
│ ├── mapper/ # 数据访问层
|
||
│ │ ├── [Entity]Mapper.java # Mapper接口
|
||
│ │ └── xml/[Entity]Mapper.xml # SQL映射文件
|
||
│ ├── service/ # 业务逻辑层
|
||
│ │ ├── I[Entity]Service.java # 服务接口
|
||
│ │ └── impl/[Entity]ServiceImpl.java # 服务实现
|
||
│ └── controller/ # 控制器层
|
||
│ └── [Entity]Controller.java # REST API控制器
|
||
└── src/main/resources/
|
||
└── mapper/[Entity]Mapper.xml # MyBatis映射文件
|
||
```
|
||
|
||
**开发要点**:
|
||
|
||
- ✅ **实体类**:必须继承`TenantEntity`,包含完整注释
|
||
- ✅ **业务对象**:添加`@Validated`参数校验注解
|
||
- ✅ **视图对象**:支持 Excel 导出,添加`@ExcelProperty`注解
|
||
- ✅ **权限控制**:每个接口必须添加`@SaCheckPermission`注解
|
||
- ✅ **响应格式**:分页用`TableDataInfo`,非分页用`R`类型
|
||
|
||
#### 步骤 4:API 接口规范实现
|
||
|
||
**16 个标准 API 接口设计模式**(基于场地预约实践):
|
||
|
||
**基础管理接口(6 个)**:
|
||
|
||
```java
|
||
@GetMapping("/list") // 分页查询列表
|
||
@GetMapping("/{id}") // 查询详情
|
||
@PostMapping // 新增
|
||
@PutMapping // 修改
|
||
@DeleteMapping("/{ids}") // 删除
|
||
@GetMapping("/available") // 获取可用选项
|
||
```
|
||
|
||
**业务功能接口(4 个)**:
|
||
|
||
```java
|
||
@GetMapping("/my") // 查询我的数据
|
||
@GetMapping("/pending") // 查询待处理数据
|
||
@GetMapping("/conflict") // 业务规则检查
|
||
@PostMapping("/approve") // 审批操作
|
||
```
|
||
|
||
**辅助功能接口(6 个)**:
|
||
|
||
```java
|
||
@PostMapping("/export") // 导出Excel
|
||
@PostMapping("/import") // 导入Excel
|
||
@GetMapping("/statistics") // 统计数据
|
||
@PostMapping("/batch") // 批量操作
|
||
@PostMapping("/{id}/enable") // 启用操作
|
||
@PostMapping("/{id}/disable") // 禁用操作
|
||
```
|
||
|
||
#### 步骤 5:立即更新 API 文档(强制性)
|
||
|
||
**⚠️ 代码写完后立即执行,不得延后!**
|
||
|
||
- 📖 **创建模块 API 文档**:`docs/开发文档/API/[模块名]管理API.md`
|
||
- 📋 **按标准模板编写**:参考`docs/开发文档/API/API文档模板.md`
|
||
- 🔗 **更新文档总览**:在`docs/开发文档/API/API文档总览.md`中添加导航
|
||
- ✅ **包含完整信息**:16 个接口的详细参数、响应、权限、业务规则
|
||
|
||
**参考示例**:
|
||
|
||
- `docs/开发文档/API/场地预约管理API.md` (400+行完整文档)
|
||
|
||
#### 步骤 6:创建自动化测试脚本
|
||
|
||
**完整的 Python 测试脚本开发**:
|
||
|
||
- 🐍 **测试脚本**:`test/[模块名]API测试脚本.py`
|
||
- 🔐 **AES+RSA 加密登录**:严格按照`cursorrules-api-test.md`规范
|
||
- 🧪 **完整功能覆盖**:测试所有 16 个 API 接口
|
||
- 📋 **业务规则测试**:验证时间冲突、权限控制等
|
||
- 🧹 **自动数据清理**:测试完成后清理测试数据
|
||
|
||
**测试脚本标准结构**:
|
||
|
||
```python
|
||
class [Module]APITest:
|
||
def __init__(self): # 初始化和配置
|
||
def login(self): # AES+RSA双重加密登录
|
||
def test_[entity]_management(self): # 基础管理功能测试
|
||
def test_[business]_features(self): # 业务功能测试
|
||
def test_business_rules(self): # 业务规则验证
|
||
def cleanup_test_data(self): # 测试数据清理
|
||
def run_all_tests(self): # 执行完整测试
|
||
```
|
||
|
||
#### 步骤 7:模块集成和配置
|
||
|
||
**模块集成到主项目**:
|
||
|
||
- 📦 **添加模块依赖**:在`ruoyi-modules/pom.xml`中添加子模块
|
||
- 🔗 **集成到启动模块**:在`ruoyi-admin/pom.xml`中添加依赖
|
||
- ⚙️ **版本管理**:在根`pom.xml`的`dependencyManagement`中配置版本
|
||
|
||
#### 步骤 8:记录变更日志(强制性)
|
||
|
||
**详细记录开发过程**:
|
||
|
||
- 📝 **变更记录**:`docs/变更管理/code-adjust-record-YYYY-MM-DD-{username}.md`
|
||
- ⏰ **按时间记录**:需求分析 → 数据库 → 后端开发 → 文档 → 测试的完整流程
|
||
- 📊 **包含统计**:文件数量、接口数量、测试覆盖率等
|
||
|
||
#### 步骤 9:验证和测试
|
||
|
||
**完整的验证流程**:
|
||
|
||
- 🖥️ **后端服务启动**:确保服务正常运行
|
||
- 🧪 **API 功能测试**:运行自动化测试脚本
|
||
- 📖 **文档准确性验证**:确保文档示例与实际响应一致
|
||
- 🔐 **权限控制测试**:验证接口权限和数据权限
|
||
|
||
#### 🚨 违规后果
|
||
|
||
**如果发现以下问题,该代码变更将被视为不完整:**
|
||
|
||
- ❌ 数据库变更未创建对应 SQL 脚本
|
||
- ❌ API 接口变更未同步更新文档
|
||
- ❌ 缺少自动化测试脚本
|
||
- ❌ 未记录详细的变更日志
|
||
- ❌ 模块未正确集成到主项目
|
||
|
||
#### 📊 开发完成标准
|
||
|
||
**一个完整的 API 模块开发必须包含**:
|
||
|
||
- ✅ 完整的需求和技术文档(2-3 个文档)
|
||
- ✅ 数据库脚本和权限配置(3 个 SQL 文件)
|
||
- ✅ 完整的后端模块代码(15-20 个 Java 文件)
|
||
- ✅ 详细的 API 接口文档(400+行)
|
||
- ✅ 自动化测试脚本(450+行)
|
||
- ✅ 完整的变更记录日志
|
||
- ✅ 模块成功集成和功能验证
|
||
|
||
#### 💡 最佳实践总结
|
||
|
||
**基于场地预约 API 开发的成功经验**:
|
||
|
||
1. **先文档后代码**:需求分析完成后再开始编码
|
||
2. **数据库优先**:表结构确定后再开发业务逻辑
|
||
3. **标准化接口**:16 个接口模式覆盖 90%的业务场景
|
||
4. **文档同步更新**:代码完成立即更新文档
|
||
5. **完整测试验证**:自动化测试确保质量
|
||
6. **规范化集成**:严格按照项目架构规范集成
|
||
|
||
## 4. 🔐 菜单界面和接口权限配置规范
|
||
|
||
### 🚨 权限配置核心原理
|
||
|
||
**⚠️ 超级重要:每个菜单界面和相关接口都必须在`sys_menu`表中配置对应的权限数据!**
|
||
|
||
#### 权限配置核心概念
|
||
|
||
1. **菜单权限**:控制用户是否能看到某个菜单项
|
||
2. **按钮权限**:控制用户是否能看到和使用某个功能按钮
|
||
3. **接口权限**:通过 Sa-Token 注解控制用户是否能调用某个 API 接口
|
||
4. **角色权限**:通过`sys_role_menu`表关联,控制哪个角色拥有哪些权限
|
||
|
||
#### 权限验证机制
|
||
|
||
```
|
||
用户登录 → 获取用户角色 → 查询角色权限(sys_role_menu) → 验证接口权限(@SaCheckPermission) → 允许/拒绝访问
|
||
```
|
||
|
||
### 🔴 sys_menu 表结构和权限配置
|
||
|
||
#### sys_menu 表核心字段说明
|
||
|
||
```sql
|
||
-- sys_menu表核心字段
|
||
`menu_id` bigint(20) 菜单ID(主键)
|
||
`menu_name` varchar(50) 菜单名称
|
||
`parent_id` bigint(20) 父菜单ID(0表示一级菜单)
|
||
`order_num` int(4) 显示顺序
|
||
`path` varchar(200) 路由地址
|
||
`component` varchar(255) 组件路径
|
||
`menu_type` char(1) 菜单类型(M目录 C菜单 F按钮)
|
||
`visible` char(1) 菜单状态(0显示 1隐藏)
|
||
`status` char(1) 菜单状态(0正常 1停用)
|
||
`perms` varchar(100) 权限标识(如:system:user:list)
|
||
`icon` varchar(100) 菜单图标
|
||
```
|
||
|
||
#### 权限标识(perms)命名规范
|
||
|
||
```
|
||
格式:[模块名]:[功能名]:[操作名]
|
||
|
||
示例:
|
||
- pm:repairOrder:list # 报修工单列表查询
|
||
- pm:repairOrder:add # 报修工单新增
|
||
- pm:repairOrder:edit # 报修工单修改
|
||
- pm:repairOrder:remove # 报修工单删除
|
||
- pm:repairOrder:statistics # 报修统计数据
|
||
- pm:category:list # 维修分类列表
|
||
- pm:category:options # 维修分类下拉选项
|
||
```
|
||
|
||
### 🔧 权限配置标准工作流程
|
||
|
||
#### 📋 新增功能权限配置流程
|
||
|
||
**步骤 1:分析权限需求**
|
||
|
||
```
|
||
1. 确定主菜单名称和路径
|
||
2. 列出所有功能按钮(新增、修改、删除、查询等)
|
||
3. 列出所有API接口及对应权限
|
||
4. 确定权限标识命名
|
||
```
|
||
|
||
**步骤 2:权限配置 SQL 编写**
|
||
使用 UPSERT 逻辑(先判断后操作):
|
||
|
||
```sql
|
||
-- 权限配置标准模板(UPSERT逻辑)
|
||
-- ==========================================
|
||
-- [模块名]权限配置脚本
|
||
-- 功能:为[模块名]添加完整权限配置
|
||
-- 时间:YYYY-MM-DD HH:mm:ss
|
||
-- 说明:使用UPSERT逻辑,避免重复插入
|
||
-- ==========================================
|
||
|
||
-- 第一步:检查并插入主菜单
|
||
INSERT INTO sys_menu (
|
||
menu_id, menu_name, parent_id, order_num, path, component,
|
||
query_param, is_frame, is_cache, menu_type, visible, status,
|
||
perms, icon, create_by, create_time, update_by, update_time, remark
|
||
)
|
||
SELECT
|
||
2100, '物业管理', 0, 6, 'pm', NULL,
|
||
'', 1, 0, 'M', '0', '0',
|
||
'', 'tool', 1, now(), 1, now(), '物业管理模块'
|
||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2100);
|
||
|
||
-- 第二步:检查并插入子菜单
|
||
INSERT INTO sys_menu (
|
||
menu_id, menu_name, parent_id, order_num, path, component,
|
||
query_param, is_frame, is_cache, menu_type, visible, status,
|
||
perms, icon, create_by, create_time, update_by, update_time, remark
|
||
)
|
||
SELECT
|
||
2110, '报修管理', 2100, 1, 'repairOrder', 'pm/repairOrder/index',
|
||
'', 1, 0, 'C', '0', '0',
|
||
'pm:repairOrder:list', 'form', 1, now(), 1, now(), '报修工单管理'
|
||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2110);
|
||
|
||
-- 第三步:批量插入功能权限(如果不存在)
|
||
INSERT INTO sys_menu (
|
||
menu_id, menu_name, parent_id, order_num, path, component,
|
||
query_param, is_frame, is_cache, menu_type, visible, status,
|
||
perms, icon, create_by, create_time, update_by, update_time, remark
|
||
)
|
||
SELECT * FROM (
|
||
SELECT 2111, '查询', 2110, 1, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:query', '#', 1, now(), 1, now(), '查询权限' UNION ALL
|
||
SELECT 2112, '新增', 2110, 2, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:add', '#', 1, now(), 1, now(), '新增权限' UNION ALL
|
||
SELECT 2113, '修改', 2110, 3, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:edit', '#', 1, now(), 1, now(), '修改权限'
|
||
) AS temp
|
||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = temp.menu_id);
|
||
|
||
-- 第四步:为admin角色分配权限(如果未分配)
|
||
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
|
||
SELECT 1, menu_id FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199;
|
||
```
|
||
|
||
**步骤 3:更新现有权限**
|
||
|
||
```sql
|
||
-- 更新现有权限信息(如果需要修改)
|
||
UPDATE sys_menu
|
||
SET
|
||
menu_name = '新的菜单名称',
|
||
path = '新的路径',
|
||
perms = '新的权限标识',
|
||
update_time = now(),
|
||
update_by = 1,
|
||
remark = '权限更新说明'
|
||
WHERE menu_id = 2110 AND EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2110);
|
||
```
|
||
|
||
### 🔄 权限配置操作规范
|
||
|
||
#### 🚨 强制性检查清单
|
||
|
||
**每次新增功能时必须检查**:
|
||
|
||
- [ ] 是否需要新增主菜单?
|
||
- [ ] 是否需要新增子菜单?
|
||
- [ ] 列出所有需要的功能权限(CRUD 等)
|
||
- [ ] 列出所有 API 接口及权限标识
|
||
- [ ] 确认权限标识命名符合规范
|
||
- [ ] 权限配置是否使用 UPSERT 逻辑?
|
||
- [ ] 是否为相关角色分配了权限?
|
||
|
||
#### 🔍 权限配置验证方法
|
||
|
||
**验证 SQL 模板**:
|
||
|
||
```sql
|
||
-- 1. 验证权限是否已正确配置
|
||
SELECT menu_id, menu_name, perms, menu_type
|
||
FROM sys_menu
|
||
WHERE perms LIKE 'pm:%'
|
||
ORDER BY menu_id;
|
||
|
||
-- 2. 验证角色权限分配
|
||
SELECT rm.role_id, r.role_name, m.menu_name, m.perms
|
||
FROM sys_role_menu rm
|
||
JOIN sys_role r ON rm.role_id = r.role_id
|
||
JOIN sys_menu m ON rm.menu_id = m.menu_id
|
||
WHERE m.perms LIKE 'pm:%'
|
||
ORDER BY rm.role_id, m.menu_id;
|
||
|
||
-- 3. 验证用户权限(通过角色)
|
||
SELECT u.user_name, r.role_name, m.menu_name, m.perms
|
||
FROM sys_user u
|
||
JOIN sys_user_role ur ON u.user_id = ur.user_id
|
||
JOIN sys_role r ON ur.role_id = r.role_id
|
||
JOIN sys_role_menu rm ON r.role_id = rm.role_id
|
||
JOIN sys_menu m ON rm.menu_id = m.menu_id
|
||
WHERE u.user_name = 'admin' AND m.perms LIKE 'pm:%'
|
||
ORDER BY m.menu_id;
|
||
```
|
||
|
||
### 🎯 Controller 权限注解规范
|
||
|
||
#### 标准权限注解使用
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/pm/repairOrder")
|
||
@Api(tags = "报修工单管理")
|
||
public class PmRepairOrderController extends BaseController {
|
||
|
||
// 列表查询 - 对应菜单权限
|
||
@SaCheckPermission("pm:repairOrder:list")
|
||
@GetMapping("/list")
|
||
@Operation(summary = "查询报修工单列表")
|
||
public TableDataInfo<PmRepairOrderVo> list(PmRepairOrderBo bo, PageQuery pageQuery) {
|
||
return repairOrderService.queryPageList(bo, pageQuery);
|
||
}
|
||
|
||
// 详情查询 - 对应按钮权限
|
||
@SaCheckPermission("pm:repairOrder:query")
|
||
@GetMapping("/{id}")
|
||
@Operation(summary = "获取报修工单详细信息")
|
||
public R<PmRepairOrderVo> getInfo(@PathVariable Long id) {
|
||
return R.ok(repairOrderService.queryById(id));
|
||
}
|
||
|
||
// 新增功能 - 对应按钮权限
|
||
@SaCheckPermission("pm:repairOrder:add")
|
||
@PostMapping
|
||
@Operation(summary = "新增报修工单")
|
||
public R<Void> add(@RequestBody PmRepairOrderBo bo) {
|
||
return toAjax(repairOrderService.insertByBo(bo));
|
||
}
|
||
|
||
// 修改功能 - 对应按钮权限
|
||
@SaCheckPermission("pm:repairOrder:edit")
|
||
@PutMapping
|
||
@Operation(summary = "修改报修工单")
|
||
public R<Void> edit(@RequestBody PmRepairOrderBo bo) {
|
||
return toAjax(repairOrderService.updateByBo(bo));
|
||
}
|
||
|
||
// 删除功能 - 对应按钮权限
|
||
@SaCheckPermission("pm:repairOrder:remove")
|
||
@DeleteMapping("/{ids}")
|
||
@Operation(summary = "删除报修工单")
|
||
public R<Void> remove(@PathVariable Long[] ids) {
|
||
return toAjax(repairOrderService.deleteWithValidByIds(List.of(ids)));
|
||
}
|
||
|
||
// 统计数据 - 对应功能权限
|
||
@SaCheckPermission("pm:repairOrder:statistics")
|
||
@GetMapping("/statistics")
|
||
@Operation(summary = "获取报修统计数据")
|
||
public R<Map<String, Object>> statistics() {
|
||
return R.ok(repairOrderService.getStatistics());
|
||
}
|
||
|
||
// 下拉选项 - 对应功能权限
|
||
@SaCheckPermission("pm:repairOrder:generateOrderNo")
|
||
@PostMapping("/generateOrderNo")
|
||
@Operation(summary = "生成工单编号")
|
||
public R<String> generateOrderNo() {
|
||
return R.ok(repairOrderService.generateOrderNo());
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 权限注解检查清单
|
||
|
||
- [ ] 每个 Controller 方法都有@SaCheckPermission 注解
|
||
- [ ] 权限标识与 sys_menu 表中的 perms 字段一致
|
||
- [ ] 权限标识命名符合[模块]:[功能]:[操作]规范
|
||
- [ ] 所有权限标识都已在 sys_menu 表中配置
|
||
|
||
### 🖥️ 前端权限控制规范
|
||
|
||
#### Vue 页面权限控制
|
||
|
||
```vue
|
||
<template>
|
||
<div class="app-container">
|
||
<!-- 功能按钮权限控制 -->
|
||
<el-row :gutter="10" class="mb8">
|
||
<el-col :span="1.5">
|
||
<el-button
|
||
v-hasPermi="['pm:repairOrder:add']"
|
||
type="primary"
|
||
plain
|
||
icon="Plus"
|
||
@click="handleAdd"
|
||
>
|
||
新增
|
||
</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button
|
||
v-hasPermi="['pm:repairOrder:edit']"
|
||
type="success"
|
||
plain
|
||
icon="Edit"
|
||
:disabled="single"
|
||
@click="handleUpdate"
|
||
>
|
||
修改
|
||
</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button
|
||
v-hasPermi="['pm:repairOrder:remove']"
|
||
type="danger"
|
||
plain
|
||
icon="Delete"
|
||
:disabled="multiple"
|
||
@click="handleDelete"
|
||
>
|
||
删除
|
||
</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 表格操作列权限控制 -->
|
||
<el-table-column label="操作" align="center" width="160">
|
||
<template #default="scope">
|
||
<el-tooltip content="修改" placement="top">
|
||
<el-button
|
||
v-hasPermi="['pm:repairOrder:edit']"
|
||
link
|
||
type="primary"
|
||
icon="Edit"
|
||
@click="handleUpdate(scope.row)"
|
||
></el-button>
|
||
</el-tooltip>
|
||
<el-tooltip content="删除" placement="top">
|
||
<el-button
|
||
v-hasPermi="['pm:repairOrder:remove']"
|
||
link
|
||
type="danger"
|
||
icon="Delete"
|
||
@click="handleDelete(scope.row)"
|
||
></el-button>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
#### API 调用权限检查
|
||
|
||
```typescript
|
||
// api调用前的权限检查(可选)
|
||
import { checkPermi } from '@/utils/permission';
|
||
|
||
// 在调用API前检查权限
|
||
const handleAdd = () => {
|
||
if (!checkPermi(['pm:repairOrder:add'])) {
|
||
ElMessage.error('您没有新增权限');
|
||
return;
|
||
}
|
||
// 执行新增操作
|
||
router.push('/pm/repairOrder/add');
|
||
};
|
||
|
||
const handleEdit = (row: any) => {
|
||
if (!checkPermi(['pm:repairOrder:edit'])) {
|
||
ElMessage.error('您没有修改权限');
|
||
return;
|
||
}
|
||
// 执行修改操作
|
||
router.push(`/pm/repairOrder/edit/${row.id}`);
|
||
};
|
||
```
|
||
|
||
### 📋 权限配置标准模板
|
||
|
||
#### 完整权限配置脚本模板
|
||
|
||
```sql
|
||
-- ==========================================
|
||
-- [模块名称]权限配置脚本
|
||
-- 功能:为[模块名称]添加完整权限配置
|
||
-- 时间:YYYY-MM-DD HH:mm:ss (⚠️必须填写真实时间!)
|
||
-- 说明:使用UPSERT逻辑,支持重复执行
|
||
-- ==========================================
|
||
|
||
-- 清理旧数据(谨慎使用,仅在测试环境)
|
||
-- DELETE FROM sys_role_menu WHERE menu_id BETWEEN [起始ID] AND [结束ID];
|
||
-- DELETE FROM sys_menu WHERE menu_id BETWEEN [起始ID] AND [结束ID];
|
||
|
||
-- 第一部分:主菜单配置
|
||
INSERT INTO sys_menu (
|
||
menu_id, menu_name, parent_id, order_num, path, component,
|
||
query_param, is_frame, is_cache, menu_type, visible, status,
|
||
perms, icon, create_by, create_time, update_by, update_time, remark
|
||
)
|
||
SELECT
|
||
[主菜单ID], '[主菜单名称]', 0, [排序号], '[路径]', NULL,
|
||
'', 1, 0, 'M', '0', '0',
|
||
'', '[图标]', 1, now(), 1, now(), '[模块描述]'
|
||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = [主菜单ID]);
|
||
|
||
-- 第二部分:子菜单配置
|
||
INSERT INTO sys_menu (
|
||
menu_id, menu_name, parent_id, order_num, path, component,
|
||
query_param, is_frame, is_cache, menu_type, visible, status,
|
||
perms, icon, create_by, create_time, update_by, update_time, remark
|
||
)
|
||
SELECT
|
||
[子菜单ID], '[子菜单名称]', [主菜单ID], [排序号], '[路径]', '[组件路径]',
|
||
'', 1, 0, 'C', '0', '0',
|
||
'[模块]:[功能]:list', '[图标]', 1, now(), 1, now(), '[功能描述]'
|
||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = [子菜单ID]);
|
||
|
||
-- 第三部分:功能权限配置(批量插入)
|
||
INSERT INTO sys_menu (
|
||
menu_id, menu_name, parent_id, order_num, path, component,
|
||
query_param, is_frame, is_cache, menu_type, visible, status,
|
||
perms, icon, create_by, create_time, update_by, update_time, remark
|
||
)
|
||
SELECT * FROM (
|
||
-- 基础CRUD权限
|
||
SELECT [权限ID1], '[功能名称]查询', [子菜单ID], 1, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:query', '#', 1, now(), 1, now(), '查询权限' UNION ALL
|
||
SELECT [权限ID2], '[功能名称]新增', [子菜单ID], 2, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:add', '#', 1, now(), 1, now(), '新增权限' UNION ALL
|
||
SELECT [权限ID3], '[功能名称]修改', [子菜单ID], 3, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:edit', '#', 1, now(), 1, now(), '修改权限' UNION ALL
|
||
SELECT [权限ID4], '[功能名称]删除', [子菜单ID], 4, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:remove', '#', 1, now(), 1, now(), '删除权限' UNION ALL
|
||
|
||
-- 特殊功能权限
|
||
SELECT [权限ID5], '[特殊功能名称]', [子菜单ID], 5, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:[特殊操作]', '#', 1, now(), 1, now(), '[特殊功能描述]'
|
||
) AS temp
|
||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = temp.menu_id);
|
||
|
||
-- 第四部分:权限分配(为admin角色分配权限)
|
||
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
|
||
SELECT 1, menu_id FROM sys_menu WHERE menu_id BETWEEN [起始ID] AND [结束ID];
|
||
|
||
-- 第五部分:验证配置结果
|
||
SELECT '权限配置验证' as message,
|
||
COUNT(*) as menu_count,
|
||
(SELECT COUNT(*) FROM sys_role_menu WHERE role_id = 1 AND menu_id BETWEEN [起始ID] AND [结束ID]) as role_permission_count
|
||
FROM sys_menu
|
||
WHERE menu_id BETWEEN [起始ID] AND [结束ID];
|
||
```
|
||
|
||
### 🧪 权限配置测试和验证
|
||
|
||
#### 权限测试标准流程
|
||
|
||
```
|
||
1. **权限配置验证**
|
||
- 检查sys_menu表中权限记录
|
||
- 检查sys_role_menu表中角色权限分配
|
||
- 验证权限标识命名规范
|
||
↓
|
||
2. **后端接口测试**
|
||
- 使用Token调用接口
|
||
- 验证权限认证是否正常
|
||
- 测试无权限用户访问是否被拒绝
|
||
↓
|
||
3. **前端界面测试**
|
||
- 登录系统查看菜单是否显示
|
||
- 检查功能按钮是否根据权限显示/隐藏
|
||
- 验证无权限用户的界面表现
|
||
↓
|
||
4. **权限缓存测试**
|
||
- 重启服务后权限是否正常
|
||
- 重新登录后权限是否正确加载
|
||
- 权限变更后是否需要重新登录
|
||
```
|
||
|
||
#### 权限测试脚本模板
|
||
|
||
```javascript
|
||
// 权限验证测试脚本
|
||
async function testPermissions(token, permissions) {
|
||
const results = [];
|
||
|
||
for (const perm of permissions) {
|
||
try {
|
||
const response = await callAPIWithToken(token, perm.endpoint, perm.data, perm.method);
|
||
results.push({
|
||
permission: perm.name,
|
||
endpoint: perm.endpoint,
|
||
success: response.success,
|
||
status: response.status,
|
||
message: response.success ? '权限验证通过' : '权限验证失败',
|
||
});
|
||
} catch (error) {
|
||
results.push({
|
||
permission: perm.name,
|
||
endpoint: perm.endpoint,
|
||
success: false,
|
||
status: 'ERROR',
|
||
message: error.message,
|
||
});
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
// 使用示例
|
||
const permissionTests = [
|
||
{ name: 'pm:repairOrder:list', endpoint: '/pm/repairOrder/list', method: 'GET', data: null },
|
||
{ name: 'pm:repairOrder:add', endpoint: '/pm/repairOrder', method: 'POST', data: {} },
|
||
{
|
||
name: 'pm:repairOrder:statistics',
|
||
endpoint: '/pm/repairOrder/statistics',
|
||
method: 'GET',
|
||
data: null,
|
||
},
|
||
];
|
||
|
||
// 执行权限测试
|
||
testPermissions(token, permissionTests).then((results) => {
|
||
console.log('权限测试结果:', results);
|
||
});
|
||
```
|
||
|
||
### 🚨 权限配置强制要求总结
|
||
|
||
#### 必须遵循的规范
|
||
|
||
1. **⚠️ 每个新功能都必须配置权限**:菜单权限 + 按钮权限 + 接口权限
|
||
2. **⚠️ 使用 UPSERT 逻辑**:权限配置脚本必须支持重复执行
|
||
3. **⚠️ 权限标识规范**:严格按照[模块]:[功能]:[操作]格式命名
|
||
4. **⚠️ Controller 注解必须**:每个接口方法都要有@SaCheckPermission 注解
|
||
5. **⚠️ 前端权限控制**:功能按钮必须使用 v-hasPermi 指令控制
|
||
6. **⚠️ 权限测试验证**:配置完成后必须进行完整的权限测试
|
||
7. **⚠️ 文档同步更新**:权限配置必须在 API 文档中说明
|
||
|
||
#### 违规检查清单
|
||
|
||
- [ ] 新功能是否缺少权限配置?
|
||
- [ ] Controller 方法是否缺少权限注解?
|
||
- [ ] 权限标识命名是否符合规范?
|
||
- [ ] 前端按钮是否有权限控制?
|
||
- [ ] 权限配置是否支持重复执行?
|
||
- [ ] 是否进行了权限功能测试?
|
||
- [ ] API 文档是否更新了权限信息?
|
||
|
||
### 🔴 VO 类基础字段规范(强制性)
|
||
|
||
**⚠️ 超级重要:为了项目一致性,所有 VO 类必须包含统一的基础字段!**
|
||
|
||
#### 🎯 规范背景
|
||
|
||
基于实际开发中发现的问题,不同查询方式对 VO 类字段的要求存在差异:
|
||
|
||
| 查询方式 | 基础字段处理 | VO 类要求 | 示例 |
|
||
| ------------------------- | -------------- | -------------- | ---------------------- |
|
||
| **MyBatis-Plus 自动查询** | 不包含继承字段 | 无需基础字段 | `HqSatCategoryVo` |
|
||
| **自定义 SQL 查询** | 明确包含字段 | 必须有对应字段 | `HqVenueReservationVo` |
|
||
|
||
**问题案例**:
|
||
|
||
```
|
||
org.mybatis.spring.MyBatisSystemException:
|
||
Could not set property 'createBy' of 'class org.dromara.hq.domain.vo.HqVenueReservationVo'
|
||
with value '1' Cause: There is no setter for property named 'createBy'
|
||
```
|
||
|
||
#### 🚨 强制性统一规范
|
||
|
||
**为了避免开发过程中的不一致问题,所有 VO 类必须包含完整的基础字段!**
|
||
|
||
#### 📋 标准 VO 类基础字段模板
|
||
|
||
```java
|
||
/**
|
||
* 标准VO类模板
|
||
*
|
||
* @author [作者]
|
||
*/
|
||
@Data
|
||
@ExcelIgnoreUnannotated
|
||
@AutoMapper(target = [Entity].class)
|
||
public class [Entity]Vo implements Serializable {
|
||
|
||
@Serial
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
// ========== 业务字段 ==========
|
||
|
||
/**
|
||
* 主键ID
|
||
*/
|
||
@ExcelProperty(value = "主键ID")
|
||
private Long id;
|
||
|
||
// ... 其他业务字段 ...
|
||
|
||
// ========== 基础字段(强制性,所有VO类必须包含) ==========
|
||
|
||
/**
|
||
* 创建者
|
||
*/
|
||
private Long createBy;
|
||
|
||
/**
|
||
* 创建时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
@ExcelProperty(value = "创建时间")
|
||
private Date createTime;
|
||
|
||
/**
|
||
* 更新者
|
||
*/
|
||
private Long updateBy;
|
||
|
||
/**
|
||
* 更新时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
|
||
}
|
||
```
|
||
|
||
#### 🛠️ 基础字段说明
|
||
|
||
| 字段 | 类型 | 必需 | 说明 | JSON 格式化 | Excel 导出 |
|
||
| -------------- | ------ | ---- | --------- | ----------- | ---------- |
|
||
| **createBy** | `Long` | ✅ | 创建者 ID | 否 | 否 |
|
||
| **createTime** | `Date` | ✅ | 创建时间 | ✅ | ✅ |
|
||
| **updateBy** | `Long` | ✅ | 更新者 ID | 否 | 否 |
|
||
| **updateTime** | `Date` | ✅ | 更新时间 | ✅ | 否 |
|
||
|
||
#### 📝 字段使用注解规范
|
||
|
||
```java
|
||
// 时间字段必须使用JSON格式化注解
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date createTime;
|
||
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
|
||
// 创建时间可选择性导出到Excel
|
||
@ExcelProperty(value = "创建时间")
|
||
private Date createTime;
|
||
|
||
// 更新时间通常不导出到Excel
|
||
private Date updateTime;
|
||
```
|
||
|
||
#### 🚨 强制性要求
|
||
|
||
1. **⚠️ 所有 VO 类必须包含四个基础字段**:createBy、createTime、updateBy、updateTime
|
||
2. **⚠️ 字段类型必须统一**:createBy/updateBy 为 Long,createTime/updateTime 为 Date
|
||
3. **⚠️ 时间字段必须使用 JSON 格式化注解**:`@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")`
|
||
4. **⚠️ 新生成的 VO 类必须检查基础字段完整性**
|
||
5. **⚠️ 修复现有 VO 类时必须补全缺失的基础字段**
|
||
|
||
#### 🔍 VO 类检查清单
|
||
|
||
**新增 VO 类检查**:
|
||
|
||
- [ ] 是否包含 createBy 字段?
|
||
- [ ] 是否包含 createTime 字段?
|
||
- [ ] 是否包含 updateBy 字段?
|
||
- [ ] 是否包含 updateTime 字段?
|
||
- [ ] 时间字段是否有 JSON 格式化注解?
|
||
- [ ] 字段类型是否正确(Long/Date)?
|
||
|
||
**现有 VO 类修复**:
|
||
|
||
- [ ] 检查 MyBatis 映射错误日志中提到的字段
|
||
- [ ] 补全缺失的基础字段
|
||
- [ ] 重新构建模块 jar 包
|
||
- [ ] 触发热重启验证修复效果
|
||
|
||
#### 💡 最佳实践
|
||
|
||
**1. 代码生成器配置**
|
||
|
||
```java
|
||
// 在代码生成模板中包含基础字段
|
||
// generator/template/vo.java.vm
|
||
/**
|
||
* 创建者
|
||
*/
|
||
private Long createBy;
|
||
|
||
/**
|
||
* 创建时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
@ExcelProperty(value = "创建时间")
|
||
private Date createTime;
|
||
|
||
/**
|
||
* 更新者
|
||
*/
|
||
private Long updateBy;
|
||
|
||
/**
|
||
* 更新时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
```
|
||
|
||
**2. 开发流程规范**
|
||
|
||
```
|
||
新增VO类 → 使用标准模板 → 包含基础字段 → 编译测试
|
||
修复VO类 → 补全缺失字段 → 重新构建 → 热重启验证
|
||
```
|
||
|
||
**3. 问题预防措施**
|
||
|
||
- 定期检查 VO 类与 Entity 类的字段对应关系
|
||
- MyBatis 映射错误要及时检查字段定义
|
||
- 使用 IDE 插件自动检查 VO 类完整性
|
||
- 在代码评审中重点检查 VO 类基础字段
|
||
|
||
#### 🎯 修复现有问题的标准流程
|
||
|
||
**当遇到 MyBatis 映射错误时**:
|
||
|
||
```bash
|
||
# 1. 分析错误日志,确认缺失字段
|
||
grep "Could not set property" logs/error.log
|
||
|
||
# 2. 修复VO类,添加缺失字段
|
||
# 编辑对应的VO类文件
|
||
|
||
# 3. 重新构建模块
|
||
cd ruoyi-modules/[模块名]
|
||
mvn clean install -U -Dmaven.test.skip=true
|
||
|
||
# 4. 重新构建主应用
|
||
cd ../../ruoyi-admin
|
||
mvn clean compile -U -Dmaven.test.skip=true
|
||
|
||
# 5. 触发热重启
|
||
echo "reload" > src/main/resources/.reloadtrigger
|
||
```
|
||
|
||
#### 📊 实际修复案例总结
|
||
|
||
**案例 1:HqVenueInfoVo**
|
||
|
||
```java
|
||
// 问题:缺少createBy、updateBy、updateTime字段
|
||
// 修复:添加标准基础字段
|
||
// 结果:场地管理界面正常加载
|
||
```
|
||
|
||
**案例 2:HqVenueReservationVo**
|
||
|
||
```java
|
||
// 问题:缺少createBy、updateBy、updateTime字段
|
||
// 修复:添加标准基础字段
|
||
// 结果:场地预约界面正常加载
|
||
```
|
||
|
||
**案例 3:HqSatCategoryVo**
|
||
|
||
```java
|
||
// 情况:使用MyBatis-Plus自动查询,无需基础字段
|
||
// 建议:为了一致性,仍建议添加基础字段
|
||
// 优势:支持未来可能的自定义SQL查询
|
||
```
|
||
|
||
### 📋 VO 类规范违规检查
|
||
|
||
**开发阶段检查**:
|
||
|
||
- 新增 VO 类时未包含基础字段
|
||
- 时间字段未使用 JSON 格式化注解
|
||
- 字段类型不符合规范(如用 String 代替 Date)
|
||
|
||
**测试阶段检查**:
|
||
|
||
- MyBatis 映射错误日志中提到字段缺失
|
||
- Excel 导出功能异常
|
||
- 时间格式显示不正确
|
||
|
||
**生产阶段检查**:
|
||
|
||
- 接口返回数据中基础字段为空
|
||
- 前端显示创建时间/更新时间异常
|
||
- 数据审计功能不完整
|
||
|
||
### 🎯 规范总结
|
||
|
||
**统一 VO 类基础字段规范的目标**:
|
||
|
||
1. **🔄 兼容性**:无论使用 MyBatis-Plus 自动查询还是自定义 SQL,都能正常工作
|
||
2. **📊 一致性**:所有 VO 类具有统一的基础字段结构
|
||
3. **🚀 可维护性**:减少因字段缺失导致的运行时错误
|
||
4. **📈 可扩展性**:支持未来可能的查询方式变更
|
||
|
||
**⚠️ 记住:宁可多加字段保证兼容性,也不要因为字段缺失导致系统异常!**
|
||
|
||
### 🔴 MyBatis-Plus 自定义 SQL 规范(强制性)
|
||
|
||
**⚠️ 基于实际开发中遇到的 SQL 拼接错误,制定强制性规范!**
|
||
|
||
#### 🎯 常见问题
|
||
|
||
**SQL 拼接错误案例**:
|
||
|
||
```xml
|
||
<!-- ❌ 错误写法 -->
|
||
<select id="selectPage">
|
||
SELECT * FROM table_name
|
||
<where>
|
||
and deleted = 0
|
||
${ew.sqlSegment}
|
||
</where>
|
||
order by create_time desc
|
||
</select>
|
||
```
|
||
|
||
**生成的错误 SQL**:
|
||
|
||
```sql
|
||
WHERE and deleted = 0 AND (applicant_id = ?) ORDER BY create_time DESC order by create_time desc
|
||
```
|
||
|
||
#### 🚨 强制性规范
|
||
|
||
**1. `${ew.sqlSegment}`使用规范**
|
||
|
||
```xml
|
||
<!-- ✅ 正确写法 -->
|
||
<select id="selectPage">
|
||
SELECT * FROM table_name
|
||
<where>
|
||
deleted = 0
|
||
${ew.sqlSegment}
|
||
</where>
|
||
</select>
|
||
```
|
||
|
||
**要点**:
|
||
|
||
- ✅ `${ew.sqlSegment}`内容**不包含前置 AND**
|
||
- ✅ 固定条件和动态条件之间**必须显式添加 AND**
|
||
- ✅ 使用`<if>`标签进行安全的条件拼接
|
||
- ✅ 使用`<where>`标签自动处理 WHERE 关键字
|
||
|
||
**2. ORDER BY 重复问题**
|
||
|
||
```java
|
||
// Java代码中设置排序(推荐)
|
||
LambdaQueryWrapper<Entity> lqw = Wrappers.lambdaQuery();
|
||
lqw.orderByDesc(Entity::getCreateTime);
|
||
```
|
||
|
||
```xml
|
||
<!-- XML中不要重复设置ORDER BY -->
|
||
<select id="selectPage">
|
||
SELECT * FROM table_name
|
||
<where>
|
||
deleted = 0
|
||
${ew.sqlSegment}
|
||
</where>
|
||
<!-- ❌ 不要在这里再写 order by -->
|
||
</select>
|
||
```
|
||
|
||
**3. 标准 Mapper.xml 模板**
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8" ?>
|
||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||
<mapper namespace="org.dromara.[module].mapper.[Entity]Mapper">
|
||
|
||
<!-- 结果映射(包含基础字段) -->
|
||
<resultMap type="org.dromara.[module].domain.vo.[Entity]Vo" id="[Entity]Result">
|
||
<result property="id" column="id"/>
|
||
<!-- 业务字段映射 -->
|
||
<result property="createBy" column="create_by"/>
|
||
<result property="createTime" column="create_time"/>
|
||
<result property="updateBy" column="update_by"/>
|
||
<result property="updateTime" column="update_time"/>
|
||
</resultMap>
|
||
|
||
<!-- 基础查询SQL -->
|
||
<sql id="select[Entity]Vo">
|
||
select id, [业务字段], create_by, create_time, update_by, update_time
|
||
from [table_name]
|
||
</sql>
|
||
|
||
<!-- 分页查询(标准模板) -->
|
||
<select id="select[Entity]Page" resultMap="[Entity]Result">
|
||
<include refid="select[Entity]Vo"/>
|
||
<where>
|
||
deleted = 0
|
||
<if test="ew != null and ew.sqlSegment != null and ew.sqlSegment != ''">
|
||
AND ${ew.sqlSegment}
|
||
</if>
|
||
</where>
|
||
</select>
|
||
|
||
</mapper>
|
||
```
|
||
|
||
#### 🔍 SQL 拼接检查清单
|
||
|
||
**开发阶段检查**:
|
||
|
||
- [ ] `${ew.sqlSegment}`前是否误加了`AND`?
|
||
- [ ] 是否存在重复的 ORDER BY 子句?
|
||
- [ ] `<where>`标签是否正确使用?
|
||
- [ ] VO 类是否包含完整的基础字段?
|
||
|
||
**测试阶段检查**:
|
||
|
||
- [ ] 分页查询是否正常工作?
|
||
- [ ] SQL 日志中是否有语法错误?
|
||
- [ ] 条件查询是否按预期执行?
|
||
|
||
#### 🛠️ 调试和排错
|
||
|
||
**启用 MyBatis SQL 日志**:
|
||
|
||
```yaml
|
||
# application-dev.yml
|
||
logging:
|
||
level:
|
||
org.dromara.[module].mapper: debug
|
||
```
|
||
|
||
**SQL 错误快速定位**:
|
||
|
||
```bash
|
||
# 1. 查看错误日志
|
||
grep "Failed to process, Error SQL" logs/error.log
|
||
|
||
# 2. 检查生成的SQL结构
|
||
# 关注WHERE子句和ORDER BY的拼接
|
||
|
||
# 3. 验证Mapper.xml中的拼接逻辑
|
||
```
|
||
|
||
#### 💡 最佳实践
|
||
|
||
**1. 统一的 Mapper 方法命名**:
|
||
|
||
```java
|
||
// 分页查询方法
|
||
Page<EntityVo> selectEntityPage(Page<Entity> page, @Param("ew") LambdaQueryWrapper<Entity> lqw);
|
||
|
||
// 列表查询方法
|
||
List<EntityVo> selectEntityList(@Param("ew") LambdaQueryWrapper<Entity> lqw);
|
||
```
|
||
|
||
**2. Service 中的条件构造**:
|
||
|
||
```java
|
||
private LambdaQueryWrapper<Entity> buildQueryWrapper(EntityBo bo) {
|
||
LambdaQueryWrapper<Entity> lqw = Wrappers.lambdaQuery();
|
||
// 添加查询条件
|
||
lqw.eq(ObjectUtil.isNotNull(bo.getId()), Entity::getId, bo.getId());
|
||
// 设置排序(统一在Java代码中设置)
|
||
lqw.orderByDesc(Entity::getCreateTime);
|
||
return lqw;
|
||
}
|
||
```
|
||
|
||
**3. 错误预防措施**:
|
||
|
||
- 代码生成器模板中使用标准的 SQL 拼接格式
|
||
- 代码评审时重点检查 Mapper.xml 的 SQL 拼接
|
||
- 单元测试中验证生成的 SQL 语句
|
||
- 使用 IDE 插件检查 XML 语法
|
||
|
||
#### 🚨 强制性要求总结
|
||
|
||
1. **⚠️ 必须在固定条件和`${ew.sqlSegment}`之间显式添加 AND**
|
||
2. **⚠️ 必须使用`<if>`标签安全拼接`${ew.sqlSegment}`**
|
||
3. **⚠️ 禁止在 XML 和 Java 代码中重复设置 ORDER BY**
|
||
4. **⚠️ 必须使用`<where>`标签处理条件拼接**
|
||
5. **⚠️ 必须在 VO 类中包含完整的基础字段**
|
||
6. **⚠️ 必须在测试中验证 SQL 拼接结果**
|
||
|
||
**违规处理**:不符合规范的 Mapper.xml 将导致运行时 SQL 错误,必须立即修复!
|
||
|
||
**⚠️ 记住:宁可多加字段保证兼容性,也不要因为字段缺失导致系统异常!**
|
||
|
||
## 5. 标准代码模板
|
||
|
||
### Controller 模板
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/system/user")
|
||
@Api(tags = "用户管理")
|
||
public class SysUserController extends BaseController {
|
||
|
||
@Autowired
|
||
private ISysUserService userService;
|
||
|
||
@SaCheckPermission("system:user:list")
|
||
@GetMapping("/list")
|
||
@Operation(summary = "查询用户列表")
|
||
public TableDataInfo<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {
|
||
return userService.queryPageList(user, pageQuery);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Service 模板
|
||
|
||
```java
|
||
@Service
|
||
public class SysUserServiceImpl implements ISysUserService {
|
||
|
||
@Autowired
|
||
private SysUserMapper userMapper;
|
||
|
||
@Override
|
||
public TableDataInfo<SysUserVo> queryPageList(SysUserBo bo, PageQuery pageQuery) {
|
||
LambdaQueryWrapper<SysUser> lqw = buildQueryWrapper(bo);
|
||
Page<SysUserVo> result = userMapper.selectVoPage(pageQuery.build(), lqw);
|
||
return TableDataInfo.build(result);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Mapper 模板
|
||
|
||
```java
|
||
@Mapper
|
||
public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
|
||
|
||
/**
|
||
* 查询用户列表
|
||
*/
|
||
List<SysUserVo> selectUserList(SysUserBo bo);
|
||
|
||
/**
|
||
* 根据条件分页查询用户列表
|
||
*/
|
||
Page<SysUserVo> selectUserPage(Page<SysUser> page, @Param("bo") SysUserBo bo);
|
||
}
|
||
```
|