11 KiB
11 KiB
腾讯文档防重复写入 - 完整解决方案
🎯 问题背景
原问题:物流链接被重复写入到腾讯文档,导致同一订单的物流信息出现多次。
根本原因:
- ❌ 没有持久化的推送状态标记
- ❌ 用户可以多次点击推送按钮
- ❌ 锁释放后仍可再次推送
- ❌ 录单时自动推送,无人工确认
✅ 完整解决方案
核心机制(五重防护)
🛡️ 第一重:订单表状态标记(持久化)
- 在
jd_order表添加两个字段:tencent_doc_pushed(0-未推送,1-已推送)tencent_doc_push_time(推送时间)
- 推送成功后立即更新订单状态
- 再次推送前先检查状态,已推送则拒绝
🔄 第五重:智能状态同步(新增)
- 批量同步时检测文档已有值
- 如果文档中已有物流链接(可能手动填写)
- 但订单状态为"未推送"
- 自动同步订单状态为"已推送"
- 保持订单状态与文档状态一致
🔒 第二重:Redis分布式锁(防并发)
- 锁的粒度:
文档ID:工作表ID:订单单号 - 30秒超时自动释放
- 同一订单同一时刻只能有一个请求处理
✅ 第三重:写入前验证(防覆盖)
每次写入前都会:
- 再次读取目标行
- 验证单号是否匹配
- 检查物流列是否为空
- 任何一项不通过都拒绝写入
📊 第四重:操作日志记录(可追溯)
- 所有操作记录到
tencent_doc_operation_log表 - 记录成功/失败/跳过状态
- 记录操作人和时间
- 可以查询历史操作
录单行为变更
旧行为(已禁用):
// 录单时如果是H-TF,自动写入腾讯文档
if ("H-TF".equals(order.getDistributionMark())) {
asyncWriteToTencentDoc(order);
}
新行为:
- ✅ 录单时不再自动推送
- ✅ 必须在订单列表手动点击按钮推送
- ✅ 推送前人工确认,避免误操作
🔄 智能状态同步机制
为什么需要智能同步?
在实际使用中,可能出现以下情况:
- 手动填写:有人直接在腾讯文档中手动填写了物流链接
- 外部导入:从其他系统导入数据到腾讯文档
- 状态不一致:订单状态显示"未推送",但文档中已有值
智能同步的工作流程
批量同步读取腾讯文档
↓
发现某行的物流列已有值
↓
查询该订单的推送状态
↓
如果订单状态为"未推送"
↓
自动更新为"已推送"
↓
记录同步日志
↓
下次批量同步时就会跳过这个订单
同步效果
| 场景 | 订单状态 | 文档状态 | 系统行为 |
|---|---|---|---|
| 正常推送 | 未推送 | 无值 | ✅ 写入物流链接,更新状态 |
| 手动填写后首次同步 | 未推送 | 有值 | ✅ 自动同步状态,跳过写入 |
| 手动填写后再次同步 | 已推送 | 有值 | ✅ 跳过(订单状态已同步) |
| 重复推送尝试 | 已推送 | 有值 | ✅ 拒绝(订单已推送) |
日志示例
INFO - ✓ 同步订单状态 - 单号: JY2025110329041, 行号: 123, 原因: 文档中已有物流链接(可能手动填写)
INFO - 记录同步日志 - 操作类型: BATCH_SYNC, 状态: SKIPPED, 错误信息: 文档中已有物流链接,已同步订单状态
📋 使用流程
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(() => {
// 调用API,forceRePush: true
});
3. 防止快速重复点击(前端防抖)
methods: {
handlePush: _.debounce(function() {
// 调用API
}, 1000, { leading: true, trailing: false })
}
✅ 验证测试
测试场景 1:首次推送
- 选择一个未推送的订单(
tencent_doc_pushed = 0) - 点击"推送物流"
- 预期:推送成功,订单状态更新为"已推送"
测试场景 2:重复推送(默认拒绝)
- 选择一个已推送的订单(
tencent_doc_pushed = 1) - 点击"推送物流"
- 预期:拒绝推送,提示"已推送"
测试场景 3:强制推送
- 选择一个已推送的订单
- 勾选"强制推送"选项
- 点击"推送物流"
- 预期:推送成功,更新推送时间
测试场景 4:并发推送
- 同一订单,同时点击两次"推送物流"按钮
- 预期:只有一个请求成功,另一个提示"正在处理中"
测试场景 5:物流列非空
- 手动在腾讯文档中填写物流链接
- 点击"推送物流"
- 预期:拒绝推送,提示"物流链接已存在"
🎯 总结
彻底解决重复写入的核心
-
持久化状态(最关键)
- 订单表增加
tencent_doc_pushed字段 - 推送成功后立即更新
- 再次推送前先检查状态
- 订单表增加
-
智能状态同步(新增核心功能)
- 批量同步时检测文档已有值
- 自动同步订单状态为"已推送"
- 处理手动填写、外部导入等场景
- 保持订单状态与文档状态一致
-
分布式锁
- 防止并发冲突
- 同一订单同时只能一个请求处理
-
写入前验证
- 验证单号匹配
- 验证物流列为空
- 防止写错行或覆盖
-
操作日志
- 所有操作可追溯
- 便于问题排查和审计
-
录单不再自动触发
- 必须手动点击按钮
- 人工确认,避免误操作
防护等级:⭐⭐⭐⭐⭐(最高)
现在即使:
- ✅ 用户多次点击 → 拒绝重复推送
- ✅ 并发请求 → 分布式锁防护
- ✅ 误操作 → 已推送则拒绝
- ✅ 别人手动填写文档 → 智能同步状态
- ✅ 外部数据导入 → 自动检测并同步
彻底解决所有重复写入场景! 🎉