springboot+状态机

最近好多业务都是流程状态的传递,借此机会写词文章,记录一下,什么样的场景设计需要我们的状态机:

目前市场流行的状态机:
 1.Spring Statemachine 

  2.阿里COLA4.4

状态机框架

  • Spring Statemachine (重量级选手)1.2k+ star.
  • squirrel-foundation(松鼠)1.8k+ star.
  • cola-statemachine (可乐)6.6k+ star.

springboot合cola-statemachine 4.4.0

cola-statemachine(阿里出品)

相比Spring statemachine状态机等的复杂,功能多;我们实际业务员需要常用的功能,简单使用,所以这类就显得不简洁;再看cola-statemachine相比就是小巧、无状态、简单、轻量、性能极高的状态机DSL实现,解决业务中的状态流转问题。
github:
https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-statemachine

学习这玩意什么用:

状态机优势

1、状态机建立的控制中心是跟外界低耦合的,通过event通信;
2、控制中心所有的状态都是预设好的,不会超预料;
3、状态的跳转都是有设定控制条件的,会按照预设的转移路径运动;
4、状态机还非常容易的扩展和变更,支持因业务的发展而变更或扩展复杂业务流程。

Spring Boot StateMachine实现 

<dependency>
		<groupId>org.springframework.statemachine</groupId>
		<artifactId>spring-statemachine-core</artifactId>
		<version>1.2.0.RELEASE</version>
	</dependency>

Spring Boot cola实现 

         <dependency>
                <groupId>com.alibaba.cola</groupId>
                <artifactId>cola-component-statemachine</artifactId>
                <version>4.4.0-SNAPSHOT</version>
            </dependency>

状态机实现步骤:

         第一步生成一个状态机

        第二步:设置一个外部状态转移类型的builder 
        第三步:设置状态机的id和ready
        第四步:触发状态机

概念:
State:状态

Event:事件,状态由事件触发,引起变化

Transition:流转,表示从一个状态到另一个状态

External Transition:外部流转,两个不同状态之间的流转

Internal Transition:内部流转,同一个状态之间的流转

Condition:条件,表示是否允许到达某个状态

Action:动作,到达某个状态之后,可以做什么

StateMachine:状态机

完成状态机配置:状态机的初始状态和所有状态机的转移规则

整个状态的调度逻辑主要依靠配置方式的定义,而所有的业务逻辑操作都被定义在了状态监听器中,其实状态监听器可以实现的功能远不止上面我们所述的内容,它还有更多的事件捕获

都是技术人员介绍那么多都是废话,直接上代码它不香

好多代码都是demo根本没有实际试用起来。今天给大家上点干活。

无脑选择spring自带的状态机:

场景一:电商的订单,和发货,退货的状态

企业无商城,你玩什么。因此我们的实际场景就是我们经常看到的订单状态的流转

状态机定义三件事:

1.定义枚举状态

2.定义枚举事件

3.定义事件流转的触发条件

有人就说了,为什么定义枚举呢?

答:你想定义啥,定义啥,你有本事,可以直接定义字典,定义接口,定义静态字段。

1.先给我们的业务订单定义状态

public enum OrderState implements IEnum<String> {
    CREATED(0, ""),//新创建订单
    PENDING(10, "Pending"),//支付待确认
    WAIT_SHIP(20, "Preparing"),//待发货
    REFUND_CHOICE(30, ""),//订单退款逻辑选择器,伪状态,数据库中不存在

    DELIVERING(40, "Shipped"),//已发货[运输中],所有的子单都发货,订单变更为已发货状态
    DELIVERED(50, "Shipped"),//已妥投[运输中],所有的子单都妥投,订单变更为妥投状态

    RECEIVED(60, "Received"),//已签收,所有的子单都签收(刨除逆向订单),订单变更为已签收
    COMPLETED(70, "Completed"),//完成,所有的子单都完成(刨除逆向订单),订单变更为已完成
    CANCELED(80, "Canceled"),//已取消,未支付订单、取消订单

    REFUND_AUDIT(90, "Reviewing"),//退货审核,所有子单都处于该状态
    REFUND_AUDIT_CHOICE(95, ""),//订单退款审核逻辑选择器,伪状态,数据库中不存在
    REFUNDING(100, "Refunding"),//退款中,所有子单都处于该状态
    REFUNDED(110, "Refunded");//已退款,所有子单都处于该状态

    /**
     * 此值为了解决父订单的状态取值而设,越大说明业务流程越深,时间离现在越近,订单的状态取此值最小的子单的状态
     */
    @Getter
    private final int logicDepth;

    @Getter
    @Setter
    private String customerDesc;

    OrderState(int depth, String desc) {
        this.logicDepth = depth;
        this.customerDesc = desc;
    }

    @Override
    public String getValue() {
        return name();
    }
}

2.定义事件

public enum OrderEvent implements IEnum<String> {
    COD_CHECKOUT,//创建订单后选择COD支付
    COD_CONFIRM,//支付确认
    COD_CANCEL,//支付取消
    COD_TIMEOUT,//支付确认超时

    PAY_SUCCESS,//PPD支付成功
    PAY_TIMEOUT,//支付超时
    PAY_CANCEL,//取消支付

    USER_CANCEL,//支付后/支付确认后用户取消
    PLATFORM_CANCEL,//支付后平台取消
    TIMEOUT_CANCEL,//订单匹配超时取消

    DO_SHIP,//发货
    ORDER_ARRIVE,//妥投
    CONFIRM_RECEIVE,//用户签收
    ORDER_REVIEW,//订单评价

    REJECT_ORDER,//用户拒签
    APPLY_RETURN,//申请退货退款

    RETURN_AGGREE,//退货退款审核通过
    RETURN_REJECT,//退货退款审核拒绝

    GOODS_RETURNED,//平台收到退回货物
    DO_REFUND,//收到货后,平台操作发起退款

    PPD_REFUND_SUCCESS,;//退款成功回调
    COD_REFUND_SUCCESS,//退款成功回调

    @Override
    public String getValue() {
        return name();
    }
}

3.定义状态机配置

@Configuration
public class OrderStateMachineConfig {

    final static String MACHINEID = "orderItemStateMachine";

    @Bean
    public StateMachinePersister<OrderState, OrderEvent, OrderStateAware> orderItemPersister(
            StateMachinePersist<OrderState, OrderEvent, OrderStateAware> orderItemStateMachinePersist) {
        return new DefaultStateMachinePersister<>(orderItemStateMachinePersist);
    }

    @Bean
    public StateMachine<OrderState, OrderEvent> orderItemStateMachine() throws Exception {
        // @formatter:off
        StateMachineBuilder.Builder<OrderState, OrderEvent> builder = StateMachineBuilder.builder();

        builder.configureConfiguration()
                .withConfiguration()
                .machineId(MACHINEID)
                .beanFactory(beanFactory);

        builder.configureStates()
                .withStates()
                .initial(OrderState.CREATED)
                .choice(OrderState.REFUND_CHOICE)
                .choice(OrderState.REFUND_AUDIT_CHOICE)
                .end(OrderState.COMPLETED)
                .end(OrderState.CANCELED)
                .end(OrderState.REFUNDED)
                .states(EnumSet.allOf(OrderState.class));

        builder.configureTransitions()
                //CREATED 状态流转
                .withExternal()
                .source(OrderState.CREATED).target(OrderState.WAIT_SHIP).event(OrderEvent.PAY_SUCCESS)
                .and().withExternal()
                .source(OrderState.CREATED).target(OrderState.CANCELED).event(OrderEvent.PAY_CANCEL)
                .and().withExternal()
                .source(OrderState.CREATED).target(OrderState.CANCELED).event(OrderEvent.PAY_TIMEOUT)
                .and().withExternal()
                .source(OrderState.CREATED).target(OrderState.PENDING).event(OrderEvent.COD_CHECKOUT)

                //PENDING 状态流转
                .and().withExternal()
                .source(OrderState.PENDING).target(OrderState.WAIT_SHIP).event(OrderEvent.COD_CONFIRM)
                .and().withExternal()
                .source(OrderState.PENDING).target(OrderState.CANCELED).event(OrderEvent.COD_CANCEL)
                .and().withExternal()
                .source(OrderState.PENDING).target(OrderState.CANCELED).event(OrderEvent.COD_TIMEOUT)

                //WAIT_SHIP 状态流转
                .and().withExternal()
                .source(OrderState.WAIT_SHIP).target(OrderState.DELIVERING).event(OrderEvent.DO_SHIP)
                .and().withExternal()
                .source(OrderState.WAIT_SHIP).target(OrderState.REFUND_CHOICE).event(OrderEvent.USER_CANCEL)
                .and().withExternal()
                .source(OrderState.WAIT_SHIP).target(OrderState.REFUND_CHOICE).event(OrderEvent.PLATFORM_CANCEL)
                .and().withExternal()
                .source(OrderState.WAIT_SHIP).target(OrderState.REFUND_CHOICE).event(OrderEvent.TIMEOUT_CANCEL)

                //退货退款/仅退款/cancel choice流转
                .and().withChoice()
                .source(OrderState.REFUND_CHOICE)
                //如果为COD支付[wait_ship,delivered],状态变为Canceled,事物提交后返还库存、退还限购,发送异步消息通知A端
                //COD received状态 不允许退款
                .first(OrderState.CANCELED, codRefundChoiceGuard,codRefundChoiceAction)
                //否则 !cod & wait_ship & 实付=0,状态变为Refunded,事物提交后返还库存、退还限购,退还cash,发送异步消息通知A端
                .then(OrderState.REFUNDED,cashRefundChoiceGuard,cashRefundChoiceAction)
                //否则 !cod & wait_ship & 实付>0, 状态变为Refunding,事物提交后返还库存、退还限购,退还cash,调用三方支付退款api,发送异步消息通知A端
                .then(OrderState.REFUNDING, ppdRefundOnlyChoiceGuard,ppdRefundOnlyChoiceAction)
                //否则 (!cod & received)(可能包含cash支付,此时也需要审核)
                .then(OrderState.REFUND_AUDIT, ppdRefundChoiceGuard,ppdRefundChoiceAction)

                //DELIVERING 状态流转
                .and().withExternal()
                .source(OrderState.DELIVERING).target(OrderState.DELIVERED).event(OrderEvent.ORDER_ARRIVE)

                //DELEVERED 状态流转
                .and().withExternal()
                .source(OrderState.DELIVERED).target(OrderState.RECEIVED).event(OrderEvent.CONFIRM_RECEIVE)
                .and().withExternal()
                .source(OrderState.DELIVERED).target(OrderState.REFUND_CHOICE).event(OrderEvent.REJECT_ORDER)

                //RECEIVED 状态流转
                .and().withExternal()
                .source(OrderState.RECEIVED).target(OrderState.COMPLETED).event(OrderEvent.ORDER_REVIEW)
                .and().withExternal()
                .source(OrderState.RECEIVED).target(OrderState.REFUND_CHOICE).event(OrderEvent.APPLY_RETURN)

                //RETURN_AUDIT 状态流转
                .and().withExternal()
                .source(OrderState.REFUND_AUDIT).target(OrderState.COMPLETED).event(OrderEvent.RETURN_REJECT)
                .and().withExternal()
                .source(OrderState.REFUND_AUDIT).target(OrderState.REFUND_AUDIT_CHOICE).event(OrderEvent.RETURN_AGGREE)

                //审核通过后
                .and().withChoice()
                .source(OrderState.REFUND_AUDIT_CHOICE)
                //实付=0
                .first(OrderState.REFUNDED,refundAuditChoiceGuard)
                //实付>0
                .last(OrderState.REFUNDING)

                //REFUNDING 状态流转
                .and().withExternal()
                .source(OrderState.REFUNDING).target(OrderState.REFUNDED).event(OrderEvent.PPD_REFUND_SUCCESS)
//                .and().withExternal()
//                .source(OrderState.REFUNDING).target(OrderState.REFUNDED).event(OrderEvent.COD_REFUND_SUCCESS)
        ;
        // @formatter:on
        return builder.build();
    }


    @Bean
    public StateMachinePersist<OrderState, OrderEvent, OrderStateAware> orderItemStateMachinePersist() {
        return new StateMachinePersist<OrderState, OrderEvent, OrderStateAware>() {
            @Override
            public void write(StateMachineContext<OrderState, OrderEvent> context, OrderStateAware contextObj) {
            }

            @Override
            public StateMachineContext<OrderState, OrderEvent> read(OrderStateAware contextObj) {
                return new DefaultStateMachineContext<>(
                        contextObj.getState(),
                        null, null, null, null, MACHINEID);
            }
        };
    }
}

 4.定义状态机

public interface OrderEventFirer extends OrderStateAware {

    default void fireEvent(StateMachineRestorer restorer, OrderEvent event) {
        StateMachine<OrderState, OrderEvent> stateMachine = restorer.restore(this);
        stateMachine.sendEvent(event);
        OrderState newState = stateMachine.getState().getId();
        changeState(newState,event);
    }

    default void fireEvent(StateMachineRestorer restorer, OrderEvent event, Map<String,Object> headers) {
        StateMachine<OrderState, OrderEvent> stateMachine = restorer.restore(this);

        MessageBuilder<OrderEvent> builder = MessageBuilder.withPayload(event);
        for (Map.Entry<String, Object> entry : headers.entrySet()) {
            builder.setHeader(entry.getKey(), entry.getValue());
        }
        Message<OrderEvent> message = builder.build();
        stateMachine.sendEvent(message);
        OrderState newState = stateMachine.getState().getId();
        changeState(newState,event);
    }

    default void changeState(OrderState newState, OrderEvent event){
        OrderState currentState = this.getState();
        Assert.state(currentState != newState,"Undefined state transition.state=" + currentState + ",event=" + event);
        this.setBackState(currentState);
        this.setState(newState);
        this.setLatestAction(event);
    }

    void setState(OrderState state);

    void setBackState(OrderState state);

    void setLatestAction(OrderEvent event);
}

5.业务代码使用

 @Override
    public void doDelivered(String orderItemUuid, TrackDetailsDto tracking) {
        OrderItem orderItem = baseMapper.findByUuid(orderItemUuid);
        orderItem.setDeliverAt(tracking.getDeliverDate());
        orderItem.fireEvent(stateMachineRestorer, OrderEvent.ORDER_ARRIVE);
        if(null == orderItem.getPaymentAt()) {
            orderItem.setPaymentAt(orderItem.getDeliverAt());
        }
        baseMapper.updateById(orderItem);

        Order order = orderDao.findByUuid(orderItem.getOrderUuid());
        if (null == order.getDeliveredCount()) {
            order.setDeliveredCount(0);
        }
        order.setDeliveredCount(order.getDeliveredCount() + 1 );

        List<OrderState> subStates = baseMapper.findByOrderUuid(order.getUuid()).stream().map(OrderItem::getState).collect(Collectors.toList());
        if (order.stateModified(subStates)) {
            smsSender.sendSms(
                    SmsBizType.ORDER_SINGED,
                    order.getLanguage(),
                    order.getShippingAddressMobile());
        }
        orderDao.updateById(order);
    }

写完收工:

好多网友看了,欲哭无泪,看了这么久不能运行。白扯半天。😄 里面缺几个实现类,缺数据库表设计。这是实际代码,能贴出来嘛。根据思路去实现才是王道,比一些demo强太多了。

你们需要这套源码,欢迎加我微信

 

各种功能业务实现。需要大牛出手帮助写的,欢迎咨询,地道的架构师,地道的经理,地道的业务终结者,只要你提,没有完成不了的。看准文章标签来吧。

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以的,我可以为您提供一个基于Spring Boot、JWT、Shiro和Redis的例子。这个例子将演示如何使用这些技术实现用户认证和授权,并且将用户的状态存储在Redis中。 首先,您需要创建一个Spring Boot项目并添加所需的依赖。 在pom.xml文件中添加以下依赖: ``` <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.7.1</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> ``` 接下来,创建一个名为`JwtUtils`的JWT工具类,用于生成和验证JWT令牌。您可以参考以下代码: ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Date; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private int expiration; @PostConstruct public void init() { secret = Base64.getEncoder().encodeToString(secret.getBytes()); } public String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } } ``` 然后,创建一个名为`RedisUtils`的Redis工具类,用于操作Redis。您可以参考以下代码: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class RedisUtils { @Autowired private RedisTemplate<String, Object> redisTemplate; public void set(String key, Object value, long expiration) { redisTemplate.opsForValue().set(key, value, expiration, TimeUnit.SECONDS); } public Object get(String key) { return redisTemplate.opsForValue().get(key); } public void delete(String key) { redisTemplate.delete(key); } public boolean hasKey(String key) { return redisTemplate.hasKey(key); } } ``` 接下来,创建一个名为`JwtRealm`的Shiro Realm,用于验证JWT令牌和授权。您可以参考以下代码: ```java import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class JwtRealm extends AuthorizingRealm { @Autowired private JwtUtils jwtUtils; @Autowired private RedisUtils redisUtils; @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO: 实现授权逻辑 } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { JwtToken jwtToken = (JwtToken) token; String username = jwtUtils.getUsernameFromToken(jwtToken.getToken()); if (username == null || !jwtUtils.validateToken(jwtToken.getToken())) { throw new AuthenticationException("无效的令牌"); } // TODO: 查询用户信息并返回认证信息 return new SimpleAuthenticationInfo(username, jwtToken.getToken(), getName()); } } ``` 最后,创建一个名为`JwtToken`的Shiro Token,用于封装JWT令牌。您可以参考以下代码: ```java import org.apache.shiro.authc.AuthenticationToken; public class JwtToken implements AuthenticationToken { private String token; public JwtToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } } ``` 以上是一个基于Spring Boot、JWT、Shiro和Redis的例子。您可以根据您的需求进行修改和扩展。希望对您有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杭州架构师

你的鼓励你创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值