Files
ruoyi-java/doc/紧急修复-重复写入问题.md
2025-11-06 20:32:16 +08:00

432 lines
11 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# 紧急修复:重复写入问题
## ❌ 严重问题
用户反馈:**完全重复写入了**
## 🔍 根本原因分析
### 问题1数据解析器无法识别超链接类型最严重
**错误代码** (`TencentDocDataParser.java` 第120-122行)
```java
private static String extractCellText(JSONObject cell) {
// ...
// ❌ 只提取 text 字段link类型提取不到
String text = cellValue.getString("text");
return text != null ? text : "";
}
```
**单元格类型**
- 普通文本:`cellValue = {"text": "xxx"}` ✅ 能提取
- **超链接**`cellValue = {"link": {"url": "xxx", "text": "xxx"}}`**提取失败!**
**后果**
1. 读取腾讯文档时,物流列(超链接类型)被解析为空字符串 `""`
2. 系统检查:`existingLogisticsLink == ""` → 认为物流列为空
3. 系统认为可以写入
4. **再次写入同一订单****重复写入!**
5. 文档中出现两行相同数据!
**验证**
```
第一次写入:
- 物流列为空 ✅
- 写入物流链接(超链接类型)✅
- 文档中有1行 ✅
第二次批量同步:
- 读取数据,物流列被解析为 "" ❌
- 系统认为物流列为空 ❌
- 又写入了一次 ❌
- 文档中有2行
```
### 问题2订单状态更新使用旧对象
**错误代码**
```java
// 批量同步中在数据收集阶段保存order对象
update.put("order", order); // ❌ 保存的是旧对象
// 写入成功后,使用旧对象更新状态
JDOrder orderToUpdate = (JDOrder) update.get("order"); // ❌ 使用旧对象
orderToUpdate.setTencentDocPushed(1);
jdOrderService.updateJDOrder(orderToUpdate); // ❌ 可能失败或覆盖其他字段
```
**问题**
1. 从数据收集到写入成功,可能间隔数秒甚至数十秒
2. 这期间订单可能被其他操作修改(如状态变更、金额更新等)
3. 使用旧对象更新会:
- ❌ 覆盖其他字段的最新值
- ❌ 可能因为乐观锁或版本号失败
- ❌ 导致 `tencentDocPushed` 字段更新失败
**后果**
- 订单状态未更新为"已推送"
- 下次批量同步时,系统认为订单未推送
- **再次写入同一订单** → **重复写入!**
### 问题2没有检查更新结果
**错误代码**
```java
jdOrderService.updateJDOrder(orderToUpdate); // ❌ 没有检查返回值
log.info("✓ 订单推送状态已更新"); // ❌ 假设成功
```
**问题**
- `updateJDOrder` 返回受影响的行数
- 如果返回0说明更新失败
- 但代码没有检查,误以为更新成功
- 订单状态实际未更新 → **下次重复写入**
### 问题3智能同步也存在同样问题
智能同步虽然查询是实时的,但也没有检查更新结果。
## ✅ 修复方案
### 修复1增强数据解析器支持超链接类型最关键
**新代码** (`TencentDocDataParser.java`)
```java
private static String extractCellText(JSONObject cell) {
// ...
JSONObject cellValue = cell.getJSONObject("cellValue");
// ✅ 优先级1检查link字段超链接类型
JSONObject link = cellValue.getJSONObject("link");
if (link != null) {
String linkText = link.getString("text");
if (linkText != null && !linkText.isEmpty()) {
return linkText; // ✅ 能正确提取超链接文本
}
String linkUrl = link.getString("url");
if (linkUrl != null && !linkUrl.isEmpty()) {
return linkUrl; // ✅ 或返回url
}
}
// ✅ 优先级2检查text字段普通文本
String text = cellValue.getString("text");
if (text != null) {
return text;
}
// ✅ 优先级3支持number、bool等其他类型
// ...
return "";
}
```
**修复效果**
```
第二次批量同步(修复后):
- 读取数据,物流列被解析为 "https://3.cn/xxx" ✅
- 系统检查existingLogisticsLink != "" ✅
- 跳过写入 ✅
- 文档仍然只有1行 ✅
```
### 修复2重新查询订单关键
```java
// ✅ 写入成功后,重新查询订单,确保数据最新
JDOrder orderToUpdate = jdOrderService.selectJDOrderByThirdPartyOrderNo(expectedOrderNo);
if (orderToUpdate != null) {
orderToUpdate.setTencentDocPushed(1);
orderToUpdate.setTencentDocPushTime(new Date());
int updateResult = jdOrderService.updateJDOrder(orderToUpdate);
// ✅ 检查更新结果
if (updateResult > 0) {
log.info("✓ 订单推送状态已更新");
} else {
log.warn("⚠️ 订单推送状态更新返回0可能未更新");
}
}
```
### 修复2移除不必要的order对象保存
```java
// ❌ 旧代码
update.put("order", order); // 不再需要
// ✅ 新代码
// 不保存order对象写入成功后重新查询
```
### 修复3增强日志
```java
// ✅ 详细日志,便于排查
log.info("✓ 订单推送状态已更新 - 单号: {}, updateResult: {}", orderNo, updateResult);
log.warn("⚠️ 订单推送状态更新返回0 - 单号: {}, 可能未更新", orderNo);
log.error("❌ 更新订单推送状态失败 - 单号: {}", orderNo, e);
```
## 📊 修复前后对比
### 场景批量同步100个订单
#### 修复前有bug
```
1. 读取100行数据
2. 收集100个订单对象保存到updates
3. 开始写入10秒后
4. 写入第1个订单成功
5. 使用10秒前的旧对象更新状态
6. 更新失败(对象已过期)或覆盖其他字段
7. 订单状态仍为"未推送"
8. 写入第2个订单...
...
下次批量同步:
1. 读取数据发现第1个订单"未推送"
2. 再次写入第1个订单 ❌ 重复写入!
3. 再次写入第2个订单 ❌ 重复写入!
...
```
#### 修复后(正确)
```
1. 读取100行数据
2. 收集100个订单号只保存必要信息
3. 开始写入
4. 写入第1个订单成功
5. 重新查询第1个订单最新数据
6. 更新状态成功 ✅
7. 检查updateResult > 0 ✅
8. 订单状态更新为"已推送"
9. 写入第2个订单...
...
下次批量同步:
1. 读取数据发现第1个订单"已推送"
2. 跳过第1个订单 ✅ 不重复!
3. 跳过第2个订单 ✅ 不重复!
...
```
## 🔧 修复的文件
### 核心修复(最重要)
1. **`TencentDocDataParser.java`** ⭐⭐⭐⭐⭐
- 行110-160`extractCellText` 方法
- **增加超链接类型支持**
- 修复数据解析bug彻底解决重复写入
### 次要修复
2. **`TencentDocController.java`**
- 行1258-1276批量同步中的订单状态更新逻辑
- 行1098-1120智能同步中的状态更新逻辑
- 行1150-1157移除不必要的order对象保存
## ✅ 验证步骤
### Step 1: 清空测试数据
```sql
-- 重置所有订单的推送状态
UPDATE jd_order
SET tencent_doc_pushed = 0,
tencent_doc_push_time = NULL
WHERE distribution_mark = 'H-TF';
-- 清空操作日志
TRUNCATE TABLE tencent_doc_operation_log;
```
### Step 2: 第一次批量同步
```bash
# 预期写入10个订单所有订单状态更新为"已推送"
# 检查日志:
grep "✓ 订单推送状态已更新" application.log | wc -l # 应该是10
grep "⚠️ 订单推送状态更新返回0" application.log | wc -l # 应该是0
```
### Step 3: 检查订单状态
```sql
-- 应该有10个订单已推送
SELECT COUNT(*) FROM jd_order
WHERE distribution_mark = 'H-TF'
AND tencent_doc_pushed = 1; -- 应该返回10
```
### Step 4: 第二次批量同步
```bash
# 预期:跳过所有已推送的订单,不重复写入
# 检查日志:
grep "跳过已推送订单" application.log | wc -l # 应该是10
```
### Step 5: 检查腾讯文档
- 每个订单应该只出现一次
- **不应该有重复的物流链接**
### Step 6: 检查操作日志
```sql
-- 每个订单应该只有1条SUCCESS记录
SELECT order_no, COUNT(*) as count
FROM tencent_doc_operation_log
WHERE operation_status = 'SUCCESS'
GROUP BY order_no
HAVING COUNT(*) > 1; -- 应该返回0行
```
## 🚨 紧急部署
### 1. 先执行SQL必须
```bash
mysql -u root -p your_database < doc/订单表添加腾讯文档推送标记.sql
mysql -u root -p your_database < doc/腾讯文档操作日志表.sql
```
### 2. 重新编译
```bash
cd d:\code\RuoYi-Vue-master\ruoyi-java
mvn clean package -DskipTests
```
### 3. 立即重启服务
```bash
# 停止旧服务
# 部署新war/jar
# 启动新服务
```
### 4. 观察日志
```bash
tail -f application.log | grep -E "(✓|⚠️|❌)"
```
## 📝 监控要点
### 正常日志(修复后)
```
✓ 写入成功 - 行: 123, 单号: JY2025110329041, 物流链接: xxx
✓ 订单推送状态已更新 - 单号: JY2025110329041, updateResult: 1
```
### 异常日志(需要关注)
```
⚠️ 订单推送状态更新返回0 - 单号: JY2025110329041, 可能未更新
→ 检查数据库连接、订单是否存在
❌ 更新订单推送状态失败 - 单号: JY2025110329041
→ 检查异常堆栈,可能是数据库锁、约束等问题
```
## 💡 预防措施
### 1. 数据库层面
```sql
-- 添加唯一索引,防止重复单号(如果适用)
CREATE UNIQUE INDEX uk_third_party_order_no
ON jd_order(third_party_order_no);
-- 添加检查约束
ALTER TABLE jd_order
ADD CONSTRAINT ck_tencent_doc_pushed
CHECK (tencent_doc_pushed IN (0, 1));
```
### 2. 应用层面
- ✅ 始终重新查询订单再更新
- ✅ 检查更新结果
- ✅ 记录详细日志
- ✅ 定期检查操作日志表
### 3. 监控告警
```sql
-- 每小时检查是否有订单被重复写入
SELECT order_no, COUNT(*) as write_count
FROM tencent_doc_operation_log
WHERE operation_status = 'SUCCESS'
AND create_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)
GROUP BY order_no
HAVING COUNT(*) > 1;
-- 如果有结果,发送告警
```
## 🎯 总结
### 根本原因(按重要性排序)
#### 1⃣ 数据解析器无法识别超链接(最严重!)⭐⭐⭐⭐⭐
**问题**`TencentDocDataParser.extractCellText()` 只提取 `cellValue.text`,对于超链接类型 `cellValue.link` 提取失败
**后果**
- 读取数据时,物流列(超链接)被解析为空字符串
- 系统误认为物流列为空
- **重复写入同一订单!**
- **文档中出现多行相同数据!**
#### 2⃣ 使用旧订单对象更新状态(严重)
**问题**:批量同步时保存旧订单对象,写入成功后使用旧对象更新状态
**后果**
- 状态更新可能失败
- 订单状态未更新为"已推送"
- 下次批量同步时重复处理
### 解决方案
#### 核心修复(必须)
**增强数据解析器支持超链接类型**
- 优先检查 `link` 字段
- 再检查 `text` 字段
- 支持 `number``bool` 等其他类型
#### 辅助修复(建议)
✅ 重新查询订单再更新状态
✅ 检查更新结果
✅ 详细日志
### 重要性
这是一个**数据完整性严重问题**,必须立即修复!
**如果不修复**
- ❌ 每次批量同步都会重复写入
- ❌ 文档中数据越来越多
- ❌ 用户无法使用批量同步功能
- ❌ 手动填写的数据也会被重复写入
**修复后**
- ✅ 正确识别物流列已有值
- ✅ 跳过已有数据的行
- ✅ 不再重复写入
- ✅ 文档数据保持唯一
---
**修复完成后,请按照验证步骤仔细测试!特别要测试超链接类型的单元格!**