diff --git a/.gitignore b/.gitignore index 988d5f855..5cc135113 100644 --- a/.gitignore +++ b/.gitignore @@ -63,5 +63,9 @@ application-preview.yml ### node ### node_modules +### claude code ### +.claude +docs + ### test -*-aitest* \ No newline at end of file +aitest \ No newline at end of file diff --git a/pigx-flow/pigx-flow-api/pom.xml b/pigx-flow/pigx-flow-api/pom.xml index 0ddeb5fdf..a730ebb01 100644 --- a/pigx-flow/pigx-flow-api/pom.xml +++ b/pigx-flow/pigx-flow-api/pom.xml @@ -7,7 +7,7 @@ com.pig4cloud pigx-flow - 5.11.0 + 5.12.0 pigx-flow-api @@ -33,9 +33,11 @@ com.pig4cloud pigx-common-excel + - com.pig4cloud - pigx-common-core + org.springframework.boot + spring-boot-starter-data-redis + true diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/config/FlowStatusEventAutoConfiguration.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/config/FlowStatusEventAutoConfiguration.java new file mode 100644 index 000000000..2cf6e0b54 --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/config/FlowStatusEventAutoConfiguration.java @@ -0,0 +1,58 @@ +package com.pig4cloud.pigx.flow.config; + +import com.pig4cloud.pigx.flow.event.FlowStatusEventListener; +import com.pig4cloud.pigx.flow.event.FlowStatusEventPublisher; +import com.pig4cloud.pigx.flow.event.IProcessInstanceStatusEventService; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; + +import java.util.List; + +/** + * 流程状态事件自动配置 + * + * @author lengleng + * @date 2026-02-10 + */ +@AutoConfiguration +@ConditionalOnClass(StringRedisTemplate.class) +public class FlowStatusEventAutoConfiguration { + + /** + * 事件发布者 - 所有依赖 flow-api 的模块可用 + */ + @Bean + @ConditionalOnMissingBean + public FlowStatusEventPublisher flowStatusEventPublisher(StringRedisTemplate redisTemplate) { + return new FlowStatusEventPublisher(redisTemplate); + } + + /** + * 事件监听器 - 仅当有 IProcessInstanceStatusEventService 实现时启用 + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(IProcessInstanceStatusEventService.class) + public FlowStatusEventListener flowStatusEventListener(List eventServices, + StringRedisTemplate redisTemplate) { + return new FlowStatusEventListener(eventServices, redisTemplate); + } + + /** + * 注册监听器到 Redis 容器 + */ + @Bean + @ConditionalOnBean(FlowStatusEventListener.class) + public ChannelTopic flowStatusTopic(RedisMessageListenerContainer container, FlowStatusEventListener listener) { + ChannelTopic topic = new ChannelTopic(FlowStatusEventPublisher.CHANNEL); + container.addMessageListener(listener, topic); + return topic; + } + +} diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/FlowStatusEventListener.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/FlowStatusEventListener.java new file mode 100644 index 000000000..791787665 --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/FlowStatusEventListener.java @@ -0,0 +1,52 @@ +package com.pig4cloud.pigx.flow.event; + +import cn.hutool.json.JSONUtil; +import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.List; + +/** + * 流程状态事件监听器 + *

+ * 监听 Redis Channel,将事件分发给匹配的 IProcessInstanceStatusEventService 实现 + *

+ * + * @author lengleng + * @date 2026-02-10 + */ +@Slf4j +@RequiredArgsConstructor +public class FlowStatusEventListener implements MessageListener { + + private final List eventServices; + + private final StringRedisTemplate redisTemplate; + + @Override + public void onMessage(Message message, byte[] pattern) { + String body = redisTemplate.getStringSerializer().deserialize(message.getBody()); + ProcessInstanceParamDto paramDto = JSONUtil.toBean(body, ProcessInstanceParamDto.class); + + log.debug("收到流程状态事件: flowId={}, processInstanceId={}", paramDto.getFlowId(), + paramDto.getProcessInstanceId()); + + // 分发给匹配 flowId 的实现 + for (IProcessInstanceStatusEventService service : eventServices) { + if (service.getFlowId().equals(paramDto.getFlowId())) { + try { + service.handleStatusEvent(paramDto); + } + catch (Exception e) { + log.error("处理流程状态事件失败: flowId={}, service={}", paramDto.getFlowId(), + service.getClass().getSimpleName(), e); + } + } + } + } + +} diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/FlowStatusEventPublisher.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/FlowStatusEventPublisher.java new file mode 100644 index 000000000..8cf2c3dbb --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/FlowStatusEventPublisher.java @@ -0,0 +1,37 @@ +package com.pig4cloud.pigx.flow.event; + +import cn.hutool.json.JSONUtil; +import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * 流程状态事件发布者 + *

+ * 将流程状态变更事件发布到 Redis Channel + *

+ * + * @author lengleng + * @date 2026-02-10 + */ +@Slf4j +@RequiredArgsConstructor +public class FlowStatusEventPublisher { + + public static final String CHANNEL = "pigx:flow:status"; + + private final StringRedisTemplate redisTemplate; + + /** + * 发布流程状态变更事件 + * @param paramDto 流程实例参数 + */ + public void publish(ProcessInstanceParamDto paramDto) { + String message = JSONUtil.toJsonStr(paramDto); + redisTemplate.convertAndSend(CHANNEL, message); + log.debug("发布流程状态事件: flowId={}, processInstanceId={}", paramDto.getFlowId(), + paramDto.getProcessInstanceId()); + } + +} diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/IProcessInstanceStatusEventService.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/IProcessInstanceStatusEventService.java new file mode 100644 index 000000000..6afb8a1b9 --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/event/IProcessInstanceStatusEventService.java @@ -0,0 +1,29 @@ +package com.pig4cloud.pigx.flow.event; + +import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto; + +/** + * 流程实例状态事件服务接口 + *

+ * 实现此接口以监听流程状态变更事件。 + * 通过 Redis Pub/Sub 实现跨模块通知。 + *

+ * + * @author lengleng + * @date 2026-02-10 + */ +public interface IProcessInstanceStatusEventService { + + /** + * 获取监听的流程定义Key + * @return 监听的流程定义Key + */ + String getFlowId(); + + /** + * 处理状态事件 + * @param paramDto 流程实例参数DTO + */ + void handleStatusEvent(ProcessInstanceParamDto paramDto); + +} diff --git a/pigx-flow/pigx-flow-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/pigx-flow/pigx-flow-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..0a86175a6 --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.pig4cloud.pigx.flow.config.FlowStatusEventAutoConfiguration \ No newline at end of file diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/BpmOaLeaveService.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/BpmOaLeaveService.java index 52e439b2b..4da92c8b9 100644 --- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/BpmOaLeaveService.java +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/BpmOaLeaveService.java @@ -2,7 +2,14 @@ package com.pig4cloud.pigx.flow.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pigx.flow.entity.BpmOaLeaveEntity; +import com.pig4cloud.pigx.flow.event.IProcessInstanceStatusEventService; +/** + * 请假申请服务接口 + * + * @author lengleng + * @date 2026-02-10 + */ public interface BpmOaLeaveService extends IService, IProcessInstanceStatusEventService { /** diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java index 3c67cef54..f0efc1c48 100644 --- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java @@ -23,8 +23,8 @@ import com.pig4cloud.pigx.flow.dto.Node; import com.pig4cloud.pigx.flow.dto.NodeUser; import com.pig4cloud.pigx.flow.entity.Process; import com.pig4cloud.pigx.flow.entity.ProcessStarter; +import com.pig4cloud.pigx.flow.event.IProcessInstanceStatusEventService; import com.pig4cloud.pigx.flow.mapper.ProcessMapper; -import com.pig4cloud.pigx.flow.service.IProcessInstanceStatusEventService; import com.pig4cloud.pigx.flow.service.IProcessService; import com.pig4cloud.pigx.flow.service.IProcessStarterService; import com.pig4cloud.pigx.flow.support.utils.ModelUtil; @@ -53,6 +53,7 @@ import java.util.Optional; *

* * @author cxygzl + * @date 2026-02-10 * @since 2023-05-25 */ @Slf4j diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/listeners/NodeTaskCompleteNotify.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/listeners/NodeTaskCompleteNotify.java index 6b7ab81da..f40a04d5e 100644 --- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/listeners/NodeTaskCompleteNotify.java +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/listeners/NodeTaskCompleteNotify.java @@ -10,17 +10,14 @@ import com.pig4cloud.pigx.common.security.util.NonWebTokenContextHolder; import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto; import com.pig4cloud.pigx.flow.dto.ProcessNodeRecordAssignUserParamDto; import com.pig4cloud.pigx.flow.entity.ProcessInstanceRecord; +import com.pig4cloud.pigx.flow.event.FlowStatusEventPublisher; import com.pig4cloud.pigx.flow.mapper.ProcessInstanceRecordMapper; -import com.pig4cloud.pigx.flow.service.IProcessInstanceStatusEventService; import com.pig4cloud.pigx.flow.service.IProcessNodeDataService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Optional; - /** * 节点任务完成通知服务 *

@@ -29,7 +26,7 @@ import java.util.Optional; *

* * @author lengleng - * @date 2024/7/9 + * @date 2026-02-10 */ @Slf4j @Service @@ -40,7 +37,7 @@ public class NodeTaskCompleteNotify { private final ProcessInstanceRecordMapper processInstanceRecordMapper; - private final Optional> processInstanceStatusEventServicesOptional; + private final FlowStatusEventPublisher flowStatusEventPublisher; /** * 异步发送任务完成通知 @@ -76,33 +73,28 @@ public class NodeTaskCompleteNotify { } /** - * 异步发送流程通知 - * @param token 认证令牌 - * @param instanceRecordParamDto 流程节点记录参数DTO - * @throws IllegalArgumentException 参数为空时抛出异常 + * 异步发送流程实例状态变更通知 + *

+ * 当流程实例状态发生变更时,查询最新的流程实例记录状态, + * 并通过Redis发布流程状态变更事件,供订阅方消费处理。 + *

+ * @param token 安全令牌,用于在异步线程中传递认证信息 + * @param instanceRecordParamDto 流程实例参数,包含流程实例ID等信息 */ public void sendFlowNotify(String token, ProcessInstanceParamDto instanceRecordParamDto) { // 在异步线程中设置认证令牌 NonWebTokenContextHolder.setToken(token); - if (processInstanceStatusEventServicesOptional.isEmpty()) { - return; - } - for (IProcessInstanceStatusEventService iProcessInstanceStatusEventService : processInstanceStatusEventServicesOptional - .get()) { + // 查询最新状态 + ProcessInstanceRecord processInstanceRecord = processInstanceRecordMapper + .selectOne(Wrappers.lambdaQuery() + .eq(ProcessInstanceRecord::getProcessInstanceId, instanceRecordParamDto.getProcessInstanceId())); - if (iProcessInstanceStatusEventService.getFlowId().equals(instanceRecordParamDto.getFlowId())) { - // 处理状态事件 - ProcessInstanceRecord processInstanceRecord = processInstanceRecordMapper.selectOne(Wrappers - .lambdaQuery() - .eq(ProcessInstanceRecord::getProcessInstanceId, instanceRecordParamDto.getProcessInstanceId())); + instanceRecordParamDto.setStatus(processInstanceRecord.getStatus()); + instanceRecordParamDto.setFinishReason(processInstanceRecord.getFinishReason()); - instanceRecordParamDto.setStatus(processInstanceRecord.getStatus()); - instanceRecordParamDto.setFinishReason(processInstanceRecord.getFinishReason()); - iProcessInstanceStatusEventService.handleStatusEvent(instanceRecordParamDto); - } - - } + // 发布到 Redis + flowStatusEventPublisher.publish(instanceRecordParamDto); } }