Merge remote-tracking branch 'origin/main'

This commit is contained in:
MDSMO
2025-08-26 10:25:03 +08:00
34 changed files with 904 additions and 137 deletions

View File

@@ -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();

View File

@@ -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" +

View 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;
}
}

View File

@@ -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: