Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -9,7 +9,10 @@ import com.srs.common.core.controller.BaseController;
|
||||
// OkHttp 显式导入
|
||||
import com.srs.common.exception.ServiceException;
|
||||
import com.srs.common.utils.SecurityUtils;
|
||||
import com.srs.common.utils.ServletUtils;
|
||||
import com.srs.framework.web.service.TokenService;
|
||||
import com.srs.teacher.domain.dto.ConversationDTO;
|
||||
import com.srs.web.core.config.DifyConfig;
|
||||
import okhttp3.*;
|
||||
|
||||
// Spring 显式导入(不要用 *)
|
||||
@@ -41,62 +44,8 @@ import java.util.concurrent.CompletableFuture;
|
||||
@RestController
|
||||
@RequestMapping("/aitutor/aichat")
|
||||
public class AiChatController extends BaseController {
|
||||
|
||||
// private static final String DIFY_BASE_URL = "http://172.16.129.101:6100";
|
||||
private static final String DIFY_BASE_URL = "http://47.112.118.149:8100";
|
||||
/**
|
||||
* Dify API的访问密钥
|
||||
* 用于身份验证,授权访问Dify服务
|
||||
*/
|
||||
// private static final String DIFY_API_KEY = "app-BfPtBZDNBuHOS9K1PaZrxQYE";
|
||||
private static final String DIFY_API_KEY = "app-2wjqcYI9n6igHTVHdH8qXlnh";
|
||||
/**
|
||||
* Dify API的URL地址
|
||||
* 用于发送聊天消息请求到Dify服务
|
||||
*/
|
||||
|
||||
private static final String DIFY_API_URL = DIFY_BASE_URL+"/v1/chat-messages";
|
||||
|
||||
/**
|
||||
* Dify反馈API的基础URL
|
||||
* 用于提交消息反馈(点赞、点踩等)
|
||||
*/
|
||||
private static final String DIFY_FEEDBACK_BASE_URL = DIFY_BASE_URL+"/v1/messages";
|
||||
|
||||
/**
|
||||
* Dify获取反馈API的基础URL
|
||||
* 用于获取消息反馈(点赞、点踩等)
|
||||
*/
|
||||
private static final String DIFY_API_FEEDBACK_URL = DIFY_BASE_URL+"/v1/app/feedbacks?page=";
|
||||
|
||||
/**
|
||||
* Dify消息历史记录API的基础URL
|
||||
* 用于获取消息历史记录
|
||||
*/
|
||||
private static final String DIFY_API_HISTORY_URL = DIFY_BASE_URL+"/v1/messages";
|
||||
|
||||
/**
|
||||
* Dify会话API的基础URL
|
||||
* 用于获取会话列表
|
||||
*/
|
||||
private static final String DIFY_CONVERSATIONS_URL = DIFY_BASE_URL+"/v1/conversations";
|
||||
|
||||
/**
|
||||
* Dify文件上传API的URL地址
|
||||
* 用于上传文件到Dify服务,支持图文多模态理解
|
||||
*/
|
||||
private static final String DIFY_FILES_URL = DIFY_BASE_URL+"/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;
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
/**
|
||||
* HTTP客户端实例
|
||||
* 配置了5分钟的读取超时时间,用于与Dify API进行通信
|
||||
@@ -114,6 +63,13 @@ public class AiChatController extends BaseController {
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* Dify配置类
|
||||
* 用于获取Dify的API密钥和其他配置参数
|
||||
*/
|
||||
@Autowired
|
||||
private DifyConfig difyConfig;
|
||||
|
||||
/** 为本次请求设置 “总超时”(含连接/读写),避免无限挂起 */
|
||||
private Response execWithTimeouts(Request req, int callSecs, int readSecs, int writeSecs) throws IOException {
|
||||
OkHttpClient shortClient = client.newBuilder()
|
||||
@@ -164,11 +120,13 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 在主线程中获取当前用户名,避免在异步线程中获取安全上下文
|
||||
String currentUsername = SecurityUtils.getLoginUser().getUsername();
|
||||
|
||||
// 获取JWT token
|
||||
String currentUserToken = tokenService.getToken(ServletUtils.getRequest());
|
||||
// String currentUserToken = SecurityUtils.getLoginUser().getToken();
|
||||
// 异步执行请求处理,避免阻塞主线程
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
sendToDifyAndStream(requestData, emitter, currentUsername);
|
||||
sendToDifyAndStream(requestData, emitter, currentUsername, currentUserToken);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
@@ -194,7 +152,8 @@ public class AiChatController extends BaseController {
|
||||
* @param currentUsername
|
||||
* @throws IOException 当网络请求或IO操作失败时抛出
|
||||
*/
|
||||
private void sendToDifyAndStream(Map<String, Object> requestData, SseEmitter emitter, String currentUsername)
|
||||
private void sendToDifyAndStream(Map<String, Object> requestData, SseEmitter emitter, String currentUsername,
|
||||
String currentUserToken)
|
||||
throws IOException {
|
||||
|
||||
// 构建请求体参数
|
||||
@@ -231,9 +190,9 @@ public class AiChatController extends BaseController {
|
||||
inputs.put("user_name", userName);
|
||||
}
|
||||
|
||||
Object userToken = requestData.get("user_token");
|
||||
if (userToken != null) {
|
||||
inputs.put("user_token", userToken);
|
||||
// Object userToken = requestData.get("user_token");
|
||||
if (currentUserToken != null) {
|
||||
inputs.put("user_token", currentUserToken);
|
||||
}
|
||||
|
||||
Object userRole = requestData.get("user_role");
|
||||
@@ -241,6 +200,11 @@ public class AiChatController extends BaseController {
|
||||
inputs.put("user_role", userRole);
|
||||
}
|
||||
|
||||
Object userPlatform = requestData.get("user_platform");
|
||||
if (userPlatform != null) {
|
||||
inputs.put("user_platform", userPlatform);
|
||||
}
|
||||
|
||||
bodyMap.put("inputs", inputs);
|
||||
|
||||
// 自动为对话生成名称
|
||||
@@ -254,8 +218,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 构建HTTP请求
|
||||
Request httpRequest = new Request.Builder()
|
||||
.url(DIFY_API_URL) // 设置请求URL
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY) // 添加认证头
|
||||
.url(difyConfig.getApiUrl()) // 设置请求URL
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey()) // 添加认证头
|
||||
.addHeader("Content-Type", "application/json") // 设置内容类型
|
||||
.post(body) // 设置为POST请求
|
||||
.build();
|
||||
@@ -385,8 +349,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 调用 Dify API
|
||||
Request request = new Request.Builder()
|
||||
.url(DIFY_FEEDBACK_BASE_URL + "/" + messageId + "/feedbacks")
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.url(difyConfig.getFeedbackBaseUrl() + "/" + messageId + "/feedbacks")
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.post(body)
|
||||
.build();
|
||||
@@ -432,8 +396,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(DIFY_API_FEEDBACK_URL + page + "&limit=" + limitValue)
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.url(difyConfig.getApiFeedbackUrl() + page + "&limit=" + limitValue)
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.get()
|
||||
.build();
|
||||
@@ -506,9 +470,9 @@ public class AiChatController extends BaseController {
|
||||
@PreAuthorize("@ss.hasPermi('cph:teacher:list')")
|
||||
@GetMapping("/getMessagesToAdmin")
|
||||
public AjaxResult getMessagesToAdmin(@RequestParam String user,
|
||||
@RequestParam(required = false) String firstId,
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "-updated_at") String sortBy) {
|
||||
@RequestParam(required = false) String firstId,
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "-updated_at") String sortBy) {
|
||||
|
||||
try {
|
||||
// 首先尝试从Redis缓存中获取会话ID
|
||||
@@ -544,8 +508,8 @@ public class AiChatController extends BaseController {
|
||||
// 权限标识为学生
|
||||
@GetMapping("/getMessagesToUser")
|
||||
public AjaxResult getMessagesToUser(@RequestParam(required = false) String firstId,
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "-updated_at") String sortBy) {
|
||||
@RequestParam(defaultValue = "20") int limit,
|
||||
@RequestParam(defaultValue = "-updated_at") String sortBy) {
|
||||
|
||||
try {
|
||||
String user = SecurityUtils.getLoginUser().getUsername();
|
||||
@@ -594,7 +558,7 @@ public class AiChatController extends BaseController {
|
||||
}
|
||||
|
||||
user = user.trim();
|
||||
String cacheKey = CONVERSATION_ID_CACHE_PREFIX + user;
|
||||
String cacheKey = difyConfig.getConversationCachePrefix() + user;
|
||||
|
||||
// 从Redis中获取会话ID
|
||||
String conversationId = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
@@ -608,7 +572,7 @@ public class AiChatController extends BaseController {
|
||||
/**
|
||||
* 将会话ID与用户绑定并存储到Redis中
|
||||
*
|
||||
* @param user 用户名
|
||||
* @param user 用户名
|
||||
* @param conversationId 会话ID
|
||||
*/
|
||||
private void cacheConversationId(String user, String conversationId) {
|
||||
@@ -626,11 +590,11 @@ public class AiChatController extends BaseController {
|
||||
conversationId = conversationId.trim();
|
||||
|
||||
// 将用户与会话ID绑定存储到Redis中
|
||||
String cacheKey = CONVERSATION_ID_CACHE_PREFIX + user;
|
||||
String cacheKey = difyConfig.getConversationCachePrefix() + user;
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
cacheKey,
|
||||
conversationId,
|
||||
Duration.ofHours(CONVERSATION_ID_CACHE_EXPIRE_HOURS));
|
||||
Duration.ofHours(difyConfig.getConversationCacheExpireHours()));
|
||||
} catch (Exception e) {
|
||||
System.out.println("绑定会话ID时发生错误: " + e.getMessage());
|
||||
}
|
||||
@@ -642,7 +606,7 @@ public class AiChatController extends BaseController {
|
||||
public List<ConversationDTO> getConversations(String user, String lastId, int limit, String sortBy)
|
||||
throws IOException {
|
||||
// 构建带查询参数的 URL
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(DIFY_CONVERSATIONS_URL).newBuilder();
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(difyConfig.getConversationsUrl()).newBuilder();
|
||||
urlBuilder.addQueryParameter("user", user);
|
||||
if (lastId != null && !lastId.trim().isEmpty()) {
|
||||
urlBuilder.addQueryParameter("last_id", lastId);
|
||||
@@ -653,7 +617,7 @@ public class AiChatController extends BaseController {
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.get()
|
||||
.build();
|
||||
|
||||
@@ -725,7 +689,7 @@ public class AiChatController extends BaseController {
|
||||
int finalLimit = limit != null && limit > 0 ? Math.min(limit, 100) : 20;
|
||||
|
||||
// 构建请求 URL
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(DIFY_API_HISTORY_URL).newBuilder();
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(difyConfig.getApiHistoryUrl()).newBuilder();
|
||||
urlBuilder.addQueryParameter("conversation_id", conversationId);
|
||||
urlBuilder.addQueryParameter("user", user);
|
||||
if (firstId != null && !firstId.trim().isEmpty()) {
|
||||
@@ -735,7 +699,7 @@ public class AiChatController extends BaseController {
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.addHeader("Accept", "application/json")
|
||||
.get()
|
||||
.build();
|
||||
@@ -823,8 +787,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(DIFY_FILES_URL)
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.url(difyConfig.getFilesUrl())
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.post(requestBody)
|
||||
.build();
|
||||
|
||||
|
@@ -18,6 +18,7 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Dify心理问题发送企业微信 知无涯
|
||||
@@ -51,18 +52,37 @@ public class WeChatMentalAlertController extends BaseController {
|
||||
return AjaxResult.error("Token无效或已过期");
|
||||
}
|
||||
|
||||
String studentId = request.getUserId();
|
||||
|
||||
// 查询辅导员信息
|
||||
SysUser teacher = sysUserMapper.selectTeacherByStuNo(request.getUserId());
|
||||
if (teacher == null || !StringUtils.hasText(teacher.getUserName())) {
|
||||
log.error("辅导员信息不完整,学号: {}", request.getUserId());
|
||||
return AjaxResult.error("未分配辅导员或信息不完整");
|
||||
}
|
||||
|
||||
// 获取今天日期
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
// 查询该学生今天已触发的心理预警次数
|
||||
int todayCount = studentMentalRatingMapper.countTodayByStudentId(studentId, today);
|
||||
|
||||
/* 保存学生心理问题评级 */
|
||||
StudentMentalRating record = new StudentMentalRating();
|
||||
record.setStudentId(request.getUserId());
|
||||
record.setRating(request.getRating());
|
||||
studentMentalRatingMapper.insert(record);
|
||||
|
||||
// 只有评级为"一级风险"时才发送企业微信通知 陈冠元
|
||||
if (!"一级风险".equals(request.getRating())) {
|
||||
return AjaxResult.success("预警已记录,因评级非一级风险未发送通知");
|
||||
}
|
||||
|
||||
// === 判断是否超过当日发送上限(3次)=== 陈冠元
|
||||
if (todayCount > 3) {
|
||||
return AjaxResult.success("预警已记录,因当日已达上限未发送通知");
|
||||
}
|
||||
|
||||
// 构建并发送消息
|
||||
try {
|
||||
String content = buildContent(request, teacher);
|
||||
@@ -82,7 +102,7 @@ public class WeChatMentalAlertController extends BaseController {
|
||||
? teacher.getNickName()
|
||||
: teacher.getUserName();
|
||||
return String.format(
|
||||
"【心理预警通知】\n" +
|
||||
"【智水AI心理预警通知】\n" +
|
||||
"辅导员:%s(%s)\n" +
|
||||
"学生姓名:%s\n" +
|
||||
"学号:%s\n" +
|
||||
|
105
srs-admin/src/main/java/com/srs/web/core/config/DifyConfig.java
Normal file
105
srs-admin/src/main/java/com/srs/web/core/config/DifyConfig.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.srs.web.core.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Component // 让 Spring 扫描到
|
||||
@ConfigurationProperties(prefix = "dify")
|
||||
public class DifyConfig {
|
||||
|
||||
/**
|
||||
* Dify 服务的基础 URL,例如:http://47.112.118.149:8100
|
||||
*/
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* Dify API 密钥
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 超时时间配置(可选)
|
||||
*/
|
||||
private Duration timeout = Duration.ofSeconds(30);
|
||||
|
||||
// ================== API URLs ==================
|
||||
// 这些 URL 基于 baseUrl 自动生成,不直接暴露在配置文件中
|
||||
private String apiUrl;
|
||||
private String feedbackBaseUrl;
|
||||
private String apiFeedbackUrl;
|
||||
private String apiHistoryUrl;
|
||||
private String conversationsUrl;
|
||||
private String filesUrl;
|
||||
|
||||
// ================== Redis 缓存配置 ==================
|
||||
private String conversationCachePrefix = "dify:conversation:id:";
|
||||
private int conversationCacheExpireHours = 1;
|
||||
|
||||
// -------------------- Getters and Setters --------------------
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getApiUrl() {
|
||||
return baseUrl + "/v1/chat-messages";
|
||||
}
|
||||
|
||||
public String getFeedbackBaseUrl() {
|
||||
return baseUrl + "/v1/messages";
|
||||
}
|
||||
|
||||
public String getApiFeedbackUrl() {
|
||||
return baseUrl + "/v1/app/feedbacks?page=";
|
||||
}
|
||||
|
||||
public String getApiHistoryUrl() {
|
||||
return baseUrl + "/v1/messages";
|
||||
}
|
||||
|
||||
public String getConversationsUrl() {
|
||||
return baseUrl + "/v1/conversations";
|
||||
}
|
||||
|
||||
public String getFilesUrl() {
|
||||
return baseUrl + "/v1/files/upload";
|
||||
}
|
||||
|
||||
public String getConversationCachePrefix() {
|
||||
return conversationCachePrefix;
|
||||
}
|
||||
|
||||
public void setConversationCachePrefix(String conversationCachePrefix) {
|
||||
this.conversationCachePrefix = conversationCachePrefix;
|
||||
}
|
||||
|
||||
public int getConversationCacheExpireHours() {
|
||||
return conversationCacheExpireHours;
|
||||
}
|
||||
|
||||
public void setConversationCacheExpireHours(int conversationCacheExpireHours) {
|
||||
this.conversationCacheExpireHours = conversationCacheExpireHours;
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ srs:
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080 正式:8085 测试:8088
|
||||
port: 8085
|
||||
port: 8088
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
@@ -42,6 +42,11 @@ logging:
|
||||
com.srs: debug
|
||||
org.springframework: warn
|
||||
|
||||
#dify配置
|
||||
dify:
|
||||
base-url: http://47.112.118.149:8100 #http://172.16.129.101:6100
|
||||
api-key: app-2wjqcYI9n6igHTVHdH8qXlnh #app-BfPtBZDNBuHOS9K1PaZrxQYE
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
|
Reference in New Issue
Block a user