This commit is contained in:
2025-11-06 13:10:09 +08:00
parent 88234c13b2
commit b4b8f03a4f
2 changed files with 297 additions and 19 deletions

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

@@ -43,7 +43,11 @@ public class InstructionServiceImpl implements IInstructionService {
@Resource @Resource
private com.ruoyi.jarvis.service.ITencentDocService tencentDocService; private com.ruoyi.jarvis.service.ITencentDocService tencentDocService;
@Resource @Resource
private com.ruoyi.jarvis.service.ITencentDocTokenService tencentDocTokenService;
@Resource
private com.ruoyi.jarvis.config.TencentDocConfig tencentDocConfig; 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";
@@ -1647,37 +1651,61 @@ private String handleTF(String input) {
// 使用独立线程异步执行,避免阻塞录单流程 // 使用独立线程异步执行,避免阻塞录单流程
new Thread(() -> { new Thread(() -> {
try { try {
// 读取腾讯文档配置 // Redis key前缀用于获取文档配置
String accessToken = tencentDocConfig.getAccessToken(); final String REDIS_KEY_PREFIX = "tencent:doc:auto:config:";
String fileId = tencentDocConfig.getFileId();
String sheetId = tencentDocConfig.getSheetId();
// 验证配置是否完整 // 1. 从Token服务获取access-token会自动从Redis读取并刷新
if (accessToken == null || accessToken.isEmpty() || String accessToken = null;
fileId == null || fileId.isEmpty() || try {
sheetId == null || sheetId.isEmpty()) { accessToken = tencentDocTokenService.getValidAccessToken();
System.err.println("腾讯文档配置不完整跳过自动写入。请检查配置accessToken=" + } catch (Exception e) {
(accessToken != null && !accessToken.isEmpty() ? "已配置" : "未配置") + System.err.println("✗ 无法获取腾讯文档访问令牌 - " + e.getMessage());
", fileId=" + (fileId != null && !fileId.isEmpty() ? "已配置" : "未配置") + System.err.println(" 提示:请先完成腾讯文档授权,访问 /jarvis/tendoc/authUrl 获取授权URL");
", sheetId=" + (sheetId != null && !sheetId.isEmpty() ? "已配置" : "未配置"));
return; 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( com.alibaba.fastjson2.JSONObject result = tencentDocService.appendLogisticsToSheet(
accessToken, fileId, sheetId, order); accessToken, fileId, sheetId, order);
if (result != null) { if (result != null) {
System.out.println("订单已自动追加到腾讯文档 - 单号: " + order.getRemark() + System.out.println("✓ H-TF订单已自动追加到腾讯文档" +
", 第三方单号: " + order.getThirdPartyOrderNo()); "\n 单号: " + order.getRemark() +
"\n 第三方单号: " + order.getThirdPartyOrderNo() +
"\n 文档: " + fileId + "/" + sheetId);
} else { } else {
System.err.println("订单追加到腾讯文档失败 - 单号: " + order.getRemark() + System.err.println("订单追加到腾讯文档失败 - 单号: " + order.getRemark() +
", API返回null"); "\n API返回null请检查access-token是否有效或文档是否存在");
} }
} catch (Exception e) { } catch (Exception e) {
// 写入失败不影响录单结果,仅记录错误日志 // 写入失败不影响录单结果,仅记录错误日志
System.err.println("异步写入腾讯文档失败 - 单号: " + order.getRemark() + System.err.println("异步写入腾讯文档失败 - 单号: " + order.getRemark() +
", 错误: " + e.getMessage()); "\n 错误: " + e.getMessage() +
"\n 提示:" +
"\n 1. 检查腾讯文档授权是否有效" +
"\n 2. 检查文档ID和工作表ID是否正确" +
"\n 3. 使用 GET /jarvis/tencentDoc/config/test 测试配置");
e.printStackTrace(); e.printStackTrace();
} }
}, "TencentDoc-Writer-" + order.getRemark()).start(); }, "TencentDoc-Writer-" + order.getRemark()).start();