当前位置: 首页 > news >正文

SpringBoot整合Flowable/Activiti

SpringBoot版本: 2.0.1.RELEASE

Flowable版本: 6.3.1

Activiti版本: 6.0.0

一.添加pom依赖

    因为之前我整合的时候有报错关于sqlsession的错误,后面查询文章才发现flowable要排除掉mybatis,又没说具体排除哪一个,所以我这干脆全部排除了

<!-- Flowable dependencies --><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.3.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-engine</artifactId><version>6.3.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring</artifactId><version>6.3.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-rest</artifactId><version>6.3.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-cmmn-spring</artifactId><version>6.3.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-dmn-spring</artifactId><version>6.3.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency>

<!--activiti modeler start--><dependency><groupId>org.activiti</groupId><artifactId>activiti-json-converter</artifactId><version>6.0.0</version><exclusions><exclusion><groupId>org.activiti</groupId><artifactId>activiti-bpmn-model</artifactId></exclusion></exclusions></dependency>

二.yml配置flowable/Activiti相关配置项

  至于层级关系,都是和spring同级的,我一般放在mybatis配置项下面,类似下面这样

  flowable:#关闭定时任务JOBasync-executor-activate: false#  将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。database-schema-update: true# activiti 模块activiti:# 解决启动报错:class path resource [processes/] cannot be resolved to URL because it does not existcheck-process-definitions: false# 检测身份信息表是否存在db-identity-used: true# 默认为false# false:项目启动时,如果数据库中没有表将抛出异常(上线后使用)# true:项目启动时,如果数据库中没有表将自动生成(开发和测试时使用)database-schema-update: true

 三.数据库配置项中url添加&nullCatalogMeansCurrent=true配置

        因为我之前用过activiti工作流,如果不加这个配置工作流会默认查询整个数据库连接下所有的库,只要发现有相关的act表就会认为已经自动生成了. 导致你在A库有了工作流的表之后想在B库再去自动生成就会发现生成不了.加上这个配置就可以了,不知道flowable影不影响,我都习惯给加上了

#  nullCatalogMeansCurrent=true
#  因为mysql使用schema标识库名而不是catalog,因此mysql会扫描所有的库来找表,
#  如果其他库中有相同名称的表,activiti就以为找到了,本质上这个表在当前数据库中并不存在。
#  解决办法就是在数据库链接的配置后面加上 nullCatalogMeansCurrent=true,标识之搜索当前链接的库。
url: jdbc:mysql://192.168.*.*:3306/flowable?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true

四.启动项目 自动生成数据表

        这个会根据flowable的版本而来 实际可能不一致 我的flowable版本是6.3.1,生成了66张表

Activiti版本是6.0.0,生成了28张表

一些常用的需要关注的表的含义(注明了注释)

 

五.IDEA中安装Flowable/Activiti插件

        安装插件: Flowable BPMN visualizer / Activiti BPMN visualizer

        然后在resources目录下新建processes目录

        右键新建一个基于BOMN 2.0协议的文件

        绘制流程图 

 

        注意: flowable/Activiti都会将处于processes目录下的符合后缀名要求的文件自动部署

                 不想要这个功能你可以修改目录名或者查询网站搜索下相关的关闭命令

六.工作流提供的几个Service

Flowable和Activiti都给我们提供了几个Service

因为本身Flowable的团队就是从Activiti团队中过来的,所以Service的命名及使用基本都是一致的,会一个就会另一个

    // 运行时执行管理Service@Autowiredprivate RuntimeService runtimeService;// 工作流任务管理Service@Autowiredprivate TaskService taskService;// 管理流程定义Service@Autowiredprivate RepositoryService repositoryService;// 核心类 可以理解为流程引擎对象@Autowiredprivate ProcessEngine processEngine;// 历史管理Service@Autowiredprivate HistoryService historyService;

七.常用方法

因为几乎所有业务都需要用到"PROC_INST_ID_" 也就是 "流程实例Id"

以及当前任务id-TASK_ID

所以我建议把这两个id都放在业务表中和你的业务表一对一关联(一般都是一个业务开一个流程实例)

1.动态部署

(提供一个文件,通过repositoryService创建部署对象去读取xml文件的内容来自动部署)

部署完之后ACT_RE_DEPLOYMENT,ACT_RE_PROCDEF两个表中会有数据

MultipartFile file; // 提供一个二进制文件对象
Deployment deployment = null;try{DeploymentBuilder deploymentBuilder =repositoryService.createDeployment()
.name(Objects.requireNonNull(file.getOriginalFilename()).substring(0, file.getOriginalFilename().indexOf(".bpmn20.xml"))).addInputStream(file.getOriginalFilename(),file.getInputStream());deployment = deploymentBuilder.deploy();}catch (Exception e){e.printStackTrace();throw new CustomException("文件部署失败");}// 部署完后可以获取部署ID
// 根据部署ID查询流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
System.out.println("流程【"+processDefinition.getName()+"】部署成功, 部署ID为: "+processDefinition.getDeploymentId()+"流程定义Key为: "+processDefinition.getKey());

2.流程启动

Map<String, Object> variables = new HashMap<>();variables.put("startUser", SecurityUtils.getLoginUser().getUser().getUserId().toString());variables.put("data", processInit.getData());variables.put("remark", processInit.getRemark());ProcessInstance processInstance = null;try{// 根据部署ID查询流程定义ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(processInit.getDeployId()).singleResult();//启动流程实例,同时还要指定业务标识businessKey  项目Id//第一个参数:是指流程定义key//第二个参数:业务标识businessKeyprocessInstance = runtimeService.startProcessInstanceByKey(processDefinition.getKey(),processInit.getWorkId().toString(),variables);runtimeService.setProcessInstanceName(processInstance.getId(),processDefinition.getName());

        启动后ACT_RU_EXECUTION会看到流程实例当前执行的节点概况,包括历史节点和当前时节点

PROC_INST_ID_是流程实例Id 

 

这两个表也可以看到实例相关节点信息

 

3.执行下一任务节点并添加审核备注

核心方法为: 

taskService.complete(task.getId());

需要注意的是:如果没有执行人,需要先认领任务而后才去审核任务

SysUser user = SecurityUtils.getLoginUser().getUser();String taskId; // 当前任务IdString processInstanceId; // 流程实例IdProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();String businessKey = processInstance.getBusinessKey(); // 流程实例的业务Id-businessKey// 获取当前待办任务Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).taskId(taskId).singleResult();if(task == null){throw new CustomException(CustomExceptionEnums.DATA_NOT_FOUND);}String assignee = task.getAssignee();if(StringUtils.isEmpty(assignee)){// 如果没有执行人 需要先认领任务taskService.claim(task.getId(),user.getUserId().toString());}if(StringUtils.isNotEmpty(processApproved.getRemark())){// 添加审批备注taskService.addComment(taskId, processInstanceId, ActivityCommentType.APPROVED_REMARK.getType(), processApproved.getRemark());}// 完成当前任务taskService.complete(task.getId());

 通过后可通过taskService获取下一个任务节点Id

// 获取下一个任务ID
List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).list();

4.获取待办/已办

因为Activiti和Flowable中并没有获取待办任务和已办任务的直接方法可以调用

所以需要我们自己写sql语句来获取

需要用到以下的表

待办任务所需

"ACT_RU_TASK"运行时任务节点表

"ACT_RU_IDENTITYLINK"运行时流程人员表

"ACT_HI_PROCINST"历史流程实例表

已办任务所需

"ACT_HI_TASKINST"历史任务节点表

"ACT_HI_PROCINST"历史流程实例表

然后我自己通过union将结果合并,并通过task_status来区分待办和已办

-- 待办和已办union all查询
select * from
(
select 0 as task_status,
RES.ID_ as task_id,RES.NAME_ as task_name,RES.CREATE_TIME_ as start_time,null as end_time,RES.PROC_INST_ID_ as process_instance_id,RES.ASSIGNEE_ as assignee,P.BUSINESS_KEY_  as business_key ,P.NAME_ as process_instance_name ,I.TYPE_ as type,I.USER_ID_ as user_id, I.GROUP_ID_ as group_id ,RES.TASK_DEF_KEY_ as task_def_key
from ACT_RU_TASK RES 
left join ACT_RU_IDENTITYLINK I on I.TASK_ID_ = RES.ID_ 
left join ACT_HI_PROCINST P on P.PROC_INST_ID_ = RES.PROC_INST_ID_ UNION ALLselect 1 as task_status,
RES.ID_ as task_id,RES.NAME_ as task_name,RES.START_TIME_ as start_time,RES.END_TIME_ as end_time,RES.PROC_INST_ID_ as process_instance_id,RES.ASSIGNEE_ as assignee,P.BUSINESS_KEY_  as business_key ,P.NAME_ as process_instance_name ,null as type,null as user_id, null as group_id,RES.TASK_DEF_KEY_ as task_def_key
from ACT_HI_TASKINST RES 
left join ACT_HI_PROCINST P on P.PROC_INST_ID_ = RES.PROC_INST_ID_  and RES.END_TIME_ is not null
)v

然后可以通过条件查询,比如"执行人","执行部门","任务节点定义key"等来进行查询

(task_status : 待办/已办)

(assignee : 执行人Id)

(task_def_key : 任务节点定义key)

(type : 表示候选用户 

  • candidate: 表示候选用户或用户组。在任务尚未被领取时,这些用户或用户组可以领取任务。
  • participant: 表示任务的参与者。通常情况下,当任务被领取后,参与者就是具体执行任务的用户。
  • owner: 表示任务的所有者。在某些情况下,任务可能会指定一个所有者,通常是在任务创建时指定,这个所有者有特殊权限或责任来处理任务。

)

(group_id  : 部门Id)

(start_time : 任务节点生成时间)

(end_time : 任务节点完成时间)

比如以下的where条件可以用作待办任务的筛选查询

WHERE v.task_status = 0 and v.assignee = '1'  and v.task_def_key=  'sid-5db27d85-b516-4784-8b58-0f5c9e1c3235' or (v.assignee is null and v.type = 'candidate' and (v.user_id = '1' or v.group_id IN ( '土整中心','123' ) )) order by v.start_time desc

比如以下的where条件可以用作已办任务的筛选查询 

WHERE v.task_status = 1 and v.assignee = '1' and v.task_def_key=  'sid-8abc3887-40c5-49c2-b8c0-b4d3fdefb7a0' and v.end_time is not null order by v.end_time desc


5. 通过taskId获取该任务的备注

/*** 通过taskId获取该任务的备注* @param taskId 任务Id* @param commentType 备注类型* @return 备注详情*/@Overridepublic List<Comment> getCommentByTaskId(String taskId,String commentType){if(StringUtils.isNotEmpty(commentType)){// 带有type筛选return taskService.getTaskComments(taskId, commentType);}return taskService.getTaskComments(taskId);}

6.查询截止目前可驳回的节点列表

我的逻辑是查询当前已完成的用户任务,不包括"开始"节点(因为"开始"节点不是用户任务)

然后有一些特殊情况需要处理:

        比如多次驳回,因为每一次驳回实际上都是将当前任务节点完成然后再加一条"目标驳回任务节点"用作下一个节点

        比如多次驳回之后取最新的节点,不可能出现可驳回列表中出现两个key相同的节点,这不符合逻辑

/*** 查询截止目前可驳回的节点列表* @param processInstanceId 流程ID* @return 结果*/@Overridepublic List<ActHistoricActivityInstanceVo> getAllActInstByInstanceIdToSimple(String processInstanceId,String taskDefKey){// 只要用户任务// 目前已完成的用户任务List<HistoricActivityInstance> userTask = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().activityType("userTask").finished().list();// 流程定义时的用户任务ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();String processDefinitionId = processInstance.getProcessDefinitionId();ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();// 定义时的所有节点Collection<FlowElement> flowElements = repositoryService.getBpmnModel(processDefinitionId).getMainProcess().getFlowElements();List<ActHistoricActivityInstanceVo> userTasks = new ArrayList<>();// 循环for (HistoricActivityInstance historicActivityInstance : userTask) {for (FlowElement flowElement : flowElements) {if (flowElement instanceof UserTask) {UserTask definitionUserTask = (UserTask) flowElement;String activityName = definitionUserTask.getName();String activityId = definitionUserTask.getId();String activityTaskDefKey = definitionUserTask.getId();// 可以查看其他用户任务属性,如Assignee、Candidate Users、Candidate Groups等// 获取任务定义对象信息// 入参taskDefKey对应的对象SysProcessTaskDef processTaskDef = sysProcessTaskDefMapper.selectSysProcessTaskDefById(taskDefKey);// 当前循环体中taskDefKey对应的对象SysProcessTaskDef currentTaskDef = sysProcessTaskDefMapper.selectSysProcessTaskDefById(activityTaskDefKey);if(currentTaskDef.getSort() >= processTaskDef.getSort()){// 不能驳回到后面的任务去(防止二次提交后可驳回的任务列表乱套)continue;}// 如果Id匹配 就加入自定义集合返回if(activityId.equals(historicActivityInstance.getActivityId()) && !activityTaskDefKey.equals(taskDefKey)){ActHistoricActivityInstanceVo activityInstanceVo = new ActHistoricActivityInstanceVo();activityInstanceVo.setActivityId(activityId);activityInstanceVo.setActivityName(activityName);// 对重复的跳过 (针对多次驳回导致存在多个相同节点的情况)
//                        if(userTasks.contains(activityInstanceVo)) continue;ActHistoricActivityInstanceVo actHistoricActivityInstanceVo = userTasks.stream().filter(u -> u.getActivityId().equals(activityInstanceVo.getActivityId())).findAny().orElse(null);if(actHistoricActivityInstanceVo != null && actHistoricActivityInstanceVo.getStartTime().compareTo(historicActivityInstance.getStartTime()) < 0){// 存在并且时间早于这一次就删除 (针对多次驳回导致存在多个相同节点的情况,取最新的节点审核情况)userTasks.remove(actHistoricActivityInstanceVo);}// 转换赋值BeanUtils.copyProperties(historicActivityInstance, activityInstanceVo);// 获取type为isFillSteps的comment(是否为填报步骤:提交填报时的那个步骤)List<Comment> comments = taskService.getTaskComments(activityInstanceVo.getTaskId(), "isFillSteps");if(comments != null && comments.size() > 0){activityInstanceVo.setFillSteps(true);}userTasks.add(activityInstanceVo);}}}}if(userTasks.size() > 0){userTasks = userTasks.stream().sorted(Comparator.comparing(ActHistoricActivityInstanceVo::getStartTime)).collect(Collectors.toList());}return userTasks;}

 

7.驳回到指定节点

提供流程实例Id,当前任务Id,驳回目标任务Id

/*** 驳回到指定节点* @param actRollbackIo 驳回对象*/@Override@Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRES_NEW)public void rejectToNode(ActRollbackIo actRollbackIo){String processInstanceId = actRollbackIo.getProcessInstanceId();String currentTaskId = actRollbackIo.getTaskId(); // 当前任务String targetTaskId = actRollbackIo.getTargetTaskId(); // 驳回目标任务//  获取该流程所有的活动实例List<HistoricActivityInstance> hisActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();String processDefinitionId = processInstance.getProcessDefinitionId();//  当前任务Task currentTask = taskService.createTaskQuery().taskId(currentTaskId).singleResult();//  驳回目标任务HistoricTaskInstance targetTask = historyService.createHistoricTaskInstanceQuery().finished().taskId(targetTaskId).singleResult();if(currentTask == null || targetTask == null){throw new CustomException(CustomExceptionEnums.MISSING_DATA_ERROR);}try{//  当前活动HistoricActivityInstance currentActivity = hisActivityList.stream().filter(e -> currentTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);//  驳回目标活动HistoricActivityInstance targetActivity = hisActivityList.stream().filter(e -> targetTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);//  获取xml文件对象BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);//  获取驳回目标活动节点FlowNode targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(targetActivity.getActivityId());//  获取当前活动节点FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivity.getActivityId());//  临时保存当前活动的原始方向List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());//  清理活动方向currentFlowNode.getOutgoingFlows().clear();//  建立新方向SequenceFlow newSequenceFlow = new SequenceFlow();newSequenceFlow.setId("newSequenceFlowId");newSequenceFlow.setSourceFlowElement(currentFlowNode);newSequenceFlow.setTargetFlowElement(targetFlowNode);List<SequenceFlow> newSequenceFlowList = new ArrayList<>();newSequenceFlowList.add(newSequenceFlow);//  当前节点指向新的方向currentFlowNode.setOutgoingFlows(newSequenceFlowList);//  完成当前任务taskService.claim(currentTaskId,SecurityUtils.getLoginUser().getUser().getUserId().toString());taskService.complete(currentTaskId);//  重新查询当前任务Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();String assignee = (StringUtils.isNull(actRollbackIo.getAssignee())) ? targetTask.getAssignee() : actRollbackIo.getAssignee().toString();if (null != nextTask) {System.out.println("最新任务=="+nextTask);taskService.setAssignee(nextTask.getId(), assignee);}else{throw new CustomException(CustomExceptionEnums.OPERATION_FAILED);}//  恢复原始方向currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}

8.撤回 删除流程

    /*** 撤回 删除流程* @param processInstanceId 流程实例Id* @param fillId 任务填报Id* @param message 撤回备注信息*/@Override@Transactional(rollbackFor = Throwable.class)public int revoke(String processInstanceId,Long fillId,String message){runtimeService.deleteProcessInstance(processInstanceId, message);historyService.deleteHistoricProcessInstance(processInstanceId);return landProjectFillService.cleanProcessInstance(fillId);}

后续有新的方法再来补充

相关文章:

SpringBoot整合Flowable/Activiti

SpringBoot版本: 2.0.1.RELEASE Flowable版本: 6.3.1 Activiti版本: 6.0.0 一.添加pom依赖 因为之前我整合的时候有报错关于sqlsession的错误,后面查询文章才发现flowable要排除掉mybatis,又没说具体排除哪一个,所以我这干脆全部排除了 <!-- Flowable dependencies -->…...

基础总结篇:Activity生命周期

private int param 1; //Activity创建时被调用 Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, “onCreate called.”); setContentView(R.layout.lifecycle); Button btn (Button) findViewById(R.id.…...

【鸿蒙 HarmonyOS】@ohos.promptAction (弹窗)

一、背景 创建并显示文本提示框、对话框和操作菜单。 文档地址&#x1f449;&#xff1a;文档中心 说明 本模块首批接口从API version 9开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 该模块不支持在UIAbility的文件声明处使用&#xff0c;即…...

ElasticSearch的常用数据类型

常见的数据类型 Text类型&#xff08;文本数据类型&#xff09; 用于全文检索的字段&#xff0c;例如电子邮件的正文或产品的描述。这些字段是analyzed&#xff0c;也就是说&#xff0c;它们通过分析器传递&#xff0c;以便 在被索引之前将字符串转换为单个术语的列表。通过分…...

C/C++预处理过程

目录 前言&#xff1a; 1. 预定义符号 2. #define定义常量 3. #define定义宏 4. 带有副作用的宏参数 5. 宏替换的规则 6. 宏和函数的对比 7. #和## 8. 命名约定 9. #undef 10. 命令行定义 11. 条件编译 12. 头文件的包含 13. 其他预处理指令 总结&#x…...

客服电话系统:专业、便捷的服务沟通桥梁

一、引言 1.客服电话系统在现代服务中的重要性 在信息化时代&#xff0c;服务行业的竞争日益激烈&#xff0c;提供高效、便捷的服务成为企业赢得市场、获取用户信任的关键。客服电话系统作为企业与用户之间的重要沟通桥梁&#xff0c;不仅承载着解答疑问、处理问题的职责&…...

IP地址与子网掩码

1 IP地址 1.1 IPv4与IPv6 1.2 IPv4地址详解 IPv4地址分4段&#xff0c;每段8位&#xff0c;共32位二进制数组成。 1.2.1 地址分类 这32位又被分为网络号和主机号两部分&#xff0c;根据网络号占用位数的不同&#xff0c;又可分为以下几类&#xff1a; A类地址&#xff1a;…...

Python爬取公众号封面图(零基础也能看懂)

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️感谢大家点赞&#x1f44d;&…...

2024.4.6学习笔记

今日学习韩顺平java0200_韩顺平Java_对象机制练习_哔哩哔哩_bilibili 今日学习p315-p328 动态绑定机制 当调用方法对象的时候&#xff0c;该方法会和该对象的内存地址/运行类型绑定 当调用对象属性时&#xff0c;没有动态绑定机制&#xff0c;哪里声明&#xff0c;哪里使用 …...

2024年华为OD机试真题-查找一个有向网络的头节点和尾节点-Java-OD统一考试(C卷)

题目描述: 给定一个有向图,图中可能包含有环,图使用二维矩阵表示,每一行的第一列表示起始节点,第二列表示终止节点,如[0, 1]表示从0到1的路径。每个节点用正整数表示。求这个数据的首节点与尾节点,题目给的用例会是一个首节点,但可能存在多个尾节点。同时,图中可能含有…...

【Django开发】0到1美多商城项目md教程第5篇:短信验证码,1. 避免频繁发送短信验证码逻辑分析【附代码文档】

美多商城完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;欢迎来到美多商城&#xff01;&#xff0c;项目准备。展示用户注册页面&#xff0c;创建用户模块子应用。用户注册业务实现&#xff0c;用户注册前端逻辑。图形验证码&#xff0c;图形验证码接口设…...

云原生:应用敏捷,华为视角下的应用现代化

Gartner 也提出&#xff0c;到 2023 年&#xff0c;新应用新服务的数量将达到 5 亿&#xff0c;也即是说&#xff1a;“每个企业都正在成为软件企业”。据IDC 预测&#xff0c;到 2025 年三分之二的企业将成为多产的“软件企业”&#xff0c;每天都会发布软件版本。越来越多的企…...

【测试篇】接口测试

接口测试&#xff0c;可以用可视化工具 postman。 如何做接口测试&#xff1f;&#xff1f; 我们可以先在浏览器中随机进入一个网页&#xff0c;打开开发者工具&#xff08;F12&#xff09;。 随便找一个接口Copy–>Copy as cURL(bash) 打开postman 复制地址 进行发送。 …...

突破校园网限速:使用 iKuai 多拨分流负载均衡 + Clash 代理(内网带宽限制通用)

文章目录 1. 简介2. iKuai 部署2.1 安装 VMware2.2 安装 iKuai(1) 下载固件(2) 安装 iKuai 虚拟机(3) 配置 iKuai 虚拟机(4) 配置 iKuai(5) 配置多拨分流 2.3 测试速度 3. Clash 部署(1) 配置磁盘分区(2) 安装 Docker(3) 安装 Clash(4) 设置代理 4. 热点&#xff1a;一起瓜分互…...

03-JAVA设计模式-工厂模式详解

工厂模式 工厂设计模式是一种创建型设计模式&#xff0c;它提供了一种封装对象创建过程的机制&#xff0c;将对象的创建与使用分离。 这种设计模式允许我们在不修改客户端代码的情况下引入新的对象类型。 在Java中&#xff0c;工厂设计模式主要有三种形式&#xff1a;简单工厂…...

百度文心大模型推理成本降至1% / 马斯克起诉OpenAI |魔法半周报

我有魔法✨为你劈开信息大海❗ 高效获取AIGC的热门事件&#x1f525;&#xff0c;更新AIGC的最新动态&#xff0c;生成相应的魔法简报&#xff0c;节省阅读时间&#x1f47b; &#x1f525;资讯预览 百度文心大模型推理成本降至1%&#xff0c;与三星、荣耀等企业达成合作 马斯…...

Struts2的入门:新建项目——》导入jar包——》jsp,action,struts.xml,web.xml——》在项目运行

文章目录 配置环境tomcat 新建项目导入jar包新建jsp界面新建action类新建struts.xml,用来配置action文件配置Struts2的核心过滤器&#xff1a;web.xml 启动测试给一个返回界面在struts.xml中配置以实现页面的跳转&#xff1a;result再写个success.jsp最后在项目运行 配置环境 …...

git 标签功能操作以及回退

Git 标签功能允许开发者为特定的提交打上标签&#xff0c;以便后续能够方便地引用这些提交。标签通常用于标记重要的版本或里程碑&#xff0c;例如软件发布的版本号。与分支不同&#xff0c;标签指向的是固定的提交&#xff0c;一旦设置&#xff0c;就不能轻易更改。下面是一些…...

利用python实现文字转语音

代码如下&#xff1a; import pathlib import tkinter as tk import tkinter.ttk as ttk import tkinter.filedialog as filedialog import tkinter.messagebox as msgbox import pyttsx3class Application(tk.Tk):def __init__(self):super().__init__()self.title("文本…...

拾光坞N3 ARM 虚拟主机 i茅台项目

拾光坞N3 在Dcoker部署i茅台案例 OS&#xff1a;Ubuntu 22.04.1 LTS aarch64 cpu&#xff1a;RK3566 ram&#xff1a;2G 部署流程——》mysql——》java8——》redis——》nginx mysql # 依赖 apt update apt install -y net-tools apt install -y libaio* # 下载mysql wg…...

docker安装nacos,单例模式(standalone),使用mysql数据库

文章目录 前言安装创建文件夹"假装"安装一下nacos拷贝文件夹删除“假装”安装的nacos容器生成nacos所需的mysql表获取mysql-schema.sql文件创建一个mysql的schema 重新生成新的nacos容器 制作docker-compose.yaml文件查看网站 前言 此处有本人写得简易版本安装&…...

【运输层】传输控制协议 TCP

目录 1、传输控制协议 TCP 概述 &#xff08;1&#xff09;TCP 的特点 &#xff08;2&#xff09;TCP 连接中的套接字概念 2、可靠传输的工作原理 &#xff08;1&#xff09;停止等待协议 &#xff08;2&#xff09;连续ARQ协议 3、TCP 报文段的首部格式 1、传输控制协议…...

深入浅出 -- 系统架构之Keepalived搭建双机热备

Keepalived重启脚本双机热备搭建 ①首先创建一个对应的目录并下载keepalived安装包&#xff08;提取码:s6aq&#xff09;到Linux中并解压&#xff1a; [rootlocalhost]# mkdir /soft/keepalived && cd /soft/keepalived [rootlocalhost]# wget https://www.keepalived.…...

如何做好产业园运营?树莓集团:响应政府号召,规划,注重大局观

随着经济的发展和产业结构的调整&#xff0c;产业园区的建设和发展已经成为推动地方经济的重要力量。如何做好产业园运营&#xff0c;提高行业竞争力&#xff0c;现已成为了一个亟待解决的问题。树莓集团作为一家有着丰富产业园运营经验的企业&#xff0c;积极响应政府号召&…...

NIO与BIO

当谈到 Java 网络编程时&#xff0c;经常会听到两个重要的概念&#xff1a;BIO&#xff08;Blocking I/O&#xff0c;阻塞 I/O&#xff09;和 NIO&#xff08;Non-blocking I/O&#xff0c;非阻塞 I/O&#xff09;。它们都是 Java 中用于处理 I/O 操作的不同编程模型。 一、介…...

YOLOv5实战记录05 Pyside6可视化界面

个人打卡&#xff0c;慎看。 指路大佬&#xff1a;【手把手带你实战YOLOv5-入门篇】YOLOv5 Pyside6可视化界面_哔哩哔哩_bilibili 零、虚拟环境迁移路径后pip报错解决 yolov5-master文件夹我换位置后&#xff0c;无法pip install了。解决如下&#xff1a; activate.bat中修改…...

HTML5.Canvas简介

1. Canvas.getContext getContext(“2d”)是Canvas元素的方法&#xff0c;用于获取一个用于绘制2D图形的绘图上下文对象。在给定的代码中&#xff0c;首先通过getElementById方法获取id为"myCanvas"的Canvas元素&#xff0c;然后使用getContext(“2d”)方法获取该Ca…...

excel统计分析——多项式回归

参考资料&#xff1a;生物统计学 多项式回归属于单变量曲线回归&#xff0c;但其形式和求解方法与多元线性回归相似。多项式回归的数学模型为&#xff1a; 令&#xff0c;&#xff0c;&#xff0c;&#xff0c;则 由于X不可逆&#xff0c;两边同时乘以X得&#xff0c;&#xff…...

SQLyog连接数据库8.0版本解析错误问题解决方案

问题描述&#xff1a; 解决方案&#xff1a; alter userrootlocalhostidentified with mysql_native_password by 密码; 再次连接就可以了。...

【数据库】SQL简介

SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;是一种用于管理关系型数据库管理系统&#xff08;RDBMS&#xff09;的标准化语言。它用于访问和操作数据库中的数据&#xff0c;执行各种任务&#xff0c;如插入、更新、删除和检索数据&#x…...

web网页设计实训报告/网站关键字优化软件

本文实例讲述了jquery实现页面百叶窗走马灯式翻滚显示效果的方法。分享给大家供大家参考。具体如下&#xff1a;1. 这里的代码需要jquery1.3以上的支持&#xff0c;如下所示&#xff1a;jQuery.fn.extend((function($){var l 4,//卷动行数t5000,//卷动完一次后&#xff0c;隔多…...

真正免费的自学网站/百度投放广告流程

计算搜索中两点之间的距离有很多用例。 如果你正在处理地理数据&#xff0c;那么无论你从事何种业务&#xff0c;这都必然会出现。 然后&#xff0c;在对这些点进行排序时&#xff0c;你可能需要考虑距离&#xff0c;因为……好吧&#xff0c;为什么不呢&#xff1f; 所以这里…...

内蒙古两学一做网站/怎么提高关键词搜索排名

本文整理自《CNCF x Alibaba 云原生技术公开课》第 17 讲。 导读&#xff1a;etcd 是容器云平台用于存储关键元信息的组件。阿里巴巴使用 etcd 已经有 3 年的历史, 在今年 双11 过程中它又一次承担了关键角色&#xff0c;接受了 双11 大压力的检验。本文作者从 etcd 性能背景出…...

网站地址格式/太原网络推广公司哪家好

生物技术领域存在大量的炒作都集中于革命性药物发现上。毕竟&#xff0c;过去十年是该领域的黄金时代。与之前的十年相比&#xff0c;2012年到2021年这段时间批准的新药增加了73%–比之前的十年增加了25%。这些药物包括治疗癌症的免疫疗法、基因疗法&#xff0c;当然还有科威德…...

深圳福田大型商城网站建设/电脑优化大师

文章目录面向对象类和对象定义类&#xff08;class&#xff09;并创建和使用对象访问可见性property_ _ _slots__ _静态方法和类方法类之间的关系继承和多态面向对象 把一组数据结构和处理它们的方法组成对象&#xff08;object&#xff09;&#xff0c;把相同行为的对象归纳为…...

品牌全案设计公司/seo快速排名源码

2008年12月信息处理技术员上午试卷 51CTO版参考答案欢迎跟帖拍砖、讨论&#xff01;...