# 腾讯文档防重复写入 - 完整解决方案 ## 🎯 问题背景 **原问题**:物流链接被重复写入到腾讯文档,导致同一订单的物流信息出现多次。 **根本原因**: 1. ❌ 没有持久化的推送状态标记 2. ❌ 用户可以多次点击推送按钮 3. ❌ 锁释放后仍可再次推送 4. ❌ 录单时自动推送,无人工确认 ## ✅ 完整解决方案 ### 核心机制(五重防护) #### 🛡️ 第一重:订单表状态标记(持久化) - 在 `jd_order` 表添加两个字段: - `tencent_doc_pushed`(0-未推送,1-已推送) - `tencent_doc_push_time`(推送时间) - 推送成功后立即更新订单状态 - 再次推送前先检查状态,已推送则拒绝 #### 🔄 第五重:智能状态同步(新增) - **批量同步时检测文档已有值** - 如果文档中已有物流链接(可能手动填写) - 但订单状态为"未推送" - **自动同步订单状态为"已推送"** - 保持订单状态与文档状态一致 #### 🔒 第二重:Redis分布式锁(防并发) - 锁的粒度:`文档ID:工作表ID:订单单号` - 30秒超时自动释放 - 同一订单同一时刻只能有一个请求处理 #### ✅ 第三重:写入前验证(防覆盖) 每次写入前都会: 1. 再次读取目标行 2. 验证单号是否匹配 3. 检查物流列是否为空 4. 任何一项不通过都拒绝写入 #### 📊 第四重:操作日志记录(可追溯) - 所有操作记录到 `tencent_doc_operation_log` 表 - 记录成功/失败/跳过状态 - 记录操作人和时间 - 可以查询历史操作 ### 录单行为变更 **旧行为**(已禁用): ```java // 录单时如果是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脚本(必须) ```bash # 1. 添加订单表字段 mysql -u root -p your_database < doc/订单表添加腾讯文档推送标记.sql # 2. 创建操作日志表 mysql -u root -p your_database < doc/腾讯文档操作日志表.sql ``` ### Step 2: 重新编译部署 ```bash cd d:\code\RuoYi-Vue-master\ruoyi-java mvn clean package -DskipTests ``` ### Step 3: 重启服务 ```bash # 停止旧服务,启动新服务 ``` ## 🛡️ 安全保障 ### 防止重复推送 | 机制 | 说明 | 效果 | |------|------|------| | 订单状态标记 | 持久化到数据库 | ✅ 永久防止重复(除非强制) | | 智能状态同步 | 自动同步文档状态到订单 | ✅ 处理手动填写场景 | | 分布式锁 | Redis锁,30秒超时 | ✅ 防止并发冲突 | | 写入前验证 | 验证单号和物流列 | ✅ 防止写错行或覆盖 | | 操作日志 | 记录所有操作 | ✅ 可追溯,可审计 | ### 防止覆盖已有数据 - ✅ 验证物流列是否为空 - ✅ 如果已有值,拒绝写入 - ✅ 返回错误提示:"该订单物流链接已存在:xxx" ### 防止并发冲突 - ✅ Redis分布式锁 - ✅ 同一订单同时只能有一个请求处理 - ✅ 锁冲突时返回:"该订单正在处理中,请稍后再试" ## 📊 数据库表结构 ### 订单表新增字段 ```sql 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); ``` ### 操作日志表 ```sql 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: 订单明明没推送过,为什么提示"已推送"? **排查方法**: ```sql -- 查询订单的推送状态 SELECT third_party_order_no, tencent_doc_pushed, tencent_doc_push_time FROM jd_order WHERE third_party_order_no = 'JY2025110329041'; ``` **解决方法**: ```sql -- 如果确认是误标记,可以手动重置 UPDATE jd_order SET tencent_doc_pushed = 0, tencent_doc_push_time = NULL WHERE third_party_order_no = 'JY2025110329041'; ``` ### Q2: 如何查看某个订单的推送历史? ```sql -- 查询操作日志 SELECT * FROM tencent_doc_operation_log WHERE order_no = 'JY2025110329041' ORDER BY create_time DESC; ``` ### Q3: 如何批量重置推送状态? ```sql -- 谨慎操作!只在确认需要重新推送时使用 UPDATE jd_order SET tencent_doc_pushed = 0, tencent_doc_push_time = NULL WHERE distribution_mark = 'H-TF' AND tencent_doc_pushed = 1; ``` ### Q4: 如何查看最近失败的推送? ```sql 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参数 ```javascript // 基本推送(默认,如果已推送则拒绝) { "thirdPartyOrderNo": "JY2025110329041", "logisticsLink": "https://3.cn/2ume-Ak1" } // 强制推送(忽略已推送状态) { "thirdPartyOrderNo": "JY2025110329041", "logisticsLink": "https://3.cn/2ume-Ak1", "forceRePush": true // 特殊情况使用 } ``` ### 返回结果 ```javascript // 成功 { "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),请勿重复操作!如需重新推送,请使用强制推送功能。" } ``` ### 前端按钮建议 ```javascript // 推送按钮应该: 1. 根据订单的 tencentDocPushed 状态显示不同文本 - 未推送:显示"推送物流" - 已推送:显示"已推送"(置灰或隐藏) 2. 提供"强制推送"选项(需二次确认) this.$confirm('该订单已推送,确定要重新推送吗?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 调用API,forceRePush: 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. **录单不再自动触发** - 必须手动点击按钮 - 人工确认,避免误操作 ### 防护等级:⭐⭐⭐⭐⭐(最高) 现在即使: - ✅ 用户多次点击 → 拒绝重复推送 - ✅ 并发请求 → 分布式锁防护 - ✅ 误操作 → 已推送则拒绝 - ✅ **别人手动填写文档** → **智能同步状态** - ✅ 外部数据导入 → 自动检测并同步 **彻底解决所有重复写入场景!** 🎉