Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -16,6 +16,8 @@ import okhttp3.*;
|
||||
import com.srs.common.core.domain.AjaxResult; // ✅ RuoYi 的返回结果类
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -44,60 +46,77 @@ public class AiChatController extends BaseController {
|
||||
* Dify API的访问密钥
|
||||
* 用于身份验证,授权访问Dify服务
|
||||
*/
|
||||
private static final String DIFY_API_KEY = "app-2wjqcYI9n6igHTVHdH8qXlnh";
|
||||
private static final String DIFY_API_KEY = "app-BfPtBZDNBuHOS9K1PaZrxQYE";
|
||||
|
||||
/**
|
||||
* Dify API的URL地址
|
||||
* 用于发送聊天消息请求到Dify服务
|
||||
*/
|
||||
|
||||
private static final String DIFY_API_URL = "http://47.112.118.149:8100/v1/chat-messages";
|
||||
private static final String DIFY_API_URL = "http://172.16.129.101/v1:6100/v1/chat-messages";
|
||||
|
||||
/**
|
||||
* Dify反馈API的基础URL
|
||||
* 用于提交消息反馈(点赞、点踩等)
|
||||
*/
|
||||
private static final String DIFY_FEEDBACK_BASE_URL = "http://47.112.118.149:8100/v1/messages";
|
||||
private static final String DIFY_FEEDBACK_BASE_URL = "http://172.16.129.101/v1:6100/v1/messages";
|
||||
|
||||
/**
|
||||
* Dify获取反馈API的基础URL
|
||||
* 用于获取消息反馈(点赞、点踩等)
|
||||
*/
|
||||
private static final String DIFY_API_FEEDBACK_URL = "http://47.112.118.149:8100/v1/app/feedbacks?page=";
|
||||
private static final String DIFY_API_FEEDBACK_URL = "http://172.16.129.101/v1:6100/v1/app/feedbacks?page=";
|
||||
|
||||
/**
|
||||
* Dify消息历史记录API的基础URL
|
||||
* 用于获取消息历史记录
|
||||
*/
|
||||
private static final String DIFY_API_HISTORY_URL = "http://47.112.118.149:8100/v1/messages";
|
||||
private static final String DIFY_API_HISTORY_URL = "http://172.16.129.101/v1:6100/v1/messages";
|
||||
|
||||
/**
|
||||
* Dify会话API的基础URL
|
||||
* 用于获取会话列表
|
||||
*/
|
||||
private static final String DIFY_CONVERSATIONS_URL = "http://47.112.118.149:8100/v1/conversations";
|
||||
private static final String DIFY_CONVERSATIONS_URL = "http://172.16.129.101/v1:6100/v1/conversations";
|
||||
|
||||
/**
|
||||
* Dify文件上传API的URL地址
|
||||
* 用于上传文件到Dify服务,支持图文多模态理解
|
||||
*/
|
||||
private static final String DIFY_FILES_URL = "http://47.112.118.149:8100/v1/files/upload";
|
||||
private static final String DIFY_FILES_URL = "http://172.16.129.101/v1:6100/v1/files/upload";
|
||||
|
||||
/**
|
||||
* Redis中会话ID的key前缀
|
||||
*/
|
||||
private static final String CONVERSATION_ID_CACHE_PREFIX = "dify:conversation:id:";
|
||||
|
||||
/**
|
||||
* Redis中会话ID的过期时间(小时)
|
||||
*/
|
||||
private static final int CONVERSATION_ID_CACHE_EXPIRE_HOURS = 1;
|
||||
|
||||
/**
|
||||
* HTTP客户端实例
|
||||
* 配置了5分钟的读取超时时间,用于与Dify API进行通信
|
||||
*/
|
||||
private final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(Duration.ofSeconds(5)) // 建连超时
|
||||
.readTimeout(Duration.ofSeconds(12)) // 读超时(服务端应在此内有数据返回)
|
||||
.writeTimeout(Duration.ofSeconds(12)) // 写超时
|
||||
.connectTimeout(Duration.ofSeconds(5)) // 建连超时
|
||||
.readTimeout(Duration.ofSeconds(12)) // 读超时(服务端应在此内有数据返回)
|
||||
.writeTimeout(Duration.ofSeconds(12)) // 写超时
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Redis模板,用于缓存会话ID
|
||||
*/
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/** 为本次请求设置 “总超时”(含连接/读写),避免无限挂起 */
|
||||
private Response execWithTimeouts(Request req, int callSecs, int readSecs, int writeSecs) throws IOException {
|
||||
OkHttpClient shortClient = client.newBuilder()
|
||||
.callTimeout(java.time.Duration.ofSeconds(callSecs)) // 整次调用最大时长
|
||||
.readTimeout(java.time.Duration.ofSeconds(readSecs)) // 读超时(这段时间没有字节到达就超时)
|
||||
.callTimeout(java.time.Duration.ofSeconds(callSecs)) // 整次调用最大时长
|
||||
.readTimeout(java.time.Duration.ofSeconds(readSecs)) // 读超时(这段时间没有字节到达就超时)
|
||||
.writeTimeout(java.time.Duration.ofSeconds(writeSecs)) // 写超时
|
||||
.build();
|
||||
return shortClient.newCall(req).execute();
|
||||
@@ -173,13 +192,14 @@ public class AiChatController extends BaseController {
|
||||
* @param currentUsername
|
||||
* @throws IOException 当网络请求或IO操作失败时抛出
|
||||
*/
|
||||
private void sendToDifyAndStream(Map<String, Object> requestData, SseEmitter emitter, String currentUsername) throws IOException {
|
||||
private void sendToDifyAndStream(Map<String, Object> requestData, SseEmitter emitter, String currentUsername)
|
||||
throws IOException {
|
||||
|
||||
// 构建请求体参数
|
||||
Map<String, Object> bodyMap = new HashMap<>();
|
||||
bodyMap.put("query", requestData.get("query")); // 用户消息内容
|
||||
bodyMap.put("user", currentUsername); // 用户标识
|
||||
bodyMap.put("response_mode", "streaming"); // 设置为流式响应模式
|
||||
bodyMap.put("user", currentUsername); // 用户标识
|
||||
bodyMap.put("response_mode", "streaming"); // 设置为流式响应模式
|
||||
|
||||
// 如果请求中没有提供conversation_id,则尝试获取或创建一个
|
||||
if (!requestData.containsKey("conversation_id") || requestData.get("conversation_id") == null) {
|
||||
@@ -232,10 +252,10 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 构建HTTP请求
|
||||
Request httpRequest = new Request.Builder()
|
||||
.url(DIFY_API_URL) // 设置请求URL
|
||||
.url(DIFY_API_URL) // 设置请求URL
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY) // 添加认证头
|
||||
.addHeader("Content-Type", "application/json") // 设置内容类型
|
||||
.post(body) // 设置为POST请求
|
||||
.addHeader("Content-Type", "application/json") // 设置内容类型
|
||||
.post(body) // 设置为POST请求
|
||||
.build();
|
||||
|
||||
// 执行HTTP请求
|
||||
@@ -258,7 +278,8 @@ public class AiChatController extends BaseController {
|
||||
try (BufferedReader reader = new BufferedReader(httpResponse.body().charStream())) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty() || !line.startsWith("data:")) continue;
|
||||
if (line.isEmpty() || !line.startsWith("data:"))
|
||||
continue;
|
||||
String jsonData = line.substring(5).trim();
|
||||
if ("[DONE]".equals(jsonData)) {
|
||||
break;
|
||||
@@ -307,13 +328,14 @@ public class AiChatController extends BaseController {
|
||||
* {
|
||||
* "message_id": "msg-123",
|
||||
* "user": "user-1",
|
||||
* "rating": "like", // "like", "dislike", 或 null
|
||||
* "rating": "like", // "like", "dislike", 或 null
|
||||
* "content": "回答很好"
|
||||
* }
|
||||
* @return 统一响应结果
|
||||
*/
|
||||
@PostMapping("/feedback")
|
||||
public AjaxResult submitFeedback(@org.springframework.web.bind.annotation.RequestBody Map<String, Object> feedbackData) {
|
||||
public AjaxResult submitFeedback(
|
||||
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> feedbackData) {
|
||||
// 校验必要字段
|
||||
String messageId = (String) feedbackData.get("message_id");
|
||||
if (messageId == null || messageId.trim().isEmpty()) {
|
||||
@@ -349,16 +371,15 @@ public class AiChatController extends BaseController {
|
||||
// 构建请求体(发送给 Dify)
|
||||
Map<String, Object> bodyMap = new HashMap<>();
|
||||
bodyMap.put("user", user);
|
||||
bodyMap.put("rating", rating); // 可以是 "like", "dislike", 或 null
|
||||
bodyMap.put("content", content); // 可选文本反馈
|
||||
bodyMap.put("rating", rating); // 可以是 "like", "dislike", 或 null
|
||||
bodyMap.put("content", content); // 可选文本反馈
|
||||
|
||||
try {
|
||||
// 序列化为 JSON
|
||||
String jsonBody = mapper.writeValueAsString(bodyMap);
|
||||
okhttp3.RequestBody body = okhttp3.RequestBody.create(
|
||||
MediaType.get("application/json; charset=utf-8"),
|
||||
jsonBody
|
||||
);
|
||||
jsonBody);
|
||||
|
||||
// 调用 Dify API
|
||||
Request request = new Request.Builder()
|
||||
@@ -431,12 +452,19 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 提取反馈信息
|
||||
feedbackItem.put("id", feedbackNode.has("id") ? feedbackNode.get("id").asText() : null);
|
||||
feedbackItem.put("message_id", feedbackNode.has("message_id") ? feedbackNode.get("message_id").asText() : null);
|
||||
feedbackItem.put("rating", feedbackNode.has("rating") ? feedbackNode.get("rating").asText() : null);
|
||||
feedbackItem.put("content", feedbackNode.has("content") ? feedbackNode.get("content").asText() : null);
|
||||
feedbackItem.put("created_at", feedbackNode.has("created_at") ? feedbackNode.get("created_at").asLong() : null);
|
||||
feedbackItem.put("app_id", feedbackNode.has("app_id") ? feedbackNode.get("app_id").asText() : null);
|
||||
feedbackItem.put("conversation_id", feedbackNode.has("conversation_id") ? feedbackNode.get("conversation_id").asText() : null);
|
||||
feedbackItem.put("message_id",
|
||||
feedbackNode.has("message_id") ? feedbackNode.get("message_id").asText() : null);
|
||||
feedbackItem.put("rating",
|
||||
feedbackNode.has("rating") ? feedbackNode.get("rating").asText() : null);
|
||||
feedbackItem.put("content",
|
||||
feedbackNode.has("content") ? feedbackNode.get("content").asText() : null);
|
||||
feedbackItem.put("created_at",
|
||||
feedbackNode.has("created_at") ? feedbackNode.get("created_at").asLong() : null);
|
||||
feedbackItem.put("app_id",
|
||||
feedbackNode.has("app_id") ? feedbackNode.get("app_id").asText() : null);
|
||||
feedbackItem.put("conversation_id",
|
||||
feedbackNode.has("conversation_id") ? feedbackNode.get("conversation_id").asText()
|
||||
: null);
|
||||
|
||||
feedbackList.add(feedbackItem);
|
||||
}
|
||||
@@ -472,7 +500,7 @@ public class AiChatController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
// 权限标识为辅导员
|
||||
// 权限标识为辅导员
|
||||
@PreAuthorize("@ss.hasPermi('cph:teacher:list')")
|
||||
@GetMapping("/getMessagesToAdmin")
|
||||
public AjaxResult getMessagesToAdmin(@RequestParam String user,
|
||||
@@ -481,14 +509,24 @@ public class AiChatController extends BaseController {
|
||||
@RequestParam(defaultValue = "-updated_at") String sortBy) {
|
||||
|
||||
try {
|
||||
List<ConversationDTO> conversations = getConversations(user);
|
||||
// 首先尝试从Redis缓存中获取会话ID
|
||||
String conversationId = getCachedConversationId(user);
|
||||
|
||||
if (conversations == null || conversations.isEmpty()) {
|
||||
return AjaxResult.error("暂无会话记录");
|
||||
// 如果Redis缓存中没有会话ID,则使用原来的方法获取
|
||||
if (conversationId == null) {
|
||||
List<ConversationDTO> conversations = getConversations(user);
|
||||
|
||||
if (conversations == null || conversations.isEmpty()) {
|
||||
return AjaxResult.error("暂无会话记录");
|
||||
}
|
||||
|
||||
conversationId = conversations.get(0).getId();
|
||||
|
||||
// 将获取到的会话ID缓存到Redis中
|
||||
cacheConversationId(user, conversationId);
|
||||
}
|
||||
|
||||
String conversation_id = conversations.get(0).getId();
|
||||
Map<String, Object> result = getConversationHistoryMessages(conversation_id, user, firstId, limit);
|
||||
Map<String, Object> result = getConversationHistoryMessages(conversationId, user, firstId, limit);
|
||||
|
||||
AjaxResult successResult = success(result);
|
||||
return successResult;
|
||||
@@ -501,7 +539,7 @@ public class AiChatController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
// 权限标识为学生
|
||||
// 权限标识为学生
|
||||
@GetMapping("/getMessagesToUser")
|
||||
public AjaxResult getMessagesToUser(@RequestParam(required = false) String firstId,
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@@ -509,14 +547,25 @@ public class AiChatController extends BaseController {
|
||||
|
||||
try {
|
||||
String user = SecurityUtils.getLoginUser().getUsername();
|
||||
List<ConversationDTO> conversations = getConversations(user);
|
||||
|
||||
if (conversations == null || conversations.isEmpty()) {
|
||||
return AjaxResult.error("暂无会话记录");
|
||||
// 首先尝试从Redis缓存中获取会话ID
|
||||
String conversationId = getCachedConversationId(user);
|
||||
|
||||
// 如果Redis缓存中没有会话ID,则使用原来的方法获取
|
||||
if (conversationId == null) {
|
||||
List<ConversationDTO> conversations = getConversations(user);
|
||||
|
||||
if (conversations == null || conversations.isEmpty()) {
|
||||
return AjaxResult.error("暂无会话记录");
|
||||
}
|
||||
|
||||
conversationId = conversations.get(0).getId();
|
||||
|
||||
// 将获取到的会话ID缓存到Redis中
|
||||
cacheConversationId(user, conversationId);
|
||||
}
|
||||
|
||||
String conversation_id = conversations.get(0).getId();
|
||||
Map<String, Object> result = getConversationHistoryMessages(conversation_id, user, firstId, limit);
|
||||
Map<String, Object> result = getConversationHistoryMessages(conversationId, user, firstId, limit);
|
||||
|
||||
AjaxResult successResult = success(result);
|
||||
return successResult;
|
||||
@@ -529,11 +578,67 @@ public class AiChatController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Redis缓存中获取用户的会话ID
|
||||
*
|
||||
* @param user 用户名
|
||||
* @return 会话ID,如果不存在则返回null
|
||||
*/
|
||||
private String getCachedConversationId(String user) {
|
||||
try {
|
||||
// 参数校验
|
||||
if (user == null || user.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
user = user.trim();
|
||||
String cacheKey = CONVERSATION_ID_CACHE_PREFIX + user;
|
||||
|
||||
// 从Redis中获取会话ID
|
||||
String conversationId = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
return conversationId;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将会话ID与用户绑定并存储到Redis中
|
||||
*
|
||||
* @param user 用户名
|
||||
* @param conversationId 会话ID
|
||||
*/
|
||||
private void cacheConversationId(String user, String conversationId) {
|
||||
try {
|
||||
// 参数校验
|
||||
if (user == null || user.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversationId == null || conversationId.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
user = user.trim();
|
||||
conversationId = conversationId.trim();
|
||||
|
||||
// 将用户与会话ID绑定存储到Redis中
|
||||
String cacheKey = CONVERSATION_ID_CACHE_PREFIX + user;
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
cacheKey,
|
||||
conversationId,
|
||||
Duration.ofHours(CONVERSATION_ID_CACHE_EXPIRE_HOURS));
|
||||
} catch (Exception e) {
|
||||
System.out.println("绑定会话ID时发生错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取会话列表
|
||||
* */
|
||||
public List<ConversationDTO> getConversations(String user, String lastId, int limit, String sortBy) throws IOException {
|
||||
*/
|
||||
public List<ConversationDTO> getConversations(String user, String lastId, int limit, String sortBy)
|
||||
throws IOException {
|
||||
// 构建带查询参数的 URL
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(DIFY_CONVERSATIONS_URL).newBuilder();
|
||||
urlBuilder.addQueryParameter("user", user);
|
||||
@@ -558,7 +663,6 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 读取响应体
|
||||
String responseBodyString = response.body().string();
|
||||
System.out.println("Raw Response: " + responseBodyString); // 打印原始数据
|
||||
JsonNode rootNode = mapper.readTree(responseBodyString);
|
||||
|
||||
// 提取 data 数组
|
||||
@@ -599,8 +703,7 @@ public class AiChatController extends BaseController {
|
||||
return getConversations(user, null, limit, sortBy);
|
||||
}
|
||||
|
||||
|
||||
//获取历史消息
|
||||
// 获取历史消息
|
||||
private Map<String, Object> getConversationHistoryMessages(
|
||||
String conversationId,
|
||||
String user,
|
||||
@@ -688,11 +791,6 @@ public class AiChatController extends BaseController {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传接口
|
||||
* <p>
|
||||
@@ -706,7 +804,7 @@ public class AiChatController extends BaseController {
|
||||
*/
|
||||
@PostMapping("/files/upload")
|
||||
public AjaxResult uploadFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("user") String user) {
|
||||
@RequestParam("user") String user) {
|
||||
try {
|
||||
// 检查文件是否为空
|
||||
if (file.isEmpty()) {
|
||||
@@ -718,7 +816,7 @@ public class AiChatController extends BaseController {
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("user", user)
|
||||
.addFormDataPart("file", file.getOriginalFilename(),
|
||||
RequestBody.create(MediaType.parse("application/octet-stream"),file.getBytes()))
|
||||
RequestBody.create(MediaType.parse("application/octet-stream"), file.getBytes()))
|
||||
.build();
|
||||
|
||||
// 构建请求
|
||||
|
Reference in New Issue
Block a user