独辟蹊径:我是如何用Java自创一套工作流引擎的(下)
作者:后端小肥肠
创作不易,未经允许严禁转载。
姊妹篇:独辟蹊径:我是如何用Java自创一套工作流引擎的(上)_java工作流引擎-CSDN博客
1. 前言
在上一篇博客中,我们详细介绍了如何利用Java语言从零开始打造一套工作流引擎的基础架构。通过设计核心表结构和实现基础代码框架,我们建立了一个坚实的理论基础。今天,我们迈入《独辟蹊径:我是如何用Java自创一套工作流引擎的(下)》,将深入探讨这一引擎在实际项目中的应用和效果。
2. 项目场景模拟
本章我们将以实际项目场景来模拟自研工作流引擎的使用,分别是申请数据资源的流程和请假申请流程。
2.1. 申请数据资源流程
2.1.1. 技术流程
假设申请数据的审批级数为2级。申请数据资源的流程图如下:

在上述流程图中,从普通用户,一级审批人员,二级审批人员视角呈现了申请数据资源的整体流程;
1. 用户提交审批数据表单,填入申请人信息(姓名、电话),申请理由和需要申请的数资源;
2. 一级审批人员收到用户提交的申请后进行审批,如果驳回则整个流程结束,如果通过则进入下一审批环节;
3. 一级审批通过后二级审批人员可进行审批,如果驳回则整个流程结束,如何通过则开放数据下载链接,用户可根据链接下载申请的数据。
2.2. 请假申请流程
2.2.1. 技术流程
请假流程如下:

在上述流程中,从用户和一级审批人员的角度呈现了整个请假流程:
1. 用户提交审批数据表单,填入申请人信息(姓名、电话)、申请理由、请假天数;
2. 一级审批人员收到用户提交的申请后进行审批,如果驳回则整个流程结束,同时通知用户流程未通过,如果通过则结束流程。
2.3. 技术实现
要在工作流中集成以上两套流程,需要基于一下几个步骤实现:
1. 设计流程定义,在business_approval_workflow新建数据审批流程和请假流程。

2. 设计流程细节,设计数据审批流程和请假流程的节点细节。

上图中,申请业务数据包含两个流程节点,第一个节点审批人为admin,第二个节点审批人为super;请假流程包含一个流程节点,审批人为admin。
3. 编写提交申请接口。
提交申请业务数据流程接口直接使用《独辟蹊径:我是如何用Java自创一套工作流引擎的(上)》中提交申请接口就行:
public Boolean addRequest(RequestDTO requestDTO) {Request request= BeanCopyUtils.copyBean(requestDTO,Request.class);request.setStatus("1");//设置整个流程状态为正在审核// 1. 插入数据到 request 表baseMapper.insert(request);// 2. 根据 workflow_id 查询业务流程的节点信息,找到 serial_number 为 1 的节点,即流程开始时的第一个节点BusinessApprovalWorkflowDetail firstNode = workflowDetaiSlService.findFirstNodeByWorkflowId(request.getWorkflowId());//获取下一级节点 填充下级节点审批人BusinessApprovalWorkflowDetail nextNode=workflowDetaiSlService.getNextNodeByPreNode(firstNode);if (firstNode != null) {// 创建一个 approval_detail 记录示例,需要根据具体情况设置字段值ApprovalDetail approvalDetail = new ApprovalDetail();approvalDetail.setRequestId(request.getId()); // 假设设置关联的 request_idapprovalDetail.setApproverUsername(firstNode.getNodeUsername()); // 设置首次节点的审批人用户名approvalDetail.setApprovalTime(new Date());approvalDetail.setNextApproverUsername(nextNode.getNodeUsername());//设置下游节点的审批人用户名approvalDetail.setStatus("1"); // 设置初始状态为待审批approvalDetail.setWorkflowId(request.getWorkflowId());approvalDetail.setNodeName(firstNode.getNodeName());approvalDetail.setNextNodeName(nextNode.getNodeName());// 插入数据到 approval_detail 表approvalDetailService.save(approvalDetail);} else {// 如果未找到对应的节点,根据实际需求进行错误处理或日志记录throw new RuntimeException("Unable to find the first node for workflow id: " + request.getWorkflowId());}return true;}
提交请假流程接口,在编写提交请假流程接口前,需要先明确请假申请表的表结构:
CREATE TABLE "public"."leave_request" ("id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,"workflow_id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,"purpose" varchar(900) COLLATE "pg_catalog"."default" NOT NULL,"leave_days" int2 NOT NULL,"applicant_name" varchar(50) COLLATE "pg_catalog"."default","applicat_username" varchar(50) COLLATE "pg_catalog"."default","applicant_phone" varchar(11) COLLATE "pg_catalog"."default","version" int4 DEFAULT 1,"is_deleted" int4 DEFAULT 0,"create_time" timestamp(6) NOT NULL,"update_time" timestamp(6)
)
;ALTER TABLE "public"."leave_request" OWNER TO "postgres";COMMENT ON COLUMN "public"."leave_request"."workflow_id" IS '业务流程id';COMMENT ON COLUMN "public"."leave_request"."purpose" IS '请假理由';COMMENT ON COLUMN "public"."leave_request"."leave_days" IS '请假天数';COMMENT ON COLUMN "public"."leave_request"."applicant_name" IS '申请人姓名';COMMENT ON COLUMN "public"."leave_request"."applicat_username" IS '申请人用户名';COMMENT ON COLUMN "public"."leave_request"."applicant_phone" IS '申请人电话';
编写controller层:
@GetMapping("")public Boolean addRequest(@Validated @RequestBody LeaveRequestDTO leaveRequestDTO){return leaveRequestService.addRequest(leaveRequestDTO);}
编写LeaveRequestDTO:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LeaveRequestDTO {private String workflowId;private String purpose;private Integer leaveDays;private String applicantName;private String applicantPhone;private String applicatUsername;
}
编写service层:
public Boolean addRequest(LeaveRequestDTO leaveRequestDTO) {LeaveRequest leaveRequest= BeanCopyUtils.copyBean(leaveRequestDTO,LeaveRequest.class);// 1. 插入数据到 request 表baseMapper.insert(leaveRequest);// 2. 根据 workflow_id 查询业务流程的节点信息,找到 serial_number 为 1 的节点,即流程开始时的第一个节点BusinessApprovalWorkflowDetail firstNode = workflowDetaiSlService.findFirstNodeByWorkflowId(leaveRequest.getWorkflowId());//获取下一级节点 填充下级节点审批人BusinessApprovalWorkflowDetail nextNode=workflowDetaiSlService.getNextNodeByPreNode(firstNode);if (firstNode != null) {// 创建一个 approval_detail 记录示例,需要根据具体情况设置字段值ApprovalDetail approvalDetail = new ApprovalDetail();approvalDetail.setRequestId(leaveRequest.getId()); // 假设设置关联的 request_idapprovalDetail.setApproverUsername(firstNode.getNodeUsername()); // 设置首次节点的审批人用户名approvalDetail.setApprovalTime(new Date());approvalDetail.setNextApproverUsername(nextNode.getNodeUsername());//设置下游节点的审批人用户名approvalDetail.setStatus("1"); // 设置初始状态为待审批approvalDetail.setWorkflowId(leaveRequest.getWorkflowId());approvalDetail.setNodeName(firstNode.getNodeName());approvalDetail.setNextNodeName(nextNode.getNodeName());// 插入数据到 approval_detail 表approvalDetailService.save(approvalDetail);} else {// 如果未找到对应的节点,根据实际需求进行错误处理或日志记录throw new RuntimeException("Unable to find the first node for workflow id: " + leaveRequest.getWorkflowId());}return true;}
上述代码实现了提交请假申请的功能:首先将从DTO转换后的请假请求数据插入数据库,然后根据流程ID查询流程的第一个节点信息,设置首次节点的审批人并插入到审批详情表中,状态设置为待审批。
4. 基于策略模式优化审批接口。
审批申请方法在上篇中如下:
@Transactional@Overridepublic Boolean approvalApplication(ApprovalDTO approvalDTO) {// 这里我写死了,实际获取应该走权限框架获取当前在线用户 usernameString username = "xfc";
// 审批人姓名,从用户表中获取String name="小肥肠";//查询出当前任务节点ApprovalDetail approvalDetail = baseMapper.selectById(approvalDTO.getId());//获取当前审批的申请信息Request request = requestMapper.selectById(approvalDetail.getRequestId());if(request==null){throw new RuntimeException("申请id有误");}// 审批通过if (approvalDTO.getStatus().equals("2")) {// 根据 workflow_id 和 node_name 联查 business_approval_workflow_detail 表,获取当前流程是否为最后节点即 is_final=1BusinessApprovalWorkflowDetail currentWorkflowDetail = businessApprovalWorkflowDetailService.findByWorkflowIdAndNodeName(approvalDTO.getWorkflowId(), approvalDetail.getNodeName());if (currentWorkflowDetail != null && currentWorkflowDetail.getIsFinal().equals("1")) {// 如果是最后节点,则删除该条数据,填充 approval_history 表,根据 request 表修改 request 数据的 status 为 2baseMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录// 更新 request 表中的状态为 2(通过)request.setStatus("2");requestMapper.updateById(request);} else {// 如果不是最后节点,则更新 business_approval_workflow_detail 为下一个节点审批信息BusinessApprovalWorkflowDetail nextNode = businessApprovalWorkflowDetailService.getNextNodeByPreNode(currentWorkflowDetail);
// 获取下一级节点的更下一级BusinessApprovalWorkflowDetail nextNextNode= businessApprovalWorkflowDetailService.getNextNodeByPreNode(nextNode);// 更新当前 approval_detail 表中的审批人和下一个审批人信息approvalDetail.setApproverUsername(nextNode.getNodeUsername());approvalDetail.setNodeName(nextNextNode.getNodeName());approvalDetail.setNextApproverUsername(nextNextNode!=null?nextNextNode.getNodeUsername():"");approvalDetail.setNextNodeName(nextNextNode!=null?nextNextNode.getNodeName():"");approvalDetail.setApprovalTime(new Date());approvalDetail.setStatus("1"); // 设置为待审批状态baseMapper.updateById(approvalDetail);}// 填充 approval_history 表ApprovalHistory approvalHistory = new ApprovalHistory();approvalHistory.setRequestId(request.getId());approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取approvalHistory.setApprovalTime(new Date());approvalHistory.setStatus("2"); // 通过approvalHistory.setRemark(approvalDTO.getRemark());approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());approvalHistory.setApplicantPhone(request.getApplicantPhone());approvalHistory.setPurpose(request.getPurpose());approvalHistory.setApplicantName(request.getApplicantName());approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录} else if (approvalDTO.getStatus().equals("3")) {// 审批驳回baseMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录// 填充 approval_history 表ApprovalHistory approvalHistory = new ApprovalHistory();approvalHistory.setRequestId(request.getId());approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取approvalHistory.setApprovalTime(new Date());approvalHistory.setStatus("3"); // 驳回approvalHistory.setRemark(approvalDTO.getRemark());approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());approvalHistory.setApplicantPhone(request.getApplicantPhone());approvalHistory.setPurpose(request.getPurpose());approvalHistory.setApplicantName(request.getApplicantName());approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录// 更新 request 表中的状态为 3(驳回)request.setStatus("3");requestMapper.updateById(request);}return true; // 或者根据实际需求返回其他业务逻辑}
上述代码其实是针对申请数据资源流程的审批操作,那如果要申请别的流程该怎么做呢?常规操作应该是在上述代码中增加if-else判断操作,根据不通业务进行士审批操作,但是随着业务流程增加,就会新增许多if-else操作,代码会十分雍总,代码可读性较差,可以通过引入策略工厂来解决上述问题。步骤如下:
1.新增审批策略工厂
@Service
public class ApprovalFactory {@AutowiredApprovalDataRequestService approvalDataRequestService;@AutowiredApprovalLeaveRequestService approvalLeaveRequestService;private static Map<String, Function<ApprovalDTO,Boolean>> approvalMap = null;@PostConstructpublic void init(){approvalMap=new HashMap<>();approvalMap.put("2",approvalDTO->approvalDataRequestService.approvalApplication(approvalDTO));approvalMap.put("1",approvalDTO ->approvalLeaveRequestService.approvalApplication(approvalDTO));}public Boolean approvalApplication(ApprovalDTO approvalDTO) {return approvalMap.get(approvalDTO.getWorkflowId()).apply(approvalDTO);}
}
上述代码为针对审批操作的策略工厂,在类初始化过程中(使用 @PostConstruct 注解的 init() 方法),通过静态的 approvalMap 对象将审批动作和对应的处理函数关联起来。具体来说:
- 当
workflowId为 "2" 时,映射到approvalDataRequestService的approvalApplication方法处理数据请求的审批逻辑。 - 当
workflowId为 "1" 时,映射到approvalLeaveRequestService的approvalApplication方法处理请假请求的审批逻辑。
最后,approvalApplication(ApprovalDTO approvalDTO) 方法根据传入的 approvalDTO 中的 workflowId 从 approvalMap 中获取相应的处理函数,并执行该函数来完成审批操作,返回处理结果,controller层直接调用策略工厂即可:
@PostMapping("/approval")public Boolean approvalApplication(@Validated @RequestBody ApprovalDTO approvalDTO) {return approvalFactory.approvalApplication(approvalDTO);}
2. 新增数据资源审批类
@Service
@Slf4j
public class ApprovalDataRequestService {@AutowiredIBusinessApprovalWorkflowDetailService businessApprovalWorkflowDetailService;@AutowiredRequestMapper requestMapper;@AutowiredApprovalHistoryMapper approvalHistoryMapper;@AutowiredApprovalDetailMapper approvalDetailMapper;@Transactionalpublic Boolean approvalApplication(ApprovalDTO approvalDTO) {// 这里我写死了,实际获取应该走权限框架获取当前在线用户 usernameString username = "xfc";
// 审批人姓名,从用户表中获取String name="小肥肠";//查询出当前任务节点ApprovalDetail approvalDetail = approvalDetailMapper.selectById(approvalDTO.getId());//获取当前审批的申请信息Request request = requestMapper.selectById(approvalDetail.getRequestId());if(request==null){throw new RuntimeException("申请id有误");}// 审批通过if (approvalDTO.getStatus().equals("2")) {// 根据 workflow_id 和 node_name 联查 business_approval_workflow_detail 表,获取当前流程是否为最后节点即 is_final=1BusinessApprovalWorkflowDetail currentWorkflowDetail = businessApprovalWorkflowDetailService.findByWorkflowIdAndNodeName(approvalDTO.getWorkflowId(), approvalDetail.getNodeName());if (currentWorkflowDetail != null && currentWorkflowDetail.getIsFinal().equals("1")) {// 如果是最后节点,则删除该条数据,填充 approval_history 表,根据 request 表修改 request 数据的 status 为 2approvalDetailMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录// 更新 request 表中的状态为 2(通过)request.setStatus("2");requestMapper.updateById(request);} else {// 如果不是最后节点,则更新 business_approval_workflow_detail 为下一个节点审批信息BusinessApprovalWorkflowDetail nextNode = businessApprovalWorkflowDetailService.getNextNodeByPreNode(currentWorkflowDetail);
// 获取下一级节点的更下一级BusinessApprovalWorkflowDetail nextNextNode= businessApprovalWorkflowDetailService.getNextNodeByPreNode(nextNode);// 更新当前 approval_detail 表中的审批人和下一个审批人信息approvalDetail.setApproverUsername(nextNode.getNodeUsername());approvalDetail.setNodeName(nextNextNode.getNodeName());approvalDetail.setNextApproverUsername(nextNextNode!=null?nextNextNode.getNodeUsername():"");approvalDetail.setNextNodeName(nextNextNode!=null?nextNextNode.getNodeName():"");approvalDetail.setApprovalTime(new Date());approvalDetail.setStatus("1"); // 设置为待审批状态approvalDetailMapper.updateById(approvalDetail);}// 填充 approval_history 表ApprovalHistory approvalHistory = new ApprovalHistory();approvalHistory.setRequestId(request.getId());approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取approvalHistory.setApprovalTime(new Date());approvalHistory.setStatus("2"); // 通过approvalHistory.setRemark(approvalDTO.getRemark());approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());approvalHistory.setApplicantPhone(request.getApplicantPhone());approvalHistory.setPurpose(request.getPurpose());approvalHistory.setApplicantName(request.getApplicantName());approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录} else if (approvalDTO.getStatus().equals("3")) {// 审批驳回approvalDetailMapper.deleteById(approvalDetail.getId()); // 删除当前审批记录// 填充 approval_history 表ApprovalHistory approvalHistory = new ApprovalHistory();approvalHistory.setRequestId(request.getId());approvalHistory.setApproverName(name); // 设置审批人姓名,或者从用户表中获取approvalHistory.setApprovalTime(new Date());approvalHistory.setStatus("3"); // 驳回approvalHistory.setRemark(approvalDTO.getRemark());approvalHistory.setWorkflowId(approvalDTO.getWorkflowId());approvalHistory.setApplicantPhone(request.getApplicantPhone());approvalHistory.setPurpose(request.getPurpose());approvalHistory.setApplicantName(request.getApplicantName());approvalHistory.setApproverUsername(username); // 设置审批人用户名,或者从用户表中获取approvalHistoryMapper.insert(approvalHistory); // 插入审批历史记录// 更新 request 表中的状态为 3(驳回)request.setStatus("3");requestMapper.updateById(request);}return true; // 或者根据实际需求返回其他业务逻辑}}
3.新增请假审批类
@Service
public class ApprovalLeaveRequestService {public Boolean approvalApplication(ApprovalDTO approvalDTO) {/*** 一样的逻辑,把对request表的操作改为leave_request*/return true;}
}
3. 结语
在本文中,针对《独辟蹊径:我是如何用Java自创一套工作流引擎的(上)》中的工作流基础代码进行了结合实际项目的扩展,本工作流引擎适用于任何相对简单的审批场景,有新的业务流程仅需针对申请表单和审批逻辑进行接口新增和策略工厂扩展即可,如本文对你有帮助请一键三连哦~

相关文章:
独辟蹊径:我是如何用Java自创一套工作流引擎的(下)
作者:后端小肥肠 创作不易,未经允许严禁转载。 姊妹篇:独辟蹊径:我是如何用Java自创一套工作流引擎的(上)_java工作流引擎-CSDN博客 1. 前言 在上一篇博客中,我们详细介绍了如何利用Java语言从…...
【Python】pycharm常用快捷键操作
目录 一.pycharm自定义修改快捷键 二.pycharm默认常用快捷键 一.pycharm自定义修改快捷键 在file-setting-keymap中可以修改快捷键,建议刚开始没特殊需求就不用修改,先熟悉系统默认的常用快捷键,但是以下情况可以考虑修改: 之前使用其他I…...
es6语法复习一
es6语法 1.var 变量提升 2.let 不存在变量提升,只能定义一次 3.const 先定义再使用,定义好来不能修改 4.解构赋值 [a,b,c][1,2,3],{a,b,c}{a:1,b:2,c:3} 5.模版字符串 let aaa; ${a} is ok 6.对象简化写法 const school{ name, change, improve(){ cons…...
【python入门】自定义函数
文章目录 定义自定义函数的基本语法参数类型示例代码函数作用域匿名函数(Lambda)闭包装饰器 Python中的自定义函数允许你编写一段可重用的代码块,这段代码可以带参数(输入),并可能返回一个值(输…...
ONLYOFFICE 桌面编辑器 8.1 版发布:全面提升文档处理效率的新体验
文章目录 什么是ONLYOFFICE ?ONLYOFFICE 桌面编辑器 8.1 发布:新功能和改进功能强大的 PDF 编辑器幻灯片版式功能从右至左语言支持多媒体功能增强无缝切换工作模式其他改进和优化总结 什么是ONLYOFFICE ? https://www.onlyoffice.com/zh/off…...
ESP32实现UDP连接——micropython版本
代码: import network import socket import timedef wifiInit(name, port):ap network.WLAN(network.AP_IF) # 创建一个热点ap.config(essidname, authmodenetwork.AUTH_OPEN) # 无需密码ap.active(True) # 激活热点ip ap.ifconfig()[0] # 获取ip地址print(…...
Windows Ternimal
Windows Ternimal 安装 Windows 终端概述 | Microsoft Learn wt --help在当前目录打开 lextm/windowsterminal-shell: Install/uninstall scripts for Windows Terminal context menu items 打开指定目录 wt -d %USERPROFILE% ohmyposh 美化 1 安装 2 添加 ohmyposh bin…...
Unity扩展编辑器功能的特性
1.添加分组标题 用于在Unity的Inspector视图中为属性或变量组创建一个自定义的标题或头部,有助于在Inspector中组织和分类不同的属性,使其更易于阅读和管理。 [Header("Common Properties")] public float MouseSensitivity 5; public float…...
API类别 - UI核心
API类别 - UI核心 引言 在当今的数字时代,用户界面(UI)是任何软件或应用成功的关键因素之一。UI核心API作为构建用户界面的基础,提供了丰富的功能和工具,使得开发者能够创建出既美观又实用的用户界面。本文将深入探讨UI核心API的不同类别,以及它们如何影响现代软件开发…...
Redis-主从复制-配置主从关系
文章目录 1、修改配置文件中的 bind ,注释该配置,取消绑定仅主机登录2、修改protected-mode 为no,取消保护模式3、查看redis的进程状态4、配置6380是6379的从机5、配置6381是6379的从机6、查看主机 6379 的主从信息 1、修改配置文件中的 bind ,注释该配置,取消绑定仅主机登录 …...
DigiRL:让 AI 自己学会控制手机
类似于苹果此前发布的Ferret-UI 的安卓开源平替。主要用于在 Android 设备上识别 UI 和执行指令,不同的是它利用了离线到在线强化学习(Offline-to-Online RL),能够快速适应应用更新或 UI 变化。...
04.Ambari自定义服务开发-自定义服务配置文件在Ambari中的设置方法
文章目录 设置方法配置文件设置Custom xxx配置文件详细的配置方法.xml文件的整体格式基础参数格式value-attributes配置介绍设置属性在服务安装后不可修改设置允许字段为空是否显示配置名称参数类型设置字符串类型PasswordBooleanIntFloatDirectoryDirectoriesContent-多行文本…...
LSTM时间序列基础学习
时间序列 时间序列可以是一维,二维,三维甚至更高维度的数据,在深度学习的世界中常见的是三维时间序列,这三个维度分别是(batch_size,time_step,input_dimensions)。 其中time_step是时间步,它…...
『Z-Workshop』 6月22日线下ALCOVE分享活动
2024 求是创新 ZJUBCA Sponsored by the ALCOVE Community TIME:2024/06/22 ADD:浙江大学紫金港校区 --- Alcove 是 Aptos 公链与 Alibaba Cloud 共同打造的亚洲首个 Move 开发者社区,致力于支持开发者使用 Move 语言构建下一代 Web3 应用&am…...
【机器学习】机器学习重要方法——迁移学习:理论、方法与实践
文章目录 迁移学习:理论、方法与实践引言第一章 迁移学习的基本概念1.1 什么是迁移学习1.2 迁移学习的类型1.3 迁移学习的优势 第二章 迁移学习的核心方法2.1 特征重用(Feature Reuse)2.2 微调(Fine-Tuning)2.3 领域适…...
uniapp, [TypeError] “Failed to fetch dynamically imported module“ 报错解决思路
文章目录 1. 背景2. 报错3. 解决思路4. 思考参考1. 背景 最近基于uniapp开发一款设备参数调试的APP软件,在使用第三方插件的过程中,出现下面的报错。 2. 报错 [plugin:vite:import-analysis] Cannot find module ‘D:/leaning/uniapp/demo/jk-uts-udp示例/uni_modules/uts-…...
四川省高等职业学校大数据技术专业建设暨专业质量监测研讨活动顺利开展
6月21日,省教育评估院在四川邮电职业技术学院组织开展全省高等职业学校大数据技术专业建设暨专业质量监测研讨活动。省教育评估院副院长赖长春,四川邮电职业技术学院党委副书记、校长冯远洪,四川邮电职业技术学院党委委员、副校长程德杰等出席…...
深入解析三大跨平台开发框架:Flutter、React Native 和 uniapp
深入解析三大跨平台开发框架:Flutter、React Native 和 uniapp 在移动开发中,跨平台开发框架已经成为开发者的首选工具。本篇将深入解析三大主流跨平台开发框架:Flutter、React Native 和 uniapp。下面将详细探讨它们的原理、优势和劣势。 …...
【吊打面试官系列-MyBatis面试题】#{}和${}的区别是什么?
大家好,我是锋哥。今天分享关于 【#{}和${}的区别是什么?】面试题,希望对大家有帮助; #{}和${}的区别是什么? #{} 是预编译处理,${}是字符串替换。 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网…...
解决HTTP 400 Bad Request错误的方法
解决HTTP 400 Bad Request错误的方法 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 在进行网络通信时,HTTP 400 Bad Request错误是相对常见的问题…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
sshd代码修改banner
sshd服务连接之后会收到字符串: SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢? 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头,…...
OPENCV图形计算面积、弧长API讲解(1)
一.OPENCV图形面积、弧长计算的API介绍 之前我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天我们主要结合轮廓检测的API去计算图形的面积,这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能,常用的API…...
window 显示驱动开发-如何查询视频处理功能(三)
D3DDDICAPS_GETPROCAMPRANGE请求类型 UMD 返回指向 DXVADDI_VALUERANGE 结构的指针,该结构包含特定视频流上特定 ProcAmp 控件属性允许的值范围。 Direct3D 运行时在D3DDDIARG_GETCAPS的 pInfo 成员指向的变量中为特定视频流的 ProcAmp 控件属性指定DXVADDI_QUER…...
