创建的一个user或group,需要不同人员去approve,user 或group状态就会发生变化。这个很适合使用spring-statemachine来做。以建立group来说明如下
建立一个group,状态是pending Approved
approve这个group,状态变成partial Approved
approve这个group,如果建立的group级别是高级,状态变成Pending Admin Approve,需要公司人员(Admin级别)去approve. 如果group级别不是高级,状态直接变成Approved
公司人员(Admin级别) approve这个group,状态变成Pending Admin Approve Confirm
公司人员(Admin级别) approve这个group,状态变成Approved
第4步,如果公司人员(Admin级别) reject这个group,状态变成Pending Admin Reject Confirm
公司人员(Admin级别) approve这个group,状态变成Rejected
第7步,如果公司人员(Admin级别) reject这个group,状态变成Pending Admin Approve,重新走流程
以下为代码参考了官方sample-persist并做了更改
CREATE TABLE `pagroup` (
`groupId` int(11) DEFAULT NULL,
`groupName` varchar(256) DEFAULT NULL,
`status` int(11) DEFAULT NULL,
`isAdvance` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8
对应的Pojo
public class Group {
private int groupId;
private String groupName;
private int state;
private boolean isAdvance;
//getter and setter...
}
spring statemachine的Configuration,java代码如下,实际上就是用java代码描述1的那个图。刚开始写有点懵,实际写下就很简单了,就是source到target的状态变化需要用什么event(事件)去trigger。需要说明的是withChoice(),这个就是说当状态从pending approved改到choice时,statemahine引擎需要知道如何去选下个状态是Approved还是pending Admin approve,这里逻辑如果group 是isadvance就是pending Admin approve否则就是approved
@Configuration
@EnableStateMachine
public class StateMachineConfig extends StateMachineConfigurerAdapter{
@Override
public void configure(StateMachineStateConfigurer states) throws Exception {
states.withStates()
.initial(Status.PENDING_APPROVAL)
.choice(Status.CHOICE)
.states(EnumSet.allOf(Status.class));
}
@Override
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
transitions
.withExternal()
.source(Status.PENDING_APPROVAL).target(Status.PARTIALLY_APPROVED).event(ActionType.APPROVE)
.and()
.withExternal()
.source(Status.PARTIALLY_APPROVED).target(Status.CHOICE).event(ActionType.APPROVE)
.and()
.withChoice()
.source(Status.CHOICE)
.first(Status.PENGDING_DOCUMENT_CHECK,(context)->{
Group group = context.getMessage().getHeaders().get("group",Group.class);
return group.isAdvance();
})
.last(Status.APPROVED)
.and()
.withExternal()
.source(Status.PENGDING_DOCUMENT_CHECK).target(Status.PENDING_APPROVAL_CONFIRMATION).event(ActionType.APPROVE)
.and()
.withExternal()
.source(Status.PENDING_APPROVAL_CONFIRMATION).target(Status.APPROVED).event(ActionType.APPROVE)
.and()
.withExternal()
.source(Status.PENDING_APPROVAL_CONFIRMATION).target(Status.PENGDING_DOCUMENT_CHECK).event(ActionType.REJECT)
.and()
.withExternal()
.source(Status.PENGDING_DOCUMENT_CHECK).target(Status.PENDING_REJECT_CONFIRMATION).event(ActionType.REJECT)
.and()
.withExternal()
.source(Status.PENDING_REJECT_CONFIRMATION).target(Status.PENGDING_DOCUMENT_CHECK).event(ActionType.REJECT)
.and()
.withExternal()
.source(Status.PENDING_REJECT_CONFIRMATION).target(Status.REJECTED).event(ActionType.APPROVE)
.and()
.withExternal()
.source(Status.PENDING_APPROVAL).target(Status.REJECTED).event(ActionType.REJECT)
.and()
.withExternal()
.source(Status.PARTIALLY_APPROVED).target(Status.REJECTED).event(ActionType.REJECT);
}
}
public enum ActionType {
APPROVE(1), REJECT(2);
}
public enum Status {
PENDING_APPROVAL("status.pending_approval", 1),
PARTIALLY_APPROVED("status.partially_approval", 2),
APPROVED("status.approved", 3),
REJECTED("status.rejected", 4),
PENGDING_DOCUMENT_CHECK("status.pending_document_check", 5),
PENDING_APPROVAL_CONFIRMATION("status.pending_approval_confirmation", 10),
PENDING_REJECT_CONFIRMATION("status.pending_reject_confirmation", 11),
CHOICE("spring.state.machine.choice",501);
private String msgKey;
private int statusCode;
Status(String desc, int statusCode) {
this.msgKey = desc;
this.statusCode = statusCode;
}
public static Status valueOf(int statusCode) {
Iterator iterator = EnumSet.allOf(Status.class).iterator();
while (iterator.hasNext()){
Status st =iterator.next();
if(st.statusCode==statusCode)
return st;
}
throw new IllegalArgumentException("invalid status code");
}
public String getMsgKey() {
return msgKey;
}
public int getStatusCode() {
return statusCode;
}
}
PersistStateMachineHandler
该代码就是往statemachine里添加一个拦截器
PersistingStateChangeInterceptor
,
拦截到所有的preStateChange事件,就通知CompositePersistStateChangeListener
里面注册的listener去处理
@Component
public class PersistStateMachineHandler extends LifecycleObjectSupport {
private final StateMachine stateMachine;
private final PersistingStateChangeInterceptor interceptor = new PersistingStateChangeInterceptor();
private final CompositePersistStateChangeListener listeners = new CompositePersistStateChangeListener();
/**
* Instantiates a new persist state machine handler.
*
* @param stateMachine the state machine
*/
@Autowired
public PersistStateMachineHandler(StateMachine stateMachine) {
Assert.notNull(stateMachine, "State machine must be set");
this.stateMachine = stateMachine;
}
//会被LifecycleObjectSupport父类的InitializingBean.afterPropertiesSet()里调用
protected void onInit() throws Exception {
//往stateMachine加入拦截器PersistingStateChangeInterceptor
stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction>() {
public void apply(StateMachineAccess function) {
function.addStateMachineInterceptor(interceptor);
}
});
//获取所有 PersistStateChangeListener的bean注册到CompositePersistStateChangeListener
Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors((ListableBeanFactory) this.getBeanFactory(), PersistStateChangeListener.class, true, false);
if (!matchingBeans.isEmpty()) {
listeners.setListeners(new ArrayList(matchingBeans.values()));
}
}
/**
* Handle event with entity.
*
* @param event the event
* @param state the state
* @return true if event was accepted
*/
public boolean handleEventWithState(Message event, Status state) {
stateMachine.stop();
List> withAllRegions = stateMachine.getStateMachineAccessor().withAllRegions();
for (StateMachineAccess a : withAllRegions) {
a.resetStateMachine(new DefaultStateMachineContext(state, null, null, null));
}
stateMachine.start();
return stateMachine.sendEvent(event);
}
/**
* Adds the persist state change listener.
*
* @param listener the listener
*/
public void addPersistStateChangeListener(PersistStateChangeListener listener) {
listeners.register(listener);
}
/**
* The listener interface for receiving persistStateChange events.
* The class that is interested in processing a persistStateChange
* event implements this interface, and the object created
* with that class is registered with a component using the
* component's addPersistStateChangeListener
method. When
* the persistStateChange event occurs, that object's appropriate
* method is invoked.
*/
public interface PersistStateChangeListener {
/**
* Called when state needs to be persisted.
*
* @param state the state
* @param message the message
* @param transition the transition
* @param stateMachine the state machine
*/
void onPersist(State state, Message message, Transition transition,
StateMachine stateMachine);
}
private class PersistingStateChangeInterceptor extends StateMachineInterceptorAdapter {
@Override
public void preStateChange(State state, Message message, Transition transition, StateMachine stateMachine) {
listeners.onPersist(state,message,transition,stateMachine);
}
}
private class CompositePersistStateChangeListener extends AbstractCompositeListener implements
PersistStateChangeListener {
public void onPersist(State state, Message message,
Transition transition, StateMachine stateMachine) {
for (Iterator iterator = getListeners().reverse(); iterator.hasNext(); ) {
PersistStateChangeListener listener = iterator.next();
listener.onPersist(state, message, transition, stateMachine);
}
}
}
}
GroupController
@RestController
public class GroupController {
@Autowired
GroupService groupService;
@RequestMapping("/group/list")
public List list(){
return groupService.listAll();
}
@PostMapping("/group/create")
public boolean create(@RequestBody Group group){
groupService.create(group);
return true;
}
//web入口,处理某个group{id}的某个事件,例如group/2/APPROVE就是对group id 为2 做approve
@RequestMapping("/group/{id}/{event}")
public boolean handle(@PathVariable("id")Integer id,@PathVariable("event") String event){
return groupService.handleAction(id,event);
}
}
GroupService
@org.springframework.stereotype.Service
public class GroupService {
@Autowired
private PersistStateMachineHandler handler;
@Autowired
private GroupRepository repository;
public boolean handleAction(int groupId, String event) {
Group group = repository.findGroupById(groupId);
//发送事件去触发状态机
return handler.handleEventWithState(MessageBuilder.withPayload(ActionType.valueOf(event))
.setHeader("group", group).build(), Status.valueOf(group.getState()));
}
public void create(Group group) {
repository.create(group);
}
public List listAll() {
return repository.listAll();
}
}
GroupRepoository 相当于Dao
@Repository
public class GroupRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
public void create(Group group){
jdbcTemplate.update("insert into pagroup(groupId,groupName,status) values (?,?,?)",
group.getGroupId(),
group.getGroupName(),
Status.PENDING_APPROVAL.getStatusCode());
}
public List listAll() {
List list = jdbcTemplate.query("select groupId,groupName,status,isAdvance from pagroup",rowMapper());
return list;
}
public Group findGroupById(int groupId) {
Group group = jdbcTemplate.queryForObject("select groupId, groupName,status,isAdvance from pagroup where groupId = ?", new Object[]{groupId},rowMapper());
return group;
}
private RowMapper rowMapper(){
return new RowMapper() {
public Group mapRow(ResultSet rs, int rowNum) throws SQLException {
Group group = new Group(rs.getInt("groupId"), rs.getString("groupName"));
group.setState(rs.getInt("status"));
group.setAdvance(rs.getBoolean("isAdvance"));
return group;
}
};
}
}
GroupPersistStateChangeListener 监听到状态机状态更改,就更新数据库里的对应字段
@Component
public class GroupPersistStateChangeListener implements PersistStateMachineHandler.PersistStateChangeListener {
@Autowired
private JdbcTemplate jdbcTemplate;
public GroupPersistStateChangeListener(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void onPersist(State state, Message message, Transition transition, StateMachine stateMachine) {
if (message != null && message.getHeaders().containsKey("group")) {
Group group = message.getHeaders().get("group", Group.class);
jdbcTemplate.update("update pagroup set status = ? where groupId = ?", state.getId().getStatusCode(), group.getGroupId());
}
}
}
启动Spring工程
@SpringBootApplication
public class BootStrap {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(BootStrap.class);
builder.run(args);
}
}
代码在github