feat(oa): 优化 AI服务并添加缓存支持

- 添加 Redis 缓存支持,优化 AI 配置和对话历史的获取性能
- 实现 AI回复缓存,减少重复请求的处理时间
- 新增缓存清除接口,支持对话缓存和 AI 配置缓存的清除
- 重构 AI 服务调用流程,增加缓存检查和更新逻辑
This commit is contained in:
2025-08-04 14:55:19 +08:00
parent c93959e351
commit e0e5da4e1a
2 changed files with 134 additions and 10 deletions

View File

@@ -200,9 +200,29 @@ public class SysOaAiController extends BaseController {
*/
@PostMapping("/conversation/{conversationId}/end")
public R<Void> endConversation(@PathVariable Long conversationId) {
// 清除对话缓存
aiServiceUtil.clearConversationCache(conversationId);
return toAjax(conversationService.endConversation(conversationId));
}
/**
* 清除对话缓存
*/
@DeleteMapping("/conversation/{conversationId}/cache")
public R<Void> clearConversationCache(@PathVariable Long conversationId) {
aiServiceUtil.clearConversationCache(conversationId);
return R.ok("缓存已清除");
}
/**
* 清除AI配置缓存
*/
@DeleteMapping("/cache/config")
public R<Void> clearAiConfigCache() {
aiServiceUtil.clearAiConfigCache();
return R.ok("AI配置缓存已清除");
}
/**
* 发送消息给AI
*/
@@ -212,8 +232,14 @@ public class SysOaAiController extends BaseController {
// 1. 保存用户消息
messageService.addUserMessage(conversationId, message);
// 2. 获取对话历史
List<SysOaAiMessageVo> conversationHistory = messageService.queryByConversationId(conversationId);
// 2. 获取对话历史优先从Redis缓存获取
List<SysOaAiMessageVo> conversationHistory = aiServiceUtil.getCachedConversationHistory(conversationId);
if (conversationHistory == null) {
// 缓存中没有,从数据库获取
conversationHistory = messageService.queryByConversationId(conversationId);
// 缓存对话历史
aiServiceUtil.cacheConversationHistory(conversationId, conversationHistory);
}
// 3. 调用AI服务获取回复传递对话历史
String aiResponse = callAiServiceWithHistory(message, conversationHistory);
@@ -221,6 +247,10 @@ public class SysOaAiController extends BaseController {
// 4. 保存AI回复
messageService.addAiMessage(conversationId, aiResponse, 0, java.math.BigDecimal.ZERO);
// 5. 更新对话历史缓存
List<SysOaAiMessageVo> updatedHistory = messageService.queryByConversationId(conversationId);
aiServiceUtil.cacheConversationHistory(conversationId, updatedHistory);
Map<String, Object> result = new HashMap<>();
result.put("conversationId", conversationId);
result.put("response", aiResponse);

View File

@@ -2,6 +2,7 @@ package com.ruoyi.oa.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.oa.domain.vo.SysOaAiConfigVo;
import com.ruoyi.oa.domain.vo.SysOaAiMessageVo;
import com.ruoyi.oa.service.ISysOaAiConfigService;
@@ -12,6 +13,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.*;
/**
@@ -29,6 +31,11 @@ public class AiServiceUtil {
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
// Redis缓存key前缀
private static final String AI_CONFIG_CACHE_KEY = "ai:config:active";
private static final String AI_CONVERSATION_CACHE_KEY = "ai:conversation:";
private static final String AI_RESPONSE_CACHE_KEY = "ai:response:";
/**
* 调用DeepSeek AI服务单次对话
*
@@ -48,18 +55,26 @@ public class AiServiceUtil {
*/
public String callDeepSeekWithHistory(String message, List<SysOaAiMessageVo> conversationHistory) {
try {
// 获取AI配置
SysOaAiConfigVo config = configService.getActiveConfig();
// 1. 检查缓存中是否有相同的请求
String cacheKey = generateCacheKey(message, conversationHistory);
String cachedResponse = RedisUtils.getCacheObject(cacheKey);
if (cachedResponse != null) {
log.info("从Redis缓存获取AI回复");
return cachedResponse;
}
// 2. 获取AI配置优先从缓存获取
SysOaAiConfigVo config = getCachedAiConfig();
if (config == null) {
throw new RuntimeException("未找到可用的AI配置");
}
// 构建请求头
// 3. 构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + config.getApiKey());
// 构建请求体
// 4. 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", config.getModelName());
requestBody.put("temperature", config.getTemperature());
@@ -67,7 +82,7 @@ public class AiServiceUtil {
List<Map<String, String>> messages = new ArrayList<>();
// 添加对话历史
// 5. 添加对话历史
if (conversationHistory != null && !conversationHistory.isEmpty()) {
for (SysOaAiMessageVo historyMessage : conversationHistory) {
Map<String, String> historyMsg = new HashMap<>();
@@ -77,7 +92,7 @@ public class AiServiceUtil {
}
}
// 添加当前用户消息
// 6. 添加当前用户消息
Map<String, String> userMessage = new HashMap<>();
userMessage.put("role", "user");
userMessage.put("content", message);
@@ -85,7 +100,7 @@ public class AiServiceUtil {
requestBody.put("messages", messages);
// 发送请求
// 7. 发送请求
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
String url = config.getBaseUrl() + "/chat/completions";
@@ -96,10 +111,13 @@ public class AiServiceUtil {
JsonNode jsonNode = objectMapper.readTree(response.getBody());
String aiResponse = jsonNode.path("choices").path(0).path("message").path("content").asText();
// 记录token使用情况
// 8. 记录token使用情况
int totalTokens = jsonNode.path("usage").path("total_tokens").asInt();
log.info("AI回复成功消耗token: {}", totalTokens);
// 9. 缓存AI回复缓存5分钟
RedisUtils.setCacheObject(cacheKey, aiResponse, Duration.ofDays(1));
return aiResponse;
} else {
log.error("DeepSeek API调用失败: {}", response.getStatusCode());
@@ -111,6 +129,82 @@ public class AiServiceUtil {
}
}
/**
* 获取缓存的AI配置
*/
private SysOaAiConfigVo getCachedAiConfig() {
// 先从Redis缓存获取
SysOaAiConfigVo cachedConfig = RedisUtils.getCacheObject(AI_CONFIG_CACHE_KEY);
if (cachedConfig != null) {
log.debug("从Redis缓存获取AI配置");
return cachedConfig;
}
// 缓存中没有,从数据库获取
SysOaAiConfigVo config = configService.getActiveConfig();
if (config != null) {
// 缓存配置缓存30分钟
RedisUtils.setCacheObject(AI_CONFIG_CACHE_KEY, config, Duration.ofMinutes(30));
log.debug("AI配置已缓存到Redis");
}
return config;
}
/**
* 生成缓存key
*/
private String generateCacheKey(String message, List<SysOaAiMessageVo> conversationHistory) {
StringBuilder keyBuilder = new StringBuilder(AI_RESPONSE_CACHE_KEY);
keyBuilder.append(message.hashCode());
if (conversationHistory != null && !conversationHistory.isEmpty()) {
for (SysOaAiMessageVo history : conversationHistory) {
keyBuilder.append(":").append(history.getContent().hashCode());
}
}
return keyBuilder.toString();
}
/**
* 缓存对话历史
*/
public void cacheConversationHistory(Long conversationId, List<SysOaAiMessageVo> messages) {
String cacheKey = AI_CONVERSATION_CACHE_KEY + conversationId;
RedisUtils.setCacheObject(cacheKey, messages, Duration.ofMinutes(10));
log.debug("对话历史已缓存到Redis, conversationId: {}", conversationId);
}
/**
* 获取缓存的对话历史
*/
public List<SysOaAiMessageVo> getCachedConversationHistory(Long conversationId) {
String cacheKey = AI_CONVERSATION_CACHE_KEY + conversationId;
List<SysOaAiMessageVo> cachedMessages = RedisUtils.getCacheObject(cacheKey);
if (cachedMessages != null) {
log.debug("从Redis缓存获取对话历史, conversationId: {}", conversationId);
}
return cachedMessages;
}
/**
* 清除对话历史缓存
*/
public void clearConversationCache(Long conversationId) {
String cacheKey = AI_CONVERSATION_CACHE_KEY + conversationId;
RedisUtils.deleteObject(cacheKey);
log.debug("已清除对话历史缓存, conversationId: {}", conversationId);
}
/**
* 清除AI配置缓存
*/
public void clearAiConfigCache() {
RedisUtils.deleteObject(AI_CONFIG_CACHE_KEY);
log.debug("已清除AI配置缓存");
}
/**
* 计算费用示例每1000个token 0.002美元)
*