diff --git a/doc/录单自动写入腾讯文档-联动功能说明.md b/doc/录单自动写入腾讯文档-联动功能说明.md new file mode 100644 index 0000000..042c451 --- /dev/null +++ b/doc/录单自动写入腾讯文档-联动功能说明.md @@ -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; + +/** 文件ID(H-TF订单的目标文档ID) */ +private String fileId; + +/** 工作表ID(H-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 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 +**功能状态**:✅ 已实现并测试 + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java index 7928191..1933abe 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java @@ -41,6 +41,15 @@ public class TencentDocConfig { /** 刷新Token地址 */ private String refreshTokenUrl = "https://docs.qq.com/oauth/v2/token"; + /** 访问令牌(用于自动写入H-TF订单到腾讯文档) */ + private String accessToken; + + /** 文件ID(H-TF订单的目标文档ID) */ + private String fileId; + + /** 工作表ID(H-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; + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java index 22f32ef..f25ada6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/InstructionServiceImpl.java @@ -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(); + } }