531 lines
11 KiB
Markdown
531 lines
11 KiB
Markdown
# 出国行程变更申请 - VO字段缺失修复
|
||
|
||
## 🔴 MyBatis映射错误
|
||
|
||
### 错误信息
|
||
|
||
```
|
||
Error querying database.
|
||
Cause: org.apache.ibatis.reflection.ReflectionException:
|
||
Could not set property 'createDept' of 'class org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo'
|
||
with value '103'
|
||
Cause: org.apache.ibatis.reflection.ReflectionException:
|
||
There is no setter for property named 'createDept' in
|
||
'class org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo'
|
||
```
|
||
|
||
### 错误原因
|
||
|
||
**SQL查询返回了 `create_dept` 字段,但VO类中没有对应的属性!**
|
||
|
||
```sql
|
||
-- SQL查询中包含 create_dept 字段
|
||
SELECT
|
||
c.change_id,
|
||
c.record_id,
|
||
...
|
||
c.create_dept, -- ❌ 查询返回了这个字段
|
||
c.create_by,
|
||
c.create_time,
|
||
...
|
||
FROM rs_overseas_itinerary_change c
|
||
```
|
||
|
||
```java
|
||
// RsOverseasItineraryChangeVo.java (修复前)
|
||
@Data
|
||
public class RsOverseasItineraryChangeVo {
|
||
|
||
private Integer deleted;
|
||
|
||
// ❌ 缺少 createDept 字段!
|
||
|
||
private Long createBy; // MyBatis找不到createDept的setter,抛出异常
|
||
private Date createTime;
|
||
...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 修复方案
|
||
|
||
### 添加缺失的 createDept 字段
|
||
|
||
**修复文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasItineraryChangeVo.java`
|
||
|
||
**修复代码**:
|
||
|
||
```java
|
||
/**
|
||
* 删除标志(0代表存在 1代表删除)
|
||
*/
|
||
private Integer deleted;
|
||
|
||
/**
|
||
* 创建部门 ✅ 新增字段
|
||
*/
|
||
private Long createDept;
|
||
|
||
/**
|
||
* 创建者
|
||
*/
|
||
private Long createBy;
|
||
|
||
/**
|
||
* 创建时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date createTime;
|
||
|
||
/**
|
||
* 更新者
|
||
*/
|
||
private Long updateBy;
|
||
|
||
/**
|
||
* 更新时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 VO类基础字段统一规范
|
||
|
||
### 🚨 强制性要求(来自项目规范)
|
||
|
||
**所有VO类必须包含以下基础字段**:
|
||
|
||
```java
|
||
/**
|
||
* 创建部门
|
||
*/
|
||
private Long createDept;
|
||
|
||
/**
|
||
* 创建者
|
||
*/
|
||
private Long createBy;
|
||
|
||
/**
|
||
* 创建时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date createTime;
|
||
|
||
/**
|
||
* 更新者
|
||
*/
|
||
private Long updateBy;
|
||
|
||
/**
|
||
* 更新时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
```
|
||
|
||
### 为什么必须包含?
|
||
|
||
#### 场景1:MyBatis-Plus 自动查询
|
||
```java
|
||
// 使用 selectList 等方法
|
||
List<RsOverseasItineraryChangeVo> list = mapper.selectVoList(wrapper);
|
||
```
|
||
- ✅ MyBatis-Plus 只查询VO类中定义的字段
|
||
- ✅ 如果VO中没有 `createDept`,查询SQL不会包含这个字段
|
||
- ✅ 不会报错
|
||
|
||
#### 场景2:自定义SQL查询(本次错误场景)
|
||
```xml
|
||
<!-- RsOverseasItineraryChangeMapper.xml -->
|
||
<select id="selectChangeList" resultType="RsOverseasItineraryChangeVo">
|
||
SELECT
|
||
c.change_id,
|
||
c.record_id,
|
||
...
|
||
c.create_dept, -- ✅ SQL明确查询了这个字段
|
||
c.create_by,
|
||
c.create_time,
|
||
...
|
||
FROM rs_overseas_itinerary_change c
|
||
</select>
|
||
```
|
||
- ❌ SQL返回了 `create_dept` 字段
|
||
- ❌ 但VO类中没有 `createDept` 属性
|
||
- ❌ MyBatis尝试调用 `setCreateDept()` 方法
|
||
- ❌ 找不到setter方法,抛出 `ReflectionException`
|
||
- ❌ 导致整个查询失败,返回500错误
|
||
|
||
### 对比示例
|
||
|
||
#### ❌ 错误的VO类(缺少基础字段)
|
||
```java
|
||
@Data
|
||
public class RsOverseasItineraryChangeVo {
|
||
private Long changeId;
|
||
private Long recordId;
|
||
private String applicantName;
|
||
// ... 业务字段
|
||
|
||
// ❌ 缺少基础字段!
|
||
// private Long createDept;
|
||
// private Long createBy;
|
||
// private Date createTime;
|
||
// private Long updateBy;
|
||
// private Date updateTime;
|
||
}
|
||
```
|
||
|
||
**结果**:
|
||
- 使用MyBatis-Plus自动查询 → ✅ 正常
|
||
- 使用自定义SQL查询(包含基础字段) → ❌ 500错误
|
||
|
||
#### ✅ 正确的VO类(包含基础字段)
|
||
```java
|
||
@Data
|
||
public class RsOverseasItineraryChangeVo {
|
||
private Long changeId;
|
||
private Long recordId;
|
||
private String applicantName;
|
||
// ... 业务字段
|
||
|
||
// ✅ 包含完整的基础字段
|
||
private Long createDept;
|
||
private Long createBy;
|
||
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date createTime;
|
||
|
||
private Long updateBy;
|
||
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
}
|
||
```
|
||
|
||
**结果**:
|
||
- 使用MyBatis-Plus自动查询 → ✅ 正常
|
||
- 使用自定义SQL查询(包含基础字段) → ✅ 正常
|
||
|
||
---
|
||
|
||
## 🔧 修复步骤
|
||
|
||
### 1. 修改VO类
|
||
|
||
✅ **已完成** - 添加 `createDept` 字段
|
||
|
||
### 2. 重新编译后端
|
||
|
||
**方法1:使用Maven命令(推荐)**
|
||
```bash
|
||
cd /Users/weizzzz/Documents/Cursor/0828CURSOR/all-project/backend
|
||
mvn clean compile -DskipTests
|
||
```
|
||
|
||
**方法2:重启后端服务**
|
||
```bash
|
||
cd /Users/weizzzz/Documents/Cursor/0828CURSOR/all-project/backend
|
||
mvn spring-boot:run
|
||
```
|
||
|
||
**方法3:使用IDE重新编译**
|
||
- IDEA: Build → Rebuild Project
|
||
- Eclipse: Project → Clean
|
||
|
||
### 3. 验证修复
|
||
|
||
**测试步骤**:
|
||
1. 重启后端服务
|
||
2. 刷新前端页面
|
||
3. 访问"出国行程变更管理"
|
||
4. 验证列表是否正常显示
|
||
|
||
**预期结果**:
|
||
- ✅ 列表正常加载
|
||
- ✅ 没有500错误
|
||
- ✅ 能看到所有变更申请数据
|
||
|
||
---
|
||
|
||
## 📊 问题根源分析
|
||
|
||
### 时间线
|
||
|
||
```
|
||
1. 数据库设计 → 表包含 create_dept 字段 ✅
|
||
2. Entity类设计 → 继承BaseEntity,包含基础字段 ✅
|
||
3. Mapper.xml设计 → SQL查询包含 create_dept ✅
|
||
4. VO类设计 → ❌ 忘记添加 create_dept 字段
|
||
5. 运行时错误 → MyBatis无法映射字段 → 500错误
|
||
```
|
||
|
||
### 为什么之前没发现?
|
||
|
||
#### 原因1:提交功能先开发
|
||
```java
|
||
// 提交API - 使用Entity类,不是VO类
|
||
public R<Void> add(@RequestBody RsOverseasItineraryChangeBo bo) {
|
||
return toAjax(rsOverseasItineraryChangeService.insertByBo(bo));
|
||
}
|
||
```
|
||
- ✅ Entity类继承 `BaseEntity`,自动包含所有基础字段
|
||
- ✅ 提交功能正常工作
|
||
|
||
#### 原因2:列表查询后开发
|
||
```java
|
||
// 列表API - 使用VO类
|
||
public TableDataInfo<RsOverseasItineraryChangeVo> list(
|
||
RsOverseasItineraryChangeBo bo,
|
||
PageQuery pageQuery
|
||
) {
|
||
return rsOverseasItineraryChangeService.queryPageList(bo, pageQuery);
|
||
}
|
||
```
|
||
- ❌ VO类缺少 `createDept` 字段
|
||
- ❌ 第一次查询列表时才发现问题
|
||
|
||
#### 原因3:测试不充分
|
||
- ✅ 测试了提交功能 → 通过
|
||
- ❌ 没有测试列表查询 → 发现问题
|
||
|
||
---
|
||
|
||
## 🎯 最佳实践建议
|
||
|
||
### 1. VO类设计模板
|
||
|
||
**所有VO类都应该使用这个模板**:
|
||
|
||
```java
|
||
package org.dromara.rs.domain.vo;
|
||
|
||
import lombok.Data;
|
||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||
import java.io.Serializable;
|
||
import java.util.Date;
|
||
|
||
/**
|
||
* XXX视图对象
|
||
*
|
||
* @author xxx
|
||
* @date xxxx-xx-xx
|
||
*/
|
||
@Data
|
||
public class XxxVo implements Serializable {
|
||
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
// ========================================
|
||
// 业务字段
|
||
// ========================================
|
||
|
||
/**
|
||
* 主键ID
|
||
*/
|
||
private Long id;
|
||
|
||
/**
|
||
* 业务字段1
|
||
*/
|
||
private String field1;
|
||
|
||
// ... 其他业务字段
|
||
|
||
// ========================================
|
||
// 基础字段(必须包含)
|
||
// ========================================
|
||
|
||
/**
|
||
* 租户编号
|
||
*/
|
||
private String tenantId;
|
||
|
||
/**
|
||
* 删除标志(0代表存在 1代表删除)
|
||
*/
|
||
private Integer deleted;
|
||
|
||
/**
|
||
* 创建部门
|
||
*/
|
||
private Long createDept;
|
||
|
||
/**
|
||
* 创建者
|
||
*/
|
||
private Long createBy;
|
||
|
||
/**
|
||
* 创建时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date createTime;
|
||
|
||
/**
|
||
* 更新者
|
||
*/
|
||
private Long updateBy;
|
||
|
||
/**
|
||
* 更新时间
|
||
*/
|
||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||
private Date updateTime;
|
||
}
|
||
```
|
||
|
||
### 2. 检查清单
|
||
|
||
**创建新VO类时,必须检查**:
|
||
|
||
- [ ] 包含业务字段
|
||
- [ ] 包含 `tenantId`
|
||
- [ ] 包含 `deleted`
|
||
- [ ] 包含 `createDept` ⭐
|
||
- [ ] 包含 `createBy` ⭐
|
||
- [ ] 包含 `createTime` ⭐(带 `@JsonFormat`)
|
||
- [ ] 包含 `updateBy` ⭐
|
||
- [ ] 包含 `updateTime` ⭐(带 `@JsonFormat`)
|
||
|
||
### 3. 测试建议
|
||
|
||
**完整的测试流程**:
|
||
|
||
```
|
||
1. 测试新增功能
|
||
2. 测试列表查询 ⭐ 验证VO字段映射
|
||
3. 测试详情查询 ⭐ 验证VO字段映射
|
||
4. 测试修改功能
|
||
5. 测试删除功能
|
||
```
|
||
|
||
**关键点**: 先测试查询,确保VO字段定义正确!
|
||
|
||
---
|
||
|
||
## 📚 相关规范
|
||
|
||
### 项目规范文档
|
||
|
||
**位置**: `.cursor/cursorrules-backend.md`
|
||
|
||
**章节**: "VO类基础字段规范(强制性)"
|
||
|
||
**核心内容**:
|
||
|
||
```markdown
|
||
### 🔴 VO类基础字段统一规范(新增 2025-07-01)
|
||
|
||
**⚠️ 超级重要:所有VO类必须包含统一的基础字段!**
|
||
|
||
**背景问题**:
|
||
- MyBatis-Plus自动查询:不包含继承字段
|
||
- 自定义SQL查询:明确包含字段,VO类必须有对应属性
|
||
- 导致运行时字段缺失错误:`Could not set property 'createBy'`
|
||
|
||
**统一解决方案**:
|
||
所有VO类必须包含的基础字段:
|
||
- Long createDept
|
||
- Long createBy
|
||
- Date createTime (含JSON格式化)
|
||
- Long updateBy
|
||
- Date updateTime (含JSON格式化)
|
||
```
|
||
|
||
---
|
||
|
||
## 🎉 修复总结
|
||
|
||
### ✅ 问题识别
|
||
|
||
- **错误类型**: MyBatis字段映射错误
|
||
- **错误原因**: VO类缺少 `createDept` 字段
|
||
- **影响范围**: 所有列表查询API
|
||
|
||
### ✅ 修复方案
|
||
|
||
- **修改文件**: `RsOverseasItineraryChangeVo.java`
|
||
- **修改内容**: 添加 `createDept` 字段
|
||
- **修改行数**: 1 个字段,6 行代码
|
||
|
||
### ✅ 验证步骤
|
||
|
||
1. ✅ 代码修改完成
|
||
2. ⏳ 需要重新编译后端
|
||
3. ⏳ 需要重启后端服务
|
||
4. ⏳ 需要测试验证
|
||
|
||
---
|
||
|
||
## 🚀 下一步操作
|
||
|
||
### 1. 重新编译后端(必须)
|
||
|
||
```bash
|
||
cd /Users/weizzzz/Documents/Cursor/0828CURSOR/all-project/backend
|
||
mvn clean compile -DskipTests
|
||
```
|
||
|
||
### 2. 重启后端服务(必须)
|
||
|
||
```bash
|
||
# 停止当前服务(Ctrl+C)
|
||
# 然后重新启动
|
||
mvn spring-boot:run
|
||
```
|
||
|
||
### 3. 刷新前端页面(必须)
|
||
|
||
```bash
|
||
# 强制刷新浏览器
|
||
Ctrl+Shift+R (Windows)
|
||
Cmd+Shift+R (Mac)
|
||
```
|
||
|
||
### 4. 测试验证(必须)
|
||
|
||
访问: http://localhost/rs/overseas/itinerary-change
|
||
|
||
**预期结果**:
|
||
- ✅ 列表正常加载
|
||
- ✅ 没有500错误
|
||
- ✅ 能看到所有变更申请数据
|
||
- ✅ 可以正常提交新申请
|
||
|
||
---
|
||
|
||
## 📞 相关文档
|
||
|
||
| 文档名称 | 说明 |
|
||
|---------|------|
|
||
| `行程变更申请-修复总结.md` | 所有问题修复汇总 |
|
||
| `行程变更申请-recordId修复说明.md` | 必填字段修复 |
|
||
| `行程变更申请-列表显示问题修复.md` | 分页问题修复 |
|
||
| `行程变更申请-VO字段缺失修复.md` | 本文档(VO字段修复) |
|
||
| `.cursor/cursorrules-backend.md` | 后端开发规范 |
|
||
|
||
---
|
||
|
||
**修复日期**: 2025-10-13
|
||
**修复人员**: Cursor AI Assistant
|
||
**问题级别**: 🔴 严重(阻塞功能)
|
||
**修复状态**: ✅ 代码修复完成,等待编译和测试
|
||
|
||
---
|
||
|
||
**重要提醒**:
|
||
1. ⚠️ 必须重新编译后端代码
|
||
2. ⚠️ 必须重启后端服务
|
||
3. ⚠️ 修改后才会生效
|
||
|
||
**编译命令**:
|
||
```bash
|
||
cd backend
|
||
mvn clean compile -DskipTests
|
||
```
|
||
|