Springboot整合Activiti详解

news2024/9/27 7:18:40

文章目录

  • 版本依赖
  • 配置文件
  • 需要注意的问题
  • 画流程图
  • activiti服务类进行编写
    • 流程部署
    • 流程定义
    • 启动流程
    • 流程实例
  • 测试流程
    • 启动流程
    • 完成任务
    • 受理任务


版本依赖

  • 开发工具 IDEA
  • SpringBoot 2.4.5(这里我试过SpringBoot 3.1.1版本,Activiti没有启动,应该是依赖冲突了,后改成了2.4.5版本)
  • Activiti 7.1.0.M6

父项目pom.xml

    <dependencyManagement>
        <dependencies>
            <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.4.5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.activiti.dependencies/activiti-dependencies -->
            <dependency>
                <groupId>org.activiti.dependencies</groupId>
                <artifactId>activiti-dependencies</artifactId>
                <version>7.1.0.M6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

子项目pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
    </dependencies>

配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    username: root
    password: 111111
    url: jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&useInformationSchema=true
    hikari:
      #连接池做大连接数
      maximum-pool-size: 30
      #连接池空闲连接最小数量
      #minimum-idle: 10
      #允许连接在连接池中闲置最长时间
      #idle-timeout: 30000
      #池中连接最长生命周期
      max-lifetime: 120000
      #等待来自池的连接的最大毫秒数
      connection-timeout: 30000
  activiti:
    #自动更新数据库结构
    #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
    #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
    #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
    #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
    database-schema-update: true
    #activiti7默认不生成历史信息表,开启历史表
    db-history-used: true
    #记录历史等级 可配置的历史级别有none, activity, audit, full
    #none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
    #activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
    #audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
    #full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
    history-level: full
    #自动检查、部署流程定义文件
    check-process-definitions: false
    # asyncExecutorActivate是指activiti在流程引擎启动就激活AsyncExecutor,异步:true-开启(默认)、false-关闭
    async-executor-activate: true

配置文件这里注意要设置 database-sechema-update的属性为true,才会自动创建表。

需要注意的问题

在初次启动时可能会报错,报错如下:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.core.userdetails.UserDetailsService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
	... 142 more

这里是需要一个UserDetailsService的Bean,需要创建一个类去实现UserDetailsService接口

UserDetailsServiceImpl.class

package org.example.config;

import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userService.findOneUserByName(username);
    }
}

UserService.class

package org.example.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    public User findOneUserByName(String username){
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User(username,"",authorities);
    }
}

启动成功后,数据库会生成25张表
在这里插入图片描述

画流程图

在使用IDEA时,需要下载一个插件
在这里插入图片描述
当这个插件安装好以后,新建文件会多一个选项
在这里插入图片描述
新建的xml文件可以通过view BPMN Diagram,转化为图表操作
在这里插入图片描述
在这里插入图片描述
如何通过图表操作画流程这里就不说了,可以自己摸索看看。

activiti服务类进行编写

package org.example.service;

import org.activiti.engine.*;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentQuery;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

@Service
public class ActivitiService {

    @Autowired
    RepositoryService repositoryService;

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    TaskService taskService;

    @Autowired
    HistoryService historyService;

    /**
     * 部署流程服务
     */
    public void deployProcess(){
        String file = "bpmn/test.bpmn20.xml";
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource(file)
                .name("流程部署测试")
                .deploy();
        System.out.println("流程部署名称:"+deployment.getName());
    }

    /**
     * 查询所有部署的流程定义
     * @return
     */
    public List<ProcessDefinition> getAllProcessDefinition(){
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        return query.orderByProcessDefinitionVersion().desc().list();
    }

    /**
     * 查询所有的部署
     * @return
     */
    public List<Deployment> getAllDeployment(){
        DeploymentQuery query = repositoryService.createDeploymentQuery();
        return query.list();
    }

    /**
     * 查询所有流程实例
     * @return
     */
    public List<ProcessInstance> getAllProcessInstance(){
        List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
        return list;
    }

    public List<Task> getAllProcessTask(){
        List<Task> list = taskService.createTaskQuery().list();
        return list;
    }


    /**
     * 查询用户待完成和待认领的任务
     * @param username
     * @return
     */
    public List<Task> getAllProcessTaskByCandidateOrAssigned(String username){
        List<Task> list = taskService.createTaskQuery().taskCandidateOrAssigned(username).list();
        return list;
    }



    public List<Task> getAllProcessTaskByCandidate(String username){
        List<Task> list = taskService.createTaskQuery().taskCandidateUser(username).list();
        return list;
    }



    /**
     * 查询用户的待完成任务
     * @param username
     * @return
     */
    public List<Task> getAllProcessTaskByAssigned(String username){
        List<Task> list = taskService.createTaskQuery().taskAssignee(username).list();
        return list;
    }

    public void complateTask(Task task, Map<String, Object> map){
        taskService.complete(task.getId(),map);
    }


    public void complateTask(Task task){
        taskService.complete(task.getId());
    }


    public void claimTask(Task task,String username){
        taskService.claim(task.getId(),username);
    }
    public ProcessInstance startProcess(String processDefinitionKey,String businessKey,Map<String,Object> map){
        return runtimeService.startProcessInstanceById(processDefinitionKey,businessKey,map);
    }

    public String getProcessDefinitionXml(String processDefinitionId) {
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
        String resourceName = processDefinition.getResourceName();
        InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
        try {
            return IOUtils.toString(resourceAsStream, StandardCharsets.UTF_8);
        } catch (IOException e) {
            // handle exception
        }
        return null;
    }


    public List<HistoricProcessInstance> getHistoryByBusinessKey(String businessKey){
        List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).list();
        return list;
    }
}

在这里插入图片描述
上图是,如果需要使用activiti中提供的服务类,直接注入即可。即springboot整合activiti启动过程中,已经在容器中自动装配了对应的服务类,需要的时候,仅仅需要取来用即可。如果启动以后报错,容器没有自动装配对应的服务类,那很有可能是依赖冲突,activiti没有正常启动。

流程部署

流程部署有多种方式,这是通过资源路径部署的,也可以通过压缩包的形式部署流程。那就需要用到DeploymentBuilderaddZipInputStream方法,这里也是用到了设计模式中的建造者模式。
在这里插入图片描述

在这里插入图片描述

流程定义

这是一个查询所有部署的流程定义的方法
在这里插入图片描述
当一个流程部署以后,就会生成一个流程定义,流程图的xml信息也会存入数据库。每次部署流程图都会生成一个新的流程定义。
在这里插入图片描述

启动流程

启动流程可以通过流程定义的id或者key,如果想通过流程定义的id启动一个流程可以使用startProcessInstanceById方法,如果想通过流程定义的key启动一个流程可以使用startProcessInstanceByKey方法。在这里插入图片描述
这里要明白流程定义id和key的区别,流程定义的key是在画流程的时候可以设置的,通过设置流程的id,这个就是流程的key。
在这里插入图片描述

在这里插入图片描述
那流程的id呢,这是部署以后在数据库表的id。可以根据需要自行选择启动流程的方法。

流程实例

查询所有启动的流程实例
在这里插入图片描述
每次开始一个流程以后,就会生成一个流程实例。通过这个方法可以查看所有的流程实例。

测试流程

package org.example;

import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.assertj.core.util.DateUtil;
import org.example.common.PrintUtil;
import org.example.entity.User;
import org.example.service.ActivitiService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

@SpringBootTest
public class SpringBootApplicationTest {

    @Resource
    DataSource dataSource;

    @Autowired
    ActivitiService activitiService;


    @Test
    void context() throws SQLException {
        System.out.println(dataSource.getConnection());
    }

    @Test
    public void deployProcess(){
        activitiService.deployProcess();
    }

    @Test
    public void TestStartProcess(){
        ProcessDefinition processDefinition = activitiService.getAllProcessDefinition().get(0);
        String bussinessKey = this.getClass().getSimpleName();
        Map<String , Object> map = new HashMap<String , Object>();
        map.put("creator","Echo");
        activitiService.startProcess(processDefinition.getId(),bussinessKey,map);
    }

    @Test
    public void findAllDeployment(){
        List<Deployment> list = activitiService.getAllDeployment();
        PrintUtil.printTable(list);
    }

    @Test
    public void findAllProcessDefinition(){
        List<ProcessDefinition> list = activitiService.getAllProcessDefinition();
        PrintUtil.printTable(list);
    }


    @Test
    public void findAllProcessInstance(){
        List<ProcessInstance> list = activitiService.getAllProcessInstance();
        PrintUtil.printTable(list);
    }

    @Test
    public void findAllProcessTask(){
        List<Task> list = activitiService.getAllProcessTask();
        PrintUtil.printTable(list);
    }

    @Test
    public void findTaskByUserId(){
        String userId = "keaizp";
        List<Task> list = activitiService.getAllProcessTaskByCandidateOrAssigned(userId);
        PrintUtil.printTable(list);
    }

    @Test
    public void claimTask(){
        String userId="keaizp";
        List<Task> list = activitiService.getAllProcessTaskByCandidate(userId);
        PrintUtil.printTable(list);
        activitiService.claimTask(list.get(0),userId);
        list = activitiService.getAllProcessTaskByCandidate("zengpei");
        PrintUtil.printTable(list);
    }
    @Test
    public void complateUserTask(){
        String userId = "Echo";
        List<Task> list = activitiService.getAllProcessTaskByAssigned(userId);
        List<String> candidateUsers = new ArrayList<String>();
        candidateUsers.add("keaizp");
        candidateUsers.add("zengpei");
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("approver",candidateUsers);
        activitiService.complateTask(list.get(0),map);
    }

    @Test
    public void processDefinitionXml(){
        List<ProcessDefinition> list = activitiService.getAllProcessDefinition();
        String xmlStr = activitiService.getProcessDefinitionXml(list.get(0).getId());
        System.out.println(xmlStr);
    }

    @Test
    public void findHistoryByBusinessKey(){
        List<HistoricProcessInstance> list = activitiService.getHistoryByBusinessKey("2023-07-02T22:45:45");
        PrintUtil.printTable(list);
    }


    @Test
    public void printTest(){
        List<User> users = new ArrayList<>();
        users.add(new User("keaizpeeeeeeeee",24,"male"));
        users.add(new User("Echo",24,"male"));
        users.add(new User("nick",24,"male"));
        users.add(new User("amy",24,"male"));
        PrintUtil.printTableFixed(users,16);
    }
}


启动流程

在这里插入图片描述
这里我用的是一个格式化的日期做bussinessKey,实际业务中最好使用 流程作业类型 + 一个业务台账id,比如:请假申请要走审批流程, 离职申请也要走审批流程,如果只用id作为bussinessKey的话,bussinessKey就无法满足唯一性。所以最好 前面加上作业类型。这里的map传入的是流程图里面的参数。
在这里插入图片描述
传入受理人,这里就会产生一个Task,就是用户的代办任务。

完成任务

在这里插入图片描述
当用户登录系统的时候,应该给用户提示,用户有未完成的代办事项,然后给出用户代办事项列表,做完一切以后,就是完成用户任务,这个时候,可以传入下一流程节点参与任务的人,当然你也可以传入几个候选人,candidate Users就是 候选人。可以传入一个用户id的列表,然后这几个人就会收到可以受理的任务,可以选择是否接受该任务。只要接受了任务,其他候选人就无法再接受此任务,同时这个任务的Assignee就会设置成受理人的id

受理任务

在这里插入图片描述
因为用户“keaizp”成为了候选者,他就可以看到自己可以受理的这个任务,用claim方法就可以受理该任务,当受理任务完成以后,再去看看另外一名候选者的受理任务,会发现已经没有待受理的任务了。


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

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

相关文章

现代卓越首席咨询顾问张智喨受邀为第十二届中国PMO大会演讲嘉宾

现代卓越首席咨询顾问张智喨老师受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;BANI时代下如何打造价值导向型PMO。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; 在BANI时代下&#xff0c;企…

机器学习14:稀疏性-Sparsity

现实世界中&#xff0c;问题的特征的数量往往是很大的&#xff0c;而其中起决定性作用的往往是很小的一部分&#xff0c;稀疏规则化算子的引入会学习去掉这些没有信息的特征&#xff0c;也就是把这些特征对应的权重置为 0。 1.稀疏性正则化&#xff1a;L₁ 正则化 稀疏向量通常…

Docker容器初识篇

学习-https://vuepress.mirror.docker-practice.com/basic_concept/container/ https://www.bookstack.cn/cate 什么是Docker Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后&#xff0c;在其上运行一个完整操作系统&#xff0c;在该系统上再运行所需应…

时间序列分解 | Matlab集合经验模态分解EEMD的信号分解

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列分解 | Matlab集合经验模态分解EEMD的信号分解 部分源码 %--------------------

JavaWeb学习路线(10)—— 关于SpringBoot的原理

一、配置文件优先级 在Springboot框架三s种配置文件的格式 application.propertiesapplication.ymlapplication.yaml Springboot是支持多配置文件的&#xff0c;所以当我们使用两种配置文件配置相同参数时&#xff0c;Springboot会执行哪一个配置文件呢&#xff1f;这就是Sp…

FFmpeg5.0源码阅读—— avcodec_open2

摘要&#xff1a;本文主要描述了FFmpeg中用于打开编解码器接口avcodec_open2大致流程的具体调用流程&#xff0c;详细描述了该接口被调用时所作的具体工作。   关键字&#xff1a;ffmpeg、avcodec_open2大致流程   注意&#xff1a;读者需要了解FFmpeg的基本使用流程&#…

SpringMVC (一) 什么是SpringMVC

一、回顾MVC 1.1、什么是MVC MVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式&#xff0c;MVC是一种架构模式。…

在 ZBrush、Maya 和 Substance 3D Painter 中制作后世界末日女性角色

今天瑞云渲染小编给大家带来了蒂亚戈布兰道 (Thiago Brandao) 分享的设计极其精细后世界末日女性角色背后的秘密&#xff0c;解释了复杂的纹理处理过程&#xff0c;并谈到了创造逼真的面部特征。 介绍 我的名字是蒂亚戈-布兰道。我来自巴西&#xff0c;目前住在这里。从小&…

【AJAX】原生AJAX

目录 一、AJAX的使用场景 二、原生的AJAX 三、HTTP &#xff1a; 1、原生AJAX GET请求 1、原生AJAX POST请求 四、总结 一、AJAX的使用场景 1、页面不刷新的情况下&#xff0c;浏览器等搜索框输入某关键字出现多个搜索选择内容,如下图所示&#xff1a; 2、登录、注册界面输…

前端JS如何实现对复杂文本进行句子分割,将每句话拆分出来?

文章目录 切割句子背景简介前端如何使用NLP&#xff1f;技术实现 切割句子背景简介 开发中遇到一种场景&#xff0c;在做文本翻译这块需求时&#xff0c;需要对输入的原文进行一句一句话的拆分出来&#xff0c;传给后台&#xff0c;获取每句话的翻译结果&#xff0c;便于实现页…

clickhouse系统日志引起的root目录磁盘满的问题处理

问题及追踪 对于生产环境&#xff0c;尤其是配置较低的生产环境&#xff0c;一定要注意资源的使用 今天就遇到一个问题&#xff0c;服务器磁盘接近满了&#xff0c;部署的平台服务异常&#xff0c;无法提供服务 简单说一下客户环境&#xff1a;客户只有老的Windows server 服…

hydra的简单使用

Hydra是一款开源的暴力PJ工具&#xff0c;集成在kali当中。 参数功能-l指定用户名-p指定密码-L指定用户名字典-P指定密码字典-C指定所用格式为“user:password”的字典文件-en null&#xff0c;表示尝试空密码-es same&#xff0c;把用户名本身当做密码进行尝试-er 反向&#…

哈工大计算机网络课程网络层协议详解之:距离向量路由算法与层次化路由算法

文章目录 距离向量路由算法Bellman-Ford算法举例距离向量路由算法 层次化路由算法层次化路由AS示例自治系统间&#xff08;Inter-AS&#xff09;路由任务 在上一节中我们介绍了路由算法中的链路状态路由算法&#xff0c;介绍它基于Dijkstra算法来实现&#xff0c;同时分析了其缺…

Jmeter接口测试断言详解

目录 前言&#xff1a; 响应断言 Apply to (响应断言的应用范围) 要测试的响应字段(可通过取样器结果查看) 响应断言&#xff1a;模式匹配 Json断言 前言&#xff1a; 在JMeter中进行接口测试时&#xff0c;断言是一个非常重要的概念。断言允许我们验证接口的响应是否符…

十七、docker学习-docker-compose安装nginx反向代理

compose安装nginx反向代理 IDEA安装docker插件 idea安装docker插件。Dockerfile、docker-compose.yml文件大部分内容会有提示信息。方便开发人员编写配置文件。 https://plugins.jetbrains.com/plugin/7724-docker/versions基础镜像 docker pull 1.21.0-alpine docker pull…

一文读懂SQL中的Aggregate(聚合) 函数和Scalar(标准)函数

目录 前言&#xff1a; 一、SQL Aggregate 函数 1、AVG() 函数 2、count()函数 3、MAX() 函数 4、MIN() 函数 5、SUM() 函数 6、SQL GROUP BY 语法 7、SQL HAVING 子句 8、SQL EXISTS 运算符 9、SQL UNION 操作符 二、SQL Scalar 函数 1、SQL UCASE() 函数 2、SQ…

Swift 周报 第三十二期

文章目录 前言新闻和社区现已提供新的设计资源visionOS SDK 现已发布 提案Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组自主整理周报的第二十三期&#xff0c;每个模块已初步成型。各位读者如果有好的提议&#xff0c;欢迎在文末留言。 欢迎投稿或推荐内容。…

力扣 -- 174. 地下城游戏

题目链接&#xff1a;174. 地下城游戏 - 力扣&#xff08;LeetCode&#xff09; 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 参考代码&#xff1a; class Solution { public:int calculateMinimumHP(vector&…

【上海海事大学806】23上岸学姐经验分享

今天很荣幸请到了一位23上岸上海海事大学的学姐来给大家做一期经验分享&#xff01;&#xff01;&#xff01;我之前也有做过关于上海海事大学806的一些真题解析以及重点勾画&#xff0c;希望能帮到大家&#xff0c; 一、学姐经验分享 很荣幸你能看到我的考研经验分享&#x…

第七章 版本控制器——git

第七章 版本控制器——git 一、git的历史二、git的特点与发展1、git的特点2、git与github 二、git的安装与注册1、git的安装2、git的使用&#xff08;1&#xff09;github注册&#xff08;2&#xff09;创建远端仓库&#xff08;3&#xff09;将远端仓库镜像复制到本地仓库指令…