【Alibaba Cola 状态机】重点解析以及实践案例

news2024/11/13 18:14:57

【Alibaba Cola 状态机】重点解析以及实践案例

1. 状态模式

状态模式是一种行为型设计模式,允许对象在内部状态改变时改变其行为,简单地讲就是,一个拥有状态的context对象,在不同状态下,其行为会发生改变。看起来是改变了对象各个接口方法的实现一样。

模式中包含角色:

  1. 上下文
  2. 抽象状态
  3. 具体状态

优点包括解耦客户端和状态对象,可扩展性强,避免大量条件语句。

缺点是可能增加系统类的数量和复杂性。

适用场景如自动售货机的状态转换、线程状态管理等。代码示例展示了自动售卖机如何利用状态模式实现不同状态的切换。

推荐文章:23种设计模式之状态模式(State Pattern)_状态模式哪种状态切换好-CSDN博客

2. 状态机

状态机(Finite State Machine,简称FSM)是一个数学模型,用于描述对象在其生命周期中可能的状态以及状态之间的转换。

它由一组状态、一组事件、一组转换规则和一组动作组成,能够清晰地表示和管理对象的行为和状态变化。

在状态机中,“有限”指的是状态和事件都是有限的,即存在一个有限的状态集和事件集。这使得状态机具有明确的、可控的行为模式,便于分析和实现。

在这里插入图片描述

状态机的组成元素:状态、事件、转换、动作

状态(State): 表示对象的当前情况或条件。状态机中的状态是有限的,例如:待机、运行、暂停、停止等。

事件(Event): 触发状态转换的输入或信号。事件可以是外部输入、内部触发或其他系统生成的信号。

转换(Transition): 定义了状态之间的切换规则。当特定的事件发生时,状态机会根据转换规则从一个状态转换到另一个状态。

动作(Action): 在状态转换过程中执行的操作或任务。动作可以是状态进入前执行的预处理、状态转换时执行的中间操作或状态退出后执行的清理工作。

状态机的优缺点

优点:

  1. 逻辑清晰:状态机将复杂的系统行为分解为简单的状态和状态之间的转换,使系统逻辑更加清晰和易于理解。
  2. 可维护性高:由于状态机的逻辑是模块化的,任何状态的修改或扩展只需在该状态的实现部分进行,不会影响其他部分,增强了代码的可维护性。
  3. 可扩展性强:新的状态和转换可以方便地添加到现有的状态机中,使系统具有良好的扩展性。

缺点:状态爆炸,对于复杂系统,状态和转换的数量可能非常庞大,导致状态机图变得复杂难以管理,这被称为“状态爆炸”问题。

3. Alibaba Cola 状态机

实现一个状态机引擎,教你看清DSL的本质_状态机 dsl-CSDN博客

相比Spring statemachine状态机等的复杂,功能多;

但是我们 实际业务员 需要常用的功能,简单使用,所以这类就显得不简洁;再看cola-statemachine相比就是小巧、无状态、简单、轻量、性能极高的状态机DSL实现,解决业务中的状态流转问题。

如果是实现业务的话,阿里状态机是不二之选,更加适合我们日常开发!KISS(Keep It Simple and Stupid)

而如果是比较复杂的业务或者组件开发,这个状态机可能也能 妙用进行应对,面对不了可能就得用 Spring statemachine 有状态的状态机,或者对阿里状态机进行 二次开发

开源者的初心就是,让开发更舒适,结合实际需求抽离出必要部分,而不是严格按照传统状态模式,传统状态机

无状态状态机–cola stateMachine-CSDN博客

状态机本身没有状态,而是提供一个状态机单例,通过输入参数 “起始状态”、“事件”、“上下文”,这些参数足以确定一次状态轮转,期间通过 condition 和 action 后,返回“最终状态”,若轮转失败,则返回原状态;

由于没有状态,所以调用者使用这个状态机都是互不干扰的,通过输入决定输出(一次轮转),做到一个实例服务多个调用者

  • 这里的上下文,就是过程中的通行数据罢了
  • 你也可以在上下文里面去设置状态,像状态模式那样,当其实在使用状态机的时候,状态机就相当于状态模式的上下文,这样看,发挥状态机的优势,就没必要设置了。

采用了无状态设计之后,我们就可以使用一个状态机 Instance 来响应所有的请求了,性能会大大的提升

原本的状态机是有状态的,状态机当前的状态决定了其行为,这样导致每次请求,都是申请一个新的状态机去维护状态的轮转,轮转的过程,状态机的状态也在时刻发生变化;

当阿里状态机理念是状态机每次进行一次轮转就行了,哪怕需要连续的过程性的轮转,多次请求状态机即可(一个轮转的 action 嵌套执行外部轮转的事件,返回的最终状态可能与实际不符,最好嵌套执行内部轮转而不是外部轮转;或者等轮转结束,再按照这次请求的响应进行下次轮转)

有状态的状态机就相当于可以本地运行的应用,不同客户之间是隔离的,状态在应用内部轮转

无状态的状态机就相当于 web 应用,所有客户访问同一个 web,请求一次,执行一次特定的轮转,响应结果

4. Alibaba Cola 状态机使用

源码其实不难,大部分还是容易看懂的,如果出现问题按照思路可以调试去查查,这里就是我总结的用法。

在这里插入图片描述

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

4.1 StateMachineUtil(核心方法封装)

@Slf4j
public class StateMachineUtil {

    public static <S, E, C> StateMachine<S, E, C> getMachine(String machineId) {
        return StateMachineFactory.get(machineId);
    }

    public static void showMachine(String machineId) {
        getMachine(machineId).showStateMachine();
    }

    public static String generatePlantUML(String machineId) {
        return getMachine(machineId).generatePlantUML();
    }

    public static <S, E, C> void printMachine(String machineId) {
        StateMachine<S, E, C> stateMachine = getMachine(machineId);
        stateMachine.showStateMachine();
        System.out.println(stateMachine.generatePlantUML());
    }

    public static <S, E, C> S fireEvent(String machineId, S state, E event, C context) {
        return (S) getMachine(machineId).fireEvent(state, event, context);
    }
    
}
  1. 通过 machineId 获得状态机实例:StateMachineFactory.get(machineId)
  2. 打印状态机(注意重写 toString 方法到想要的效果)
  3. 执行状态机(machineId,起始状态,事件,上下文)(返回最终状态)

4.2 状态机准备信息

根据状态机轮转所需的信息,也就是准备信息,在状态机构建过程中需要用到;

我将其抽象成接口,有两个:外部流转助手、内部流转助手

  • 状态的事件会让状态变成别的状态,还是不变就是这里外和内
public interface StateExternalTransitionHelper<S, E, C> {

    List<S> getFromState();

    S getToState(S from) throws GlobalServiceException;

    E getOnEvent();

    Condition<C> getWhenCondition();

    Action<S, E, C> getPerformAction();

}

from 为 list 是因为有时候同一事件,多个状态都可以执行,其中 getToState 通过 from 得知 to,并允许抛出异常;

public interface StateInternalTransitionHelper<S, E, C> {

    List<S> getWithinList();

    E getOnEvent();

    Condition<C> getWhenCondition();

    Action<S, E, C> getPerformAction();
}

4.3 通过准备信息构造状态机

外部流转助手、内部流转助手 的实现类,就蕴含了准备信息,将这些在状态机构造过程中应用:

private static <S, E, C> void builderAssign(StateMachineBuilder<S, E, C> builder,
                                            StateExternalTransitionHelper<S, E, C> helper) {
    List<S> fromStateList = helper.getFromState();
    if(!CollectionUtil.isEmpty(fromStateList)) {
        E onEvent = helper.getOnEvent();
        Condition<C> whenCondition = helper.getWhenCondition();
        Action<S, E, C> performAction = helper.getPerformAction();
        fromStateList.forEach(fromState -> {
            try {
                S toState = helper.getToState(fromState); // 若抛异常就忽略这一个,构造下一个状态轮转
                // 不保证每个 toState 相等的情况下,不用 externalTransitions 与 fromAmong
                builder.externalTransition()
                    .from(fromState)
                    .to(toState)
                    .on(onEvent)
                    .when(whenCondition)
                    .perform(performAction);
            } catch (GlobalServiceException e) {
                log.warn(e.getMessage());
            }
        });
    }
}

private static <S, E, C> void builderAssign(StateMachineBuilder<S, E, C> builder,
                                            StateInternalTransitionHelper<S, E, C> helper) {
    List<S> withinList = helper.getWithinList();
    if(!CollectionUtil.isEmpty(withinList)) {
        E onEvent = helper.getOnEvent();
        Condition<C> whenCondition = helper.getWhenCondition();
        Action<S, E, C> performAction = helper.getPerformAction();
        withinList.forEach(within -> {
            builder.internalTransition()
                .within(within)
                .on(onEvent)
                .when(whenCondition)
                .perform(performAction);
        });
    }
}

public static <S, E, C> void buildMachine(String machineId,
                                          List<? extends StateExternalTransitionHelper<S, E, C>> externalHelpers,
                                          List<? extends StateInternalTransitionHelper<S, E, C>> internalHelpers) {
    // 创建一个 builder
    StateMachineBuilder<S, E, C> builder = StateMachineBuilderFactory.create();
    // 添加轮转
    externalHelpers.forEach(helper -> {
        builderAssign(builder, helper);
    });
    internalHelpers.forEach(helper -> {
        builderAssign(builder, helper);
    });
    // 创建状态机
    builder.build(machineId);
}

4.4 示例(简历状态轮转)

不要直接实现那两个接口,要先用模块内部的接口继承,这样注入 Spring 容器中的 bean,获取的时候指定我们自己的接口类型,防止获得别的模块的 helper bean

public interface ResumeStateExternalTransitionHelper extends StateExternalTransitionHelper<ResumeStatus, ResumeEvent, ResumeContext> {

}
public interface ResumeStateInternalTransitionHelper extends StateInternalTransitionHelper<ResumeStatus, ResumeEvent, ResumeContext> {

}

在这里插入图片描述

上下文:

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class ResumeContext {

    private Long managerId;

    private StuResume resume;

    ResumeExecuteDTO executeDTO;

    public void log(ResumeStatus from, ResumeStatus to, ResumeEvent event) {
        log.info("resume state from {} to {} run {} currentResume {} managerId {} executeDTO {}",
                from, to, event, resume.getId(), managerId, executeDTO);
    }

}

简历状态:

@Getter
public enum ResumeStatus {
    DRAFT("草稿", 0),

    PENDING_SELECTION("待筛选", 1),
    REJECTED("筛选不通过", 2),

    SCHEDULE_INITIAL_INTERVIEW("待安排初试", 3),
    PENDING_INITIAL_INTERVIEW("待初试", 4),
    INITIAL_INTERVIEW_PASSED("初试通过", 5), // 仅当初试为最后一个流程时显示
    INITIAL_INTERVIEW_FAILED("初试不通过", 6), // 仅当初试为最后一个流程时显示

    SCHEDULE_SECOND_INTERVIEW("待安排复试", 7),
    PENDING_SECOND_INTERVIEW("待复试", 8),
    SECOND_INTERVIEW_PASSED("复试通过", 9), // 仅当复试为最后一个流程时显示
    SECOND_INTERVIEW_FAILED("复试不通过", 10), // 仅当复试为最后一个流程时显示

    SCHEDULE_FINAL_INTERVIEW("待安排终试", 11),
    PENDING_FINAL_INTERVIEW("待终试", 12),
    FINAL_INTERVIEW_PASSED("终试通过", 13), // 仅当复试为最后一个流程时显示
    FINAL_INTERVIEW_FAILED("终试不通过", 14), // 仅当复试为最后一个流程时显示

    PENDING_HANDLING("待处理", 15),
    SUSPENDED("挂起", 16),

    ;

    ResumeStatus(String message, Integer code) {
        this.message = message;
        this.code = code;
    }

    @Override
    public String toString() {
        return message;
    }

    private final String message;

    @EnumValue
    @JsonValue
    private final Integer code;

    public static ResumeStatus get(Integer code) {
        for (ResumeStatus resumeStatus : ResumeStatus.values()) {
            if(resumeStatus.getCode().equals(code)) {
                return resumeStatus;
            }
        }
        throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_EXCEPTION);
    }
}

简历事件:

@Getter
public enum ResumeEvent {

    NEXT(1, "推进"),

    APPROVE(2, "通过"),

    ELIMINATE(3, "淘汰"),

    RESET(4, "重置"),

    PENDING(5, "待处理"),

    SUSPEND(6, "挂起"),

    CONFIRM(7, "转正"),

    ;

    @Override
    public String toString() {
        return description;
    }

    private final Integer event;

    private final String description;

    ResumeEvent(Integer event, String description) {
        this.event = event;
        this.description = description;
    }

    public static ResumeEvent get(Integer event) {
        for (ResumeEvent resumeEvent : ResumeEvent.values()) {
            if(resumeEvent.getEvent().equals(event)) {
                return resumeEvent;
            }
        }
        throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_TRANS_EVENT_ERROR);
    }

}

你也可以不是枚举类,但是我们要提供有限的固定的实例,并且代表状态和事件,枚举太适用了!

常量类:

public interface ResumeStateMachineConstants {

    String RESUME_STATE_MACHINE_ID = "resumeStateMachineId";

}

配置类:

@Configuration
@RequiredArgsConstructor
public class ResumeStateMachineBuildConfig {

    private final List<ResumeStateExternalTransitionHelper> externalHelpers;

    private final List<ResumeStateInternalTransitionHelper> internalHelpers;

    @PostConstruct
    public void buildInterviewMachine() {
        StateMachineUtil.buildMachine(
                ResumeStateMachineConstants.RESUME_STATE_MACHINE_ID,
                externalHelpers,
                internalHelpers
        );
        StateMachineUtil.printMachine(ResumeStateMachineConstants.RESUME_STATE_MACHINE_ID);
    }

}

一个 helper 实现展示:

@Component
@RequiredArgsConstructor
public class ResumeNextStateHelper implements ResumeStateExternalTransitionHelper {

    private final Condition<ResumeContext> defaultResumeCondition;

    private final Action<ResumeStatus, ResumeEvent, ResumeContext> defaultResumeAction;
    
    @Override
    public List<ResumeStatus> getFromState() {
        return List.of(
                DRAFT,
                PENDING_SELECTION,
                SCHEDULE_INITIAL_INTERVIEW,
                PENDING_INITIAL_INTERVIEW,
                SCHEDULE_SECOND_INTERVIEW,
                PENDING_SECOND_INTERVIEW,
                SCHEDULE_FINAL_INTERVIEW
        );
    }

    @Override
    public ResumeStatus getToState(ResumeStatus from) throws GlobalServiceException {
        return switch (from) {
            case DRAFT -> PENDING_SELECTION;
            case PENDING_SELECTION -> SCHEDULE_INITIAL_INTERVIEW;
            case SCHEDULE_INITIAL_INTERVIEW -> PENDING_INITIAL_INTERVIEW;
            case PENDING_INITIAL_INTERVIEW -> SCHEDULE_SECOND_INTERVIEW;
            case SCHEDULE_SECOND_INTERVIEW -> PENDING_SECOND_INTERVIEW;
            case PENDING_SECOND_INTERVIEW -> SCHEDULE_FINAL_INTERVIEW;
            case SCHEDULE_FINAL_INTERVIEW -> PENDING_FINAL_INTERVIEW;
            default -> throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_EXCEPTION);
        };
    }

    @Override
    public ResumeEvent getOnEvent() {
        return ResumeEvent.NEXT;
    }

    @Override
    public Condition<ResumeContext> getWhenCondition() {
        return defaultResumeCondition;
    }

    @Override
    public Action<ResumeStatus, ResumeEvent, ResumeContext> getPerformAction() {
        return defaultResumeAction;
    }
}

在这里插入图片描述

这里这两个是我写的默认值,并注入了容器,这样的写法,需要注意 bean 的名称不要冲突了

通过以上代码,在项目启动的时候即可构造出状态机:

在这里插入图片描述

执行状态机(代码片段):

在这里插入图片描述

我个人觉得没必要在状态机内部将状态落库,根据请求状态机的响应,进行落库

5. 小思考 · 状态机设计的合理性

状态机并不是通过 from 和 to 确定 event,而是 from 和 event 确定轮转

通过 from 到 to 的变化,是确定不了事件的,实现通过 from 到 to 的变化触发某一事件,事件可能不止一个,而且并不保证触发我们想要的那一个

比如一次请求,对象的当前属性就是 from,请求是以 event 作为参数,还是以 to 作为参数

① 若是 event 作为参数,那么根据 fromevent 即可进行状态轮转,并返回最终的状态

② 若是 to 作为参数,fromto 的这个状态变化会触发什么特定行为

状态机更注重的是状态的轮转,也就是 ①,而 ② 是状态变化的后置行为

以“一场面试”为例,一场面试的状态可以是: 未开始进行中已结束

如果是 ①,传入的参数可以是 “开始面试” 这一具体事件,当前面试状态是 ”未开始“,就可以轮转成”进行中“,也可以是 “通知用户” 这一事件,状态内部流转的过程中对用户进行面试通知;

如果是 ②,传入的参数可以是 ”进行中“,面试状态将直接更新成”进行中“,状态变化触发对应的事件,但做不了内部轮转

① 这种方式靠的是状态机,让请求行为更加具体,如进行“开始面试”,“通知用户”这种具体的行为

而 ② 更加狭隘,如果我们设计一份代码来管理 fromto 触发的事件,如果要符合我们的接口预期,这往往可能 并不能做到 ① 的可读性高、灵活性高、通用性高、可扩展性高

用 ② 来进行状态轮转不太合适,但是也不是完全没用,更适合用其管理一些状态变化固定的“副作用”,作为状态轮转的后置行为,还是不错的!可以结合责任链模式去实现

但是又说回来,fromto 的转变的副作用,在编写代码的时候应该也就是规定对应事件的副作用吧,比如 “未开始” 到 “进行中”,就是“面试开始” 这一具体行为的副作用,那其实在状态机就能实现啊;

也就是说 ② 这种方式适合,有多个事件可以进行 fromto 的轮转,并且这些事件都有共同的副作用,这个副作用就可以用 ② 来管理,出现这种情况也很极端了~

在比较简单的系统,fromto 能够 可以确定唯一的事件,没有内部轮转的需求 ,用这个也无所谓

如果非要 ② 这种请求方式,不想指定 event,出现特殊的状态变化,就得自动触发对应事件,这种就属于特殊需求了, fromto 触发的事件的映射关系,也只能在 to 已知的情况下可以复用,甚至只用于这一特殊请求,代码写得可能比较死,不太优雅🙁,太为难了(;′⌒`),反正我不是很喜欢,我个人比较喜欢优雅直观的设计;

我个人认为要是有个接口可以任意改变状态,那么应该不需要触发什么特定事件了,毕竟都允许可以任意更改了,或者说有一个统一的事件

可以定义一个”任意更改状态“的事件,上下文携带 toState,所有状态都可以触发,在状态机的内部/外部轮转(状态的行为)即可

但这不符合状态机明确的设计理念

综上所述,

用 ① 更加合适,可以让请求行为更加优雅具体直观和灵活,开发低耦合更具有扩展性,能实现的功能更多;② 的这种请求方式适合那种状态变化没有任何事件触发的场景,或触发的都是同一事件的场景,等不具有状态轮转的过程概念的场景。

BUT,

规矩是死的,视具体需求而论,切勿只纸上谈兵!

以上也只是我的想法,不同人有不同的想法和理念!

状态机还可以结合很多其他的设计模式,更多妙用等你开发!

状态机你爱咋用咋用,只要合理都 OK 啦!

开发是灵活的过程,开发者的理念为准,没有固定的强限定,但是我们要满足其基本的优秀写法!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2057845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring项目:文字花园(四)

一.实现登录 传统思路: • 登陆⻚⾯把⽤⼾名密码提交给服务器. • 服务器端验证⽤⼾名密码是否正确, 并返回校验结果给后端 • 如果密码正确, 则在服务器端创建 Session . 通过 Cookie 把 sessionId 返回给浏览器. 问题: 集群环境下⽆法直接使⽤Session. 原因分析: 我们开…

渐变纹理的使用

1、渐变纹理的使用 通过单张纹理和凹凸纹理相&#xff0c;我们知道图片中存储的数据不仅仅可以是颜色数据&#xff0c;还可以是高度、法线数据。 理论上来说&#xff0c;图片中存储的数据我们可以自定义规则&#xff0c;我们可以往图片中存储任何满足 我们需求的数据用于渲染。…

原神4.8版本抽到角色和重点培养数据表

<!DOCTYPE html> <html lang"zh-cn"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>原神4.8版本抽到角色和重点培养数据表</title…

vue-element-admin——<keep-alive>不符合预期缓存的原因

vue-element-admin——<keep-alive>不符合预期缓存的原因 本文章&#xff0c;以现在中后台开发用的非常多的开源项目vue-element-admin为案例。首先&#xff0c;列出官方文档与缓存<keep-alive>相关的链接&#xff08;请认真阅读&#xff0c;出现缓存<keep-ali…

MSR配置

公钥私钥 网页上提供的脚本安装客户端??去掉跳板机 history | grep azcopy 44 azcopy 47 azcopy cp --recursive --log-level NONE --overwrite true https://singularitywor9084471172.blob.core.windows.net/yifanyang/thinking.py\?sv\2023-01-03\&st\2024-…

机器学习:逻辑回归实现下采样和过采样

1、概述 逻辑回归本身是一种分类算法&#xff0c;它并不涉及下采样或过采样操作。然而&#xff0c;在处理不平衡数据集时&#xff0c;这些技术经常被用来改善模型的性能。下采样和过采样是两种常用的处理不平衡数据集的方法。 2、下采样 1、概念 下采样是通过减少数量较多的类…

【学习笔记】Day 19

一、进度概述 1、机器学习常识1-11&#xff0c;以及相关代码复现 二、详情 1、不确定性 所谓不确定性, 是指我们在进行预测的时候, 不能够保证 100% 的准确。而机器学习&#xff0c;比的就是谁 “猜的更准”。 不确定性&#xff0c;可能由信息不足、信息模糊等原因产…

编写开放接口与思考

编写开放接口与思考 一、情景描述&#xff1a; 当一个项目开发一定程度时&#xff0c;会有跟合作厂商对接共同开发的情况&#xff0c;那么如果合作厂商想要使用你项目中的某个接口&#xff0c;你该如何把接口暴露给他们&#xff1f; 二、实现方式分析 1、因为现在接口大部分…

超融合/分布式 IT 架构有哪些常见故障类型?如何针对性解决和预防?

本文刊于《中国金融电脑》2024 年第 7 期。 作者&#xff1a;SmartX 金融团队 以超融合为代表的分布式 IT 基础架构凭借其高性能、高可靠和灵活的扩展能力&#xff0c;在满足大规模、高并发、低延迟业务需求等方面展现出显著优势&#xff0c;成为众多金融机构构建 IT 基础设施…

Nginx: 体系化知识点梳理

概述 我们需要对 Nginx 要有体系化的一个认识对 Nginx 自身来说&#xff0c;它是作为一个中间件的&#xff0c;只要是中间件&#xff0c;它必然会涉及到前端和后端对于 Nginx 来说&#xff0c;它是需要协调整个前后端的一个组件那对于中间件来&#xff0c;我们要理解整个外部系…

Python 设置Excel工作表页边距、纸张大小/方向、打印区域、缩放比例

在使用Excel进行数据分析或报告制作时&#xff0c;页面设置是确保最终输出效果专业、美观的关键步骤。合理的页面设置不仅能够优化打印效果&#xff0c;还能提升数据的可读性。本文将详细介绍如何使用Python操作Excel中的各项页面设置功能。 目录 Python 设置Excel工作表页边…

Hexo通过GitHub设置自定义域名

本身GitHub也是支持自定义域名的&#xff0c;本次教程将讲解如何使用GitHub自带的自定义域名解析。 1. GitHub设置 1.1 登录GitHub账号 登录GitHub账号&#xff0c;找到名称为 用户名.github.io的仓库&#xff0c;并点击进入。 1.2 进入Settings页面 点击如图的Settings按…

Mysql的相关编程基础知识

一. 配置MySQL 首先下载mysql-5.0.96-winx64&#xff0c;安装过程如下图所示。 1.安装MySQL 5.0 ​ ​ 2.选择手动配置、服务类型、通用多功能型和安装路径 ​ 3.设置数据库访问量连接数为15、端口为3306&#xff08;代码中设置URL用到&#xff09;、编码方式为utf-8 ​ 4.设…

【C语言】冒泡排序保姆级教学

C语言冒泡排序保姆级教学 直奔主题&#xff1a; 拿排升序举例子 第一步&#xff1a; 将想要排序的数组中数值最大的那个数排到该数组的最后 具体实现如下图&#xff1a; 第一步代码实现 for (int i 1; i < n ; i)//n为数组大小此处为4 {if (a[i - 1] > a[i])//注意越…

k8s综合项目

一、准备环境 1.1 部署服务器 在centos7.9系统里搭建v1.23版本的k8s集群&#xff0c;准备四台服务器&#xff0c;两台作为master&#xff0c;主机名分别为 k8s-master和k8s-master-2&#xff0c;主机名为k8s-master&#xff0c;两台作为 node&#xff0c;主机名分别为k8s-nod…

嵌入式开发输出调试信息的常用方法

嵌入式开发为什么需要输出调试信息&#xff1f; 稳严文&#xff1a;因为输出调试信息是嵌入式开发中一项非常重要的实践&#xff0c;它有助于保证软件的可靠性、稳定性和性能&#xff0c;也是故障排查的关键工具之一。 白话文&#xff1a;程序猿想知道自己敲的代码是否正确、是…

Nginx反向代理功能

反向代理&#xff1a;reverse proxy&#xff0c;指的是代理外网用户的请求到内部的指定的服务器&#xff0c;并将数据返回给用户的 一种方式&#xff0c;这是用的比较多的一种方式。 Nginx 除了可以在企业提供高性能的web服务之外&#xff0c;另外还可以将 nginx 本身不具备的…

如何选择最佳路线?

交通线路的选择 日常交通线路的选择&#xff0c;并不是按最短路径选择的。还要参考道路的等级&#xff0c;道路是否拥堵&#xff0c;道路通行速度等多种情形。本程序列举出所有能通行的线路&#xff0c;并计算出行驶距离&#xff0c;来供用户选择。当然&#xff0c;也可以加入…

linux | ubuntu虚拟机创建硬盘、磁盘分区、分区挂载、自动挂载、磁盘清理

点击上方"蓝字"关注我们 01、创建硬盘 >>> 什么使硬盘? 点击虚拟机设置,这里有两个硬盘,一个100G,一个20G 应用场景,下载yocto时,磁盘空间不足,所以写下这篇文章,供大家参考 >>> 开始创建一个新的硬盘 点击添加 【选择硬盘 下一步】 【推…

三、Vuex是什么?Vuex详解

什么是Vuex 在理解Vuex之前&#xff0c;首先需要理解我们为什么要使用它&#xff1f;它解决了什么问题&#xff1f; 为什么要使用它 在Vue开发中&#xff0c;我们常常会用到组件直接的传值、通讯。有父子通讯&#xff0c;兄弟组件通讯…但是传参对于多层嵌套就显得非常繁琐&am…