This commit is contained in:
2025-11-06 13:03:01 +08:00
parent 228883250b
commit 88234c13b2
3 changed files with 517 additions and 8 deletions

View File

@@ -0,0 +1,420 @@
# 录单自动写入腾讯文档 - 联动功能说明
## 🎯 功能概述
当通过系统录入订单时,如果订单的**分销标识是 `H-TF`**,系统会**自动将订单数据异步追加到腾讯文档表格**,实现录单与腾讯文档的自动联动。
---
## ✨ 功能特点
### 1. 自动触发
-**无需手动操作**:录单时自动检测分销标识
-**仅针对 H-TF 订单**:其他分销标识的订单不受影响
-**异步执行**:不阻塞录单流程,录单响应速度不受影响
### 2. 异常处理
-**配置缺失提示**:如果腾讯文档配置不完整,会记录错误日志但不影响录单
-**写入失败容错**:即使写入腾讯文档失败,录单仍然成功
-**详细日志**:所有操作都有详细的日志记录,便于排查问题
---
## 🔧 实现逻辑
### 流程图
```
用户提交录单(分销标识: H-TF
解析订单数据
保存到数据库 ✅
检测分销标识 === "H-TF"?
↓ 是
启动异步线程
读取腾讯文档配置
├─ accessToken
├─ fileId
└─ sheetId
配置完整? === 是?
↓ 是
调用 appendLogisticsToSheet
追加订单到表格 ✅
记录成功日志
```
---
## 📋 代码修改清单
### 1. InstructionServiceImpl.java
**添加依赖注入**
```java
@Resource
private ITencentDocService tencentDocService;
@Resource
private TencentDocConfig tencentDocConfig;
```
**录单后检查分销标识**第1232-1235行
```java
// 如果分销标识是 H-TF自动写入腾讯文档
if ("H-TF".equals(order.getDistributionMark())) {
asyncWriteToTencentDoc(order);
}
```
**异步写入方法**第1640-1684行
```java
/**
* 异步写入订单到腾讯文档
* 当订单的分销标识是 H-TF 时,自动追加到腾讯文档表格
*/
private void asyncWriteToTencentDoc(JDOrder order) {
// 使用独立线程异步执行,避免阻塞录单流程
new Thread(() -> {
try {
// 读取腾讯文档配置
String accessToken = tencentDocConfig.getAccessToken();
String fileId = tencentDocConfig.getFileId();
String sheetId = tencentDocConfig.getSheetId();
// 验证配置是否完整
if (accessToken == null || accessToken.isEmpty() ||
fileId == null || fileId.isEmpty() ||
sheetId == null || sheetId.isEmpty()) {
System.err.println("腾讯文档配置不完整,跳过自动写入...");
return;
}
// 调用腾讯文档服务追加订单数据
JSONObject result = tencentDocService.appendLogisticsToSheet(
accessToken, fileId, sheetId, order);
if (result != null) {
System.out.println("订单已自动追加到腾讯文档 - 单号: " +
order.getRemark() + ", 第三方单号: " + order.getThirdPartyOrderNo());
}
} catch (Exception e) {
// 写入失败不影响录单结果,仅记录错误日志
System.err.println("异步写入腾讯文档失败: " + e.getMessage());
e.printStackTrace();
}
}, "TencentDoc-Writer-" + order.getRemark()).start();
}
```
---
### 2. TencentDocConfig.java
**添加配置字段**第44-51行
```java
/** 访问令牌用于自动写入H-TF订单到腾讯文档 */
private String accessToken;
/** 文件IDH-TF订单的目标文档ID */
private String fileId;
/** 工作表IDH-TF订单的目标工作表ID */
private String sheetId;
```
**添加 Getter/Setter 方法**第130-152行
```java
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
```
---
## ⚙️ 配置方法
### 在 application-dev.yml 中添加配置
```yaml
tencent:
doc:
# 已有配置
app-id: 你的AppID
app-secret: 你的AppSecret
redirect-uri: http://localhost:30313/jarvis/tencentDoc/callback
api-base-url: https://docs.qq.com/openapi/spreadsheet/v3
# 新增配置用于自动写入H-TF订单
access-token: 你的访问令牌 # 必须配置
file-id: DUW50RUprWXh2TGJK # 目标文档ID
sheet-id: BB08J2 # 目标工作表ID
```
### 在 application-prod.yml 中添加相同配置
```yaml
tencent:
doc:
# 生产环境配置
access-token: 生产环境访问令牌
file-id: 生产环境文档ID
sheet-id: 生产环境工作表ID
```
---
## 📝 配置说明
### 1. access-token访问令牌
**获取方式**
1. 通过OAuth2.0授权流程获取
2. 访问:`http://localhost:30313/jarvis/tencentDoc/auth`
3. 完成授权后,从返回结果中获取 `access_token`
**注意事项**
- ✅ 访问令牌有有效期通常30天
- ✅ 过期后需要使用 `refresh_token` 刷新
- ✅ 建议定期更新配置文件中的令牌
---
### 2. file-id文件ID
**获取方式**
- 从腾讯文档的URL中获取
- 例如:`https://docs.qq.com/sheet/DUW50RUprWXh2TGJK`
- `file-id` 就是 `DUW50RUprWXh2TGJK`
---
### 3. sheet-id工作表ID
**获取方式**
- 从腾讯文档的URL中获取
- 例如:`https://docs.qq.com/sheet/DUW50RUprWXh2TGJK?tab=BB08J2`
- `sheet-id` 就是 `BB08J2`
**或者通过API获取**
```bash
curl -X GET 'http://localhost:30313/jarvis/tencentDoc/getSheetList?fileId=DUW50RUprWXh2TGJK'
```
---
## 🧪 测试验证
### 1. 录入H-TF订单
**测试数据**
```
单:
2025-01-21 001
备注测试H-TF自动写入
分销标记H-TF
第三方单号JY202511050001
型号ZQD180F-EB200
链接https://item.jd.com/123456.html
下单付款1650
后返金额50
地址张三13800138000上海市浦东新区张江高科技园区
物流链接https://3.cn/test-link
订单号JD123456789
下单人:测试用户
```
---
### 2. 查看控制台日志
**成功日志**
```
订单已自动追加到腾讯文档 - 单号: 测试H-TF自动写入, 第三方单号: JY202511050001
```
**配置缺失日志**
```
腾讯文档配置不完整,跳过自动写入。请检查配置:
accessToken=未配置
fileId=未配置
sheetId=未配置
```
**写入失败日志**
```
异步写入腾讯文档失败 - 单号: 测试H-TF自动写入, 错误: 401 Unauthorized
```
---
### 3. 检查腾讯文档
打开目标腾讯文档表格,查看最后一行是否已追加新订单数据。
**预期表格内容**
| 日期 | 公司 | 单号 | 型号 | ... | 物流单号 | 是否安排 | 标记 |
|------|------|------|------|-----|----------|----------|------|
| ... | ... | ... | ... | ... | ... | ... | ... |
| (新增行)| JY202511050001 | ... | ZQD180F-EB200 | ... | https://3.cn/test-link | | |
---
## 📊 功能对比
| 场景 | 修改前 | 修改后 |
|------|--------|--------|
| **H-TF订单录入** | 只保存到数据库 | 保存到数据库 + 自动追加到腾讯文档 ✅ |
| **非H-TF订单录入** | 只保存到数据库 | 只保存到数据库 |
| **录单响应速度** | 快 | 快(异步不阻塞) ✅ |
| **腾讯文档配置缺失** | - | 仅记录日志,不影响录单 ✅ |
| **腾讯文档写入失败** | - | 仅记录日志,不影响录单 ✅ |
---
## ⚠️ 注意事项
### 1. 配置安全
- ⚠️ **access-token 是敏感信息**,不要提交到代码仓库
- ✅ 建议使用环境变量或加密配置管理
- ✅ 定期更新访问令牌
---
### 2. 令牌有效期
- ⚠️ 访问令牌通常有30天有效期
- ✅ 令牌过期后需要刷新
- ✅ 建议实现自动刷新机制(后续优化)
---
### 3. 异步执行
- ✅ 写入腾讯文档是异步执行的
- ✅ 录单接口会立即返回,不等待腾讯文档写入完成
- ⚠️ 需要查看控制台日志确认是否写入成功
---
### 4. 错误处理
- ✅ 配置不完整:跳过写入,记录日志
- ✅ 写入失败:记录错误日志,不影响录单
- ⚠️ 不会重试:写入失败后不会自动重试(需要手动补录)
---
## 🚀 后续优化建议
### 1. 令牌自动刷新
```java
// 检查令牌是否即将过期
if (isTokenExpiringSoon()) {
// 自动刷新令牌
refreshAccessToken();
}
```
---
### 2. 写入失败重试
```java
// 写入失败后,将任务加入重试队列
if (!success) {
retryQueue.add(order);
}
```
---
### 3. 批量写入
```java
// 收集多个H-TF订单批量写入腾讯文档
List<JDOrder> pendingOrders = collectPendingOrders();
batchWriteToTencentDoc(pendingOrders);
```
---
### 4. 监控和告警
```java
// 统计写入成功率
int successCount = ...;
int totalCount = ...;
double successRate = (double) successCount / totalCount;
if (successRate < 0.8) {
// 发送告警通知
sendAlert("腾讯文档写入成功率低于80%");
}
```
---
## 📚 相关文档
- [腾讯文档开放平台](https://docs.qq.com/open/)
- [OAuth2.0授权流程](https://docs.qq.com/open/document/app/oauth2/)
- [在线表格批量更新接口](https://docs.qq.com/open/document/app/openapi/v3/sheet/batchupdate/update.html)
---
## ✅ 总结
### 核心功能
1.**自动联动**H-TF订单录入后自动追加到腾讯文档
2.**异步执行**:不阻塞录单流程
3.**容错机制**:配置缺失或写入失败不影响录单
### 配置要求
1. ✅ 配置 `tencent.doc.access-token`
2. ✅ 配置 `tencent.doc.file-id`
3. ✅ 配置 `tencent.doc.sheet-id`
### 使用建议
1. ✅ 定期检查访问令牌是否过期
2. ✅ 监控控制台日志,确认写入成功
3. ✅ 如有写入失败,手动补录到腾讯文档
---
**文档版本**1.0
**创建时间**2025-11-05
**功能状态**:✅ 已实现并测试

View File

@@ -41,6 +41,15 @@ public class TencentDocConfig {
/** 刷新Token地址 */
private String refreshTokenUrl = "https://docs.qq.com/oauth/v2/token";
/** 访问令牌用于自动写入H-TF订单到腾讯文档 */
private String accessToken;
/** 文件IDH-TF订单的目标文档ID */
private String fileId;
/** 工作表IDH-TF订单的目标工作表ID */
private String sheetId;
/**
* 配置初始化后验证
*/
@@ -117,5 +126,29 @@ public class TencentDocConfig {
public void setRefreshTokenUrl(String refreshTokenUrl) {
this.refreshTokenUrl = refreshTokenUrl;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
}

View File

@@ -40,6 +40,10 @@ public class InstructionServiceImpl implements IInstructionService {
private StringRedisTemplate stringRedisTemplate;
@Resource
private IProductJdConfigService productJdConfigService;
@Resource
private com.ruoyi.jarvis.service.ITencentDocService tencentDocService;
@Resource
private com.ruoyi.jarvis.config.TencentDocConfig tencentDocConfig;
// 录单模板(与 jd/JDUtil 中 WENAN_D 保持一致)
private static final String WENAN_D = "单:\n" + "{单号} \n备注{单的备注}\n" + "分销标记:{分销标记}\n" + "第三方单号:{第三方单号}\n" + "型号:\n" + "{型号}\n" + "链接:\n" + "{链接}\n" + "下单付款:\n" + "\n" + "后返金额:\n" + "\n" + "地址:\n" + "{地址}\n" + "物流链接:\n" + "\n" + "订单号:\n" + "\n" + "下单人:\n" + "\n" + "京粉实际价格:\n" + "\n";
@@ -625,14 +629,10 @@ private String handleTF(String input) {
* 型号地址电话的处理逻辑不变将10.10 腾锋 JY202510093195 设置成分销标识这个字段比如之前是H-TF现在改成H-TF(10.10 腾锋 JY202510093195)
* */
if (parts.length >= 7) { // 至少需要分销信息(3) + 型号(1) + 数量(1) + 姓名(1) + 电话(1) = 7个字段
// 处理分销标记: 简化格式,只保留H-TF和第三方单号(第三方单号已单独存储)
// 提取第三方单号第3个字段格式如JY202511051374
String thirdPartyOrderNo = parts.length > 2 ? parts[2].trim() : "";
// 处理分销标记: 只保留H-TF,不包含其他内容(第三方单号已单独存储)
String fenxiaoInfo = "H-TF";
// 如果第三方单号存在,添加到分销标记中(但保持简短)
if (!thirdPartyOrderNo.isEmpty()) {
fenxiaoInfo = "H-TF(" + thirdPartyOrderNo + ")";
}
// 提取第三方单号第3个字段格式如JY202511051374用于单独存储添加到分销标记中
String thirdPartyOrderNo = parts.length > 2 ? parts[2].trim() : "";
// 提取型号第4个字段
String modelNumber = parts[3].replace("\\n", "");
@@ -730,7 +730,8 @@ private String handleTF(String input) {
StringBuilder order = new StringBuilder();
order.append(""+phone).append("\n").append(fenxiaoInfo).append("\n").append(modelNumber).append("\n").append(jf).append("\n").append(quantityStr).append("\n").append(address);
outputs.add(generateOrderText(order.toString()));
// 传递第三方单号给 generateOrderText以便在生成订单文本时正确提取
outputs.add(generateOrderText(order.toString(), thirdPartyOrderNo));
} else {
outputs.add("TF 指令格式TF\t分销信息\t分销信息\t分销信息\t型号\t数量\t姓名\t电话\t地址 ;也支持多行,每行一条数据");
}
@@ -1228,6 +1229,11 @@ private String handleTF(String input) {
jdOrderService.insertJDOrder(order);
}
// 如果分销标识是 H-TF自动写入腾讯文档
if ("H-TF".equals(order.getDistributionMark())) {
asyncWriteToTencentDoc(order);
}
// 返回完整的表单格式,使用原始输入保留完整物流链接
return formatOrderForm(order, originalInput);
}
@@ -1363,6 +1369,10 @@ private String handleTF(String input) {
}
distributionMark = firstLine;
}
// 如果分销标记是H-TF格式包含括号只保留H-TF
if (distributionMark != null && distributionMark.startsWith("H-TF(")) {
distributionMark = "H-TF";
}
order.setDistributionMark(distributionMark);
// 优先从字段中获取第三方单号,如果没有则从分销标记中提取
@@ -1626,6 +1636,52 @@ private String handleTF(String input) {
}
return String.valueOf(v);
}
/**
* 异步写入订单到腾讯文档
* 当订单的分销标识是 H-TF 时,自动追加到腾讯文档表格
*
* @param order 订单对象
*/
private void asyncWriteToTencentDoc(JDOrder order) {
// 使用独立线程异步执行,避免阻塞录单流程
new Thread(() -> {
try {
// 读取腾讯文档配置
String accessToken = tencentDocConfig.getAccessToken();
String fileId = tencentDocConfig.getFileId();
String sheetId = tencentDocConfig.getSheetId();
// 验证配置是否完整
if (accessToken == null || accessToken.isEmpty() ||
fileId == null || fileId.isEmpty() ||
sheetId == null || sheetId.isEmpty()) {
System.err.println("腾讯文档配置不完整跳过自动写入。请检查配置accessToken=" +
(accessToken != null && !accessToken.isEmpty() ? "已配置" : "未配置") +
", fileId=" + (fileId != null && !fileId.isEmpty() ? "已配置" : "未配置") +
", sheetId=" + (sheetId != null && !sheetId.isEmpty() ? "已配置" : "未配置"));
return;
}
// 调用腾讯文档服务追加订单数据
com.alibaba.fastjson2.JSONObject result = tencentDocService.appendLogisticsToSheet(
accessToken, fileId, sheetId, order);
if (result != null) {
System.out.println("订单已自动追加到腾讯文档 - 单号: " + order.getRemark() +
", 第三方单号: " + order.getThirdPartyOrderNo());
} else {
System.err.println("订单追加到腾讯文档失败 - 单号: " + order.getRemark() +
", API返回null");
}
} catch (Exception e) {
// 写入失败不影响录单结果,仅记录错误日志
System.err.println("异步写入腾讯文档失败 - 单号: " + order.getRemark() +
", 错误: " + e.getMessage());
e.printStackTrace();
}
}, "TencentDoc-Writer-" + order.getRemark()).start();
}
}