JavaEE从入门到起飞(九) ~Activiti 工作流
工作流
当一道流程逻辑需要用到多个表单的提交和多个角色的审核共同完成
的时候,就可以使用工作流。
工作流一般使用的是第三方技术,也就是说别人帮你创建数据库表和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、搭建环境:使用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:用于部署
流程定义
,可以添加、删除、查询和管理流程定义,相当与类
,所有人请假都需要按照这个模板执行。- act_re_deployment:流程部署,记录每次工作流的部署信息,包括部署名称和时间等
- act_ge_bytearray:流程资源表,系统会将流程定义的两个文件保存到这张表中
- act_re_procdef:流程定义表,记录每个流程定义的信息,包括流程定义的名称、版本号、部署ID等
-
RuntimeService:用于
启动
、查询和管理正在运行的流程实例,相当于对象
,启动之后每个人请假都要按照这个模板走。- act_ru_execution表(运行时流程实例表):插入一条新的流程实例的执行信息,包括流程实例ID、流程定义ID等
- act_ru_task(运行时任务表):插入一条新的任务记录,表示流程实例的启动任务
- act_ru_identitylink (运行时身份关联表):插入一条新的身份关联记录,表示流程实例的启动任务与相关用户的关系
-
TaskService:用于查询和管理当前用户可以操作的
任务
,以及完成任务,相当于栈中的方法
。流程启动之后,不同的人登录的时候,需要查询是否要自己要执行的任务,如果有可以对流程进行操作,一般是在一个专门的事务待办页面
- act_ru_execution表中发现,新增了【经理审批】待执行的节点,而完成的【填写请假单】节点被删除了
- act_ru_task表中,待办任务也变成了李四【经理审批】, 一个任务结束后,该任务的记录就会在ru_task表中删除,会在运行时任务表添加下一条需要执行的任务。
- act_ru_variable表中会存储代码中传入的表单数据
-
HistoryService:用于查询
历史数据
,例如已完成的流程实例、已删除的流程实例、用户任务等,历史统计,主要是用于查询,比如说统计某个人这个月请了多少假。相当于日志
。- act_hi_procinst:历史的流程实例
- act_hi_actinst:历史的活动实例
- **act_hi_taskinst:历史的任务实例 ** 任务执行完毕后,endTime结束时间会有值。
- act_hi_identitylink:历史流程用户关系
- 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}来代替
代码实现
/*** @author windStop* @version 1.0* @description 测试:含有表达式的代理人* @date 2024年08月21日17:37:23*/
@SuppressWarnings("ALL")
@SpringBootTest
@Slf4j
public class ActivitiTestPlus {@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;@Testpublic 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("任务完成....");}}//老师同意@Testpublic 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("任务二执行完毕...");}}//老师不同意@Testpublic 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表保存了候选人信息.
拾取任务(提权)
提升候选人的权限为执行人。
// 测试候选人@Testpublic 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","王老师");//候选老师1variables.put("c2","陈老师");//候选老师2variables.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("任务完成....");}}// 候选人之一,王老师执行任务@Testpublic 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。
//归还:拾取的用户不审批了。就放弃审批人的操作//交接:拾取任务后如果不想操作那么可以归还任务,也可以将任务交接给其他用户@Testpublic 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(), "陈老师");}}
候选人组(防止新增该权限的人)
按照组名查任务,该组名一般都是权限
按照组查找任务,查找到了然后进行提升权限。
拾取任务
拾取任务的目的是将候选人提升为审批人
//查询经理部门的任务@Testpublic void test4() {//模拟登录,防止UsernameNotFoundException错误//new SecurityUtil().logInAs("孙经理");//根据候选人查询任务List<Task> list = taskService.createTaskQuery().taskCandidateGroup("manageGroup") // 查询经理部门的任务.list();//任务拾取: 将指定用户从候选人提升为审批人for (Task task : list) {taskService.claim(task.getId(), "孙经理");}}
其他操作都和上述相似
查询历史任务
历史任务的查询需要使用HistoryService完成,主要就是根据各种条件从前面讲过的一堆历史表中查询数据
@Autowiredprivate HistoryService historyService;@Testpublic 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中,有以下几种类型的网关:
-
排他网关:用于在流程中进行条件判断,根据不同的条件选择不同的分支路径,只有满足条件的分支会被执行,其他分支会被忽略,
排除其他路径,只走一条。一条同意即可
-
并行网关:用于将流程分成多个并行的分支,这些分支可以同时执行,当所有分支都执行完毕后,流程会继续向下执行,
全部路径都走,都同意才可
,有一个拒绝直接结束流程实例。 -
包容网关:用于根据多个条件的组合情况选择分支路径,可以选择满足任意一个条件的分支执行,或者选择满足所有条件的分支执行,
带条件的只走一条,没条件的必走
排他网关
排他网关用于对流程中分支进行决策,当执行到达这个网关时,会按照所有出口顺序流定义的顺序对它们进行计算,选择第一个条件为true的顺序流继续流程。
注意:这种设计有一点小bug
,当有多个条件都符合会选取到第一个为true的路径进行走。
并行网关
并行网关用于将流程分成多个并行的分支,这些分支可以同时执行,当所有分支都执行完毕后,流程会继续向下执行
- fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支
- join汇聚: 所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后, 流程就会通过网关
包容网关
包含网关用于根据多个条件的组合情况选择分支路径,可以选择满足任意一个条件的分支执行(有条件必须执行,无条件的必须执行)
业务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 查询:可以直接获取到流程实例的完整信息,包括历史记录
。
相关文章:

JavaEE从入门到起飞(九) ~Activiti 工作流
工作流 当一道流程逻辑需要用到多个表单的提交和多个角色的审核共同完成的时候,就可以使用工作流。 工作流一般使用的是第三方技术,也就是说别人帮你创建数据库表和service层、mapper层,你只需要注入工具接口即可使用。 原理:一…...

微服务的保护
一、雪崩问题及解决方案 1.雪崩问题 微服务之间,一个微服务依赖多个其他的微服务。当一个微服务A依赖的一个微服务B出错时,微服务A会被阻塞,但其他不依赖于B的微服务不会受影响。 当有多个微服务依赖于B时,服务器支持的线程和并…...

2024前端面试题-网络篇
1.跨域问题 同源策略:需要协议、域名、端口号相同跨域原因:不符合同源策略便会产生跨域问题解决跨域:JSONP、配置代理、通过CORS解决 2.RPC和HTTP的区别 主要区别是序列化和反序列化,RPC通过二进制高效传输,HTTP是j…...

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——6.vector
1.杨辉三角 . - 力扣(LeetCode) 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 class Solution { public:vector<vector<int>> generate(int numRows) {vector<vector<int>> arr;int i 0;int j 0;for (i…...

设计模式---简单工厂模式
简单工厂模式(Simple Factory Pattern) 是一种创建型设计模式,它定义了一个工厂类,通过这个工厂类可以创建不同类型的对象。简单工厂模式的主要目的是将对象的创建逻辑集中在一个地方,简化客户端的代码,使得…...

Vue | Vue 中的 refInForde 用法
refInFor:如果你在渲染函数中给多个元素都应用了相同的 ref 名,那么 $refs.myRef 会变成一个数组。 vue中的refInFor属性是Vue框架中用于在循环渲染的元素上设置引用的一种方式。 在Vue中,ref属性通常用于给元素或子组件注册引用信息&am…...

【原创】java+swing+mysql房屋租赁管理系统设计与实现
个人主页:程序员杨工 个人简介:从事软件开发多年,前后端均有涉猎,具有丰富的开发经验 博客内容:全栈开发,分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片,希望和大家…...

Django 中render、redirect 和 HttpResponse的区别
在 Python 的 Web 框架 Django 中,render, redirect 和 HttpResponse 是用于处理 HTTP 响应的不同函数,它们各自有不同的用途: HttpResponse: HttpResponse 是 Django 中最基本的响应对象,用于返回给客户端的 HTTP 响应…...

CRYPTO 2020
分类文章编号安全模型1-6公钥加密,功能加密,见证加密7-12后量子密码13-20密码分析21-31最佳论文32-34多方安全计算35-49真实应用50-55零知识证明56-62格和相关难题63-68泄露和外包加密69-74非交互式零知识证明,共识和延迟函数75-79构建80-85Security Models 1. Handling Ad…...

java 函数接口Consumer简介与示例【函数式编程】【Stream】
Java 8 中的 消费者接口Consumer 是一个函数接口,它可以接受一个泛型 类型参数,它属于java.util.function包。我们来看看Java函数接口库中的定义: FunctionalInterface public interface Consumer<T> {/*** Performs this operation o…...

黑神话:悟空-配置推荐
显卡推荐(按类别整理) 1. GTX 10系列、GTX 16系列: 如果希望体验光线追踪,建议根据预算升级到RTX 40系列显卡。对于1080p分辨率,至少需要RTX 4060才能流畅运行。 2. RTX 20系列: RTX 2060、RTX 2070&#…...

Android14 蓝牙设备类型修改
Android14 蓝牙设备类型设置修改设置 文章目录 Android14 蓝牙设备类型设置修改设置一、前言二、修改蓝牙设备类型1、蓝牙设备类型和对应的属性2、Debug设备设置和获取蓝牙设备类型3、系统源码中设置蓝牙设备类型4、Java代码中设置蓝牙prop属性可行吗? 三、其他1、A…...

vue3 语法糖<script setup>
在 Vue 3 中,<script setup>是一种新的语法糖,它极大地简化了组件的编写方式。 <script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。 基本概念 简洁的语法…...

微服务设计原则——高性能:异步与并发
文章目录 1.异步1.1 调用异步1.2 流程异步1.3 数据流异步1.4 小结 2.并发2.1 请求并发2.2 冗余请求2.3 小结 参考文献 1.异步 对于处理耗时长的任务,如果采用同步等待的方式,会严重降低系统的吞吐量,可以采用异步化进行解决。 异步…...

机器学习——决策树,朴素贝叶斯
一.决策树 决策树中的基尼系数(Gini Index)是用于衡量数据集中不纯度(或混杂度)的指标。基尼系数的取值范围在0到0.5之间,其中0表示数据完全纯(同一类别),0.5表示数据完全混杂。 基…...

C语言基础(十)
编译预处理命令: 预编译命令在C语言中用于在编译前进行一些特定的处理和控制,帮助程序员更灵活地管理源代码和控制编译过程。 C语言常用的预编译命令: #include:用于包含头文件,将另一个文件的内容插入到当前文件中…...

人像比对-人证比对-人脸身份证比对-人脸身份证实名认证-人脸三要素对比-实人认证
人脸身份证实名认证是一种基于生物识别技术的身份验证方式,主要依托证件OCR识别技术、活体检测、人脸比对等技术手段,对用户身份信息真实性进行核验,确保用户为真人且为本人。以下是关于人脸身份证实名认证的详细解析: 一、认证流…...

Android 上下滑隐藏显示状态栏
一、DisplayPolicy类中监听滑动事件,然后发送广播事件 Android12类路径: frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.javamSystemGestures new SystemGesturesPointerEventListener(mUiContext, mHandler,new SystemGest…...

USBCAN-II/II+使用方法以及qt操作介绍
一.USBCAN-II/II介绍 USBCAN-II/II 是一款常用的 USB-CAN 转换器,广泛应用于汽车电子、工业自动化等领域。以下是使用该设备的一般步骤和方法: 1. 硬件连接 连接设备:将 USBCAN-II/II 的 USB 接口连接到计算机的 USB 端口。 连接 CAN 网络…...

笔记-系统规划与管理师-案例题-2022年-IT服务部署实施
【说明】 某大型企业去年信息化投入大,完成了重点核心业务系统的建设。由于应急相应预案制定得不充分并且未开展演练,出现了系统性故障时,部分关键的应用系统不可用且在12小时内未能完成恢复业务,给企业带来了较大损失。 为加强该…...

Kubernetes 清理资源常用的 Kubernetes 清理命
清理特定状态的 Pod: 清理 Evicted 状态的 Pod: kubectl get pods --all-namespaces -o wide | grep Evicted | awk {print $1,$2} | xargs -L1 kubectl delete pod -n清理 Error 状态的 Pod: kubectl get pods --all-namespaces -o wide | g…...

【数据结构初阶】二叉树--基本概念
hello! 目录 一、树 1.1 树的概念和结构 1.2 树的相关术语 1.3 树的表示 1.4 树形结构实际应用场景 二、二叉树 2.1 概念和结构 2.2 特殊的二叉树 2.2.1 满二叉树 2.2.2 完全二叉树 2.3 二叉树的存储结构 2.3.1 顺序结构 2.3.2 链式结构 …...

Pytorch添加自定义算子之(12)-开闭原则设计tensorrt和onnxruntime推理语义分割模型
一、开闭原则 开闭原则是SOLID原则中的一个,指的是尽量使用开放扩展,关闭修改的设计原则。 在C++中如何使用开闭原则导出动态库,可以按照以下步骤进行: 定义抽象基类:定义动态库中的抽象基类,该基类应该封装可扩展的接口。 实现派生类:实现基类的派生类,这些派生类将封…...

第二百零九节 Java格式 - Java数字格式类
Java格式 - Java数字格式类 以下两个类可用于格式化和解析数字: java.text.NumberFormatjava.text.DecimalFormat NumberFormat 类可以格式化一个数字特定地区的预定义格式。 DecimalFormat 类可以格式化数字以特定区域设置的自定义格式。 NumberFormat类的 getXXXInstance…...

LSI-9361阵列卡笔记
背景 要将raid0更改为JBOD直通模式 注意的点是要先将raid模式调整为JBOD之后重启机器,即可 备注:转换过程中硬盘中的数据未丢失。 步骤贴图 refer https://zhiliao.h3c.com/questions/dispcont/123250 https://blog.csdn.net/GreapFruit_J/article/…...

ArcGIS热点分析 (Getis-Ord Gi*)——基于地级市尺度的七普人口普查数据的热点与冷点分析
先了解什么是热点分析 ? 热点分析 (Getis-Ord Gi*) 是一种用于空间数据分析的技术,主要用于识别地理空间数据中值的聚集模式,可以帮助我们理解哪些区域存在高值或低值的聚集,这些聚集通常被称为“热点”或“冷点”,Gi* 统计量为…...

ASIACRYPT 2021
分类文章编号获奖论文1-3后量子密码4-9多方计算10-15物理攻击,泄露和对策16-21理论22-27公钥密码和鉴权密钥交换28-33高级加密和签名34-39对称密钥构建40-46量子安全47-53获奖论文54对称密码分析55-66增强型公钥加密和时间锁难题67-72同态加密和加密搜索73-77NIZK和SNARK78-80…...

C#学习之路day1
目录 一、概念:.net和c# 二、.net发展方向 三、.Net两种交互模式 四、创建项目 五、vs的组成部分 六、我的第一个C#程序 七、多个项目时启动项目的设置 八、注释 九、快捷键 一、概念:.net和c# 1、.net/dotnet :一般指.Net Framework框架&#…...

【安当产品应用案例100集】010-基于国密UKEY的信封加密应用案例
安当有个客户开发了一套C/S架构的软件,Server在云端,Client由不同的用户使用。最初软件设计开发的时候,没有考虑数据安全形势日渐严峻的问题,Server端和Client端直接就建立一个socket连接来进行通信,Server端发出去的数…...

扫码点餐系统小程序功能分析
扫码点餐系统小程序通常具备以下核心功能: 用户界面:提供直观易用的界面,方便用户浏览菜单、选择菜品、查看订单状态等 。菜单展示:展示餐厅的菜单,包括菜品图片、价格、描述等信息 。扫码点餐:用户通过…...