java-activiti笔记
版本:activiti7
<dependency><groupId>org.activiti</groupId><artifactId>activiti-json-converter</artifactId><version>7.0.0.Beta2</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency><!--activiti与springboot整合包--><dependency><groupId>org.activiti</groupId><artifactId>activiti-spring-boot-starter</artifactId><version>7.0.0.Beta2</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency>
发布
流程图

会签

这里是将流程图通过bpmn.io插件设计好流程图,存放到服务器端
public AjaxResult publishBpm(BpmFormVo bpmFormVo) {FileInputStream bpmnfileInputStream = null;try{BpmForm byId = getById(bpmFormVo.getId());xml=BpmFormManager.xmlnsActivitiEmpty(xml);xml=BpmFormManager.gatewaySupply(xml);activitiFlowService.delFlowByDefKey(bpmFormVo.getCode());repositoryService.createDeployment().addString(code+ ProcessConstants.SUFFIX, xml).name(code).key(code).deploy();bpmFormVo.setPublishTime(new Date());bpmFormVo.setState(1);bpmFormMapper.publishBpm(bpmFormVo);if("1".equals(byId.getCountersignature())){bpmCountersignService.bpmPublishHandle(byId,xml);}}catch (Exception e){e.printStackTrace();return AjaxResult.error("部署失败");}finally {try {if(bpmnfileInputStream!=null){bpmnfileInputStream.close();}}catch (Exception e){e.printStackTrace();}}return AjaxResult.success("部署成功");}
重新发布需要做如下处理
@SneakyThrowspublic void delFlowByDefKey(String code) {List<String> deploymentIdByName = activitiFlowMapper.getDeploymentIdByName(code+ ProcessConstants.SUFFIX);if(CollUtil.isEmpty(deploymentIdByName)){return;}for(String did:deploymentIdByName){repositoryService.deleteDeployment(did);}}
启动
@Overridepublic ProcessInstance startProcess(String definitionKey, String bussinessId,String userId,String userName, String projectId) {String bpmCode=definitionKey;Authentication.setAuthenticatedUserId(userId);Map<String, Object> map=new HashMap<>();map.put("state",ProcessConstants.AUDIT_YES);map.put("assignee",userId);ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(definitionKey, bussinessId, map);Task task = taskService.createTaskQuery().processDefinitionKey(definitionKey).taskAssignee(userId) //设置任务的负责人.orderByTaskCreateTime().desc().list().get(0);if (task!=null){taskService.addComment(task.getId(), task.getProcessInstanceId(), "流程提交","流程提交成功!");startTaskComplete(bpmCode,projectId,task.getId(),null);}startLogProcessHandle(bpmCode,definitionKey,bussinessId,projectId,userId,userName,processInstance,task);return processInstance;}
将会签中的占位的数据赋值
@Overridepublic void startTaskComplete(String bpmCode,String projectId,String taskId, Map<String, Object> variables) {BpmForm bpmForm = bpmFormService.queryInfoByCode(bpmCode, projectId);if(variables==null){variables=new HashMap<>();}if(bpmForm!=null&&"1".equals(bpmForm.getCountersignature())){//会签模式Object xmlByKey = activitiFlowMapper.getXmlByKey(bpmForm.getResCode() + ".bpmn");Map<String, List<String>> stringListMap = bpmUtil.collectionUseMap(bpmForm, (byte[]) xmlByKey);variables.putAll(stringListMap);}if(CollUtil.isEmpty(variables)){taskService.complete(taskId);}else{taskService.complete(taskId,variables);}}@SneakyThrowspublic Map<String,List<String>> collectionUseMap(BpmForm bpmForm,byte[] resByte){Map<String,List<String>> result=new HashMap<>();if("0".equals(bpmForm.getCountersignature())){return result;}String resStr = new String(resByte);BpmnModel bpmnModel = ModelUtil.getBpmnModel(resStr);Collection<UserTask> allUserTaskEvent = ModelUtil.getAllUserTaskEvent(bpmnModel);if(CollUtil.isEmpty(allUserTaskEvent)){return result;}SysRoleVo sysRoleVo=new SysRoleVo();sysRoleVo.setProjectId(bpmForm.getProjectId());List<SysRoleVo> sysRoleVos = sysRoleService.queryList(sysRoleVo);SysDept sysDept=new SysDept();sysDept.setProjectId(bpmForm.getProjectId());List<SysDept> sysDepts = sysDeptService.queryList(sysDept);for(UserTask userTask:allUserTaskEvent){if(userTask.hasMultiInstanceLoopCharacteristics()){MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();String inputDataItem = loopCharacteristics.getInputDataItem();String coll = inputDataItem.replace("${", "").replace("}", "");String[] groups = coll.split("_");if("ROLE".equals(groups[0])){List<String> idListByRoleId = getIdListByRoleId(groups[1], sysRoleVos).getUserIdList();if(CollUtil.isNotEmpty(idListByRoleId)){result.put(coll,idListByRoleId);}}else if("DEPT".equals(groups[0])){List<String> idListByDeptId = getIdListByDeptId(groups[1], sysDepts).getUserIdList();if(CollUtil.isNotEmpty(idListByDeptId)){result.put(coll,idListByDeptId);}}}}return result;}
流程审批
@SneakyThrowspublic CompletResultVo orSignCompletHandle(String taskId, String result, String remark, String definitionKey, Map<String, Object> variables, String userName){Task task = taskService.createTaskQuery().taskId(taskId).singleResult();taskService.addComment(taskId, task.getProcessInstanceId(), result,remark);if(variables==null){variables=new HashMap<>();}variables.put("state",result);taskService.complete(taskId,variables);CompletResultVo completResultVo=new CompletResultVo();boolean afterHandle = afterHandle(task, userName, remark, result);completResultVo.setAuditEndState(afterHandle);return completResultVo;}boolean afterHandle(Task task,String userName,String remark,String result){//存储审核日志信息QueryWrapper queryWrapper=new QueryWrapper<>();queryWrapper.eq("process_instance_id",task.getProcessInstanceId());BussTask bussTask = bussTaskMapper.selectOne(queryWrapper);//日志记录
completByTaskLogHandle(bussTask,task,task.getProcessInstanceId(),userName,remark, result);//更新结束记录return endHandle(task.getProcessInstanceId(),bussTask,userName);}
判断流程结束后相关操作
boolean endHandle(String piid,BussTask bussTask,String userName){List<Task> task1 = taskService.createTaskQuery().processInstanceId(piid).list();bussTaskMapper.updateById(bussTask);if(CollUtil.isEmpty(task1)){bussTaskMapper.updateStatus(piid,ProcessConstants.AUDIT_NO);endLogHandle(bussTask);return true;}return false;}
会签审批
@SneakyThrowspublic CompletResultVo countersignCompletHandle(String taskId, String result, String remark,String definitionKey,String projectId,String userName,Long userId,BpmForm bpmForm){Task task = taskService.createTaskQuery().taskId(taskId).singleResult();String taskDefinitionKey = task.getTaskDefinitionKey();UserTask userTask = getUserTaskByNodeId(bpmForm.getResCode(), taskDefinitionKey);if(userTask==null){return orSignCompletHandle(taskId,result,remark,definitionKey,projectId,null,userName);}if(!userTask.hasMultiInstanceLoopCharacteristics()){return orSignCompletHandle(taskId,result,remark,definitionKey,projectId,null,userName);}MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();String completionCondition = loopCharacteristics.getCompletionCondition();String codeByInfo = BpmCountersignTypeEnum.getCodeByInfo(completionCondition);Map<String,Object> variables=new HashMap<>();QueryWrapper queryWrapper=new QueryWrapper<>();queryWrapper.eq("process_instance_id",task.getProcessInstanceId());BussTask bussTask = bussTaskMapper.selectOne(queryWrapper);List<LogProcess> bussList = getLogs(bussTask.getBussId(), task.getProcessInstanceId());List<BpmCountersignRecord> bpmCountersignRecords=new ArrayList<>();if(CollUtil.isNotEmpty(bussList)){BpmCountersignRecord bpmCountersignRecord=new BpmCountersignRecord();bpmCountersignRecord.setExecutionId(bussList.get(0).getExecutionId());bpmCountersignRecords= bpmCountersignService.queryListByExeId(bpmCountersignRecord);}BpmCountersign bpmCountersignByTaskInfo = bpmCountersignService.getBpmCountersignByTaskInfo(task.getTaskDefinitionKey(), bussTask.getDefinitionKey());String[] user_split = bpmCountersignByTaskInfo.getUids().split(",");if("all".equals(codeByInfo)){if(ProcessConstants.AUDIT_NO.equals(result)){variables.put("pass",true);variables.put("state",ProcessConstants.AUDIT_NO);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,false,true,bussList);}else{if(user_split.length<=(bpmCountersignRecords.size()+1)){variables.put("pass",true);variables.put("state",ProcessConstants.AUDIT_YES);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,true,true,bussList);}else{variables.put("pass",false);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,false,false,bussList);}}}else if("half".equals(codeByInfo)){if(user_split.length<=(bpmCountersignRecords.size()+1)){int ok_users=0;for(BpmCountersignRecord bpm:bpmCountersignRecords){if(ProcessConstants.AUDIT_YES.equals(bpm.getAuditResult())){ok_users+=1;}}if(ProcessConstants.AUDIT_YES.equals(result)){ok_users+=1;}String state=null;BigDecimal divide = new BigDecimal(ok_users).divide(new BigDecimal(user_split.length), 2, BigDecimal.ROUND_HALF_UP)double doubleValue = divide.doubleValue();if(doubleValue>=0.5){state=ProcessConstants.AUDIT_YES;}else{state=ProcessConstants.AUDIT_NO;}variables.put("state",state);variables.put("pass",true);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,state.equals(ProcessConstants.AUDIT_YES)?true:false,true,bussList);}else{variables.put("pass",false);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,false,false,bussList);}}else if("single".equals(codeByInfo)){if(ProcessConstants.AUDIT_YES.equals(result)) {variables.put("pass",true);variables.put("state",ProcessConstants.AUDIT_YES);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,true,true,bussList);}else{if(user_split.length<=(bpmCountersignRecords.size()+1)){variables.put("pass",true);variables.put("state",ProcessConstants.AUDIT_NO);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,false,true,bussList);}else{variables.put("pass",false);taskService.complete(taskId,variables);countersignRecordHandle(task,bpmForm,userName,userId,bpmCountersignByTaskInfo,result,remark,bussTask,false,false,bussList);}}}CompletResultVo completResultVo=new CompletResultVo();List<LogProcess> bussList2 = getLogs(bussTask.getBussId(), task.getProcessInstanceId());String aname="";if(CollUtil.isNotEmpty(bussList2)){aname=bussList2.get(0).getAssigneeName();}else{aname= bussList.get(0).getAssigneeName();}boolean endHandle = endHandle(task.getProcessInstanceId(), bussTask, aname);completResultVo.setAuditEndState(endHandle);return completResultVo;}//日志及操作记录/**** @param task* @param bpmForm* @param userName* @param userId* @param bpmCountersignByTaskInfo* @param result 当前人的审核状态* @param remark* @param auditState 最终审核状态(会签)* @param isEnd 当前节点是否全部完成*/void countersignRecordHandle(Task task,BpmForm bpmForm,String userName,Long userId,BpmCountersign bpmCountersignByTaskInfo,String result,String remark,BussTask bussTask,boolean auditState,boolean isEnd,List<LogProcess> bussList){LogProcess logRecord=null;if(CollUtil.isEmpty(bussList)){logRecord=new LogProcess();logRecord.setProjectId(bpmForm.getProjectId());String myExeId=UUID.randomUUID().toString();logRecord.setExecutionId(myExeId);logRecord.setBusinessId(bussTask.getBussId());logRecord.setTaskId(task.getId());logRecord.setAssigneeName(bpmCountersignByTaskInfo.getUnames());logRecord.setAssignee(bpmCountersignByTaskInfo.getUids());logRecord.setInstanceId(task.getProcessInstanceId());logRecord.setDefinitionKey(bpmForm.getResCode());if(isEnd){logRecord.setStepType(auditState?ActivitiStepType.approved.getCode():ActivitiStepType.reject.getCode());}else{logRecord.setStepType(ActivitiStepType.unapproved.getCode());}logProcessMapper.insert(logRecord);}else{if(isEnd){logRecord = bussList.get(0);logRecord.setStepType(auditState?ActivitiStepType.approved.getCode():ActivitiStepType.reject.getCode());logProcessMapper.updateById(logRecord);}}BpmCountersignRecord bcr=new BpmCountersignRecord();bcr.setNodeId(task.getTaskDefinitionKey());bcr.setBpmCode(bpmForm.getCode());bcr.setUserId(userId+"");bcr.setUserName(userName);if(logRecord!=null){bcr.setLogId(logRecord.getId());}else{bcr.setLogId(bussList.get(0).getId());}bcr.setBusinessId(bussTask.getBussId());bcr.setAuditRemark(remark);bcr.setAuditResult(result);bcr.setExecutionId(logRecord.getExecutionId());bcr.setProcessInstanceId(task.getProcessInstanceId());bpmCountersignRecordMapper.insert(bcr);}
会签流程表达式枚举,全部通过即通过,半数通过即通过,一人通过即通过:
public enum BpmCountersignTypeEnum {all("all", "${nrOfCompletedInstances==nrOfInstances||pass==true}"),half("half", "${nrOfCompletedInstances/nrOfInstances>=0.5&&pass==true}"),single("single", "${nrOfCompletedInstances>=1&&pass==true}");private final String code;private final String info;BpmCountersignTypeEnum(String code, String info){this.code = code;this.info = info;}public static String getCodeByInfo(String info){if(StringUtils.isBlank(info)){return null;}for(BpmCountersignTypeEnum bct:BpmCountersignTypeEnum.values()){String elEscape = EscapeUtil.elEscape(bct.info);if(info.equals(elEscape)){return bct.code;}}return null;}public String getCode(){return code;}public String getInfo(){return info;}
}
这里:
nrOfInstances:总实例数nrOfCompletedInstances:已完成实例数nrOfActiviteInstances:未完成的实例数
全部通过即通过和一人通过即通过直接处理即可,state是流线走向,按比例通过时,我这里用一张表记录了每次操作,即BpmCountersignRecord,将此次记录作为流程操作记录的子关联表,executionId作为关联键。
其他
每次流程启动表act_ge_bytearray中会出现如下图的数据,这就是会签流程里面的变量数据

当流程审批结束后,var-开头的数据就会消失,hist开头的是act_hi表中相关数据

相关文章:
java-activiti笔记
版本:activiti7 <dependency><groupId>org.activiti</groupId><artifactId>activiti-json-converter</artifactId><version>7.0.0.Beta2</version><exclusions><exclusion><groupId>org.mybatis</g…...
Layui——隐藏表单项后不再进行验证
目录 修改后的部分代码 修改后的完整代码 我编辑用户信息和添加新用户用的是同一个表单,不同的是编辑用户信息里没有密码项和确认密码项,但是把它们隐藏后仍然要进行验证,也就是说它们俩的验证并没有随着表单项的隐藏而关闭。原因…...
Github Copilot 使用技巧
🎯目标读者 本文不包含如何安装 Github Copilot本文介绍了 Github Copilot 使用方法和一些技巧 本人已经使用 Github Copilot 2 年了,交了 3 次年费,每年 100$ 着实心痛,但是用着确实爽歪歪 但是感觉一直只用了一小部分功能&am…...
【实现100个unity特效之20】用unity实现物品悬浮和发光像素粒子特效
最终效果 文章目录 最终效果新增飞升粒子效果光圈效果修改不同颜色完结 新增飞升粒子效果 效果 光圈效果 效果 修改不同颜色 完结 赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持…...
GPT-4o mini发布,轻量级大模型如何颠覆AI的未来?
从巨无霸到小巨人:GPT-4o Mini的创新之路 ©作者|潇潇 来源|神州问学 引言 随着人工智能技术的飞速进步,AI领域的竞争日益激烈,大型模型的发布几乎成为常态。然而,这些庞大的模型通常需要大量的计算资源和存储空间ÿ…...
高性能的 C++ Web 开发框架 CPPCMS + WebSocket 模拟实现聊天与文件传输案例。
1. 项目结构 2. config.json {"service": {"api": "http","port": 8080,"ip": "0.0.0.0"},"http": {"script": "","static": "/static"} }3. CMakeLists.txt…...
合合信息OCR支持30类国内常见票据一站式分类识别,支持医疗发票、数电票识别
合合信息TextIn平台明星产品——国内通用票据识别,重磅更新! 产品支持票据类型扩展到23大类、30小类,覆盖场景更全面,同时升级优化了多款票据识别模型,平均识别率较前版本提升11.5%,整体识别速度提升21.9%…...
LeetCode-day40-3151. 特殊数组 I
LeetCode-day40-3151. 特殊数组 I 题目描述示例示例1:示例2:示例3: 思路代码 题目描述 如果数组的每一对相邻元素都是两个奇偶性不同的数字,则该数组被认为是一个 特殊数组 。 Aging 有一个整数数组 nums。如果 nums 是一个 特殊…...
技术研究:Redis 数据结构与 I/O 模型
数据结构 Redis之所以“快”,一方面因为它是内存数据库,所有操作都在内存上完成,内存的访问速度本来就快。另一方面则是因为高效的数据结构,使得操作键值效率较高。总体来说,Redis使用了一个用来保存每个Key/Value的全…...
46-扇孔的处理及铺铜以及布线
1.先连信号线 2.电源管脚,以如下方式处理: 引线打孔处理...
LVS实验的三模式总结
文章目录 LVS的概念叙述NAT工作模式实战案例**思想:**NAT工作模式的优点NAT工作模式的缺点 NAT工作模式的应用场景大致配置 route:打开路由内核功能 部署DR模式集群案例工作思想:大致工作图如下思路模型 具体配置与事实步骤补充 防火墙标签解…...
游戏安全入门-扫雷分析远程线程注入
前言 无论学习什么,首先,我们应该有个目标,那么入门windows游戏安全,脑海中浮现出来的一个游戏 – 扫雷,一款家喻户晓的游戏,虽然已经被大家分析的不能再透了,但是我觉得自己去分析一下还是极好…...
bert-base-chinese模型的完整训练、推理和一些思考
前言 使用google-bert/bert-base-chinese模型进行中文文本分类任务,使用THUCNews中文数据集进行训练,训练完成后,可以导出模型,进行预测。 项目详细介绍和数据下载 数据集下载地址 Github完整代码 现记录训练过程中的一些感悟…...
JS基础5(JS的作用域和JS预解析)
JS的作用域 1. 全局作用域 全局作用域是在代码的任何地方都能访问到的最外层作用域。在浏览器环境下,全局作用域就是window对象,因此所有在全局作用域中声明的变量和函数都会成为window对象的属性和方法。 var globalVar "I am global"; …...
Doris 夺命 30 连问!(中)
导言 抱歉,作为从 S2 开始的骨灰级玩家看到 EDGUZI 官宣首发上线,兴奋之余忘了写文档 - -||,还望各位看官老爷见谅,这次错了,下次还敢 ^_^ 这是继上次的 30 问上篇的中篇,也是 10 个问题,有些…...
书生.浦江大模型实战训练营——(四)书生·浦语大模型全链路开源开放体系
最近在学习书生.浦江大模型实战训练营,所有课程都免费,以关卡的形式学习,也比较有意思,提供免费的算力实战,真的很不错(无广)!欢迎大家一起学习,打开LLM探索大门…...
SpringBoot 整合 RabbitMQ 实现延迟消息
一、业务场景说明 用于解决用户下单以后,订单超时如何取消订单的问题。 用户进行下单操作(会有锁定商品库存、使用优惠券、积分一系列的操作);生成订单,获取订单的id;获取到设置的订单超时时间࿰…...
Cilium:基于开源 eBPF 的网络、安全性和可观察性
基于 eBPF 的网络、安全性和可观察性 Cilium 是一种开源的云原生解决方案,它利用 Linux 内核中的 eBPF 技术来提供、保护和监控工作负载之间的网络连接。 什么是 eBPF? eBPF 是一项源自 Linux 内核的技术,允许沙盒程序在特权上下文&#x…...
Axios 详解与使用指南
Axios 详解与使用指南 1. Axios 简介 Axios 是一个基于 Promise 的 HTTP 客户端,能够在浏览器和 Node.js 环境中运行。它提供了一种简便的方式来执行 HTTP 请求,并支持多种请求方法,如 GET、POST、PUT、DELETE 等。Axios 的配置灵活&#x…...
深度学习 —— 个人学习笔记20(转置卷积、全卷积网络)
声明 本文章为个人学习使用,版面观感若有不适请谅解,文中知识仅代表个人观点,若出现错误,欢迎各位批评指正。 三十九、转置卷积 import torch from torch import nndef trans_conv(X, K):h, w K.shapeY torch.zeros((X.shape[…...
SWIFT报文格式规范:从字符约束到金融交易安全的深度解析
1. SWIFT报文格式规范的核心价值 第一次接触SWIFT报文时,我被那些看似简单的字母代号震撼到了——谁能想到,像"2!n"这样简单的符号组合,竟然承载着全球金融系统的运转规则?在跨境汇款中输错一个字符可能导致资金滞留数周…...
OpenClaw学习助手:Gemma-3-12b-it生成错题本与定制复习计划
OpenClaw学习助手:Gemma-3-12b-it生成错题本与定制复习计划 1. 为什么需要AI学习助手? 作为一名经常需要处理大量学习资料的开发者,我一直在寻找能够提升学习效率的工具。传统的错题本整理方式需要手动抄写题目、标注知识点、寻找同类练习题…...
基于Wan 3D Causal VAE(Show-o2)的模型,重新完整地分析 10分钟的视频 对应多少 vison token
可以。这次我按 Show-o2 官方 432432 配置 和 Wan 3D Causal VAE 的公开时间压缩规则,把 10B token 且全部都是 vision token 的情况重新完整算一遍。下面的“大小”我统一按 未压缩 RGB 原始数据量 来算;如果你问的是实际 JPG / PNG / MP4 落盘大小,那会随压缩格式、码率和…...
马年市场快报分析:欧美组合式一氧化碳及可燃气体报警器指南
马年市场快报分析:欧美组合式一氧化碳及可燃气体报警器指南根据您提供的快报内容,我将从专业角度逐步分析欧美组合式一氧化碳(CO)及可燃气体报警器的关键信息,包括安全标准、风险因素、探测器区别、安装建议以及相关产…...
【米家IoT开发】巧用Charles抓包,高效定位与调试网络接口
1. 为什么Charles是米家IoT开发的调试神器 当你开发米家扩展程序时,最头疼的莫过于接口返回异常数据,或者请求莫名其妙失败。这时候如果只能靠猜问题出在哪里,那简直就是在黑暗中摸索。我刚开始做米家IoT开发时,就经常被这种问题困…...
【数字电路】从双稳态到触发器:时序逻辑的存储基石
1. 数字世界的记忆细胞:双稳态电路探秘 当你按下电脑电源键的瞬间,数十亿个微型存储单元开始工作,它们就像数字世界的记忆细胞,忠实地记录着每一个比特的信息。这一切的起点,正是我们今天要探讨的双稳态电路。想象一下…...
FastAPI + SQLite:从基础CRUD到安全并发的实战指南
核心摘要本文将带你超越FastAPI SQLite的基础CRUD搭建,聚焦于安全防护(认证、授权、输入验证)与并发处理(数据库连接池、异步优化)两大实战痛点。你会获得一套可直接复用的项目骨架,并理解其背后的设计逻辑…...
从“看见光点”到“看懂世界”:视觉重建让这个世界变得更近一些
三十多年前,“让盲人重新看见”更像一句带有未来感的科学想象。而今天,这件事已经进入临床试验和真实的人体研究。视觉重建之所以被视为脑机接口里最具挑战性的方向之一,不只是因为它要解决“能不能刺激大脑”的问题,更因为它要回…...
别再只玩单机了!用AirSim+Python实现你的第一个无人机编队(附完整代码)
从单机到编队:用AirSim和Python打造你的第一支无人机小队 想象一下,当你第一次在AirSim中成功让无人机起飞时的兴奋感——现在,是时候将这份快乐乘以N倍了。本文将带你跨越单机操作的舒适区,进入无人机编队控制的新世界。不需要复…...
BurpSuite导入P12证书遇到密码问题?3种无密码解决方案实测
BurpSuite导入P12证书遇到密码问题?3种无密码解决方案实测 在企业安全测试和渗透评估过程中,客户端证书认证是常见的防护机制。当BurpSuite提示需要P12证书密码而您又无法获取时,整个测试流程可能陷入僵局。本文将分享三种经过实战验证的解决…...
