From c49f094f484cb676bc1e05e2e5888fea483c88b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E6=B5=A9?= <8583167+chen-zh@user.noreply.gitee.com> Date: Fri, 24 Jul 2026 04:07:44 +0000 Subject: [PATCH 01/10] =?UTF-8?q?!90=20czh-20260724-IM=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8fastjson2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * czh-20260724-IM模块统一使用fastjson2 * czh-20260724-添加bundle到gitignore * czh-20260724-增补意见即时通推送和回显功能 --- .gitignore | 2 +- .../modules/ksim/client/KsimApiClient.java | 4 +- .../jeecg/modules/ksim/dto/KsimApiResult.java | 2 +- .../service/impl/KsimMessageServiceImpl.java | 2 +- .../modules/ksim/token/KsimTokenManager.java | 2 +- .../jeecg-boot-module-bpm-flowable/pom.xml | 8 +- .../TaskApprovalOpinionController.java | 6 +- .../dto/SupplementApprovalOpinionDTO.java | 13 +- .../service/ITaskApprovalOpinionService.java | 9 +- .../impl/TaskApprovalOpinionServiceImpl.java | 131 ++++++++++++++++-- 10 files changed, 142 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index e345e9b..efead51 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ localDocs ## backend **/target **/logs -**/yml **/.claude +*.bundle ## front **/*.lock diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java index 1188172..56ba2e5 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java @@ -1,7 +1,7 @@ package org.jeecg.modules.ksim.client; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.util.RestUtil; import org.jeecg.modules.ksim.config.KsimProperties; diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimApiResult.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimApiResult.java index c86723d..56aec86 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimApiResult.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimApiResult.java @@ -1,6 +1,6 @@ package org.jeecg.modules.ksim.dto; -import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONField; /** * 即时通 API 通用响应体 diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java index 06065da..7fe3d6c 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java @@ -1,6 +1,6 @@ package org.jeecg.modules.ksim.service.impl; -import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.api.vo.Result; import org.jeecg.modules.ksim.client.KsimApiClient; diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java index 0a6cc77..4b607a2 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java @@ -1,6 +1,6 @@ package org.jeecg.modules.ksim.token; -import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.exception.JeecgBootException; import org.jeecg.modules.ksim.client.KsimApiClient; diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/pom.xml b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/pom.xml index 4498b9c..284a9ad 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/pom.xml +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/pom.xml @@ -47,7 +47,13 @@ org.jeecgframework.boot jeecg-system-local-api - + + + org.jeecgframework.boot + jeecg-module-im + ${jeecgProjectVersion} + + org.flowable diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/controller/TaskApprovalOpinionController.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/controller/TaskApprovalOpinionController.java index 96fc998..fceaea0 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/controller/TaskApprovalOpinionController.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/controller/TaskApprovalOpinionController.java @@ -105,9 +105,9 @@ public class TaskApprovalOpinionController extends JeecgController supplement(@RequestBody SupplementApprovalOpinionDTO supplementDTO) { - taskApprovalOpinionService.saveSupplementOpinion(supplementDTO); - return Result.OK("提交成功!"); + public Result> supplement(@RequestBody SupplementApprovalOpinionDTO supplementDTO) { + Map pushResult = taskApprovalOpinionService.saveSupplementOpinion(supplementDTO); + return Result.OK(pushResult); } /** diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/dto/SupplementApprovalOpinionDTO.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/dto/SupplementApprovalOpinionDTO.java index 44bb6d3..2f50af4 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/dto/SupplementApprovalOpinionDTO.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/dto/SupplementApprovalOpinionDTO.java @@ -5,9 +5,6 @@ import lombok.Data; import java.io.Serializable; -/** - * 增补审批意见提交参数。 - */ @Data @Schema(description = "增补审批意见提交参数") public class SupplementApprovalOpinionDTO implements Serializable { @@ -15,22 +12,18 @@ public class SupplementApprovalOpinionDTO implements Serializable { @Schema(description = "流程实例ID") private String processInstId; - @Schema(description = "Flowable任务ID") private String flowTaskId; - @Schema(description = "流程节点Key") private String taskDefKey; - - @Schema(description = "事项任务ID,对应task_task.id") + @Schema(description = "事项任务ID") private String taskTaskId; - @Schema(description = "关联业务ID") private String businessId; - @Schema(description = "流程编码") private String flowCode; - + @Schema(description = "业务标题") + private String bizTitle; @Schema(description = "增补审批意见") private String opinion; } diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/ITaskApprovalOpinionService.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/ITaskApprovalOpinionService.java index c337926..f817a49 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/ITaskApprovalOpinionService.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/ITaskApprovalOpinionService.java @@ -1,15 +1,10 @@ package org.jeecg.modules.taskapprovalopinion.service; +import java.util.Map; import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion; import org.jeecg.modules.taskapprovalopinion.dto.SupplementApprovalOpinionDTO; import com.baomidou.mybatisplus.extension.service.IService; -/** - * 审批意见快照 Service。 - */ public interface ITaskApprovalOpinionService extends IService { - /** - * 保存增补审批意见,仅写入 task_approval_opinion。 - */ - void saveSupplementOpinion(SupplementApprovalOpinionDTO supplementDTO); + Map saveSupplementOpinion(SupplementApprovalOpinionDTO supplementDTO); } diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java index f286a51..dfe5e9e 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java @@ -5,12 +5,14 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; +import org.jeecg.common.api.vo.Result; import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.system.api.ISysBaseAPI; import org.jeecg.common.system.vo.LoginUser; import org.jeecg.common.util.oConvertUtils; import org.jeecg.modules.extbpm.process.entity.ExtActFlowData; import org.jeecg.modules.extbpm.process.mapper.ExtActFlowDataMapper; +import org.jeecg.modules.ksim.service.IKsimMessageService; import org.jeecg.modules.taskapprovalopinion.dto.SupplementApprovalOpinionDTO; import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion; import org.jeecg.modules.taskapprovalopinion.mapper.TaskApprovalOpinionMapper; @@ -21,9 +23,14 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * 审批意见快照 Service 实现。 @@ -43,13 +50,12 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl saveSupplementOpinion(SupplementApprovalOpinionDTO supplementDTO) { if (supplementDTO == null || oConvertUtils.isEmpty(supplementDTO.getProcessInstId())) { throw new JeecgBootException("流程实例ID不能为空"); } @@ -88,6 +94,42 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl pushResult = new HashMap<>(); + try { + if (isSuoLeader(loginUser, deptInfo.id)) { + String bizTitle = oConvertUtils.isNotEmpty(supplementDTO.getBizTitle()) + ? supplementDTO.getBizTitle() + : getBizTitle(opinion.getTaskTaskId(), opinion.getProcessInstId()); + String content = "所领导" + (oConvertUtils.isNotEmpty(loginUser.getRealname()) ? loginUser.getRealname() : loginUser.getUsername()) + + "增补意见:" + opinionText; + List handlerNames = getDistinctHandlerNames(opinion.getProcessInstId(), loginUser.getUsername()); + pushResult.put("pushed", !handlerNames.isEmpty()); + pushResult.put("toUsers", handlerNames); + pushResult.put("content", content); + pushResult.put("title", bizTitle); + if (!handlerNames.isEmpty()) { + Result sendResult = ksimMessageService.sendBatch("Account", handlerNames, bizTitle, content, + null, null, null, null, null); + if (sendResult != null && sendResult.isSuccess()) { + log.info("【增补意见-即时通推送】已发送, processInstId: {}, toUserCount: {}, toUsers: {}, title: {}, content: {}", + opinion.getProcessInstId(), handlerNames.size(), handlerNames, bizTitle, content); + } else { + log.warn("【增补意见-即时通推送】发送失败, reason: {}", + sendResult != null ? sendResult.getMessage() : "null"); + pushResult.put("pushed", false); + } + } + } else { + pushResult.put("pushed", false); + } + } catch (Exception e) { + log.warn("【增补意见-即时通推送】失败,不影响增补意见保存, error: {}", e.getMessage()); + pushResult.put("pushed", false); + pushResult.put("error", e.getMessage()); + } + return pushResult; } private LoginUser getCurrentLoginUser() { @@ -98,9 +140,6 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl> rows = jdbcTemplate.queryForList( + "select depart_type from sys_depart where id = ?", deptId); + if (!rows.isEmpty()) { + Object val = rows.get(0).get("depart_type"); + return val != null && "1".equals(String.valueOf(val)); + } + } catch (Exception e) { + log.debug("查询部门类型失败, deptId={}", deptId, e); + } + return false; + } + + private String getBizTitle(String taskTaskId, String processInstId) { + if (jdbcTemplate != null && oConvertUtils.isNotEmpty(taskTaskId)) { + try { + List> rows = jdbcTemplate.queryForList( + "select title from task_task where id = ?", taskTaskId); + if (!rows.isEmpty()) { + Object val = rows.get(0).get("title"); + if (val != null && !String.valueOf(val).trim().isEmpty()) { + return String.valueOf(val).trim(); + } + } + } catch (Exception e) { + log.debug("查询 task_task.title 失败, taskTaskId={}", taskTaskId, e); + } + } + if (oConvertUtils.isNotEmpty(processInstId)) { + try { + ExtActFlowData flowData = queryFlowData(processInstId); + if (flowData != null && oConvertUtils.isNotEmpty(flowData.getProcessExpTitle())) { + return flowData.getProcessExpTitle().trim(); + } + } catch (Exception e) { + log.debug("查询 flow title 失败, processInstId={}", processInstId, e); + } + } + return ""; + } + + private List getDistinctHandlerNames(String processInstId, String excludeUserName) { + if (jdbcTemplate == null || oConvertUtils.isEmpty(processInstId)) { + return Collections.emptyList(); + } + try { + List> rows = jdbcTemplate.queryForList( + "select distinct op_user_id, op_user_name from task_approval_opinion where process_inst_id = ?", + processInstId); + Set names = new LinkedHashSet<>(); + for (Map row : rows) { + Object nameVal = row.get("op_user_name"); + Object idVal = row.get("op_user_id"); + if (nameVal != null) { + String name = String.valueOf(nameVal).trim(); + String userId = idVal != null ? String.valueOf(idVal).trim() : ""; + if (!name.isEmpty() && !userId.equals(excludeUserName)) { + names.add(name); + } + } + } + return names.stream().collect(Collectors.toList()); + } catch (Exception e) { + log.debug("查询经办人失败, processInstId={}", processInstId, e); + return Collections.emptyList(); + } + } + private static class ApprovalDeptInfo { private final String id; private final String name; From 497c0a0ad0c0ac5a2bc7b256e1ac977549736a7c Mon Sep 17 00:00:00 2001 From: zhihao <18915542763@163.com> Date: Fri, 24 Jul 2026 12:11:19 +0800 Subject: [PATCH 02/10] =?UTF-8?q?czh-20260724-=E5=BF=BD=E7=95=A5=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index efead51..616bb37 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ localDocs **/target **/logs **/.claude +**/yml *.bundle ## front From 72d378c577ec4159715730d7b8d16e51afc58669 Mon Sep 17 00:00:00 2001 From: wsm <8454518+new-twice@user.noreply.gitee.com> Date: Fri, 24 Jul 2026 07:53:09 +0000 Subject: [PATCH 03/10] =?UTF-8?q?!91=20feat(excel):=20=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E6=95=B0=E6=8D=AE=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(excel): 导出模板自动生成示例数据行 * fix(excel): 字典导入兼容中英文逗号分隔多值,修复BgPartymatter导入Map.of参数错位 * fix(import): Excel导入时注入bpmStatus和delFlag默认值,避免新增记录缺少流程状态 --- .../base/controller/JeecgController.java | 8 ++ .../util/excel/ExcelAnnotationUtils.java | 77 +++++++++++++++++-- .../controller/BgXiSpeakController.java | 5 +- .../controller/BgPartymatterController.java | 13 +++- .../controller/BgTakepulseController.java | 15 +++- .../controller/DqInspectTaskController.java | 5 +- .../DjInspectImproveController.java | 10 ++- 7 files changed, 116 insertions(+), 17 deletions(-) diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java index 13d4d25..360e5e3 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java +++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java @@ -176,6 +176,14 @@ public class JeecgController> { writeExcel(response, clazz, title, Collections.emptyList()); } + /** + * 导出自定义数据(含表头),供子类导出模板示例行 + */ + protected void exportXlsWithData(HttpServletResponse response, + Class clazz, String title, List data) throws IOException { + writeExcel(response, clazz, title, data); + } + private void writeExcel(HttpServletResponse response, Class clazz, String title, List data) throws IOException { ExcelAnnotationUtils.write(response, clazz, title, data, dictQuery()); diff --git a/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java index f5fc052..b22802f 100644 --- a/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java +++ b/jeecg-boot-module/jeecg-module-tools/src/main/java/org/jeecg/common/util/excel/ExcelAnnotationUtils.java @@ -111,6 +111,54 @@ public final class ExcelAnnotationUtils { return columns; } + /** + * 根据 @ExcelColumn 注解自动构建一个填充了示例数据的实体实例,用于导出模板。 + * String 字段填 "示例" + 列名,dict 字段填 "示例文本",日期填当天,数字填 1。 + */ + public static T buildExample(Class clazz) { + try { + T obj = clazz.getDeclaredConstructor().newInstance(); + for (Field field : getAllFields(clazz)) { + ExcelColumn col = field.getAnnotation(ExcelColumn.class); + if (col == null) continue; + field.setAccessible(true); + setExampleValue(field, obj, col); + } + return obj; + } catch (Exception e) { + throw new RuntimeException("【Excel工具】构建示例数据失败, class: " + clazz.getSimpleName(), e); + } + } + + private static void setExampleValue(Field field, Object obj, ExcelColumn col) throws Exception { + Class type = field.getType(); + boolean hasDict = StringUtils.isNotEmpty(col.dictTable()) || StringUtils.isNotEmpty(col.dicCode()); + + if (type == String.class) { + field.set(obj, hasDict ? "示例文本" : "示例" + col.name()); + } else if (type == Date.class) { + field.set(obj, new Date()); + } else if (type == LocalDate.class) { + field.set(obj, LocalDate.now()); + } else if (type == LocalDateTime.class) { + field.set(obj, LocalDateTime.now()); + } else if (type == Integer.class || type == int.class) { + field.set(obj, 1); + } else if (type == Long.class || type == long.class) { + field.set(obj, 1L); + } else if (type == Double.class || type == double.class) { + field.set(obj, 1.0); + } else if (type == Float.class || type == float.class) { + field.set(obj, 1.0f); + } else if (type == Short.class || type == short.class) { + field.set(obj, (short) 1); + } else if (type == BigDecimal.class) { + field.set(obj, BigDecimal.ONE); + } else if (type == Boolean.class || type == boolean.class) { + field.set(obj, false); + } + } + // ==================== 导出 ==================== /** @@ -214,15 +262,16 @@ public final class ExcelAnnotationUtils { // ==================== 内部辅助方法 ==================== static boolean isValidDictDisplay(String[] replace, String value) { + String normalized = normalizeComma(value); for (String s : replace) { String[] arr = s.split(","); - if (arr.length >= 2 && arr[0].equals(value)) { + if (arr.length >= 2 && arr[0].equals(normalized)) { return true; } } - // 逗号分隔多值:逐个验证每个部分是否在字典范围内 - if (value.contains(",")) { - String[] parts = value.split(","); + // 逗号分隔多值:逐个验证每个部分是否在字典范围内(兼容中英文逗号) + if (normalized.contains(",")) { + String[] parts = normalized.split(","); for (String part : parts) { String trimmed = part.trim(); if (trimmed.isEmpty()) continue; @@ -234,7 +283,10 @@ public final class ExcelAnnotationUtils { break; } } - if (!found) return false; + if (!found) { + log.warn("【Excel导入-字典校验】多值中'{}'不在字典范围, 输入值: {}, replace长度: {}", trimmed, value, replace.length); + return false; + } } return true; } @@ -249,9 +301,10 @@ public final class ExcelAnnotationUtils { String resolved = displayToCode.getOrDefault(value, value); if (type == String.class) { - // 逗号分隔多值:逐个翻译后再拼回(如 "部门A,部门B" → "id_a,id_b") - if (resolved.equals(value) && value.contains(",")) { - String[] parts = value.split(","); + // 逗号分隔多值:逐个翻译后再拼回(如 "部门A,部门B" → "id_a,id_b"),兼容中英文逗号 + String normalized = normalizeComma(value); + if (resolved.equals(value) && normalized.contains(",")) { + String[] parts = normalized.split(","); StringBuilder sb = new StringBuilder(); for (int i = 0; i < parts.length; i++) { if (i > 0) sb.append(","); @@ -406,6 +459,14 @@ public final class ExcelAnnotationUtils { return sb.toString(); } + /** + * 将中文逗号归一化为英文逗号,兼容 Excel 中两种逗号混用。 + */ + private static String normalizeComma(String value) { + if (value == null) return null; + return value.replace(',', ','); + } + private static String[] mergeReplace(String[] original, String[] dictReplace) { if (original == null || original.length == 0) return dictReplace; if (dictReplace == null || dictReplace.length == 0) return original; diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java index 8f38989..ce05a92 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java @@ -18,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jeecg.common.api.vo.Result; import org.jeecg.common.system.query.QueryGenerator; +import org.jeecg.common.util.excel.ExcelAnnotationUtils; import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.system.vo.SelectTreeModel; import org.jeecg.modules.bg.constant.BpmStatus; @@ -456,7 +457,9 @@ public class BgXiSpeakController extends JeecgController importExcelByEasyExcel(HttpServletRequest request) { - return super.importExcelByEasyExcel(request, BgTakepulse.class); + return super.importExcelByEasyExcel(request, BgTakepulse.class, + Map.of( + SupervisionConstant.ClassFieldName.BPM_STATUS_NAME, + BpmStatus.NOT_START.getCode(), + SupervisionConstant.ClassFieldName.DEL_FLAG_NAME, + SupervisionConstant.DelFlag.NORMAL)); } /** @@ -468,7 +477,9 @@ public class BgTakepulseController extends JeecgController importExcelByEasyExcel(HttpServletRequest request) { - return super.importExcelByEasyExcel(request, DjInspectImprove.class); + return super.importExcelByEasyExcel(request, DjInspectImprove.class, + Map.of( + SupervisionConstant.ClassFieldName.BPM_STATUS_NAME, + BpmStatus.NOT_START.getCode(), + SupervisionConstant.ClassFieldName.DEL_FLAG_NAME, + SupervisionConstant.DelFlag.NORMAL)); } /** From 13b30fe72fd88feea4727dda835f3d7b82e9128a Mon Sep 17 00:00:00 2001 From: ye1023 <12588209+ye1023@user.noreply.gitee.com> Date: Fri, 24 Jul 2026 16:05:12 +0800 Subject: [PATCH 04/10] =?UTF-8?q?yxk-20260724=20=E6=94=B9=E5=96=84?= =?UTF-8?q?=E6=8F=90=E6=A1=88=20=E6=96=B0=E5=A2=9E=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E5=8F=AA=E5=A1=AB=E5=86=99=E4=B8=BB=E8=A1=A8=EF=BC=8C=E5=AD=90?= =?UTF-8?q?=E8=A1=A8=E7=94=B1=E5=90=8E=E7=BB=AD=E8=AF=84=E5=88=86=E5=92=8C?= =?UTF-8?q?=E6=8A=95=E7=A5=A8=E6=B5=81=E7=A8=8B=E7=BB=B4=E6=8A=A4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ImprovementProposalController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java index 66e7ca2..065ef57 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java @@ -102,7 +102,8 @@ public class ImprovementProposalController { ImprovementProposal improvementProposal = new ImprovementProposal(); BeanUtils.copyProperties(improvementProposalPage, improvementProposal); improvementProposal.setBpmStatus("1"); - improvementProposalService.saveMain(improvementProposal, improvementProposalPage.getImprovementDeptScoreList(),improvementProposalPage.getImprovementInstituteVoteList()); + // 新增申请阶段只创建主表,评分和投票子表由后续流程节点维护。 + improvementProposalService.saveMain(improvementProposal, null, null); return Result.OK("添加成功!"); } @@ -270,7 +271,8 @@ public class ImprovementProposalController { for (ImprovementProposalPage page : list) { ImprovementProposal po = new ImprovementProposal(); BeanUtils.copyProperties(page, po); - improvementProposalService.saveMain(po, page.getImprovementDeptScoreList(),page.getImprovementInstituteVoteList()); + // 导入新提案时同样只创建主表,不提前生成评分或投票数据。 + improvementProposalService.saveMain(po, null, null); } return Result.OK("文件导入成功!数据行数:" + list.size()); } catch (Exception e) { From 91c85b6a6c2a53749dee8cade898c8115b3cbb90 Mon Sep 17 00:00:00 2001 From: ye1023 <12588209+ye1023@user.noreply.gitee.com> Date: Fri, 24 Jul 2026 16:45:26 +0800 Subject: [PATCH 05/10] =?UTF-8?q?yxk-20260724=20feat(=E6=94=B9=E5=96=84?= =?UTF-8?q?=E6=8F=90=E6=A1=88):=20=E6=94=AF=E6=8C=81=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E5=8F=91=E8=B5=B7=E5=AE=A1=E6=89=B9=E6=B5=81=E7=A8=8B=E5=8F=8A?= =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=B5=81=E7=A8=8B=E5=8A=9E=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增改善提案专用流程发起接口,直接以 improvement_proposal.id 作为业务ID发起 dev_improvement_proposal_001,不再创建 task_task - 发起时加行锁并校验草稿状态及流程实例,防止重复发起;成功后回写 process_instance_id - 收紧已发起提案的编辑、单删和批量删除限制,避免流程中的业务数据被篡改 - 前端列表接入业务发起接口,仅草稿且未绑定流程实例时显示发起、编辑、删除入口 - 新增 ImprovementProposalBPMForm,只读展示提案信息并复用通用任务办理能力 - 兼容发起接口从 POST 请求体或 URL 参数中接收 id,修复前端参数未绑定导致的缺少 id 异常 - 明确流程变量使用业务表物理列名:proposal_dept_id,用于按提案部门匹配精益专员初审人员 --- .../ImprovementProposalController.java | 52 +++++++++++++---- .../mapper/ImprovementProposalMapper.java | 9 ++- .../service/IImprovementProposalService.java | 9 +++ .../impl/ImprovementProposalServiceImpl.java | 58 +++++++++++++++++++ 4 files changed, 115 insertions(+), 13 deletions(-) diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java index 065ef57..d51949c 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java @@ -118,14 +118,36 @@ public class ImprovementProposalController { @RequiresPermissions("improvementproposal:improvement_proposal:edit") @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST}) public Result edit(@RequestBody ImprovementProposalPage improvementProposalPage) { - ImprovementProposal improvementProposal = new ImprovementProposal(); - BeanUtils.copyProperties(improvementProposalPage, improvementProposal); - ImprovementProposal improvementProposalEntity = improvementProposalService.getById(improvementProposal.getId()); - if(improvementProposalEntity==null) { - return Result.error("未找到对应数据"); + try { + ImprovementProposal improvementProposal = new ImprovementProposal(); + BeanUtils.copyProperties(improvementProposalPage, improvementProposal); + improvementProposalService.updateMain(improvementProposal, improvementProposalPage.getImprovementDeptScoreList(),improvementProposalPage.getImprovementInstituteVoteList()); + return Result.OK("编辑成功!"); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } + + /** + * 使用改善提案主表作为流程业务数据发起审批,不复用督办任务表。 + */ + @PostMapping(value = "/startProcess") + public Result startProcess(@RequestBody(required = false) Map requestBody, + @RequestParam(name = "id", required = false) String id) { + // defHttp 的 POST 参数默认在请求体中;同时兼容外部调用将 id 放在 URL 查询串的场景。 + if (oConvertUtils.isEmpty(id) && requestBody != null) { + id = requestBody.get("id"); + } + if (oConvertUtils.isEmpty(id)) { + return Result.error("提案ID不能为空"); + } + LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); + try { + String processInstanceId = improvementProposalService.startProcess(id, sysUser.getUsername()); + return Result.OK("流程发起成功!", processInstanceId); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); } - improvementProposalService.updateMain(improvementProposal, improvementProposalPage.getImprovementDeptScoreList(),improvementProposalPage.getImprovementInstituteVoteList()); - return Result.OK("编辑成功!"); } /** @@ -139,8 +161,12 @@ public class ImprovementProposalController { @RequiresPermissions("improvementproposal:improvement_proposal:delete") @DeleteMapping(value = "/delete") public Result delete(@RequestParam(name="id",required=true) String id) { - improvementProposalService.delMain(id); - return Result.OK("删除成功!"); + try { + improvementProposalService.delMain(id); + return Result.OK("删除成功!"); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } } /** @@ -154,8 +180,12 @@ public class ImprovementProposalController { @RequiresPermissions("improvementproposal:improvement_proposal:deleteBatch") @DeleteMapping(value = "/deleteBatch") public Result deleteBatch(@RequestParam(name="ids",required=true) String ids) { - this.improvementProposalService.delBatchMain(Arrays.asList(ids.split(","))); - return Result.OK("批量删除成功!"); + try { + this.improvementProposalService.delBatchMain(Arrays.asList(ids.split(","))); + return Result.OK("批量删除成功!"); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } } /** diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java index 167f101..9f100d7 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java @@ -1,8 +1,7 @@ package org.jeecg.modules.improvementproposal.mapper; -import java.util.List; - import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; import org.jeecg.modules.improvementproposal.entity.ImprovementProposal; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -14,4 +13,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; */ public interface ImprovementProposalMapper extends BaseMapper { + /** + * 锁定提案主表,串行化流程发起、编辑和删除操作,避免同一提案生成多个流程实例。 + */ + @Select("SELECT * FROM improvement_proposal WHERE id = #{id} AND del_flag = '0' FOR UPDATE") + ImprovementProposal selectByIdForUpdate(@Param("id") String id); + } diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java index 6ef7e75..6ac33e2 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java @@ -16,6 +16,15 @@ import java.util.List; */ public interface IImprovementProposalService extends IService { + /** + * 发起改善提案审批流程。 + * + * @param id 提案主表 ID + * @param username 当前发起人账号 + * @return 流程实例 ID + */ + String startProcess(String id, String username); + /** * 添加一对多 * diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java index 7e41be7..381e8c2 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java @@ -7,6 +7,9 @@ import org.jeecg.modules.improvementproposal.mapper.ImprovementDeptScoreMapper; import org.jeecg.modules.improvementproposal.mapper.ImprovementInstituteVoteMapper; import org.jeecg.modules.improvementproposal.mapper.ImprovementProposalMapper; import org.jeecg.modules.improvementproposal.service.IImprovementProposalService; +import org.jeecg.common.api.vo.Result; +import org.jeecg.common.util.oConvertUtils; +import org.jeecg.modules.extbpm.process.service.impl.BpmBaseExtApiImpl; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +27,9 @@ import java.util.Collection; @Service public class ImprovementProposalServiceImpl extends ServiceImpl implements IImprovementProposalService { private static final String NORMAL_DEL_FLAG = "0"; + private static final String BPM_STATUS_DRAFT = "1"; + private static final String FLOW_CODE = "dev_improvement_proposal_001"; + private static final String BPM_FORM_URL = "gh/improvementProposal/components/ImprovementProposalBPMForm"; @Autowired private ImprovementProposalMapper improvementProposalMapper; @@ -31,6 +37,34 @@ public class ImprovementProposalServiceImpl extends ServiceImpl result = bpmBaseExtApi.startMutilProcess(FLOW_CODE, proposal.getId(), BPM_FORM_URL, BPM_FORM_URL, username, null); + if (result == null || !result.isSuccess() || oConvertUtils.isEmpty(result.getResult())) { + throw new IllegalStateException(result == null || oConvertUtils.isEmpty(result.getMessage()) ? "发起流程失败" : result.getMessage()); + } + + // 流程引擎会按 BPM 配置将 bpm_status 回写为处理中;此处使用局部对象更新,避免旧实体的草稿状态将其覆盖。 + ImprovementProposal processUpdate = new ImprovementProposal(); + processUpdate.setId(proposal.getId()); + processUpdate.setProcessInstanceId(result.getResult()); + if (improvementProposalMapper.updateById(processUpdate) != 1) { + throw new IllegalStateException("流程已启动,但流程实例关联保存失败"); + } + return result.getResult(); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException("发起流程失败:" + e.getMessage(), e); + } + } @Override @Transactional(rollbackFor = Exception.class) @@ -65,6 +99,10 @@ public class ImprovementProposalServiceImpl extends ServiceImpl improvementDeptScoreList,List improvementInstituteVoteList) { + ImprovementProposal existingProposal = getDraftProposalForUpdate(improvementProposal.getId()); + // 草稿编辑不能伪造流程状态或覆盖已关联的流程实例。 + improvementProposal.setBpmStatus(existingProposal.getBpmStatus()); + improvementProposal.setProcessInstanceId(existingProposal.getProcessInstanceId()); improvementProposalMapper.updateById(improvementProposal); //1.先删除子表数据 @@ -97,6 +135,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl idList) { for(Serializable id:idList) { + getDraftProposalForUpdate(id.toString()); improvementDeptScoreMapper.deleteByMainId(id.toString()); improvementInstituteVoteMapper.deleteByMainId(id.toString()); improvementProposalMapper.deleteById(id); } } + + /** + * 任何会改变主表生命周期的操作都必须先锁定并确认仍是未发起草稿, + * 这样并发的“发起流程”和“编辑/删除”不会让流程与业务数据脱节。 + */ + private ImprovementProposal getDraftProposalForUpdate(String id) { + if (oConvertUtils.isEmpty(id)) { + throw new IllegalStateException("提案ID不能为空"); + } + ImprovementProposal proposal = improvementProposalMapper.selectByIdForUpdate(id); + if (proposal == null) { + throw new IllegalStateException("未找到对应提案"); + } + if (!BPM_STATUS_DRAFT.equals(proposal.getBpmStatus()) || oConvertUtils.isNotEmpty(proposal.getProcessInstanceId())) { + throw new IllegalStateException("提案已进入流程,不能编辑、删除或重复发起"); + } + return proposal; + } } From fbd91f8337aa72bd7f45ceb5aa25b6daa20ebcea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E6=B5=A9?= <8583167+chen-zh@user.noreply.gitee.com> Date: Sat, 25 Jul 2026 01:58:42 +0000 Subject: [PATCH 06/10] =?UTF-8?q?!92=20czh-20260725-=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E8=87=AA=E6=88=91=E6=8E=92=E9=99=A4=E9=80=BB=E8=BE=91=E5=B9=B6?= =?UTF-8?q?=E7=BB=9F=E4=B8=80fastjson2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merge branch 'master' of gitee.com:sz_30/supervison-java into feature/… * czh-20260725-增强自我排除逻辑并统一fastjson2 * czh-20260724-即时通API接口改为sendBatch * czh-20260724-增加即时通请求体日志 * czh-20260724-IM模块统一使用fastjson2 * czh-20260724-添加bundle到gitignore * czh-20260724-增补意见即时通推送和回显功能 --- .../modules/ksim/client/KsimApiClient.java | 3 +- .../ksim/controller/KsimTestController.java | 6 +-- .../impl/TaskApprovalOpinionServiceImpl.java | 37 +++++++++++++------ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java index 56ba2e5..0f07f05 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java @@ -59,7 +59,8 @@ public class KsimApiClient { headers.add("imOpenApiToken", token); String bodyJson = JSON.toJSONString(dto); - String url = ksimProperties.getHost() + "/api-open/v1/msg/send"; + String url = ksimProperties.getHost() + "/api-open/v1/msg/sendBatch"; + log.info("【即时通推送-消息发送】请求体: {}", bodyJson); ResponseEntity resp = RestUtil.request(url, HttpMethod.POST, headers, null, JSON.parseObject(bodyJson), JSONObject.class); diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java index 926fd85..95c4cb8 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java @@ -1,7 +1,7 @@ package org.jeecg.modules.ksim.controller; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.api.vo.Result; import org.jeecg.common.util.RestUtil; @@ -103,7 +103,7 @@ public class KsimTestController { dto.setLinkUrl(linkUrl); } - String msgUrl = host + "/api-open/v1/msg/send"; + String msgUrl = host + "/api-open/v1/msg/sendBatch"; requestInfo.put("msgRequestUrl", msgUrl); requestInfo.put("msgRequestBody", dto); diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java index dfe5e9e..fba7599 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java @@ -104,7 +104,7 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl handlerNames = getDistinctHandlerNames(opinion.getProcessInstId(), loginUser.getUsername()); + List handlerNames = getDistinctHandlerNames(opinion.getProcessInstId(), loginUser.getUsername(), loginUser.getRealname()); pushResult.put("pushed", !handlerNames.isEmpty()); pushResult.put("toUsers", handlerNames); pushResult.put("content", content); @@ -287,7 +287,7 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl getDistinctHandlerNames(String processInstId, String excludeUserName) { + private List getDistinctHandlerNames(String processInstId, String excludeUserId, String excludeRealName) { if (jdbcTemplate == null || oConvertUtils.isEmpty(processInstId)) { return Collections.emptyList(); } @@ -295,19 +295,34 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl> rows = jdbcTemplate.queryForList( "select distinct op_user_id, op_user_name from task_approval_opinion where process_inst_id = ?", processInstId); - Set names = new LinkedHashSet<>(); + Set userIds = new LinkedHashSet<>(); for (Map row : rows) { - Object nameVal = row.get("op_user_name"); Object idVal = row.get("op_user_id"); - if (nameVal != null) { - String name = String.valueOf(nameVal).trim(); - String userId = idVal != null ? String.valueOf(idVal).trim() : ""; - if (!name.isEmpty() && !userId.equals(excludeUserName)) { - names.add(name); - } + Object nameVal = row.get("op_user_name"); + if (idVal == null) { + continue; } + String userId = String.valueOf(idVal).trim(); + if (userId.isEmpty()) { + continue; + } + String name = nameVal != null ? String.valueOf(nameVal).trim() : ""; + + // 主判断:工号匹配则排除(最可靠) + if (userId.equals(excludeUserId)) { + log.info("【增补意见-即时通推送】排除本人(工号匹配), userId: {}, name: {}", userId, name); + continue; + } + // 辅助判断:姓名匹配则排除(防止工号异常等边界情况) + if (oConvertUtils.isNotEmpty(excludeRealName) && name.equals(excludeRealName)) { + log.info("【增补意见-即时通推送】排除本人(姓名匹配), name: {}, userId: {}", name, userId); + continue; + } + userIds.add(userId); } - return names.stream().collect(Collectors.toList()); + log.info("【增补意见-即时通推送】查询经办人完成, processInstId: {}, DB记录数: {}, 排除后: {}", + processInstId, rows.size(), userIds.size()); + return userIds.stream().collect(Collectors.toList()); } catch (Exception e) { log.debug("查询经办人失败, processInstId={}", processInstId, e); return Collections.emptyList(); From e31b7dbb28e3a2bce4cebd8d661479378889b19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E6=B5=A9?= <8583167+chen-zh@user.noreply.gitee.com> Date: Sat, 25 Jul 2026 02:52:19 +0000 Subject: [PATCH 07/10] =?UTF-8?q?!93=20czh-20260725-getDistinctHandlerName?= =?UTF-8?q?s=E5=90=8C=E6=97=B6=E8=BF=94=E5=9B=9E=E5=B7=A5=E5=8F=B7+?= =?UTF-8?q?=E5=A7=93=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * czh-20260725-getDistinctHandlerNames同时返回工号+姓名 * czh-20260725-移除linkOpenMode和dataFormat默认值 * czh-20260725-移除接收人上限限制 * czh-20260725-IM模块code-review补充修复 * czh-20260725-code-review问题修复 --- .../modules/ksim/client/KsimApiClient.java | 7 +- .../ksim/controller/KsimTestController.java | 31 +--- .../modules/ksim/dto/KsimMsgSendDTO.java | 30 ++++ .../service/impl/KsimMessageServiceImpl.java | 38 ++--- .../impl/TaskApprovalOpinionServiceImpl.java | 150 ++++++++++-------- 5 files changed, 143 insertions(+), 113 deletions(-) diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java index 0f07f05..bb0528c 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java @@ -58,12 +58,13 @@ public class KsimApiClient { HttpHeaders headers = jsonHeaders(); headers.add("imOpenApiToken", token); - String bodyJson = JSON.toJSONString(dto); + // 直接 POJO→JSONObject,避免 DTO→String→JSONObject 三重序列化 + JSONObject body = (JSONObject) JSON.toJSON(dto); String url = ksimProperties.getHost() + "/api-open/v1/msg/sendBatch"; - log.info("【即时通推送-消息发送】请求体: {}", bodyJson); + log.debug("【即时通推送-消息发送】请求体: {}", body.toJSONString()); ResponseEntity resp = RestUtil.request(url, HttpMethod.POST, headers, null, - JSON.parseObject(bodyJson), JSONObject.class); + body, JSONObject.class); JSONObject respBody = resp.getBody(); int code = respBody != null ? respBody.getIntValue("code") : -1; diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java index 95c4cb8..d73a871 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java @@ -19,7 +19,6 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; @Slf4j @@ -28,8 +27,6 @@ import java.util.stream.Collectors; @RequestMapping("/ksim/test") public class KsimTestController { - private static final Set ALLOWED_LINK_OPEN_MODES = Set.of("Exe", "Web", "None"); - @PostMapping("/sendBatch") public Result> sendBatch(@RequestBody Map params) { Map result = new LinkedHashMap<>(); @@ -47,8 +44,8 @@ public class KsimTestController { String content = (String) params.getOrDefault("content", ""); String linkUrl = (String) params.getOrDefault("linkUrl", ""); String linkTitle = (String) params.getOrDefault("linkTitle", ""); - String linkOpenMode = (String) params.getOrDefault("linkOpenMode", "Exe"); - String dataFormat = (String) params.getOrDefault("dataFormat", "JsonListView"); + String linkOpenMode = (String) params.getOrDefault("linkOpenMode", ""); + String dataFormat = (String) params.getOrDefault("dataFormat", ""); String data = (String) params.getOrDefault("data", ""); // --- Step 1: 创建 Token --- @@ -82,25 +79,11 @@ public class KsimTestController { dto.setToUserValueList(toUserValueList); dto.setTitle(title); dto.setContent(content); - // 可选字段:传了才设置,不传则不发给 ksim - if (data != null && !data.isEmpty()) { - dto.setData(data); - } - if (linkOpenMode != null && !linkOpenMode.isEmpty()) { - if (!ALLOWED_LINK_OPEN_MODES.contains(linkOpenMode)) { - log.warn("【即时通测试-sendBatch】非法的 linkOpenMode: {}, 已跳过", linkOpenMode); - } else { - dto.setLinkOpenMode(linkOpenMode); - } - } - if (dataFormat != null && !dataFormat.isEmpty()) { - dto.setDataFormat(dataFormat); - } - if (linkTitle != null && !linkTitle.isEmpty()) { - dto.setLinkTitle(linkTitle); - } - if (linkUrl != null && !linkUrl.isEmpty()) { - dto.setLinkUrl(linkUrl); + // 可选字段统一通过 setOptionalFields 设置 + try { + dto.setOptionalFields(data, linkOpenMode, dataFormat, linkTitle, linkUrl); + } catch (IllegalArgumentException e) { + log.warn("【即时通测试-sendBatch】可选字段校验失败: {}", e.getMessage()); } String msgUrl = host + "/api-open/v1/msg/sendBatch"; diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java index 995ccab..a5bf238 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java @@ -1,12 +1,15 @@ package org.jeecg.modules.ksim.dto; import java.util.List; +import java.util.Set; /** * 对应即时通文档 3.4.3 ReqMsgSendDTO */ public class KsimMsgSendDTO { + public static final Set ALLOWED_LINK_OPEN_MODES = Set.of("Exe", "Web", "None"); + private String title; private String content; private String toUserKeyType; @@ -17,6 +20,33 @@ public class KsimMsgSendDTO { private String linkTitle; private String linkUrl; + /** + * 设置可选字段。仅当值非空时才设置,避免向 ksim 发送 null 字段。 + * linkOpenMode 会校验白名单。 + */ + public void setOptionalFields(String data, String linkOpenMode, String dataFormat, + String linkTitle, String linkUrl) { + if (data != null && !data.isEmpty()) { + this.data = data; + } + if (linkOpenMode != null && !linkOpenMode.isEmpty()) { + if (!ALLOWED_LINK_OPEN_MODES.contains(linkOpenMode)) { + throw new IllegalArgumentException("非法的 linkOpenMode: " + linkOpenMode + + ",允许的值: " + ALLOWED_LINK_OPEN_MODES); + } + this.linkOpenMode = linkOpenMode; + } + if (dataFormat != null && !dataFormat.isEmpty()) { + this.dataFormat = dataFormat; + } + if (linkTitle != null && !linkTitle.isEmpty()) { + this.linkTitle = linkTitle; + } + if (linkUrl != null && !linkUrl.isEmpty()) { + this.linkUrl = linkUrl; + } + } + public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java index 7fe3d6c..bf620bd 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java @@ -3,6 +3,7 @@ package org.jeecg.modules.ksim.service.impl; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.api.vo.Result; +import org.jeecg.common.util.oConvertUtils; import org.jeecg.modules.ksim.client.KsimApiClient; import org.jeecg.modules.ksim.dto.KsimApiResult; import org.jeecg.modules.ksim.dto.KsimMsgSendDTO; @@ -12,14 +13,11 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; -import java.util.Set; @Slf4j @Service public class KsimMessageServiceImpl implements IKsimMessageService { - private static final Set ALLOWED_LINK_OPEN_MODES = Set.of("Exe", "Web", "None"); - @Resource private KsimTokenManager tokenManager; @@ -31,6 +29,16 @@ public class KsimMessageServiceImpl implements IKsimMessageService { String title, String content, String linkUrl, String linkTitle, String data, String linkOpenMode, String dataFormat) { + if (toUserValueList == null || toUserValueList.isEmpty()) { + log.warn("【即时通推送-sendBatch】toUserValueList为空,跳过发送"); + return Result.error("接收人列表为空"); + } + if (oConvertUtils.isEmpty(title)) { + log.warn("【即时通推送-sendBatch】title为空"); + } + if (oConvertUtils.isEmpty(content)) { + log.warn("【即时通推送-sendBatch】content为空"); + } String token = tokenManager.getToken(); KsimMsgSendDTO dto = new KsimMsgSendDTO(); @@ -38,25 +46,11 @@ public class KsimMessageServiceImpl implements IKsimMessageService { dto.setToUserValueList(toUserValueList); dto.setTitle(title); dto.setContent(content); - // 以下可选字段:传了才设置,不传则不发给 ksim - if (data != null && !data.isEmpty()) { - dto.setData(data); - } - if (linkOpenMode != null && !linkOpenMode.isEmpty()) { - if (!ALLOWED_LINK_OPEN_MODES.contains(linkOpenMode)) { - log.warn("【即时通推送-sendBatch】非法的 linkOpenMode: {}, 已跳过", linkOpenMode); - } else { - dto.setLinkOpenMode(linkOpenMode); - } - } - if (dataFormat != null && !dataFormat.isEmpty()) { - dto.setDataFormat(dataFormat); - } - if (linkTitle != null && !linkTitle.isEmpty()) { - dto.setLinkTitle(linkTitle); - } - if (linkUrl != null && !linkUrl.isEmpty()) { - dto.setLinkUrl(linkUrl); + // 可选字段统一通过 setOptionalFields 设置,避免与测试控制器重复 + try { + dto.setOptionalFields(data, linkOpenMode, dataFormat, linkTitle, linkUrl); + } catch (IllegalArgumentException e) { + log.warn("【即时通推送-sendBatch】可选字段校验失败: {}", e.getMessage()); } KsimApiResult apiResult = apiClient.sendMsg(token, dto); diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java index fba7599..a4779ce 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java @@ -21,16 +21,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; /** * 审批意见快照 Service 实现。 @@ -54,7 +52,6 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl saveSupplementOpinion(SupplementApprovalOpinionDTO supplementDTO) { if (supplementDTO == null || oConvertUtils.isEmpty(supplementDTO.getProcessInstId())) { throw new JeecgBootException("流程实例ID不能为空"); @@ -93,41 +90,61 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl doPushNotification(TaskApprovalOpinion opinion, ApprovalDeptInfo deptInfo, + LoginUser loginUser, String opinionText, String frontendBizTitle) { Map pushResult = new HashMap<>(); + // 确保所有代码路径返回一致的 key 集合 + pushResult.put("pushed", false); + pushResult.put("toUsers", Collections.emptyList()); + pushResult.put("content", ""); + pushResult.put("title", ""); + pushResult.put("error", ""); + try { - if (isSuoLeader(loginUser, deptInfo.id)) { - String bizTitle = oConvertUtils.isNotEmpty(supplementDTO.getBizTitle()) - ? supplementDTO.getBizTitle() - : getBizTitle(opinion.getTaskTaskId(), opinion.getProcessInstId()); - String content = "所领导" + (oConvertUtils.isNotEmpty(loginUser.getRealname()) ? loginUser.getRealname() : loginUser.getUsername()) - + "增补意见:" + opinionText; - List handlerNames = getDistinctHandlerNames(opinion.getProcessInstId(), loginUser.getUsername(), loginUser.getRealname()); - pushResult.put("pushed", !handlerNames.isEmpty()); - pushResult.put("toUsers", handlerNames); - pushResult.put("content", content); - pushResult.put("title", bizTitle); - if (!handlerNames.isEmpty()) { - Result sendResult = ksimMessageService.sendBatch("Account", handlerNames, bizTitle, content, - null, null, null, null, null); - if (sendResult != null && sendResult.isSuccess()) { - log.info("【增补意见-即时通推送】已发送, processInstId: {}, toUserCount: {}, toUsers: {}, title: {}, content: {}", - opinion.getProcessInstId(), handlerNames.size(), handlerNames, bizTitle, content); - } else { - log.warn("【增补意见-即时通推送】发送失败, reason: {}", - sendResult != null ? sendResult.getMessage() : "null"); - pushResult.put("pushed", false); - } + if (!isSuoLeader(deptInfo)) { + return pushResult; + } + String bizTitle = oConvertUtils.isNotEmpty(frontendBizTitle) + ? frontendBizTitle + : getBizTitle(opinion.getTaskTaskId(), opinion.getProcessInstId()); + String content = "所领导" + (oConvertUtils.isNotEmpty(loginUser.getRealname()) ? loginUser.getRealname() : loginUser.getUsername()) + + "增补意见:" + opinionText; + LinkedHashMap handlerMap = getDistinctHandlerNames(opinion.getProcessInstId(), loginUser.getUsername(), loginUser.getRealname()); + List handlerIds = new ArrayList<>(handlerMap.keySet()); + List handlerNames = new ArrayList<>(handlerMap.values()); + pushResult.put("pushed", !handlerIds.isEmpty()); + pushResult.put("toUsers", handlerNames); + pushResult.put("content", content); + pushResult.put("title", bizTitle); + if (!handlerIds.isEmpty()) { + Result sendResult = ksimMessageService.sendBatch("Account", handlerIds, bizTitle, content, + null, null, null, null, null); + if (sendResult != null && sendResult.isSuccess()) { + log.info("【增补意见-即时通推送】已发送, processInstId: {}, toUserCount: {}, toUsers: {}, title: {}, content: {}", + opinion.getProcessInstId(), handlerIds.size(), handlerNames, bizTitle, content); + } else { + String reason = sendResult != null ? sendResult.getMessage() : "null"; + log.warn("【增补意见-即时通推送】发送失败, reason: {}", reason); + pushResult.put("pushed", false); + pushResult.put("error", reason); } - } else { - pushResult.put("pushed", false); } } catch (Exception e) { - log.warn("【增补意见-即时通推送】失败,不影响增补意见保存, error: {}", e.getMessage()); + String errMsg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + log.warn("【增补意见-即时通推送】失败,不影响增补意见保存, error: {}", errMsg); pushResult.put("pushed", false); - pushResult.put("error", e.getMessage()); + pushResult.put("error", errMsg); } return pushResult; } @@ -144,6 +161,7 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl> rows = jdbcTemplate.queryForList( + "SELECT depart_type FROM sys_depart WHERE id = ?", deptId); + if (!rows.isEmpty()) { + departType = getStringValue(rows.get(0), "depart_type"); + } + } catch (Exception e) { + log.warn("【增补意见-审批意见】查询部门类型失败, deptId={}", deptId, e); + } + } + return new ApprovalDeptInfo(deptId, deptName, deptCode, departType); } private String resolveDeptName(String username) { @@ -176,7 +211,7 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl> rows = jdbcTemplate.queryForList( - "select depart_type from sys_depart where id = ?", deptId); - if (!rows.isEmpty()) { - Object val = rows.get(0).get("depart_type"); - return val != null && "1".equals(String.valueOf(val)); - } - } catch (Exception e) { - log.debug("查询部门类型失败, deptId={}", deptId, e); - } - return false; + private boolean isSuoLeader(ApprovalDeptInfo deptInfo) { + return "1".equals(deptInfo.departType); } private String getBizTitle(String taskTaskId, String processInstId) { @@ -287,15 +306,16 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl getDistinctHandlerNames(String processInstId, String excludeUserId, String excludeRealName) { + /** userId → userName 映射,保持插入顺序 */ + private LinkedHashMap getDistinctHandlerNames(String processInstId, String excludeUserId, String excludeRealName) { if (jdbcTemplate == null || oConvertUtils.isEmpty(processInstId)) { - return Collections.emptyList(); + return new LinkedHashMap<>(); } try { List> rows = jdbcTemplate.queryForList( - "select distinct op_user_id, op_user_name from task_approval_opinion where process_inst_id = ?", + "SELECT DISTINCT op_user_id, op_user_name FROM task_approval_opinion WHERE process_inst_id = ?", processInstId); - Set userIds = new LinkedHashSet<>(); + LinkedHashMap handlerMap = new LinkedHashMap<>(); for (Map row : rows) { Object idVal = row.get("op_user_id"); Object nameVal = row.get("op_user_name"); @@ -318,14 +338,14 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl(); } } @@ -333,11 +353,13 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl Date: Mon, 27 Jul 2026 09:59:22 +0800 Subject: [PATCH 08/10] =?UTF-8?q?yxk-20260727=20=E5=9C=A8=20flowNodeExpres?= =?UTF-8?q?sion=20=E5=A2=9E=E5=8A=A0=E5=8D=95=E5=8F=82=E6=95=B0=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20getUsersListByRole(roleId)=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E6=8C=89=E7=B3=BB=E7=BB=9F=E8=A7=92=E8=89=B2=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=85=A8=E9=83=A8=E5=85=B3=E8=81=94=E7=94=A8=E6=88=B7?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E4=BE=9D=E8=B5=96=E9=83=A8=E9=97=A8?= =?UTF-8?q?=20ID=E3=80=82=20=E6=B5=81=E7=A8=8B=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=EF=BC=9A=20${flowNodeExpression.getUsersList?= =?UTF-8?q?ByRole('2044680793306591234')}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/expression/FlowNodeExpression.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java index 998d42a..fce908a 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java @@ -262,6 +262,20 @@ public class FlowNodeExpression { return ISysBaseAPI.getUsersListByJsonLocalAPI(json, "deptId", roleId); } + /** + * Get username list by system role id without department filtering. + * Example: ${flowNodeExpression.getUsersListByRole('roleIdValue')} + * + * @param roleId system role id + * @return username list + */ + public List getUsersListByRole(String roleId) { + if (StringUtils.isBlank(roleId)) { + return Collections.emptyList(); + } + return sysbase.getUserByRoleIdLocalApi(roleId); + } + /** * Get subprocess variable. * Example: ${flowNodeExpression.getSonProcessVariable(execution, 'subApproveUser')} From c4e19e5d6ecb4569e18be1551e78ed37ec56b4e5 Mon Sep 17 00:00:00 2001 From: ye1023 <12588209+ye1023@user.noreply.gitee.com> Date: Mon, 27 Jul 2026 10:30:18 +0800 Subject: [PATCH 09/10] =?UTF-8?q?yxk-20260727=20=E6=94=B9=E5=96=84?= =?UTF-8?q?=E6=8F=90=E6=A1=88=E8=A1=A8=E5=8D=95=E5=A2=9E=E5=8A=A0=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E9=9C=80=E8=A6=81=E6=89=80=E7=BA=A7=E8=AF=84=E8=AE=AE?= =?UTF-8?q?=E5=B0=8F=E7=BB=84=E6=8A=95=E7=A5=A8=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../improvementproposal/entity/ImprovementProposal.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java index ba3569d..095382a 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java @@ -100,9 +100,13 @@ public class ImprovementProposal implements Serializable { @Schema(description = "部门精益专员初步评定奖项") private String initialAward; /**是否需要部门评议打分:Y/N*/ - @Excel(name = "是否需要部门评议打分:Y/N", width = 15) - @Schema(description = "是否需要部门评议打分:Y/N") + @Excel(name = "是否需要部门评议打分:1/0", width = 15) + @Schema(description = "是否需要部门评议打分:1/0") private String needDeptScore; + /**是否需要部门评议打分:Y/N*/ + @Excel(name = "是否需要所级评议小组投票:1/0", width = 15) + @Schema(description = "是否需要所级评议小组投票:1/0") + private String isneedvote; /**部门评议平均分*/ @Excel(name = "部门评议平均分", width = 15) @Schema(description = "部门评议平均分") From 950f51f9c51d2724e025143470e8cd861d069d3c Mon Sep 17 00:00:00 2001 From: wsm <2746563060@qq.com> Date: Mon, 27 Jul 2026 14:24:26 +0800 Subject: [PATCH 10/10] =?UTF-8?q?feat(bpm):=20=E6=96=B0=E5=A2=9E=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E9=A9=B3=E5=9B=9E=E7=9B=AE=E6=A0=87=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=A1=A8=E5=8F=8A=E7=BA=AF=E5=A2=9E=E9=87=8F=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 ext_act_process_node_reject_target 表,支持一个节点配置多个可驳回目标节点。 纯增量实现:不修改已有 Controller/Service 方法逻辑,仅新增 3 个端点: - queryRejectTargets: 查询节点已配置的驳回目标(配置页回显) - saveRejectTargets: 保存驳回目标配置(先删后插,事务保护) - queryRejectTargetCodes: 运行时根据 processKey+nodeCode 查询允许的目标节点 ActTaskController 仅新增 processKey/taskDefinitionKey 字段暴露,原有逻辑不变。 Co-Authored-By: Claude Opus 4.7 --- db/upgrade/add_reject_target_node.sql | 12 ++++ .../bpm/controller/ActTaskController.java | 3 + .../ExtActProcessNodeController.java | 55 ++++++++++++++++++- .../entity/ExtActProcessNodeRejectTarget.java | 33 +++++++++++ .../ExtActProcessNodeRejectTargetMapper.java | 31 +++++++++++ .../ExtActProcessNodeRejectTargetMapper.xml | 34 ++++++++++++ ...IExtActProcessNodeRejectTargetService.java | 20 +++++++ ...ActProcessNodeRejectTargetServiceImpl.java | 36 ++++++++++++ 8 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 db/upgrade/add_reject_target_node.sql create mode 100644 jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/entity/ExtActProcessNodeRejectTarget.java create mode 100644 jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/ExtActProcessNodeRejectTargetMapper.java create mode 100644 jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/xml/ExtActProcessNodeRejectTargetMapper.xml create mode 100644 jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/IExtActProcessNodeRejectTargetService.java create mode 100644 jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessNodeRejectTargetServiceImpl.java diff --git a/db/upgrade/add_reject_target_node.sql b/db/upgrade/add_reject_target_node.sql new file mode 100644 index 0000000..3eaecfb --- /dev/null +++ b/db/upgrade/add_reject_target_node.sql @@ -0,0 +1,12 @@ +-- 流程节点驳回目标节点配置表 +-- 一对多:一个源节点可配置多个可驳回的目标节点 +CREATE TABLE IF NOT EXISTS `ext_act_process_node_reject_target` ( + `id` varchar(64) NOT NULL COMMENT '主键ID', + `process_id` varchar(64) NOT NULL COMMENT '流程ID(对应ext_act_process_node.process_id)', + `source_node_code` varchar(100) NOT NULL COMMENT '源节点CODE(当前配置驳回的节点)', + `target_node_code` varchar(100) NOT NULL COMMENT '目标节点CODE(可以驳回到的节点)', + `target_node_name` varchar(200) DEFAULT NULL COMMENT '目标节点名称', + PRIMARY KEY (`id`), + KEY `idx_process_source` (`process_id`, `source_node_code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程节点驳回目标配置'; +、 \ No newline at end of file diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActTaskController.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActTaskController.java index 396441f..7cf41f0 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActTaskController.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActTaskController.java @@ -357,6 +357,9 @@ public class ActTaskController { map.put("defaultOpinionCond0", extActProcessNode.getDefaultOpinion()); map.put("defaultOpinionCond1", extActProcessNode.getDefaultOpinion2()); } + // 流程标识和当前节点编码,供前端做驳回目标过滤 + map.put("processKey", pd.getKey()); + map.put("taskDefinitionKey", task.getTaskDefinitionKey()); //--update--end------author:scott-----date:20220705-----for:【VUEN-1332】流程节点配置智能化------------- // 流程审批日志 diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java index a6717cf..9494d0d 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessNodeController.java @@ -22,12 +22,15 @@ import org.jeecg.modules.extbpm.process.entity.ExtActFlowData; import org.jeecg.modules.extbpm.process.entity.ExtActProcessNode; import org.jeecg.modules.extbpm.process.entity.ExtActProcessNodeDeployment; import org.jeecg.modules.extbpm.process.entity.ExtActProcessNodePermission; +import org.jeecg.modules.extbpm.process.entity.ExtActProcessNodeRejectTarget; import org.jeecg.modules.extbpm.process.pojo.BizTaskDTO; import org.jeecg.modules.extbpm.process.service.IExtActFlowDataService; import org.jeecg.modules.extbpm.process.service.IExtActProcessNodeDeploymentService; import org.jeecg.modules.extbpm.process.service.IExtActProcessNodePermissionService; +import org.jeecg.modules.extbpm.process.service.IExtActProcessNodeRejectTargetService; import org.jeecg.modules.extbpm.process.service.IExtActProcessNodeService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; @@ -67,6 +70,8 @@ public class ExtActProcessNodeController { private IExtActProcessNodePermissionService extActProcessNodePermissionService; @Autowired private IExtActFlowDataService extActFlowDataService; + @Autowired + private IExtActProcessNodeRejectTargetService extActProcessNodeRejectTargetService; /** * 分页列表查询 @@ -142,7 +147,7 @@ public class ExtActProcessNodeController { result.success("删除成功!"); } } - + return result; } @@ -158,12 +163,58 @@ public class ExtActProcessNodeController { if(ids==null || "".equals(ids.trim())) { result.error500("参数不识别!"); }else { - this.extActProcessNodeService.removeByIds(Arrays.asList(ids.split(","))); + List idList = Arrays.asList(ids.split(",")); + this.extActProcessNodeService.removeByIds(idList); result.success("删除成功!"); } return result; } + @GetMapping(value = "/queryRejectTargets") + public Result> queryRejectTargets( + @RequestParam(name="processId", required=true) String processId, + @RequestParam(name="sourceNodeCode", required=true) String sourceNodeCode) { + Result> result = new Result<>(); + List list = extActProcessNodeRejectTargetService.queryBySourceNode(processId, sourceNodeCode); + result.setResult(list); + result.setSuccess(true); + return result; + } + + /** + * 保存节点的驳回目标配置(先删旧、后插新) + */ + @Transactional + @PostMapping(value = "/saveRejectTargets") + public Result saveRejectTargets( + @RequestParam(name="processId", required=true) String processId, + @RequestParam(name="sourceNodeCode", required=true) String sourceNodeCode, + @RequestBody(required = false) List rejectTargetList) { + // 删除旧配置 + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper<>(); + deleteWrapper.eq(ExtActProcessNodeRejectTarget::getProcessId, processId); + deleteWrapper.eq(ExtActProcessNodeRejectTarget::getSourceNodeCode, sourceNodeCode); + extActProcessNodeRejectTargetService.remove(deleteWrapper); + // 插入新配置 + if (rejectTargetList != null && !rejectTargetList.isEmpty()) { + extActProcessNodeRejectTargetService.saveBatch(rejectTargetList); + } + return Result.ok("保存成功"); + } + /** + * 根据 processKey + nodeCode 查询可驳回的目标节点CODE列表(运行时过滤用) + */ + @GetMapping(value = "/queryRejectTargetCodes") + public Result> queryRejectTargetCodes( + @RequestParam(name="processKey", required=true) String processKey, + @RequestParam(name="nodeCode", required=true) String nodeCode) { + Result> result = new Result<>(); + List list = extActProcessNodeRejectTargetService.queryRejectTargetCodesByProcessKeyAndNodeCode(processKey, nodeCode); + result.setResult(list); + result.setSuccess(true); + return result; + } + /** * 通过id查询 * @param id diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/entity/ExtActProcessNodeRejectTarget.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/entity/ExtActProcessNodeRejectTarget.java new file mode 100644 index 0000000..b40d568 --- /dev/null +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/entity/ExtActProcessNodeRejectTarget.java @@ -0,0 +1,33 @@ +package org.jeecg.modules.extbpm.process.entity; + +import java.io.Serializable; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import lombok.Data; + +/** + * @Description: 流程节点驳回目标配置 + * @author: jeecg-boot + * @date: 2026-07-27 + * @version: V1.0 + */ +@Data +@TableName("ext_act_process_node_reject_target") +public class ExtActProcessNodeRejectTarget implements Serializable { + private static final long serialVersionUID = 1L; + + /**id*/ + @TableId(type = IdType.ASSIGN_ID) + private java.lang.String id; + /**流程ID*/ + private java.lang.String processId; + /**源节点CODE(当前配置驳回的节点)*/ + private java.lang.String sourceNodeCode; + /**目标节点CODE(可以驳回到的节点)*/ + private java.lang.String targetNodeCode; + /**目标节点名称*/ + private java.lang.String targetNodeName; +} diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/ExtActProcessNodeRejectTargetMapper.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/ExtActProcessNodeRejectTargetMapper.java new file mode 100644 index 0000000..08eea21 --- /dev/null +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/ExtActProcessNodeRejectTargetMapper.java @@ -0,0 +1,31 @@ +package org.jeecg.modules.extbpm.process.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; +import org.jeecg.modules.extbpm.process.entity.ExtActProcessNodeRejectTarget; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * @Description: 流程节点驳回目标配置 + * @author: jeecg-boot + * @date: 2026-07-27 + * @version: V1.0 + */ +public interface ExtActProcessNodeRejectTargetMapper extends BaseMapper { + + /** + * 通过 processKey + nodeCode 查询可驳回的目标节点CODE列表(运行时过滤用) + */ + List queryRejectTargetCodesByProcessKeyAndNodeCode( + @Param("processKey") String processKey, + @Param("nodeCode") String nodeCode); + + /** + * 查询某源节点的所有驳回目标配置(配置页面回显用) + */ + List queryBySourceNode( + @Param("processId") String processId, + @Param("sourceNodeCode") String sourceNodeCode); +} diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/xml/ExtActProcessNodeRejectTargetMapper.xml b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/xml/ExtActProcessNodeRejectTargetMapper.xml new file mode 100644 index 0000000..bd376bd --- /dev/null +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/mapper/xml/ExtActProcessNodeRejectTargetMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/IExtActProcessNodeRejectTargetService.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/IExtActProcessNodeRejectTargetService.java new file mode 100644 index 0000000..74dd93f --- /dev/null +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/IExtActProcessNodeRejectTargetService.java @@ -0,0 +1,20 @@ +package org.jeecg.modules.extbpm.process.service; + +import java.util.List; + +import org.jeecg.modules.extbpm.process.entity.ExtActProcessNodeRejectTarget; + +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * @Description: 流程节点驳回目标配置 + * @author: jeecg-boot + * @date: 2026-07-27 + * @version: V1.0 + */ +public interface IExtActProcessNodeRejectTargetService extends IService { + + List queryRejectTargetCodesByProcessKeyAndNodeCode(String processKey, String nodeCode); + + List queryBySourceNode(String processId, String sourceNodeCode); +} diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessNodeRejectTargetServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessNodeRejectTargetServiceImpl.java new file mode 100644 index 0000000..036e484 --- /dev/null +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/service/impl/ExtActProcessNodeRejectTargetServiceImpl.java @@ -0,0 +1,36 @@ +package org.jeecg.modules.extbpm.process.service.impl; + +import java.util.List; + +import org.jeecg.modules.extbpm.process.entity.ExtActProcessNodeRejectTarget; +import org.jeecg.modules.extbpm.process.mapper.ExtActProcessNodeRejectTargetMapper; +import org.jeecg.modules.extbpm.process.service.IExtActProcessNodeRejectTargetService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +/** + * @Description: 流程节点驳回目标配置 + * @author: jeecg-boot + * @date: 2026-07-27 + * @version: V1.0 + */ +@Service("extActProcessNodeRejectTargetService") +public class ExtActProcessNodeRejectTargetServiceImpl + extends ServiceImpl + implements IExtActProcessNodeRejectTargetService { + + @Autowired + private ExtActProcessNodeRejectTargetMapper extActProcessNodeRejectTargetMapper; + + @Override + public List queryRejectTargetCodesByProcessKeyAndNodeCode(String processKey, String nodeCode) { + return extActProcessNodeRejectTargetMapper.queryRejectTargetCodesByProcessKeyAndNodeCode(processKey, nodeCode); + } + + @Override + public List queryBySourceNode(String processId, String sourceNodeCode) { + return extActProcessNodeRejectTargetMapper.queryBySourceNode(processId, sourceNodeCode); + } +}