refactor(workflow): 工作流模块改由 ConditionalOnWorkflowEnabled 条件装配
- 移除启动类手工 ComponentScan 排除与 WorkflowModuleConfig、 WorkflowEnvironmentPostProcessor、spring.factories 自动装配配置 - 新增 @ConditionalOnWorkflowEnabled 注解,按 flowable.enabled 决定工作流模块是否生效 - 启动类回归 @SpringBootApplication,各配置属性类改用 @Component 显式注册
This commit is contained in:
30
README.md
30
README.md
@@ -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> </td>
|
||||
<td align="center">
|
||||
<img src="./docs/images/qrcode/wechat-mp.jpg" height="180" alt="小程序「有来技术」"><br>
|
||||
<sub>小程序「有来技术」</sub>
|
||||
</td>
|
||||
<td> </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>
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -70,9 +70,6 @@ public interface FormDataService {
|
||||
/**
|
||||
* 按表单标识删除全部提交数据(演示数据重置用)
|
||||
*
|
||||
* <p>跨模块窄接口:formKey 到 formId 的解析与删除条件封装在 form 模块内部,
|
||||
* 调用方(workflow)不感知表单实体与查询条件</p>
|
||||
*
|
||||
* @param formKey 表单唯一标识
|
||||
*/
|
||||
void removeByFormKey(String formKey);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import java.util.List;
|
||||
* @author Ray.Hao
|
||||
* @since 4.9.0
|
||||
*/
|
||||
@ConditionalOnWorkflowEnabled
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class WorkflowEngineConfig {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 节点名称
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.boot.EnvironmentPostProcessor=\
|
||||
com.youlai.boot.module.config.WorkflowEnvironmentPostProcessor
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user