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 index 3556763..4452dbc 100644 --- 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 @@ -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,60 +44,8 @@ import java.util.concurrent.CompletableFuture; @RestController @RequestMapping("/aitutor/aichat") public class AiChatController extends BaseController { - - /** - * Dify API的访问密钥 - * 用于身份验证,授权访问Dify服务 - */ - private static final String DIFY_API_KEY = "app-BfPtBZDNBuHOS9K1PaZrxQYE"; - - /** - * Dify API的URL地址 - * 用于发送聊天消息请求到Dify服务 - */ - - 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://172.16.129.101/v1:6100/v1/messages"; - - /** - * Dify获取反馈API的基础URL - * 用于获取消息反馈(点赞、点踩等) - */ - 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://172.16.129.101/v1:6100/v1/messages"; - - /** - * Dify会话API的基础URL - * 用于获取会话列表 - */ - 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://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; - + @Autowired + private TokenService tokenService; /** * HTTP客户端实例 * 配置了5分钟的读取超时时间,用于与Dify API进行通信 @@ -112,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() @@ -162,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 { @@ -192,7 +152,8 @@ public class AiChatController extends BaseController { * @param currentUsername * @throws IOException 当网络请求或IO操作失败时抛出 */ - private void sendToDifyAndStream(Map requestData, SseEmitter emitter, String currentUsername) + private void sendToDifyAndStream(Map requestData, SseEmitter emitter, String currentUsername, + String currentUserToken) throws IOException { // 构建请求体参数 @@ -229,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"); @@ -239,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); // 自动为对话生成名称 @@ -252,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(); @@ -383,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(); @@ -430,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(); @@ -504,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 @@ -532,7 +498,7 @@ public class AiChatController extends BaseController { return successResult; } catch (IOException e) { e.printStackTrace(); - return AjaxResult.error("网络请求失败,请稍后重试"); + return AjaxResult.error("网络请求失败,请稍后重试getMessagesToAdmin"); } catch (Exception e) { e.printStackTrace(); return AjaxResult.error("请求处理失败: " + e.getMessage()); @@ -542,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(); @@ -571,7 +537,7 @@ public class AiChatController extends BaseController { return successResult; } catch (IOException e) { e.printStackTrace(); - return AjaxResult.error("网络请求失败,请稍后重试"); + return AjaxResult.error("网络请求失败,请稍后重试getMessagesToUser"); } catch (Exception e) { e.printStackTrace(); return AjaxResult.error("请求处理失败: " + e.getMessage()); @@ -592,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); @@ -606,7 +572,7 @@ public class AiChatController extends BaseController { /** * 将会话ID与用户绑定并存储到Redis中 * - * @param user 用户名 + * @param user 用户名 * @param conversationId 会话ID */ private void cacheConversationId(String user, String conversationId) { @@ -624,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()); } @@ -640,7 +606,7 @@ public class AiChatController extends BaseController { public List 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); @@ -651,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(); @@ -723,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()) { @@ -733,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(); @@ -821,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(); 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 index 2118080..dc6260f 100644 --- 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 @@ -21,6 +21,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; +import java.time.LocalDate; /** * Dify心理问题发送企业微信 知无涯 @@ -57,18 +58,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); @@ -88,7 +108,7 @@ public class WeChatMentalAlertController extends BaseController { ? teacher.getNickName() : teacher.getUserName(); return String.format( - "【心理预警通知】\n" + + "【智水AI心理预警通知】\n" + "辅导员:%s(%s)\n" + "学生姓名:%s\n" + "学号:%s\n" + diff --git a/srs-admin/src/main/java/com/srs/web/core/config/DifyConfig.java b/srs-admin/src/main/java/com/srs/web/core/config/DifyConfig.java new file mode 100644 index 0000000..da6cacd --- /dev/null +++ b/srs-admin/src/main/java/com/srs/web/core/config/DifyConfig.java @@ -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; + } +} diff --git a/srs-admin/src/main/resources/application.yml b/srs-admin/src/main/resources/application.yml index e3e763e..afe0801 100644 --- a/srs-admin/src/main/resources/application.yml +++ b/srs-admin/src/main/resources/application.yml @@ -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: diff --git a/srs-common/src/main/java/com/srs/common/utils/WeChatUtil.java b/srs-common/src/main/java/com/srs/common/utils/WeChatUtil.java index 7a0a42b..b94a681 100644 --- a/srs-common/src/main/java/com/srs/common/utils/WeChatUtil.java +++ b/srs-common/src/main/java/com/srs/common/utils/WeChatUtil.java @@ -157,7 +157,7 @@ public class WeChatUtil { String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; JSONObject msg = new JSONObject(); - msg.put("touser", "2023429229"); + msg.put("touser", toUser); msg.put("msgtype", "text"); msg.put("agentid", weChatConfig.getAgentId()); diff --git a/srs-comprehensive/src/main/java/com/srs/comprehensive/controller/SrsIdentifytableController.java b/srs-comprehensive/src/main/java/com/srs/comprehensive/controller/SrsIdentifytableController.java index 177c8f1..8b02515 100644 --- a/srs-comprehensive/src/main/java/com/srs/comprehensive/controller/SrsIdentifytableController.java +++ b/srs-comprehensive/src/main/java/com/srs/comprehensive/controller/SrsIdentifytableController.java @@ -126,4 +126,15 @@ public class SrsIdentifytableController extends BaseController { { return toAjax(srsIdentifytableService.deleteSrsIdentifytableByIds(ids)); } + + /** + * 班级信息 + */ + @Log(title = "学生鉴定信息", businessType = BusinessType.DELETE) + @GetMapping("/deptdata") + @ApiOperation("获取对应人的班级信息") + public AjaxResult deptData() + { + return success(srsIdentifytableService.deptDataLsit()); + } } 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 a18b327..50b6399 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 @@ -55,6 +55,14 @@ public interface CphMsgMapper extends BaseMapper */ public int deleteCphMsgById(Long id); + /** + * 根据接收人和内容中的标识来查找流程消息 (使用LIKE查询) 知无涯 + * + * @param cphMsg 包含 receiver 和 content (作为模糊查询的标识) 的查询对象 + * @return 匹配的消息列表 + */ + public List selectCphMsgListForFlowable(CphMsg cphMsg); + /** * 批量删除消息 * diff --git a/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/SrsIdentifytableMapper.java b/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/SrsIdentifytableMapper.java index 15ecd27..fe99489 100644 --- a/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/SrsIdentifytableMapper.java +++ b/srs-comprehensive/src/main/java/com/srs/comprehensive/mapper/SrsIdentifytableMapper.java @@ -2,6 +2,8 @@ package com.srs.comprehensive.mapper; import java.util.List; +import com.srs.common.core.domain.entity.SysDept; +import com.srs.comprehensive.domain.SrsClass; import com.srs.comprehensive.domain.SrsIdentifytable; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -107,4 +109,13 @@ public interface SrsIdentifytableMapper extends BaseMapper { * @return 结果 */ int deleteSrsGraduataByIds(Long[] ids); + + /** + * 获取对应人的班级信息 + * + * @param userName 用户名称 + * @return 结果 + */ + List selectDeptByDeptCodes(String userName); + } 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 6d9cf84..eae412d 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 @@ -59,6 +59,14 @@ public interface ICphMsgService */ public int deleteCphMsgById(Long id); + /** + * 删除消息信息 精准匹配版 + * + * @param cphMsg 消息 + * @return 列表 + */ + List selectCphMsgListForFlowable(CphMsg cphMsg); + /** * 根据学号查询用户ID * diff --git a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ISrsIdentifytableService.java b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ISrsIdentifytableService.java index d24ce27..5d548a2 100644 --- a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ISrsIdentifytableService.java +++ b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/ISrsIdentifytableService.java @@ -3,6 +3,8 @@ package com.srs.comprehensive.service; import java.util.List; import com.baomidou.mybatisplus.extension.service.IService; +import com.srs.comprehensive.domain.CascaderEntity; +import com.srs.comprehensive.domain.SrsClass; import com.srs.comprehensive.domain.SrsIdentifytable; /** @@ -59,4 +61,12 @@ public interface ISrsIdentifytableService extends IService { * @return 结果 */ int deleteSrsIdentifytableById(Long id); + + /** + * 获取对应人的班级信息 + * + * @param + * @return 集合结果 + */ + List deptDataLsit(); } 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 d3a13e0..a739eb0 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 @@ -95,6 +95,16 @@ public class CphMsgServiceImpl extends ServiceImpl implemen { return cphMsgMapper.deleteCphMsgById(id); } + /** + * 删除消息信息 + * + * @param cphMsg 消息 + * @return 结果 + */ + @Override + public List selectCphMsgListForFlowable(CphMsg cphMsg) { + return cphMsgMapper.selectCphMsgListForFlowable(cphMsg); + } /** * 根据学号查询用户ID diff --git a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/SrsIdentifytableServiceImpl.java b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/SrsIdentifytableServiceImpl.java index 494b65a..ec86368 100644 --- a/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/SrsIdentifytableServiceImpl.java +++ b/srs-comprehensive/src/main/java/com/srs/comprehensive/service/impl/SrsIdentifytableServiceImpl.java @@ -1,17 +1,20 @@ package com.srs.comprehensive.service.impl; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; + +import com.srs.common.core.domain.entity.SysDept; +import com.srs.common.core.domain.model.LoginUser; import com.srs.common.utils.DateUtils; -import com.srs.comprehensive.domain.GraduateStudentNews; -import com.srs.comprehensive.domain.SaveStudent; -import com.srs.comprehensive.mapper.GraduateTextMapper; -import com.srs.comprehensive.mapper.SrsGraduateStudentMapper; +import com.srs.common.utils.SecurityUtils; +import com.srs.comprehensive.domain.*; +import com.srs.comprehensive.mapper.*; +import com.srs.comprehensive.service.ICascaderDataStudentService; import com.srs.comprehensive.util.FileUploadUtil; import org.springframework.beans.factory.annotation.Autowired; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; -import com.srs.comprehensive.mapper.SrsIdentifytableMapper; -import com.srs.comprehensive.domain.SrsIdentifytable; import com.srs.comprehensive.service.ISrsIdentifytableService; /** @@ -156,4 +159,23 @@ public class SrsIdentifytableServiceImpl extends ServiceImpl deptDataLsit() { + //获取登录人的信息 + LoginUser loginUser = SecurityUtils.getLoginUser(); + List depts = srsIdentifytableMapper.selectDeptByDeptCodes(loginUser.getUsername()); + if(depts != null && depts.size() > 0) { + return depts; + } + return new ArrayList<>(); + } + } diff --git a/srs-comprehensive/src/main/resources/mapper/comprehensive/CphGoodApplyMapper.xml b/srs-comprehensive/src/main/resources/mapper/comprehensive/CphGoodApplyMapper.xml index c17a5f6..ab54c16 100644 --- a/srs-comprehensive/src/main/resources/mapper/comprehensive/CphGoodApplyMapper.xml +++ b/srs-comprehensive/src/main/resources/mapper/comprehensive/CphGoodApplyMapper.xml @@ -781,6 +781,12 @@ from sys_performance as a where (a.shstatus IS NOT NULL and a.ksstatus IS NULL) -- 科室复核待办 or (a.ksstatus IS NOT NULL and a.xgstatus IS NULL) -- 学工处长待办 + -- 邵政文-(宿舍管理-住宿费用-辅导员确认待办) + union + select concat('zsfy-',count(a.id)) as `all` + from dms_new_record as a + left join view_dms_record as d on a.stu_no = d.stu_no + where d.employee_id = #{tNo} and a.apply_status = 6 diff --git a/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml b/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml index 8c6b99e..6ea5ee4 100644 --- a/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml +++ b/srs-comprehensive/src/main/resources/mapper/comprehensive/CphMsgMapper.xml @@ -84,4 +84,16 @@ + + + diff --git a/srs-comprehensive/src/main/resources/mapper/comprehensive/SrsIdentifytableMapper.xml b/srs-comprehensive/src/main/resources/mapper/comprehensive/SrsIdentifytableMapper.xml index 95af267..5ce6e37 100644 --- a/srs-comprehensive/src/main/resources/mapper/comprehensive/SrsIdentifytableMapper.xml +++ b/srs-comprehensive/src/main/resources/mapper/comprehensive/SrsIdentifytableMapper.xml @@ -54,6 +54,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where id = #{id} + + + + diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/ArchivingNotifyListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/ArchivingNotifyListener.java new file mode 100644 index 0000000..3619a28 --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/ArchivingNotifyListener.java @@ -0,0 +1,55 @@ +package com.srs.flowable.listener.disciplinary; + +import com.srs.common.utils.WeChatUtil; +import com.srs.common.utils.spring.SpringUtils; +import com.srs.flowable.mapper.DisciplinaryMapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.stereotype.Component; +import java.util.List; + +/** + * 执行监听器:在“学生教育管理科审批归档”节点开始时, 知无涯 + * 向负责归档的角色发送企业微信通知。 + */ +@Component +@Slf4j +public class ArchivingNotifyListener implements ExecutionListener { + + @Override + public void notify(DelegateExecution delegateExecution) { + log.info("流程实例 [{}]: 已进入学生教育管理科审批归档节点,准备发送通知。", delegateExecution.getProcessInstanceId()); + + try { + DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class); + + // 1. 【核心】定义负责归档的角色的 role_key。 + final String ARCHIVING_ROLE_KEY = "xsjyglkspgd"; + + // 2. 根据角色Key查询用户ID列表 + List userIdList = disciplinaryMapper.getApprovalByRoleKey(ARCHIVING_ROLE_KEY); + + if (userIdList == null || userIdList.isEmpty()) { + log.warn("根据角色Key '{}' 未找到任何归档人员,无法发送通知。", ARCHIVING_ROLE_KEY); + return; + } + + // 3. 批量查询 userName 并发送通知 + List userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList); + if (!userNameList.isEmpty()) { + String toUser = String.join("|", userNameList); + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + String content = "您有一条新的学生违纪归档任务待处理,请点击前往处理。"; + + weChatUtil.sendTextMessage(toUser, content); + log.info("已成功向归档角色 ({}) 发送通知。接收人: {}", ARCHIVING_ROLE_KEY, toUser); + } else { + log.warn("角色 '{}' 中找到了 {} 个用户,但无人配置企业微信账号,未发送任何通知。", ARCHIVING_ROLE_KEY, userIdList.size()); + } + + } catch (Exception e) { + log.error("向归档角色发送企业微信通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/EJXYSJListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/EJXYSJListener.java index 28ab42a..8f48c95 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/EJXYSJListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/EJXYSJListener.java @@ -9,7 +9,7 @@ import org.flowable.engine.delegate.ExecutionListener; import org.springframework.stereotype.Component; /** - * 根据辅导员部门id获取二级学院书记 + * 根据辅导员部门id获取二级学院书记 知无涯 */ @Component @Slf4j diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/GenericMessageListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/GenericMessageListener.java new file mode 100644 index 0000000..50c1305 --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/GenericMessageListener.java @@ -0,0 +1,128 @@ +package com.srs.flowable.listener.disciplinary; // 您可以根据需要调整包路径 + +import com.srs.common.utils.SecurityUtils; +import com.srs.comprehensive.domain.CphMsg; +import com.srs.comprehensive.service.ICphMsgService; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +/** + * 【通用线上监听器】为即将到来的任务创建内部“我的消息”记录。 知无涯1 + *

+ * 该监听器被设计为在SequenceFlow的'take'事件上触发。 + * 它会智能地从流程变量中读取单个接收人(变量名: approval)或多个接收人(变量名: userList), + * 然后为每一位接收人创建一条内部消息。 + *

+ */ +@Component("genericMessageListener") +@Slf4j +public class GenericMessageListener implements ExecutionListener { + + @Autowired + private ICphMsgService cphMsgService; + + @Override + public void notify(DelegateExecution execution) { + log.info("流程实例 [{}]: 触发通用消息监听器...", execution.getProcessInstanceId()); + + List receiverIdList = getReceiverIds(execution); + if (receiverIdList.isEmpty()) { + return; + } + + // --- 关键改动:获取下一个即将创建的任务ID --- + // 注意:在线上监听器中,任务尚未创建,所以我们无法直接获取任务ID。 + // 但我们可以获取到这个“活动”的定义ID,它在流程定义中是唯一的。 + String activityId = execution.getCurrentActivityId(); + + String taskName = execution.getCurrentFlowElement() != null ? execution.getCurrentFlowElement().getName() : "新"; + + // --- 关键改动:构建带有隐藏标识的消息内容 --- + String messageText = "您有一条新的【" + taskName + "】待办任务需要处理,"; + // 我们嵌入一个隐藏的span,它包含了流程实例ID和活动ID作为唯一标识 + String hiddenIdentifier = String.format("", + execution.getProcessInstanceId(), + activityId); + + String finalContent = messageText + hiddenIdentifier; + + Long senderId = getSenderId(); + + for (Long receiverId : receiverIdList) { + try { + CphMsg cphMsg = new CphMsg(); + cphMsg.setReceiver(receiverId); + cphMsg.setSender(senderId); + cphMsg.setContent(finalContent); // 使用包含隐藏标识的内容 + + cphMsgService.insertCphMsg(cphMsg); + log.info("已成功为用户 [{}] 创建内部消息 (关联 Activity ID: {})。", receiverId, activityId); + } catch (Exception e) { + log.error("为用户 [{}] 创建内部消息时发生异常: {}", receiverId, e.getMessage(), e); + } + } + } + + /** + * 智能地从流程变量中解析出接收人ID列表。 + * 策略是: + * 1. 优先检查 'userList' 变量,这通常用于多实例任务。 + * 2. 如果 'userList' 不存在或无效,则检查 'approval' 变量,这通常用于单实例任务。 + * + * @param execution 当前的执行实例 + * @return 包含一个或多个用户ID的列表,如果都找不到则返回空列表。 + */ + @SuppressWarnings("unchecked") + private List getReceiverIds(DelegateExecution execution) { + // 优先检查 'userList' (用于多实例) + Object userListObj = execution.getVariable("userList"); + if (userListObj instanceof List && !((List) userListObj).isEmpty()) { + try { + // 确保列表内容是Long类型 + return (List) userListObj; + } catch (ClassCastException e) { + log.error("流程变量 'userList' 不是 Long 类型的列表。", e); + } + } + + // 其次检查 'approval' (用于单实例) + Object approvalObj = execution.getVariable("approval"); + if (approvalObj instanceof Number) { + return Collections.singletonList(((Number) approvalObj).longValue()); + } + // 兼容字符串形式的ID + if (approvalObj instanceof String) { + try { + return Collections.singletonList(Long.parseLong((String) approvalObj)); + } catch (NumberFormatException e) { + log.warn("流程变量 'approval' 的值 '{}' 无法转换为Long类型。", approvalObj); + } + } + + // 如果都找不到,返回一个不可变的空列表 + return Collections.emptyList(); + } + + /** + * 安全地获取当前操作的发送人ID。 + * 在异步执行的监听器中,可能无法获取到安全上下文,因此提供了默认值。 + * + * @return 发送人用户ID,默认为1L (系统管理员)。 + */ + private Long getSenderId() { + try { + Long userId = SecurityUtils.getUserId(); + // 如果SecurityUtils返回null,也使用默认值 + return userId != null ? userId : 1L; + } catch (Exception e) { + log.warn("在监听器中获取发送人ID时发生异常,将使用默认系统管理员ID(1L)。异常信息: {}", e.getMessage()); + return 1L; // 默认为系统管理员 + } + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/InitiatorResultListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/InitiatorResultListener.java new file mode 100644 index 0000000..0aede46 --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/InitiatorResultListener.java @@ -0,0 +1,61 @@ +package com.srs.flowable.listener.disciplinary; + +import com.srs.common.utils.WeChatUtil; +import com.srs.common.utils.spring.SpringUtils; +import com.srs.flowable.mapper.DisciplinaryMapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.stereotype.Component; + +/** + * 执行监听器:在“申请人(辅导员)接收”节点开始时, 知无涯 + * 向流程发起人发送企业微信通知,告知其结果已出。 + */ +@Component +@Slf4j +public class InitiatorResultListener implements ExecutionListener { + + @Override + public void notify(DelegateExecution delegateExecution) { + log.info("流程实例 [{}]: 已进入辅导员接收结果节点,准备向发起人发送通知。", delegateExecution.getProcessInstanceId()); + + try { + DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class); + + // 1. 从流程变量中获取发起人的ID。Flowable会自动将发起人ID存入名为 "INITIATOR" 的变量中。 + // 这个变量通常是 String 类型。 + Object initiatorObj = delegateExecution.getVariable("INITIATOR"); + if (initiatorObj == null) { + log.warn("在流程实例 [{}] 中未找到发起人(INITIATOR)变量,无法发送通知。", delegateExecution.getProcessInstanceId()); + return; + } + + // 2. 将发起人ID转换为 Long 类型,以便查询数据库 + Long initiatorUserId; + try { + initiatorUserId = Long.parseLong(initiatorObj.toString()); + } catch (NumberFormatException e) { + log.error("发起人(INITIATOR)变量 '{}' 无法转换为Long类型,无法发送通知。", initiatorObj); + return; + } + + // 3. 使用 initiatorUserId 查询对应的企业微信 userName + String initiatorUserName = disciplinaryMapper.getUserNameByUserId(initiatorUserId); + + if (initiatorUserName != null && !initiatorUserName.isEmpty()) { + // 4. 发送通知 + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + String content = "您提交的学生违纪处分申请已有最终处理结果,请点击前往处理。\""; + + weChatUtil.sendTextMessage(initiatorUserName, content); + log.info("已成功向流程发起人 ({}) 发送结果通知。", initiatorUserName); + } else { + log.warn("找到了发起人(userId:{}),但其对应的企业微信账号(userName)为空,未发送通知。", initiatorUserId); + } + + } catch (Exception e) { + log.error("向流程发起人发送结果通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/MessageProcessorListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/MessageProcessorListener.java new file mode 100644 index 0000000..4ebd9c7 --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/MessageProcessorListener.java @@ -0,0 +1,99 @@ +package com.srs.flowable.listener.disciplinary; // 您可以根据需要调整包路径 + +import com.srs.comprehensive.domain.CphMsg; +import com.srs.comprehensive.service.ICphMsgService; +import lombok.extern.slf4j.Slf4j; +import org.flowable.task.service.delegate.DelegateTask; +import org.flowable.task.service.delegate.TaskListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +import javax.annotation.PostConstruct; +import java.util.List; + +/** + * 【任务监听器】在任务完成后,删除内部消息。 + *

+ * 该监听器被设计为在UserTask的'complete'事件上触发。 + * 它通过解析消息内容中的隐藏HTML标识来找到与当前已完成任务关联的消息, + * 并将其删除。 + *

+ */ +@Component("messageProcessorListener") // 定义一个清晰的Bean名称 +@Slf4j +public class MessageProcessorListener implements TaskListener { + + @Autowired + private ICphMsgService cphMsgService; + + /** + * 依赖检查方法,确保关键服务已注入。 + */ + @PostConstruct + public void checkDependencies() { + Assert.notNull(cphMsgService, "ICphMsgService dependency not injected for MessageProcessorListener!"); + } + + @Override + public void notify(DelegateTask delegateTask) { + // 步骤 1: 获取当前任务的办理人ID + String assigneeIdStr = delegateTask.getAssignee(); + if (assigneeIdStr == null || assigneeIdStr.isEmpty()) { + log.warn("任务 [ID: {}] 没有办理人,无法处理消息。", delegateTask.getId()); + return; + } + Long receiverId; + try { + receiverId = Long.parseLong(assigneeIdStr); + } catch (NumberFormatException e) { + log.error("任务办理人ID '{}' 无法转换为Long类型。", assigneeIdStr); + return; + } + + // 步骤 2: 获取用于查找消息的唯一标识 + String processInstanceId = delegateTask.getProcessInstanceId(); + // taskDefinitionKey 对应流程图中节点的ID,即我之前在 GenericMessageListener 中存入的 activityId + String activityId = delegateTask.getTaskDefinitionKey(); + + log.info("任务 [ID: {}, Name: {}] (办理人: {}) 已完成。开始查找并处理关联的内部消息...", + delegateTask.getId(), delegateTask.getName(), receiverId); + log.debug("用于查找的关联标识 -> ProcInst ID: [{}], Activity ID: [{}]", processInstanceId, activityId); + + try { + // 步骤 3: 构造用于模糊查询的、唯一的标识字符串 + // 这个字符串必须与 GenericMessageListener 中生成该标识的逻辑完全一致 + String searchIdentifier = String.format("data-proc-inst-id='%s' data-activity-id='%s'", + processInstanceId, + activityId); + + // 步骤 4: 创建查询参数并调用服务 + CphMsg queryParam = new CphMsg(); + queryParam.setReceiver(receiverId); + queryParam.setContent(searchIdentifier); // 将标识作为模糊查询的内容 + + // 注意:这一步要求 CphMsgMapper.xml 中的 selectCphMsgListForFlowable 方法支持对 content 字段的 LIKE 查询 + List messagesToProcess = cphMsgService.selectCphMsgListForFlowable(queryParam); + + if (messagesToProcess.isEmpty()) { + log.info("未找到需要为用户 [{}] 处理的、与当前任务关联的消息。", receiverId); + return; + } + + // 步骤 5: 遍历所有找到的消息,并进行处理(删除) + for (CphMsg msg : messagesToProcess) { + try { + cphMsgService.deleteCphMsgById(msg.getId()); + log.info("已成功删除与任务关联的消息 [ID: {}]。", msg.getId()); + } catch (Exception e) { + // 捕获单个消息删除失败的异常,确保不影响其他消息的处理 + log.error("删除消息 [ID: {}] 时发生异常: {}", msg.getId(), e.getMessage(), e); + } + } + + } catch (Exception e) { + // 捕获整个处理过程中的异常,确保不影响主流程的流转 + log.error("处理用户 [{}] 的内部消息时发生严重异常: {}", receiverId, e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/SchoolLeaderApprovalListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/SchoolLeaderApprovalListener.java new file mode 100644 index 0000000..5bc4cfa --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/SchoolLeaderApprovalListener.java @@ -0,0 +1,55 @@ +package com.srs.flowable.listener.disciplinary; + +import com.srs.common.utils.WeChatUtil; +import com.srs.common.utils.spring.SpringUtils; +import com.srs.flowable.mapper.DisciplinaryMapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.stereotype.Component; +import java.util.List; + +/** + * 执行监听器:在“校领导审批”节点开始时,向校领导发送企业微信通知。 知无涯 + * (简化版:因为流程能到达此节点,即表示前序节点已全票通过) + */ +@Component +@Slf4j +public class SchoolLeaderApprovalListener implements ExecutionListener { + + @Override + public void notify(DelegateExecution delegateExecution) { + log.info("流程实例 [{}]: 已进入校领导审批节点,准备发送通知。", delegateExecution.getProcessInstanceId()); + + try { + DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class); + + // 1. 【核心】定义“校领导”这个角色的 role_key + final String LEADER_ROLE_KEY = "xldsp"; + + // 2. 根据角色Key查询用户ID列表 + List userIdList = disciplinaryMapper.getApprovalByRoleKey(LEADER_ROLE_KEY); + + if (userIdList == null || userIdList.isEmpty()) { + log.warn("根据角色Key '{}' 未找到任何校领导用户,无法发送通知。", LEADER_ROLE_KEY); + return; + } + + // 3. 批量查询 userName 并发送通知 + List userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList); + if (!userNameList.isEmpty()) { + String toUser = String.join("|", userNameList); + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + String content = "校领导您有一条新的学生违纪审批任务待处理,请点击前往处理。"; + + weChatUtil.sendTextMessage(toUser, content); + log.info("已成功向校领导 (角色: {}) 发送通知。接收人: {}", LEADER_ROLE_KEY, toUser); + } else { + log.warn("角色 '{}' 中找到了 {} 个用户,但无人配置企业微信账号,未发送任何通知。", LEADER_ROLE_KEY, userIdList.size()); + } + + } catch (Exception e) { + log.error("向校领导发送企业微信通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/StuInfoListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/StuInfoListener.java index cab8e3e..40c4281 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/StuInfoListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/StuInfoListener.java @@ -1,12 +1,17 @@ package com.srs.flowable.listener.disciplinary; +import com.srs.common.utils.WeChatUtil; +import com.srs.common.utils.spring.SpringUtils; +import com.srs.flowable.mapper.DisciplinaryMapper; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; /** - * 设置处分学生审批参数 + * 流程结束监听器。 + * 在流程的最终节点,向当事学生发送处理结果的通知。 知无涯 */ @Component @Slf4j @@ -14,10 +19,41 @@ public class StuInfoListener implements ExecutionListener { @Override public void notify(DelegateExecution delegateExecution) { - Long stuId = (Long)delegateExecution.getVariable("stuId"); + // 1. 从流程变量中获取学生ID + Object stuIdObj = delegateExecution.getVariable("stuId"); - log.info("学生信息:{}",stuId); - delegateExecution.setVariable("approval",stuId); - // todo 企业微信推送消息 + // 安全检查:确保stuId存在且类型正确 + if (!(stuIdObj instanceof Long)) { + log.error("流程实例 [{}] 在结束时未能获取到有效的 'stuId' 变量,无法发送最终通知。", delegateExecution.getProcessInstanceId()); + return; + } + Long stuId = (Long) stuIdObj; + log.info("流程实例 [{}] 即将结束,准备向学生ID: {} 发送最终通知。", delegateExecution.getProcessInstanceId(), stuId); + + // 2. 向该学生发送企业微信通知 + try { + DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class); + + // 根据学生ID查询其对应的企业微信账号(userName) + String userName = disciplinaryMapper.getUserNameByUserId(stuId); + + // 检查是否找到了对应的企业微信账号 + if (!StringUtils.hasText(userName)) { + log.warn("流程实例 [{}]: 根据学生ID '{}' 未找到对应的企业微信账号,无法发送最终通知。", delegateExecution.getProcessInstanceId(), stuId); + return; // 中止发送逻辑 + } + + // 3. 构建并发送通知内容 + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + String content = "您好,关于您的违纪处理流程已办结,最终结果已生成。请点击登录系统查看详情。"; + + weChatUtil.sendTextMessage(userName, content); + log.info("流程实例 [{}]: 已成功向学生ID '{}' (企业微信账号: {}) 发送了流程办结通知。", delegateExecution.getProcessInstanceId(), stuId, userName); + + } catch (Exception e) { + // 即使通知失败,也不应影响流程的正常结束 + log.error("流程实例 [{}]: 在向学生ID '{}' 发送最终通知时出现异常。错误详情: {}", + delegateExecution.getProcessInstanceId(), stuId, e.getMessage(), e); + } } -} +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XGLDSHListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XGLDSHListener.java new file mode 100644 index 0000000..30580fb --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XGLDSHListener.java @@ -0,0 +1,51 @@ +package com.srs.flowable.listener.disciplinary; + +import com.srs.common.utils.WeChatUtil; +import com.srs.common.utils.spring.SpringUtils; +import com.srs.flowable.mapper.DisciplinaryMapper; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; + +import java.util.List; + +public class XGLDSHListener implements ExecutionListener { + + @Override + public void notify(DelegateExecution delegateExecution) { + DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class); + + // 固定角色 Key + final String TARGET_ROLE_KEY = "xgldsp"; + + // 步骤 1: 查询角色下的所有审批人 userId + List userIdList = disciplinaryMapper.getApprovalByRoleKey(TARGET_ROLE_KEY); + + if (userIdList == null || userIdList.isEmpty()) { + throw new RuntimeException("未找到角色 '" + TARGET_ROLE_KEY + "' 的审批人员,无法发送通知。"); + } + + try { + // 步骤 2: 查询 userName 列表 + List userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList); + + if (userNameList != null && !userNameList.isEmpty()) { + String toUser = String.join("|", userNameList); + + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + + String content = "您有一条新的学生违纪审批任务待处理," + + "请点击前往处理。"; + + // 步骤 3: 发送企业微信通知 + weChatUtil.sendTextMessage(toUser, content); +// log.info("已成功向角色 '{}' 的成员发送企业微信审批通知。接收人: {}", TARGET_ROLE_KEY, toUser); + + } else { +// log.warn("角色 '{}' 下找到 {} 个用户,但无对应的企业微信账号,无法发送通知。", TARGET_ROLE_KEY, userIdList.size()); + } + + } catch (Exception e) { +// log.error("向角色 '{}' 发送企业微信通知时出现异常,但流程将继续。错误详情: {}", TARGET_ROLE_KEY, e.getMessage(), e); + } + } +} diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java index 13a0cd1..16faf40 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java @@ -10,7 +10,7 @@ import org.springframework.stereotype.Component; import java.util.List; /** - * 学生教育管理科审核节点的执行监听器。 + * 学生教育管理科审核节点的执行监听器。 知无涯 * 向“学生教育管理科”角色发送企业微信通知。 */ @Component diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XWGSListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XWGSListener.java index 4394ee1..5345fb0 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XWGSListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XWGSListener.java @@ -15,7 +15,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestBody; /** - * 根据辅导员的部门id,查询该部门的学无干事人员 + * 根据辅导员的部门id,查询该部门的学无干事人员 知无涯 */ @Component @Slf4j diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java index ce8ecea..a318bed 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java @@ -1,55 +1,74 @@ package com.srs.flowable.listener.disciplinary; import com.srs.common.utils.WeChatUtil; -import com.srs.common.utils.spring.SpringUtils; import com.srs.flowable.mapper.DisciplinaryMapper; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; +import java.util.stream.Collectors; /** - * 学院违纪处理委员会审批 + * 【线上监听器】为“学院违纪处理委员会”多实例任务准备数据并发送通知 知无涯 */ -@Component +@MapperScan("com.srs.flowable.mapper") +@Component("xywjclwyhListener") @Slf4j public class XYWJCLWYHListener implements ExecutionListener { + // 推荐:使用依赖注入,而不是SpringUtils + @Autowired + private DisciplinaryMapper disciplinaryMapper; + + @Autowired + private WeChatUtil weChatUtil; + @Override - public void notify(DelegateExecution delegateExecution) { - DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean("disciplinaryMapper"); - // 步骤 1: 根据角色Key获取审批人ID列表 (第1次DB查询) + public void notify(DelegateExecution execution) { + log.info("流程实例 [{}]: 在进入'学院违纪处理委员会'节点前,开始准备审批数据...", execution.getProcessInstanceId()); + + // 步骤 1: 单一数据库查询,获取所有需要的审批人信息 (ID和企微账号) + // List userInfos = disciplinaryMapper.getUserInfoByRoleKey("xywjclwyh"); + // 注意:如果您不方便修改Mapper,这里可以保持原来的两次查询,逻辑依然正确。 + // 我们先用原始的两次查询来演示,更具普适性。 + List userIdList = disciplinaryMapper.getApprovalByRoleKey("xywjclwyh"); - if (userIdList == null || userIdList.isEmpty()){ - log.error("未找到角色相关信息"); + + if (userIdList == null || userIdList.isEmpty()) { + log.error("角色 'xywjclwyh' 未配置或未找到任何有效用户,流程无法继续。"); throw new RuntimeException("学院违纪处理委员会审批人员未设置"); - }else{ - delegateExecution.setVariable("userList",userIdList); - // todo 企业微信推送消息 - // --- 企业微信推送消息模块 --- - try { - // 步骤 2: 一次性批量查询所有有效的userName (第2次DB查询,性能最优) - List userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList); - - if (!userNameList.isEmpty()) { - // 步骤 3: 将 userName 列表用 "|" 连接成一个字符串 - String toUser = String.join("|", userNameList); - - WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); - String content = "您有一条新的学生违纪审批任务待处理,请点击前往处理。"; - - weChatUtil.sendTextMessage(toUser, content); - log.info("已成功向学院违纪处理委员会发送群组通知。接收人: {}", toUser); - } else { - log.warn("角色 'xywjclwyh' 存在审批人,但无人配置企业微信账号,未发送任何通知。"); - } - } catch (Exception e) { - // 保证即使通知失败,流程也能继续 - log.error("向学院违纪处理委员会发送企业微信通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e); - } } + // 步骤 2: 设置多实例任务需要的流程变量 (这是“线上”为“节点”准备的核心数据) + execution.setVariable("userList", userIdList); + log.info("流程实例 [{}]: 已成功设置'userList'变量,包含 {} 个审批人。", execution.getProcessInstanceId(), userIdList.size()); + + // 步骤 3: 发送企业微信通知 + sendWeChatNotification(userIdList, execution.getProcessInstanceId()); } -} + + /** + * 封装并执行企业微信通知逻辑 + */ + private void sendWeChatNotification(List userIdList, String processInstanceId) { + try { + List userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList); + if (userNameList == null || userNameList.isEmpty()) { + log.warn("流程实例 [{}]: 角色'xywjclwyh'存在审批人ID,但无人配置企微账号,未发送通知。", processInstanceId); + return; + } + + String toUser = String.join("|", userNameList); + String content = "您有一条新的学生违纪审批任务待处理,请点击前往处理。"; + + weChatUtil.sendTextMessage(toUser, content); + log.info("流程实例 [{}]: 已成功向学院违纪处理委员会发送通知。接收人: {}", processInstanceId, toUser); + } catch (Exception e) { + log.error("流程实例 [{}]: 发送企微通知时出现异常,但不影响主流程。错误: {}", processInstanceId, e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/relieve/StuCounselorListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/relieve/StuCounselorListener.java index 075da46..4f9ddce 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/relieve/StuCounselorListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/relieve/StuCounselorListener.java @@ -1,11 +1,13 @@ package com.srs.flowable.listener.relieve; +import com.srs.common.utils.WeChatUtil; import com.srs.common.utils.spring.SpringUtils; import com.srs.flowable.mapper.LeaveMapper; import com.srs.flowable.mapper.RelieveMapper; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Map; @@ -17,6 +19,9 @@ import java.util.Map; @Slf4j public class StuCounselorListener implements ExecutionListener { + @Autowired + private WeChatUtil weChatUtil; + @Override public void notify(DelegateExecution delegateExecution) { RelieveMapper relieveMapper = (RelieveMapper) SpringUtils.getBean("relieveMapper"); @@ -31,6 +36,36 @@ public class StuCounselorListener implements ExecutionListener { delegateExecution.setVariable("deptId",map.get("deptId")); // todo 企业微信推送消息 + /** + * 庞世斌 + */ + try { + Long userId = map.get("userId"); + + // 使用 userId 查询对应的企业微信账号 + String userName = relieveMapper.getUserNameByUserId(userId); + + if (userName != null && !userName.isEmpty()) { + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + + // 构造包含超链接的消息内容 + String content = "您有一条新的学生违纪审批任务待处理," + + "点此查看"; + + // 发送企业微信消息 + weChatUtil.sendTextMessage(userName, content); + log.info("✅ 已成功向学务干事(userName:{})发送企业微信审批通知。", userName); + + } else { + // 如果找不到 userName,记录警告日志,但流程继续 + log.warn("⚠ 找到了审批人(userId:{}), 但其对应的企业微信账号(userName)为空,无法发送通知。", userId); + } + } catch (Exception e) { + Long userId = map.get("userId"); // 确保日志里带上 userId + log.error("❌ 向学务干事(userId:{})发送企业微信通知时出现异常,但流程将继续。错误详情:", userId, e); + } + + }else { throw new RuntimeException("获取辅导员信息失败"); diff --git a/srs-flowable/src/main/java/com/srs/flowable/mapper/DisciplinaryMapper.java b/srs-flowable/src/main/java/com/srs/flowable/mapper/DisciplinaryMapper.java index 3b45c21..73cd76f 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/mapper/DisciplinaryMapper.java +++ b/srs-flowable/src/main/java/com/srs/flowable/mapper/DisciplinaryMapper.java @@ -23,7 +23,7 @@ public interface DisciplinaryMapper { */ String getUserNameByUserId(@Param("userId") Long userId); /** - * 知无涯新增:根据用户ID列表批量查询用户名列表 + * 知无涯 根据用户ID列表批量查询用户名列表 * @param userIdList 用户ID列表 * @return 用户的username列表 */ diff --git a/srs-flowable/src/main/java/com/srs/flowable/mapper/RelieveMapper.java b/srs-flowable/src/main/java/com/srs/flowable/mapper/RelieveMapper.java index 76971ae..e354e3c 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/mapper/RelieveMapper.java +++ b/srs-flowable/src/main/java/com/srs/flowable/mapper/RelieveMapper.java @@ -11,6 +11,10 @@ public interface RelieveMapper { Map getCounselorInfo(Long stuUserId); + + + String getUserNameByUserId ( long userId); + /** * 查询学生解除处分申请 * diff --git a/srs-flowable/src/main/resources/mapper/RelieveMapper.xml b/srs-flowable/src/main/resources/mapper/RelieveMapper.xml index fbd358b..2978efc 100644 --- a/srs-flowable/src/main/resources/mapper/RelieveMapper.xml +++ b/srs-flowable/src/main/resources/mapper/RelieveMapper.xml @@ -113,6 +113,12 @@ where relieve_id = #{relieveId} + + \ No newline at end of file diff --git a/srs-framework/src/main/java/com/srs/framework/web/service/TokenService.java b/srs-framework/src/main/java/com/srs/framework/web/service/TokenService.java index 9b7a59a..cf5d8e9 100644 --- a/srs-framework/src/main/java/com/srs/framework/web/service/TokenService.java +++ b/srs-framework/src/main/java/com/srs/framework/web/service/TokenService.java @@ -209,7 +209,7 @@ public class TokenService * @param request * @return token */ - private String getToken(HttpServletRequest request) + public String getToken(HttpServletRequest request) { String token = request.getHeader(header); if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) diff --git a/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryApplication.java b/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryApplication.java index 1a179c0..039f39b 100644 --- a/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryApplication.java +++ b/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryApplication.java @@ -222,6 +222,14 @@ public class RtStuDisciplinaryApplication extends BaseEntity { @Excel(name = "籍贯") private String jg; + /** + * 政治面貌 + */ + @ApiModelProperty("政治面貌") + @TableField("political_status") + @Excel(name = "政治面貌") + private String politicalStatus; + /** * 市/县 */ diff --git a/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryRelieve.java b/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryRelieve.java index 13d42f0..f94c8e7 100644 --- a/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryRelieve.java +++ b/srs-routine/src/main/java/com/srs/routine/domain/RtStuDisciplinaryRelieve.java @@ -214,6 +214,14 @@ public class RtStuDisciplinaryRelieve extends BaseEntity { @Excel(name = "籍贯") private String jg; + /** + * 政治面貌 + */ + @ApiModelProperty("政治面貌") + @TableField("political_status") + @Excel(name = "政治面貌") + private String politicalStatus; + /** * 市/县 */ diff --git a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java index bb95bfe..95679a7 100644 --- a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java +++ b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java @@ -265,7 +265,7 @@ public class RtStuDisciplinaryApplicationServiceImpl extends ServiceImpl sysUsers = sysUserMapper.selectAllocatedList(sysUser); + + // 提取二级学院的书记的账号 + List userNames = sysUsers.stream() + .map(SysUser::getUserName) + .collect(Collectors.toList()); + + // 只有政治面貌是团员时才发送企业微信消息 + if ("团员".equals(politicalStatus)) { + // 消息内容 + String messageContent = "【解除处分】" + applicantName + "的解除处分申请已通过审核。"; + int batchSize = 10; + for (int i = 0; i < userNames.size(); i += batchSize) { + List batch = userNames.subList(i, Math.min(i + batchSize, userNames.size())); + // 拼接成"user1|user2|user3"格式 + String toUser = String.join("|", batch); + // 调用企业微信发送消息方法 + weChatUtil.sendTextMessage(toUser, messageContent); + } + } + } catch (Exception e) { + log.error("发送企业微信消息失败:", e); } } return dto; diff --git a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuMultiLevelReviewServiceImpl.java b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuMultiLevelReviewServiceImpl.java index 2a381ec..b117b83 100644 --- a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuMultiLevelReviewServiceImpl.java +++ b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuMultiLevelReviewServiceImpl.java @@ -117,7 +117,7 @@ public class RtStuMultiLevelReviewServiceImpl extends ServiceImpl 0) { String messageContent = rtStuMultiLevelReview.getNotes(); if (messageContent == null || messageContent.trim().isEmpty()) { - messageContent = "你申请办理的学生证制作完成,长堽校区前往xxx领取,里建校区前往xxx领取"; + messageContent = "你申请办理的学生证制作完成,长堽校区前往经管楼学工处1-1办公室领取,里建校区前往“一站式”学生社区大厅领取"; } weChatUtil.sendTextMessage(rtStuMultiLevelReview.getStuNo(), messageContent); diff --git a/srs-routine/src/main/resources/mapper/routine/RtStuDisciplinaryApplicationMapper.xml b/srs-routine/src/main/resources/mapper/routine/RtStuDisciplinaryApplicationMapper.xml index ed4c2d0..6c710d0 100644 --- a/srs-routine/src/main/resources/mapper/routine/RtStuDisciplinaryApplicationMapper.xml +++ b/srs-routine/src/main/resources/mapper/routine/RtStuDisciplinaryApplicationMapper.xml @@ -34,6 +34,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -44,7 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select application_id, applicant_id, applicant_name, penalty_number, stu_no, stu_name, stu_id, penalty_type, penalty_status, violation_date, expiration_date, evidence_upload, penalty_recommendation, violation_regulations, submission_status, process_instance_id, deploy_id, create_by, create_time, update_by, update_time, remark, gender, - department_Name, grade_name, class_name, mz, birthday, jg, hksz2,disposition_service,letter_service,disciplinary_date from rt_stu_disciplinary_application + department_Name, grade_name, class_name, mz, birthday, jg,political_status,hksz2,disposition_service,letter_service,disciplinary_date from rt_stu_disciplinary_application + + + \ No newline at end of file