diff --git a/srs-admin/src/main/java/com/srs/web/controller/aitutor/AiChatController.java b/srs-admin/src/main/java/com/srs/web/controller/aitutor/AiChatController.java new file mode 100644 index 0000000..5f5ea23 --- /dev/null +++ b/srs-admin/src/main/java/com/srs/web/controller/aitutor/AiChatController.java @@ -0,0 +1,743 @@ +package com.srs.web.controller.aitutor; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.srs.common.core.controller.BaseController; + +// OkHttp 显式导入 +import com.srs.common.exception.ServiceException; +import com.srs.common.utils.SecurityUtils; +import com.srs.teacher.domain.dto.ConversationDTO; +import okhttp3.*; + +// Spring 显式导入(不要用 *) +import com.srs.common.core.domain.AjaxResult; // ✅ RuoYi 的返回结果类 +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.BufferedReader; +import java.io.IOException; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +/** + * Dify聊天控制器 + *

+ * 该控制器用于处理与Dify AI聊天服务的通信,提供流式聊天功能。 + * Dify是一个LLM应用开发平台,此控制器通过调用其API实现与AI模型的交互。 + *

+ */ +@RestController +@RequestMapping("/aitutor/aichat") +public class AiChatController extends BaseController { + + /** + * Dify API的访问密钥 + * 用于身份验证,授权访问Dify服务 + */ + private static final String DIFY_API_KEY = "app-2wjqcYI9n6igHTVHdH8qXlnh"; + + /** + * Dify API的URL地址 + * 用于发送聊天消息请求到Dify服务 + */ + + private static final String DIFY_API_URL = "http://47.112.118.149:8100/v1/chat-messages"; + + /** + * Dify反馈API的基础URL + * 用于提交消息反馈(点赞、点踩等) + */ + private static final String DIFY_FEEDBACK_BASE_URL = "http://47.112.118.149:8100/v1/messages"; + + /** + * Dify获取反馈API的基础URL + * 用于获取消息反馈(点赞、点踩等) + */ + private static final String DIFY_API_FEEDBACK_URL = "http://47.112.118.149:8100/v1/app/feedbacks?page="; + + /** + * Dify消息历史记录API的基础URL + * 用于获取消息历史记录 + */ + private static final String DIFY_API_HISTORY_URL = "http://47.112.118.149:8100/v1/messages"; + + /** + * Dify会话API的基础URL + * 用于获取会话列表 + */ + private static final String DIFY_CONVERSATIONS_URL = "http://47.112.118.149:8100/v1/conversations"; + + /** + * Dify文件上传API的URL地址 + * 用于上传文件到Dify服务,支持图文多模态理解 + */ + private static final String DIFY_FILES_URL = "http://47.112.118.149:8100/v1/files/upload"; + + /** + * HTTP客户端实例 + * 配置了5分钟的读取超时时间,用于与Dify API进行通信 + */ + private final OkHttpClient client = new OkHttpClient.Builder() + .readTimeout(Duration.ofMinutes(5)) + .build(); + + /** + * JSON对象映射器 + * 用于处理JSON数据的序列化和反序列化 + */ + private final ObjectMapper mapper = new ObjectMapper(); + + /** + * 处理流式聊天请求的端点 + *

+ * 该方法接收客户端发送的聊天请求,并通过SSE(Server-Sent Events)方式将响应流式传输回客户端。 + * 使用异步处理避免阻塞主线程,提高系统并发处理能力。 + *

+ * + * @param requestData 包含聊天请求数据的Map,应包含以下字段: + * - query: 用户的聊天消息内容(必需) + * - user: 用户标识符(必需) + * - conversation_id: 对话ID,用于维持对话上下文(可选) + * - inputs: 输入参数,用于传递额外的上下文信息(可选) + * @return SseEmitter 用于向客户端流式传输响应的SSE发射器 + */ + @PostMapping("/stream") + public SseEmitter stream(@org.springframework.web.bind.annotation.RequestBody Map requestData) { + + SseEmitter emitter = new SseEmitter(300_000L); + + // 设置超时处理回调 + emitter.onTimeout(() -> { + System.out.println("!!! SSE连接超时 !!!"); + emitter.complete(); + }); + + // 设置错误处理回调 + emitter.onError(ex -> { + System.out.println("!!! SSE连接发生错误 !!!"); + ex.printStackTrace(); + emitter.completeWithError(ex); + }); + + // 在主线程中获取当前用户名,避免在异步线程中获取安全上下文 + String currentUsername = SecurityUtils.getLoginUser().getUsername(); + + // 异步执行请求处理,避免阻塞主线程 + CompletableFuture.runAsync(() -> { + try { + sendToDifyAndStream(requestData, emitter, currentUsername); + } catch (Exception e) { + e.printStackTrace(); + try { + emitter.send(SseEmitter.event().name("error").data("服务器内部错误: " + e.getMessage())); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + emitter.completeWithError(e); + } + }); + return emitter; + } + + /** + * 发送请求到Dify并流式传输响应 + *

+ * 该方法构建请求参数,调用Dify API,并处理返回的流式响应数据。 + * 根据Dify API返回的不同事件类型,将数据通过SSE发送给客户端。 + *

+ * + * @param requestData 包含聊天请求数据的Map,包含用户消息、用户标识等信息 + * @param emitter 用于向客户端发送SSE事件的发射器 + * @param currentUsername + * @throws IOException 当网络请求或IO操作失败时抛出 + */ + private void sendToDifyAndStream(Map requestData, SseEmitter emitter, String currentUsername) throws IOException { + + // 构建请求体参数 + Map bodyMap = new HashMap<>(); + bodyMap.put("query", requestData.get("query")); // 用户消息内容 + bodyMap.put("user", currentUsername); // 用户标识 + bodyMap.put("response_mode", "streaming"); // 设置为流式响应模式 + + // 如果请求中没有提供conversation_id,则尝试获取或创建一个 + if (!requestData.containsKey("conversation_id") || requestData.get("conversation_id") == null) { + try { + List conversations = getConversations(currentUsername, null, 1, "-updated_at"); + if (conversations != null && !conversations.isEmpty()) { + // 使用已有的会话ID + bodyMap.put("conversation_id", conversations.get(0).getId()); + } + // 如果没有会话,Dify会自动创建一个新的会话 + } catch (Exception e) { + System.out.println("获取用户会话时出错: " + e.getMessage()); + // 继续执行,让Dify自动创建新会话 + } + } else { + // 如果存在对话ID,则添加到请求参数中 + bodyMap.put("conversation_id", requestData.get("conversation_id")); + } + + // 添加输入参数,默认为空HashMap + Map inputs = (Map) requestData.getOrDefault("inputs", new HashMap<>()); + + inputs.put("user_id", currentUsername); + + Object userName = requestData.get("user_name"); + if (userName != null) { + inputs.put("user_name", userName); + } + + Object userToken = requestData.get("user_token"); + if (userToken != null) { + inputs.put("user_token", userToken); + } + + Object userRole = requestData.get("user_role"); + if (userRole != null) { + inputs.put("user_role", userRole); + } + + bodyMap.put("inputs", inputs); + + // 自动为对话生成名称 + bodyMap.put("auto_generate_name", false); + + // 将请求参数转换为JSON字符串 + String jsonBody = mapper.writeValueAsString(bodyMap); + + // 创建请求体对象 + RequestBody body = RequestBody.create(MediaType.get("application/json; charset=utf-8"), jsonBody); + + // 构建HTTP请求 + Request httpRequest = new Request.Builder() + .url(DIFY_API_URL) // 设置请求URL + .addHeader("Authorization", "Bearer " + DIFY_API_KEY) // 添加认证头 + .addHeader("Content-Type", "application/json") // 设置内容类型 + .post(body) // 设置为POST请求 + .build(); + + // 执行HTTP请求 + try (Response httpResponse = client.newCall(httpRequest).execute()) { + // 检查响应是否成功 + if (!httpResponse.isSuccessful()) { + String errorMsg = "Dify 请求失败: " + httpResponse.code() + " " + httpResponse.message(); + System.out.println(errorMsg); + try { + httpResponse.body().string(); + } catch (Exception e) { + System.out.println("无法读取响应体: " + e.getMessage()); + } + emitter.send(SseEmitter.event().name("error").data(errorMsg)); + emitter.complete(); + return; + } + + // 读取响应流数据 + try (BufferedReader reader = new BufferedReader(httpResponse.body().charStream())) { + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty() || !line.startsWith("data:")) continue; + String jsonData = line.substring(5).trim(); + if ("[DONE]".equals(jsonData)) { + break; + } + + // 解析JSON数据 + JsonNode node = mapper.readTree(jsonData); + String eventType = node.get("event").asText(); + + // 根据事件类型处理不同的响应,直接转发原始数据 + switch (eventType) { + case "workflow_started": + case "node_started": + case "node_finished": + case "message_start": + case "message": + case "message_end": + case "error": + case "ping": + // 直接转发Dify的原始SSE数据格式 + emitter.send(SseEmitter.event().data(jsonData)); + break; + default: + break; + } + } + } + // 完成SSE流传输 + emitter.complete(); + } catch (Exception e) { + // 处理异常情况 + e.printStackTrace(); + emitter.completeWithError(e); + } + } + + /** + * 提交消息反馈(点赞、点踩、撤销、文本反馈) + *

+ * 该接口代理前端调用 Dify 的反馈 API,避免在前端暴露 API Key。 + * 支持:'like', 'dislike', 或 null(撤销) + *

+ * + * @param feedbackData 包含 feedback 信息的 JSON 对象 + * 示例: + * { + * "message_id": "msg-123", + * "user": "user-1", + * "rating": "like", // "like", "dislike", 或 null + * "content": "回答很好" + * } + * @return 统一响应结果 + */ + @PostMapping("/feedback") + public AjaxResult submitFeedback(@org.springframework.web.bind.annotation.RequestBody Map feedbackData) { + // 校验必要字段 + String messageId = (String) feedbackData.get("message_id"); + if (messageId == null || messageId.trim().isEmpty()) { + return AjaxResult.error("message_id 不能为空"); + } + + // 获取 user(必填,建议前端传) + Object userObj = feedbackData.get("user"); + if (userObj == null || userObj.toString().trim().isEmpty()) { + return AjaxResult.error("user 不能为空"); + } + String user = userObj.toString(); + + // 处理 rating:支持 "like", "dislike", null(撤销) + Object ratingObj = feedbackData.get("rating"); + String rating = null; + + if (ratingObj != null) { + String raw = ratingObj.toString(); + if ("like".equals(raw)) { + rating = "like"; + } else if ("dislike".equals(raw)) { + rating = "dislike"; + } else { + return AjaxResult.error("rating 必须是 'like'、'dislike' 或 null(撤销)"); + } + } + // 如果 ratingObj 为 null,rating 保持 null,表示撤销 + + // 可选 content + String content = (String) feedbackData.get("content"); + + // 构建请求体(发送给 Dify) + Map bodyMap = new HashMap<>(); + bodyMap.put("user", user); + 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 + ); + + // 调用 Dify API + Request request = new Request.Builder() + .url(DIFY_FEEDBACK_BASE_URL + "/" + messageId + "/feedbacks") + .addHeader("Authorization", "Bearer " + DIFY_API_KEY) + .addHeader("Content-Type", "application/json") + .post(body) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful()) { + return AjaxResult.success("反馈提交成功"); + } else { + String errorMsg = "Dify 反馈失败: " + response.code(); + try (ResponseBody errorBody = response.body()) { + if (errorBody != null) { + errorMsg += " - " + errorBody.string(); + } + } catch (IOException e) { + errorMsg += " (无法读取错误详情)"; + } + return AjaxResult.error(errorMsg); + } + } + } catch (Exception e) { + return AjaxResult.error("提交反馈失败: " + e.getMessage()); + } + } + + /** + * 获取APP的消息点赞和反馈列表 + *

+ * 该接口用于获取整个Dify应用的终端用户反馈、点赞列表,类似Dify的日志界面 + *

+ * + * @param page 页码,默认值:1 + * @param limit 每页数量,默认值:20,最大100 + * @return 包含点赞、反馈列表的统一响应结果 + */ + @GetMapping("/app/feedbacks") + public AjaxResult getAppFeedbacks( + @RequestParam(value = "page", defaultValue = "1") String page, + @RequestParam(value = "limit", defaultValue = "20") String limit) { + + try { + // 参数校验和限制 + int limitValue = Math.min(Math.max(Integer.parseInt(limit), 1), 100); + + // 构建请求 + Request request = new Request.Builder() + .url(DIFY_API_FEEDBACK_URL + page + "&limit=" + limitValue) + .addHeader("Authorization", "Bearer " + DIFY_API_KEY) + .addHeader("Content-Type", "application/json") + .get() + .build(); + + // 发送请求 + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful()) { + String responseBody = response.body().string(); + JsonNode rootNode = mapper.readTree(responseBody); + + // 解析数据 + JsonNode dataNode = rootNode.get("data"); + List> feedbackList = new ArrayList<>(); + + if (dataNode != null && dataNode.isArray()) { + for (JsonNode feedbackNode : dataNode) { + Map feedbackItem = new HashMap<>(); + + // 提取反馈信息 + 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); + + feedbackList.add(feedbackItem); + } + } + + // 直接返回数组形式的数据 + return AjaxResult.success(feedbackList); + } else { + String errorMsg = "获取反馈列表失败: " + response.code() + " " + response.message(); + try (ResponseBody errorBody = response.body()) { + if (errorBody != null) { + String errorBodyString = errorBody.string(); + errorMsg += " - 响应体: " + errorBodyString; + + // 尝试解析响应体中的错误信息 + try { + JsonNode errorNode = mapper.readTree(errorBodyString); + if (errorNode.has("message")) { + errorMsg += " - 错误详情: " + errorNode.get("message").asText(); + } + } catch (Exception e) { + // 如果无法解析为JSON,就保持原始响应体 + } + } + } catch (IOException e) { + errorMsg += " (无法读取错误详情): " + e.getMessage(); + } + return AjaxResult.error(errorMsg); + } + } + } catch (Exception e) { + return AjaxResult.error("获取反馈列表时发生异常: " + e.getMessage()); + } + } + + // 权限标识为辅导员 + @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) { + + try { + List conversations = getConversations(user); + + if (conversations == null || conversations.isEmpty()) { + return AjaxResult.error("暂无会话记录"); + } + + String conversation_id = conversations.get(0).getId(); + Map result = getConversationHistoryMessages(conversation_id, user, firstId, limit); + + AjaxResult successResult = success(result); + return successResult; + } catch (IOException e) { + e.printStackTrace(); + return AjaxResult.error("网络请求失败,请稍后重试"); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("请求处理失败: " + e.getMessage()); + } + } + + // 权限标识为学生 + @GetMapping("/getMessagesToUser") + public AjaxResult getMessagesToUser(@RequestParam(required = false) String firstId, + @RequestParam(defaultValue = "20") int limit, + @RequestParam(defaultValue = "-updated_at") String sortBy) { + + try { + String user = SecurityUtils.getLoginUser().getUsername(); + List conversations = getConversations(user); + + if (conversations == null || conversations.isEmpty()) { + return AjaxResult.error("暂无会话记录"); + } + + String conversation_id = conversations.get(0).getId(); + Map result = getConversationHistoryMessages(conversation_id, user, firstId, limit); + + AjaxResult successResult = success(result); + return successResult; + } catch (IOException e) { + e.printStackTrace(); + return AjaxResult.error("网络请求失败,请稍后重试"); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("请求处理失败: " + e.getMessage()); + } + } + + + /* + * 获取会话列表 + * */ + public List 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); + if (lastId != null && !lastId.trim().isEmpty()) { + urlBuilder.addQueryParameter("last_id", lastId); + } + urlBuilder.addQueryParameter("limit", String.valueOf(limit)); + urlBuilder.addQueryParameter("sort_by", sortBy); + + // 构建请求 + Request request = new Request.Builder() + .url(urlBuilder.build()) + .addHeader("Authorization", "Bearer " + DIFY_API_KEY) + .get() + .build(); + + // 执行请求 + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Dify API 请求失败: " + response.code() + " " + response.message()); + } + + // 读取响应体 + String responseBodyString = response.body().string(); + System.out.println("Raw Response: " + responseBodyString); // 打印原始数据 + JsonNode rootNode = mapper.readTree(responseBodyString); + + // 提取 data 数组 + JsonNode dataArray = rootNode.path("data"); + List conversations = new ArrayList<>(); + + if (dataArray.isArray()) { + for (JsonNode node : dataArray) { + ConversationDTO dto = new ConversationDTO(); + dto.setId(node.path("id").asText(null)); + dto.setName(node.path("name").asText(null)); + dto.setIntroduction(node.path("introduction").asText(null)); + + // 处理时间戳(假设接口返回的是 Unix 时间戳,单位:秒) + long createdAt = node.path("created_at").asLong(0L); + long updatedAt = node.path("updated_at").asLong(0L); + + dto.setCreated_at(createdAt > 0 ? new Date(createdAt * 1000) : null); + dto.setUpdated_at(updatedAt > 0 ? new Date(updatedAt * 1000) : null); + + conversations.add(dto); + } + } + + return conversations; + } + } + + public List getConversations(String user) throws IOException { + return getConversations(user, null, 1, "-updated_at"); + } + + public List getConversations(String user, String lastId) throws IOException { + return getConversations(user, lastId, 0, null); + } + + public List getConversations(String user, Integer limit, String sortBy) throws IOException { + return getConversations(user, null, limit, sortBy); + } + + + //获取历史消息 + private Map getConversationHistoryMessages( + String conversationId, + String user, + String firstId, + Integer limit) throws ServiceException { + + // 参数校验 + if (conversationId == null || conversationId.trim().isEmpty()) { + throw new IllegalArgumentException("conversationId 不能为空"); + } + if (user == null || user.trim().isEmpty()) { + throw new IllegalArgumentException("user 不能为空"); + } + + conversationId = conversationId.trim(); + user = user.trim(); + int finalLimit = limit != null && limit > 0 ? Math.min(limit, 100) : 20; + + // 构建请求 URL + HttpUrl.Builder urlBuilder = HttpUrl.parse(DIFY_API_HISTORY_URL).newBuilder(); + urlBuilder.addQueryParameter("conversation_id", conversationId); + urlBuilder.addQueryParameter("user", user); + if (firstId != null && !firstId.trim().isEmpty()) { + urlBuilder.addQueryParameter("first_id", firstId.trim()); + } + urlBuilder.addQueryParameter("limit", String.valueOf(finalLimit)); + + Request request = new Request.Builder() + .url(urlBuilder.build()) + .addHeader("Authorization", "Bearer " + DIFY_API_KEY) + .addHeader("Accept", "application/json") + .get() + .build(); + + try (Response response = client.newCall(request).execute()) { + + if (!response.isSuccessful()) { + String body = response.body() != null ? response.body().string() : "No body"; + String msg = String.format("HTTP %d %s - %s", response.code(), response.message(), body); + throw new ServiceException("请求失败"); + } + + ResponseBody responseBody = response.body(); + if (responseBody == null) { + return emptyResult(finalLimit); + } + + String responseBodyString = responseBody.string(); + JsonNode rootNode; + try { + rootNode = mapper.readTree(responseBodyString); + } catch (JsonProcessingException e) { + throw new ServiceException("响应数据格式错误"); + } + + // 提取 data 数组 + List> data = new ArrayList<>(); + JsonNode dataArray = rootNode.get("data"); + if (dataArray != null && dataArray.isArray()) { + data = mapper.convertValue(dataArray, new TypeReference>>() { + }); + } + + // 提取 has_more + boolean hasMore = rootNode.path("has_more").asBoolean(false); + + // ✅ 直接构建前端需要的返回结构 + Map result = new HashMap<>(); + result.put("data", data); + result.put("has_more", hasMore); + result.put("limit", finalLimit); + return result; // 直接返回,调用方直接丢给前端 + + } catch (IOException e) { + throw new ServiceException("网络请求失败: " + e.getMessage()); + } + } + + // 辅助方法:返回空结果 + private Map emptyResult(int limit) { + Map result = new HashMap<>(); + result.put("data", new ArrayList<>()); + result.put("has_more", false); + result.put("limit", limit); + return result; + } + + + + + + + /** + * 文件上传接口 + *

+ * 上传文件并在发送消息时使用,可实现图文多模态理解。支持应用程序所支持的所有格式。 + * 上传的文件仅供当前终端用户使用。 + *

+ * + * @param file 要上传的文件 + * @param user 用户标识,用于定义终端用户的身份 + * @return 包含文件信息的统一响应结果 + */ + @PostMapping("/files/upload") + public AjaxResult uploadFile(@RequestParam("file") MultipartFile file, + @RequestParam("user") String user) { + try { + // 检查文件是否为空 + if (file.isEmpty()) { + return AjaxResult.error("文件不能为空"); + } + + // 创建MultipartBody以上传文件 + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("user", user) + .addFormDataPart("file", file.getOriginalFilename(), + RequestBody.create(MediaType.parse("application/octet-stream"),file.getBytes())) + .build(); + + // 构建请求 + Request request = new Request.Builder() + .url(DIFY_FILES_URL) + .addHeader("Authorization", "Bearer " + DIFY_API_KEY) + .post(requestBody) + .build(); + + // 发送请求 + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful()) { + String responseBody = response.body().string(); + JsonNode rootNode = mapper.readTree(responseBody); + + // 返回成功结果 + return AjaxResult.success("文件上传成功", mapper.convertValue(rootNode, Map.class)); + } else { + String errorMsg = "文件上传失败: " + response.code() + " " + response.message(); + try (ResponseBody errorBody = response.body()) { + if (errorBody != null) { + errorMsg += " - " + errorBody.string(); + } + } catch (IOException e) { + errorMsg += " (无法读取错误详情)"; + } + return AjaxResult.error(errorMsg); + } + } + } catch (Exception e) { + return AjaxResult.error("文件上传异常: " + e.getMessage()); + } + } +} diff --git a/srs-admin/src/main/java/com/srs/web/controller/common/WeChatMentalAlertController.java b/srs-admin/src/main/java/com/srs/web/controller/common/WeChatMentalAlertController.java new file mode 100644 index 0000000..9aa2976 --- /dev/null +++ b/srs-admin/src/main/java/com/srs/web/controller/common/WeChatMentalAlertController.java @@ -0,0 +1,129 @@ +package com.srs.web.controller.common; + +import com.srs.common.core.controller.BaseController; +import com.srs.common.core.domain.AjaxResult; +import com.srs.common.core.domain.entity.SysUser; +import com.srs.common.core.domain.model.LoginUser; +import com.srs.common.utils.WeChatUtil; +import com.srs.framework.web.service.TokenService; +import com.srs.system.domain.StudentMentalRating; +import com.srs.system.mapper.StudentMentalRatingMapper; +import com.srs.system.mapper.SysUserMapper; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +/** + * Dify心理问题发送企业微信 知无涯 + */ +@Slf4j +@RestController +@RequestMapping("/api/wechat") +public class WeChatMentalAlertController extends BaseController { + + @Autowired + private SysUserMapper sysUserMapper; + + @Autowired + private WeChatUtil weChatUtil; + + @Autowired + private TokenService tokenService; + @Autowired + private StudentMentalRatingMapper studentMentalRatingMapper; + + /** + * 处理心理预警通知请求 + */ + @PostMapping("/mentalAlert") + public AjaxResult handleMentalAlert(@RequestBody MentalAlertRequest request, + HttpServletRequest httpRequest) { + + // 校验 token 是否有效 + LoginUser loginUser = tokenService.getLoginUser(httpRequest); + if (loginUser == null) { + return AjaxResult.error("Token无效或已过期"); + } + + // 查询辅导员信息 + SysUser teacher = sysUserMapper.selectTeacherByStuNo(request.getUserId()); + if (teacher == null || !StringUtils.hasText(teacher.getUserName())) { + log.error("辅导员信息不完整,学号: {}", request.getUserId()); + return AjaxResult.error("未分配辅导员或信息不完整"); + } + /* 保存学生心理问题评级 */ + StudentMentalRating record = new StudentMentalRating(); + record.setStudentId(request.getUserId()); + record.setRating(request.getRating()); + studentMentalRatingMapper.insert(record); + + // 构建并发送消息 + try { + String content = buildContent(request, teacher); + weChatUtil.sendTextMessage(teacher.getUserName(), content); + return AjaxResult.success("消息已发送至辅导员"); + } catch (Exception e) { + log.error("发送企业微信失败", e); + return AjaxResult.error("发送失败: " + e.getMessage()); + } + } + + /** + * 构建企业微信消息内容 + */ + private String buildContent(MentalAlertRequest request, SysUser teacher) { + String teacherName = StringUtils.hasText(teacher.getNickName()) + ? teacher.getNickName() + : teacher.getUserName(); + return String.format( + "【心理预警通知】\n" + + "辅导员:%s(%s)\n" + + "学生姓名:%s\n" + + "学号:%s\n" + + "问题描述:%s\n" + + "AI建议:%s\n" + + "心理问题评级:%s", + teacherName, + teacher.getUserName(), + request.getUserName(), + request.getUserId(), + request.getStudentQuestion(), + request.getAiAnswer(), + request.getRating() + ); + } + + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class MentalAlertRequest { + private String userId; // 学生学号 + private String userName; // 学生姓名 + private String studentQuestion; // 学生提问内容 + private String aiAnswer; // AI回复内容 + private String rating; // 心理问题评级 + } + + /** + * 获取全部学生心理评级记录 + */ + @GetMapping("/rating/all") + public AjaxResult allRatings() { + return AjaxResult.success(studentMentalRatingMapper.selectAll()); + } + + /** + * 根据学号获取全部记录 + */ + @GetMapping("/rating/{stuNo}") + public AjaxResult listByStuNo(@PathVariable String stuNo) { + return AjaxResult.success(studentMentalRatingMapper.selectByStuNo(stuNo)); + } +} \ No newline at end of file diff --git a/srs-admin/src/main/java/com/srs/web/controller/comprehensive/CphMsgController.java b/srs-admin/src/main/java/com/srs/web/controller/comprehensive/CphMsgController.java index 3b3d006..61e1039 100644 --- a/srs-admin/src/main/java/com/srs/web/controller/comprehensive/CphMsgController.java +++ b/srs-admin/src/main/java/com/srs/web/controller/comprehensive/CphMsgController.java @@ -23,7 +23,7 @@ import com.srs.common.core.page.TableDataInfo; /** * 消息Controller - * + * * @author srs * @date 2023-07-12 */ @@ -103,4 +103,13 @@ public class CphMsgController extends BaseController { return toAjax(cphMsgService.deleteCphMsgByIds(ids)); } + + /** + * 根据学号查询用户ID + */ + @GetMapping("/getUserIdByStuNo/{stuNo}") + public AjaxResult getUserIdByStuNo(@PathVariable("stuNo") String stuNo) + { + return success(cphMsgService.getUserIdByStuNo(stuNo)); + } } diff --git a/srs-admin/src/main/java/com/srs/web/controller/routine/NotificationManagementController.java b/srs-admin/src/main/java/com/srs/web/controller/routine/NotificationManagementController.java index e88e4ab..38f1d9c 100644 --- a/srs-admin/src/main/java/com/srs/web/controller/routine/NotificationManagementController.java +++ b/srs-admin/src/main/java/com/srs/web/controller/routine/NotificationManagementController.java @@ -1,8 +1,6 @@ package com.srs.web.controller.routine; - import java.util.List; import javax.servlet.http.HttpServletResponse; - import com.srs.routine.domain.NotificationManagement; import com.srs.routine.domain.dto.SendNotificationDto; import com.srs.routine.service.INotificationManagementService; @@ -31,17 +29,6 @@ public class NotificationManagementController extends BaseController { @Autowired private INotificationManagementService notificationManagementService; - /** - * 查询通知管理列表 - */ - @PreAuthorize("@ss.hasPermi('routine:NotificationManagement:list')") - @GetMapping("/list") - @ApiOperation("查询通知管理列表") - public TableDataInfo list(NotificationManagement notificationManagement) { - startPage(); - List list = notificationManagementService.selectNotificationManagementList(notificationManagement); - return getDataTable(list); - } /** * 导出通知管理列表 @@ -50,7 +37,8 @@ public class NotificationManagementController extends BaseController { @Log(title = "通知管理", businessType = BusinessType.EXPORT) @PostMapping("/export") @ApiOperation("导出通知管理列表") - public void export(HttpServletResponse response, NotificationManagement notificationManagement) { + public void export(HttpServletResponse response, NotificationManagement notificationManagement) + { List list = notificationManagementService.selectNotificationManagementList(notificationManagement); ExcelUtil util = new ExcelUtil(NotificationManagement.class); util.exportExcel(response, list, "通知管理数据"); @@ -62,7 +50,8 @@ public class NotificationManagementController extends BaseController { @PreAuthorize("@ss.hasPermi('routine:NotificationManagement:query')") @GetMapping(value = "/{id}") @ApiOperation("获取通知管理详细信息") - public AjaxResult getInfo(@PathVariable("id") Long id) { + public AjaxResult getInfo(@PathVariable("id") Long id) + { return success(notificationManagementService.selectNotificationManagementById(id)); } @@ -73,7 +62,8 @@ public class NotificationManagementController extends BaseController { @Log(title = "通知管理", businessType = BusinessType.INSERT) @PostMapping("/add") @ApiOperation("新增通知管理") - public AjaxResult add(@RequestBody NotificationManagement notificationManagement) { + public AjaxResult add(@RequestBody NotificationManagement notificationManagement) + { return toAjax(notificationManagementService.insertNotificationManagement(notificationManagement)); } @@ -84,7 +74,8 @@ public class NotificationManagementController extends BaseController { @Log(title = "通知管理", businessType = BusinessType.UPDATE) @PostMapping("/update") @ApiOperation("修改通知管理") - public AjaxResult edit(@RequestBody NotificationManagement notificationManagement) { + public AjaxResult edit(@RequestBody NotificationManagement notificationManagement) + { return toAjax(notificationManagementService.updateNotificationManagement(notificationManagement)); } @@ -95,8 +86,20 @@ public class NotificationManagementController extends BaseController { @Log(title = "通知管理", businessType = BusinessType.DELETE) @PostMapping("/{ids}") @ApiOperation("删除通知管理") - public AjaxResult remove(@PathVariable Long[] ids) { - return toAjax(notificationManagementService.deleteNotificationManagementByIds(ids)); + public AjaxResult remove(@PathVariable String ids) + { + // 处理逗号分隔的ID字符串 + String[] idArray = ids.split(","); + Long[] idLongArray = new Long[idArray.length]; + for (int i = 0; i < idArray.length; i++) { + idLongArray[i] = Long.parseLong(idArray[i]); + } + + if (idLongArray.length == 1) { + return toAjax(notificationManagementService.deleteNotificationManagementById(idLongArray[0])); + } else { + return toAjax(notificationManagementService.deleteNotificationManagementByIds(idLongArray)); + } } /** @@ -105,7 +108,8 @@ public class NotificationManagementController extends BaseController { @PreAuthorize("@ss.hasPermi('routine:NotificationManagement:list')") @GetMapping("/gradeList") @ApiOperation("获取年级列表") - public AjaxResult getGradeList() { + public AjaxResult getGradeList() + { List gradeList = notificationManagementService.getGradeList(); return success(gradeList); } @@ -117,7 +121,8 @@ public class NotificationManagementController extends BaseController { @Log(title = "按年级发送通知", businessType = BusinessType.INSERT) @PostMapping("/sendByGrades") @ApiOperation("按年级发送通知") - public AjaxResult sendNotificationByGrades(@RequestBody SendNotificationDto sendNotificationDto) { + public AjaxResult sendNotificationByGrades(@RequestBody SendNotificationDto sendNotificationDto) + { int result = notificationManagementService.sendNotificationByGrades(sendNotificationDto); return toAjax(result); } @@ -128,7 +133,8 @@ public class NotificationManagementController extends BaseController { @PreAuthorize("@ss.hasPermi('routine:NotificationManagement:list')") @GetMapping("/my-sent") @ApiOperation("查询当前用户发送的通知列表") - public TableDataInfo mySentList(NotificationManagement notificationManagement) { + public TableDataInfo mySentList(NotificationManagement notificationManagement) + { startPage(); List list = notificationManagementService.selectNotificationManagementListBySender(notificationManagement); return getDataTable(list); diff --git a/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuIdReissueController.java b/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuIdReissueController.java index 15cd199..604577a 100644 --- a/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuIdReissueController.java +++ b/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuIdReissueController.java @@ -145,8 +145,16 @@ public class RtStuIdReissueController extends BaseController { @PostMapping("/add") @ApiOperation("新增学生证补办") public AjaxResult add(@RequestBody RtStuIdReissue rtStuIdReissue) { - // rtStuIdReissue.setCounsellorId(SecurityUtils.getUserId()); - rtStuIdReissue.setCreateBy(SecurityUtils.getUsername()); + // 检查该学号是否已有申请记录 + RtStuIdReissue existingApplication = rtStuIdReissueService.selectRtStuIdReissueByStuNo(rtStuIdReissue.getStuNo()); + if (existingApplication != null) { + // 如果已有申请记录且未完成制作,则不允许重复提交 + if (existingApplication.getInspectionProgress() < 3) { + return AjaxResult.error("您已有正在处理的申请,请勿重复提交"); + } + // 如果已完成制作,允许提交新申请,但需要先删除旧记录 + rtStuIdReissueService.deleteRtStuIdReissueById(existingApplication.getId()); + } return toAjax(rtStuIdReissueService.insertRtStuIdReissue(rtStuIdReissue)); } @@ -169,9 +177,30 @@ public class RtStuIdReissueController extends BaseController { @PostMapping("/update") @ApiOperation("修改学生证补办") public AjaxResult edit(@RequestBody RtStuIdReissue rtStuIdReissue) { + // 检查当前申请状态,如果已完成制作则不允许修改 + RtStuIdReissue currentApplication = rtStuIdReissueService.selectRtStuIdReissueById(rtStuIdReissue.getId()); + if (currentApplication != null && currentApplication.getInspectionProgress() >= 3) { + return AjaxResult.error("申请已完成制作,不可修改"); + } return toAjax(rtStuIdReissueService.updateRtStuIdReissue(rtStuIdReissue)); } + /** + * 删除单个学生证补办申请(学生取消申请) + */ + @PreAuthorize("@ss.hasPermi('routine:stuIdReissue:remove')") + @Log(title = "取消学生证补办申请", businessType = BusinessType.DELETE) + @PostMapping("/cancel/{id}") + @ApiOperation("取消学生证补办申请") + public AjaxResult cancelApplication(@PathVariable Long id) { + // 先检查审核状态,如果已完成制作则不允许取消 + RtStuIdReissue rtStuIdReissue = rtStuIdReissueService.selectRtStuIdReissueById(id); + if (rtStuIdReissue != null && rtStuIdReissue.getInspectionProgress() >= 3) { + return AjaxResult.error("当前审核已完成,不可取消"); + } + return toAjax(rtStuIdReissueService.deleteRtStuIdReissueById(id)); + } + /** * 删除学生证补办 */ @@ -182,4 +211,6 @@ public class RtStuIdReissueController extends BaseController { public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(rtStuIdReissueService.deleteRtStuIdReissueByIds(ids)); } + + } diff --git a/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuMultiLevelReviewController.java b/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuMultiLevelReviewController.java index 692cbe5..c1ed12b 100644 --- a/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuMultiLevelReviewController.java +++ b/srs-admin/src/main/java/com/srs/web/controller/routine/RtStuMultiLevelReviewController.java @@ -114,4 +114,15 @@ public class RtStuMultiLevelReviewController extends BaseController { public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(rtStuMultiLevelReviewService.deleteRtStuMultiLevelReviewByIds(ids)); } + + /** + * 更新审核信息并同时更新学生证补办状态 + */ + @PreAuthorize("@ss.hasPermi('routine:stuMultiLevelReview:edit')") + @Log(title = "更新审核信息并同时更新学生证补办状态", businessType = BusinessType.UPDATE) + @PostMapping("/updateWithStuIdReissue") + @ApiOperation("更新审核信息并同时更新学生证补办状态") + public AjaxResult updateWithStuIdReissue(@RequestBody RtStuMultiLevelReview rtStuMultiLevelReview) { + return toAjax(rtStuMultiLevelReviewService.updateRtStuMultiLevelReviewWithStuIdReissue(rtStuMultiLevelReview)); + } } diff --git a/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/CphMsgMapper.java b/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/CphMsgMapper.java index 7eafa0f..a18b327 100644 --- a/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/CphMsgMapper.java +++ b/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/CphMsgMapper.java @@ -8,7 +8,7 @@ import org.apache.ibatis.annotations.Mapper; /** * 消息Mapper接口 - * + * * @author srs * @date 2023-07-12 */ @@ -17,7 +17,7 @@ public interface CphMsgMapper extends BaseMapper { /** * 查询消息 - * + * * @param id 消息主键 * @return 消息 */ @@ -25,7 +25,7 @@ public interface CphMsgMapper extends BaseMapper /** * 查询消息列表 - * + * * @param cphMsg 消息 * @return 消息集合 */ @@ -33,7 +33,7 @@ public interface CphMsgMapper extends BaseMapper /** * 新增消息 - * + * * @param cphMsg 消息 * @return 结果 */ @@ -41,7 +41,7 @@ public interface CphMsgMapper extends BaseMapper /** * 修改消息 - * + * * @param cphMsg 消息 * @return 结果 */ @@ -49,7 +49,7 @@ public interface CphMsgMapper extends BaseMapper /** * 删除消息 - * + * * @param id 消息主键 * @return 结果 */ @@ -57,9 +57,18 @@ public interface CphMsgMapper extends BaseMapper /** * 批量删除消息 - * + * * @param ids 需要删除的数据主键集合 * @return 结果 */ public int deleteCphMsgByIds(Long[] ids); + + + /** + * 根据学号查询用户ID + * + * @param stuNo 学号 + * @return 用户ID + */ + public Long getUserIdByStuNo(String stuNo); } diff --git a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ICphMsgService.java b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ICphMsgService.java index d63cf70..6d9cf84 100644 --- a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ICphMsgService.java +++ b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ICphMsgService.java @@ -5,15 +5,15 @@ import com.srs.comprehensive.domain.CphMsg; /** * 消息Service接口 - * + * * @author srs * @date 2023-07-12 */ -public interface ICphMsgService +public interface ICphMsgService { /** * 查询消息 - * + * * @param id 消息主键 * @return 消息 */ @@ -21,7 +21,7 @@ public interface ICphMsgService /** * 查询消息列表 - * + * * @param cphMsg 消息 * @return 消息集合 */ @@ -29,7 +29,7 @@ public interface ICphMsgService /** * 新增消息 - * + * * @param cphMsg 消息 * @return 结果 */ @@ -37,7 +37,7 @@ public interface ICphMsgService /** * 修改消息 - * + * * @param cphMsg 消息 * @return 结果 */ @@ -45,7 +45,7 @@ public interface ICphMsgService /** * 批量删除消息 - * + * * @param ids 需要删除的消息主键集合 * @return 结果 */ @@ -53,9 +53,17 @@ public interface ICphMsgService /** * 删除消息信息 - * + * * @param id 消息主键 * @return 结果 */ public int deleteCphMsgById(Long id); + + /** + * 根据学号查询用户ID + * + * @param stuNo 学号 + * @return 用户ID + */ + public Long getUserIdByStuNo(String stuNo); } diff --git a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/CphMsgServiceImpl.java b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/CphMsgServiceImpl.java index ea498e9..d3a13e0 100644 --- a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/CphMsgServiceImpl.java +++ b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/CphMsgServiceImpl.java @@ -12,7 +12,7 @@ import com.srs.comprehensive.service.ICphMsgService; /** * 消息Service业务层处理 - * + * * @author srs * @date 2023-07-12 */ @@ -24,7 +24,7 @@ public class CphMsgServiceImpl extends ServiceImpl implemen /** * 查询消息 - * + * * @param id 消息主键 * @return 消息 */ @@ -36,7 +36,7 @@ public class CphMsgServiceImpl extends ServiceImpl implemen /** * 查询消息列表 - * + * * @param cphMsg 消息 * @return 消息 */ @@ -48,7 +48,7 @@ public class CphMsgServiceImpl extends ServiceImpl implemen /** * 新增消息 - * + * * @param cphMsg 消息 * @return 结果 */ @@ -61,7 +61,7 @@ public class CphMsgServiceImpl extends ServiceImpl implemen /** * 修改消息 - * + * * @param cphMsg 消息 * @return 结果 */ @@ -74,7 +74,7 @@ public class CphMsgServiceImpl extends ServiceImpl implemen /** * 批量删除消息 - * + * * @param ids 需要删除的消息主键 * @return 结果 */ @@ -86,7 +86,7 @@ public class CphMsgServiceImpl extends ServiceImpl implemen /** * 删除消息信息 - * + * * @param id 消息主键 * @return 结果 */ @@ -95,4 +95,16 @@ public class CphMsgServiceImpl extends ServiceImpl implemen { return cphMsgMapper.deleteCphMsgById(id); } + + /** + * 根据学号查询用户ID + * + * @param stuNo 学号 + * @return 用户ID + */ + @Override + public Long getUserIdByStuNo(String stuNo) + { + return cphMsgMapper.getUserIdByStuNo(stuNo); + } } diff --git a/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml b/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml index 0dcfa25..8c6b99e 100644 --- a/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml +++ b/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml @@ -1,9 +1,9 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + @@ -21,19 +21,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + - + insert into cph_msg @@ -44,7 +44,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" create_time, update_by, update_time, - + #{sender}, #{receiver}, @@ -53,7 +53,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{createTime}, #{updateBy}, #{updateTime}, - + @@ -75,9 +75,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - delete from cph_msg where id in + delete from cph_msg where id in #{id} - \ No newline at end of file + + + diff --git a/srs-routine/src/main/java/com/srs/routine/domain/NotificationManagement.java b/srs-routine/src/main/java/com/srs/routine/domain/NotificationManagement.java index 5baed55..49cc959 100644 --- a/srs-routine/src/main/java/com/srs/routine/domain/NotificationManagement.java +++ b/srs-routine/src/main/java/com/srs/routine/domain/NotificationManagement.java @@ -6,10 +6,8 @@ import io.swagger.annotations.ApiModelProperty; import lombok.*; import com.srs.common.core.domain.BaseEntity; - - /** - * 通知管理对象 cph_msg + * 通知管理对象 srs_notification * * @author srs * @date 2025-07-30 @@ -20,40 +18,63 @@ import com.srs.common.core.domain.BaseEntity; @AllArgsConstructor @Builder @ApiModel(value = "NotificationManagement对象" , description = "通知管理") -@TableName("cph_msg") +@TableName("srs_notification") public class NotificationManagement extends BaseEntity{ -private static final long serialVersionUID=1L; + private static final long serialVersionUID=1L; /** - * $column.columnComment + * 通知ID */ - @ApiModelProperty("${column.columnComment}") + @ApiModelProperty("通知ID") @TableId(value = "id", type = IdType.AUTO) - @Excel(name = "${comment}" , readConverterExp = "$column.readConverterExp()") + @Excel(name = "通知ID") private Long id; /** - * 发送人用户id + * 通知标题 */ - @ApiModelProperty("发送人用户id") + @ApiModelProperty("通知标题") + @TableField("title") + @Excel(name = "通知标题") + private String title; + + /** + * 通知内容 + */ + @ApiModelProperty("通知内容") + @TableField("content") + @Excel(name = "通知内容") + private String content; + + /** + * 年级ID列表,以逗号分隔 + */ + @ApiModelProperty("年级ID列表,以逗号分隔") + @TableField("grade_ids") + @Excel(name = "年级ID列表") + private String gradeIds; + + /** + * 年级名称 + */ + @ApiModelProperty("年级名称") + @TableField("gradeName") + @Excel(name = "年级名称") + private String gradeName; + + /** + * 发送人用户ID + */ + @ApiModelProperty("发送人用户ID") @TableField("sender") - @Excel(name = "发送人用户id") + @Excel(name = "发送人用户ID") private Long sender; /** - * 收信人用户id + * 接收人用户ID(用于cph_msg表) */ - @ApiModelProperty("收信人用户id") + @ApiModelProperty("接收人用户ID") @TableField("receiver") private Long receiver; - /** - * 消息内容 - */ - @ApiModelProperty("消息内容") - @TableField("content") - @Excel(name = "消息内容") - private String content; - - } diff --git a/srs-routine/src/main/java/com/srs/routine/domain/dto/SendNotificationDto.java b/srs-routine/src/main/java/com/srs/routine/domain/dto/SendNotificationDto.java index 18d78dd..c9894f8 100644 --- a/srs-routine/src/main/java/com/srs/routine/domain/dto/SendNotificationDto.java +++ b/srs-routine/src/main/java/com/srs/routine/domain/dto/SendNotificationDto.java @@ -22,6 +22,9 @@ public class SendNotificationDto { @ApiModelProperty("选中的年级ID列表") private List selectedGrades; + @ApiModelProperty("通知标题") + private String title; + @ApiModelProperty("通知内容") private String content; diff --git a/srs-routine/src/main/java/com/srs/routine/mapper/NotificationManagementMapper.java b/srs-routine/src/main/java/com/srs/routine/mapper/NotificationManagementMapper.java index 01107e1..d1f492c 100644 --- a/srs-routine/src/main/java/com/srs/routine/mapper/NotificationManagementMapper.java +++ b/srs-routine/src/main/java/com/srs/routine/mapper/NotificationManagementMapper.java @@ -76,15 +76,15 @@ public interface NotificationManagementMapper extends BaseMapper selectStudentIdsByGrades(@Param("gradeIds") List gradeIds); /** - * 根据学号查询用户ID + * 根据年级ID列表查询学生学号列表 * - * @param stuNo 学号 - * @return 用户ID + * @param gradeIds 年级ID列表 + * @return 学生学号列表 */ - Long selectUserIdByStuNo(@Param("stuNo") String stuNo); + List selectStudentNosByGrades(@Param("gradeIds") List gradeIds); /** - * 批量插入通知记录 + * 批量插入通知记录到cph_msg表 * * @param notifications 通知记录列表 * @return 结果 @@ -99,4 +99,23 @@ public interface NotificationManagementMapper extends BaseMapper selectNotificationManagementListBySender(@Param("notification") NotificationManagement notificationManagement, @Param("senderId") Long senderId); + + /** + * 批量删除cph_msg表数据 + * + * @param notificationList 通知列表 + * @return 结果 + */ + int batchDeleteCphMsg(@Param("list") List notificationList); + + /** + * 批量更新cph_msg表数据 + * + * @param oldList 原数据列表 + * @param newList 新数据列表 + * @return 结果 + */ + int batchUpdateCphMsg(@Param("oldList") List oldList, @Param("newList") List newList); + + } diff --git a/srs-routine/src/main/java/com/srs/routine/mapper/RtStuMultiLevelReviewMapper.java b/srs-routine/src/main/java/com/srs/routine/mapper/RtStuMultiLevelReviewMapper.java index 85b2fa9..8b567cc 100644 --- a/srs-routine/src/main/java/com/srs/routine/mapper/RtStuMultiLevelReviewMapper.java +++ b/srs-routine/src/main/java/com/srs/routine/mapper/RtStuMultiLevelReviewMapper.java @@ -63,4 +63,14 @@ public interface RtStuMultiLevelReviewMapper extends BaseMapper getStudentIdsByGrades(List gradeIds); - - /** - * 根据学号查询用户ID - * - * @param stuNo 学号 - * @return 用户ID - */ - Long getUserIdByStuNo(String stuNo); - + List getStudentNosByGrades(List gradeIds); /** * 查询当前用户发送的通知列表 @@ -99,6 +90,4 @@ public interface INotificationManagementService extends IService selectNotificationManagementListBySender(NotificationManagement notificationManagement); - - } diff --git a/srs-routine/src/main/java/com/srs/routine/service/IRtStuIdReissueService.java b/srs-routine/src/main/java/com/srs/routine/service/IRtStuIdReissueService.java index bb21c8d..2f77088 100644 --- a/srs-routine/src/main/java/com/srs/routine/service/IRtStuIdReissueService.java +++ b/srs-routine/src/main/java/com/srs/routine/service/IRtStuIdReissueService.java @@ -70,5 +70,13 @@ public interface IRtStuIdReissueService extends IService { */ StuInfoReturnVo getStuInfo(String stuNo); + /** + * 完成制作 + * + * @param id 学生证补办主键 + * @return 结果 + */ + int completedRtStuIdReissue(Long id); + } diff --git a/srs-routine/src/main/java/com/srs/routine/service/IRtStuMultiLevelReviewService.java b/srs-routine/src/main/java/com/srs/routine/service/IRtStuMultiLevelReviewService.java index f02096f..31cba1d 100644 --- a/srs-routine/src/main/java/com/srs/routine/service/IRtStuMultiLevelReviewService.java +++ b/srs-routine/src/main/java/com/srs/routine/service/IRtStuMultiLevelReviewService.java @@ -61,4 +61,15 @@ public interface IRtStuMultiLevelReviewService extends IService selectNotificationManagementList(NotificationManagement notificationManagement) { @@ -57,6 +60,7 @@ public class NotificationManagementServiceImpl extends ServiceImpl 0 && originalNotification != null) { + // 构建更新条件:根据原内容、发送人和创建时间匹配 + List updateList = new ArrayList<>(); + updateList.add(originalNotification); + + // 构建新数据用于更新 + List newList = new ArrayList<>(); + NotificationManagement newMsg = new NotificationManagement(); + newMsg.setContent(notificationManagement.getContent()); + newMsg.setSender(notificationManagement.getSender()); + newMsg.setCreateTime(originalNotification.getCreateTime()); // 保持原创建时间不变 + newList.add(newMsg); + + // 批量更新cph_msg表 + notificationManagementMapper.batchUpdateCphMsg(updateList, newList); + } + + return result; } /** @@ -79,7 +109,22 @@ public class NotificationManagementServiceImpl extends ServiceImpl notifications = new ArrayList<>(); + for (Long id : ids) { + NotificationManagement notification = selectNotificationManagementById(id); + if (notification != null) { + notifications.add(notification); + } + } + + if (!notifications.isEmpty()) { + notificationManagementMapper.batchDeleteCphMsg(notifications); + } + + // 删除srs_notification表数据 return notificationManagementMapper.deleteNotificationManagementByIds(ids); } @@ -90,7 +135,17 @@ public class NotificationManagementServiceImpl extends ServiceImpl notifications = new ArrayList<>(); + notifications.add(notification); + notificationManagementMapper.batchDeleteCphMsg(notifications); + } + + // 删除srs_notification表数据 return notificationManagementMapper.deleteNotificationManagementById(id); } @@ -111,65 +166,58 @@ public class NotificationManagementServiceImpl extends ServiceImpl 0) { + // 根据年级获取学生用户ID列表 + List studentIds = notificationManagementMapper.selectStudentIdsByGrades(sendNotificationDto.getSelectedGrades()); + + if (studentIds != null && !studentIds.isEmpty()) { + // 使用srs_notification表的create_time,确保两个表的时间一致 + java.util.Date notificationCreateTime = notification.getCreateTime(); + + // 批量构建cph_msg记录 + List cphMsgList = studentIds.stream() + .map(studentId -> { + NotificationManagement msg = new NotificationManagement(); + msg.setSender(SecurityUtils.getUserId()); + msg.setReceiver(studentId); + msg.setContent(sendNotificationDto.getContent()); + msg.setCreateTime(notificationCreateTime); + return msg; + }) + .collect(Collectors.toList()); + + // 批量插入cph_msg表 + int i= notificationManagementMapper.batchInsertNotification(cphMsgList); + // 如果插入成功,发送企业微信消息 + if (i > 0) { + //根据年级获取学生学号列表 + List studentNos = getStudentNosByGrades(sendNotificationDto.getSelectedGrades()); + if (studentNos != null && !studentNos.isEmpty()) { + // 批量发送企业微信消息 + sendWeChatMessagesBatch(studentNos, sendNotificationDto.getContent()); + } + } + return i; + } } - // 根据年级获取学生用户ID列表 - List studentIds = getStudentIdsByGrades(sendNotificationDto.getSelectedGrades()); - - if (studentIds == null || studentIds.isEmpty()) { - return 0; - } - - // 批量构建通知记录 - final Long finalSenderId = senderId; - List notifications = studentIds.stream() - .map(studentId -> { - NotificationManagement notification = new NotificationManagement(); - notification.setSender(finalSenderId); - notification.setReceiver(studentId); - notification.setContent(sendNotificationDto.getContent()); - notification.setCreateTime(DateUtils.getNowDate()); - return notification; - }) - .collect(Collectors.toList()); - - // 批量插入 - int i= notificationManagementMapper.batchInsertNotification(notifications); - if (i > 0){ - // todo 给这些学生发送企业微信消息 - // 先给:20200016 - } - return i; + return result; } - /** - * 根据年级获取学生用户ID列表 - * - * @param gradeIds 年级ID列表 - * @return 学生用户ID列表 - */ - @Override - public List getStudentIdsByGrades(List gradeIds) { - return notificationManagementMapper.selectStudentIdsByGrades(gradeIds); - } - - /** - * 根据学号查询用户ID - * - * @param stuNo 学号 - * @return 用户ID - */ - @Override - public Long getUserIdByStuNo(String stuNo) { - return notificationManagementMapper.selectUserIdByStuNo(stuNo); - } - - /** * 查询当前用户发送的通知列表 * @@ -182,6 +230,34 @@ public class NotificationManagementServiceImpl extends ServiceImpl studentNos, String content) { + // 企业微信批量发送限制,每次最多发送1000个用户 + int batchSize = 1000; + for (int i = 0; i < studentNos.size(); i += batchSize) { + List batch = studentNos.subList(i, Math.min(i + batchSize, studentNos.size())); + // 拼接成"2023001|2023002|2023003"格式 + String toUser = String.join("|", batch); + // 调用企业微信发送消息方法 + weChatUtil.sendTextMessage(toUser, content); + } + } + + /** + * 根据年级获取学生学号列表 + * + * @param gradeIds 年级ID列表 + * @return 学生学号列表 + */ + @Override + public List getStudentNosByGrades(List gradeIds) { + return notificationManagementMapper.selectStudentNosByGrades(gradeIds); + } } diff --git a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuIdReissueServiceImpl.java b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuIdReissueServiceImpl.java index 0764b7f..632636d 100644 --- a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuIdReissueServiceImpl.java +++ b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuIdReissueServiceImpl.java @@ -137,4 +137,19 @@ public class RtStuIdReissueServiceImpl extends ServiceImpl 0) { + String messageContent = rtStuMultiLevelReview.getNotes(); + if (messageContent == null || messageContent.trim().isEmpty()) { + messageContent = "你申请办理的学生证制作完成,长堽校区前往xxx领取,里建校区前往xxx领取"; + } + WeChatUtil weChatUtil = new WeChatUtil(); + weChatUtil.sendTextMessage(rtStuMultiLevelReview.getStuNo(), messageContent); + } + return result; + } + + } diff --git a/srs-routine/src/main/resources/mapper/routine/NotificationManagementMapper.xml b/srs-routine/src/main/resources/mapper/routine/NotificationManagementMapper.xml index bbcd073..17f3399 100644 --- a/srs-routine/src/main/resources/mapper/routine/NotificationManagementMapper.xml +++ b/srs-routine/src/main/resources/mapper/routine/NotificationManagementMapper.xml @@ -1,14 +1,16 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + - - + + + + @@ -27,7 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select id, sender, receiver, content, create_by, create_time, update_by, update_time from cph_msg + select id, title, content, grade_ids, sender, create_by, create_time, update_by, update_time from srs_notification @@ -36,57 +38,69 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + + - + + - insert into cph_msg + insert into srs_notification - sender, - receiver, + title, content, + grade_ids, + sender, create_by, create_time, update_by, update_time, - + - #{sender}, - #{receiver}, + #{title}, #{content}, + #{gradeIds}, + #{sender}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, - + - update cph_msg + update srs_notification - sender = #{sender}, - receiver = #{receiver}, + title = #{title}, content = #{content}, + grade_ids = #{gradeIds}, + sender = #{sender}, create_by = #{createBy}, create_time = #{createTime}, update_by = #{updateBy}, @@ -96,11 +110,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - delete from cph_msg where id = #{id} + delete from srs_notification where id = #{id} - delete from cph_msg where id in + delete from srs_notification where id in #{id} @@ -126,8 +140,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + - + insert into cph_msg (sender, receiver, content, create_time) values @@ -136,4 +161,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - \ No newline at end of file + + + delete from cph_msg where (content, sender, create_time) in + + (#{item.content}, #{item.sender}, #{item.createTime}) + + + + + + update cph_msg + set content = #{newList[0].content} + where content = #{oldList[0].content} + and sender = #{oldList[0].sender} + and create_time = #{oldList[0].createTime} + + + diff --git a/srs-routine/src/main/resources/mapper/routine/RtStuMultiLevelReviewMapper.xml b/srs-routine/src/main/resources/mapper/routine/RtStuMultiLevelReviewMapper.xml index 3f8fb60..dd4c7b4 100644 --- a/srs-routine/src/main/resources/mapper/routine/RtStuMultiLevelReviewMapper.xml +++ b/srs-routine/src/main/resources/mapper/routine/RtStuMultiLevelReviewMapper.xml @@ -103,4 +103,28 @@ #{id} - \ No newline at end of file + + + + + update rt_stu_multi_level_review + + stu_name = #{stuName}, + stuNo = #{stuNo}, + reason = #{reason}, + reviewer = #{reviewer}, + reviewer_identity = #{reviewerIdentity}, + review_time = #{reviewTime}, + reviewer_id = #{reviewerId}, + notes = #{notes}, + type = #{type}, + reviewer_status = #{reviewerStatus}, + + where id = #{id}; + + + update rt_stu_id_reissue + set inspection_progress = #{reviewerStatus} + where stu_no = #{stuNo}; + + diff --git a/srs-system/src/main/java/com/srs/system/domain/StudentMentalRating.java b/srs-system/src/main/java/com/srs/system/domain/StudentMentalRating.java new file mode 100644 index 0000000..b26f57d --- /dev/null +++ b/srs-system/src/main/java/com/srs/system/domain/StudentMentalRating.java @@ -0,0 +1,49 @@ +package com.srs.system.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.srs.common.core.domain.BaseEntity; + +import java.util.Date; + +/** + * 学生心理问题评级表 知无涯 + */ +public class StudentMentalRating extends BaseEntity { + + private static final long serialVersionUID = 1L; + + private Long id; + private String studentId; + private String rating; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStudentId() { + return studentId; + } + + public void setStudentId(String studentId) { + this.studentId = studentId; + } + + public String getRating() { + return rating; + } + + public void setRating(String rating) { + this.rating = rating; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createdTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date updatedTime; +} \ No newline at end of file diff --git a/srs-system/src/main/java/com/srs/system/mapper/StudentMentalRatingMapper.java b/srs-system/src/main/java/com/srs/system/mapper/StudentMentalRatingMapper.java new file mode 100644 index 0000000..bcc0f32 --- /dev/null +++ b/srs-system/src/main/java/com/srs/system/mapper/StudentMentalRatingMapper.java @@ -0,0 +1,32 @@ +package com.srs.system.mapper; + +import com.srs.system.domain.StudentMentalRating; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface StudentMentalRatingMapper { + + /** + * 按学号查询 知无涯 + */ + StudentMentalRating selectByStudentId(String studentId); + + /** + * 插入 + */ + int insert(StudentMentalRating record); + + /** + * 按学号更新评级 + */ + int updateRatingByStudentId(StudentMentalRating record); + + /** 全部记录 */ + List selectAll(); + + /** 单学号全部记录 */ + List selectByStuNo(@Param("stuNo") String stuNo); + +} + diff --git a/srs-system/src/main/resources/mapper/system/StudentMentalRatingMapper.xml b/srs-system/src/main/resources/mapper/system/StudentMentalRatingMapper.xml new file mode 100644 index 0000000..88cd1e5 --- /dev/null +++ b/srs-system/src/main/resources/mapper/system/StudentMentalRatingMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + INSERT INTO student_mental_rating(student_id, rating) + VALUES (#{studentId}, #{rating}) + + + + UPDATE student_mental_rating + SET rating = #{rating} + WHERE student_id = #{studentId} + + + + + + + + \ No newline at end of file diff --git a/srs-teacher/src/main/java/com/srs/teacher/domain/dto/ConversationDTO.java b/srs-teacher/src/main/java/com/srs/teacher/domain/dto/ConversationDTO.java new file mode 100644 index 0000000..a741143 --- /dev/null +++ b/srs-teacher/src/main/java/com/srs/teacher/domain/dto/ConversationDTO.java @@ -0,0 +1,53 @@ +package com.srs.teacher.domain.dto; + +import java.util.Date; + +public class ConversationDTO { + private String id; // 会话 ID + private String name; // 会话名称,默认由大语言模型生成。 + private String introduction; // 开场白 + private Date created_at; // 创建时间 + private Date updated_at; // 更新时间 + + // Getters and Setters for the properties + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getIntroduction() { + return introduction; + } + + public void setIntroduction(String introduction) { + this.introduction = introduction; + } + + public Date getCreated_at() { + return created_at; + } + + public void setCreated_at(Date created_at) { + this.created_at = created_at; + } + + public Date getUpdated_at() { + return updated_at; + } + + public void setUpdated_at(Date updated_at) { + this.updated_at = updated_at; + } +} +