diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocConfigController.java new file mode 100644 index 0000000..91d19ce --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocConfigController.java @@ -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()); + } + } + + /** + * 更新配置(保存到Redis,180天有效期) + * + * @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不能为空"); + } + + // 保存到Redis(180天有效期) + 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", "配置更新成功,已保存到Redis(180天有效期)"); + 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); + } +} + 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 f25ada6..53aadf1 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 @@ -43,7 +43,11 @@ public class InstructionServiceImpl implements IInstructionService { @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 保持一致) 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"; @@ -1647,37 +1651,61 @@ private String handleTF(String input) { // 使用独立线程异步执行,避免阻塞录单流程 new Thread(() -> { try { - // 读取腾讯文档配置 - String accessToken = tencentDocConfig.getAccessToken(); - String fileId = tencentDocConfig.getFileId(); - String sheetId = tencentDocConfig.getSheetId(); + // Redis key前缀(用于获取文档配置) + final String REDIS_KEY_PREFIX = "tencent:doc:auto:config:"; - // 验证配置是否完整 - 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() ? "已配置" : "未配置")); + // 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("订单已自动追加到腾讯文档 - 单号: " + order.getRemark() + - ", 第三方单号: " + order.getThirdPartyOrderNo()); + System.out.println("✓ H-TF订单已自动追加到腾讯文档" + + "\n 单号: " + order.getRemark() + + "\n 第三方单号: " + order.getThirdPartyOrderNo() + + "\n 文档: " + fileId + "/" + sheetId); } else { - System.err.println("订单追加到腾讯文档失败 - 单号: " + order.getRemark() + - ", API返回null"); + System.err.println("✗ 订单追加到腾讯文档失败 - 单号: " + order.getRemark() + + "\n API返回null,请检查access-token是否有效或文档是否存在"); } } catch (Exception e) { // 写入失败不影响录单结果,仅记录错误日志 - System.err.println("异步写入腾讯文档失败 - 单号: " + order.getRemark() + - ", 错误: " + e.getMessage()); + 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();