定制flowable实现服务编排

文章目录

  • 准备springboot项目
  • 覆盖默认配置
  • 给BPMN组件注入自定义逻辑
  • 增加自定义接口

本文基于flowable版本6.6.0

准备springboot项目

pom.xml文件引入flowable的流程引擎、管理UI

 	<dependencies>
		<dependency>
			<groupId>org.flowablegroupId>
			<artifactId>flowable-spring-boot-starterartifactId>
			<version>${flowable.version}version>
		dependency>
		<dependency>
			<groupId>org.flowablegroupId>
			<artifactId>flowable-spring-boot-starter-ui-taskartifactId>
			<version>${flowable.version}version>
		dependency>
		<dependency>
			<groupId>org.flowablegroupId>
			<artifactId>flowable-spring-boot-starter-ui-adminartifactId>
			<version>${flowable.version}version>
		dependency>
		<dependency>
			<groupId>org.flowablegroupId>
			<artifactId>flowable-spring-boot-starter-ui-modelerartifactId>
			<version>${flowable.version}version>
		dependency>
		<dependency>
			<groupId>org.flowablegroupId>
			<artifactId>flowable-spring-boot-starter-ui-idmartifactId>
			<version>${flowable.version}version>
		dependency>
		<dependency>
			<groupId>org.flowablegroupId>
			<artifactId>flowable-secure-javascriptartifactId>
			<version>${flowable.version}version>
		dependency>

		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
			<version>6.0.6version>
		dependency>
	dependencies>

覆盖默认配置

FlowableDefaultPropertiesEnvironmentPostProcessor类会加载本地的flowable-default.properties文件,
可在里面覆盖springboot和flowable的默认配置项。
按如下配置,流程模板编辑器的访问地址是http://127.0.0.1:8080/flowable/modeler,账户是admin/wzp

server.port=8080
server.tomcat.basedir=/apps/svr/flowable
server.servlet.context-path=/flowable
spring.application.name=flowable-engine

# Flowable Admin Properties
# Default REST endpoint configs
#
flowable.admin.app.server-config.process.password=wzp
flowable.admin.app.server-config.cmmn.password=wzp
flowable.admin.app.server-config.app.password=wzp
flowable.admin.app.server-config.dmn.password=wzp
flowable.admin.app.server-config.form.password=wzp
flowable.admin.app.server-config.content.password=wzp

# Flowable IDM Properties
flowable.idm.app.admin.password=wzp
flowable.idm.enabled=true
flowable.idm.password-encoder=spring_delegating_bcrypt

#
# DATABASE
#
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://10.0.0.1:3306/flowable?serverTimezone=CTT&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=flowable
spring.datasource.password=wzp

#
# Connection pool (see https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby)
#
spring.datasource.hikari.poolName=${spring.application.name}
# 8 minutes
spring.datasource.hikari.maxLifetime=480000
# 5 minutes
spring.datasource.hikari.idleTimeout=300000
spring.datasource.hikari.validationTimeout=3000
spring.datasource.hikari.connectionTimeout=3000
spring.datasource.hikari.minimumIdle=2
spring.datasource.hikari.maximumPoolSize=200
spring.datasource.hikari.connection-test-query=select 1

#
# Default Task Executor (will be used for @Async)
#
spring.task.execution.pool.core-size=20
spring.task.execution.pool.max-size=20
spring.task.execution.pool.queue-capacity=200
spring.task.execution.thread-name-prefix=flowable-task-Executor-

给BPMN组件注入自定义逻辑

BaseEngineConfigurationWithConfigurers会自动利用所有EngineConfigurationConfigurer类型的bean,对引擎的配置进行补充

	/**
	 * 给ScriptTask提供打印日志的工具bean
	 */
	@Bean("log")
	public Logger scriptTaskLog() {
		return LoggerFactory.getLogger("flowable.ScriptTask");
	}

	@Bean
	public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> processEngineConfigurer() {
		return new EngineConfigurationConfigurer<SpringProcessEngineConfiguration>() {
			@Override
			public void configure(SpringProcessEngineConfiguration engineConfiguration) {
				final int acquireJobs = 20;
				/**
				 * The number of async jobs that are acquired during one query (before a job is
				 * executed, an acquirement thread fetches jobs from the database and puts them
				 * on the queue).
				 *
				 * Consider using the global acquire lock when there are too many nodes
				 * competing for the same resources (and thus too many optimistic locking
				 * exceptions).
				 */
				engineConfiguration.setAsyncExecutorMaxAsyncJobsDuePerAcquisition(acquireJobs);
				engineConfiguration.setAsyncExecutorMaxTimerJobsPerAcquisition(acquireJobs);
				/**
				 * The size of the queue on which jobs to be executed are placed, before they
				 * are actually executed. Default value = 2048. (This property is only
				 * applicable when using the {@link DefaultAsyncJobExecutor}).
				 */
				engineConfiguration.setAsyncExecutorThreadPoolQueueSize(acquireJobs * 10);
				engineConfiguration.setAsyncExecutorCorePoolSize(acquireJobs);
				engineConfiguration.setAsyncExecutorMaxPoolSize(acquireJobs);
				/**
				 * The amount of time (in milliseconds) an async job is locked when acquired by
				 * the async executor. During this period of time, no other async executor will
				 * try to acquire and lock this job.
				 * 

* Default value = 60 minutes; */ engineConfiguration.setAsyncExecutorAsyncJobLockTimeInMillis(acquireJobs * 10 * 1000); engineConfiguration.setAsyncExecutorTimerLockTimeInMillis(acquireJobs * 10 * 1000); // 打印所有活动 List<FlowableEventListener> listeners = engineConfiguration.getEventListeners(); if (listeners == null) { listeners = new ArrayList<>(); engineConfiguration.setEventListeners(listeners); } listeners.add(new CustomFlowableEngineEventListener()); // 补充流程任务的默认逻辑,比如Http任务返回4XX时任务执行失败 List<BpmnParseHandler> parsers = engineConfiguration.getPreBpmnParseHandlers(); if (parsers == null) { parsers = new ArrayList<>(); engineConfiguration.setPreBpmnParseHandlers(parsers); } parsers.add(new CustomHttpTaskBpmnParseHandler()); // 增加javascript脚本的安全限制 SecureJavascriptConfigurator configurator = new SecureJavascriptConfigurator().setMaxStackDepth(10) .setMaxScriptExecutionTime(3000L).setMaxMemoryUsed(3145728L).setEnableAccessToBeans(true) .setNrOfInstructionsBeforeStateCheckCallback(10); engineConfiguration.addConfigurator(configurator); } }; } public static class CustomFlowableEngineEventListener extends AbstractFlowableEngineEventListener { @Override protected void activityStarted(FlowableActivityEvent event) { logger.info(event.getActivityType() + ":" + event.getActivityId() + " " + event.getProcessInstanceId() + " started"); } @Override protected void activityCompleted(FlowableActivityEvent event) { logger.info(event.getActivityType() + ":" + event.getActivityId() + " " + event.getProcessInstanceId() + " completed"); } @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { logger.info(event.getActivityType() + ":" + event.getActivityId() + " " + event.getProcessInstanceId() + " cancelled"); } } final static String HTTP_TASK_PROPERTIES_HANDLE_STATUS_CODES = "handleStatusCodes"; final static String HTTP_TASK_PROPERTIES_FAIL_STATUS_CODES = "failStatusCodes"; // 处理HttpTask,转化为异步执行 public static class CustomHttpTaskBpmnParseHandler extends HttpServiceTaskParseHandler { @Override protected void executeParse(BpmnParse bpmnParse, ServiceTask serviceTask) { if (serviceTask.isForCompensation()) { // 补偿任务不能异步执行 serviceTask.setAsynchronous(false); } else { serviceTask.setAsynchronous(true); serviceTask.setExclusive(true); } String retry = serviceTask.getFailedJobRetryTimeCycleValue(); if (StringUtils.isBlank(retry)) { // 不进行重试(默认是出错后再执行两次) serviceTask.setFailedJobRetryTimeCycleValue("R1/PT1M"); } // 设置异常statusCode,默认只有网络连接失败才识别为任务执行出错 List<FieldExtension> fields = serviceTask.getFieldExtensions(); boolean statusCodesSet = false; for (FieldExtension field : fields) { if (field.getFieldName().equals(HTTP_TASK_PROPERTIES_HANDLE_STATUS_CODES) || field.getFieldName().equals(HTTP_TASK_PROPERTIES_FAIL_STATUS_CODES)) { statusCodesSet = true; } } if (!statusCodesSet) { FieldExtension field = new FieldExtension(); field.setFieldName(HTTP_TASK_PROPERTIES_HANDLE_STATUS_CODES); field.setStringValue("4XX,5XX"); fields.add(field); } super.executeParse(bpmnParse, serviceTask); } }

增加自定义接口

组合HttpTask和ScriptTask即可实现服务调用的在线编排;
Task会执行失败,所以需要定时查询死信任务,进行手动恢复,或修复流程模板并迁移流程实例到新的模板;
下述自定义controller提供了死信查询接口:

@Controller
@RequestMapping("/flowable")
public class FlowableController {
	protected final Logger logger = LoggerFactory.getLogger(getClass());
	@Autowired
	private TaskService taskService;
	@Autowired
	private RepositoryService repositoryService;
	@Autowired
	private ManagementService managementService;
	@Autowired
	private RuntimeService runtimeService;
	@Autowired
	private HistoryService historyService;

	@PostMapping(value = "/deploy", produces = "text/plain;charset=utf-8")
	@ResponseBody
	public String deploy(HttpServletRequest request, Locale locale, TimeZone tz) throws Exception {
		byte[] bytes = StreamUtils.copyToByteArray(request.getInputStream());
		logger.info(new String(bytes, StandardCharsets.UTF_8));
		Deployment deployment = repositoryService.createDeployment()
				.addBytes("ManualDeploy.bpmn20.xml", bytes).deploy();

		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
				.deploymentId(deployment.getId()).singleResult();
		logger.info("deployed process definition : " + processDefinition.getName());

		return "OK";
	}

	@GetMapping(value = "/dead", produces = "text/plain;charset=utf-8")
	@ResponseBody
	public String dead() throws Exception {
		List<Job> jobs = managementService.createDeadLetterJobQuery().list();
		logger.info("找到" + jobs.size() + "个死信任务");
		for (Job job : jobs) {
			logger.info(job.getId() + "错误原因: " + managementService.getDeadLetterJobExceptionStacktrace(job.getId()));
			logger.info(job.getId() + "属于流程" + job.getProcessDefinitionId());
//			managementService.moveDeadLetterJobToExecutableJob(job.getId(), 1);
		}
		return String.valueOf(jobs.size());
	}
}

PS:在http://127.0.0.1:8080/flowable/admin/#/process-definitions进行流程实例的迁移操作,迁移后会自动执行死信节点

你可能感兴趣的:(微服务,java,spring,boot,flowable)