JavaEE从入门到起飞(九) ~Activiti 工作流

news2024/11/24 14:56:02

工作流

当一道流程逻辑需要用到多个表单的提交和多个角色的审核共同完成的时候,就可以使用工作流。

工作流一般使用的是第三方技术,也就是说别人帮你创建数据库表和service层、mapper层,你只需要注入工具接口即可使用。

原理:一切操作都是sql语句,第三方做好,你只要按照它们的规范,即可完成工作流。

Activiti

Activiti是一个开源的轻量级工作流引擎,2010年基于jBPM4实现首次开源。官网地址:https://www.activiti.org

Activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN进行定义

BPMN是一种用于图形化表示和描述业务流程的标准化标记语言,目前主流的版本是2.0

事件event

事件是业务流程模型中的重要元素之一,事件可以发生在流程的任何阶段,并且可以影响流程的执行。分为以下几类:

  • 开始事件(Start Event):表示流程的起点,通常用于触发流程的启动

  • 结束事件(End Event):表示流程的结束点,通常用于触发流程的结束

在这里插入图片描述

活动activiti

任务(Task)是最基本的活动类型,表示一个简单的、可执行的工作单元。任务通常由人工执行,并且需要指定执行者

用户任务是由人工执行的,需要指定执行的用户或角色,并提供相应的输入

手动任务是由系统自动执行的,不需要指定执行的用户或角色

在这里插入图片描述

流向flow

流是连接两个流程节点的连线。常见的流向包含以下几种

在这里插入图片描述

请假工作流案例

在学校中,如果有事需要请假,一般需要向得到老师批准才可以完成请假。

  • 学生:请假的学生需要先填写请假单,填写的字段有:请假人、请假天数、开始请假时间、请假事由。
  • 老师:审批员工的请假单,如果不同意,则需要说明不同意的理由。
    1. 同意,进入下个任务
    2. 不同意,直接结束该流程实例
    3. 驳回,觉得信息填写不完善,需要改善

在这里插入图片描述

实现步骤

1、搭建环境:使用SpringBoot集成Activiti,把初始化环境做出来

2、绘制流程:按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来

3、部署流程:把画好的流程定义文件,加载到数据库中,生成表的数据

4、操作流程:使用java代码来操作数据库表中的内容

提示:数据库的依赖和配置也需要写,但我这里就不写了

添加依赖

        <!--安全框架 spring security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--springboot与activiti7整合的starter-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.10.0</version>
        </dependency>
    <!--如果activiti依赖下载不了,可以配置如下地址进行下载-->
    <repositories>
        <repository>
            <id>activiti-releases</id>
            <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases</url>
        </repository>
    </repositories>

添加配置文件

  activiti:
    # 记录所有历史数据
    history-level: full
    # 是否需要使用历史表,默认false不使用,而配置true是使用历史表
    db-history-used: true
    # 关闭流程自动部署,需要手动部署流程
    check-process-definitions: false
    # 如果部署过程遇到任何问题,服务不会失败
    deployment-mode: never-fail

注意:需要在自己的MySQL中创建一个新的数据库:activiti-db

我们也可以直接查询数据库,数据库中创建了25张表,目前说明Springboot已成功集成了activiti7

在这里插入图片描述

表结构

Activiti 的表都以ACT_ 开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的 API 对应。

  • ACT_GE :GE 表示 general, 通用数据
  • ACT_RE :RE表示 repository,这个前缀的表包含了流程定义信息
  • ACT_RU:RU表示 runtime,这些运行时的表,包含流程实例,任务,变量,异步任务等运行中的数据
  • ACT_HI:HI表示 history, 这些表包含历史数据,比如历史流程实例, 变量,任务等等
表分类表名解释
一般数据
[ACT_GE_BYTEARRAY]通用的流程定义和流程资源
[ACT_GE_PROPERTY]系统相关属性
流程历史记录
[ACT_HI_ACTINST]历史的活动实例
[ACT_HI_ATTACHMENT]历史的流程附件
[ACT_HI_COMMENT]历史的说明性信息
[ACT_HI_DETAIL]历史的流程运行中的细节信息
[ACT_HI_IDENTITYLINK]历史的流程运行过程中用户关系
[ACT_HI_PROCINST]历史的流程实例
[ACT_HI_TASKINST]历史的任务实例
[ACT_HI_VARINST]历史的流程运行中的变量信息
流程定义表
[ACT_RE_DEPLOYMENT]部署单元信息
[ACT_RE_MODEL]模型信息
[ACT_RE_PROCDEF]已部署的流程定义
运行实例表
[ACT_RU_EVENT_SUBSCR]运行时事件
[ACT_RU_EXECUTION]运行时流程执行实例
[ACT_RU_IDENTITYLINK]运行时用户关系信息,存储任务节点与参与者的相关信息
[ACT_RU_JOB]运行时作业
[ACT_RU_TASK]运行时任务
[ACT_RU_VARIABLE]运行时变量表

常见api(重点)

在activiti7框架内部,已经对25张表的数据操作,已经封装了对应的service(相当于自己开发的25个mapper和25个service

  • RepositoryService:用于部署流程定义,可以添加、删除、查询和管理流程定义,相当与类,所有人请假都需要按照这个模板执行。

    1. act_re_deployment:流程部署,记录每次工作流的部署信息,包括部署名称和时间等
    2. act_ge_bytearray:流程资源表,系统会将流程定义的两个文件保存到这张表中
    3. act_re_procdef:流程定义表,记录每个流程定义的信息,包括流程定义的名称、版本号、部署ID等
  • RuntimeService:用于启动、查询和管理正在运行的流程实例,相当于对象,启动之后每个人请假都要按照这个模板走。

    1. act_ru_execution表(运行时流程实例表):插入一条新的流程实例的执行信息,包括流程实例ID、流程定义ID等
    2. act_ru_task(运行时任务表):插入一条新的任务记录,表示流程实例的启动任务
    3. act_ru_identitylink (运行时身份关联表):插入一条新的身份关联记录,表示流程实例的启动任务与相关用户的关系
  • TaskService:用于查询和管理当前用户可以操作的任务,以及完成任务,相当于栈中的方法

    • 流程启动之后,不同的人登录的时候,需要查询是否要自己要执行的任务,如果有可以对流程进行操作,一般是在一个专门的事务待办页面
    • act_ru_execution表中发现,新增了【经理审批】待执行的节点,而完成的【填写请假单】节点被删除了
    • act_ru_task表中,待办任务也变成了李四【经理审批】, 一个任务结束后,该任务的记录就会在ru_task表中删除,会在运行时任务表添加下一条需要执行的任务。
    • act_ru_variable表中会存储代码中传入的表单数据
  • HistoryService:用于查询历史数据,例如已完成的流程实例、已删除的流程实例、用户任务等,历史统计,主要是用于查询,比如说统计某个人这个月请了多少假。相当于日志

    1. act_hi_procinst:历史的流程实例
    2. act_hi_actinst:历史的活动实例
    3. **act_hi_taskinst:历史的任务实例 ** 任务执行完毕后,endTime结束时间会有值。
    4. act_hi_identitylink:历史流程用户关系
    5. act_hi_varinst:历史流程运行中的变量信息

开始定义一个类(流程模板),根据类创建对象(流程实例)按照栈的顺序(流程模板定义的),执行类中的每个方法(任务),在每次操作后都会记录日志(历史数据)。

因为我们现在使用的是springboot集成了activiti,这些api也被spring容器进行了管理,需要用到以上api的时候,直接注入即可

绘制流程

我们打开bpmn-js,可以直接在页面中画图,步骤如下:

① 定义流程编号(ID)和名称

在这里插入图片描述

② 新增一个用户任务,并指定代理人为:张三

在这里插入图片描述

③ 新增一个用户任务,并指定代理人为:李四,同时需要结束这个流程,最后需要有一个结束事件

在这里插入图片描述

④ 流程图画好之后,在页面的左下角有一个导出,就可以直接导出为bpmn文件(xml文件)

在这里插入图片描述

⑤ 把生成后的bpmn文件改名拷贝到idea中备用,存储位置:resource/bpmn/qingjia.bpmn

​ 因为保存的文件都是xml文件,我们为了方便查看这些流程,也可以截个图一起放入bpmn目录下

在这里插入图片描述

案例分析

在指派用户任务的执行人时,使用的都是直接指派给固定账号,这样流程设计审批的灵活性就很差

因此,Activiti提供了各种不同的分配方式,这章我们就来详细研究下其它任务分配方式,主要有:表达式分配、监听器分配

表达式分配

值表达式就是使用UEL表达式(一种占位符)来替换具体的分配人,在使用的时候只需要对表达式中的变量进行赋值即可

${user.assignee} 指定对象的属性值为代理人

${student} 指定变量值为代理人

绘制流程

重新绘制前面的流程,但是在代理人的位置不再直接写死为张三、李四,而是使用 a s s i n g e e 0 、 {assingee0}、 assingee0{assingee1}来代替

image-20240821215647106

代码实现

/**
 * @author windStop
 * @version 1.0
 * @description 测试:含有表达式的代理人
 * @date 2024年08月21日17:37:23
 */
@SuppressWarnings("ALL")
@SpringBootTest
@Slf4j
public class ActivitiTestPlus {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;

    @Test
    public void test(){
        //1. 创建模版,部署信息,流程定义
        Deployment deploy = repositoryService.createDeployment()
                .addClasspathResource("bpmn/qingjia2.bpmn")
                .addClasspathResource("bpmn/qingjia.png")
                .name("qingjia")
                .deploy();
        log.info("部署信息:{}", deploy);
        //2. 创建流程实例
        Map<String,Object> claim = new HashMap<>();
        claim.put("student","张三");
        claim.put("teacher","吴彦祖");
        ProcessInstance processInstance = runtimeService.
                startProcessInstanceByKey("qingjia",claim);
        log.info("流程实例信息:{}",processInstance);
        //3. 查询自己的待办任务
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("qingjia").taskAssignee("张三").list();
        log.info("自己待办任务集合:{}",list);
        //4. 任务一:执行请假
        for (Task task : list) {
            log.info("正在执行的任务id为:{}", task.getId());
            log.info("正在执行的任务名称为:{}", task.getName());
            //请假原因,任务一 需要填写的额外内容,一般是前端表单传来的数据(用户自己输入)
            Map<String,Object> leaveContent = new HashMap<>();
            leaveContent.put("username","蔡申友");//请假人
            leaveContent.put("reason","出差工作..");//请假原因
            leaveContent.put("startDate","2024年8月21日18:10:53");//请假开始时间
            leaveContent.put("days",20);//请假天数
            //完成任务
            taskService.complete(task.getId(),leaveContent);
            log.info("任务完成....");
        }
    }
	//老师同意
    @Test
    public void test2(){
        //5. 任务二:老师审批
        //5.1 查询自己的任务待办集
        List<Task> list = taskService.createTaskQuery().processDefinitionKey("qingjia")
                .taskAssignee("吴彦祖").list();
        log.info("老师的任务集为:{}",list);
        //5.2 老师执行任务
        for (Task task : list) {
            log.info("正在执行的任务id为:{}", task.getId());
            log.info("正在执行的任务名称为:{}", task.getName());
            //组装业务数据,前端传递的
            Map<String,Object> claim = new HashMap<>();
            claim.put("remark","同意请假,但要按时返校");
            taskService.complete(task.getId(),claim);
            log.info("任务二执行完毕...");
        }
    }
    //老师不同意
    @Test
    public void test3(){
        //1. 查询老师(当前用户),要处理的任务集,在专门的页面展示
         List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionKey("qingjia").taskAssignee("吴彦祖").list();
        //2. 执行任务 -> 拒绝任务
        for (Task task : tasks) {
            log.info("正在执行的任务id为:{}", task.getId());
            log.info("正在执行的任务名称为:{}", task.getName());
            //组装业务数据,前端传递的
            Map<String,Object> claim = new HashMap<>();
            claim.put("approvalStatus", "不同意");
            claim.put("approvalNode", "时间太久,不同意");
            //记录流程变量
            runtimeService.setVariables(task.getProcessInstanceId(),claim);
            //添加流程变量,删除流程实例,表示任务被拒绝
            runtimeService.deleteProcessInstance(task.getProcessInstanceId(), "时间太久,不同意");
            log.info("任务二, 拒绝执行完毕...");
        }
    }
}

注意:

​ 老师不同意则流程会终止执行。,如果老师审批不同意,那么主任就不用审批了,整个流程就应该直接结束。结束调用的方法和同意不同,并且变量需要存到流程变量中。

因此不同意,则应该是终止流程而不是完成节点,在删除流程时,同时也把审批不同意及理由,作为流程变量存储到流程变量中。

候选人

在前面的流程定义中在任务结点的都是设置了一个负责人,但是在企业中,每个节点上都可能有多个负责人。

下面我们就需要使用候选人或者候选人组做为身份标识替换掉前面的单个参与者来完成任务。需要将候选人提权成执行人才能执行任务,该任务只能让一个人处理。

一个审批节点可能有多个人同时具有审批的权限,这时我们就可以通过候选人来处理。

注意:候选人默认可以在身份关联表中(ru_identtitylink)查看

绘制流程

这次绘制流程时,对于经理审批,不再设置代理人,而是设置候选人,多个候选人是,分隔

在这里插入图片描述

流程启动后任务,在act_ru_task表中的审批人是空的,但是act_ru_identitylink表保存了候选人信息.

拾取任务(提权)

提升候选人的权限为执行人。

  	// 测试候选人
    @Test
    public void test(){
        //1. 创建流程模版
        Deployment deploy = repositoryService.createDeployment()
                .addClasspathResource("bpmn/qingjia3.bpmn")
                .addClasspathResource("bpmn/qingjia.png")
                .name("qingjia").deploy();
        log.info("模版创建成功:{}",deploy);
        //2. 启动流程(创建流程实例)
        //2.1 填充执行人变量
        Map<String,Object> variables = new HashMap<>();
        variables.put("a1","蔡申友");//填充发起人
        variables.put("c1","王老师");//候选老师1
        variables.put("c2","陈老师");//候选老师2
        variables.put("c3","张老师");//候选老师3
        //2.2 启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia",variables);
        log.info("启动流程成功:{}",processInstance);
        //3. 发起人,查询自己的任务列表
        List<Task> list = taskService.createTaskQuery().list();
        //4. 发起流程 执行任务一
        for (Task task : list) {
            log.info("正在执行的任务id为:{}", task.getId());
            log.info("正在执行的任务名称为:{}", task.getName());
            //请假原因,任务一 需要填写的额外内容,一般是前端表单传来的数据(用户自己输入)
            Map<String,Object> leaveContent = new HashMap<>();
            leaveContent.put("username","蔡申友");//请假人
            leaveContent.put("reason","出差工作..");//请假原因
            leaveContent.put("startDate","2024年8月21日18:10:53");//请假开始时间
            leaveContent.put("days",20);//请假天数
            //完成任务
            taskService.complete(task.getId(),leaveContent);
            log.info("任务完成....");
        }
    }
    // 候选人之一,王老师执行任务
    @Test
    public void test2(){
        //模拟登录,防止UsernameNotFoundException错误
        securityUtil.logInAs("王老师");
        //查询候选人或者执行人 和指定人相同的任务
        List<Task> list = taskService.createTaskQuery().taskCandidateOrAssigned("王老师").list();
        //全部任务拾取: 将指定用户从候选人提升为审批人
        for (Task task : list) {
            taskService.claim(task.getId(),"王老师");
            //执行任务
            Map<String,Object> map = new HashMap<>();
            map.put("leavenContend","同意请假");
            taskService.complete(task.getId(),map);
        }
    }

归还任务(消权)/ 交接任务(换人)

归还任务:将执行人列设置为null。

交接任务:将执行人的值设置为别人的名称 / id。

    //归还:拾取的用户不审批了。就放弃审批人的操作
    //交接:拾取任务后如果不想操作那么可以归还任务,也可以将任务交接给其他用户
    @Test
    public void test3() {
        //模拟登录,防止UsernameNotFoundException错误
        new SecurityUtil().logInAs("王老师");

        List<Task> list = taskService.createTaskQuery()
                .taskCandidateOrAssigned("王老师") // 根据 审批人或者候选人 来查询待办任务
                .list();
        for (Task task : list) {
            // 归还操作的本质其实就是设置审批人为空
            // taskService.unclaim(task.getId());

            //任务交接
            taskService.setAssignee(task.getId(), "陈老师");
        }
    }

候选人组(防止新增该权限的人)

按照组名查任务,该组名一般都是权限 按照组查找任务,查找到了然后进行提升权限。

拾取任务

拾取任务的目的是将候选人提升为审批人

    //查询经理部门的任务
    @Test
    public void test4() {
        //模拟登录,防止UsernameNotFoundException错误
        //new SecurityUtil().logInAs("孙经理");

        //根据候选人查询任务
        List<Task> list = taskService.createTaskQuery()
                .taskCandidateGroup("manageGroup") // 查询经理部门的任务
                .list();

        //任务拾取: 将指定用户从候选人提升为审批人
        for (Task task : list) {
            taskService.claim(task.getId(), "孙经理");
        }
    }

其他操作都和上述相似

查询历史任务

历史任务的查询需要使用HistoryService完成,主要就是根据各种条件从前面讲过的一堆历史表中查询数据

    @Autowired
    private HistoryService historyService;

    @Test
    public void test9(){
        HistoricTaskInstanceQuery instanceQuery = historyService.createHistoricTaskInstanceQuery()
                .includeProcessVariables()//包含流程变量(配合下面使用)
                .orderByHistoricTaskInstanceEndTime().desc()//按历史任务实例结束时间排序
                .finished()//只查询已完成的任务
                .taskAssignee("张三");//根据执行人查询

        //自定义流程变量  条件查询
        //instanceQuery.processVariableValueGreaterThan("days", 1);

        //查询历史流程
        List<HistoricTaskInstance> list = instanceQuery.list();
        for (HistoricTaskInstance history : list) {
            System.out.println("Id: " + history.getId());
            System.out.println("ProcessInstanceId: " + history.getProcessInstanceId());
            System.out.println("StartTime: " + history.getStartTime());
            System.out.println("Name: " + history.getName());
            Map<String, Object> processVariables = history.getProcessVariables();
            System.out.println(processVariables.get("days").toString());
            System.out.println(processVariables.get("reason").toString());
            System.out.println("=======================================");
        }
    }

查询条件API说明

方法名称
processInstanceBusinessKey(String processInstanceBusinessKey)根据流程实例业务Key查询
taskId(String taskId)根据任务ID查询
taskAssignee(String taskAssignee) | taskAssigneeLike(String taskAssignee)根据执行人查询
finished()已完成的(申请过、同意过)
unfinished()未完成任务
orderByHistoricTaskInstanceEndTime().desc()按照执行时间排序
taskName(String var1) | taskNameLike(String var1)根据节点任务名称查询
list()返回分页数据
includeProcessVariables()包含流程变量(配合下面使用)
processVariableValueEquals(String variableName, Object variableValue)两个值相等
processVariableValueNotEquals(String variableName, Object variableValue)两个值不相等
processVariableValueGreaterThan(String name, Object value)大于
processVariableValueLessThan(String name, Object value)小于

流程网关

网关用于控制流程的执行流向,它的作用是在流程执行时进行决策,决定流程的下一个执行步骤。Activiti7中,有以下几种类型的网关:

  1. 排他网关:用于在流程中进行条件判断,根据不同的条件选择不同的分支路径,只有满足条件的分支会被执行,其他分支会被忽略,排除其他路径,只走一条。一条同意即可

  2. 并行网关:用于将流程分成多个并行的分支,这些分支可以同时执行,当所有分支都执行完毕后,流程会继续向下执行,全部路径都走,都同意才可,有一个拒绝直接结束流程实例。

  3. 包容网关:用于根据多个条件的组合情况选择分支路径,可以选择满足任意一个条件的分支执行,或者选择满足所有条件的分支执行,带条件的只走一条,没条件的必走

排他网关

排他网关用于对流程中分支进行决策,当执行到达这个网关时,会按照所有出口顺序流定义的顺序对它们进行计算,选择第一个条件为true的顺序流继续流程。

image-20240821232356684

注意:这种设计有一点小bug,当有多个条件都符合会选取到第一个为true的路径进行走。

并行网关

并行网关用于将流程分成多个并行的分支,这些分支可以同时执行,当所有分支都执行完毕后,流程会继续向下执行

  • fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支
  • join汇聚: 所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后, 流程就会通过网关
image-20240821234253512

包容网关

包含网关用于根据多个条件的组合情况选择分支路径,可以选择满足任意一个条件的分支执行(有条件必须执行,无条件的必须执行)

image-20240821235238649

业务id对接

目前我们已经基本完成了activiti的学习,我们发现目前的工作流其实是脱离我们的实际业务存在的

如果想将activiti与实际业务联系起来,需要用到它提供的一个字段:buinessId,这个字段用来记录业务表的主键

我们可以在启动流程的时候设置

ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables);

你可以直接通过 businessKey 查询到与特定请假申请相关的流程实例。

// 查询请假申请的流程实例
List<HistoricProcessInstance> processInstances = historyService.createHistoricProcessInstanceQuery()
    .processInstanceBusinessKey(leaveApplication.getId().toString())
    .list();

for (HistoricProcessInstance instance : processInstances) {
    System.out.println("Process Instance ID: " + instance.getId());
    System.out.println("Start Time: " + instance.getStartTime());
    System.out.println("End Time: " + instance.getEndTime());
    System.out.println("Status: " + (instance.getEndTime() == null ? "Running" : "Completed"));
}

通过 businessKey,你可以查询到与特定请假申请相关的所有流程实例,这有助于审计和监控流程实例的状态。

// 查询请假申请的流程实例
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
    .processInstanceBusinessKey(leaveApplication.getId().toString())
    .singleResult();

if (processInstance != null && processInstance.getEndTime() != null) {
    // 流程实例已完成
    LeaveStatus status = processInstance.getEndTime().isBefore(LocalDate.now()) ? LeaveStatus.APPROVED : LeaveStatus.REJECTED;
    leaveApplication.setStatus(status);
    leaveApplicationRepository.save(leaveApplication);
}

通过 businessKey,你可以轻松地找到与特定请假申请相关的流程实例,并根据流程实例的状态更新请假申请的状态

注意:与执行人查询的区别

查询目的不同:
使用执行人查询:主要关注当前分配给特定执行人的任务,适用于查询当前执行人需要处理的任务
使用 businessKey 查询:关注与特定业务实体相关的所有流程实例,适用于审计和监控流程实例的状态。
查询范围不同:
使用执行人查询:仅限于当前分配给执行人的任务,不涉及流程实例的全局状态。
使用 businessKey 查询:可以查询到与特定业务实体相关的所有流程实例,包括已完成的流程实例。
查询深度不同:
使用执行人查询:通常只涉及到当前的任务,可能需要额外步骤来获取流程实例的信息。
使用 businessKey 查询:可以直接获取到流程实例的完整信息,包括历史记录

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

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

相关文章

深度优先搜索-放苹果

放苹果 http://noi.openjudge.cn/ch0205/666/ #include<bits/stdc.h> using namespace std;int dfs(int,int); //第一个赋值为1 其余为0 int a[11]{1},ans,n,m;int main(){ int k; cin>>k; for(int i1;i<k;i){ ans0; cin>>m>>n; dfs(m,1);//m个…

滴答拍摄影项目

TOC springboot0796滴答拍摄影项目 第1章 绪论 1.1背景及意义 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中人们对拍摄影方面的要求也在不断提高&#xff0c;旅游的人数更是不断增加&#xff0c;使得拍摄影项…

FPGA开发——IIC协议介绍

简介 其实关于IIC通信协议的相关原理的相关理论我在最开始的文章当中进行过讲解&#xff0c;但是没有详细的去进行过说明&#xff0c;在今天的这篇文章中我们就来详细说明一下IIC协议的相关理论。 一、IIC 总线组成 1、定义介绍 采用串行总线可以简化系统硬件结构、减小系统…

科技在环境保护中的作用

面对日益严峻的环境问题&#xff0c;‌科技在环境保护中发挥着越来越重要的作用。‌从清洁能源的开发和应用&#xff0c;‌到环保技术的不断进步&#xff0c;‌再到智能环保监测系统的建立&#xff0c;‌科技正以前所未有的力量推动着环境保护事业的发展。‌ 清洁能源技术的开…

揭秘!移动安全管理系统是什么?有什么功能?(从小白到精通一文揭晓!)

在2024年&#xff0c;移动终端管控软件在企业和组织中的应用日益广泛。 移动安全管理系统不仅提高了管理效率&#xff0c;还增强了数据安全性和移动办公的便捷性。 以下是六款值得推荐的移动终端管控软件&#xff1a; 1. 安企神 特点&#xff1a;作为行业领头羊&#xff0c;…

【PHP入门教程】PHPStudy环境搭建+HelloWorld运行

文章目录 PHP 的历史PHP 的用途PHP 的特点和优势PHP 环境搭建环境准备安装window 安装CentOS / Ubuntu / Debian 安装 第一个Hello World使用Apache服务运行命令行运行代码 PHP 的历史 PHP&#xff08;Hypertext Preprocessor&#xff09;超文本预处理器是一种开源的通用脚本语…

m3u8转mp4,3款软件让你快速转换!

在这个数字化时代&#xff0c;视频已成为我们生活中不可或缺的一部分。无论是学习资料、娱乐影片还是工作演示&#xff0c;视频文件总是以不同的格式存在。其中&#xff0c;M3U8和MP4作为两种常见的视频格式&#xff0c;各有其应用场景。然而&#xff0c;有时我们需要将M3U8格式…

如何在算家云搭建模型Linly-Talker(数字人文本配音)

一、模型介绍 Linly-Talker 是一款集成了多种人工智能技术的数字人对话系统&#xff0c;集成了各种技术,例如 Whisper、Linly、微软语音服务和 SadTalker 会说话的生成系统。该系统部署在 Gradio 上&#xff0c;用户可通过上传图像与 AI 助手进行互动&#xff0c;可以根据自己…

UDP+TCP

一、UDP协议 1.recvfrom:recvform(int sockfd,void *buf,size_t len,int flags,struct sockaddr *src_addr,socklen_t *addrlen); 参数&#xff1a;socket的fd; 保存数据的空间地址 &#xff1b; 空间大小&#xff1b; 默认接收方式&#xff08;默认阻塞&#xf…

【蓝桥杯集训100题】scratch游泳时长 蓝桥杯scratch比赛专项预测编程题 集训模拟练习题第27题

目录 scratch游泳时长 一、题目要求 编程实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、python资料 scratc…

5 分钟 Stable Diffusion 本地安装指南

一、Stable Diffusion 简介 Stable Diffusion 是一款非常强大的基于深度学习的 AI 图像生成技术。它由众多研究团队和开发者共同努力而成&#xff0c;其中包括德国慕尼黑大学和总部位于纽约的 RunwayML 公司的国际研究团队。 Stable Diffusion 的工作原理基于扩散模型。简单来…

TCP和UDP编程的学习

UDP编程特点&#xff1a;c/s模型 不可靠 &#xff1b; 无链接 &#xff1b;数据报形式&#xff08;效率高&#xff09;&#xff1b; TCP编程特点&#xff1a; 面向链接&#xff1b; 可靠传输&#xff1b;&#xff08;保证数据准确可靠&#xff09;&#xff1b; 面向字节流&a…

ES6解构赋值详解;全面掌握:JavaScript解构赋值的终极指南

目录 全面掌握&#xff1a;JavaScript解构赋值的终极指南 一、数组解构赋值 1、基本用法 2、跳过元素 3、剩余元素 4、默认值 二、对象解构赋值 1、基本用法 2、变量重命名 3、默认值 4、嵌套解构 三、复杂的嵌套结构解构 四、函数参数解构赋值 1、对象解构作为函…

wordrpess心得

基本心得 多语言切换方式&#xff1a; 使用en.xxxx和jp.xxx多域名实现&#xff0c;配合宝塔面板管理 举例&#xff1a;切换按钮 <div class"language-switcher"><a class"language-button" href"https://xxx">JA</a> <a c…

【C++ Primer Plus习题】2.2

问题: 解答: #include <iostream> using namespace std;#define LONG_TO_MA 220int main() {double distance 0;cout << "请输入距离(单位为long):";while (true){cin >> distance;if (cin.fail()){cout << "输入有误!请输入数字:&qu…

一键过原创工具,轻松搬运文章秒变原创

在当今信息爆炸的时代&#xff0c;内容创作的重要性不言而喻。对于追求效率的创作者来说&#xff0c;一键过原创工具无疑是一股清新的风潮&#xff0c;它以其独特的功能&#xff0c;让文章搬运变得轻松&#xff0c;瞬间转化为原创佳作。一键过原创工具的诞生&#xff0c;不仅代…

【OpenCV】 中使用 Lucas-Kanade 光流进行对象跟踪和路径映射

文章目录 一、说明二、什么是Lucas-Kanade 方法三、Lucas-Kanade 原理四、代码实现4.1 第 1 步&#xff1a;用户在第一帧绘制一个矩形4.2 第 2 步&#xff1a;从图像中提取关键点4.3 第 3 步&#xff1a;跟踪每一帧的关键点 一、说明 本文针对基于光流法的目标追踪进行叙述&am…

Godot利用刚体让3d物体动起来

效果如图 有人说你怎么能控制刚体不是物理引擎控制吗&#xff1f;哈哈&#xff0c;非也&#xff0c;非也&#xff0c;我控制不是刚体而是相机&#xff0c;记住刚体的运动状态是不受玩家控制的&#xff0c;你最多给一个力&#xff0c;但是这个力怎么让物体运动是由物理引擎控制。…

Vivado生成bitstream时报[DRC CFGBVS-1]警告的问题

目录 警告信息解决方法 警告信息 [DRC CFGBVS-1] Missing CFGBVS and CONFIG_VOLTAGE Design Properties: Neither the CFGBVS nor CONFIG_VOLTAGE voltage property is set in the current_design. Configuration bank voltage select (CFGBVS) must be set to VCCO or GND, …

6.Linux_服务器搭建

TFTP服务器 1、概述 什么是TFTP服务器&#xff1a; TFTP&#xff08;Trivial File Transfer Protocol&#xff09;即简单文件传输协议是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议&#xff0c;提供不复杂、开销不大的文件传输服务。端口号为69 介…