当前位置: 首页 > news >正文

cola架构:有限状态机(FSM)源码分析

目录

0. cola状态机简述

1.cola状态机使用实例

2.cola状态机源码解析

2.1 语义模型源码

2.1.1 Condition和Action接口

2.1.2 State

2.1.3 Transition接口

2.1.4 StateMachine接口

2.2 Builder模式

2.2.1 StateMachine Builder模式

2.2.2 ExternalTransitionBuilder---转移构造Builder模式

2.2.2.1 链式调用顺序保障

2.2.2.2 链式调用具体实现


0. cola状态机简述

cola状态机采用无状态设计,不存储中间状态,重点关注状态之间的转移,这样的设计使得cola更加的简单、轻量、易于上手,在高并发多线程场景下应用单例模式可以实现更高的性能;

cola状态机框架根据实际使用场景进行抽象领域建模,抽象出如下的语义模型:

  • State:状态
  • Event:事件,状态由事件触发,引起变化
  • Transition:流转,表示从一个状态到另一个状态
  • External Transition:外部流转,两个不同状态之间的流转
  • Internal Transition:内部流转,同一个状态之间的流转
  • Condition:条件,表示是否允许到达某个状态
  • Action:动作,到达某个状态之后,可以做什么
  • StateMachine:状态机

上述语义模型之间的实体关系模型如下:

  •  一个状态机实例(StateMachine)可以包含多个状态(State)
  • 每个状态可以包含多个状态转移(Transition)
  • 每个状态转移(Transition)包含初始状态、目标状态、驱动事件(Event),以及状态转移条件(Condition)和命中转移条件之后的动作(Action)

理清上述语义模型的概念后,下面通过源码分析来探究cola状态机的内部实现; 

1.cola状态机使用实例

在深入源码解析之前,首先通过一个具体的状态机实例了解状态机的使用方法,后面再逐步探究其执行过程:

public class StateMachineTest {static String MACHINE_ID = "TestStateMachine";static enum States {STATE1,STATE2,STATE3,STATE4}static enum Events {EVENT1,EVENT2,EVENT3,EVENT4,INTERNAL_EVENT}static class Context {String operator = "frank";String entityId = "123465";}@Testpublic void testExternalNormal() {StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();builder.externalTransition().from(States.STATE1).to(States.STATE2).on(Events.EVENT1).when(checkCondition()).perform(doAction());StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());Assert.assertEquals(States.STATE2, target);}private Condition<Context> checkCondition() {return new Condition<Context>() {@Overridepublic boolean isSatisfied(Context context) {System.out.println("Check condition : " + context);return true;}};}private Action<States, Events, Context> doAction() {return (from, to, event, ctx) -> {System.out.println(ctx.operator + " is operating " + ctx.entityId + " from:" + from + " to:" + to + " on:" + event);};}
}

 上面首先通过Builder模式创建StateMachine实例,其中定义了STATE1到STATE2的转移,然后通过fireEvent触发状态转移,在满足Condition时,执行了具体的动作Action;

2.cola状态机源码解析

cola状态机的核心逻辑都是围绕着上述的语义模型展开的,下面首先看下语义模型的源码;

2.1 语义模型源码

2.1.1 Condition和Action接口

 Condition接口:

/*** Condition** @author Frank Zhang* @date 2020-02-07 2:50 PM*/
public interface Condition<C> {/*** @param context context object* @return whether the context satisfied current condition*/boolean isSatisfied(C context);default String name(){return this.getClass().getSimpleName();}
}

Action接口: 

/*** Generic strategy interface used by a state machine to respond* events by executing an {@code Action} with a {@link StateContext}.** @author Frank Zhang* @date 2020-02-07 2:51 PM*/
public interface Action<S, E, C> {//    /**
//     * Execute action with a {@link StateContext}.
//     *
//     * @param context the state context
//     */
//    void execute(StateContext<S, E> context);public void execute(S from, S to, E event, C context);}

Condition接口定义了isSatisfied方法评估是否命中条件;

Action接口通过方法execute执行具体的动作;

2.1.2 State

State接口定义如下:

/*** State** @param <S> the type of state* @param <E> the type of event** @author Frank Zhang* @date 2020-02-07 2:12 PM*/
public interface State<S,E,C> extends Visitable{/*** Gets the state identifier.** @return the state identifiers*/S getId();/*** Add transition to the state* @param event the event of the Transition* @param target the target of the transition* @return*/Transition<S,E,C> addTransition(E event, State<S, E, C> target, TransitionType transitionType);List<Transition<S,E,C>> getEventTransitions(E event);Collection<Transition<S,E,C>> getAllTransitions();}

State接口实现:

/*** StateImpl** @author Frank Zhang* @date 2020-02-07 11:19 PM*/
public class StateImpl<S,E,C> implements State<S,E,C> {protected final S stateId;private EventTransitions eventTransitions = new EventTransitions();StateImpl(S stateId){this.stateId = stateId;}@Overridepublic Transition<S, E, C> addTransition(E event, State<S,E,C> target, TransitionType transitionType) {Transition<S, E, C> newTransition = new TransitionImpl<>();newTransition.setSource(this);newTransition.setTarget(target);newTransition.setEvent(event);newTransition.setType(transitionType);Debugger.debug("Begin to add new transition: "+ newTransition);eventTransitions.put(event, newTransition);return newTransition;}@Overridepublic List<Transition<S, E, C>> getEventTransitions(E event) {return eventTransitions.get(event);}@Overridepublic Collection<Transition<S, E, C>> getAllTransitions() {return eventTransitions.allTransitions();}@Overridepublic S getId() {return stateId;}@Overridepublic String accept(Visitor visitor) {String entry = visitor.visitOnEntry(this);String exit = visitor.visitOnExit(this);return entry + exit;}@Overridepublic boolean equals(Object anObject){if(anObject instanceof State){State other = (State)anObject;if(this.stateId.equals(other.getId()))return true;}return false;}@Overridepublic String toString(){return stateId.toString();}
}

可以看出State聚合了多个状态转移(Transition) ,

状态转移是通过EventTransitions类来进行封装管理的,源码如下:

/*** EventTransitions** 同一个Event可以触发多个Transitions,https://github.com/alibaba/COLA/pull/158** @author Frank Zhang* @date 2021-05-28 5:17 PM*/
public class EventTransitions<S,E,C> {private HashMap<E, List<Transition<S,E,C>>> eventTransitions;public EventTransitions(){eventTransitions = new HashMap<>();}public void put(E event, Transition<S, E, C> transition){if(eventTransitions.get(event) == null){List<Transition<S,E,C>> transitions = new ArrayList<>();transitions.add(transition);eventTransitions.put(event, transitions);}else{List existingTransitions = eventTransitions.get(event);verify(existingTransitions, transition);existingTransitions.add(transition);}}/*** Per one source and target state, there is only one transition is allowed* @param existingTransitions* @param newTransition*/private void verify(List<Transition<S,E,C>> existingTransitions, Transition<S,E,C> newTransition) {for (Transition transition : existingTransitions) {if (transition.equals(newTransition)) {throw new StateMachineException(transition + " already Exist, you can not add another one");}}}public List<Transition<S,E,C>> get(E event){return eventTransitions.get(event);}public List<Transition<S,E,C>> allTransitions(){List<Transition<S,E,C>> allTransitions = new ArrayList<>();for(List<Transition<S,E,C>> transitions : eventTransitions.values()){allTransitions.addAll(transitions);}return allTransitions;}
}

2.1.3 Transition接口

接口定义如下:

/*** {@code Transition} is something what a state machine associates with a state* changes.** @author Frank Zhang** @param <S> the type of state* @param <E> the type of event* @param <C> the type of user defined context, which is used to hold application data** @date 2020-02-07 2:20 PM*/
public interface Transition<S, E, C>{/*** Gets the source state of this transition.** @return the source state*/State<S,E,C> getSource();void setSource(State<S, E, C> state);E getEvent();void setEvent(E event);void setType(TransitionType type);/*** Gets the target state of this transition.** @return the target state*/State<S,E,C> getTarget();void setTarget(State<S, E, C> state);/*** Gets the guard of this transition.** @return the guard*/Condition<C> getCondition();void setCondition(Condition<C> condition);Action<S,E,C> getAction();void setAction(Action<S, E, C> action);/*** Do transition from source state to target state.** @return the target state*/State<S, E, C> transit(C ctx, boolean checkCondition);/*** Verify transition correctness*/void verify();
}

具体实现如下:

/*** TransitionImpl。** This should be designed to be immutable, so that there is no thread-safe risk** @author Frank Zhang* @date 2020-02-07 10:32 PM*/
public class TransitionImpl<S,E,C> implements Transition<S,E,C> {private State<S, E, C> source;private State<S, E, C> target;private E event;private Condition<C> condition;private Action<S,E,C> action;private TransitionType type = TransitionType.EXTERNAL;@Overridepublic State<S, E, C> getSource() {return source;}@Overridepublic void setSource(State<S, E, C> state) {this.source = state;}@Overridepublic E getEvent() {return this.event;}@Overridepublic void setEvent(E event) {this.event = event;}@Overridepublic void setType(TransitionType type) {this.type = type;}@Overridepublic State<S, E, C> getTarget() {return this.target;}@Overridepublic void setTarget(State<S, E, C> target) {this.target = target;}@Overridepublic Condition<C> getCondition() {return this.condition;}@Overridepublic void setCondition(Condition<C> condition) {this.condition = condition;}@Overridepublic Action<S, E, C> getAction() {return this.action;}@Overridepublic void setAction(Action<S, E, C> action) {this.action = action;}@Overridepublic State<S, E, C> transit(C ctx, boolean checkCondition) {Debugger.debug("Do transition: "+this);this.verify();if (!checkCondition || condition == null || condition.isSatisfied(ctx)) {if(action != null){action.execute(source.getId(), target.getId(), event, ctx);}return target;}Debugger.debug("Condition is not satisfied, stay at the "+source+" state ");return source;}@Overridepublic final String toString() {return source + "-[" + event.toString() +", "+type+"]->" + target;}@Overridepublic boolean equals(Object anObject){if(anObject instanceof Transition){Transition other = (Transition)anObject;if(this.event.equals(other.getEvent())&& this.source.equals(other.getSource())&& this.target.equals(other.getTarget())){return true;}}return false;}@Overridepublic void verify() {if(type== TransitionType.INTERNAL && source != target) {throw new StateMachineException(String.format("Internal transition source state '%s' " +"and target state '%s' must be same.", source, target));}}
}

可以看出,Transition聚合了源State、目标State、Event、Condition、Action;

在上述方法transit中,Transition在满足条件Condition的条件下,执行了具体动作Action,并且返回目标State;

2.1.4 StateMachine接口

/*** StateMachine** @author Frank Zhang** @param <S> the type of state* @param <E> the type of event* @param <C> the user defined context* @date 2020-02-07 2:13 PM*/
public interface StateMachine<S, E, C> extends Visitable{/*** Verify if an event {@code E} can be fired from current state {@code S}* @param sourceStateId* @param event* @return*/boolean verify(S sourceStateId,E event);/*** Send an event {@code E} to the state machine.** @param sourceState the source state* @param event the event to send* @param ctx the user defined context* @return the target state*/S fireEvent(S sourceState, E event, C ctx);/*** MachineId is the identifier for a State Machine* @return*/String getMachineId();/*** Use visitor pattern to display the structure of the state machine*/void showStateMachine();String generatePlantUML();
}

状态机具体实现:

/*** For performance consideration,* The state machine is made "stateless" on purpose.* Once it's built, it can be shared by multi-thread* <p>* One side effect is since the state machine is stateless, we can not get current state from State Machine.** @author Frank Zhang* @date 2020-02-07 5:40 PM*/
public class StateMachineImpl<S, E, C> implements StateMachine<S, E, C> {private String machineId;private final Map<S, State<S, E, C>> stateMap;private boolean ready;private FailCallback<S, E, C> failCallback;public StateMachineImpl(Map<S, State<S, E, C>> stateMap) {this.stateMap = stateMap;}@Overridepublic boolean verify(S sourceStateId, E event) {isReady();State sourceState = getState(sourceStateId);List<Transition<S, E, C>> transitions = sourceState.getEventTransitions(event);return transitions != null && transitions.size() != 0;}@Overridepublic S fireEvent(S sourceStateId, E event, C ctx) {isReady();Transition<S, E, C> transition = routeTransition(sourceStateId, event, ctx);if (transition == null) {Debugger.debug("There is no Transition for " + event);failCallback.onFail(sourceStateId, event, ctx);return sourceStateId;}return transition.transit(ctx, false).getId();}private Transition<S, E, C> routeTransition(S sourceStateId, E event, C ctx) {State sourceState = getState(sourceStateId);List<Transition<S, E, C>> transitions = sourceState.getEventTransitions(event);if (transitions == null || transitions.size() == 0) {return null;}Transition<S, E, C> transit = null;for (Transition<S, E, C> transition : transitions) {if (transition.getCondition() == null) {transit = transition;} else if (transition.getCondition().isSatisfied(ctx)) {transit = transition;break;}}return transit;}private State getState(S currentStateId) {State state = StateHelper.getState(stateMap, currentStateId);if (state == null) {showStateMachine();throw new StateMachineException(currentStateId + " is not found, please check state machine");}return state;}private void isReady() {if (!ready) {throw new StateMachineException("State machine is not built yet, can not work");}}@Overridepublic String accept(Visitor visitor) {StringBuilder sb = new StringBuilder();sb.append(visitor.visitOnEntry(this));for (State state : stateMap.values()) {sb.append(state.accept(visitor));}sb.append(visitor.visitOnExit(this));return sb.toString();}@Overridepublic void showStateMachine() {SysOutVisitor sysOutVisitor = new SysOutVisitor();accept(sysOutVisitor);}@Overridepublic String generatePlantUML() {PlantUMLVisitor plantUMLVisitor = new PlantUMLVisitor();return accept(plantUMLVisitor);}@Overridepublic String getMachineId() {return machineId;}public void setMachineId(String machineId) {this.machineId = machineId;}public void setReady(boolean ready) {this.ready = ready;}public void setFailCallback(FailCallback<S, E, C> failCallback) {this.failCallback = failCallback;}
}

状态机方法fireEvent的执行逻辑如下:

  1. 通过routeTransition方法,获取目标转移Transition(只会返回一个)

  2. 执行目标转移Transition的转移方法transit,并返回目标State

在方法routeTransition中:

  1. 首先根据源State和Event获取关联的转移列表
  2. 遍历转移列表,返回命中转移条件Condition的转移

2.2 Builder模式

上述cola状态机语义模型的源码就解析完成了,在实际使用时,需要将语义模型进行组合构造编排,这里是通过Builder模式来完成的,下面进行展开说明;

2.2.1 StateMachine Builder模式

StateMachineBuilder顶层接口定义如下:

/*** StateMachineBuilder** @author Frank Zhang* @date 2020-02-07 5:32 PM*/
public interface StateMachineBuilder<S, E, C> {/*** Builder for one transition** @return External transition builder*/ExternalTransitionBuilder<S, E, C> externalTransition();/*** Builder for multiple transitions** @return External transition builder*/ExternalTransitionsBuilder<S, E, C> externalTransitions();/*** Start to build internal transition** @return Internal transition builder*/InternalTransitionBuilder<S, E, C> internalTransition();/*** set up fail callback, default do nothing {@code NumbFailCallbackImpl}** @param callback*/void setFailCallback(FailCallback<S, E, C> callback);StateMachine<S, E, C> build(String machineId);}

具体实现如下:

/*** StateMachineBuilderImpl** @author Frank Zhang* @date 2020-02-07 9:40 PM*/
public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {/*** StateMap is the same with stateMachine, as the core of state machine is holding reference to states.*/private final Map<S, State<S, E, C>> stateMap = new ConcurrentHashMap<>();private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);private FailCallback<S, E, C> failCallback = new NumbFailCallback<>();@Overridepublic ExternalTransitionBuilder<S, E, C> externalTransition() {return new TransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL);}@Overridepublic ExternalTransitionsBuilder<S, E, C> externalTransitions() {return new TransitionsBuilderImpl<>(stateMap, TransitionType.EXTERNAL);}@Overridepublic InternalTransitionBuilder<S, E, C> internalTransition() {return new TransitionBuilderImpl<>(stateMap, TransitionType.INTERNAL);}@Overridepublic void setFailCallback(FailCallback<S, E, C> callback) {this.failCallback = callback;}@Overridepublic StateMachine<S, E, C> build(String machineId) {stateMachine.setMachineId(machineId);stateMachine.setReady(true);stateMachine.setFailCallback(failCallback);StateMachineFactory.register(stateMachine);return stateMachine;}}

StateMachineBuilder定义中:

  • 通过build方法完成状态机实例的创建 
  • 通过externalTransition方法完成外部转移的构造
  • 通过internalTransition方法完成内部转移的构造

在build方法中,构造StateMachine实例,并注册到StateMachineFactory中 ,然后返回StateMachine实例,完成构建;

对于转移Transition的构造,下面以externalTransition返回的ExternalTransitionBuilder为例进行具体分析,内部转移同理不再展开;

2.2.2 ExternalTransitionBuilder---转移构造Builder模式

2.2.2.1 链式调用顺序保障

在上述状态机使用实例中,是通过链式编程来完成外部转移构造的,如下:

这里通过接口约束,限制了:

  1. externalTransition方法之后只能调用from方法
  2. from方法之后只能调用to方法等调用顺序
  3. ......依次类推

内部原理是:

  1. externalTransition方法返回了ExternalTransitionBuilder接口,而ExternalTransitionBuilder接口只定义了from方法,并且返回From接口
  2. From接口中又只定义了to方法,并且返回To接口
  3. ......依次类推

相关接口定义说明如下:

/*** ExternalTransitionBuilder** @author Frank Zhang* @date 2020-02-07 6:11 PM*/
public interface ExternalTransitionBuilder<S, E, C> {/*** Build transition source state.* @param stateId id of state* @return from clause builder*/From<S, E, C> from(S stateId);}
/*** From** @author Frank Zhang* @date 2020-02-07 6:13 PM*/
public interface From<S, E, C> {/*** Build transition target state and return to clause builder* @param stateId id of state* @return To clause builder*/To<S, E, C> to(S stateId);}
/*** To** @author Frank Zhang* @date 2020-02-07 6:14 PM*/
public interface To<S, E, C> {/*** Build transition event* @param event transition event* @return On clause builder*/On<S, E, C> on(E event);
}
public interface On<S, E, C> extends When<S, E, C>{/*** Add condition for the transition* @param condition transition condition* @return When clause builder*/When<S, E, C> when(Condition<C> condition);
}
public interface When<S, E, C>{/*** Define action to be performed during transition** @param action performed action*/void perform(Action<S, E, C> action);
}

 通过上述严格的顺序调用,保证了Transition构造的正确性和易读性;

2.2.2.2 链式调用具体实现

下面看一下上述链式调用相关接口的具体实现逻辑:

ExternalTransitionBuilder的具体实现类如下:

/*** TransitionBuilderImpl** @author Frank Zhang* @date 2020-02-07 10:20 PM*/
class TransitionBuilderImpl<S,E,C> extends AbstractTransitionBuilder<S,E,C> implements ExternalTransitionBuilder<S,E,C>, InternalTransitionBuilder<S,E,C> {private State<S, E, C> source;private Transition<S, E, C> transition;public TransitionBuilderImpl(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {super(stateMap, transitionType);}@Overridepublic From<S, E, C> from(S stateId) {source = StateHelper.getState(stateMap, stateId);return this;}@Overridepublic To<S, E, C> within(S stateId) {source = target = StateHelper.getState(stateMap, stateId);return this;}@Overridepublic When<S, E, C> when(Condition<C> condition) {transition.setCondition(condition);return this;}@Overridepublic On<S, E, C> on(E event) {transition = source.addTransition(event, target, transitionType);return this;}@Overridepublic void perform(Action<S, E, C> action) {transition.setAction(action);}}

其中AbstractTransitionBuilder抽象类具体实现如下,实现了接口From、To、On:

 abstract class AbstractTransitionBuilder<S,E,C> implements  From<S,E,C>,On<S,E,C>,To<S,E,C>{final Map<S, State<S, E, C>> stateMap;protected State<S, E, C> target;final TransitionType transitionType;public AbstractTransitionBuilder(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {this.stateMap = stateMap;this.transitionType = transitionType;}@Overridepublic To<S, E, C> to(S stateId) {target = StateHelper.getState(stateMap, stateId);return this;}
}

 上述用到的StateHelper帮助类说明如下:

public class StateHelper {public static <S, E, C> State<S, E, C> getState(Map<S, State<S, E, C>> stateMap, S stateId){State<S, E, C> state = stateMap.get(stateId);if (state == null) {state = new StateImpl<>(stateId);stateMap.put(stateId, state);}return state;}
}

下面对链式调用执行过程具体说明如下: 

  1. from方法实现中,通过StateHelper帮助类完成源State的创建;
  2.  to方法实现中,同样通过StateHelper帮助类完成目标State的创建;
  3. on方法实现中,在源State中添加新的转移;
  4. when方法实现中,对构造的转移添加转移条件Condition;
  5. perform方法实现中,对构造的转移添加具体动作Action;

最后通过状态机build方法,完成状态机的构造并注入所有State列表;

至此,状态机实例、状态机包含的状态、状态关联的所有转移都构造完毕,后续就可以通过状态机的触发方法fireEvent完成状态转移了。

相关文章:

cola架构:有限状态机(FSM)源码分析

目录 0. cola状态机简述 1.cola状态机使用实例 2.cola状态机源码解析 2.1 语义模型源码 2.1.1 Condition和Action接口 2.1.2 State 2.1.3 Transition接口 2.1.4 StateMachine接口 2.2 Builder模式 2.2.1 StateMachine Builder模式 2.2.2 ExternalTransitionBuilder-…...

通信仿真软件SystemView安装教程(超详细)

介绍 system view是一种电子仿真工具。它是一个信号级的系统仿真软件&#xff0c;主要用于电路与通信系统的设计和仿真&#xff0c;是一个强有力的动态系统分析工具&#xff0c;能满足从数字信号处理&#xff0c;滤波器设计&#xff0c;直到复杂的通信系统等不同层次的设计&am…...

Go学习第八章——面向“对象”编程(入门——结构体与方法)

Go面向“对象”编程&#xff08;入门——结构体与方法&#xff09; 1 结构体1.1 快速入门1.2 内存解析1.3 创建结构体四种方法1.4 注意事项和使用细节 2 方法2.1 方法的声明和调用2.2 快速入门案例2.3 调用机制和传参原理2.4 注意事项和细节2.5 方法和函数区别 3 工厂模式 Gola…...

「滚雪球学Java」:方法函数(章节汇总)

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…...

数据分析必备原理思路(二)

文章目录 三、主流的数据分析方法与框架使用1. 五个数据分析领域关键的理论基础&#xff08;1&#xff09;大数定律&#xff08;2&#xff09;罗卡定律&#xff08;3&#xff09;幸存者偏差&#xff08;4&#xff09;辛普森悖论&#xff08;5&#xff09;帕累托最优&#xff08…...

分布式ID系统设计(1)

分布式ID系统设计(1) 在分布式服务中&#xff0c;需要对data和message进行唯一标识。 比如订单、支付等。然后在数据库分库分表之后也需要一个唯一id来表示。 基于DB的自增就肯定不能满足了。这个时候能够生成一个Global的唯一ID的服务就很有必要我们姑且把它叫做id-server 。…...

机器学习(python)笔记整理

目录 一、数据预处理&#xff1a; 1. 缺失值处理&#xff1a; 2. 重复值处理&#xff1a; 3. 数据类型&#xff1a; 二、特征工程: 1. 规范化&#xff1a; 2. 归一化&#xff1a; 3. 标准化(方差)&#xff1a; 三、训练模型&#xff1a; 如何计算精确度&#xff0c;召…...

微客云霸王餐系统 1.0 : 全面孵化+高额返佣

1、业务简介。业务模式是消费者以5-10元吃到原价15-25元的外卖&#xff0c;底层逻辑是帮外卖商家做推广&#xff0c;解决新店基础销量、老店增加单量、品牌打万单店的需求。 因为外卖店的平均生命周期只有6个月&#xff0c;不断有新店愿意送霸王餐。部分老店也愿意做活动&…...

极智开发 | Hello world for Manim

欢迎关注我的公众号 [极智视界],获取我的更多经验分享 大家好,我是极智视界,本文分享一下 Hello world for Manim。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq Manim 是什么呢?Manim 是一个用于创…...

【云上探索实验室-码上学堂】免费学习领好礼!

走过路过&#xff0c;不要错过&#xff01;上云AI三步走&#xff0c;学着课程奖品有&#xff01; 亚马逊云科技又放福利了&#xff0c;为了让同学们更快入手Amazon CodeWhisperer&#xff0c;官方推出《云上探索实验室-码上学堂》活动&#xff0c;作为一名Amazon CodeWhisperer…...

Flutter最全面试题大全

在理解这些问题之前&#xff0c;建议看一下Flutter架构原理&#xff0c;如下链接&#xff1a; https://blog.csdn.net/wang_yong_hui_1234/article/details/130427887?spm1001.2014.3001.5501 目录 一. 有个Text节点&#xff0c;由于文字内容过多&#xff0c;发生了溢出错误&…...

Linux---(四)权限

文章目录 一、shell命令及运行原理1.什么是操作系统&#xff1f;2.外壳程序3.用户为什么不直接访问操作系统内核?4.操作系统内核为什么不直接把结果显示出来&#xff1f;非要加外壳程序&#xff1f;5.shell理解重点总结&#xff08;1&#xff09;shell是什么&#xff1f;&…...

财务RPA机器人真的能提高效率吗?

财务部门作为一个公司的管理职能部门承担着一个公司在商业活动中各个方面的重要职责。理论上来说&#xff0c;一个公司的财务部门的实际工作包含但不限于对企业的盈亏情况进行评估、对风险进行预测、通过数据分析把握好公司的财务状况、税务管理等。 然而&#xff0c;实际上在…...

国产信号发生器 1442/1442A射频信号发生器

信号发生器 1442/A射频信号发生器 1442系列射频信号发生器是一款针对通信、电子等射频应用而设计开发的产品。覆盖了所有的常用射频频段。它采用模块化结构设计&#xff0c;全中文界面、大屏幕菜单控制&#xff0c;其输出信号相位噪声极低&#xff0c;频率分辨率和准确度高&am…...

Kafka与Spark案例实践

1.概述 Kafka系统的灵活多变&#xff0c;让它拥有丰富的拓展性&#xff0c;可以与第三方套件很方便的对接。例如&#xff0c;实时计算引擎Spark。接下来通过一个完整案例&#xff0c;运用Kafka和Spark来合理完成。 2.内容 2.1 初始Spark 在大数据应用场景中&#xff0c;面对…...

山西电力市场日前价格预测【2023-10-27】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-27&#xff09;山西电力市场全天平均日前电价为347.06元/MWh。其中&#xff0c;最高日前电价为618.09元/MWh&#xff0c;预计出现在18: 15。最低日前电价为163.49元/MWh&#xff0c;预计…...

centos7安装redis(包含各种报错)

本文主要介绍如果在Centos7下安装Redis。 1.安装依赖 redis是由C语言开发&#xff0c;因此安装之前必须要确保服务器已经安装了gcc&#xff0c;可以通过如下命令查看机器是否安装&#xff1a; gcc -v如果没有安装则通过以下命令安装&#xff1a; yum install -y gcc2.下载r…...

使用GoQuery实现头条新闻采集

概述 在本文中&#xff0c;我们将介绍如何使用Go语言和GoQuery库实现一个简单的爬虫程序&#xff0c;用于抓取头条新闻的网页内容。我们还将使用爬虫代理服务&#xff0c;提高爬虫程序的性能和安全性。我们将使用多线程技术&#xff0c;提高采集效率。最后&#xff0c;我们将展…...

“一带一路”十周年:用英语讲好中华传统故事

图为周明霏小选手 2023年是“一带一路”倡议提出十周年。十年来&#xff0c;中国的“友谊圈”已经扩展到亚洲、非洲、欧洲、大洋洲和拉丁美洲&#xff0c;这一倡议已经成为提升我国文化软实力、传播中华传统文化的重要策略和途径之一。在这个广阔的交流平台上&#xff0c;使用…...

机器视觉兄弟们还有几个月就拿到年终奖了,但我想跑路了

大聪明的我一般会把年终奖拿了&#xff0c;再走。听说有人还没有年终奖&#xff0c;太伤心了&#xff0c;赶紧跑吧。注意&#xff0c;机器视觉小白不要轻举妄动。 今年太难了&#xff0c;真的是让人很难过&#xff0c;很不爽&#xff0c;很不舒服。 公司难&#xff0c;机器视…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...