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

11 KiB
Raw Permalink Blame History

紧急修复:重复写入问题

严重问题

用户反馈:完全重复写入了

🔍 根本原因分析

问题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"}} 提取失败!

后果

  1. 读取腾讯文档时,物流列(超链接类型)被解析为空字符串 ""
  2. 系统检查:existingLogisticsLink == "" → 认为物流列为空
  3. 系统认为可以写入
  4. 再次写入同一订单重复写入!
  5. 文档中出现两行相同数据!

验证

第一次写入:
  - 物流列为空 ✅
  - 写入物流链接(超链接类型)✅
  - 文档中有1行 ✅

第二次批量同步:
  - 读取数据,物流列被解析为 "" ❌
  - 系统认为物流列为空 ❌
  - 又写入了一次 ❌
  - 文档中有2行

问题2订单状态更新使用旧对象

错误代码

// 批量同步中在数据收集阶段保存order对象
update.put("order", order); // ❌ 保存的是旧对象

// 写入成功后,使用旧对象更新状态
JDOrder orderToUpdate = (JDOrder) update.get("order"); // ❌ 使用旧对象
orderToUpdate.setTencentDocPushed(1);
jdOrderService.updateJDOrder(orderToUpdate); // ❌ 可能失败或覆盖其他字段

问题

  1. 从数据收集到写入成功,可能间隔数秒甚至数十秒
  2. 这期间订单可能被其他操作修改(如状态变更、金额更新等)
  3. 使用旧对象更新会:
    • 覆盖其他字段的最新值
    • 可能因为乐观锁或版本号失败
    • 导致 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个订单 ✅ 不重复!
...

🔧 修复的文件

核心修复(最重要)

  1. TencentDocDataParser.java
    • 行110-160extractCellText 方法
    • 增加超链接类型支持
    • 修复数据解析bug彻底解决重复写入

次要修复

  1. 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 字段
  • 支持 numberbool 等其他类型

辅助修复(建议)

重新查询订单再更新状态 检查更新结果 详细日志

重要性

这是一个数据完整性严重问题,必须立即修复!

如果不修复

  • 每次批量同步都会重复写入
  • 文档中数据越来越多
  • 用户无法使用批量同步功能
  • 手动填写的数据也会被重复写入

修复后

  • 正确识别物流列已有值
  • 跳过已有数据的行
  • 不再重复写入
  • 文档数据保持唯一

修复完成后,请按照验证步骤仔细测试!特别要测试超链接类型的单元格!