refactor(workflow): 工作流模块改由 ConditionalOnWorkflowEnabled 条件装配

- 移除启动类手工 ComponentScan 排除与 WorkflowModuleConfig、
  WorkflowEnvironmentPostProcessor、spring.factories 自动装配配置
- 新增 @ConditionalOnWorkflowEnabled 注解,按 flowable.enabled 决定工作流模块是否生效
- 启动类回归 @SpringBootApplication,各配置属性类改用 @Component 显式注册
This commit is contained in:
Ray.Hao
2026-09-08 16:40:47 +08:00
parent 2bc7b49bb6
commit 34bfeedadf
38 changed files with 142 additions and 149 deletions

View File

@@ -149,9 +149,8 @@ youlai-boot/
│ │ │ └── util/ # 安全工具类
│ │ └── web/ # Web 层(全局异常/跨域/限流)
│ ├── module/ # 业务扩展模块
│ │ ├── config/ # 模块装配(工作流按 flowable.enabled 开关)
│ │ ├── form/ # 动态表单
│ │ └── workflow/ # 工作流(Flowable 审批流)
│ │ └── workflow/ # 工作流
│ ├── support/ # 能力服务
│ │ ├── file/ # 文件存储(RustFS/阿里云OSS/本地)
│ │ ├── mail/ # 邮件服务
@@ -215,10 +214,14 @@ youlai-boot/
2. 在 `application-{profile}.yml` 中开启:
```yaml
flowable:
workflow:
enabled: true
```
Flowable 引擎开关(process / idm / eventregistry)在同一 `application-{profile}.yml` 中以占位符跟随 `workflow.enabled`,无需单独维护。
演示流程的启动自动部署由同文件 `workflow.demo.enabled` 控制(dev/youlai 默认 `true`、prod 默认 `false`,仅在模块开启后生效)。
**多租户**
需要租户隔离与租户配置,使用独立仓库 [youlai-boot-tenant](https://gitee.com/youlaiorg/youlai-boot-tenant)。
@@ -229,8 +232,23 @@ youlai-boot/
如需技术支持、商务合作、二次开发、项目定制或私有化部署,可联系作者微信(见下方二维码)。
| 公众号「有来技术」 | <br /> | 小程序「有来技术」 | <br /> | 添加作者微信 |
| --------- | ------ | --------- | ------ | ------ |
| <br /> | <br /> | <br /> | <br /> | <br /> |
<table align="center">
<tr>
<td align="center">
<img src="./docs/images/qrcode/wechat-official.jpg" height="180" alt="公众号「有来技术」"><br>
<sub>公众号「有来技术」</sub>
</td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td align="center">
<img src="./docs/images/qrcode/wechat-mp.jpg" height="180" alt="小程序「有来技术」"><br>
<sub>小程序「有来技术」</sub>
</td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td align="center">
<img src="./docs/images/qrcode/wechat-personal.png" height="180" alt="添加作者微信"><br>
<sub>添加作者微信</sub>
</td>
</tr>
</table>
<p align="center"><em>技术交流 · 问题反馈 · 商务合作</em></p>

View File

@@ -295,7 +295,7 @@
<version>${weixin-java-miniapp.version}</version>
</dependency>
<!-- 工作流引擎(可选扩展 module/workflow,只引 process 引擎,不含 CMMN/DMN) -->
<!-- 工作流引擎(可选扩展,只引 process 引擎,不含 CMMN/DMN) -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>

View File

@@ -1,34 +1,17 @@
package com.youlai.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 应用启动类
*
* <p>等价于 @SpringBootApplication,但显式声明 @ComponentScan 以排除工作流包,
* 改由 {@link com.youlai.boot.module.config.WorkflowModuleConfig} 按 flowable.enabled 决定是否扫描</p>
*
* @author Ray.Hao
* @since 3.0.0
*/
@ConfigurationPropertiesScan
@EnableScheduling
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.youlai.boot", excludeFilters = {
// 保留 @SpringBootApplication 默认的排除器
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com\\.youlai\\.boot\\.module\\.workflow\\..*")
})
@SpringBootApplication
public class YouLaiBootApplication {
public static void main(String[] args) {

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.auth.password.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 验证码配置属性
@@ -9,6 +10,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Ray.Hao
* @since 2023/11/24
*/
@Component
@ConfigurationProperties(prefix = "captcha")
@Data
public class CaptchaProperties {

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.auth.wxma.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信小程序配置属性
@@ -9,6 +10,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Ray.Hao
*/
@Data
@Component
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.map.MapUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@@ -14,6 +15,7 @@ import java.util.Map;
* @author Ray
* @since 2.11.0
*/
@Component
@ConfigurationProperties(prefix = "codegen")
@Data
public class CodegenProperties {

View File

@@ -22,7 +22,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@ConditionalOnProperty(name = "spring.cache.enabled") // xxl.job.enabled = true 才会自动装配
@ConditionalOnProperty(name = "spring.cache.enabled") // 未配置视为关闭
public class RedisCacheConfig {
/**

View File

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.Pattern;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import org.springframework.stereotype.Component;
/**
* 安全模块配置属性类
@@ -19,6 +20,7 @@ import org.springframework.validation.annotation.Validated;
*/
@Data
@Validated
@Component
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.framework.web.ratelimit;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.time.Duration;
@@ -13,6 +14,7 @@ import java.time.Duration;
* @since 4.3.1
*/
@Data
@Component
@ConfigurationProperties(prefix = "rate-limit")
public class RateLimitProperties {

View File

@@ -1,64 +0,0 @@
package com.youlai.boot.module.config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
/**
* 工作流关闭时排除 Flowable 引擎自动配置
*
* @author Ray.Hao
* @since 4.9.0
*/
public class WorkflowEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
/** 工作流开关属性 */
private static final String WORKFLOW_ENABLED = "flowable.enabled";
/** 自动配置排除列表属性 */
private static final String AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
/** Flowable 引擎相关的自动配置类 */
private static final List<String> FLOWABLE_AUTOCONFIGURATIONS = List.of(
"org.flowable.spring.boot.ProcessEngineAutoConfiguration",
"org.flowable.spring.boot.ProcessEngineServicesAutoConfiguration"
);
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 与 WorkflowModuleConfig 的条件一致:未配置视为关闭
if ("true".equalsIgnoreCase(environment.getProperty(WORKFLOW_ENABLED))) {
return;
}
// 合并已有的排除项,避免覆盖用户自己的 spring.autoconfigure.exclude
List<String> excluded = new ArrayList<>(Binder.get(environment)
.bind(AUTOCONFIGURE_EXCLUDE, Bindable.listOf(String.class))
.orElseGet(List::of));
for (String className : FLOWABLE_AUTOCONFIGURATIONS) {
if (!excluded.contains(className)) {
excluded.add(className);
}
}
Map<String, Object> properties = new HashMap<>();
properties.put(AUTOCONFIGURE_EXCLUDE, String.join(",", excluded));
environment.getPropertySources().addFirst(new MapPropertySource("workflowDisabled", properties));
}
/**
* 晚于 ConfigDataEnvironmentPostProcessor 执行,保证 profile 配置文件已加载
*/
@Override
public int getOrder() {
return ConfigDataEnvironmentPostProcessor.ORDER + 1;
}
}

View File

@@ -1,19 +0,0 @@
package com.youlai.boot.module.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 工作流模块装配
*
* <p>仅当 flowable.enabled 为 true 时扫描工作流包,未配置视为关闭</p>
*
* @author Ray.Hao
* @since 4.9.0
*/
@Configuration
@ConditionalOnProperty(name = "flowable.enabled", havingValue = "true")
@ComponentScan(basePackages = "com.youlai.boot.module.workflow")
public class WorkflowModuleConfig {
}

View File

@@ -70,9 +70,6 @@ public interface FormDataService {
/**
* 按表单标识删除全部提交数据(演示数据重置用)
*
* <p>跨模块窄接口:formKey 到 formId 的解析与删除条件封装在 form 模块内部,
* 调用方(workflow)不感知表单实体与查询条件</p>
*
* @param formKey 表单唯一标识
*/
void removeByFormKey(String formKey);

View File

@@ -88,7 +88,6 @@ public class FormDataServiceImpl extends ServiceImpl<FormDataMapper, FormData> i
@Override
public boolean savePublicFormData(String formKey, Map<String, Object> data, String ip) {
FormDefinition form = formDefinitionService.getByFormKey(formKey);
// 公开访问总闸:未开启公开或未发布的表单拿 key 也提交不了
if (!FormStatusEnum.PUBLISHED.getValue().equals(form.getStatus())
|| !Integer.valueOf(1).equals(form.getIsPublic())) {
throw new BusinessException("表单不存在或未开放公开访问");
@@ -218,8 +217,6 @@ public class FormDataServiceImpl extends ServiceImpl<FormDataMapper, FormData> i
/**
* 按表单标识删除全部提交数据(演示数据重置用)
*
* <p>formKey 到 formId 的解析与删除条件封装在本模块内部,跨模块调用方不感知表单实体与查询条件</p>
*
* @param formKey 表单唯一标识
*/
@Override

View File

@@ -229,7 +229,6 @@ public class FormDefinitionServiceImpl extends ServiceImpl<FormDefinitionMapper,
.setSql("version = version + 1")
);
if (result) {
// 回读原子自增后的最新表单,快照记录与当前定义一致的规则
FormDefinition published = this.getById(id);
// 固化版本快照:数据回显按提交时版本加载,防止规则变更(字段删除/改名)导致历史数据漂移
FormSnapshot snapshot = new FormSnapshot();
@@ -451,7 +450,6 @@ public class FormDefinitionServiceImpl extends ServiceImpl<FormDefinitionMapper,
menuFormData.setParentId(underAdminCatalog ? adminCatalog.getId() : 0L);
menuFormData.setName(DEFAULT_CATALOG_NAME);
menuFormData.setType(MenuTypeEnum.CATALOG.getValue());
menuFormData.setRouteName("FormCenter");
menuFormData.setRoutePath(underAdminCatalog ? "center" : "/form-center");
menuFormData.setComponent("Layout");
// alwaysShow=1:角色仅有单个表单入口时父级目录不被侧边栏折叠吞掉
@@ -462,10 +460,14 @@ public class FormDefinitionServiceImpl extends ServiceImpl<FormDefinitionMapper,
menuFormData.setIcon("el-icon-EditPen");
menuService.saveMenu(menuFormData);
return menuService.getOne(new LambdaQueryWrapper<Menu>()
.eq(Menu::getRouteName, "FormCenter")
// 目录类型保存时 routeName 被置空,按名称+类型回查
Menu createdCatalog = menuService.getOne(new LambdaQueryWrapper<Menu>()
.eq(Menu::getName, DEFAULT_CATALOG_NAME)
.eq(Menu::getType, MenuTypeEnum.CATALOG.getValue())
.last("LIMIT 1")
);
Assert.notNull(createdCatalog, "默认目录创建失败");
return createdCatalog;
}
/**

View File

@@ -0,0 +1,24 @@
package com.youlai.boot.module.workflow.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
/**
* 工作流启用条件
*
* <p>workflow.enabled 显式为 true 时生效,未配置视为关闭</p>
*
* @author Ray.Hao
* @since 4.9.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnBooleanProperty(prefix = "workflow", name = "enabled")
public @interface ConditionalOnWorkflowEnabled {
}

View File

@@ -16,6 +16,7 @@ import java.util.List;
* @author Ray.Hao
* @since 4.9.0
*/
@ConditionalOnWorkflowEnabled
@Configuration(proxyBeanMethods = false)
public class WorkflowEngineConfig {

View File

@@ -6,6 +6,7 @@ import com.youlai.boot.module.workflow.model.vo.ModelXmlVO;
import com.youlai.boot.module.workflow.model.vo.ProcessStageVO;
import com.youlai.boot.module.workflow.model.vo.StartableProcessVO;
import com.youlai.boot.module.workflow.service.WorkflowDefinitionService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -19,12 +20,13 @@ import java.util.List;
/**
* 流程定义控制层(发布产物:发布后供发起、审批与流程图查看)
*
* <p>定义的发布入口在模型接口(deploy),删除随模型级联,本层不再提供独立分页与删除</p>
* <p>定义的发布在模型接口(deploy),删除随模型级联</p>
*
* @author Ray.Hao
* @since 4.9.0
*/
@Tag(name = "13.工作流定义接口")
@ConditionalOnWorkflowEnabled
@RestController
@RequestMapping("/api/v1/workflow/definitions")
@RequiredArgsConstructor

View File

@@ -13,6 +13,7 @@ import com.youlai.boot.module.workflow.model.vo.InstanceDetailVO;
import com.youlai.boot.module.workflow.model.vo.InstancePageVO;
import com.youlai.boot.module.workflow.model.vo.ProcessDiagramVO;
import com.youlai.boot.module.workflow.service.WorkflowInstanceService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.*;
* @since 4.9.0
*/
@Tag(name = "13.工作流实例接口")
@ConditionalOnWorkflowEnabled
@RestController
@RequestMapping("/api/v1/workflow/instances")
@RequiredArgsConstructor

View File

@@ -14,6 +14,7 @@ import com.youlai.boot.module.workflow.model.vo.ModelVO;
import com.youlai.boot.module.workflow.model.vo.ModelXmlVO;
import com.youlai.boot.module.workflow.service.WorkflowDemoService;
import com.youlai.boot.module.workflow.service.WorkflowModelService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -31,6 +32,7 @@ import java.util.Arrays;
* @since 4.9.0
*/
@Tag(name = "13.工作流模型接口")
@ConditionalOnWorkflowEnabled
@RestController
@RequestMapping("/api/v1/workflow/models")
@RequiredArgsConstructor

View File

@@ -15,6 +15,7 @@ import com.youlai.boot.module.workflow.model.vo.RejectTargetVO;
import com.youlai.boot.module.workflow.model.vo.TaskDetailVO;
import com.youlai.boot.module.workflow.model.vo.TodoTaskVO;
import com.youlai.boot.module.workflow.service.WorkflowTaskService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -33,6 +34,7 @@ import java.util.List;
* @since 4.9.0
*/
@Tag(name = "13.工作流任务接口")
@ConditionalOnWorkflowEnabled
@RestController
@RequestMapping("/api/v1/workflow/tasks")
@RequiredArgsConstructor

View File

@@ -4,8 +4,7 @@ package com.youlai.boot.module.workflow.handler;
* 流程审批通过后的业务处理器(扩展点)
*
* <p>一个流程定义 Key 对应一个处理器,由 ProcessCompletedListener 统一路由;
* 新增"审批通过后处理"(如报销入账、采购入库、离职禁用账号)只需实现本接口并注册为 Spring Bean,
* 无需改动审批与引擎代码</p>
* 新增"审批通过后处理"(如报销入账、采购入库、离职禁用账号)只需实现本接口并注册为 Spring Bean</p>
*
* @author Ray.Hao
* @since 4.9.0

View File

@@ -5,6 +5,7 @@ import com.youlai.boot.module.form.model.vo.FormDataVO;
import com.youlai.boot.module.form.service.FormDataService;
import com.youlai.boot.module.workflow.handler.ProcessCompletedContext;
import com.youlai.boot.module.workflow.handler.ProcessCompletedHandler;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -19,6 +20,7 @@ import org.springframework.stereotype.Component;
* @since 4.9.0
*/
@Slf4j
@ConditionalOnWorkflowEnabled
@Component
@RequiredArgsConstructor
public class ExpenseProcessCompletedHandler implements ProcessCompletedHandler {

View File

@@ -5,6 +5,7 @@ import com.youlai.boot.module.form.model.vo.FormDataVO;
import com.youlai.boot.module.form.service.FormDataService;
import com.youlai.boot.module.workflow.handler.ProcessCompletedContext;
import com.youlai.boot.module.workflow.handler.ProcessCompletedHandler;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -19,6 +20,7 @@ import org.springframework.stereotype.Component;
* @since 4.9.0
*/
@Slf4j
@ConditionalOnWorkflowEnabled
@Component
@RequiredArgsConstructor
public class PurchaseProcessCompletedHandler implements ProcessCompletedHandler {

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.module.workflow.listener;
import com.youlai.boot.module.workflow.handler.ProcessCompletedContext;
import com.youlai.boot.module.workflow.handler.ProcessCompletedHandler;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
@@ -27,6 +28,7 @@ import java.util.stream.Collectors;
* @since 4.9.0
*/
@Slf4j
@ConditionalOnWorkflowEnabled
@Component
public class ProcessCompletedListener implements FlowableEventListener {

View File

@@ -1,6 +1,7 @@
package com.youlai.boot.module.workflow.runner;
import com.youlai.boot.module.workflow.service.WorkflowDemoService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
@@ -11,13 +12,14 @@ import org.springframework.stereotype.Component;
* 演示流程启动初始化器
*
* <p>委托 {@link WorkflowDemoService} 幂等初始化,重置能力由管理端接口提供。
* 由 flowable.demo.enabled 控制(默认开启),生产环境置 false 后本组件不注册,启动零开销</p>
* 由 workflow.demo.enabled 控制(默认关闭),置 true 后启动时初始化演示流程</p>
*
* @author Ray.Hao
* @since 4.9.0
*/
@ConditionalOnWorkflowEnabled
@Component
@ConditionalOnProperty(name = "flowable.demo.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "workflow.demo.enabled", havingValue = "true")
@RequiredArgsConstructor
public class WorkflowDemoInitializer implements ApplicationRunner {

View File

@@ -15,6 +15,7 @@ import com.youlai.boot.system.model.entity.UserRole;
import com.youlai.boot.system.service.RoleService;
import com.youlai.boot.system.service.UserService;
import com.youlai.boot.system.service.UserRoleService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.ProcessDefinition;
@@ -34,6 +35,7 @@ import java.util.stream.Collectors;
* @author Ray.Hao
* @since 4.9.0
*/
@ConditionalOnWorkflowEnabled
@Service
@RequiredArgsConstructor
public class WorkflowDefinitionServiceImpl implements WorkflowDefinitionService {

View File

@@ -5,8 +5,8 @@ import cn.hutool.json.JSONUtil;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.module.form.service.FormDataService;
import com.youlai.boot.module.workflow.service.WorkflowDemoService;
import com.youlai.boot.module.workflow.service.WorkflowDemoService;
import com.youlai.boot.module.workflow.util.BpmnModelHelper;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
@@ -29,12 +29,12 @@ import java.util.Set;
* <p>初始化按 key 幂等双写:部署流程定义(可发起)+ 创建模型草稿(流程模型页可见),两者各自查重互不依赖。
* 引擎表(ACT_*)不走 SQL 预置:流程定义跨三表且主键由引擎生成,直接写库绕过解析校验,重置同理依赖引擎级联删除</p>
*
* <p>重置为工作流数据全量恢复出厂:清空所有流程定义、模型、实例、历史(含用户自建)及关联表单提交数据后重建演示流程,
* 供演示环境定期复原,等价于业务脚本"DROP 重建"的数据效果而不动引擎表结构</p>
* <p>重置供演示环境定期复原,数据效果等价业务脚本"DROP 重建",不动引擎表结构</p>
*
* @author Ray.Hao
* @since 4.9.0
*/
@ConditionalOnWorkflowEnabled
@Service
@RequiredArgsConstructor
@Slf4j
@@ -141,7 +141,7 @@ public class WorkflowDemoServiceImpl implements WorkflowDemoService {
}
/**
* 幂等创建模型草稿(流程模型页数据源),editorSource 与部署 XML 同源,metaInfo 与服务层约定一致
* 幂等创建模型草稿(流程模型页数据源),editorSource 与部署 XML 同源
*/
private void createModelIfAbsent(DemoProcess process, String bpmnXml) {
long count = repositoryService.createModelQuery()
@@ -160,8 +160,6 @@ public class WorkflowDemoServiceImpl implements WorkflowDemoService {
/**
* 清理演示表单提交数据(流程 businessKey 指向 form_data,随流程一并复位)
*
* <p>formKey 到删除条件的解析封装在 form 模块(removeByFormKey 窄接口),本模块不感知表单实体</p>
*/
private void deleteFormData(String formKey) {
formDataService.removeByFormKey(formKey);

View File

@@ -18,6 +18,7 @@ import com.youlai.boot.module.workflow.service.WorkflowDefinitionService;
import com.youlai.boot.module.workflow.service.WorkflowInstanceService;
import com.youlai.boot.module.workflow.util.ApprovalHistoryHelper;
import com.youlai.boot.module.workflow.util.BpmnModelHelper;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.identity.Authentication;
@@ -47,6 +48,7 @@ import java.util.Set;
* @since 4.9.0
*/
@Slf4j
@ConditionalOnWorkflowEnabled
@Service
@RequiredArgsConstructor
public class WorkflowInstanceServiceImpl implements WorkflowInstanceService {
@@ -127,7 +129,7 @@ public class WorkflowInstanceServiceImpl implements WorkflowInstanceService {
InstancePageVO pageVO = new InstancePageVO();
pageVO.setId(instance.getId());
pageVO.setProcessDefinitionKey(instance.getProcessDefinitionKey());
// 实例名优先(发起时可自定义标题区分批次),旧实例 NAME_ 为空则回退流程定义名
// 实例名优先,NAME_ 为空回退流程定义名
pageVO.setProcessName(StrUtil.isBlank(instance.getName())
? instance.getProcessDefinitionName() : instance.getName());
pageVO.setBusinessKey(instance.getBusinessKey());
@@ -174,7 +176,7 @@ public class WorkflowInstanceServiceImpl implements WorkflowInstanceService {
HistoricProcessInstance instance = getHistoricInstance(instanceId);
InstanceDetailVO detailVO = new InstanceDetailVO();
detailVO.setId(instance.getId());
// 实例名优先(与列表保持一致),旧实例 NAME_ 为空则回退流程定义名
// 实例名优先,NAME_ 为空回退流程定义名
detailVO.setProcessName(StrUtil.isBlank(instance.getName())
? instance.getProcessDefinitionName() : instance.getName());
detailVO.setStartUser(instance.getStartUserId());

View File

@@ -16,6 +16,7 @@ import com.youlai.boot.module.workflow.service.WorkflowModelService;
import com.youlai.boot.module.workflow.util.BpmnModelHelper;
import com.youlai.boot.system.model.entity.Role;
import com.youlai.boot.system.service.RoleService;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
@@ -39,6 +40,7 @@ import java.util.stream.Collectors;
* @author Ray.Hao
* @since 4.9.0
*/
@ConditionalOnWorkflowEnabled
@Service
@RequiredArgsConstructor
public class WorkflowModelServiceImpl implements WorkflowModelService {

View File

@@ -18,6 +18,7 @@ import com.youlai.boot.module.workflow.service.WorkflowDefinitionService;
import com.youlai.boot.module.workflow.service.WorkflowTaskService;
import com.youlai.boot.module.workflow.util.ApprovalHistoryHelper;
import com.youlai.boot.module.workflow.util.BpmnModelHelper;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.identity.Authentication;
@@ -52,6 +53,7 @@ import java.util.stream.Collectors;
* @since 4.9.0
*/
@Slf4j
@ConditionalOnWorkflowEnabled
@Service
@RequiredArgsConstructor
public class WorkflowTaskServiceImpl implements WorkflowTaskService {
@@ -94,7 +96,6 @@ public class WorkflowTaskServiceImpl implements WorkflowTaskService {
taskVO.setName(task.getName());
taskVO.setProcessInstanceId(task.getProcessInstanceId());
ProcessInstance instance = instances.get(task.getProcessInstanceId());
// 实例名优先(发起时可自定义标题区分批次),旧实例 NAME_ 为空则回退流程定义名
taskVO.setProcessName(resolveProcessName(
instance == null ? null : instance.getName(),
processNames.get(task.getProcessDefinitionId())));
@@ -177,7 +178,6 @@ public class WorkflowTaskServiceImpl implements WorkflowTaskService {
detailVO.setTaskName(task.getName());
detailVO.setTaskDefinitionKey(task.getTaskDefinitionKey());
detailVO.setProcessInstanceId(task.getProcessInstanceId());
// 实例名优先(与列表口径一致),旧实例 NAME_ 为空则回退流程定义名
detailVO.setProcessName(resolveProcessName(instance.getName(), instance.getProcessDefinitionName()));
detailVO.setTaskFormKey(bpmnModelHelper.getUserTaskFormKey(
task.getProcessDefinitionId(), task.getTaskDefinitionKey()));
@@ -227,7 +227,6 @@ public class WorkflowTaskServiceImpl implements WorkflowTaskService {
public void completeTask(String taskId, CompleteTaskForm formData) {
Task task = getCurrentUserTask(taskId);
this.addComment(task, formData.getComment());
// 或签(候选组)任务办理即认领,历史表写入办理人,已办列表可追溯
this.claimIfCandidate(task);
taskService.complete(taskId, formData.getVariables() == null ? Map.of() : formData.getVariables());
}

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.module.workflow.util;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.module.workflow.model.vo.ApprovalHistoryVO;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.HistoryService;
import org.flowable.engine.TaskService;
@@ -19,6 +20,7 @@ import java.util.stream.Collectors;
* @author Ray.Hao
* @since 4.9.0
*/
@ConditionalOnWorkflowEnabled
@Component
@RequiredArgsConstructor
public class ApprovalHistoryHelper {

View File

@@ -3,6 +3,7 @@ package com.youlai.boot.module.workflow.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.module.workflow.config.ConditionalOnWorkflowEnabled;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
@@ -22,11 +23,12 @@ import java.util.Set;
import java.util.stream.Collectors;
/**
* BPMN 模型读取辅助类(formKey 与 XML 解析统一收口,避免散落各 Service)
* BPMN 模型读取辅助(formKey 与 XML 解析)
*
* @author Ray.Hao
* @since 4.9.0
*/
@ConditionalOnWorkflowEnabled
@Component
@RequiredArgsConstructor
public class BpmnModelHelper {
@@ -34,7 +36,7 @@ public class BpmnModelHelper {
private final RepositoryService repositoryService;
/**
* 用户任务节点的 BPMN 原始信息(办理人配置由上层翻译为角色/用户展示)
* 用户任务节点的 BPMN 原始信息(办理人为未翻译的原始配置)
*
* @param nodeId 节点ID
* @param nodeName 节点名称

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.support.file.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
@@ -18,6 +19,7 @@ import java.util.stream.Collectors;
* @since 4.8.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "storage")
public class StorageProperties {

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.support.mail;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 邮件配置属性
@@ -9,6 +10,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Ray
* @since 2024/8/17
*/
@Component
@ConfigurationProperties(prefix = "spring.mail")
@Data
public class MailProperties {

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.support.sms;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@@ -12,6 +13,7 @@ import java.util.Map;
* @since 2024/8/17
*/
@Data
@Component
@ConfigurationProperties(prefix = "sms.aliyun")
public class AliyunSmsProperties {

View File

@@ -1,2 +0,0 @@
org.springframework.boot.EnvironmentPostProcessor=\
com.youlai.boot.module.config.WorkflowEnvironmentPostProcessor

View File

@@ -237,12 +237,21 @@ wx:
appid: Your_AppId
secret: Your_AppSecret
# 工作流
flowable:
# 工作流总开关,未配置视为关闭
# 工作流(默认关闭,开启见 README「可选扩展」)
workflow:
# 工作流模块开关,未配置视为关闭
enabled: false
# 引擎表初始化策略,true 启动时检查并自动建/更新 ACT_* 表;可选 true/false/create/create-drop
database-schema-update: true
demo:
# 演示流程自动部署开关
enabled: true
flowable:
# 引擎开关跟随 workflow.enabled
process:
enabled: ${workflow.enabled:false}
idm:
enabled: ${workflow.enabled:false}
eventregistry:
enabled: ${workflow.enabled:false}
# 引擎表初始化策略,true 启动时检查并自动建/更新 ACT_* 表;可选 true/false/create/create-drop
database-schema-update: true

View File

@@ -236,11 +236,20 @@ wx:
secret: Your_AppSecret
# 工作流(默认关闭,开启见 README「可选扩展」)
flowable:
# 工作流总开关,未配置视为关闭
workflow:
# 工作流模块开关,未配置视为关闭
enabled: false
# 引擎表初始化策略,false 要求 ACT_* 表已存在
database-schema-update: false
demo:
# 演示流程自动部署开关
enabled: false
flowable:
# 引擎开关跟随 workflow.enabled
process:
enabled: ${workflow.enabled:false}
idm:
enabled: ${workflow.enabled:false}
eventregistry:
enabled: ${workflow.enabled:false}
# 引擎表初始化策略,false 要求 ACT_* 表已存在
database-schema-update: false