独辟蹊径:我是如何用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错误是相对常见的问题…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...