diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/SocialMediaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/SocialMediaController.java index 247e52c..d405500 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/SocialMediaController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/SocialMediaController.java @@ -156,6 +156,50 @@ public class SocialMediaController extends BaseController } } + /** + * 获取大模型接入配置(API 地址、密钥等,与 Jarvis 共用 Redis) + */ + @GetMapping("/llm-config") + public AjaxResult getLlmConfig() + { + try { + return socialMediaService.getLlmConfig(); + } catch (Exception e) { + logger.error("获取大模型接入配置失败", e); + return AjaxResult.error("获取失败: " + e.getMessage()); + } + } + + /** + * 保存大模型接入配置 + */ + @Log(title = "保存大模型接入配置", businessType = BusinessType.UPDATE) + @PostMapping("/llm-config/save") + public AjaxResult saveLlmConfig(@RequestBody Map request) + { + try { + return socialMediaService.saveLlmConfig(request); + } catch (Exception e) { + logger.error("保存大模型接入配置失败", e); + return AjaxResult.error("保存失败: " + e.getMessage()); + } + } + + /** + * 清除大模型接入配置(Jarvis 使用 yml 默认 Ollama) + */ + @Log(title = "重置大模型接入配置", businessType = BusinessType.DELETE) + @DeleteMapping("/llm-config") + public AjaxResult resetLlmConfig() + { + try { + return socialMediaService.resetLlmConfig(); + } catch (Exception e) { + logger.error("重置大模型接入配置失败", e); + return AjaxResult.error("重置失败: " + e.getMessage()); + } + } + /** * 闲鱼文案(手动):根据标题+可选型号生成代下单、教你下单文案,不依赖JD接口 */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ISocialMediaService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ISocialMediaService.java index d00b3e0..2dba69e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ISocialMediaService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ISocialMediaService.java @@ -72,5 +72,20 @@ public interface ISocialMediaService * @return 包含代下单、教你下单两种文案的 Map */ Map generateXianyuWenan(String title, String remark); + + /** + * 获取社媒大模型接入配置(与 Jarvis 共用 Redis,供 OpenAI 兼容接口或 Ollama) + */ + com.ruoyi.common.core.domain.AjaxResult getLlmConfig(); + + /** + * 保存社媒大模型接入配置 + */ + com.ruoyi.common.core.domain.AjaxResult saveLlmConfig(Map request); + + /** + * 清除 Redis 中的接入配置,Jarvis 侧回退为 application.yml 默认 Ollama + */ + com.ruoyi.common.core.domain.AjaxResult resetLlmConfig(); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SocialMediaServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SocialMediaServiceImpl.java index 1892e80..031523a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SocialMediaServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SocialMediaServiceImpl.java @@ -52,6 +52,15 @@ public class SocialMediaServiceImpl implements ISocialMediaService put("content:both", "通用文案生成提示词模板\n占位符:%s - 商品名称,%s - 价格信息,%s - 关键词信息"); }}; + /** 与 Jarvis_java SocialMediaLlmClient 使用相同 Redis 键 */ + private static final String LLM_KEY_MODE = "social_media:llm:mode"; + private static final String LLM_KEY_BASE_URL = "social_media:llm:base_url"; + private static final String LLM_KEY_API_KEY = "social_media:llm:api_key"; + private static final String LLM_KEY_MODEL = "social_media:llm:model"; + + private static final String LLM_MODE_OLLAMA = "ollama"; + private static final String LLM_MODE_OPENAI = "openai"; + /** 闲鱼文案-通用基础说明 */ private static final String WENAN_BASE = "全新未拆封正品,包邮包安装,支持查验后再签收。\n" @@ -433,6 +442,121 @@ public class SocialMediaServiceImpl implements ISocialMediaService } } + @Override + public AjaxResult getLlmConfig() { + try { + Map data = new HashMap<>(); + if (redisTemplate == null) { + data.put("redisAvailable", false); + data.put("mode", LLM_MODE_OLLAMA); + data.put("baseUrl", ""); + data.put("model", ""); + data.put("hasApiKey", false); + data.put("apiKeyMasked", null); + return AjaxResult.success(data); + } + data.put("redisAvailable", true); + String modeRaw = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODE)); + String mode = StringUtils.isEmpty(modeRaw) ? LLM_MODE_OLLAMA : modeRaw.toLowerCase(); + data.put("mode", mode); + data.put("baseUrl", StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_BASE_URL))); + data.put("model", StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODEL))); + String rawKey = redisTemplate.opsForValue().get(LLM_KEY_API_KEY); + boolean hasKey = StringUtils.isNotEmpty(StringUtils.trim(rawKey)); + data.put("hasApiKey", hasKey); + data.put("apiKeyMasked", maskLlmApiKey(rawKey)); + return AjaxResult.success(data); + } catch (Exception e) { + log.error("读取大模型接入配置失败", e); + return AjaxResult.error("读取失败: " + e.getMessage()); + } + } + + private static String maskLlmApiKey(String secret) { + if (StringUtils.isEmpty(StringUtils.trim(secret))) { + return null; + } + String s = secret.trim(); + if (s.length() <= 4) { + return "****"; + } + return "****" + s.substring(s.length() - 4); + } + + @Override + public AjaxResult saveLlmConfig(Map request) { + try { + if (redisTemplate == null) { + return AjaxResult.error("Redis未配置,无法保存"); + } + String mode = request.get("mode") == null ? "" : request.get("mode").toString().trim().toLowerCase(); + if (!LLM_MODE_OLLAMA.equals(mode) && !LLM_MODE_OPENAI.equals(mode)) { + return AjaxResult.error("接入方式只能是 ollama 或 openai"); + } + String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim(); + String model = request.get("model") == null ? "" : request.get("model").toString().trim(); + + if (LLM_MODE_OPENAI.equals(mode)) { + if (StringUtils.isEmpty(baseUrl)) { + return AjaxResult.error("OpenAI 兼容模式须填写 Chat Completions 完整地址(如 https://api.openai.com/v1/chat/completions)"); + } + if (StringUtils.isEmpty(model)) { + return AjaxResult.error("OpenAI 兼容模式须填写模型名称"); + } + } + + boolean clearApiKey = Boolean.TRUE.equals(request.get("clearApiKey")) + || "true".equalsIgnoreCase(String.valueOf(request.get("clearApiKey"))); + if (clearApiKey) { + redisTemplate.delete(LLM_KEY_API_KEY); + } + + Object apiKeyObj = request.get("apiKey"); + if (!clearApiKey && apiKeyObj != null) { + String newKey = apiKeyObj.toString().trim(); + if (StringUtils.isNotEmpty(newKey)) { + redisTemplate.opsForValue().set(LLM_KEY_API_KEY, newKey); + } + } + + redisTemplate.opsForValue().set(LLM_KEY_MODE, mode); + if (StringUtils.isEmpty(baseUrl)) { + redisTemplate.delete(LLM_KEY_BASE_URL); + } else { + redisTemplate.opsForValue().set(LLM_KEY_BASE_URL, baseUrl); + } + if (StringUtils.isEmpty(model)) { + redisTemplate.delete(LLM_KEY_MODEL); + } else { + redisTemplate.opsForValue().set(LLM_KEY_MODEL, model); + } + + log.info("保存大模型接入配置成功, mode={}", mode); + return AjaxResult.success("保存成功"); + } catch (Exception e) { + log.error("保存大模型接入配置失败", e); + return AjaxResult.error("保存失败: " + e.getMessage()); + } + } + + @Override + public AjaxResult resetLlmConfig() { + try { + if (redisTemplate == null) { + return AjaxResult.error("Redis未配置"); + } + redisTemplate.delete(LLM_KEY_MODE); + redisTemplate.delete(LLM_KEY_BASE_URL); + redisTemplate.delete(LLM_KEY_API_KEY); + redisTemplate.delete(LLM_KEY_MODEL); + log.info("已清除大模型接入 Redis 配置"); + return AjaxResult.success("已恢复为 Jarvis 默认 Ollama 配置"); + } catch (Exception e) { + log.error("清除大模型接入配置失败", e); + return AjaxResult.error("清除失败: " + e.getMessage()); + } + } + /** * 根据标题(+可选型号备注)生成闲鱼文案(代下单、教你下单),不依赖JD接口 */