11 KiB
11 KiB
紧急修复:重复写入问题
❌ 严重问题
用户反馈:完全重复写入了
🔍 根本原因分析
问题1:数据解析器无法识别超链接类型(最严重!)
错误代码 (TencentDocDataParser.java 第120-122行):
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"}}❌ 提取失败!
后果:
- 读取腾讯文档时,物流列(超链接类型)被解析为空字符串
"" - 系统检查:
existingLogisticsLink == ""→ 认为物流列为空 - 系统认为可以写入
- 再次写入同一订单 → 重复写入!
- 文档中出现两行相同数据!
验证:
第一次写入:
- 物流列为空 ✅
- 写入物流链接(超链接类型)✅
- 文档中有1行 ✅
第二次批量同步:
- 读取数据,物流列被解析为 "" ❌
- 系统认为物流列为空 ❌
- 又写入了一次 ❌
- 文档中有2行!❌
问题2:订单状态更新使用旧对象
错误代码:
// 批量同步中,在数据收集阶段保存order对象
update.put("order", order); // ❌ 保存的是旧对象
// 写入成功后,使用旧对象更新状态
JDOrder orderToUpdate = (JDOrder) update.get("order"); // ❌ 使用旧对象
orderToUpdate.setTencentDocPushed(1);
jdOrderService.updateJDOrder(orderToUpdate); // ❌ 可能失败或覆盖其他字段
问题:
- 从数据收集到写入成功,可能间隔数秒甚至数十秒
- 这期间订单可能被其他操作修改(如状态变更、金额更新等)
- 使用旧对象更新会:
- ❌ 覆盖其他字段的最新值
- ❌ 可能因为乐观锁或版本号失败
- ❌ 导致
tencentDocPushed字段更新失败
后果:
- 订单状态未更新为"已推送"
- 下次批量同步时,系统认为订单未推送
- 再次写入同一订单 → 重复写入!
问题2:没有检查更新结果
错误代码:
jdOrderService.updateJDOrder(orderToUpdate); // ❌ 没有检查返回值
log.info("✓ 订单推送状态已更新"); // ❌ 假设成功
问题:
updateJDOrder返回受影响的行数- 如果返回0,说明更新失败
- 但代码没有检查,误以为更新成功
- 订单状态实际未更新 → 下次重复写入
问题3:智能同步也存在同样问题
智能同步虽然查询是实时的,但也没有检查更新结果。
✅ 修复方案
修复1:增强数据解析器支持超链接类型(最关键!)
新代码 (TencentDocDataParser.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:重新查询订单(关键)
// ✅ 写入成功后,重新查询订单,确保数据最新
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对象保存
// ❌ 旧代码
update.put("order", order); // 不再需要
// ✅ 新代码
// 不保存order对象,写入成功后重新查询
修复3:增强日志
// ✅ 详细日志,便于排查
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个订单 ✅ 不重复!
...
🔧 修复的文件
核心修复(最重要)
TencentDocDataParser.java⭐⭐⭐⭐⭐- 行110-160:
extractCellText方法 - 增加超链接类型支持
- 修复数据解析bug,彻底解决重复写入
- 行110-160:
次要修复
TencentDocController.java- 行1258-1276:批量同步中的订单状态更新逻辑
- 行1098-1120:智能同步中的状态更新逻辑
- 行1150-1157:移除不必要的order对象保存
✅ 验证步骤
Step 1: 清空测试数据
-- 重置所有订单的推送状态
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: 第一次批量同步
# 预期:写入10个订单,所有订单状态更新为"已推送"
# 检查日志:
grep "✓ 订单推送状态已更新" application.log | wc -l # 应该是10
grep "⚠️ 订单推送状态更新返回0" application.log | wc -l # 应该是0
Step 3: 检查订单状态
-- 应该有10个订单已推送
SELECT COUNT(*) FROM jd_order
WHERE distribution_mark = 'H-TF'
AND tencent_doc_pushed = 1; -- 应该返回10
Step 4: 第二次批量同步
# 预期:跳过所有已推送的订单,不重复写入
# 检查日志:
grep "跳过已推送订单" application.log | wc -l # 应该是10
Step 5: 检查腾讯文档
- 每个订单应该只出现一次
- 不应该有重复的物流链接
Step 6: 检查操作日志
-- 每个订单应该只有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(必须)
mysql -u root -p your_database < doc/订单表添加腾讯文档推送标记.sql
mysql -u root -p your_database < doc/腾讯文档操作日志表.sql
2. 重新编译
cd d:\code\RuoYi-Vue-master\ruoyi-java
mvn clean package -DskipTests
3. 立即重启服务
# 停止旧服务
# 部署新war/jar
# 启动新服务
4. 观察日志
tail -f application.log | grep -E "(✓|⚠️|❌)"
📝 监控要点
正常日志(修复后)
✓ 写入成功 - 行: 123, 单号: JY2025110329041, 物流链接: xxx
✓ 订单推送状态已更新 - 单号: JY2025110329041, updateResult: 1
异常日志(需要关注)
⚠️ 订单推送状态更新返回0 - 单号: JY2025110329041, 可能未更新
→ 检查数据库连接、订单是否存在
❌ 更新订单推送状态失败 - 单号: JY2025110329041
→ 检查异常堆栈,可能是数据库锁、约束等问题
💡 预防措施
1. 数据库层面
-- 添加唯一索引,防止重复单号(如果适用)
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. 监控告警
-- 每小时检查是否有订单被重复写入
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等其他类型
辅助修复(建议)
✅ 重新查询订单再更新状态 ✅ 检查更新结果 ✅ 详细日志
重要性
这是一个数据完整性严重问题,必须立即修复!
如果不修复:
- ❌ 每次批量同步都会重复写入
- ❌ 文档中数据越来越多
- ❌ 用户无法使用批量同步功能
- ❌ 手动填写的数据也会被重复写入
修复后:
- ✅ 正确识别物流列已有值
- ✅ 跳过已有数据的行
- ✅ 不再重复写入
- ✅ 文档数据保持唯一
修复完成后,请按照验证步骤仔细测试!特别要测试超链接类型的单元格!