This commit is contained in:
2025-11-06 20:18:15 +08:00
parent b532aa1b84
commit e865220a50
6 changed files with 798 additions and 7 deletions

View File

@@ -0,0 +1,274 @@
# 智能状态同步机制 - 详细说明
## 📖 背景
在实际使用中,腾讯文档的物流链接可能通过多种方式填写:
1. **系统推送**:通过"推送物流"按钮自动填写
2. **手动填写**:用户直接在文档中手动填写
3. **外部导入**从Excel等外部文件导入
4. **协同编辑**:团队成员直接编辑文档
如果没有智能同步机制,会导致:
- ❌ 订单状态显示"未推送",但文档中已有值
- ❌ 批量同步时重复查询这些订单
- ❌ 增加数据库查询负担
- ❌ 状态不一致,影响业务判断
## ✨ 智能同步机制
### 核心思路
**以腾讯文档的实际状态为准,自动同步到订单系统**
```
文档是最终展示层(实际填写状态)
订单系统是管理层(推送状态记录)
文档有值 + 订单未标记 = 状态不一致
智能同步:自动更新订单状态
```
## 🔄 工作流程
### 场景1系统推送正常流程
```
用户点击"推送物流"
1. 检查订单状态:未推送 ✅
2. 检查文档物流列:无值 ✅
写入物流链接到文档
更新订单状态为"已推送"
记录操作日志SUCCESS
```
**结果**:订单状态 ✅ 已推送 | 文档状态 ✅ 有值
---
### 场景2手动填写后首次批量同步智能同步触发
```
某人手动在文档中填写物流链接
订单状态仍为"未推送"(因为是手动填写)
批量同步开始
1. 读取文档数据
2. 发现某行物流列已有值
3. 查询该单号对应的订单
检测到状态不一致:
- 订单状态:未推送 ❌
- 文档状态:有值 ✅
【智能同步触发】
自动更新订单状态为"已推送"
记录同步日志SKIPPED文档中已有值已同步订单状态
```
**结果**:订单状态 ✅ 已推送 | 文档状态 ✅ 有值
---
### 场景3手动填写后再次批量同步无需同步
```
批量同步开始
1. 读取文档数据
2. 发现某行物流列已有值
3. 查询该单号对应的订单
检测到状态一致:
- 订单状态:已推送 ✅(上次已同步)
- 文档状态:有值 ✅
直接跳过(无需同步)
```
**结果**:订单状态 ✅ 已推送 | 文档状态 ✅ 有值
---
### 场景4用户尝试重复推送拒绝
```
用户点击"推送物流"
1. 检查订单状态:已推送 ❌
拒绝推送
返回错误提示:
"该订单已推送到腾讯文档推送时间2025-11-06 12:30:00请勿重复操作"
```
**结果**:请求被拒绝,订单和文档状态保持不变
## 📊 状态同步矩阵
| 订单状态 | 文档物流列 | 用户操作 | 系统行为 | 最终状态 |
|---------|-----------|---------|---------|---------|
| 未推送 | 无值 | 单个推送 | ✅ 写入物流链接,更新订单状态 | 已推送 + 有值 |
| 未推送 | 无值 | 批量同步 | ✅ 写入物流链接,更新订单状态 | 已推送 + 有值 |
| 未推送 | **有值** | 单个推送 | ❌ 拒绝(文档已有值) | 未推送 + 有值 |
| 未推送 | **有值** | 批量同步 | ✅ **智能同步订单状态** | **已推送 + 有值** |
| 已推送 | 有值 | 单个推送 | ❌ 拒绝(订单已推送) | 已推送 + 有值 |
| 已推送 | 有值 | 批量同步 | ✅ 跳过(订单已推送) | 已推送 + 有值 |
| 已推送 | 无值 | 单个推送 | ❌ 拒绝(订单已推送) | 已推送 + 无值 |
| 已推送 | 无值 | 批量同步 | ✅ 跳过(订单已推送) | 已推送 + 无值 |
**重点场景**第4行 - 未推送 + 有值 + 批量同步 = **智能同步**
## 🎯 核心优势
### 1. 自动化
- ✅ 无需人工干预
- ✅ 批量同步时自动检测
- ✅ 自动修正状态不一致
### 2. 高效性
- ✅ 同步后下次批量同步会跳过
- ✅ 减少数据库查询
- ✅ 减少不必要的状态检查
### 3. 可追溯
- ✅ 记录同步操作日志
- ✅ 标记同步原因:"文档中已有物流链接(可能手动填写)"
- ✅ 便于审计和问题排查
### 4. 兼容性
- ✅ 兼容手动填写
- ✅ 兼容外部导入
- ✅ 兼容协同编辑
- ✅ 兼容各种数据来源
## 📝 代码实现(简化版)
```java
// 批量同步时,检查文档物流列
String existingLogisticsLink = row.getString(logisticsLinkColumn);
if (existingLogisticsLink != null && !existingLogisticsLink.trim().isEmpty()) {
// 文档中已有物流链接,检查订单状态
JDOrder existingOrder = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo);
if (existingOrder != null &&
(existingOrder.getTencentDocPushed() == null ||
existingOrder.getTencentDocPushed() == 0)) {
// 状态不一致,触发智能同步
existingOrder.setTencentDocPushed(1);
existingOrder.setTencentDocPushTime(new Date());
jdOrderService.updateJDOrder(existingOrder);
log.info("✓ 同步订单状态 - 单号: {}, 行号: {}, 原因: 文档中已有物流链接(可能手动填写)",
orderNo, excelRow);
// 记录同步日志
logOperation(fileId, sheetId, "BATCH_SYNC", orderNo, excelRow,
existingLogisticsLink, "SKIPPED", "文档中已有物流链接,已同步订单状态");
}
skippedCount++; // 跳过写入
continue;
}
```
## 🔍 日志示例
### 智能同步触发
```
2025-11-06 14:30:15 INFO - 批量同步开始 - 范围第3-202行
2025-11-06 14:30:16 INFO - 发现物流列已有值 - 单号: JY2025110329041, 行号: 123
2025-11-06 14:30:16 INFO - 检测到状态不一致 - 订单状态: 未推送, 文档状态: 有值
2025-11-06 14:30:16 INFO - ✓ 同步订单状态 - 单号: JY2025110329041, 行号: 123, 原因: 文档中已有物流链接(可能手动填写)
2025-11-06 14:30:16 INFO - 记录同步日志 - 操作类型: BATCH_SYNC, 状态: SKIPPED
```
### 操作日志表记录
```sql
INSERT INTO tencent_doc_operation_log (
file_id, sheet_id, operation_type, order_no, target_row,
logistics_link, operation_status, error_message, operator, create_time
) VALUES (
'DUW50RUprWXh2TGJK', 'BB08J2', 'BATCH_SYNC', 'JY2025110329041', 123,
'https://3.cn/2ume-Ak1', 'SKIPPED', '文档中已有物流链接,已同步订单状态',
'admin', '2025-11-06 14:30:16'
);
```
## 🛠️ 排查与维护
### 查询智能同步记录
```sql
-- 查询所有智能同步操作
SELECT order_no, target_row, logistics_link, create_time, operator
FROM tencent_doc_operation_log
WHERE operation_status = 'SKIPPED'
AND error_message LIKE '%文档中已有物流链接,已同步订单状态%'
ORDER BY create_time DESC;
```
### 查询状态不一致的订单理论上应该为0
```sql
-- 如果有记录,说明智能同步未触发或失败
SELECT o.third_party_order_no, o.tencent_doc_pushed, o.logistics_link
FROM jd_order o
WHERE o.logistics_link IS NOT NULL
AND o.logistics_link != ''
AND (o.tencent_doc_pushed IS NULL OR o.tencent_doc_pushed = 0);
```
### 手动修正状态不一致
```sql
-- 如果发现状态不一致,可以手动修正
UPDATE jd_order
SET tencent_doc_pushed = 1,
tencent_doc_push_time = NOW()
WHERE logistics_link IS NOT NULL
AND logistics_link != ''
AND (tencent_doc_pushed IS NULL OR tencent_doc_pushed = 0);
```
## ✅ 最佳实践
1. **定期批量同步**
- 建议每天运行一次批量同步
- 自动修正所有状态不一致
2. **监控同步日志**
- 定期检查 `SKIPPED` 状态的日志
- 分析手动填写的频率和模式
3. **培训团队成员**
- 告知团队手动填写会被系统自动同步
- 建议优先使用系统推送功能
4. **备份重要数据**
- 定期备份腾讯文档
- 定期备份订单数据库
## 🎉 总结
智能状态同步机制确保了:
-**订单状态****文档实际状态** 始终保持一致
- ✅ 兼容多种数据来源(系统推送、手动填写、外部导入)
- ✅ 减少重复查询,提高系统效率
- ✅ 所有同步操作可追溯,便于审计
**这是一个真正智能的、自适应的状态管理机制!** 🚀

View File

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

View File

@@ -0,0 +1,8 @@
-- 给订单表添加腾讯文档推送标记字段
ALTER TABLE jd_order
ADD COLUMN `tencent_doc_pushed` tinyint(1) DEFAULT 0 COMMENT '是否已推送到腾讯文档0-未推送1-已推送)' AFTER `logistics_link`,
ADD COLUMN `tencent_doc_push_time` datetime DEFAULT NULL COMMENT '推送到腾讯文档的时间' AFTER `tencent_doc_pushed`;
-- 添加索引,方便查询未推送的订单
CREATE INDEX idx_tencent_doc_pushed ON jd_order(tencent_doc_pushed, distribution_mark);

View File

@@ -467,6 +467,7 @@ public class TencentDocController extends BaseController {
// 1. 获取参数
thirdPartyOrderNo = (String) params.get("thirdPartyOrderNo");
String logisticsLink = (String) params.get("logisticsLink");
Boolean forceRePush = params.get("forceRePush") != null ? (Boolean) params.get("forceRePush") : false;
if (thirdPartyOrderNo == null || thirdPartyOrderNo.isEmpty()) {
return AjaxResult.error("第三方单号不能为空");
@@ -475,7 +476,24 @@ public class TencentDocController extends BaseController {
return AjaxResult.error("物流链接不能为空");
}
// 2. 获取访问令牌
// 2. 检查订单是否已推送(防止重复推送)
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(thirdPartyOrderNo);
if (order == null) {
logOperation(null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
"FAILED", "订单不存在");
return AjaxResult.error("订单不存在:" + thirdPartyOrderNo);
}
// 如果已推送且不是强制重推,则拒绝
if (order.getTencentDocPushed() != null && order.getTencentDocPushed() == 1 && !forceRePush) {
String pushTimeStr = order.getTencentDocPushTime() != null ?
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(order.getTencentDocPushTime()) : "未知";
logOperation(null, null, "WRITE_SINGLE", thirdPartyOrderNo, null, logisticsLink,
"SKIPPED", String.format("订单已推送,推送时间: %s", pushTimeStr));
return AjaxResult.error(String.format("该订单已推送到腾讯文档(推送时间:%s请勿重复操作如需重新推送请使用强制推送功能。", pushTimeStr));
}
// 3. 获取访问令牌
String accessToken;
try {
accessToken = tencentDocTokenService.getValidAccessToken();
@@ -710,11 +728,24 @@ public class TencentDocController extends BaseController {
log.info("✓ 物流链接写入成功 - 单号: {}, 行: {}, 链接: {}", thirdPartyOrderNo, targetRow, logisticsLink);
// 13. 更新订单的推送状态(标记为已推送)
try {
order.setTencentDocPushed(1);
order.setTencentDocPushTime(new java.util.Date());
jdOrderService.updateJDOrder(order);
log.info("✓ 订单推送状态已更新 - 单号: {}", thirdPartyOrderNo);
} catch (Exception e) {
log.error("更新订单推送状态失败,但写入腾讯文档已成功", e);
// 不影响主流程,继续返回成功
}
JSONObject result = new JSONObject();
result.put("thirdPartyOrderNo", thirdPartyOrderNo);
result.put("logisticsLink", logisticsLink);
result.put("row", targetRow);
result.put("column", logisticsColumn);
result.put("pushed", true);
result.put("pushTime", order.getTencentDocPushTime());
return AjaxResult.success("物流链接填充成功", result);
@@ -817,6 +848,10 @@ public class TencentDocController extends BaseController {
Boolean.valueOf(params.get("forceStart").toString()) : false;
Integer forceStartRow = params.get("forceStartRow") != null ?
Integer.valueOf(params.get("forceStartRow").toString()) : configStartRow;
// 新增参数是否跳过已推送的订单默认true防止重复推送
Boolean skipPushedOrders = params.get("skipPushedOrders") != null ?
Boolean.valueOf(params.get("skipPushedOrders").toString()) : true;
if (accessToken == null || fileId == null || sheetId == null) {
return AjaxResult.error("文档配置不完整,请先配置 fileId 和 sheetId");
@@ -1056,10 +1091,30 @@ public class TencentDocController extends BaseController {
orderNo = orderNo.trim();
// 检查物流链接列是否已有值
// 检查物流链接列是否已有值(可能是别人手动填写的)
String existingLogisticsLink = row.getString(logisticsLinkColumn);
if (existingLogisticsLink != null && !existingLogisticsLink.trim().isEmpty()) {
skippedCount++; // 已有物流链接,跳过
// 文档中已有物流链接,同步更新订单的推送状态
try {
JDOrder existingOrder = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo);
if (existingOrder != null &&
(existingOrder.getTencentDocPushed() == null || existingOrder.getTencentDocPushed() == 0)) {
// 订单未标记为已推送,但文档中已有值,同步状态
existingOrder.setTencentDocPushed(1);
existingOrder.setTencentDocPushTime(new java.util.Date());
jdOrderService.updateJDOrder(existingOrder);
log.info("✓ 同步订单状态 - 单号: {}, 行号: {}, 原因: 文档中已有物流链接(可能手动填写)",
orderNo, excelRow);
// 记录同步日志
logOperation(fileId, sheetId, "BATCH_SYNC", orderNo, excelRow, existingLogisticsLink,
"SKIPPED", "文档中已有物流链接,已同步订单状态");
}
} catch (Exception e) {
log.error("同步订单状态失败 - 单号: {}, 行号: {}", orderNo, excelRow, e);
}
skippedCount++; // 已有物流链接,跳过写入
continue;
}
@@ -1067,22 +1122,42 @@ public class TencentDocController extends BaseController {
// 根据第三方单号查询订单
JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo);
if (order != null && order.getLogisticsLink() != null && !order.getLogisticsLink().trim().isEmpty()) {
if (order == null) {
errorCount++;
log.warn("未找到订单 - 单号: {}, 行号: {}", orderNo, excelRow);
continue;
}
// 检查订单是否已推送(防止重复推送)
if (skipPushedOrders && order.getTencentDocPushed() != null && order.getTencentDocPushed() == 1) {
skippedCount++;
log.info("跳过已推送订单 - 单号: {}, 推送时间: {}, 行号: {}",
orderNo,
order.getTencentDocPushTime() != null ?
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(order.getTencentDocPushTime()) : "未知",
excelRow);
continue;
}
if (order.getLogisticsLink() != null && !order.getLogisticsLink().trim().isEmpty()) {
String logisticsLink = order.getLogisticsLink().trim();
// 构建更新请求
// 构建更新请求,同时保存订单对象用于后续更新推送状态
JSONObject update = new JSONObject();
update.put("row", excelRow);
update.put("column", logisticsLinkColumn);
update.put("orderNo", orderNo);
update.put("logisticsLink", logisticsLink);
update.put("order", order); // 保存订单对象
updates.add(update);
filledCount++;
log.info("找到订单物流链接 - 单号: {}, 物流链接: {}, 行号: {}", orderNo, logisticsLink, excelRow);
log.info("找到订单物流链接 - 单号: {}, 物流链接: {}, 行号: {}, 已推送: {}",
orderNo, logisticsLink, excelRow,
order.getTencentDocPushed() != null && order.getTencentDocPushed() == 1 ? "" : "");
} else {
errorCount++;
log.warn("未找到订单物流链接为空 - 单号: {}, 行号: {}", orderNo, excelRow);
log.warn("订单物流链接为空 - 单号: {}, 行号: {}", orderNo, excelRow);
}
} catch (Exception e) {
errorCount++;
@@ -1179,9 +1254,33 @@ public class TencentDocController extends BaseController {
successUpdates++;
log.info("✓ 写入成功 - 行: {}, 单号: {}, 物流链接: {}", row, expectedOrderNo, logisticsLink);
// 更新订单的推送状态
try {
JDOrder orderToUpdate = (JDOrder) update.get("order");
if (orderToUpdate != null) {
orderToUpdate.setTencentDocPushed(1);
orderToUpdate.setTencentDocPushTime(new java.util.Date());
jdOrderService.updateJDOrder(orderToUpdate);
log.info("✓ 订单推送状态已更新 - 单号: {}", expectedOrderNo);
}
} catch (Exception e) {
log.error("更新订单推送状态失败(但写入腾讯文档已成功) - 单号: {}", expectedOrderNo, e);
// 不影响主流程,继续执行
}
// 记录操作日志
logOperation(fileId, sheetId, "BATCH_SYNC", expectedOrderNo, row, logisticsLink,
"SUCCESS", null);
} catch (Exception e) {
log.error("写入数据失败 - 行: {}", entry.getKey(), e);
errorCount++;
// 记录失败日志
String orderNo = entry.getValue().getString("orderNo");
String logisticsLink = entry.getValue().getString("logisticsLink");
logOperation(fileId, sheetId, "BATCH_SYNC", orderNo, entry.getKey(), logisticsLink,
"FAILED", "写入异常: " + e.getMessage());
}
// 添加延迟避免API调用频率过高
@@ -1245,16 +1344,19 @@ public class TencentDocController extends BaseController {
result.put("errorCount", errorCount);
result.put("orderNoColumn", orderNoColumn);
result.put("logisticsLinkColumn", logisticsLinkColumn);
result.put("skipPushedOrders", skipPushedOrders); // 是否跳过已推送订单
// 构建详细的提示信息
String message = String.format(
"✓ 同步完成:成功填充 %d 条,跳过 %d 条,错误 %d 条\n" +
" 本次范围:第 %d-%d 行\n" +
" 当前进度:第 %d 行\n" +
" 防重推送:%s\n" +
" %s",
filledCount, skippedCount, errorCount,
startRow, currentMaxRow,
currentMaxRow,
skipPushedOrders ? "已启用(跳过已推送订单)" : "已禁用",
nextSyncHint
);
result.put("message", message);

View File

@@ -50,6 +50,12 @@ public class JDOrder extends BaseEntity {
@Excel(name = "物流链接")
private String logisticsLink;
/** 是否已推送到腾讯文档0-未推送1-已推送) */
private Integer tencentDocPushed;
/** 推送到腾讯文档的时间 */
private Date tencentDocPushTime;
/** 订单号 */
@Excel(name = "订单号", cellType = ColumnType.STRING)
private String orderId;

View File

@@ -12,6 +12,8 @@
<result property="rebateAmount" column="rebate_amount"/>
<result property="address" column="address"/>
<result property="logisticsLink" column="logistics_link"/>
<result property="tencentDocPushed" column="tencent_doc_pushed"/>
<result property="tencentDocPushTime" column="tencent_doc_push_time"/>
<result property="orderId" column="order_id"/>
<result property="buyer" column="buyer"/>
<result property="orderTime" column="order_time"/>
@@ -97,10 +99,12 @@
insert into jd_order (
remark, distribution_mark, model_number, link,
payment_amount, rebate_amount, address, logistics_link,
tencent_doc_pushed, tencent_doc_push_time,
order_id, buyer, order_time, create_time, update_time, status, is_count_enabled, third_party_order_no, jingfen_actual_price
) values (
#{remark}, #{distributionMark}, #{modelNumber}, #{link},
#{paymentAmount}, #{rebateAmount}, #{address}, #{logisticsLink},
0, null,
#{orderId}, #{buyer}, #{orderTime}, now(), now(), #{status}, #{isCountEnabled}, #{thirdPartyOrderNo}, #{jingfenActualPrice}
)
</insert>
@@ -116,6 +120,8 @@
<if test="rebateAmount != null"> rebate_amount = #{rebateAmount},</if>
<if test="address != null"> address = #{address},</if>
<if test="logisticsLink != null"> logistics_link = #{logisticsLink},</if>
<if test="tencentDocPushed != null"> tencent_doc_pushed = #{tencentDocPushed},</if>
<if test="tencentDocPushTime != null"> tencent_doc_push_time = #{tencentDocPushTime},</if>
<if test="orderId != null"> order_id = #{orderId},</if>
<if test="buyer != null"> buyer = #{buyer},</if>
<if test="orderTime != null"> order_time = #{orderTime},</if>