|
|
@@ -1,7 +1,10 @@
|
|
|
package com.qqflow.engine.domain.flow.service.impl;
|
|
|
|
|
|
import com.qqflow.engine.domain.ewechat.service.EnterpriseWechatMsgService;
|
|
|
+import com.qqflow.engine.domain.flow.enums.ApprovalAction;
|
|
|
+import com.qqflow.engine.domain.flow.mapper.ProcessInstanceMapper;
|
|
|
import com.qqflow.engine.domain.flow.po.ApprovalTask;
|
|
|
+import com.qqflow.engine.domain.flow.po.ProcessInstance;
|
|
|
import com.qqflow.engine.domain.flow.service.NotificationService;
|
|
|
import com.qqflow.engine.domain.system.dto.WeComConfigDTO;
|
|
|
import com.qqflow.engine.domain.system.entity.SysUser;
|
|
|
@@ -11,8 +14,12 @@ import com.qqflow.engine.domain.system.mapper.SysUserRoleMapper;
|
|
|
import com.qqflow.engine.domain.system.service.SysNotificationConfigService;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
|
|
|
@@ -30,27 +37,63 @@ public class WeComNotificationService implements NotificationService {
|
|
|
|
|
|
private static final String ASSIGNEE_TYPE_ROLE = "ROLE";
|
|
|
|
|
|
+ /** textcard 标题最多 128 字节(中文约 42 字),截断到 40 字留余量 */
|
|
|
+ private static final int TEXT_CARD_TITLE_MAX = 40;
|
|
|
+
|
|
|
private final SysUserMapper sysUserMapper;
|
|
|
private final SysUserRoleMapper sysUserRoleMapper;
|
|
|
private final SysNotificationConfigService sysNotificationConfigService;
|
|
|
private final EnterpriseWechatMsgService wechatMsgService;
|
|
|
+ private final ProcessInstanceMapper processInstanceMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * H5 前端地址(逗号分隔:第一个为实际跳转地址,其余为免登 redirect 白名单);
|
|
|
+ * 未配置时降级为纯文本通知
|
|
|
+ */
|
|
|
+ @Value("${wecom.h5-base-url:}")
|
|
|
+ private String h5BaseUrl;
|
|
|
|
|
|
@Override
|
|
|
public void notifyTaskAssigned(List<Long> assigneeIds, String assigneeType, String processName, String instanceTitle, String nodeName) {
|
|
|
if (isSuppressed() || !this.isEnabled()) return;
|
|
|
+ List<String> wecomUserIds = new ArrayList<>();
|
|
|
for (Long assigneeId : assigneeIds) {
|
|
|
String wecomUserId = resolveWeComUserId(assigneeId, assigneeType);
|
|
|
- if (wecomUserId == null) continue;
|
|
|
- String content = String.format("【待办提醒】\n流程:%s\n标题:%s\n节点:%s\n请及时处理。",
|
|
|
- processName, instanceTitle, nodeName);
|
|
|
- wechatMsgService.sendTextToUsers(Collections.singletonList(wecomUserId), content);
|
|
|
+ if (wecomUserId != null) wecomUserIds.add(wecomUserId);
|
|
|
+ }
|
|
|
+ if (wecomUserIds.isEmpty()) return;
|
|
|
+ String description = String.format("流程:%s\n标题:%s\n节点:%s\n请及时处理。", processName, instanceTitle, nodeName);
|
|
|
+ String url = h5Url();
|
|
|
+ if (url != null) {
|
|
|
+ // 文本卡片:可点击跳转企微 H5 待办(点击后免登)
|
|
|
+ wechatMsgService.sendTextCardToUsers(wecomUserIds, textCardTitle("【待办审批】", instanceTitle),
|
|
|
+ description, url + "/m/todo", "去审批");
|
|
|
+ } else {
|
|
|
+ wechatMsgService.sendTextToUsers(wecomUserIds, "【待办提醒】\n" + description);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void notifyTaskCompleted(Long instanceId, String processName, String instanceTitle,
|
|
|
String nodeName, String operatorName, String action) {
|
|
|
- // 审批完成通知:目前仅记录日志,后续可扩展
|
|
|
+ if (isSuppressed() || !this.isEnabled()) return;
|
|
|
+ // 通知流程发起人:其发起的流程处理到了哪个节点、结果如何
|
|
|
+ ProcessInstance instance = processInstanceMapper.selectById(instanceId);
|
|
|
+ if (instance == null || instance.getApplicantId() == null) return;
|
|
|
+ SysUser applicant = sysUserMapper.selectById(instance.getApplicantId());
|
|
|
+ if (applicant == null || !StringUtils.hasText(applicant.getWecomUserId())) return;
|
|
|
+ String actionText = resolveActionText(action);
|
|
|
+ // 事件里的 processName 可能为空,避免出现「流程:」空行
|
|
|
+ String processLine = StringUtils.hasText(processName) ? "流程:" + processName + "\n" : "";
|
|
|
+ String description = processLine + String.format("标题:%s\n节点「%s」已由 %s 处理(%s)",
|
|
|
+ instanceTitle, nodeName, operatorName, actionText);
|
|
|
+ String url = h5Url();
|
|
|
+ if (url != null) {
|
|
|
+ wechatMsgService.sendTextCardToUsers(Collections.singletonList(applicant.getWecomUserId()),
|
|
|
+ textCardTitle("【审批进度】", instanceTitle), description, url + "/m/todo", "查看");
|
|
|
+ } else {
|
|
|
+ wechatMsgService.sendTextToUsers(Collections.singletonList(applicant.getWecomUserId()), description);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -68,8 +111,17 @@ public class WeComNotificationService implements NotificationService {
|
|
|
public void notifyTaskOverdue(ApprovalTask task, String processName, String instanceTitle, String nodeName, boolean overdue) {
|
|
|
if (isSuppressed() || !this.isEnabled()) return;
|
|
|
String type = overdue ? "已超时催办" : "即将超时提醒";
|
|
|
- String content = String.format("【%s】\n流程:%s\n标题:%s\n节点:%s\n请及时处理。",
|
|
|
- type, processName, instanceTitle, nodeName);
|
|
|
+ String description = String.format("流程:%s\n标题:%s\n节点:%s\n请及时处理。", processName, instanceTitle, nodeName);
|
|
|
+ String url = h5Url();
|
|
|
+ if (url != null) {
|
|
|
+ List<String> wecomUserIds = ASSIGNEE_TYPE_ROLE.equalsIgnoreCase(task.getAssigneeType())
|
|
|
+ ? wechatMsgService.getRoleWecomUserIds(task.getAssigneeId())
|
|
|
+ : resolveUserWecomUserIds(task.getAssigneeId());
|
|
|
+ wechatMsgService.sendTextCardToUsers(wecomUserIds, textCardTitle("【" + type + "】", instanceTitle),
|
|
|
+ description, url + "/m/todo", "去审批");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String content = "【" + type + "】\n" + description;
|
|
|
if (ASSIGNEE_TYPE_ROLE.equalsIgnoreCase(task.getAssigneeType())) {
|
|
|
wechatMsgService.sendTextToRole(task.getAssigneeId(), content);
|
|
|
} else {
|
|
|
@@ -77,6 +129,42 @@ public class WeComNotificationService implements NotificationService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /** 单人任务的企微 userid 列表(未绑定企微返回空列表) */
|
|
|
+ private List<String> resolveUserWecomUserIds(Long userId) {
|
|
|
+ SysUser user = sysUserMapper.selectById(userId);
|
|
|
+ if (user == null || !StringUtils.hasText(user.getWecomUserId())) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ return Collections.singletonList(user.getWecomUserId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 实际跳转用的 H5 地址(配置逗号分隔时取第一个);未配置返回 null → 降级纯文本 */
|
|
|
+ private String h5Url() {
|
|
|
+ if (!StringUtils.hasText(h5BaseUrl)) return null;
|
|
|
+ return Arrays.stream(h5BaseUrl.split(","))
|
|
|
+ .map(String::trim)
|
|
|
+ .filter(StringUtils::hasText)
|
|
|
+ .findFirst()
|
|
|
+ .map(url -> url.endsWith("/") ? url.substring(0, url.length() - 1) : url)
|
|
|
+ .orElse(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** textcard 标题:前缀 + 实例标题,超长截断(企微限制 128 字节) */
|
|
|
+ private String textCardTitle(String prefix, String instanceTitle) {
|
|
|
+ String title = prefix + (instanceTitle == null ? "" : instanceTitle);
|
|
|
+ return title.length() > TEXT_CARD_TITLE_MAX ? title.substring(0, TEXT_CARD_TITLE_MAX) + "…" : title;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 审批动作 → 中文(用于进度通知) */
|
|
|
+ private String resolveActionText(String action) {
|
|
|
+ if (action == null) return "";
|
|
|
+ if (ApprovalAction.APPROVE.getCode().equalsIgnoreCase(action)) return ApprovalAction.APPROVE.getDesc();
|
|
|
+ if (ApprovalAction.REJECT.getCode().equalsIgnoreCase(action)) return ApprovalAction.REJECT.getDesc();
|
|
|
+ if (ApprovalAction.RETURN.getCode().equalsIgnoreCase(action)) return ApprovalAction.RETURN.getDesc();
|
|
|
+ if (ApprovalAction.TRANSFER.getCode().equalsIgnoreCase(action)) return ApprovalAction.TRANSFER.getDesc();
|
|
|
+ return action;
|
|
|
+ }
|
|
|
+
|
|
|
private String resolveWeComUserId(Long assigneeId, String assigneeType) {
|
|
|
// 企微通知按用户维度发送:角色任务取该角色下第一个有企微账号的用户
|
|
|
if (ASSIGNEE_TYPE_ROLE.equalsIgnoreCase(assigneeType)) {
|