当前位置: 首页 > 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;电子商…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...