Files
ruoyi-java/doc/腾讯文档防重复写入-完整解决方案.md
2025-11-06 20:18:15 +08:00

11 KiB
Raw Permalink Blame History

腾讯文档防重复写入 - 完整解决方案

🎯 问题背景

原问题:物流链接被重复写入到腾讯文档,导致同一订单的物流信息出现多次。

根本原因

  1. 没有持久化的推送状态标记
  2. 用户可以多次点击推送按钮
  3. 锁释放后仍可再次推送
  4. 录单时自动推送,无人工确认

完整解决方案

核心机制(五重防护)

🛡️ 第一重:订单表状态标记(持久化)

  • jd_order 表添加两个字段:
    • tencent_doc_pushed0-未推送1-已推送)
    • tencent_doc_push_time(推送时间)
  • 推送成功后立即更新订单状态
  • 再次推送前先检查状态,已推送则拒绝

🔄 第五重:智能状态同步(新增)

  • 批量同步时检测文档已有值
  • 如果文档中已有物流链接(可能手动填写)
  • 但订单状态为"未推送"
  • 自动同步订单状态为"已推送"
  • 保持订单状态与文档状态一致

🔒 第二重Redis分布式锁防并发

  • 锁的粒度:文档ID:工作表ID:订单单号
  • 30秒超时自动释放
  • 同一订单同一时刻只能有一个请求处理

第三重:写入前验证(防覆盖)

每次写入前都会:

  1. 再次读取目标行
  2. 验证单号是否匹配
  3. 检查物流列是否为空
  4. 任何一项不通过都拒绝写入

📊 第四重:操作日志记录(可追溯)

  • 所有操作记录到 tencent_doc_operation_log
  • 记录成功/失败/跳过状态
  • 记录操作人和时间
  • 可以查询历史操作

录单行为变更

旧行为(已禁用):

// 录单时如果是H-TF自动写入腾讯文档
if ("H-TF".equals(order.getDistributionMark())) {
    asyncWriteToTencentDoc(order);
}

新行为

  • 录单时不再自动推送
  • 必须在订单列表手动点击按钮推送
  • 推送前人工确认,避免误操作

🔄 智能状态同步机制

为什么需要智能同步?

在实际使用中,可能出现以下情况:

  1. 手动填写:有人直接在腾讯文档中手动填写了物流链接
  2. 外部导入:从其他系统导入数据到腾讯文档
  3. 状态不一致:订单状态显示"未推送",但文档中已有值

智能同步的工作流程

批量同步读取腾讯文档
    ↓
发现某行的物流列已有值
    ↓
查询该订单的推送状态
    ↓
如果订单状态为"未推送"
    ↓
自动更新为"已推送"
    ↓
记录同步日志
    ↓
下次批量同步时就会跳过这个订单

同步效果

场景 订单状态 文档状态 系统行为
正常推送 未推送 无值 写入物流链接,更新状态
手动填写后首次同步 未推送 有值 自动同步状态,跳过写入
手动填写后再次同步 已推送 有值 跳过(订单状态已同步)
重复推送尝试 已推送 有值 拒绝(订单已推送)

日志示例

INFO  - ✓ 同步订单状态 - 单号: JY2025110329041, 行号: 123, 原因: 文档中已有物流链接(可能手动填写)
INFO  - 记录同步日志 - 操作类型: BATCH_SYNC, 状态: SKIPPED, 错误信息: 文档中已有物流链接,已同步订单状态

📋 使用流程

1. 首次推送

  1. 在订单列表找到目标订单
  2. 点击"推送物流"按钮
  3. 系统检查:
    • 订单未推送过 → 执行推送
    • 推送成功 → 更新订单状态为"已推送"
    • 返回成功提示

2. 再次推送(默认拒绝)

  1. 再次点击"推送物流"按钮
  2. 系统检查:
    • 订单已推送 → 拒绝推送
    • 📝 提示:"该订单已推送到腾讯文档推送时间2025-11-06 12:30:00请勿重复操作"

3. 强制重新推送(特殊情况)

如果需要重新推送(例如腾讯文档被误删),可以:

  • 前端传递参数:forceRePush: true
  • 系统会忽略"已推送"状态,重新执行推送

🔧 部署步骤

Step 1: 执行SQL脚本必须

# 1. 添加订单表字段
mysql -u root -p your_database < doc/订单表添加腾讯文档推送标记.sql

# 2. 创建操作日志表
mysql -u root -p your_database < doc/腾讯文档操作日志表.sql

Step 2: 重新编译部署

cd d:\code\RuoYi-Vue-master\ruoyi-java
mvn clean package -DskipTests

Step 3: 重启服务

# 停止旧服务,启动新服务

🛡️ 安全保障

防止重复推送

机制 说明 效果
订单状态标记 持久化到数据库 永久防止重复(除非强制)
智能状态同步 自动同步文档状态到订单 处理手动填写场景
分布式锁 Redis锁30秒超时 防止并发冲突
写入前验证 验证单号和物流列 防止写错行或覆盖
操作日志 记录所有操作 可追溯,可审计

防止覆盖已有数据

  • 验证物流列是否为空
  • 如果已有值,拒绝写入
  • 返回错误提示:"该订单物流链接已存在xxx"

防止并发冲突

  • Redis分布式锁
  • 同一订单同时只能有一个请求处理
  • 锁冲突时返回:"该订单正在处理中,请稍后再试"

📊 数据库表结构

订单表新增字段

ALTER TABLE jd_order 
ADD COLUMN `tencent_doc_pushed` tinyint(1) DEFAULT 0 COMMENT '是否已推送到腾讯文档0-未推送1-已推送)',
ADD COLUMN `tencent_doc_push_time` datetime DEFAULT NULL COMMENT '推送到腾讯文档的时间';

CREATE INDEX idx_tencent_doc_pushed ON jd_order(tencent_doc_pushed, distribution_mark);

操作日志表

CREATE TABLE `tencent_doc_operation_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `file_id` varchar(100) DEFAULT NULL,
  `sheet_id` varchar(100) DEFAULT NULL,
  `operation_type` varchar(50) DEFAULT NULL COMMENT 'WRITE_SINGLE / BATCH_SYNC',
  `order_no` varchar(100) DEFAULT NULL,
  `target_row` int(11) DEFAULT NULL,
  `logistics_link` varchar(500) DEFAULT NULL,
  `operation_status` varchar(20) DEFAULT NULL COMMENT 'SUCCESS / FAILED / SKIPPED',
  `error_message` text,
  `operator` varchar(100) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `remark` varchar(500) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_order_no` (`order_no`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_file_sheet` (`file_id`, `sheet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

🔍 常见问题排查

Q1: 订单明明没推送过,为什么提示"已推送"

排查方法

-- 查询订单的推送状态
SELECT third_party_order_no, tencent_doc_pushed, tencent_doc_push_time
FROM jd_order
WHERE third_party_order_no = 'JY2025110329041';

解决方法

-- 如果确认是误标记,可以手动重置
UPDATE jd_order 
SET tencent_doc_pushed = 0, tencent_doc_push_time = NULL
WHERE third_party_order_no = 'JY2025110329041';

Q2: 如何查看某个订单的推送历史?

-- 查询操作日志
SELECT * FROM tencent_doc_operation_log 
WHERE order_no = 'JY2025110329041' 
ORDER BY create_time DESC;

Q3: 如何批量重置推送状态?

-- 谨慎操作!只在确认需要重新推送时使用
UPDATE jd_order 
SET tencent_doc_pushed = 0, tencent_doc_push_time = NULL
WHERE distribution_mark = 'H-TF' 
AND tencent_doc_pushed = 1;

Q4: 如何查看最近失败的推送?

SELECT order_no, error_message, create_time, operator
FROM tencent_doc_operation_log 
WHERE operation_status = 'FAILED' 
AND create_time > DATE_SUB(NOW(), INTERVAL 1 DAY)
ORDER BY create_time DESC;

📞 前端对接说明

API参数

// 基本推送(默认,如果已推送则拒绝)
{
  "thirdPartyOrderNo": "JY2025110329041",
  "logisticsLink": "https://3.cn/2ume-Ak1"
}

// 强制推送(忽略已推送状态)
{
  "thirdPartyOrderNo": "JY2025110329041",
  "logisticsLink": "https://3.cn/2ume-Ak1",
  "forceRePush": true  // 特殊情况使用
}

返回结果

// 成功
{
  "code": 200,
  "msg": "物流链接填充成功",
  "data": {
    "thirdPartyOrderNo": "JY2025110329041",
    "logisticsLink": "https://3.cn/2ume-Ak1",
    "row": 123,
    "column": 12,
    "pushed": true,
    "pushTime": "2025-11-06 12:30:00"
  }
}

// 失败(已推送)
{
  "code": 500,
  "msg": "该订单已推送到腾讯文档推送时间2025-11-06 12:30:00请勿重复操作如需重新推送请使用强制推送功能。"
}

前端按钮建议

// 推送按钮应该:
1. 根据订单的 tencentDocPushed 状态显示不同文本
   - 未推送显示"推送物流"
   - 已推送显示"已推送"置灰或隐藏

2. 提供"强制推送"选项需二次确认
   this.$confirm('该订单已推送,确定要重新推送吗?', '警告', {
     confirmButtonText: '确定',
     cancelButtonText: '取消',
     type: 'warning'
   }).then(() => {
     // 调用APIforceRePush: true
   });

3. 防止快速重复点击前端防抖
   methods: {
     handlePush: _.debounce(function() {
       // 调用API
     }, 1000, { leading: true, trailing: false })
   }

验证测试

测试场景 1首次推送

  1. 选择一个未推送的订单(tencent_doc_pushed = 0
  2. 点击"推送物流"
  3. 预期:推送成功,订单状态更新为"已推送"

测试场景 2重复推送默认拒绝

  1. 选择一个已推送的订单(tencent_doc_pushed = 1
  2. 点击"推送物流"
  3. 预期:拒绝推送,提示"已推送"

测试场景 3强制推送

  1. 选择一个已推送的订单
  2. 勾选"强制推送"选项
  3. 点击"推送物流"
  4. 预期:推送成功,更新推送时间

测试场景 4并发推送

  1. 同一订单,同时点击两次"推送物流"按钮
  2. 预期:只有一个请求成功,另一个提示"正在处理中"

测试场景 5物流列非空

  1. 手动在腾讯文档中填写物流链接
  2. 点击"推送物流"
  3. 预期:拒绝推送,提示"物流链接已存在"

🎯 总结

彻底解决重复写入的核心

  1. 持久化状态(最关键)

    • 订单表增加 tencent_doc_pushed 字段
    • 推送成功后立即更新
    • 再次推送前先检查状态
  2. 智能状态同步(新增核心功能)

    • 批量同步时检测文档已有值
    • 自动同步订单状态为"已推送"
    • 处理手动填写、外部导入等场景
    • 保持订单状态与文档状态一致
  3. 分布式锁

    • 防止并发冲突
    • 同一订单同时只能一个请求处理
  4. 写入前验证

    • 验证单号匹配
    • 验证物流列为空
    • 防止写错行或覆盖
  5. 操作日志

    • 所有操作可追溯
    • 便于问题排查和审计
  6. 录单不再自动触发

    • 必须手动点击按钮
    • 人工确认,避免误操作

防护等级:(最高)

现在即使:

  • 用户多次点击 → 拒绝重复推送
  • 并发请求 → 分布式锁防护
  • 误操作 → 已推送则拒绝
  • 别人手动填写文档智能同步状态
  • 外部数据导入 → 自动检测并同步

彻底解决所有重复写入场景! 🎉