SpringBoot整合Squirrel状态机实战:订单状态流转的优雅实现(附完整Demo)
SpringBoot整合Squirrel状态机实战订单状态流转的优雅实现附完整Demo在复杂的业务系统中状态流转是核心逻辑之一尤其像订单、工单、审批流这类场景状态多、规则复杂、变更频繁。如果直接用if-else或switch-case硬编码初期看似简单但随着业务迭代代码会迅速膨胀维护成本指数级上升一个状态变更可能牵一发而动全身。我经历过一个电商项目订单状态从最初的5个扩展到近20个各种促销、售后、风控规则交织原来的状态判断代码成了“祖传屎山”每次改动都战战兢兢。这时候状态机模式就成了救星。它把状态、事件、动作和流转规则抽象出来用声明式或配置化的方式管理让业务逻辑清晰可见也更容易测试和扩展。在Java生态里Squirrel Foundation是一个轻量级、功能强大的状态机框架它支持注解和流式API两种定义方式与SpringBoot集成起来非常顺畅。今天我就结合一个完整的订单状态流转案例带你从零开始把Squirrel状态机“装进”SpringBoot项目聊聊怎么设计、怎么集成、怎么避开那些常见的“坑”。我会提供可直接运行的代码片段并分享一些在真实项目中打磨出来的实践经验。1. 理解核心状态机与Squirrel Foundation在深入代码之前我们得先对齐一下基本概念。有限状态机Finite State Machine, FSM不是什么新潮玩意儿它是一种数学模型用来描述一个对象在其生命周期内所经历的状态序列以及如何响应来自外部的各种事件。简单说它明确了三件事状态State对象在特定时刻所处的状况比如订单的“待支付”、“已发货”。事件Event触发状态改变的外部动作比如用户点击“支付”、系统触发“超时取消”。转换Transition事件发生后从一个状态切换到另一个状态的规则和过程。把这三者用代码管理起来就是状态机框架的价值。Squirrel Foundation在这方面做得相当优雅它通过注解或流式API让你能像描述业务规则一样去定义状态机而不是埋在过程式代码里。1.1 为什么选择Squirrel市面上状态机方案不少比如Spring State Machine、自己手写状态模式。Squirrel吸引我的地方在于轻量且功能完整核心jar包很小但支持了状态机需要的绝大部分特性包括嵌套状态机、并行状态、历史状态等高级功能。两种定义风格注解式声明直观像写配置流式API动态灵活适合规则需要运行时变化的场景。清晰的生命周期监听提供了TransitionBegin、TransitionComplete、TransitionEnd、TransitionException等关键节点回调方便我们插入业务逻辑比如日志、监控、持久化。与Spring生态友好虽然状态机实例本身不由Spring容器管理但通过一些设计模式后面会详细讲可以很方便地注入Spring管理的Bean如Service、Repository实现业务操作。为了更直观地对比我整理了Squirrel核心组件与传统硬编码方式的区别特性维度Squirrel状态机传统if-else/switch-case规则可视化通过注解或API集中定义一目了然分散在各个业务方法中难以全局把握可维护性增删状态或转换规则通常只需修改一处定义需在多处查找并修改条件分支易遗漏可测试性状态机本身可独立单元测试规则逻辑清晰业务逻辑与状态判断耦合测试用例复杂扩展性易于添加新的监听器、或支持动态规则加载结构僵化添加新状态或事件可能需重构大量代码复杂度管理天然适合管理多状态、多事件的复杂流转当状态超过10个代码可读性和维护性急剧下降提示不是所有场景都需要状态机。对于极其简单、几乎不会变化的状态流转比如只有“启用/禁用”两种状态直接使用枚举或常量配合简单判断可能更直接。但当你的状态图开始变得像一张蜘蛛网时就是引入状态机的好时机。1.2 Squirrel状态机的核心生命周期理解生命周期是正确集成和持久化的关键。一次完整的状态转换在Squirrel内部会经历以下几个阶段TransitionBegin转换开始。此时状态还未改变。执行退出动作Exit Action离开源状态时执行的操作。执行转换动作Transition Action即你在Transit注解中callMethod指定的方法或流式API中定义的动作。这里是执行业务逻辑如更新库存、调用支付接口的主要位置。执行进入动作Entry Action进入目标状态时执行的操作。TransitionComplete状态转换已完成。这是最关键的节点此时状态机内部的状态已经更新为目标状态但整个转换过程尚未最终结束。很多同学会选择在此处进行状态持久化例如将新状态写入数据库因为此时状态已确定且后续的TransitionEnd阶段可能还会触发一些异步通知等不影响核心状态的操作。TransitionEnd转换过程全部结束。如果过程中发生异常则会进入TransitionException分支然后跳到TransitionEnd。如果事件在当前状态下不被允许没有对应的转换规则则会触发TransitionDeclined。这个生命周期模型为我们集成事务、持久化提供了清晰的钩子。2. 项目搭建与基础集成接下来我们动手构建一个SpringBoot项目并集成Squirrel。我会以电商订单的经典状态流转为例。2.1 环境与依赖准备首先创建一个标准的SpringBoot项目这里使用SpringBoot 2.7.xJDK 11。在pom.xml中添加Squirrel Foundation的依赖。dependency groupIdorg.squirrelframework/groupId artifactIdsquirrel-foundation/artifactId version0.3.8/version /dependency当然还需要数据库访问相关的依赖比如MyBatis-Plus和H2内存数据库方便演示dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency2.2 定义状态、事件与上下文这是状态机的“词汇表”建议单独放在一个包下如com.example.order.fsm。状态枚举OrderStatepublic enum OrderState { /** * 初始状态订单创建但未提交 */ INIT, /** * 待支付 */ WAIT_PAY, /** * 支付成功待发货 */ WAIT_SHIP, /** * 已发货运输中 */ SHIPPED, /** * 已签收订单完成 */ RECEIVED, /** * 取消关闭 */ CANCELLED, /** * 售后中 */ AFTER_SALE; // 可以添加一些辅助方法比如判断是否为终态 public boolean isFinalState() { return this RECEIVED || this CANCELLED; } }事件枚举OrderEventpublic enum OrderEvent { /** * 提交订单 */ SUBMIT, /** * 支付成功 */ PAY_SUCCESS, /** * 商家发货 */ SHIP, /** * 用户确认收货 */ CONFIRM_RECEIPT, /** * 用户取消订单 */ USER_CANCEL, /** * 系统超时取消 */ TIMEOUT_CANCEL, /** * 申请售后 */ APPLY_AFTER_SALE; }上下文对象OrderContext 上下文用于在状态转换过程中传递业务数据比如订单ID、操作人、时间戳等。它贯穿整个状态转换生命周期。Data AllArgsConstructor NoArgsConstructor public class OrderContext { /** * 订单ID */ private Long orderId; /** * 操作人ID用户或系统 */ private String operator; /** * 扩展参数可用于传递额外信息 */ private MapString, Object extraParams; }3. 注解式状态机实战注解式定义非常直观适合状态流转规则相对固定的场景。我们来定义一个订单状态机。3.1 定义状态机类创建一个OrderStateMachine类继承AbstractUntypedStateMachine。使用States和Transitions注解来声明状态和转换规则。import org.squirrelframework.foundation.fsm.annotation.*; import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine; import lombok.extern.slf4j.Slf4j; States({ State(name INIT, entryCallMethod entryInit, exitCallMethod exitInit, initialState true), State(name WAIT_PAY, entryCallMethod entryWaitPay, exitCallMethod exitWaitPay), State(name WAIT_SHIP, entryCallMethod entryWaitShip), State(name SHIPPED, entryCallMethod entryShipped), State(name RECEIVED), State(name CANCELLED), State(name AFTER_SALE) }) Transitions({ Transit(from INIT, to WAIT_PAY, on SUBMIT, callMethod onSubmitOrder), Transit(from WAIT_PAY, to WAIT_SHIP, on PAY_SUCCESS, callMethod onPaySuccess), Transit(from WAIT_PAY, to CANCELLED, on USER_CANCEL, callMethod onUserCancel), Transit(from WAIT_PAY, to CANCELLED, on TIMEOUT_CANCEL, callMethod onTimeoutCancel), Transit(from WAIT_SHIP, to SHIPPED, on SHIP, callMethod onShip), Transit(from SHIPPED, to RECEIVED, on CONFIRM_RECEIPT, callMethod onConfirmReceipt), Transit(from WAIT_SHIP, to AFTER_SALE, on APPLY_AFTER_SALE, callMethod onApplyAfterSale), Transit(from SHIPPED, to AFTER_SALE, on APPLY_AFTER_SALE, callMethod onApplyAfterSale) }) StateMachineParameters(stateType OrderState.class, eventType OrderEvent.class, contextType OrderContext.class) Slf4j public class OrderStateMachine extends AbstractUntypedStateMachine { Autowired private OrderService orderService; // 如何注入后面会解决 // 状态转换动作方法 protected void onSubmitOrder(OrderState from, OrderState to, OrderEvent event, OrderContext context) { log.info(订单[{}]提交从[{}]状态到[{}]状态操作人:{}, context.getOrderId(), from, to, context.getOperator()); // 核心业务逻辑更新订单状态、记录日志等 orderService.updateOrderStatus(context.getOrderId(), to); } protected void onPaySuccess(OrderState from, OrderState to, OrderEvent event, OrderContext context) { log.info(订单[{}]支付成功从[{}]状态到[{}]状态, context.getOrderId(), from, to); orderService.updateOrderStatus(context.getOrderId(), to); // 可以触发其他业务如扣减库存、发送支付成功通知等 // inventoryService.deductStock(orderId); // notificationService.sendPaySuccessMsg(orderId); } protected void onUserCancel(OrderState from, OrderState to, OrderEvent event, OrderContext context) { log.warn(订单[{}]被用户取消原因:{}, context.getOrderId(), context.getExtraParams().get(cancelReason)); orderService.cancelOrder(context.getOrderId(), (String)context.getExtraParams().get(cancelReason)); } // ... 其他转换动作方法 // 状态进入/退出动作可选 protected void entryInit(OrderState from, OrderState to, OrderEvent event, OrderContext context) { log.debug(进入INIT状态); } protected void exitInit(OrderState from, OrderState to, OrderEvent event, OrderContext context) { log.debug(离开INIT状态); } protected void entryWaitPay(OrderState from, OrderState to, OrderEvent event, OrderContext context) { log.info(订单[{}]进入待支付状态开始计时..., context.getOrderId()); // 可以在这里启动一个定时任务用于超时取消 } // ... 其他entry/exit方法 }这里有个问题OrderStateMachine实例是由Squirrel框架动态创建的不是Spring Bean所以直接用Autowired注入OrderService会失败。我们需要一个桥梁。3.2 构建状态机引擎解决Spring集成与事务我们需要创建一个StateMachineEngine它由Spring管理负责创建状态机实例并注入Spring上下文。import org.squirrelframework.foundation.fsm.UntypedStateMachine; import org.squirrelframework.foundation.fsm.UntypedStateMachineBuilder; import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import lombok.extern.slf4j.Slf4j; /** * 状态机引擎模板 * param T 具体状态机类型 */ Slf4j Component public abstract class AbstractStateMachineEngineT extends UntypedStateMachine implements ApplicationContextAware { protected UntypedStateMachineBuilder stateMachineBuilder; private ApplicationContext applicationContext; SuppressWarnings(unchecked) public AbstractStateMachineEngine() { // 通过泛型解析获取具体状态机类的Class对象 ClassT stateMachineClass (ClassT) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractStateMachineEngine.class); if (stateMachineClass ! null) { // 创建状态机构建器并声明需要注入ApplicationContext stateMachineBuilder StateMachineBuilderFactory.create(stateMachineClass, ApplicationContext.class); } } Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext applicationContext; } /** * 触发状态转换的核心方法 * param event 事件 * param context 上下文 * return 是否发生错误 */ public boolean fire(OrderEvent event, OrderContext context) { // 1. 获取当前业务对象的状态例如从数据库查询 OrderState currentState loadCurrentState(context.getOrderId()); // 2. 创建状态机实例并注入Spring Context T stateMachine stateMachineBuilder.newUntypedStateMachine(currentState, applicationContext); // 3. 获取事务管理器开启编程式事务 PlatformTransactionManager transactionManager applicationContext.getBean(PlatformTransactionManager.class); DefaultTransactionDefinition def new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status transactionManager.getTransaction(def); boolean hasError false; try { // 4. 触发状态转换 stateMachine.fire(event, context); // 5. 提交事务 transactionManager.commit(status); log.info(状态转换成功: orderId{}, event{}, context.getOrderId(), event); } catch (Exception ex) { // 6. 转换失败回滚事务 transactionManager.rollback(status); hasError true; log.error(状态转换失败: orderId{}, event{}, context.getOrderId(), event, ex); // 这里可以根据异常类型进行更精细的处理比如重试、告警等 handleTransitionException(stateMachine, ex, context); } finally { // 7. 销毁状态机实例释放资源重要 if (stateMachine ! null) { stateMachine.terminate(); } } return hasError; } /** * 加载当前状态需子类或调用方实现 */ protected abstract OrderState loadCurrentState(Long orderId); /** * 处理转换异常 */ protected void handleTransitionException(T stateMachine, Exception ex, OrderContext context) { // 默认实现记录日志。子类可重写以进行更复杂的错误恢复。 log.error(状态机[{}]处理事件[{}]时发生异常订单ID:{}, stateMachine.getClass().getSimpleName(), context.getOrderId(), ex); } }然后为我们的订单状态机创建一个具体的引擎Service public class OrderStateMachineEngine extends AbstractStateMachineEngineOrderStateMachine { Autowired private OrderMapper orderMapper; Override protected OrderState loadCurrentState(Long orderId) { Order order orderMapper.selectById(orderId); if (order null) { throw new RuntimeException(订单不存在: orderId); } return order.getStatus(); } Override protected void handleTransitionException(OrderStateMachine stateMachine, Exception ex, OrderContext context) { // 订单状态转换失败可以发送告警通知运营人员 // alertService.sendAlert(订单状态转换异常, context.getOrderId(), ex.getMessage()); super.handleTransitionException(stateMachine, ex, context); } }现在OrderStateMachine需要改造一下构造函数以接收ApplicationContext// 在OrderStateMachine类中添加 public OrderStateMachine(ApplicationContext applicationContext) { // 通过ApplicationContext获取Spring Bean this.orderService applicationContext.getBean(OrderService.class); }注意StateMachineBuilder是线程安全的可以作为单例。但StateMachine实例是非线程安全的每次处理一个状态转换事件时都需要通过builder.newStateMachine(...)创建一个新的实例并在使用后调用terminate()销毁。我们的AbstractStateMachineEngine.fire()方法已经包含了这个生命周期管理。3.3 状态持久化的时机选择这是一个关键设计点。状态应该在何时保存到数据库结合Squirrel的生命周期常见选择有在Transition Action中callMethod方法里这是最直接的方式业务逻辑和持久化在一起。但要注意如果后续的Entry Action或监听器抛异常状态已经持久化可能导致不一致。需要确保整个转换过程在同一个事务内。在afterTransitionCompleted回调中如前所述这是Squirrel官方推荐的位置之一。此时状态机内部状态已更新且尚未触发TransitionEnd。在这个方法里持久化可以确保只有成功转换的状态才会被保存。在TransitionEnd回调中此时所有转换相关的动作包括可能的外部通知都已完成。在这里持久化最“安全”但如果持久化失败状态机已经认为转换成功可能引发问题。在我们的AbstractStateMachineEngine设计中将整个fire方法包裹在一个编程式事务中。这意味着无论在callMethod还是afterTransitionCompleted中执行数据库更新只要事务提交成功所有更改都会生效一旦任何地方抛出异常事务回滚数据库状态保持不变。这提供了很强的一致性保证。我个人的习惯是在callMethod中执行业务逻辑和核心状态更新依赖引擎层的事务保证原子性。afterTransitionCompleted更适合做一些非核心的、最终一致性的操作比如发送领域事件、更新搜索引擎索引等。4. 高级特性与生产实践掌握了基础集成后我们来看看一些能让你项目更稳健、更灵活的高级用法。4.1 监听器Listener的妙用监听器允许你在状态转换的各个生命周期节点插入自定义逻辑而无需修改状态机主体代码。Squirrel支持两种监听器注解式和编程式。注解式监听器非常简洁可以单独定义一个类Component // 可以被Spring管理 Slf4j public class OrderStateMachineListener { OnTransitionBegin public void onTransitionBegin(OrderState from, OrderEvent event, OrderContext context) { log.info([监听器] 转换开始. 订单[{}], 从[{}]状态, 事件[{}], context.getOrderId(), from, event); // 可以做权限校验、参数校验等 if (event OrderEvent.PAY_SUCCESS !validatePayment(context)) { throw new RuntimeException(支付校验失败); } } OnTransitionComplete public void onTransitionComplete(OrderState from, OrderState to, OrderEvent event, OrderContext context) { log.info([监听器] 转换完成. 订单[{}], [{}] - [{}], 事件[{}], context.getOrderId(), from, to, event); // 发送领域事件解耦其他业务系统 applicationEventPublisher.publishEvent(new OrderStatusChangedEvent(this, context.getOrderId(), from, to)); } OnTransitionException public void onTransitionException(OrderState from, OrderState to, OrderEvent event, OrderContext context, TransitionException e) { log.error([监听器] 转换异常! 订单[{}], 事件[{}], context.getOrderId(), event, e); // 记录异常指标用于监控告警 metricsCounter.increment(fsm.transition.exception); } Autowired private ApplicationEventPublisher applicationEventPublisher; // ... 其他依赖 }然后在创建状态机实例后需要将这个监听器注册上去。我们可以在AbstractStateMachineEngine.fire方法中改进public boolean fire(OrderEvent event, OrderContext context) { // ... 创建stateMachine ... // 注册全局监听器 for (StateMachineListener listener : applicationContext.getBeansOfType(StateMachineListener.class).values()) { stateMachine.addStateMachineListener(listener); } // ... 触发fire和事务管理 ... }编程式监听器则通过重写状态机类中的特定方法来实现如afterTransitionCompleted更贴近状态机本身适合状态机专属的逻辑。4.2 条件转换Conditional Transition与动态规则不是所有事件在任何情况下都能触发转换。Squirrel支持在定义转换时添加条件。使用when属性MVEL表达式Transit(from WAIT_PAY, to CANCELLED, on USER_CANCEL, callMethod onUserCancel, when context.extraParams[cancelType] USER_REQUEST)这个转换只有在上下文extraParams中cancelType为USER_REQUEST时才会执行。更复杂的条件判断可以在callMethod方法内部实现如果条件不满足直接抛出异常状态转换会被拒绝进入TransitionDeclined流程。对于需要动态规则的场景比如运营后台可配置订单状态流转规则注解式就不太适合了。这时应该使用**流式APIFluent API**在运行时构建状态机。核心思路是将规则存储在数据库或配置中心在应用启动或规则变更时动态调用StateMachineBuilder的API来定义externalTransition、internalTransition等。// 伪代码示例动态构建 StateMachineBuilder... builder StateMachineBuilderFactory.create(...); ListTransitionRule rules ruleService.loadActiveRules(); for (TransitionRule rule : rules) { builder.externalTransition() .from(rule.getFromState()) .to(rule.getToState()) .on(rule.getEvent()) .callMethod(rule.getActionMethodName()); } // 将这个builder缓存起来供后续创建状态机实例使用4.3 分布式环境下的考量在微服务或分布式部署中状态机的使用需要额外小心并发控制同一个订单几乎同时收到“支付成功”和“用户取消”两个事件怎么办必须在触发状态机前对订单ID加锁如使用Redis分布式锁或数据库行锁确保串行处理。可以在AbstractStateMachineEngine.fire方法最开始处加锁。public boolean fire(OrderEvent event, OrderContext context) { String lockKey order_lock: context.getOrderId(); if (!distributedLock.tryLock(lockKey, 3, TimeUnit.SECONDS)) { throw new ConcurrentAccessException(订单正在被处理请稍后重试); } try { // ... 原有的fire逻辑 ... } finally { distributedLock.unlock(lockKey); } }状态机实例的无状态化我们的设计里状态机实例是每次请求新创建的用完即销毁这本身就是无状态的适合分布式部署。事件溯源与补偿对于关键业务可以考虑使用**事件溯源Event Sourcing**模式。不直接持久化最终状态而是持久化所有状态转换事件。当需要查询当前状态时从头回放所有事件计算得出。这为调试、审计和实现补偿事务Saga提供了极大便利。Squirrel的监听器是发出领域事件的绝佳位置。4.4 测试策略状态机的可测试性是其一大优势。可以分别对状态机规则和业务逻辑进行测试。单元测试状态机规则直接测试OrderStateMachine模拟事件和上下文断言状态是否正确转换以及是否正确调用了callMethod。Test void testSubmitOrderTransition() { OrderStateMachine fsm builder.newStateMachine(OrderState.INIT, applicationContext); OrderContext context new OrderContext(123L, testUser, null); fsm.fire(OrderEvent.SUBMIT, context); assertEquals(OrderState.WAIT_PAY, fsm.getCurrentState()); // 验证是否调用了正确的Service方法 (可通过Mockito verify) }集成测试测试完整的OrderStateMachineEngine.fire()流程包括数据库持久化和事务回滚。可视化状态图Squirrel支持导出状态图为DOT格式可以用Graphviz生成图片。这对于和产品、测试同学沟通业务规则非常有帮助。String dot StateMachineVisualizer.generateDot(stateMachine); // 将dot字符串写入文件然后用工具生成png/svg5. 完整Demo与代码片段最后我们串联一个完整的REST API示例展示如何在实际Controller中使用状态机引擎。OrderController.java:RestController RequestMapping(/api/order) Slf4j public class OrderController { Autowired private OrderStateMachineEngine stateMachineEngine; Autowired private OrderService orderService; PostMapping(/{orderId}/submit) public ApiResponseVoid submitOrder(PathVariable Long orderId) { OrderContext context new OrderContext(orderId, getCurrentUserId(), null); boolean hasError stateMachineEngine.fire(OrderEvent.SUBMIT, context); if (hasError) { return ApiResponse.error(订单提交失败); } return ApiResponse.success(); } PostMapping(/{orderId}/pay-callback) public ApiResponseVoid payCallback(PathVariable Long orderId, RequestBody PayCallbackRequest request) { // 验证支付通知的签名等... if (!verifyPayCallback(request)) { return ApiResponse.error(非法回调); } MapString, Object extra new HashMap(); extra.put(paymentId, request.getPaymentId()); extra.put(amount, request.getAmount()); OrderContext context new OrderContext(orderId, payment_system, extra); boolean hasError stateMachineEngine.fire(OrderEvent.PAY_SUCCESS, context); if (hasError) { // 状态转换失败可能需要触发支付冲正或人工介入 log.error(支付回调状态转换失败orderId{}, paymentId{}, orderId, request.getPaymentId()); return ApiResponse.error(系统处理支付结果失败请稍后查看订单状态); } return ApiResponse.success(); } PostMapping(/{orderId}/cancel) public ApiResponseVoid userCancel(PathVariable Long orderId, RequestParam String reason) { MapString, Object extra new HashMap(); extra.put(cancelReason, reason); extra.put(cancelType, USER_REQUEST); OrderContext context new OrderContext(orderId, getCurrentUserId(), extra); boolean hasError stateMachineEngine.fire(OrderEvent.USER_CANCEL, context); if (hasError) { return ApiResponse.error(取消失败订单可能无法取消); } return ApiResponse.success(); } // 其他接口... }这个Demo展示了从API入口到状态机引擎的完整调用链。关键在于业务逻辑在callMethod中和状态流转规则在注解中被清晰地分离开。当需要增加一个新的状态比如“部分发货PART_SHIPPED”或一个新的转换事件比如“商家备注MERCHANT_NOTE”时你通常只需要修改状态枚举、事件枚举并在Transitions中添加一条新的规则即可Controller和Service层的改动非常小。在实际项目中我们还将这个模式应用到了工单系统、审核流程、商品上下架等场景效果显著。代码的可读性和可维护性得到了团队的一致好评。最初引入状态机时团队成员需要一点学习成本但一旦熟悉了这种声明式的思维开发效率和对复杂业务逻辑的把控能力都提升了不少。

相关新闻

微信小程序接入DeepSeek大模型实战:火山方舟API调用避坑指南

微信小程序接入DeepSeek大模型实战:火山方舟API调用避坑指南

微信小程序接入DeepSeek大模型实战:火山方舟API调用避坑指南 最近在帮几个创业团队做AI功能的小程序,发现大家对接入DeepSeek这类大模型特别感兴趣,但实际操作中踩的坑一个比一个深。特别是通过火山方舟平台调用时,从账号注册到代…

2026/7/4 3:27:49 阅读更多 →
Qwen3-0.6B-FP8入门教程:利用LaTeX编写技术报告时的内容辅助

Qwen3-0.6B-FP8入门教程:利用LaTeX编写技术报告时的内容辅助

Qwen3-0.6B-FP8入门教程:利用LaTeX编写技术报告时的内容辅助 写技术报告,尤其是用LaTeX排版的时候,你是不是也经常遇到这样的烦恼?对着空白的章节标题发呆,不知道从何下笔;好不容易写完一段技术描述&#…

2026/7/4 18:12:38 阅读更多 →
lwIP实战:TCP连接异常处理全解析(含RST、ERR_ABRT等错误码详解)

lwIP实战:TCP连接异常处理全解析(含RST、ERR_ABRT等错误码详解)

lwIP实战:TCP连接异常处理全解析(含RST、ERR_ABRT等错误码详解) 在嵌入式网络开发中,TCP连接的稳定性直接决定了产品的可靠性。尤其是在资源受限的IoT设备上,网络环境复杂多变,TCP连接随时可能因各种原因中…

2026/7/4 12:16:19 阅读更多 →

最新新闻

大模型数据准备实战:高信噪比语料构建七步法

大模型数据准备实战:高信噪比语料构建七步法

1. 为什么说“数据准备”才是训练定制大模型时最耗神、也最值钱的环节你有没有过这种体验:花两周时间调参、换架构、折腾分布式训练,最后发现模型在业务场景里答非所问,逻辑混乱,甚至编造事实?我带过三支不同行业的LLM…

2026/7/4 18:13:16 阅读更多 →
遗传算法优化大模型参数:自动化调参实战

遗传算法优化大模型参数:自动化调参实战

1. 项目概述:当遗传算法遇上大模型去年在优化一个客服对话系统时,我花了整整两周手工调整prompt模板和模型参数。直到某天深夜调试时突然想到:为什么不让算法自己寻找最优解?这就是GA(遗传算法)大模型组合的…

2026/7/4 18:11:15 阅读更多 →
机器学习新手必学的5大核心领域进阶地图

机器学习新手必学的5大核心领域进阶地图

1. 这不是一份“排行榜”,而是一张新手进阶地图:为什么初学者必须先搞懂这5个机器学习领域你点开这篇博客,大概率正站在机器学习的入口处——手头可能刚装好Python,跑通了第一个print("Hello, ML!"),但面对“…

2026/7/4 18:11:15 阅读更多 →
AI十年演进路径:从边缘智能到可信AI的工程化落地

AI十年演进路径:从边缘智能到可信AI的工程化落地

1. 这不是预言,而是技术演进路径的推演:我们真正该关注的AI十年图景你点开这篇文章,大概率不是为了听一句“AI会改变世界”——这句话从2012年AlexNet横空出世那天起,就被重复了上万遍。我做AI工程落地和系统架构设计整整11年&…

2026/7/4 18:07:14 阅读更多 →
Spring Boot + MyBatis + Vue 全栈毕设实战:从零到部署的完整项目开发指南

Spring Boot + MyBatis + Vue 全栈毕设实战:从零到部署的完整项目开发指南

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Claude 随心用,限时 5 折。 👉 点击领海量免费额度 计算机专业的学生在完成毕业设计或课程设计时,常常面临一个核心矛盾:既要理解项目背后的技术原理&#xff0…

2026/7/4 18:07:14 阅读更多 →
从零实现大语言模型:Happy-LLM开源教程带你手写LLaMA2

从零实现大语言模型:Happy-LLM开源教程带你手写LLaMA2

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Claude 随心用,限时 5 折。 👉 点击领海量免费额度 最近在社区里看到很多开发者,尤其是刚接触AI大模型的朋友,普遍反映一个痛点:大模型相关的资料要…

2026/7/4 18:05:14 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻