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

一个完整的流程表单流转

1.写在前面

一个完整的流程表单审批(起表单-->各环节审批-->回退-->重新审批-->完成),前端由Vue2+js+Element UI升级为Vue3+ts+Element Plus,后端流程框架使用Flowable,项目参考了ruoyi-vue-pro(https://gitee.com/zhijiantianya/ruoyi-vue-pro)项目。

2.视频演示

OA系统自定义表单审批流转流程演示

3.表单

3.1表单的设计

依据业务需求,完全自定义表单,可以依据流程节点设置表单中每个属性的读写,实现原理是读取审批节点的编码,依据编码控制每个属性的读写。

<el-tree-select:disabled="!required"v-model="form.deptCode":data="deptList":props="defaultPropsForData"check-strictlynode-key="id"placeholder="请选择集团成员单位"/>/*** 根据不同参数控制表单属性的可读可写* @param pattern*/
const changeRequiredByPattern = (pattern: string) => {if (pattern === 'create') {//说明增加required.value = true} else if (pattern === 'update' || pattern === 'starter') {//修改required.value = truegetBid()} else {//只读required.value = falsegetBid()}
}

3.2.表单与流程关联

通过设计流程时定义的流程编码,在创建表单的后端服务调用中,实现表单与流程的绑定。

/*** 投标对应的流程定义 KEY*/public static final String PROCESS_KEY = "bidApproval";//发起BPM流程Map<String, Object> processInstanceVariables = new HashMap<>();processInstanceVariables.put("deptId", bidDO.getDeptId());processInstanceVariables.put("bidMoney", bidDO.getBidMoney());String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY).setVariables(processInstanceVariables).setBusinessKey(bidDO.getProjectName()));

3.3.表单与前端流程实例的关联

在通过待办打开需要审批的表单时,不同的流程实例如何对应不同的表单,并在页面展示具体的表单数据了?答案是通过定义流程时填写的表单组件名称,利用Vue的<component>元组件来实现。

<componentref="formDetailRef"v-if="processInstance.id !== undefined":is="processInstance.processDefinition.formComponentName":processInstanceId="processInstance.id":pattern="runningTasks.length > 0 ? runningTasks[0].definitionKey : 'readOnly'"@success="getDetail"/>

3.4.表单的保存

表单的保存分为提交时的保存与不提交的保存。不提交的保存用于修改数据但流程不需要提交到下一节点审批的情况,方便保存数据进入待办里面进行后续的修改。提交的时,流程会自动调用保存接口,先进行业务数据的保存,然后再进行流程的提交。

<el-button color="#626aef" @click="handleSave"><Icon icon="ep:coin" />保存</el-button><el-button type="success" @click="handleApproval(item)"><Icon icon="ep:select" />提交</el-button>/** 处理保存表单的操作 只更新表单数据 不提交流程任务 */
const formDetailRef = ref()
const handleSave = () => {formDetailRef.value.submitForm()
}/** 处理审批通过的操作 */
const approvalRef = ref()
const handleApproval = async (item) => {approvalRef.value.open(item)
}
@Override@Transactional(rollbackFor = Exception.class)public Long updateBid(BidUpdateReqVO updateReqVO) {BidDO bidDO = BidConvert.INSTANCE.convert3(updateReqVO);//checkDeptIsMateTendererDept(deptRespDTO, tendererDeptRespDTO);bidDO.setDeptId(findDeptByCode(updateReqVO.getDeptCode()).getId());bidDO.setTendererId(findDeptByName(updateReqVO.getTendererName()).getId());fileApi.deleteFile(bidMapper.selectById(bidDO.getId()).getFiles(), bidDO.getFiles());bidMapper.updateById(bidDO);return bidDO.getId();}@Override@Transactional(rollbackFor = Exception.class)public void approveProcessTask(BidUpdateReqVO bidUpdateReqVO, BpmProcessTaskApprovalDTO bpmProcessTaskApprovalDTO) {//需要根据流程中不同的节点 更改对应的表单信息 比如 需要在流程最后一个节点点击提交时 更改流程状态为完成BidDO bidDO = BidConvert.INSTANCE.convert3(bidUpdateReqVO);bidDO.setDeptId(findDeptByCode(bidUpdateReqVO.getDeptCode()).getId());bidDO.setTendererId(findDeptByName(bidUpdateReqVO.getTendererName()).getId());updateFlowInfoByProcessInstanceState(bidDO);fileApi.deleteFile(bidMapper.selectById(bidDO.getId()).getFiles(), bidDO.getFiles());bidMapper.updateById(bidDO);bpmProcessTaskApi.approvalTask(bpmProcessTaskApprovalDTO);}

4.流程的审批

流程的审批按照设计流程的审批节点依次进行流转,不支持夸环节提交,支持流程的自由回退。提交当前审批任务时,进行下一节点的人员选择,在流程设计时,每个节点审批的人员的选择逻辑已经确定,也支持自由选择组织中的所有人员。

4.1.流程审批人员的设置

流程审批节点的人员设置主要思路为给定一个角色,让审批人员提交任务时,从角色中选择一个人员,这样可以缩小选择的范围。如果表单有对应的部门属性,可以设置审批人员是某个角色中且部门与表单部门属性相同的人员。

// 选择角色中的人员
private Set<Long> calculateTaskCandidateUsersByRole(BpmTaskAssignRuleDO rule) {return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());}//依据角色选择表单部门中的所属人员
private Set<Long> calculateTaskCandidateUsersByRolePerson(Map<String, Object> variables, BpmTaskAssignRuleDO rule) {Long deptId = (Long) variables.get("deptId");//获取流程实例变量的部门//从角色中获取属于该部门的人员Set<Long> userIdsByRoleId = permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());Set<Long> userIdsByDeptId = userApi.getUsersByDeptId(deptId);return new HashSet<>(CollUtil.intersection(userIdsByDeptId, userIdsByRoleId));}

4.2.流程的流转

流程的流转其实没有什么好说的,就是按照流程设计的审批节点依次往下走,遇到网关时,根据前期设计好的条件读取对应的属性跳转到不同审批支线。本例中,会根据投标金额是否大于500万做判断,走不同的分支,而500万的属性,在创建流程时就已经传入了流程实例的变量中。

流程的回退,流程的回退依据流程的节点图,不管流程流转了多少圈,回退只允许回退当前审批节点的前面节点。

public Set<BpmDoneUserTaskNodeRespVO> getDoneUserTaskNodes(String taskId) {Set<BpmDoneUserTaskNodeRespVO> resultList = new HashSet<>();//获取流程实例idTask task = getTask(taskId);// 校验流程实例存在ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());//获取历史任务实例 条件为 流程实例 未完成 按照任务开始时间降序排列List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery().processInstanceId(instance.getProcessInstanceId()).finished().orderByHistoricTaskInstanceEndTime().desc().list();//需要做一个筛选,只能选择当前任务节点之前的节点进行回退// 1. 获取流程模型实例 BpmnModelBpmnModel bpmnModel = bpmProcessDefinitionService.getBpmnModel(task.getProcessDefinitionId());// 2. 通过任务节点id,来获取当前节点信息FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());// 3.获取下一个节点(或者多个节点的)信息,需要去重,因为并行的节点之前的节点会找多遍Set<FlowElement> flowElements = new HashSet<>();// 4.获取流程实例的变量Map<String, Object> variables = taskService.getVariables(taskId);getBeforeNodes(flowElement, flowElements, variables);if (flowElements.isEmpty()){//说明处于第一个节点,此时不能回退throw exception(TASK_ROLLBACK_FORBIDDEN);}//找交集for(FlowElement f: flowElements) {HistoricTaskInstance h = historicTaskInstances.stream().filter( hi -> hi.getTaskDefinitionKey().equals(f.getId())).findFirst().orElse(null);if (h == null){//没有获取到最新的的节点审批信息,是不正常的情况throw exception(TASK_ROLLBACK_APPROVED_INFO_NULL);}//获取 审批人员的编号Long assignee = Long.valueOf(h.getAssignee());//获取人员信息UserRespDTO userRespDTO = adminUserApi.getUser(assignee);//获取部门信息DeptRespDTO deptRespDTO = deptApi.getDept(userRespDTO.getDeptId());resultList.add(BpmTaskConvert.INSTANCE.convertBpmDoneUserTaskNodeRespVO(h, userRespDTO, deptRespDTO));}return resultList;}

5.流程审批详情

流程审批详情包括审批记录与流程图的展示。

5.1.审批记录详情

审批记录根据审批的先后顺序展示数据,状态根据提交回退的不同使用不同颜色的标签显示。

<el-table v-loading="loading" :data="tasks" border><el-table-column align="center" prop="name" label="审批环节" width="200" /><el-table-column align="center" prop="assigneeUser.nickname" label="审批人" width="180" /><el-table-columnlabel="任务开始时间"align="center"prop="createTime"width="180":formatter="dateFormatter"/><el-table-columnlabel="任务结束时间"align="center"prop="endTime"width="180":formatter="dateFormatter"/><el-table-column label="任务耗时" align="center" prop="durationInMillis" width="150"><template #default="scope"><span>{{ formatPast2(scope.row.durationInMillis) }}</span></template></el-table-column><el-table-column label="审批结果" align="center" prop="result" width="150"><template #default="scope"><el-tag :type="getTimelineItemType(scope.row)">{{ getDictLabel(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT, scope.row.result) }}</el-tag></template></el-table-column><el-table-column label="审批意见" align="center" prop="reason" width="250" /></el-table>
public List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId) {// 获得任务列表List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序.list();if (CollUtil.isEmpty(tasks)) {return Collections.emptyList();}// 获得 TaskExtDO MapList<BpmTaskExtDO> bpmTaskExtDOs = taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);// 获得 ProcessInstance MapHistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);// 获得 User MapSet<Long> userIds = convertSet(tasks, task -> NumberUtils.parseLong(task.getAssignee()));userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));Map<Long, UserRespDTO> userMap = adminUserApi.getUserMap(userIds);// 获得 Dept MapMap<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), UserRespDTO::getDeptId));// 拼接数据return BpmTaskConvert.INSTANCE.convertList3(tasks, bpmTaskExtDOMap, processInstance, userMap, deptMap);}

5.2.流程图显示

流程图显示比较复杂,可以查看对应的代码,主要就是使用了bpmn-js库,根据后端的数据,进行不同的展示,核心代码个人理解是这一块。

const highlightDiagram = async () => {const activityList = activityLists.valueif (activityList.length === 0) {return}// 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现// 再次基础上,增加不同审批结果的颜色等等let canvas = bpmnModeler.get('canvas')let todoActivity: any = activityList.find((m: any) => !m.endTime) // 找到待办的任务let endActivity: any = activityList[activityList.length - 1] // 获得最后一个任务// debuggerbpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n: any) => {let activity: any = activityList.find((m: any) => m.key === n.id) // 找到对应的活动if (!activity) {return}if (n.$type === 'bpmn:UserTask') {// 用户任务// 处理用户任务的高亮const task: any = taskList.value.find((m: any) => m.id === activity.taskId) // 找到活动对应的 taskIdif (!task) {return}// 高亮任务canvas.addMarker(n.id, getResultCss(task.result))// 如果非通过,就不走后面的线条了if (task.result !== 2) {return}// 处理 outgoing 出线const outgoing = getActivityOutgoing(activity)outgoing?.forEach((nn: any) => {// debuggerlet targetActivity: any = activityList.find((m: any) => m.key === nn.targetRef.id)// 如果目标活动存在,则根据该活动是否结束,进行【bpmn:SequenceFlow】连线的高亮设置if (targetActivity) {canvas.addMarker(nn.id, targetActivity.endTime ? 'highlight' : 'highlight-todo')} else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {// TODO 芋艿:这个流程,暂时没走到过canvas.addMarker(nn.id, activity.endTime ? 'highlight' : 'highlight-todo')canvas.addMarker(nn.targetRef.id, activity.endTime ? 'highlight' : 'highlight-todo')} else if (nn.targetRef.$type === 'bpmn:EndEvent') {// TODO 芋艿:这个流程,暂时没走到过if (!todoActivity && endActivity.key === n.id) {canvas.addMarker(nn.id, 'highlight')canvas.addMarker(nn.targetRef.id, 'highlight')}if (!activity.endTime) {canvas.addMarker(nn.id, 'highlight-todo')canvas.addMarker(nn.targetRef.id, 'highlight-todo')}}})} else if (n.$type === 'bpmn:ExclusiveGateway') {// 排它网关// 设置【bpmn:ExclusiveGateway】排它网关的高亮canvas.addMarker(n.id, getActivityHighlightCss(activity))// 查找需要高亮的连线let matchNN: any = undefinedlet matchActivity: any = undefinedconst outgoing = getActivityOutgoing(activity)outgoing.forEach((nn: any) => {let targetActivity = activityList.find((m: any) => m.key === nn.id)if (!targetActivity) {return}// 特殊判断 endEvent 类型的原因,ExclusiveGateway 可能后续连有 2 个路径://  1. 一个是 UserTask => EndEvent//  2. 一个是 EndEvent// 在选择路径 1 时,其实 EndEvent 可能也存在,导致 1 和 2 都高亮,显然是不正确的。// 所以,在 matchActivity 为 EndEvent 时,需要进行覆盖~~if (!matchActivity || matchActivity.type === 'endEvent') {matchNN = nnmatchActivity = targetActivity}})if (matchNN && matchActivity) {canvas.addMarker(matchNN.id, getActivityHighlightCss(matchActivity))}} else if (n.$type === 'bpmn:ParallelGateway') {// 并行网关// 设置【bpmn:ParallelGateway】并行网关的高亮canvas.addMarker(n.id, getActivityHighlightCss(activity))const outgoing = getActivityOutgoing(activity)outgoing.forEach((nn: any) => {// 获得连线是否有指向目标。如果有,则进行高亮const targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)if (targetActivity) {canvas.addMarker(nn.id, getActivityHighlightCss(targetActivity)) // 高亮【bpmn:SequenceFlow】连线// 高亮【...】目标。其中 ... 可以是 bpm:UserTask、也可以是其它的。当然,如果是 bpm:UserTask 的话,其实不做高亮也没问题,因为上面有逻辑做了这块。canvas.addMarker(nn.targetRef.id, getActivityHighlightCss(targetActivity))}})} else if (n.$type === 'bpmn:StartEvent') {// 开始节点 流程只要发起 开始节点就是完成状态let targetActivity = activityList.find((m) => m.key === n.id)if (targetActivity) {canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)}// 开始节点const outgoing = getActivityOutgoing(activity)outgoing.forEach((nn) => {// outgoing 例如说【bpmn:SequenceFlow】连线// 获得连线是否有指向目标。如果有,则进行高亮let targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)if (targetActivity) {canvas.addMarker(nn.id, 'highlight') // 高亮【bpmn:SequenceFlow】连线canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)}})} else if (n.$type === 'bpmn:EndEvent') {// 结束节点if (!processInstance.value || processInstance.value.result === 1) {return}canvas.addMarker(n.id, getResultCss(processInstance.value.result))} else if (n.$type === 'bpmn:ServiceTask') {//服务任务if (activity.startTime > 0 && activity.endTime === 0) {//进入执行,标识进行色canvas.addMarker(n.id, getResultCss(1))}if (activity.endTime > 0) {// 执行完成,节点标识完成色, 所有outgoing标识完成色。canvas.addMarker(n.id, getResultCss(2))const outgoing = getActivityOutgoing(activity)outgoing?.forEach((out) => {canvas.addMarker(out.id, getResultCss(2))})}}})
}

6.写在最后

本文简单的介绍了一个OA办公系统表单审批的全过程,行文比较粗糙,代码只展示了很少的一部分,如果有兴趣一起研究讨论的,欢迎留言批评指教。

相关文章:

一个完整的流程表单流转

1.写在前面 一个完整的流程表单审批&#xff08;起表单-->各环节审批-->回退-->重新审批-->完成&#xff09;&#xff0c;前端由Vue2jsElement UI升级为Vue3tsElement Plus&#xff0c;后端流程框架使用Flowable&#xff0c;项目参考了ruoyi-vue-pro(https://gite…...

2024杭州国际智慧城市,人工智能,安防展览会(杭州智博会)

在智能化浪潮的冲击下&#xff0c;我们的生活与环境正在经历一场深刻的变革。这是一场前所未有的技术革命&#xff0c;它以前所未有的速度和广度&#xff0c;改变着我们的生活方式、工作方式、思维方式和社会结构。在这场变革中&#xff0c;有的人选择激流勇进&#xff0c;拥抱…...

编程笔记 html5cssjs 031 HTML视频

编程笔记 html5&css&js 031 HTML视频 一、<video>: 视频元素二、属性三、事件四、嵌入视频页面五、练习小结 视频应用广泛&#xff0c;当前的互联网应用中&#xff0c;视频越来越重要&#xff0c;比如抖音、快手、腾讯视频等应用。 一、<video>: 视频元素 …...

SpringBoot外部配置文件

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:SpringBoot外部配置文件 📚个人知识库: Leo知识库,欢迎大家访问 1.前言☕…...

99个Python脚本实用实例

题目&#xff1a;有四个数字&#xff1a;1、2、3、4&#xff0c;能组成多少个互不相同且无重复数字的三位数&#xff1f;各是多少&#xff1f; #!/usr/bin/python# -*- coding: UTF-8 -*-for i in range(1,5): for j in range(1,5): for k in range(1,5): …...

HarmonyOS 工程目录介绍

工程目录 AppScope&#xff1a;存放应用全局所需要的资源文件 base element&#xff1a;文件夹主要存放公共的字符串、布局文件等资源media&#xff1a;存放全局公共的多媒体资源文件app.json5&#xff1a;应用的全局的配置文件&#xff0c;用于存放应用公共的配置信息 {"…...

门店管理系统驱动智慧零售升级

在当今数字化经济的大潮中&#xff0c;实体门店正在经历一场由内而外的深度变革。门店管理系统以其高效、便捷和全面的功能特性&#xff0c;为实体店提供了高效的运营解决方案。 门店管理系统拜托了传统零售业对本地化软件的依赖&#xff0c;它将复杂的信息技术转化为易于获取…...

Iterator迭代器操作集合元素时,不能用集合删除元素

在使用Iterator迭代器对集合中的元素进行迭代时&#xff0c;如果调用了集合对象的remove()方法删除元素或者调用add()方法添加元素之后&#xff0c;继续使用迭代器遍历元素&#xff0c;会出现异常(java.util.ConcurrentModificationException)。 import java.util.ArrayList; …...

Spring Boot是什么-特点介绍

什么是SpringBoot Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其中“Boot”的意思就是“引导”&#xff0c;Spring Boot 并不是对 Spring 功能上的增强&#xff0c;而是提供了一种快速开发 Spring应用的方式。 Spring Boot 特点 嵌入的 Tomcat&#xff0c;无需部署…...

相机成像之图像传感器与ISP【四】

文章目录 1、图像传感器基础1.1 基础原理——光电效应1.2 基础的图像传感器设计1.3 衡量传感器效率的一个关键指标&#xff1a;光量子效率&#xff08;QE&#xff09;1.4 感光单元的响应1.5 像素的满阱容量1.6 像素尺寸和填充比例1.7 微透镜的作用1.8 光学低通滤波器简介1.9 传…...

新手入门Java 方法带参,方法重载及面向对象和面向过程的区别介绍

第二章 方法带参 课前回顾 1.描述类和对象的关系 类是一组对象的共有特征和行为的描述。对象是类的其中一个具体的成员。 2.如何创建对象 类名 对象名 new 类名();3.如何定义和调用方法 public void 方法名(){}对象名.方法名();4.成员变量和局部变量的区别 成员变量有初…...

使用Sqoop将Hive数据导出到TiDB

关系型数据库与大数据平台之间的数据传输之前写过一些 使用Sqoop将数据在HDFS与MySQL互导 使用Sqoop将SQL Server视图中数据导入Hive 使用DataX将Hive与MySQL中的表互导 使用Sqoop将Hive数据导出到TiDB虽然没写过&#xff0c;但网上一堆写的&#xff0c;那为什么我要专门写一下…...

互联网上门洗衣洗鞋工厂系统搭建;

随着移动互联网的普及&#xff0c;人们越来越依赖手机应用程序来解决生活中的各种问题。通过手机预约服务、购买商品、获取信息已经成为一种生活习惯。因此&#xff0c;开发一款上门洗鞋小程序&#xff0c;可以满足消费者对于方便、快捷、专业的洗鞋服务的需求&#xff0c;同时…...

Redis面试题12

Redis 的主从复制是什么&#xff1f; Redis 的主从复制是一种数据备份和高可用性机制&#xff0c;通过将一个 Redis 服务器的数据复制到其他 Redis 从服务器上来实现数据的冗余备份和读写分离。 主从复制的工作原理如下&#xff1a; 配置主服务器并开启主从复制功能。从服务器…...

el-tree多个树进行节点同步联动(完整版)

2024.1.11今天我学习了如何对多个el-tree树进行相同节点的联动效果&#xff0c;如图&#xff1a; 这边有两棵树&#xff0c;我们发现第一个树和第二个树之间会有重复的指标&#xff0c;当我们选中第一个树的指标&#xff0c;我们希望第二个树如果也有重复的指标也能进行勾选上&…...

python两个字典合并,两个list合并

1.两个字典&#xff1a; a{‘a’:1,‘b’:2,‘c’:3} b {‘aa’:11,‘bb’:22,‘cc’:33} 合并1&#xff1a;dict(a, **b) 结果&#xff1a;{‘a’: 1,‘aa’: 11,‘c’: 3,‘b’: 2,‘bb’: 22,‘cc’: 33} 合并2&#xff1a;dict(a.items()b.items()) 结果&#xff1a;{‘…...

搜维尔科技:【简报】元宇宙数字人赛道,《全息影像技术应用》!

期待着看展的主角来到今天要参观的全息影像展&#xff0c;平时就喜欢看展的她对于所谓的全息影像非常好奇&#xff0c;于是她带着期待的心情进入展内。进入展内的主角看到的是与之前完全不同的画展&#xff0c;每幅画看起来就像真的一样&#xff0c;充满好奇的她在展览的各处游…...

SparkSQL和Hive语法差异

SparkSQL和Hive语法差异 1、仅支持Hive SparkSQL关联条件on不支持函数rand()创建零时表时&#xff0c;Spark不支持直接赋值nullSpark无法读取字段类型为void的表SparkSQL中如果表达式没有指定别名&#xff0c;SparkSQL会将整个表达式作为别名&#xff0c;如果表达式中包含特殊…...

XCODE IOS 静态链接库替换升级

XCODE 版本15.2. 一个很久需求没更新的IOS 应用&#xff0c;近来有新需求要开发。 拉下代码运行&#xff0c;出现了个BAD_ACCESS错误。出错的位置位于一个调用的第三方的.a静态库内部。因为调用代码并没有修改&#xff0c;很容易想到可能XCODE相关升级&#xff0c;导致的问题。…...

API设计:从基础到优秀实践

在这次深入探讨中&#xff0c;我们将深入了解API设计&#xff0c;从基础知识开始&#xff0c;逐步进阶到定义出色API的最佳实践。 作为开发者&#xff0c;你可能对许多这些概念很熟悉&#xff0c;但我将提供详细的解释&#xff0c;以加深你的理解。 API设计&#xff1a;电子商…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !

我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...