Flowable入门
Flowable初体验
Flowable是什么
Flowable 是一个使用 Java 编写的轻量级业务流程引擎,常用于需要人工审批相关的业务,比如请假、报销、采购等业务。
为什么要使用工作流呢?
-
对于复杂的业务流程,通过数据库的状态字段难以控制和维护,工作流引擎则更易于维护和拓展
-
工作流的流程图更加直观,流程走到了哪里,一目了然
Flowable初体验
官网例子:Getting Started · Flowable Open Source Documentation
照着官网的例子做即可,都有详细的步骤。
SpringBoot整合Flowable
添加maven依赖
新建maven工程,pom.xml引入如下依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.7.2</version></dependency></dependencies>
flowable默认使用H2数据库,这是一种内存型数据库,我们需要将数据持久化,这里使用的MySQL,SpringBoot版本则是使用2.7.5
配置信息
由于使用了flowable的starter,先看在spring.factories配置了哪些自动装配类:
可以看到,这里有个自动装配类:org.flowable.spring.boot.ProcessEngineAutoConfiguration
这里有很多Properties结尾的类,都是可以自定义的属性,以后再详细看。先看FlowableProperties
这个类:
@ConfigurationProperties(prefix = "flowable"
)
public class FlowableProperties {private boolean checkProcessDefinitions = true;private boolean asyncExecutorActivate = true;private boolean asyncHistoryExecutorActivate = true;private boolean restApiEnabled;private String activityFontName = "Arial";private String labelFontName = "Arial";private String annotationFontName = "Arial";private String deploymentName = "SpringBootAutoDeployment";private String databaseSchemaUpdate = "true";private String databaseSchema;private boolean useLockForDatabaseSchemaUpdate = false;/** @deprecated */@Deprecatedprivate boolean isDbIdentityUsed = true;private boolean isDbHistoryUsed = true;private HistoryLevel historyLevel;private String processDefinitionLocationPrefix;private List<String> processDefinitionLocationSuffixes;private boolean jpaEnabled;private List<String> customMybatisMappers;private List<String> customMybatisXMLMappers;protected boolean formFieldValidationEnabled;private Duration lockPollRate;private Duration schemaLockWaitTime;private boolean enableHistoryCleaning;private String historyCleaningCycle;@DurationUnit(ChronoUnit.DAYS)private Duration historyCleaningAfter;private int historyCleaningBatchSize;private boolean historyCleaningSequential;public FlowableProperties() {this.historyLevel = HistoryLevel.AUDIT;this.processDefinitionLocationPrefix = "classpath*:/processes/";this.processDefinitionLocationSuffixes = Arrays.asList("**.bpmn20.xml", "**.bpmn");this.jpaEnabled = true;this.formFieldValidationEnabled = false;this.lockPollRate = Duration.ofSeconds(10L);this.schemaLockWaitTime = Duration.ofMinutes(5L);this.enableHistoryCleaning = false;this.historyCleaningCycle = "0 0 1 * * ?";this.historyCleaningAfter = Duration.ofDays(365L);this.historyCleaningBatchSize = 100;this.historyCleaningSequential = false;}
}
这里有很多属性,先挑几个看一下:
-
checkProcessDefinitions:表示是否检查
classpath*:/processes/
目录下的流程定义。默认值为true,表示要检查,且会自动部署流程。若设置为false,则不会检查,也就相当于关闭了自动部署流程的功能。 -
asyncExecutorActivate:异步执行器是否开启,默认值为true,用于开启异步任务
-
processDefinitionLocationPrefix:流程定义所在位置的前缀,默认值为
processDefinitionLocationPrefix
,可以自定义为自己喜欢的目录。 -
processDefinitionLocationSuffixes:流程定义文件名后缀,默认支持
.bpmn20.xml, .bpmn
文件。
在application.yml文件中,添加配置信息,如果不添加,就使用默认配置:
server:port: 8888spring:datasource:username: rootpassword: root# nullCatalogMeansCurrent=true,加了这个参数才会自动生成表url: jdbc:mysql://localhost:3306/flowable_study?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true# flowable打印sql语句
logging:level:org.flowable.engine.impl.persistence.entity.*: debugorg.flowable.task.service.impl.persistence.entity.*: debug
打印flowable的SQL语句,是为了方便分析调用的api底层做了什么。
表结构简单分析
启动SpringBoot项目,则会在数据库flowable_study
中自动生成79张表:
其中:
- APP 表示这都是跟应用程序相关的表。
- CMMN 表示这都是跟 CMMN 协议相关的表。
- CO(CONTENT)表示这都是跟内容引擎相关的表。
- DMN 表示这都是跟 DMN 协议相关的表。
- EVT(EVENT)表示这都是跟事件相关的表。
- FO(FORM)表示这都是跟表单相关的表。
- GE(GENERAL)表示这都是通用表,适用于各种用例的。
- HI(HISTORY)这些是包含历史数据的表。当从运行时表中删除数据时,历史表仍然包含这些已完成实例的所有信息。
- ID(IDENTITY)表示这都是跟用户身份认证相关的表。
- PROCDEF(PROCESSDEFINE) 表示这都是跟记录流程定义相关的表。
- RE(REPOSITORY)表示这都是跟流程的定义、流程的资源等等包含了静态信息相关的表。
- RU(RUNTIME)代表运行时,这些是包含尚未完成的流程、案例等的运行时数据的运行时表。Flowable 仅在执行期间存储运行时数据,并在实例结束后删除记录,这使运行时表保持小而快。
- CHANNEL 表示这都是跟泳道相关的表。
- EV 表示这个是跟 FLW_ 搭配的,在这里似乎并没有一个明确的含义,相关的表也都是跟 Liquibase 相关的。
- EVENT 表示这都是跟事件相关的表。
暂时需要关注GE、RE、ID、RU、HI
这5类相关的表。
流程部署
自动部署
根据配置信息部分的介绍,可以知道,flowable集成到SpringBoot后,SpringBoot程序启动时就会扫描resources/processes
目录下后缀为.bpmn20.xml, .bpmn
的流程文件,并且会自动部署。
手动部署
自动部署,不方便对流程进行修改,一旦修改,要重启应用,才会生效,很麻烦,而手动部署则可以解决此问题。手动部署,又分为两种情况:
-
直接上传流程文件,进行部署
-
通过前端流程设计流程模型,拿到流程资源,进行部署
上传流程文件
这个主要是写接口,便于通过接口进行部署更新:
public String deployProcess(MultipartFile file){if (file == null){return "流程部署文件不能为空";}String deploymentId = null;try {Deployment deploy = repositoryService.createDeployment().addInputStream(file.getOriginalFilename(), file.getInputStream()).deploy();deploymentId = deploy.getId();} catch (IOException e) {System.out.println("流程部署失败");return "流程部署失败: " + e.getMessage();}return deploymentId;}
流程模型部署
对于当前前后端分离的开发模式,一般是前端通过模型设计器,设计好流程模型后,再进行部署:
/*** 流程部署* @param modelId 模型id* @return*/@PostMapping(value = "/deploy/{modelId}")public Result deploy(@PathVariable String modelId){Model model = repositoryService.getModel(modelId);// 获取model的xml内容byte[] source = repositoryService.getModelEditorSource(modelId);// 部署流程Deployment deployment = repositoryService.createDeployment().name(model.getName()).key(model.getKey()).category(model.getCategory()).addBytes(model.getName() + ".bpmn", source).deploy();return Result.ok("操作成功",deployment.getId());}
这里的思路,就是获取模型的流程图二进制资源,进行部署更新。
流程实例
流程定义部署之后,需要创建流程实例,才算是真正的发起了一个流程。流程定义和流程实例,可以类比类和对象
的关系,流程定义可以看作是类,是对象的模板,而流程实例可以看作是对象。启动流程实例的几种方式:
-
org.flowable.engine.FormService#submitStartFormData
-
org.flowable.engine.RuntimeService#startProcessInstanceByKey
-
org.flowable.engine.RuntimeService#startProcessInstanceByKeyAndTenantId
-
org.flowable.engine.RuntimeService#startProcessInstanceById
-
org.flowable.engine.RuntimeService#startProcessInstanceWithForm
-
org.flowable.engine.RuntimeService#startProcessInstanceByMessage
-
org.flowable.engine.RuntimeService#startProcessInstanceByMessageAndTenantId
最常用的是org.flowable.engine.RuntimeService#startProcessInstanceByKey
,这里的key,指的是流程xml文件里面的id:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qpcQncZM-1683429303847)(file://F:\学习笔记\工作流相关\images\流程定义key.png?msec=1683427681427)]
通过如下方法,启动流程:
流程启动后,便可以拿到流程实例。
流程变量
流程变量,分为以下三类:
-
全局变量:会存入数据库,整个流程实例过程中,都可以使用
-
局部变量:会存入数据库,针对某个Execution或Task有效,其他节点不可见
-
瞬时变量:不存入数据库,目前我没用过
在流程实例没有结束之前,变量会被存入到表act_ru_variable
、act_hi_varinst
中,流程结束后,只保留act_hi_varinst
表中的记录,每个变量占一条记录。
下面主要看下,对于全局变量和局部变量,要怎样设置和使用:
全局变量
启动时设置
通过org.flowable.engine.RuntimeService#startProcessInstanceByXXX
方法启动流程时,有一个可选参数,用于在流程实例创建及启动时设置变量,如:
运行时设置
-
org.flowable.engine.RuntimeService#setVariable
-
org.flowable.engine.TaskService#setVariable
局部变量
局部变量,在运行时设置,针对某个Execution或Task有效,其他节点不可见
-
org.flowable.engine.RuntimeService#setVariablesLocal
-
org.flowable.engine.TaskService#setVariablesLocal
变量的使用
流程变量,常用于:
-
条件判断:如排他网关出线中使用变量,判断流程应该走哪条分支
-
审批人(组)的占位:通过变量,可以达到动态设置审批人的效果
-
事件监听器:使用Bean的名字
-
多实例:使用变量或变量表达式
参考官网:The Flowable API · Flowable Open Source Documentation
流程任务
流程任务,包括系统服务任务、用户任务、脚本任务等,这里针对的是用户任务。
待签收任务
待签收的任务,就是待认领的任务:
-
一个任务发给不同的人处理,任何一个人都可以抢办任务,即个人待签收任务
-
一个任务发给同一个岗位处理的,只需要是这个岗位的其中一人处理即可,这种情况就是岗位待签收的任务
个人待签收
个人待签收任务,即当前用户可以认领的任务,认领(签收)完成之后,就变成了个人的待办任务了。查询个人待签收任务的方法如下:
taskService.createTaskQuery().taskCandidateUser(userId).list()
userId就是要查询的用户id,或者其他唯一标识符,一般通过id或者工号来标识。
如果想自定义sql查询,也是可以的:
SELECTRES.*
FROMACT_RU_TASK RES
WHERERES.ASSIGNEE_ IS NULL
AND EXISTS (SELECTLINK.ID_FROMACT_RU_IDENTITYLINK LINKWHERELINK.TYPE_ = 'candidate'AND LINK.TASK_ID_ = RES.ID_AND (LINK.USER_ID_ = ?)
)
api接口执行的结果,就是执行了上面的sql
岗位待签收
岗位,对于系统来说,可以认为是角色,也就是说,你可以认领这个角色下的任务,变成自己的待办任务,通过api接口查询:
taskService.createTaskQuery().taskCandidateGroupIn(Arrays.asList(roleArr)).list();
roleArr
就是用户的角色id数组。
也可以通过sql查询:
SELECTRES.*
FROMACT_RU_TASK RES
WHERERES.ASSIGNEE_ IS NULL
AND EXISTS (SELECTLINK.ID_FROMACT_RU_IDENTITYLINK LINKWHERELINK.TYPE_ = 'candidate'AND LINK.TASK_ID_ = RES.ID_AND ((LINK.GROUP_ID_ IN(?, ?, ?)))
)
将个人待签收任务和岗位待签收任务合并,就是所有的待签收任务了。
可以通过api实现,也可以通过sql自己查询
api查询:
taskService.createTaskQuery().or().taskCandidateUser(userId).taskCandidateGroupIn(Arrays.asList(roleArr)).list()
sql查询:
SELECTRES.*
FROMACT_RU_TASK RES
WHERERES.ASSIGNEE_ IS NULL
AND EXISTS (SELECTLINK.ID_FROMACT_RU_IDENTITYLINK LINKWHERELINK.TYPE_ = 'candidate'AND LINK.TASK_ID_ = RES.ID_AND ((LINK.USER_ID_ = ?) OR (LINK.GROUP_ID_ IN(?, ?, ?)))
)
签收任务
签收任务就是认领任务,将待签收任务,变成待办任务
taskService.claim(taskId,userId);
也可以使用
taskService.setAssignee(taskId,userId);
两者的区别就是,如果该任务已经有人认领了:
-
taskService.claim
会抛出异常,认领失败 -
taskService.setAssignee
不会抛出异常,userId成为新的任务办理人
退回已签收
当你后悔签收了任务,且没有处理任务之前,是可以退回已经签收的任务的
设置任务办理人为空即可
taskService.setAssignee(taskId, null);
这样就能将已签收的任务退回,其他人可进行签收处理。
待办任务
查询待办
待办任务,即流程的办理的人的任务。
也是两种方式:
- 通过api查询
taskService.createTaskQuery().taskAssignee(userId).list()
- 通过sql查询
SELECTRES.*
FROMACT_RU_TASK RES
WHERERES.ASSIGNEE_ = ?
完成待办
完成待办,就是完成自己的任务,驱动流程走向下一个节点
taskService.complete(taskId,variables);
-
taskId
就是当前要完成的任务id -
variables
就是流程变量,类型是Map<String, Object>
,一般需要传递一些变量,比如采购金额是多少,审批是否通过,设置下一个节点的审批人等信息
已办任务
查询已办
已办任务,一般通过查询历史数据来得到的,可以通过HistoryService
来查询
historyService.createHistoricTaskInstanceQuery().finished().taskAssignee(userId).list()
当然,也可以自己写sql查询:
SELECTRES.*
FROMACT_HI_TASKINST RES
WHERERES.ASSIGNEE_ = ?
AND RES.END_TIME_ IS NOT NULL
这里的关键就是END_TIME_
不为 NULL
任务转办与委派
任务转办
任务的转办,就是将任务交给另外一个人全权处理:
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 设置原办理人是任务的委派人
taskService.setOwner(taskId,task.getAssignee());
// 设置任务新的办理者
taskService.setAssignee(taskId, targetUserId);
收到转办任务的人,通过下面的方式就可以完成任务:
taskService.complete(taskId)
任务委派
委派,就是给任务设置代理人,让他帮忙解决问题
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 设置原办理人是任务的所有者
taskService.setOwner(taskId,task.getAssignee());
// 设置任务代理人
taskService.delegateTask(taskId,targetUserId);
代理人解决任务(而不是完成任务)
taskService.resolveTask(taskId)
转办与委派的区别
-
任务委派只是委派人将当前的任务交给被委派人进行审批,解决 任务后又重新回到委派人身上。 为什么是解决呢?而不是完成,是因为当被委派人直接完成任务时,任务就不会回到委派人。
-
任务转办就是把任务全权交给转办的人处理,不会回到原办理人。
参考链接:工作流操作-委派、转办_asarao的博客-CSDN博客
多实例任务
多实例任务,是指将任务分配给多个人处理,当满足给定的条件时,流程才会到下一个节点。根据处理任务顺序的差异,可分为:
-
串行多实例:当上一个人处理了任务,下一个人才会接收到待办任务。例如:某个审批节点需要A、B、C三个人审批,可能的顺序是:A审批完成后,B才能接收到审批任务,B审批完成后,C才会接收到任务进行审批,依次进行。
-
并行多实例:审批人可以同时进行审批,这就是
并行
。例如:某个审批节点需要A、B、C三个人审批,则A、B、C可以同时审批,不需要等其他人审批完成,就可以审批。
配置参数
-
collection(集合): 传入List参数,一般为用户ID集合
-
elementVariable(元素变量):List中单个参数的名称,自定义变量名称
-
loopCardinality(基数):循环次数
-
isSequential:是否串行
-
completionCondition(完成条件):任务出口条件
-
nrOfInstances:实例总数
-
nrOfCompletedInstances:已完成的实例个数
-
loopCounter:已经循环的次数
-
nrOfActiveInstances:未完成的实例个数
串行多实例
在流程设计里,将isSequential
设置为true
即可
<multiInstanceLoopCharacteristics isSequential="true">...
</multiInstanceLoopCharacteristics>
图形上显示就就是三条水平的横线
并行多实例
在流程设计里,将isSequential
设置为false
即可
<multiInstanceLoopCharacteristics isSequential="false">...
</multiInstanceLoopCharacteristics>
图形上显示就就是三条垂直的横线
相关文章:

Flowable入门
Flowable初体验 Flowable是什么 Flowable 是一个使用 Java 编写的轻量级业务流程引擎,常用于需要人工审批相关的业务,比如请假、报销、采购等业务。 为什么要使用工作流呢? 对于复杂的业务流程,通过数据库的状态字段难以控制和…...

Scala Option类型,异常处理,IO,高阶函数
Option类型 实际开发中, 在返回一些数据时, 难免会遇到空指针异常(NullPointerException), 遇到一次就处理一次相对来讲还是比较繁琐的. 在Scala中, 我们返回某些数据时,可以返回一个Option类型的对象来封装具体的数据,从而实现有效的避免空指针异常。S…...
unity进阶学习笔记:单例模式
游戏框架: 游戏框架一般包括消息框架,状态机,管理器,工具类。 消息框架指游戏物体之的通信框架,虽然unity引擎自带一套消息框架,但该框架只能用于父子物体之间通信,无法实现大部分非父子关系的…...

软件测试——性能指标
登录功能示例: 并发用户数500; 响应时间2S; TPS到500; CPU不得超过75%; 性能指标有哪些? 响应时间 并发用户数 TPS CPU 内存 磁盘吞吐量 网络吞吐量 移动端FPS 移动端耗电量 APP启动时间 性能…...
leetcode 405. 数字转换为十六进制数
题目描述解题思路执行结果 leetcode 405. 数字转换为十六进制数. 题目描述 数字转换为十六进制数 给定一个整数,编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用 补码运算 方法。 注意: 十六进制中所有字母(a-f)都必须是小写。 十六进制…...

部门来了个软件测试,听说是00后,上来一顿操作给我看呆了...
前段时间公司新来了个同事,听说大学是学的广告专业,因为喜欢IT行业就找了个培训班,后来在一家小公司干了三年,现在跳槽来我们公司。来了之后把现有项目的性能优化了一遍,服务器缩减一半,性能反而提升4倍!给…...

使用篇丨链路追踪(Tracing)很简单:链路拓扑
作者:涯海 最近一年,小玉所在的业务部门发起了轰轰烈烈的微服务化运动,大量业务中台应用被拆分成更细粒度的微服务应用。为了迎接即将到来的双十一大促重保活动,小玉的主管让她在一周内梳理出订单中心的全局关键上下游依赖&#…...
2023年厦门等保二级备案办理流程
根据规定,已运营/运行或新建的第二级以上信息系统的企业,事业单位/行政机关/民办非企业单位/社团组织/其他组织必须办理等保备案。 2023年厦门等保二级备案办理流程 办理机构:公安局 办结时限:受理后10个工作日 办理方式:网上…...
提高开发效率,从这些小技巧开始——5个让你爱上IDEA的增加体验小技巧
前言 如果你是一名Java开发人员,那么你一定会使用IntelliJ IDEA这个IDE。IntelliJ IDEA作为目前最受欢迎的Java IDE之一,已经成为了众多Java开发人员必备的工具之一。但是,你是否知道如何利用IDEA中的一些小技巧来提高你的开发效率和体验呢&a…...

Python基础合集 练习22 (错误与异常处理语句2)
‘’’ try: 语句块 except: 语句块2 else ‘’’ class Mobe1(): def init(self) -> None: pass def mob1(self):while True:try:num int(input(请输入一个数: ))result 50 / numprint(result)print(50/{0}{1}.format(num, result))except (ZeroDivisionError, ValueEr…...

ELK -- kibana 用nginx代理后无法访问
背景: 本地搭建好elk后,一切正常,后面改成用nginx代理kibana的5601端口,发现代理后无法正常访问(未代理的地址可正常访问),花了很多时间去查问题,报错基本都是http://ip:port/spaces…...

什么是分布式事务
目录 分布式事务基础 事务 本地事务 分布式事务 分布式事务的场景 分布式事务解决方案 全局事务 优点 缺点 可靠消息服务 第一步 :消息由系统A投递到中间件 超时访问机制 最大努力通知 第一步:消息由系统A投递到中间件 第二步:消息…...
在 Python 中将整数转换为罗马数字
罗马数字使用以下七个符号书写。 Symbol Value I 1 V 5 X 10 L 50 C 100 D 500 M 1000这些符号用于表示数以千计的数字。 罗马写20,可以用两个X拼成XX。 但是 XXXX 不…...
HashTable,Properties,TreeSet源码分析
目录 HashTable基本介绍 Hashtable和HashMap对比 Properties Properties基本介绍 应该如何选择集合 TreeSet源码分析 HashTable基本介绍 1)存放的元素是键值对:即K-V 2)hashtable的键和值都不能为null,否则会抛出NullPointerException 3)hashTab…...

多维图像去噪方法研究
一、背景介绍 由于传感器技术的快速发展,高光谱(HS)遥感(RS)成像为飞机等数据采集设备远距离观测和分析地球表面提供了大量的空间和光谱信息,航天器和卫星。 HS RS 技术的最新进展甚至革命为实现各种应用的…...
托福口语考察内容和形式
首先我们来简单介绍一下托福口语考试的构成和task1的任务形式。 目录 TOEFL Speaking Test Format Independent Task 1 Task 1 Test Format Task 1 Testing Interface(考试界面) Task 1 Question Type...
【地铁上的设计模式】--结构型模式:代理模式
什么是代理模式 代理模式是一种结构型设计模式,通过代理对象控制对原始对象的访问。代理对象充当客户端和实际对象之间的中介,隐藏了实际对象的复杂性,并提供了一些额外的控制。 在软件系统中,代理模式可以有多种应用。例如&…...

Oracle 体系结构
文章目录 Oracle体系结构Oracle的内存结构Oracle的进程结构服务器进程后台进程可选后台进程 物理存储结构逻辑存储结构 概念: Oracle server由Oracle instance和Oracle database组成,Oracle instance由后台进程和共享内存组成,Oracle的实例包…...
java手写日历系统(亲测)
package com.test.test02;import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Scanner;public class Test08 {//这是一个main方法,是程序的入口public static void main(String[] args) {//录入日期的StringScanner sc new Scanner…...

35-40的技术人员为什么会被“不友好”,请你们自身反思-拒做职场的“嗯嗯”怪
35-40真的是IT人员的一道坎吗? IT技术做不到35-40,可是我身边有大量35-40事业发达、职业发展更好的朋友。同时,我身边也有大量35-40被“毕业”的人更多。 本人经过7年来先后带队过3个大型研发团队,最少的也有60-70号人。最多的达到…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...

PH热榜 | 2025-06-08
1. Thiings 标语:一套超过1900个免费AI生成的3D图标集合 介绍:Thiings是一个不断扩展的免费AI生成3D图标库,目前已有超过1900个图标。你可以按照主题浏览,生成自己的图标,或者下载整个图标集。所有图标都可以在个人或…...