一、活动状态机配置
package com.zhibi.xiuba.configurer;
import com.zhibi.xiuba.statemachine.EventsOnTransition;
import com.zhibi.xiuba.statemachine.TaskEvent;
import com.zhibi.xiuba.task.spi.domain.Task;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.processor.StateMachineMethodInvokerHelper;
import org.springframework.statemachine.processor.StateMachineRuntime;
import org.springframework.statemachine.transition.Transition;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import static com.zhibi.xiuba.task.spi.domain.Task.TaskStatusEnum.*;
/**
* Created by QINHE on 2017/8/14.
*/
@Configuration
@EnableStateMachineFactory(name = "taskStateMachine", contextEvents = false)
public class TaskStateMachineConfig extends EnumStateMachineConfigurerAdapter<Task.TaskStatusEnum, TaskEvent> implements ApplicationContextAware {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Map<TaskEvent, StateMachineMethodInvokerHelper> actionMap = new HashMap<>();
@Override
public void configure(StateMachineConfigurationConfigurer<Task.TaskStatusEnum, TaskEvent> config) throws Exception {
super.configure(config);
config.withConfiguration().autoStartup(false).listener(new StateMachineListenerAdapter<Task.TaskStatusEnum, TaskEvent>() {
@Override
public void transition(Transition transition) {
logger.info(ToStringBuilder.reflectionToString(transition, ToStringStyle.MULTI_LINE_STYLE));
}
@Override
public void stateMachineError(StateMachine stateMachine, Exception exception) {
logger.error(exception.getLocalizedMessage(), exception);
}
});
}
@Override
public void configure(StateMachineStateConfigurer<Task.TaskStatusEnum, TaskEvent> states) throws Exception {
states.withStates().initial(WAITING_PAY).end(FINISHED)
.end(CLOSED).end(DELETED).states(EnumSet.allOf(Task.TaskStatusEnum.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<Task.TaskStatusEnum, TaskEvent> transitions) throws Exception {
transitions
.withExternal().source(WAITING_PAY).target(DELETED).event(TaskEvent.PAY_WITH_DELETE).action(buildAction()).and()
.withExternal().source(WAITING_PAY).target(WAITING_PAY).event(TaskEvent.NEED_PAY_MORE).action(buildAction()).and()
.withExternal().source(WAITING_PAY).target(WAITING_AUDIT).event(TaskEvent.SUBMIT_AUDIT).action(buildAction()).and()
.withExternal().source(WAITING_MODIFY).target(WAITING_PAY).event(TaskEvent.MODIFY_WAITING_PAY).action(buildAction()).and()
.withExternal().source(WAITING_MODIFY).target(WAITING_AUDIT).event(TaskEvent.MODIFY_SUBMIT_AUDIT).action(buildAction()).and()
.withExternal().source(WAITING_MODIFY).target(DELETED).event(TaskEvent.MODIFY_WITH_DELETE).action(buildAction()).and()
.withExternal().source(WAITING_AUDIT).target(WAITING_MODIFY).event(TaskEvent.AUDIT_NOT_PASS).action(buildAction()).and()
.withExternal().source(WAITING_AUDIT).target(UNDER_WAY).event(TaskEvent.AUDIT_PASS).action(buildAction()).and()
.withExternal().source(UNDER_WAY).target(FINISHED).event(TaskEvent.TASK_FINISH).action(buildAction());
//TODO:任务关闭状态这里没配置
}
@Bean
public Action<Task.TaskStatusEnum, TaskEvent> buildAction() {
return new Action<Task.TaskStatusEnum, TaskEvent>() {
@Override
public void execute(StateContext<Task.TaskStatusEnum, TaskEvent> context) {
TaskEvent event = context.getEvent();
StateMachineMethodInvokerHelper helper = actionMap.get(event);
if (helper == null) {
throw new RuntimeException("not found invoke execute!!!!event is" + event.name());
}
StateMachineRuntime runtime = new StateMachineRuntime() {
@Override
public StateContext getStateContext() {
return context;
}
};
try {
helper.process(runtime);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> map = applicationContext.getBeansWithAnnotation(WithStateMachine.class);
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object bean = entry.getValue();
Class<?> beanClass = getBeanClass(bean);
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
EventsOnTransition a = AnnotationUtils.findAnnotation(method, EventsOnTransition.class);
if (a == null) {
return;
}
TaskEvent[] taskEvent = a.events();
StateMachineMethodInvokerHelper helper = new StateMachineMethodInvokerHelper(bean, method);
for (TaskEvent event : taskEvent) {
actionMap.put(event, helper);
}
}
});
}
}
/**
* Gets the bean class. Will check if bean is a proxy and
* find a class from there as target class, otherwise
* we just get bean class.
*
* @param bean the bean
* @return the bean class
*/
private Class<?> getBeanClass(Object bean) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
return (targetClass != null) ? targetClass : bean.getClass();
}
}
状态转变触发(trigger)->状态机启动并加载配置->guard->action->onTransition->变成新状态
1、项目中一般都需要打印日志,所有实体的toString()方法都是用简单的”+”,因为每”+” 一个就会 new 一个 String 对象,这样如果系统内存小的话会暴内存。使用ToStringBuilder就可以避免暴内存这种问题。
ToStringBuilder的reflectionToString方法:
logger.info(“请求数据:”+ToStringBuilder.reflectionToString(req));
2、本项目没有使用原生的statemachine的listen注解@WithStateMachine 和@OnTransition(target = “UNPAID”)开发,而是自己做了状态迁移listener,该注解不使用OnTransition避免业务异常无法正常被状态机接收,状态还是会继续改变
3、configure(StateMachineConfigurationConfigurer
package com.zhibi.xiuba.statemachine;
import com.zhibi.xiuba.exception.StateEventException;
import com.zhibi.xiuba.model.ControllerResult;
import com.zhibi.xiuba.task.spi.domain.ShowkerTask;
import com.zhibi.xiuba.task.spi.domain.Task;
import com.zhibi.xiuba.task.spi.enums.OperatorEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 状态机事件发送服务
* <p>
* 请使用本服务发送事件已保证逻辑正确性
* Created by RUAN on 2017/8/16.
*/
@Service
public class StateMachineInvokeService implements IStateMachineInvokeService {
@Autowired
private StateMachineFactory<Task.TaskStatusEnum, TaskEvent> taskStateMachine;
@Autowired
private StateMachineFactory<ShowkerTask.StatusEnum, ShowkerTaskEvent> showKerTaskStateMachine;
/**
* 发送活动事件
*
* @param event活动事件
* @param originalTask 原始活动对象,未变更前数据
* @param params 扩展参数
*/
@Override
public void sendTaskEvent(TaskEvent event, Task originalTask, OperatorEnum operator, Map<String, Object> params) {
if (params == null) {
params = new HashMap<>();
}
params.put(IStateMachineConstants.OPERATOR, operator.getCode());
sendTaskEvent(event, originalTask, params);
}
/**
* 发送活动事件
*
* @param event活动事件
* @param originalTask 原始活动对象,未变更前数据
* @param params 扩展参数
*/
@Override
public void sendTaskEvent(TaskEvent event, Task originalTask, Map<String, Object> params) {
StateMachine<Task.TaskStatusEnum, TaskEvent> stateMachine = taskStateMachine.getStateMachine("TaskStateMachine:" + originalTask.getId());
String taskStatus = originalTask.getTaskStatus();
Task.TaskStatusEnum statusEnum = Task.TaskStatusEnum.getEnumByCode(taskStatus);
if (statusEnum == null) {
throw new RuntimeException("not found task status:" + taskStatus);
}
stateMachine.stop();
//启动之前恢复状态机的状态
List<StateMachineAccess<Task.TaskStatusEnum, TaskEvent>> withAllRegions = stateMachine.getStateMachineAccessor().withAllRegions();
for (StateMachineAccess<Task.TaskStatusEnum, TaskEvent> a : withAllRegions) {
a.resetStateMachine(new DefaultStateMachineContext<>(statusEnum, null, null, null, null, stateMachine.getId()));
}
stateMachine.start();
stateMachine.sendEvent(MessageBuilder.withPayload(event).setHeader(IStateMachineConstants.TASK_OBJECT, originalTask).copyHeaders(params).build());
}
/**
* 发送拿手任务事件
*
* @param event拿手任务事件
* @param originalTask 原始拿手任务对象,未变更前数据
* @param params 扩展参数
*/
@Override
public void sendShowKerTaskEvent(ShowkerTaskEvent event, ShowkerTask originalTask, OperatorEnum operatorEnum, Map<String, Object> params) {
if (params == null) {
params = new HashMap<>();
}
params.put(IStateMachineConstants.OPERATOR, operatorEnum.getCode());
sendShowKerTaskEvent(event, originalTask, params);
}
/**
* 发送拿手任务事件
*
* @param event拿手任务事件
* @param originalTask 原始拿手任务对象,未变更前数据
* @param params 扩展参数
*/
@Override
public void sendShowKerTaskEvent(ShowkerTaskEvent event, ShowkerTask originalTask, Map<String, Object> params) {
StateMachine<ShowkerTask.StatusEnum, ShowkerTaskEvent> stateMachine = showKerTaskStateMachine.getStateMachine("ShowkerTaskStateMachine:" + originalTask.getId());
String taskStatus = originalTask.getStatus();
ShowkerTask.StatusEnum statusEnum = ShowkerTask.StatusEnum.getEnumByCode(taskStatus);
if (statusEnum == null) {
throw new RuntimeException("not found showker task status:" + taskStatus);
}
stateMachine.stop();
//启动之前恢复状态机的状态
List<StateMachineAccess<ShowkerTask.StatusEnum, ShowkerTaskEvent>> withAllRegions = stateMachine.getStateMachineAccessor().withAllRegions();
for (StateMachineAccess<ShowkerTask.StatusEnum, ShowkerTaskEvent> a : withAllRegions) {
a.resetStateMachine(new DefaultStateMachineContext<>(statusEnum, null, null, null, null, stateMachine.getId()));
}
stateMachine.start();
if (!stateMachine.sendEvent(MessageBuilder.withPayload(event).setHeader(IStateMachineConstants.TASK_OBJECT, originalTask).copyHeaders(params).build())) {
throw new StateEventException(ControllerResult.build(false, "stateMachine_not_executed", "状态机未调用"));
}
}
}
三、状态迁移处理器
package com.zhibi.xiuba.statemachine;
import com.zhibi.xiuba.job.IJobType;
import com.zhibi.xiuba.service.IJobService;
import com.zhibi.xiuba.service.ITaskService;
import com.zhibi.xiuba.task.spi.domain.Task;
import com.zhibi.xiuba.task.spi.domain.TaskOpLog;
import com.zhibi.xiuba.task.spi.enums.OperatorEnum;
import com.zhibi.xiuba.utils.StringUtils;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.annotation.EventHeaders;
import org.springframework.statemachine.annotation.WithStateMachine;
import java.util.Calendar;
import java.util.Map;
/**
* 活动状态迁移处理器
* Created by RUAN on 2017/8/15.
*
* @see com.zhibi.xiuba.configurer.TaskStateMachineConfig
*/
@WithStateMachine(name = "taskStateMachine")
public class TaskTransitionHandler {
private final ITaskService taskService;
private final IJobService jobService;
public TaskTransitionHandler(ITaskService taskService,
IJobService jobService) {
this.taskService = taskService;
this.jobService = jobService;
}
/**
* 提交审核
*/
@SuppressWarnings("Duplicates")
@EventsOnTransition(events = {TaskEvent.SUBMIT_AUDIT})
public void submitAudit(@EventHeaders Map<String, Object> headers,
StateContext<Task.TaskStatusEnum, TaskEvent> context) {
try {
OperatorEnum operator = null;
if (StringUtils.isNotBlank((String) context.getMessageHeaders().get(IStateMachineConstants.OPERATOR))) {
//传参中带有操作人的
operator = OperatorEnum.getEnumByCode((String) context.getMessageHeaders().get(IStateMachineConstants.OPERATOR));
}
Task task = (Task) headers.get(IStateMachineConstants.TASK_OBJECT);
Long fee = (Long) headers.get("fee");
if (task.getMarginPaid() == null) {
task.setMarginPaid(0L);
}
TaskOpLog.TypeEnum typeEnum;
if (task.getMarginPaid() <= 0) {
typeEnum = TaskOpLog.TypeEnum.FEE_PAY;
} else {
typeEnum = TaskOpLog.TypeEnum.FEE_REPAY;
}
if (task.getPromotionExpensesPaid() == null) {
task.setPromotionExpensesPaid(0L);
}
if (task.getMarginSurplus() == null) {
task.setMarginSurplus(0L);
}
task.setMarginPaid(fee - (task.getPromotionExpensesNeed() - task.getPromotionExpensesPaid()) + task.getMarginPaid());
task.setMarginSurplus(task.getMarginPaid() - task.getMarginSurplus());
task.setPromotionExpensesPaid(task.getPromotionExpensesNeed());
task.setTaskStatus(context.getTarget().getId().getCode());
taskService.save(task);
taskService.createLog(task.getId(), typeEnum, operator);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
* 待补充保证金
*/
@SuppressWarnings("Duplicates")
@EventsOnTransition(events = {TaskEvent.NEED_PAY_MORE})
public void needPayMore(@EventHeaders Map<String, Object> headers,
StateContext<Task.TaskStatusEnum, TaskEvent> context) {
try {
OperatorEnum operator = null;
if (StringUtils.isNotBlank((String) context.getMessageHeaders().get(IStateMachineConstants.OPERATOR))) {
//传参中带有操作人的
operator = OperatorEnum.getEnumByCode((String) context.getMessageHeaders().get(IStateMachineConstants.OPERATOR));
}
Task task = (Task) headers.get(IStateMachineConstants.TASK_OBJECT);
Long fee = (Long) headers.get("fee");
if (task.getMarginPaid() == null) {
task.setMarginPaid(0L);
}
TaskOpLog.TypeEnum typeEnum;
if (task.getMarginPaid() <= 0) {
typeEnum = TaskOpLog.TypeEnum.FEE_PAY;
} else {
typeEnum = TaskOpLog.TypeEnum.FEE_REPAY;
}
if (task.getPromotionExpensesPaid() == null) {
task.setPromotionExpensesPaid(0L);
}
if (task.getMarginSurplus() == null) {
task.setMarginSurplus(0L);
}
if (fee <= (task.getPromotionExpensesNeed() - task.getPromotionExpensesPaid())) {
task.setPromotionExpensesPaid(fee + task.getPromotionExpensesPaid());
} else {
task.setMarginPaid(fee - (task.getPromotionExpensesNeed() - task.getPromotionExpensesPaid()) + task.getMarginPaid());
task.setMarginSurplus(task.getMarginPaid() - task.getMarginSurplus());
task.setPromotionExpensesPaid(task.getPromotionExpensesNeed());
}
task.setTaskStatus(context.getTarget().getId().getCode());
taskService.save(task);
taskService.createLog(task.getId(), typeEnum, operator);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
* 任务完成
*/
@EventsOnTransition(events = TaskEvent.TASK_FINISH)
public void taskFinish(@EventHeaders Map<String, Object> headers,
StateContext<Task.TaskStatusEnum, TaskEvent> context) {
try {
OperatorEnum operator = null;
if (StringUtils.isNotBlank((String) context.getMessageHeaders().get(IStateMachineConstants.OPERATOR))) {
//传参中带有操作人的
operator = OperatorEnum.getEnumByCode((String) context.getMessageHeaders().get(IStateMachineConstants.OPERATOR));
}
Task task = (Task) headers.get(IStateMachineConstants.TASK_OBJECT);
task.setTaskStatus(context.getTarget().getId().getCode());
task.setSettlementStatus(Task.SettlementStatusEnum.WAITING_SETTLEMENT.getCode());
Calendar calendar = Calendar.getInstance();
task.setFinishTime(calendar.getTime());
taskService.save(task);
taskService.createLog(task.getId(), TaskOpLog.TypeEnum.FINISH, operator);
taskService.moveToEsAndIndexCacheHistory(task);
jobService.submitTaskJob(task.getId(), IJobType.TASK_AUTO_AUDIT_SHOWKER, 48);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}