Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -9,7 +9,10 @@ import com.srs.common.core.controller.BaseController;
|
||||
// OkHttp 显式导入
|
||||
import com.srs.common.exception.ServiceException;
|
||||
import com.srs.common.utils.SecurityUtils;
|
||||
import com.srs.common.utils.ServletUtils;
|
||||
import com.srs.framework.web.service.TokenService;
|
||||
import com.srs.teacher.domain.dto.ConversationDTO;
|
||||
import com.srs.web.core.config.DifyConfig;
|
||||
import okhttp3.*;
|
||||
|
||||
// Spring 显式导入(不要用 *)
|
||||
@@ -41,62 +44,8 @@ import java.util.concurrent.CompletableFuture;
|
||||
@RestController
|
||||
@RequestMapping("/aitutor/aichat")
|
||||
public class AiChatController extends BaseController {
|
||||
|
||||
// private static final String DIFY_BASE_URL = "http://172.16.129.101:6100";
|
||||
private static final String DIFY_BASE_URL = "http://47.112.118.149:8100";
|
||||
/**
|
||||
* Dify API的访问密钥
|
||||
* 用于身份验证,授权访问Dify服务
|
||||
*/
|
||||
// private static final String DIFY_API_KEY = "app-BfPtBZDNBuHOS9K1PaZrxQYE";
|
||||
private static final String DIFY_API_KEY = "app-2wjqcYI9n6igHTVHdH8qXlnh";
|
||||
/**
|
||||
* Dify API的URL地址
|
||||
* 用于发送聊天消息请求到Dify服务
|
||||
*/
|
||||
|
||||
private static final String DIFY_API_URL = DIFY_BASE_URL+"/v1/chat-messages";
|
||||
|
||||
/**
|
||||
* Dify反馈API的基础URL
|
||||
* 用于提交消息反馈(点赞、点踩等)
|
||||
*/
|
||||
private static final String DIFY_FEEDBACK_BASE_URL = DIFY_BASE_URL+"/v1/messages";
|
||||
|
||||
/**
|
||||
* Dify获取反馈API的基础URL
|
||||
* 用于获取消息反馈(点赞、点踩等)
|
||||
*/
|
||||
private static final String DIFY_API_FEEDBACK_URL = DIFY_BASE_URL+"/v1/app/feedbacks?page=";
|
||||
|
||||
/**
|
||||
* Dify消息历史记录API的基础URL
|
||||
* 用于获取消息历史记录
|
||||
*/
|
||||
private static final String DIFY_API_HISTORY_URL = DIFY_BASE_URL+"/v1/messages";
|
||||
|
||||
/**
|
||||
* Dify会话API的基础URL
|
||||
* 用于获取会话列表
|
||||
*/
|
||||
private static final String DIFY_CONVERSATIONS_URL = DIFY_BASE_URL+"/v1/conversations";
|
||||
|
||||
/**
|
||||
* Dify文件上传API的URL地址
|
||||
* 用于上传文件到Dify服务,支持图文多模态理解
|
||||
*/
|
||||
private static final String DIFY_FILES_URL = DIFY_BASE_URL+"/v1/files/upload";
|
||||
|
||||
/**
|
||||
* Redis中会话ID的key前缀
|
||||
*/
|
||||
private static final String CONVERSATION_ID_CACHE_PREFIX = "dify:conversation:id:";
|
||||
|
||||
/**
|
||||
* Redis中会话ID的过期时间(小时)
|
||||
*/
|
||||
private static final int CONVERSATION_ID_CACHE_EXPIRE_HOURS = 1;
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
/**
|
||||
* HTTP客户端实例
|
||||
* 配置了5分钟的读取超时时间,用于与Dify API进行通信
|
||||
@@ -114,6 +63,13 @@ public class AiChatController extends BaseController {
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* Dify配置类
|
||||
* 用于获取Dify的API密钥和其他配置参数
|
||||
*/
|
||||
@Autowired
|
||||
private DifyConfig difyConfig;
|
||||
|
||||
/** 为本次请求设置 “总超时”(含连接/读写),避免无限挂起 */
|
||||
private Response execWithTimeouts(Request req, int callSecs, int readSecs, int writeSecs) throws IOException {
|
||||
OkHttpClient shortClient = client.newBuilder()
|
||||
@@ -164,11 +120,13 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 在主线程中获取当前用户名,避免在异步线程中获取安全上下文
|
||||
String currentUsername = SecurityUtils.getLoginUser().getUsername();
|
||||
|
||||
// 获取JWT token
|
||||
String currentUserToken = tokenService.getToken(ServletUtils.getRequest());
|
||||
// String currentUserToken = SecurityUtils.getLoginUser().getToken();
|
||||
// 异步执行请求处理,避免阻塞主线程
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
sendToDifyAndStream(requestData, emitter, currentUsername);
|
||||
sendToDifyAndStream(requestData, emitter, currentUsername, currentUserToken);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
@@ -194,7 +152,8 @@ public class AiChatController extends BaseController {
|
||||
* @param currentUsername
|
||||
* @throws IOException 当网络请求或IO操作失败时抛出
|
||||
*/
|
||||
private void sendToDifyAndStream(Map<String, Object> requestData, SseEmitter emitter, String currentUsername)
|
||||
private void sendToDifyAndStream(Map<String, Object> requestData, SseEmitter emitter, String currentUsername,
|
||||
String currentUserToken)
|
||||
throws IOException {
|
||||
|
||||
// 构建请求体参数
|
||||
@@ -231,9 +190,9 @@ public class AiChatController extends BaseController {
|
||||
inputs.put("user_name", userName);
|
||||
}
|
||||
|
||||
Object userToken = requestData.get("user_token");
|
||||
if (userToken != null) {
|
||||
inputs.put("user_token", userToken);
|
||||
// Object userToken = requestData.get("user_token");
|
||||
if (currentUserToken != null) {
|
||||
inputs.put("user_token", currentUserToken);
|
||||
}
|
||||
|
||||
Object userRole = requestData.get("user_role");
|
||||
@@ -241,6 +200,11 @@ public class AiChatController extends BaseController {
|
||||
inputs.put("user_role", userRole);
|
||||
}
|
||||
|
||||
Object userPlatform = requestData.get("user_platform");
|
||||
if (userPlatform != null) {
|
||||
inputs.put("user_platform", userPlatform);
|
||||
}
|
||||
|
||||
bodyMap.put("inputs", inputs);
|
||||
|
||||
// 自动为对话生成名称
|
||||
@@ -254,8 +218,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 构建HTTP请求
|
||||
Request httpRequest = new Request.Builder()
|
||||
.url(DIFY_API_URL) // 设置请求URL
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY) // 添加认证头
|
||||
.url(difyConfig.getApiUrl()) // 设置请求URL
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey()) // 添加认证头
|
||||
.addHeader("Content-Type", "application/json") // 设置内容类型
|
||||
.post(body) // 设置为POST请求
|
||||
.build();
|
||||
@@ -385,8 +349,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 调用 Dify API
|
||||
Request request = new Request.Builder()
|
||||
.url(DIFY_FEEDBACK_BASE_URL + "/" + messageId + "/feedbacks")
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.url(difyConfig.getFeedbackBaseUrl() + "/" + messageId + "/feedbacks")
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.post(body)
|
||||
.build();
|
||||
@@ -432,8 +396,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(DIFY_API_FEEDBACK_URL + page + "&limit=" + limitValue)
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.url(difyConfig.getApiFeedbackUrl() + page + "&limit=" + limitValue)
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.get()
|
||||
.build();
|
||||
@@ -594,7 +558,7 @@ public class AiChatController extends BaseController {
|
||||
}
|
||||
|
||||
user = user.trim();
|
||||
String cacheKey = CONVERSATION_ID_CACHE_PREFIX + user;
|
||||
String cacheKey = difyConfig.getConversationCachePrefix() + user;
|
||||
|
||||
// 从Redis中获取会话ID
|
||||
String conversationId = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
@@ -626,11 +590,11 @@ public class AiChatController extends BaseController {
|
||||
conversationId = conversationId.trim();
|
||||
|
||||
// 将用户与会话ID绑定存储到Redis中
|
||||
String cacheKey = CONVERSATION_ID_CACHE_PREFIX + user;
|
||||
String cacheKey = difyConfig.getConversationCachePrefix() + user;
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
cacheKey,
|
||||
conversationId,
|
||||
Duration.ofHours(CONVERSATION_ID_CACHE_EXPIRE_HOURS));
|
||||
Duration.ofHours(difyConfig.getConversationCacheExpireHours()));
|
||||
} catch (Exception e) {
|
||||
System.out.println("绑定会话ID时发生错误: " + e.getMessage());
|
||||
}
|
||||
@@ -642,7 +606,7 @@ public class AiChatController extends BaseController {
|
||||
public List<ConversationDTO> getConversations(String user, String lastId, int limit, String sortBy)
|
||||
throws IOException {
|
||||
// 构建带查询参数的 URL
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(DIFY_CONVERSATIONS_URL).newBuilder();
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(difyConfig.getConversationsUrl()).newBuilder();
|
||||
urlBuilder.addQueryParameter("user", user);
|
||||
if (lastId != null && !lastId.trim().isEmpty()) {
|
||||
urlBuilder.addQueryParameter("last_id", lastId);
|
||||
@@ -653,7 +617,7 @@ public class AiChatController extends BaseController {
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.get()
|
||||
.build();
|
||||
|
||||
@@ -725,7 +689,7 @@ public class AiChatController extends BaseController {
|
||||
int finalLimit = limit != null && limit > 0 ? Math.min(limit, 100) : 20;
|
||||
|
||||
// 构建请求 URL
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(DIFY_API_HISTORY_URL).newBuilder();
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(difyConfig.getApiHistoryUrl()).newBuilder();
|
||||
urlBuilder.addQueryParameter("conversation_id", conversationId);
|
||||
urlBuilder.addQueryParameter("user", user);
|
||||
if (firstId != null && !firstId.trim().isEmpty()) {
|
||||
@@ -735,7 +699,7 @@ public class AiChatController extends BaseController {
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.addHeader("Accept", "application/json")
|
||||
.get()
|
||||
.build();
|
||||
@@ -823,8 +787,8 @@ public class AiChatController extends BaseController {
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(DIFY_FILES_URL)
|
||||
.addHeader("Authorization", "Bearer " + DIFY_API_KEY)
|
||||
.url(difyConfig.getFilesUrl())
|
||||
.addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
|
||||
.post(requestBody)
|
||||
.build();
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Dify心理问题发送企业微信 知无涯
|
||||
@@ -51,18 +52,37 @@ public class WeChatMentalAlertController extends BaseController {
|
||||
return AjaxResult.error("Token无效或已过期");
|
||||
}
|
||||
|
||||
String studentId = request.getUserId();
|
||||
|
||||
// 查询辅导员信息
|
||||
SysUser teacher = sysUserMapper.selectTeacherByStuNo(request.getUserId());
|
||||
if (teacher == null || !StringUtils.hasText(teacher.getUserName())) {
|
||||
log.error("辅导员信息不完整,学号: {}", request.getUserId());
|
||||
return AjaxResult.error("未分配辅导员或信息不完整");
|
||||
}
|
||||
|
||||
// 获取今天日期
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
// 查询该学生今天已触发的心理预警次数
|
||||
int todayCount = studentMentalRatingMapper.countTodayByStudentId(studentId, today);
|
||||
|
||||
/* 保存学生心理问题评级 */
|
||||
StudentMentalRating record = new StudentMentalRating();
|
||||
record.setStudentId(request.getUserId());
|
||||
record.setRating(request.getRating());
|
||||
studentMentalRatingMapper.insert(record);
|
||||
|
||||
// 只有评级为"一级风险"时才发送企业微信通知 陈冠元
|
||||
if (!"一级风险".equals(request.getRating())) {
|
||||
return AjaxResult.success("预警已记录,因评级非一级风险未发送通知");
|
||||
}
|
||||
|
||||
// === 判断是否超过当日发送上限(3次)=== 陈冠元
|
||||
if (todayCount > 3) {
|
||||
return AjaxResult.success("预警已记录,因当日已达上限未发送通知");
|
||||
}
|
||||
|
||||
// 构建并发送消息
|
||||
try {
|
||||
String content = buildContent(request, teacher);
|
||||
@@ -82,7 +102,7 @@ public class WeChatMentalAlertController extends BaseController {
|
||||
? teacher.getNickName()
|
||||
: teacher.getUserName();
|
||||
return String.format(
|
||||
"【心理预警通知】\n" +
|
||||
"【智水AI心理预警通知】\n" +
|
||||
"辅导员:%s(%s)\n" +
|
||||
"学生姓名:%s\n" +
|
||||
"学号:%s\n" +
|
||||
|
||||
105
srs-admin/src/main/java/com/srs/web/core/config/DifyConfig.java
Normal file
105
srs-admin/src/main/java/com/srs/web/core/config/DifyConfig.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.srs.web.core.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Component // 让 Spring 扫描到
|
||||
@ConfigurationProperties(prefix = "dify")
|
||||
public class DifyConfig {
|
||||
|
||||
/**
|
||||
* Dify 服务的基础 URL,例如:http://47.112.118.149:8100
|
||||
*/
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* Dify API 密钥
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 超时时间配置(可选)
|
||||
*/
|
||||
private Duration timeout = Duration.ofSeconds(30);
|
||||
|
||||
// ================== API URLs ==================
|
||||
// 这些 URL 基于 baseUrl 自动生成,不直接暴露在配置文件中
|
||||
private String apiUrl;
|
||||
private String feedbackBaseUrl;
|
||||
private String apiFeedbackUrl;
|
||||
private String apiHistoryUrl;
|
||||
private String conversationsUrl;
|
||||
private String filesUrl;
|
||||
|
||||
// ================== Redis 缓存配置 ==================
|
||||
private String conversationCachePrefix = "dify:conversation:id:";
|
||||
private int conversationCacheExpireHours = 1;
|
||||
|
||||
// -------------------- Getters and Setters --------------------
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getApiUrl() {
|
||||
return baseUrl + "/v1/chat-messages";
|
||||
}
|
||||
|
||||
public String getFeedbackBaseUrl() {
|
||||
return baseUrl + "/v1/messages";
|
||||
}
|
||||
|
||||
public String getApiFeedbackUrl() {
|
||||
return baseUrl + "/v1/app/feedbacks?page=";
|
||||
}
|
||||
|
||||
public String getApiHistoryUrl() {
|
||||
return baseUrl + "/v1/messages";
|
||||
}
|
||||
|
||||
public String getConversationsUrl() {
|
||||
return baseUrl + "/v1/conversations";
|
||||
}
|
||||
|
||||
public String getFilesUrl() {
|
||||
return baseUrl + "/v1/files/upload";
|
||||
}
|
||||
|
||||
public String getConversationCachePrefix() {
|
||||
return conversationCachePrefix;
|
||||
}
|
||||
|
||||
public void setConversationCachePrefix(String conversationCachePrefix) {
|
||||
this.conversationCachePrefix = conversationCachePrefix;
|
||||
}
|
||||
|
||||
public int getConversationCacheExpireHours() {
|
||||
return conversationCacheExpireHours;
|
||||
}
|
||||
|
||||
public void setConversationCacheExpireHours(int conversationCacheExpireHours) {
|
||||
this.conversationCacheExpireHours = conversationCacheExpireHours;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ srs:
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080 正式:8085 测试:8088
|
||||
port: 8085
|
||||
port: 8088
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
@@ -42,6 +42,11 @@ logging:
|
||||
com.srs: debug
|
||||
org.springframework: warn
|
||||
|
||||
#dify配置
|
||||
dify:
|
||||
base-url: http://47.112.118.149:8100 #http://172.16.129.101:6100
|
||||
api-key: app-2wjqcYI9n6igHTVHdH8qXlnh #app-BfPtBZDNBuHOS9K1PaZrxQYE
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,14 @@ public interface CphMsgMapper extends BaseMapper<CphMsg>
|
||||
*/
|
||||
public int deleteCphMsgById(Long id);
|
||||
|
||||
/**
|
||||
* 根据接收人和内容中的标识来查找流程消息 (使用LIKE查询) 知无涯
|
||||
*
|
||||
* @param cphMsg 包含 receiver 和 content (作为模糊查询的标识) 的查询对象
|
||||
* @return 匹配的消息列表
|
||||
*/
|
||||
public List<CphMsg> selectCphMsgListForFlowable(CphMsg cphMsg);
|
||||
|
||||
/**
|
||||
* 批量删除消息
|
||||
*
|
||||
|
||||
@@ -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<SrsIdentifytable> {
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteSrsGraduataByIds(Long[] ids);
|
||||
|
||||
/**
|
||||
* 获取对应人的班级信息
|
||||
*
|
||||
* @param userName 用户名称
|
||||
* @return 结果
|
||||
*/
|
||||
List<SrsClass> selectDeptByDeptCodes(String userName);
|
||||
|
||||
}
|
||||
|
||||
@@ -59,6 +59,14 @@ public interface ICphMsgService
|
||||
*/
|
||||
public int deleteCphMsgById(Long id);
|
||||
|
||||
/**
|
||||
* 删除消息信息 精准匹配版
|
||||
*
|
||||
* @param cphMsg 消息
|
||||
* @return 列表
|
||||
*/
|
||||
List<CphMsg> selectCphMsgListForFlowable(CphMsg cphMsg);
|
||||
|
||||
/**
|
||||
* 根据学号查询用户ID
|
||||
*
|
||||
|
||||
@@ -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<SrsIdentifytable> {
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteSrsIdentifytableById(Long id);
|
||||
|
||||
/**
|
||||
* 获取对应人的班级信息
|
||||
*
|
||||
* @param
|
||||
* @return 集合结果
|
||||
*/
|
||||
List<SrsClass> deptDataLsit();
|
||||
}
|
||||
|
||||
@@ -95,6 +95,16 @@ public class CphMsgServiceImpl extends ServiceImpl<CphMsgMapper,CphMsg> implemen
|
||||
{
|
||||
return cphMsgMapper.deleteCphMsgById(id);
|
||||
}
|
||||
/**
|
||||
* 删除消息信息
|
||||
*
|
||||
* @param cphMsg 消息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public List<CphMsg> selectCphMsgListForFlowable(CphMsg cphMsg) {
|
||||
return cphMsgMapper.selectCphMsgListForFlowable(cphMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学号查询用户ID
|
||||
|
||||
@@ -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<SrsIdentifytableMap
|
||||
public int deleteSrsIdentifytableById(Long id) {
|
||||
return srsIdentifytableMapper.deleteSrsIdentifytableById(id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取对应人的班级信息
|
||||
*
|
||||
* @param
|
||||
* @return 集合结果
|
||||
*/
|
||||
@Override
|
||||
public List<SrsClass> deptDataLsit() {
|
||||
//获取登录人的信息
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
List<SrsClass> depts = srsIdentifytableMapper.selectDeptByDeptCodes(loginUser.getUsername());
|
||||
if(depts != null && depts.size() > 0) {
|
||||
return depts;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
@@ -84,4 +84,16 @@
|
||||
<select id="getUserIdByStuNo" parameterType="String" resultType="Long">
|
||||
select user_id from sys_user where user_name = #{stuNo}
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectCphMsgListForFlowable" parameterType="CphMsg" resultMap="CphMsgResult">
|
||||
<include refid="selectCphMsgVo"/>
|
||||
<where>
|
||||
<if test="receiver != null "> and receiver = #{receiver}</if>
|
||||
<if test="content != null and content != ''">
|
||||
and content LIKE CONCAT('%', #{content}, '%')
|
||||
</if>
|
||||
</where>
|
||||
order by id desc
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -54,6 +54,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<!-- 班级信息 -->
|
||||
<select id="selectDeptByDeptCodes" parameterType="String" resultType="com.srs.comprehensive.domain.SrsClass">
|
||||
SELECT DISTINCT v.class_id,v.class_name
|
||||
FROM srs_dev.view_stu_info v
|
||||
JOIN srs_class c ON v.teacher_id = c.teacher_id
|
||||
WHERE v.t_no = #{userName}
|
||||
</select>
|
||||
|
||||
|
||||
<!-- <insert id="insertSrsIdentifytable" parameterType="SrsIdentifytable" useGeneratedKeys="true" keyProperty="id">-->
|
||||
<!-- insert into srs_identifytable-->
|
||||
<!-- <trim prefix="(" suffix=")" suffixOverrides=",">-->
|
||||
|
||||
@@ -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<Long> userIdList = disciplinaryMapper.getApprovalByRoleKey(ARCHIVING_ROLE_KEY);
|
||||
|
||||
if (userIdList == null || userIdList.isEmpty()) {
|
||||
log.warn("根据角色Key '{}' 未找到任何归档人员,无法发送通知。", ARCHIVING_ROLE_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 批量查询 userName 并发送通知
|
||||
List<String> userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList);
|
||||
if (!userNameList.isEmpty()) {
|
||||
String toUser = String.join("|", userNameList);
|
||||
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
|
||||
String content = "您有一条新的学生违纪归档任务待处理,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import org.flowable.engine.delegate.ExecutionListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 根据辅导员部门id获取二级学院书记
|
||||
* 根据辅导员部门id获取二级学院书记 知无涯
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
|
||||
@@ -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
|
||||
* <p>
|
||||
* 该监听器被设计为在SequenceFlow的'take'事件上触发。
|
||||
* 它会智能地从流程变量中读取单个接收人(变量名: approval)或多个接收人(变量名: userList),
|
||||
* 然后为每一位接收人创建一条内部消息。
|
||||
* </p>
|
||||
*/
|
||||
@Component("genericMessageListener")
|
||||
@Slf4j
|
||||
public class GenericMessageListener implements ExecutionListener {
|
||||
|
||||
@Autowired
|
||||
private ICphMsgService cphMsgService;
|
||||
|
||||
@Override
|
||||
public void notify(DelegateExecution execution) {
|
||||
log.info("流程实例 [{}]: 触发通用消息监听器...", execution.getProcessInstanceId());
|
||||
|
||||
List<Long> 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("<span id='flow-identifier' data-proc-inst-id='%s' data-activity-id='%s' style='display:none;'></span>",
|
||||
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<Long> getReceiverIds(DelegateExecution execution) {
|
||||
// 优先检查 'userList' (用于多实例)
|
||||
Object userListObj = execution.getVariable("userList");
|
||||
if (userListObj instanceof List && !((List<?>) userListObj).isEmpty()) {
|
||||
try {
|
||||
// 确保列表内容是Long类型
|
||||
return (List<Long>) 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; // 默认为系统管理员
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "您提交的学生违纪处分申请已有最终处理结果,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。\"";
|
||||
|
||||
weChatUtil.sendTextMessage(initiatorUserName, content);
|
||||
log.info("已成功向流程发起人 ({}) 发送结果通知。", initiatorUserName);
|
||||
} else {
|
||||
log.warn("找到了发起人(userId:{}),但其对应的企业微信账号(userName)为空,未发送通知。", initiatorUserId);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("向流程发起人发送结果通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 【任务监听器】在任务完成后,删除内部消息。
|
||||
* <p>
|
||||
* 该监听器被设计为在UserTask的'complete'事件上触发。
|
||||
* 它通过解析消息内容中的隐藏HTML标识来找到与当前已完成任务关联的消息,
|
||||
* 并将其删除。
|
||||
* </p>
|
||||
*/
|
||||
@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<CphMsg> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Long> userIdList = disciplinaryMapper.getApprovalByRoleKey(LEADER_ROLE_KEY);
|
||||
|
||||
if (userIdList == null || userIdList.isEmpty()) {
|
||||
log.warn("根据角色Key '{}' 未找到任何校领导用户,无法发送通知。", LEADER_ROLE_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 批量查询 userName 并发送通知
|
||||
List<String> userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList);
|
||||
if (!userNameList.isEmpty()) {
|
||||
String toUser = String.join("|", userNameList);
|
||||
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
|
||||
String content = "校领导您有一条新的学生违纪审批任务待处理,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "您好,关于您的违纪处理流程已办结,最终结果已生成。<a href='http://zhxg.gxsdxy.cn/web/#/pages/Disciplinary/MyDisciplinary'>请点击登录系统查看详情</a>。";
|
||||
|
||||
weChatUtil.sendTextMessage(userName, content);
|
||||
log.info("流程实例 [{}]: 已成功向学生ID '{}' (企业微信账号: {}) 发送了流程办结通知。", delegateExecution.getProcessInstanceId(), stuId, userName);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 即使通知失败,也不应影响流程的正常结束
|
||||
log.error("流程实例 [{}]: 在向学生ID '{}' 发送最终通知时出现异常。错误详情: {}",
|
||||
delegateExecution.getProcessInstanceId(), stuId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Long> userIdList = disciplinaryMapper.getApprovalByRoleKey(TARGET_ROLE_KEY);
|
||||
|
||||
if (userIdList == null || userIdList.isEmpty()) {
|
||||
throw new RuntimeException("未找到角色 '" + TARGET_ROLE_KEY + "' 的审批人员,无法发送通知。");
|
||||
}
|
||||
|
||||
try {
|
||||
// 步骤 2: 查询 userName 列表
|
||||
List<String> userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList);
|
||||
|
||||
if (userNameList != null && !userNameList.isEmpty()) {
|
||||
String toUser = String.join("|", userNameList);
|
||||
|
||||
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
|
||||
|
||||
String content = "您有一条新的学生违纪审批任务待处理," +
|
||||
"<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
|
||||
|
||||
// 步骤 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生教育管理科审核节点的执行监听器。
|
||||
* 学生教育管理科审核节点的执行监听器。 知无涯
|
||||
* 向“学生教育管理科”角色发送企业微信通知。
|
||||
*/
|
||||
@Component
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
/**
|
||||
* 根据辅导员的部门id,查询该部门的学无干事人员
|
||||
* 根据辅导员的部门id,查询该部门的学无干事人员 知无涯
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
|
||||
@@ -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<DisciplinaryMapper.UserInfo> userInfos = disciplinaryMapper.getUserInfoByRoleKey("xywjclwyh");
|
||||
// 注意:如果您不方便修改Mapper,这里可以保持原来的两次查询,逻辑依然正确。
|
||||
// 我们先用原始的两次查询来演示,更具普适性。
|
||||
|
||||
List<Long> 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 企业微信推送消息
|
||||
// --- 企业微信推送消息模块 ---
|
||||
}
|
||||
|
||||
// 步骤 2: 设置多实例任务需要的流程变量 (这是“线上”为“节点”准备的核心数据)
|
||||
execution.setVariable("userList", userIdList);
|
||||
log.info("流程实例 [{}]: 已成功设置'userList'变量,包含 {} 个审批人。", execution.getProcessInstanceId(), userIdList.size());
|
||||
|
||||
// 步骤 3: 发送企业微信通知
|
||||
sendWeChatNotification(userIdList, execution.getProcessInstanceId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装并执行企业微信通知逻辑
|
||||
*/
|
||||
private void sendWeChatNotification(List<Long> userIdList, String processInstanceId) {
|
||||
try {
|
||||
// 步骤 2: 一次性批量查询所有有效的userName (第2次DB查询,性能最优)
|
||||
List<String> userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList);
|
||||
if (userNameList == null || userNameList.isEmpty()) {
|
||||
log.warn("流程实例 [{}]: 角色'xywjclwyh'存在审批人ID,但无人配置企微账号,未发送通知。", processInstanceId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userNameList.isEmpty()) {
|
||||
// 步骤 3: 将 userName 列表用 "|" 连接成一个字符串
|
||||
String toUser = String.join("|", userNameList);
|
||||
|
||||
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
|
||||
String content = "您有一条新的学生违纪审批任务待处理,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
|
||||
|
||||
weChatUtil.sendTextMessage(toUser, content);
|
||||
log.info("已成功向学院违纪处理委员会发送群组通知。接收人: {}", toUser);
|
||||
} else {
|
||||
log.warn("角色 'xywjclwyh' 存在审批人,但无人配置企业微信账号,未发送任何通知。");
|
||||
}
|
||||
log.info("流程实例 [{}]: 已成功向学院违纪处理委员会发送通知。接收人: {}", processInstanceId, toUser);
|
||||
} catch (Exception e) {
|
||||
// 保证即使通知失败,流程也能继续
|
||||
log.error("向学院违纪处理委员会发送企业微信通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e);
|
||||
log.error("流程实例 [{}]: 发送企微通知时出现异常,但不影响主流程。错误: {}", processInstanceId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 = "您有一条新的学生违纪审批任务待处理," +
|
||||
"<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>点此查看</a>";
|
||||
|
||||
// 发送企业微信消息
|
||||
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("获取辅导员信息失败");
|
||||
|
||||
@@ -23,7 +23,7 @@ public interface DisciplinaryMapper {
|
||||
*/
|
||||
String getUserNameByUserId(@Param("userId") Long userId);
|
||||
/**
|
||||
* 知无涯新增:根据用户ID列表批量查询用户名列表
|
||||
* 知无涯 根据用户ID列表批量查询用户名列表
|
||||
* @param userIdList 用户ID列表
|
||||
* @return 用户的username列表
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,10 @@ public interface RelieveMapper {
|
||||
|
||||
Map<String,Long> getCounselorInfo(Long stuUserId);
|
||||
|
||||
|
||||
|
||||
String getUserNameByUserId ( long userId);
|
||||
|
||||
/**
|
||||
* 查询学生解除处分申请
|
||||
*
|
||||
|
||||
@@ -113,6 +113,12 @@
|
||||
<include refid="selectRtStuDisciplinaryRelieveVo"/>
|
||||
where relieve_id = #{relieveId}
|
||||
</select>
|
||||
<!-- 根据userid获取到userName-->
|
||||
<select id="getUserNameByUserId" parameterType="long" resultType="string">
|
||||
SELECT user_name
|
||||
FROM sys_user
|
||||
WHERE user_id = #{userId}
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
@@ -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))
|
||||
|
||||
@@ -265,7 +265,7 @@ public class RtStuDisciplinaryApplicationServiceImpl extends ServiceImpl<RtStuDi
|
||||
|
||||
// todo 企业微信推送消息
|
||||
|
||||
AjaxResult ajaxResult = flowDefinitionService.startProcessInstanceById("flow_n27gxm4k:8:610039", variables);
|
||||
AjaxResult ajaxResult = flowDefinitionService.startProcessInstanceById("flow_n27gxm4k:22:720004", variables);
|
||||
String code = ajaxResult.get("code").toString();
|
||||
if (code.equals("200")) {
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.srs.system.mapper;
|
||||
import com.srs.system.domain.StudentMentalRating;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface StudentMentalRatingMapper {
|
||||
@@ -28,5 +29,13 @@ public interface StudentMentalRatingMapper {
|
||||
/** 单学号全部记录 */
|
||||
List<StudentMentalRating> selectByStuNo(@Param("stuNo") String stuNo);
|
||||
|
||||
/**
|
||||
* 查询今天的心理评级记录 陈冠元
|
||||
* @param studentId 学生ID
|
||||
* @param today 今天的日期
|
||||
* @return 记录数
|
||||
*/
|
||||
int countTodayByStudentId(@Param("studentId") String studentId, @Param("today") LocalDate today);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -43,4 +43,12 @@
|
||||
WHERE student_id = #{stuNo}
|
||||
ORDER BY created_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询今天该学生是否已经填写过心理评测 陈冠元 -->
|
||||
<select id="countTodayByStudentId" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM student_mental_rating
|
||||
WHERE student_id = #{studentId}
|
||||
AND DATE(created_time) = #{today}
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user