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

396 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 腾讯文档防重复写入 - 完整解决方案
## 🎯 问题背景
**原问题**:物流链接被重复写入到腾讯文档,导致同一订单的物流信息出现多次。
**根本原因**
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(() => {
// 调用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. **录单不再自动触发**
- 必须手动点击按钮
- 人工确认,避免误操作
### 防护等级:⭐⭐⭐⭐⭐(最高)
现在即使:
- ✅ 用户多次点击 → 拒绝重复推送
- ✅ 并发请求 → 分布式锁防护
- ✅ 误操作 → 已推送则拒绝
-**别人手动填写文档****智能同步状态**
- ✅ 外部数据导入 → 自动检测并同步
**彻底解决所有重复写入场景!** 🎉