# 紧急修复:重复写入问题 ## ❌ 严重问题 用户反馈:**完全重复写入了** ## 🔍 根本原因分析 ### 问题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` 等其他类型 #### 辅助修复(建议) ✅ 重新查询订单再更新状态 ✅ 检查更新结果 ✅ 详细日志 ### 重要性 这是一个**数据完整性严重问题**,必须立即修复! **如果不修复**: - ❌ 每次批量同步都会重复写入 - ❌ 文档中数据越来越多 - ❌ 用户无法使用批量同步功能 - ❌ 手动填写的数据也会被重复写入 **修复后**: - ✅ 正确识别物流列已有值 - ✅ 跳过已有数据的行 - ✅ 不再重复写入 - ✅ 文档数据保持唯一 --- **修复完成后,请按照验证步骤仔细测试!特别要测试超链接类型的单元格!**