Compare commits

...

2 Commits

Author SHA1 Message Date
b4b8f03a4f 1 2025-11-06 13:10:09 +08:00
88234c13b2 1 2025-11-06 13:03:01 +08:00
4 changed files with 795 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

@@ -0,0 +1,250 @@
package com.ruoyi.web.controller.jarvis;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.jarvis.config.TencentDocConfig;
import com.ruoyi.jarvis.service.ITencentDocService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 腾讯文档配置管理Controller
* 用于动态配置H-TF订单自动写入腾讯文档的相关参数
*
* @author system
*/
@RestController
@RequestMapping("/jarvis/tencentDoc/config")
public class TencentDocConfigController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(TencentDocConfigController.class);
@Autowired
private TencentDocConfig tencentDocConfig;
@Autowired
private ITencentDocService tencentDocService;
@Autowired
private RedisCache redisCache;
// Redis key前缀
private static final String REDIS_KEY_PREFIX = "tencent:doc:auto:config:";
/**
* 获取当前配置
*/
@GetMapping
public AjaxResult getConfig() {
try {
JSONObject config = new JSONObject();
// 从Redis获取配置如果存在
String accessToken = redisCache.getCacheObject(REDIS_KEY_PREFIX + "accessToken");
String fileId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "fileId");
String sheetId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "sheetId");
// 如果Redis中没有则使用配置文件中的默认值
if (accessToken == null || accessToken.isEmpty()) {
accessToken = tencentDocConfig.getAccessToken();
}
if (fileId == null || fileId.isEmpty()) {
fileId = tencentDocConfig.getFileId();
}
if (sheetId == null || sheetId.isEmpty()) {
sheetId = tencentDocConfig.getSheetId();
}
config.put("accessToken", maskSensitiveInfo(accessToken)); // 脱敏显示
config.put("fileId", fileId);
config.put("sheetId", sheetId);
config.put("appId", tencentDocConfig.getAppId());
config.put("apiBaseUrl", tencentDocConfig.getApiBaseUrl());
// 检查配置是否完整
boolean isComplete = accessToken != null && !accessToken.isEmpty() &&
fileId != null && !fileId.isEmpty() &&
sheetId != null && !sheetId.isEmpty();
config.put("isConfigured", isComplete);
return AjaxResult.success("获取配置成功", config);
} catch (Exception e) {
log.error("获取腾讯文档配置失败", e);
return AjaxResult.error("获取配置失败: " + e.getMessage());
}
}
/**
* 更新配置保存到Redis180天有效期
*
* @param params 包含 accessToken, fileId, sheetId
*/
@Log(title = "腾讯文档配置", businessType = BusinessType.UPDATE)
@PostMapping
public AjaxResult updateConfig(@RequestBody JSONObject params) {
try {
String accessToken = params.getString("accessToken");
String fileId = params.getString("fileId");
String sheetId = params.getString("sheetId");
// 验证必填字段
if (accessToken == null || accessToken.trim().isEmpty()) {
return AjaxResult.error("访问令牌不能为空");
}
if (fileId == null || fileId.trim().isEmpty()) {
return AjaxResult.error("文件ID不能为空");
}
if (sheetId == null || sheetId.trim().isEmpty()) {
return AjaxResult.error("工作表ID不能为空");
}
// 保存到Redis180天有效期
redisCache.setCacheObject(REDIS_KEY_PREFIX + "accessToken", accessToken.trim(), 180, TimeUnit.DAYS);
redisCache.setCacheObject(REDIS_KEY_PREFIX + "fileId", fileId.trim(), 180, TimeUnit.DAYS);
redisCache.setCacheObject(REDIS_KEY_PREFIX + "sheetId", sheetId.trim(), 180, TimeUnit.DAYS);
// 同时更新TencentDocConfig对象内存中
tencentDocConfig.setAccessToken(accessToken.trim());
tencentDocConfig.setFileId(fileId.trim());
tencentDocConfig.setSheetId(sheetId.trim());
log.info("腾讯文档配置已更新 - fileId: {}, sheetId: {}", fileId.trim(), sheetId.trim());
JSONObject result = new JSONObject();
result.put("message", "配置更新成功已保存到Redis180天有效期");
result.put("fileId", fileId.trim());
result.put("sheetId", sheetId.trim());
return AjaxResult.success("配置更新成功", result);
} catch (Exception e) {
log.error("更新腾讯文档配置失败", e);
return AjaxResult.error("配置更新失败: " + e.getMessage());
}
}
/**
* 测试配置是否有效
* 尝试读取指定表格的工作表列表
*/
@GetMapping("/test")
public AjaxResult testConfig() {
try {
// 获取当前配置
String accessToken = redisCache.getCacheObject(REDIS_KEY_PREFIX + "accessToken");
String fileId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "fileId");
if (accessToken == null || accessToken.isEmpty()) {
accessToken = tencentDocConfig.getAccessToken();
}
if (fileId == null || fileId.isEmpty()) {
fileId = tencentDocConfig.getFileId();
}
// 验证配置
if (accessToken == null || accessToken.isEmpty()) {
return AjaxResult.error("访问令牌未配置");
}
if (fileId == null || fileId.isEmpty()) {
return AjaxResult.error("文件ID未配置");
}
// 测试API调用获取工作表列表
log.info("测试腾讯文档配置 - fileId: {}", fileId);
JSONObject result = tencentDocService.getSheetList(accessToken, fileId);
if (result != null) {
JSONObject testResult = new JSONObject();
testResult.put("status", "success");
testResult.put("message", "配置有效API调用成功");
testResult.put("fileId", fileId);
testResult.put("apiResponse", result);
return AjaxResult.success("配置测试成功", testResult);
} else {
return AjaxResult.error("配置测试失败API返回null");
}
} catch (Exception e) {
log.error("测试腾讯文档配置失败", e);
return AjaxResult.error("配置测试失败: " + e.getMessage());
}
}
/**
* 清除配置从Redis中删除
*/
@Log(title = "腾讯文档配置", businessType = BusinessType.DELETE)
@DeleteMapping
public AjaxResult clearConfig() {
try {
redisCache.deleteObject(REDIS_KEY_PREFIX + "accessToken");
redisCache.deleteObject(REDIS_KEY_PREFIX + "fileId");
redisCache.deleteObject(REDIS_KEY_PREFIX + "sheetId");
log.info("腾讯文档配置已清除");
return AjaxResult.success("配置已清除");
} catch (Exception e) {
log.error("清除腾讯文档配置失败", e);
return AjaxResult.error("清除配置失败: " + e.getMessage());
}
}
/**
* 获取文档的工作表列表用于选择工作表ID
*
* @param fileId 文件ID
*/
@GetMapping("/sheets")
public AjaxResult getSheetList(@RequestParam String fileId) {
try {
// 获取访问令牌
String accessToken = redisCache.getCacheObject(REDIS_KEY_PREFIX + "accessToken");
if (accessToken == null || accessToken.isEmpty()) {
accessToken = tencentDocConfig.getAccessToken();
}
if (accessToken == null || accessToken.isEmpty()) {
return AjaxResult.error("访问令牌未配置请先设置accessToken");
}
if (fileId == null || fileId.isEmpty()) {
return AjaxResult.error("文件ID不能为空");
}
// 调用API获取工作表列表
JSONObject result = tencentDocService.getSheetList(accessToken, fileId);
if (result != null) {
return AjaxResult.success("获取工作表列表成功", result);
} else {
return AjaxResult.error("获取工作表列表失败API返回null");
}
} catch (Exception e) {
log.error("获取工作表列表失败", e);
return AjaxResult.error("获取工作表列表失败: " + e.getMessage());
}
}
/**
* 脱敏显示敏感信息
* 例如eyJhbGciOi... -> eyJhb...***...o6U显示前6个和后3个字符
*/
private String maskSensitiveInfo(String info) {
if (info == null || info.isEmpty()) {
return "未配置";
}
if (info.length() <= 10) {
return info.substring(0, 3) + "***";
}
return info.substring(0, 6) + "***" + info.substring(info.length() - 3);
}
}

View File

@@ -41,6 +41,15 @@ public class TencentDocConfig {
/** 刷新Token地址 */ /** 刷新Token地址 */
private String refreshTokenUrl = "https://docs.qq.com/oauth/v2/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) { public void setRefreshTokenUrl(String refreshTokenUrl) {
this.refreshTokenUrl = 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,14 @@ public class InstructionServiceImpl implements IInstructionService {
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@Resource @Resource
private IProductJdConfigService productJdConfigService; private IProductJdConfigService productJdConfigService;
@Resource
private com.ruoyi.jarvis.service.ITencentDocService tencentDocService;
@Resource
private com.ruoyi.jarvis.service.ITencentDocTokenService tencentDocTokenService;
@Resource
private com.ruoyi.jarvis.config.TencentDocConfig tencentDocConfig;
@Resource
private com.ruoyi.common.core.redis.RedisCache redisCache;
// 录单模板(与 jd/JDUtil 中 WENAN_D 保持一致) // 录单模板(与 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"; 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 +633,10 @@ private String handleTF(String input) {
* 型号地址电话的处理逻辑不变将10.10 腾锋 JY202510093195 设置成分销标识这个字段比如之前是H-TF现在改成H-TF(10.10 腾锋 JY202510093195) * 型号地址电话的处理逻辑不变将10.10 腾锋 JY202510093195 设置成分销标识这个字段比如之前是H-TF现在改成H-TF(10.10 腾锋 JY202510093195)
* */ * */
if (parts.length >= 7) { // 至少需要分销信息(3) + 型号(1) + 数量(1) + 姓名(1) + 电话(1) = 7个字段 if (parts.length >= 7) { // 至少需要分销信息(3) + 型号(1) + 数量(1) + 姓名(1) + 电话(1) = 7个字段
// 处理分销标记: 简化格式,只保留H-TF和第三方单号(第三方单号已单独存储) // 处理分销标记: 只保留H-TF,不包含其他内容(第三方单号已单独存储)
// 提取第三方单号第3个字段格式如JY202511051374
String thirdPartyOrderNo = parts.length > 2 ? parts[2].trim() : "";
String fenxiaoInfo = "H-TF"; String fenxiaoInfo = "H-TF";
// 如果第三方单号存在,添加到分销标记中(但保持简短) // 提取第三方单号第3个字段格式如JY202511051374用于单独存储添加到分销标记中
if (!thirdPartyOrderNo.isEmpty()) { String thirdPartyOrderNo = parts.length > 2 ? parts[2].trim() : "";
fenxiaoInfo = "H-TF(" + thirdPartyOrderNo + ")";
}
// 提取型号第4个字段 // 提取型号第4个字段
String modelNumber = parts[3].replace("\\n", ""); String modelNumber = parts[3].replace("\\n", "");
@@ -730,7 +734,8 @@ private String handleTF(String input) {
StringBuilder order = new StringBuilder(); 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); 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 { } else {
outputs.add("TF 指令格式TF\t分销信息\t分销信息\t分销信息\t型号\t数量\t姓名\t电话\t地址 ;也支持多行,每行一条数据"); outputs.add("TF 指令格式TF\t分销信息\t分销信息\t分销信息\t型号\t数量\t姓名\t电话\t地址 ;也支持多行,每行一条数据");
} }
@@ -1228,6 +1233,11 @@ private String handleTF(String input) {
jdOrderService.insertJDOrder(order); jdOrderService.insertJDOrder(order);
} }
// 如果分销标识是 H-TF自动写入腾讯文档
if ("H-TF".equals(order.getDistributionMark())) {
asyncWriteToTencentDoc(order);
}
// 返回完整的表单格式,使用原始输入保留完整物流链接 // 返回完整的表单格式,使用原始输入保留完整物流链接
return formatOrderForm(order, originalInput); return formatOrderForm(order, originalInput);
} }
@@ -1363,6 +1373,10 @@ private String handleTF(String input) {
} }
distributionMark = firstLine; distributionMark = firstLine;
} }
// 如果分销标记是H-TF格式包含括号只保留H-TF
if (distributionMark != null && distributionMark.startsWith("H-TF(")) {
distributionMark = "H-TF";
}
order.setDistributionMark(distributionMark); order.setDistributionMark(distributionMark);
// 优先从字段中获取第三方单号,如果没有则从分销标记中提取 // 优先从字段中获取第三方单号,如果没有则从分销标记中提取
@@ -1626,6 +1640,76 @@ private String handleTF(String input) {
} }
return String.valueOf(v); return String.valueOf(v);
} }
/**
* 异步写入订单到腾讯文档
* 当订单的分销标识是 H-TF 时,自动追加到腾讯文档表格
*
* @param order 订单对象
*/
private void asyncWriteToTencentDoc(JDOrder order) {
// 使用独立线程异步执行,避免阻塞录单流程
new Thread(() -> {
try {
// Redis key前缀用于获取文档配置
final String REDIS_KEY_PREFIX = "tencent:doc:auto:config:";
// 1. 从Token服务获取access-token会自动从Redis读取并刷新
String accessToken = null;
try {
accessToken = tencentDocTokenService.getValidAccessToken();
} catch (Exception e) {
System.err.println("✗ 无法获取腾讯文档访问令牌 - " + e.getMessage());
System.err.println(" 提示:请先完成腾讯文档授权,访问 /jarvis/tendoc/authUrl 获取授权URL");
return;
}
// 2. 从Redis获取文档配置fileId 和 sheetId
String fileId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "fileId");
String sheetId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "sheetId");
// 如果Redis中没有则使用配置文件中的默认值
if (fileId == null || fileId.isEmpty()) {
fileId = tencentDocConfig.getFileId();
}
if (sheetId == null || sheetId.isEmpty()) {
sheetId = tencentDocConfig.getSheetId();
}
// 3. 验证配置是否完整
if (fileId == null || fileId.isEmpty() || sheetId == null || sheetId.isEmpty()) {
System.err.println("✗ H-TF订单文档配置不完整跳过自动写入。" +
"\n 提示:请通过 POST /jarvis/tencentDoc/config 接口配置文档ID和工作表ID" +
"\n fileId: " + (fileId != null && !fileId.isEmpty() ? fileId : "未配置") +
"\n sheetId: " + (sheetId != null && !sheetId.isEmpty() ? sheetId : "未配置"));
return;
}
// 4. 调用腾讯文档服务追加订单数据
com.alibaba.fastjson2.JSONObject result = tencentDocService.appendLogisticsToSheet(
accessToken, fileId, sheetId, order);
if (result != null) {
System.out.println("✓ H-TF订单已自动追加到腾讯文档" +
"\n 单号: " + order.getRemark() +
"\n 第三方单号: " + order.getThirdPartyOrderNo() +
"\n 文档: " + fileId + "/" + sheetId);
} else {
System.err.println("✗ 订单追加到腾讯文档失败 - 单号: " + order.getRemark() +
"\n API返回null请检查access-token是否有效或文档是否存在");
}
} catch (Exception e) {
// 写入失败不影响录单结果,仅记录错误日志
System.err.println("✗ 异步写入腾讯文档失败 - 单号: " + order.getRemark() +
"\n 错误: " + e.getMessage() +
"\n 提示:" +
"\n 1. 检查腾讯文档授权是否有效" +
"\n 2. 检查文档ID和工作表ID是否正确" +
"\n 3. 使用 GET /jarvis/tencentDoc/config/test 测试配置");
e.printStackTrace();
}
}, "TencentDoc-Writer-" + order.getRemark()).start();
}
} }