新闻详情

新闻详情

首页 / 资讯中心 / 详情

Flowable工作流动态预测下一节点:原理、代码实现与踩坑指南

发布时间:2026/10/2 1:14:41来源:尧图网络
Flowable工作流动态预测下一节点:原理、代码实现与踩坑指南
我几年前刚接触Flowable那会儿遇到最多的一个开发需求就是用户在前端点了“提交审批”之后页面要立刻展示“下一环节是哪个节点、该谁来处理”。如果你只是把流程定义画好、直接启动流程实例那这一步其实是隐式的——Flowable会按照流程图的连线自动往下走但你没法在真正执行之前把下一步的情况提前告诉前端。后来项目越做越复杂还会碰到“同一个节点根据表单参数走不同分支”“兜底节点和会签节点混在一个流程里”这类场景光靠固定的流程图已经不够用了必须在运行时根据任务ID和业务参数动态判断下一节点并且把候选分配策略也一并查出来。这篇内容就围绕这个需求展开把实现思路、底层原理、实际代码和排查经验一次说清楚。这个方案适合谁看两类人尤其合适一是刚开始在Spring Boot项目里集成Flowable、被“怎么动态跳转节点”卡住的新手二是已经能跑通基础审批流但需要在流程中做动态路由、候选人动态指定、前端预览路由信息的后端开发。内容不涉及特别高深的理论但会把动态预测“下一节点”这件事从原理到实操拆得很透照着我这个思路改造基本能覆盖绝大多数审批流的“动态下一节点”需求。1. 项目背景与核心需求拆解1.1 为什么“静态流程图”不够用一定需要动态预测Flowable本身是一个很成熟的流程引擎流程图里每条连线上都可以写条件表达式。比如“金额大于5000走总监审批小于等于5000走部门经理审批”这种固定规则通过conditionExpression就能解决流程跑起来引擎会自动判断。但实际业务里没那么简单。举个真实例子。我们做的是一个工单审批系统表单里有一个“加急”开关加急工单可以跳过部门经理直接到总监同时还有一个“是否涉及资金”选项涉及资金的工单到了总监之后还要自动抄送财务节点。这种逻辑用固定表达式也能写但问题是前端需要在下发审批之前就知道接下来的路由结果不然页面没法渲染“即将到达XX节点”的提示也没法提前校验当前操作人是否有权限操作下一节点。再比如驳回场景。某个审批节点被驳回了是回到发起人修改还是回到上一个审批人重新审批这往往取决于业务参数比如驳回原因里是否勾选了“需重新审核”。这种动态路由如果不主动去预测流程引擎运行到那里才会判断但这时候前端已经没法提前给出友好提示了。所以所谓“任务节点动态预测”本质上就是在当前任务还没完成之前通过当前任务的ID、流程实例的上下文、表单携带的业务参数去模拟引擎下一步会怎么走把可能到达的节点和这些节点上的候选人有谁提前查出来并返回给调用方。1.2 目标拆解拿到任务ID之后具体要返回什么我们把这个需求拆开任务其实很明确输入当前任务IDtaskId加上业务参数比如金额、部门、加急标识等这些参数可能存在于流程变量里也可能在前端请求里直接传过来。处理从任务对应的执行实例execution出发读取当前活动节点所有可出口outgoing的连线解析每条连线上的条件表达式筛选出能够通过的条件分支。输出下一个节点的定义信息节点ID、名称、类型以及该节点上的候选分配策略——候选人、候选组或者是固定的办理人是单人办理还是多人会签。候选分配策略这个点是很多人容易忽略的。因为Flowable里一个节点上的办理人可能配置得很复杂可能是指定的用户ID也可能是候选组candidateGroups还可能是通过表达式动态计算出来的比如${assignee1}这种。既然要做“动态预测”那就不能只把节点名称返回给前端得把“这个节点谁有权限处理”也一并梳理出来否则前端还是没法展示完整信息。2. 底层思路与核心原理2.1 Flowable中节点流转的基本模型连线、执行实例和条件表达式要搞清楚“动态预测”怎么实现先得弄明白Flowable在运行业务流程时节点之间的流转靠什么驱动。流程定义BPMN文件里节点和节点之间靠sequenceFlow连接。每一条sequenceFlow有两个属性很关键sourceRef连线的起点节点ID。targetRef连线的终点节点ID。conditionExpression连线上的条件表达式只有在率先节点执行完之后引擎才会去求值。这些连线本身是由流程引擎来执行的。当流程实例运行到某个节点时引擎会生成对应的执行实例execution这个执行实例内部会记录“当前停留在哪个节点”。节点执行完成并且触发complete()之后引擎会找到这个节点的所有出口连线逐条对条件表达式求值。多条连线条件都为真时就会在这里分裂出多个并行分支只有一条为真时就单一路由下去。所以动态预测的核心操作是在当前节点尚未complete之前手动仿真这个过程。读取出当前taskId对应的execution通过execution拿到当前的活动节点currentActivityId再找到这个节点的所有sequenceFlow然后解析每条连线的条件表达式。相当于把引擎已经做过的路由判断提前手动做一遍。这个思路在Flowable的API层面是完全可行的。RuntimeService提供了createExecutionQuery()可以根据executionId查询执行实例RepositoryService提供了getBpmnModel()来获取流程定义模型有了BpmnModel就能通过Process找到某个节点的FlowNode继而拿到它的outgoingFlows。整套链路是通的。2.2 任务ID如何关联到执行实例和当前节点很多开发者在第一步就卡住了拿到了taskId却不知道去哪里查“当前节点”。其实链路很短通过TaskService的createTaskQuery().taskId(taskId).singleResult()拿到Task对象。Task对象里有getExecutionId()方法这个执行实例ID就是当前任务所在分支的执行实例ID。通过RuntimeService的createExecutionQuery().executionId(executionId).singleResult()拿到Execution对象。Execution里有getActivityId()这个就是当前任务对应的环节节点ID。这里要注意一个细节Task对象里也有getProcessInstanceId()你可以从流程实例ID出发找到根执行实例processInstance但流程实例ID对应的执行实例是根执行实例它不一定能直接反映当前任务所在的具体分支。在一个多实例或者有并行分支的流程里同一个流程实例下可能有多个执行实例每个执行实例停留的节点不同。所以更稳妥的方式是先从Task拿到executionId再定位执行实例再拿当前活动节点ID。这样无论流程怎么分裂都能准确锁定“任务现在挂在哪个节点上”。拿当前节点ID还有一个好处是有些开发者在多实例并行场景下不去查执行实例而是去查流程实例下面的历史节点结果拿到一堆历史记录根本不知道哪个是当前要预测路由的起始点。从Task反查Execution再拿ActivityId是对的方向。2.3 条件表达式求值怎么让引擎提前“算”出路由拿到当前节点之后下一步就是读出口连线和条件表达式。核心代码大概是这样的BpmnModel bpmnModel repositoryService.getBpmnModel(processDefinitionId); Process process bpmnModel.getProcesses().get(0); FlowElement currentFlowElement process.getFlowElement(currentActivityId); if (currentFlowElement instanceof UserTask) { UserTask userTask (UserTask) currentFlowElement; ListSequenceFlow outgoingFlows userTask.getOutgoingFlows(); for (SequenceFlow flow : outgoingFlows) { String conditionExpression flow.getConditionExpression(); // 对conditionExpression求值 } }UserTask用户任务节点和ExclusiveGateway排他网关都继承自FlowNode它们都有getOutgoingFlows()方法。所以不管是直接从一个审批节点往后走还是走到一个排他网关再分流代码写法类似。关键是条件表达式的求值。Flowable里条件表达式是JUELJava Unified Expression Language语法比如${amount 5000}。如果你自己写个表达式解析器去解析它会很痛苦。好在Flowable本身就有现成的解析能力我们可以借助DelegateExecution来模拟。一个更简洁的做法是在读取到conditionExpression之后手动构造一个VariableMap或者直接使用流程实例已有的变量然后让Flowable自带的ExpressionManager去求值。不过这块需要拿到ProcessEngineConfiguration有些版本API有差异。我实际项目里用的一个比较稳的办法是直接调用Flowable内置的ConditionEvaluator或ExpressionManager来求值不自己去写表达式解析逻辑。ExpressionManager expressionManager processEngine.getProcessEngineConfiguration().getExpressionManager(); Expression expression expressionManager.createExpression(conditionExpression); Object value expression.getValue(variableMap);variableMap就是从流程实例和请求参数合并出来的变量集合。这样写最省事也最贴近引擎真实行为不会出现“你解析出来的结果和引擎实际路由不一样”的背离情况。3. 核心代码实现与候选分配策略解析3.1 代码结构设计一个动态预测服务在Spring Boot项目集成Flowable时我建议把“动态预测下一节点”的能力封装成一个独立的Service。对外只需要暴露一个方法传入taskId和可选的业务参数返回下一节点列表以及每个节点上的候选分配信息。先定义返回对象public class NextNodeInfo { private String nodeId; // 下一节点ID private String nodeName; // 下一节点名称 private String nodeType; // 节点类型userTask / exclusiveGateway / endEvent private ListString assigneeList; // 办理人 private ListString candidateUserList; // 候选人 private ListString candidateGroupList;// 候选组 private String multiInstanceType; // 多实例类型NONE / PARALLEL / SEQUENTIAL }NextNodeInfo里的字段别嫌多实际页面展示时都用得上。assigneeList是明确指定办理人的场景candidateUserList和candidateGroupList是候选池场景multiInstanceType用来判断是不是会签/或签节点。再写主服务Service public class FlowableNextNodePredictor { private final TaskService taskService; private final RuntimeService runtimeService; private final RepositoryService repositoryService; public ListNextNodeInfo predictNextNodes(String taskId, MapString, Object bizParams) { Task task taskService.createTaskQuery().taskId(taskId).singleResult(); if (task null) { throw new RuntimeException(任务不存在: taskId); } // 1. 拿到执行实例和当前活动节点 Execution execution runtimeService.createExecutionQuery() .executionId(task.getExecutionId()).singleResult(); String currentActivityId execution.getActivityId(); // 2. 获取流程定义模型找到当前节点对象 BpmnModel bpmnModel repositoryService.getBpmnModel(task.getProcessDefinitionId()); Process process bpmnModel.getMainProcess(); FlowElement currentElement process.getFlowElement(currentActivityId); if (!(currentElement instanceof FlowNode)) { return Collections.emptyList(); } // 3. 从当前节点出发找出可通过的后续节点 ListNextNodeInfo resultList new ArrayList(); traverseOutgoing((FlowNode) currentElement, task.getProcessInstanceId(), bizParams, resultList, new HashSet()); return resultList; } }3.2 遍历出口连线模拟引擎的条件判断traverseOutgoing是核心方法。需要考虑三种情况当前节点往下走经过的是一条普通连线那下一个节点是唯一确定的。当前节点往下走经过的是排他网关网关后面有多条分支需要计算条件表达式来判断走哪一条。当前节点往下走经过的是并行网关网关后面可以同时有多条分支每条分支都是下一步。所以我的实现思路是先看当前节点的所有出口连线把每条连线指向的目标节点拿出来。如果目标节点是ExclusiveGateway则需要在网关节点再次判断条件筛选出能通过的连线往下继续如果目标节点是ParallelGateway则所有出口都算作下一步如果目标节点是UserTask则直接作为下一个待办节点如果目标节点是EndEvent表示流程结束。private void traverseOutgoing(FlowNode flowNode, String processInstanceId, MapString, Object bizParams, ListNextNodeInfo resultList, SetString visited) { for (SequenceFlow flow : flowNode.getOutgoingFlows()) { // 判断连线条件是否满足 if (!evaluateCondition(flow.getConditionExpression(), processInstanceId, bizParams)) { continue; } FlowElement targetElement flow.getTargetFlowElement(); if (targetElement instanceof UserTask) { UserTask userTask (UserTask) targetElement; NextNodeInfo info buildNextNodeInfo(userTask, processInstanceId, bizParams); resultList.add(info); } else if (targetElement instanceof ExclusiveGateway) { // 排他网关只走满足条件的那一条分支 ExclusiveGateway gateway (ExclusiveGateway) targetElement; traverseOutgoing(gateway, processInstanceId, bizParams, resultList, visited); } else if (targetElement instanceof ParallelGateway) { // 并行网关的分支都要走 for (SequenceFlow parallelFlow : ((FlowNode) targetElement).getOutgoingFlows()) { if (!evaluateCondition(parallelFlow.getConditionExpression(), processInstanceId, bizParams)) { continue; } FlowElement parallelTarget parallelFlow.getTargetFlowElement(); if (parallelTarget instanceof UserTask) { resultList.add(buildNextNodeInfo((UserTask) parallelTarget, processInstanceId, bizParams)); } else { // 递归处理嵌套网关 traverseOutgoing((FlowNode) parallelTarget, processInstanceId, bizParams, resultList, visited); } } } else if (targetElement instanceof EndEvent) { // 流程结束 NextNodeInfo endInfo new NextNodeInfo(); endInfo.setNodeId(((EndEvent) targetElement).getId()); endInfo.setNodeName(流程结束); endInfo.setNodeType(endEvent); resultList.add(endInfo); } } }这里有几个递归的坑要注意。第一个是死循环如果流程图里有返回上游的连线比如驳回连线递归里要加一个visited集合去重否则可能栈溢出。第二个是网关嵌套我们项目有段时间流程图设计得很奔放排他网关后面还接着排他网关并行网关后面还套排他网关代码里就一定要递归处理不能只匹配一层。3.3 条件表达式求值把流程变量和请求参数合并evaluateCondition这个方法我的处理方式是把流程实例已有的变量和前端传过来的业务参数合并成一份Map然后用Flowable自身的表达式引擎去求值。这样能保证和你引擎真实执行时用的是同一套变量。private boolean evaluateCondition(String conditionExpression, String processInstanceId, MapString, Object bizParams) { if (StringUtils.isBlank(conditionExpression)) { return true; } // 去掉 ${ 和 }取表达式主体 String expressionText conditionExpression.trim(); if (expressionText.startsWith(${)) { expressionText expressionText.substring(2, expressionText.length() - 1); } // 合并流程变量和业务参数 MapString, Object variables new HashMap(); if (processInstanceId ! null) { variables.putAll(runtimeService.getVariables(processInstanceId)); } if (bizParams ! null) { variables.putAll(bizParams); } // 使用Flowable的表达式管理器求值 ExpressionManager expressionManager processEngine.getProcessEngineConfiguration().getExpressionManager(); Expression expression expressionManager.createExpression(expressionText); Object value expression.getValue(variables); return value instanceof Boolean (Boolean) value; }我在网上看过不少博客有人是自己写表达式解析去判断大于小于的我强烈不建议那么做。因为流程变量可能是多层的、可能有空值、可能涉及实体属性自己解析很容易漏掉边界情况。直接用Flowable自带的ExpressionManager哪怕你写的是${order.amount 5000 order.type URGENT}只要order对象放进了变量里它就能正确求值跟你跑流程时的行为完全一致。一个需要留意的点是ExpressionManager在获取之前要先拿到ProcessEngine。如果你是注入RuntimeService、TaskService这种方式可以用processEngine.getProcessEngineConfiguration()或者更简单一点在Spring Boot里直接注入ProcessEngine。3.4 候选分配策略解析从节点定义到具体办理人拿到下一个UserTask节点之后候选分配策略的解析分两块第一块是节点本身的分配配置。在BPMN模型里UserTask有几个属性assignee固定办理人可以是用户ID也可以是表达式。candidateUsers候选人列表多个用户都可以认领。candidateGroups候选组组内成员都可以认领。flowable:multiInstanceLoopCharacteristics多实例配置里面可以配置.assignee、.candidateUsers等。直接读取这些属性很简单。麻烦的是有些流程设计器会把办理人写成表达式比如${applyUser}意思是“谁提交的谁就是办理人”。如果只拿到赋值文本直接返回给前端前端根本不知道这是啥。所以我的做法是如果assignee或candidateUsers里的值是${...}格式就尝试用变量解析成实际值如果解析失败或者变量为空就把原字符串保留在列表里由调用方自行决定怎么展示。第二块是判断多实例参与人。会签节点在Flowable里的配置是多实例multiInstanceLoopCharacteristics参与人通常配置成集合变量比如assigneeList。碰到这种节点需要读取multiInstanceLoopCharacteristics的inputDataExpression比如${assigneeList}再从流程变量里取出这个集合作为候选人列表返回。这样前端就能展示“这步有3个人要会签”了。private NextNodeInfo buildNextNodeInfo(UserTask userTask, String processInstanceId, MapString, Object bizParams) { NextNodeInfo info new NextNodeInfo(); info.setNodeId(userTask.getId()); info.setNodeName(userTask.getName()); info.setNodeType(userTask); // 处理指派人和候选人 ListString assigneeList new ArrayList(); resolveExpressionValue(userTask.getAssignee(), processInstanceId, bizParams, assigneeList); info.setAssigneeList(assigneeList); ListString candidateUsers new ArrayList(); for (String candidateUser : userTask.getCandidateUsers()) { resolveExpressionValue(candidateUser, processInstanceId, bizParams, candidateUsers); } info.setCandidateUserList(candidateUsers); info.setCandidateGroupList(new ArrayList(userTask.getCandidateGroups())); // 多实例处理 MultiInstanceLoopCharacteristics mi userTask.getLoopCharacteristics(); if (mi ! null) { info.setMultiInstanceType(mi.isSequential() ? SEQUENTIAL : PARALLEL); String inputExpression mi.getInputDataExpression(); if (StringUtils.isNotBlank(inputExpression)) { String variableName extractVariableName(inputExpression); Object collectionVal runtimeService.getVariable(processInstanceId, variableName); if (collectionVal instanceof Collection) { Collection? collection (Collection?) collectionVal; ListString participants new ArrayList(); for (Object item : collection) { participants.add(String.valueOf(item)); } info.setCandidateUserList(participants); } } } return info; }有个细节容易踩坑UserTask类里的getCandidateUsers()返回的可能是字符串列表但每个元素本身可能是表达式字符串比如${candidateUserList}这种。所以resolveExpressionValue这个方法要能把表达式解析成实际值也要处理普通字符串原样返回。还有一个更隐蔽的坑多实例节点的候选人列表和节点本身的候选人列表不一定是冲突的。有些设计器会把多实例inputDataExpression配置成${assigneeList}人的同时又把userTask.candidateUsers也配置了。这时候以谁为准我的约定是如果有多实例配置优先使用多实例的inputDataExpression解析结果作为候选列表没有多实例配置才用candidateUsers和candidateGroups。3.5 完整调用示例Controller层怎么对接对外接口设计其实很简单一个普通的后端接口就可以了PostMapping(/flowable/predict/next) public ApiResponseListNextNodeInfo predictNext(RequestParam String taskId, RequestBody(required false) MapString, Object bizParams) { ListNextNodeInfo nextNodes predictor.predictNextNodes(taskId, bizParams); return ApiResponse.success(nextNodes); }前端拿到返回结果后可以渲染“下一节点”的弹窗提示也可以用于校验当前用户是否具备操作下一节点的权限。我们在实际项目里还在这个基础上做了一层缓存同一流程实例的预测结果加了个短期过期时间避免用户每次打开详情页都重复解析一遍。4. 常见问题与排查技巧实录4.1 流程定义换版导致的任务查询为空这个问题在流程调试阶段特别常见。Flowable的流程定义是有版本号的你部署了新版本之后已发起的老流程任务仍然用老版本的流程定义ID。如果你在接口里传的是“流程定义Key”而不是“流程定义ID”可能会查到新版本的定义导致当前节点根本不在新模型里。解决办法是task.getProcessDefinitionId()返回的是发布时实际使用的流程定义ID预测逻辑里所有模型读取都基于这个ID绝对不要去根据processDefinitionKey再查一遍。一旦用错版本后面所有解析都是空中楼阁。4.2 条件表达式求值失败变量缺失和返回值类型不对很多人问我为什么evaluateCondition总是返回false。最常见的原因是流程实例里根本没有设置那个变量。比如表达式中写了${amount 5000}但你调用predictNextNodes传的bizParams里没有amountFlowable的表达式解析遇到不存在的变量不会抛异常而是返回null。null不是Boolean我的代码里value instanceof Boolean就挡掉了最终结果当作false处理。还有一种情况是表达式里写的是${flag true}但流程变量flag是字符串true这个没问题可如果你把flag设置成了布尔值true那和字符串true比较就会失败。排查这类问题时先把流程变量打出来看一眼再对照表达式里的类型。4.3 排他网关识别不到流程图模型结构层次不对BpmnModel在加载多个子流程时有一个需要注意的地方我上面的实现用的是bpmnModel.getMainProcess()。如果你的流程定义里嵌了子流程subProcess那些节点不在主流程里process.getFlowElement(currentActivityId)就会返回null。这种情况可以先遍历bpmnModel.getProcesses()找所有流程再写一个递归方法去查找目标节点。另外如果你的流程图里用了callActivity调用子流程那预测逻辑会更复杂需要跨流程实例去解析我建议在小项目里先规避这个复杂度把callActivity场景单独特殊处理。4.4 并行网关场景下返回多个节点前端展示容易懵并行网关会同时激活多个分支所以predictNextNodes返回列表里会有多个NextNodeInfo。前端拿到多个节点时要注意展示逻辑可以并列展示“下一节点A、B”也可以分成主流程节点和并行节点两块。我们项目里就是只展示第一个同时出现的主线节点并行分支在后续步骤里再逐步展示不然审批单页面上堆五六个节点名称业务方看了直接崩溃。4.5 候选人配置成表达式但变量没设置结果返回一堆${}前面提过候选人可能是${applyUser}这种表达式。如果流程变量里没有applyUserresolveExpressionValue解析失败列表里就会保留原始字符串${applyUser}。这种数据返回给前端页面直接显示一个大括号模板非常难看。我的处理方式是解析失败的表达式默认塞一个空字符串进去并且对前端说明这个节点“未配置具体办理人”前端可以显示“待指定”。同时在后端日志里打一条warn提醒流程设计者候选变量缺失。这样既不影响主流程展示也方便排查流程配置问题。4.6 实测对比预测结果和引擎实际执行不一致怎么办万一你预测出来的节点和流程实际跑起来到达的节点不一致优先怀疑两个方向第一变量是在任务完成的那一刻才设置的。比如你当前节点是用户任务节点上还配置了taskListener在complete事件里往流程变量里塞了amount的值。这种情况下你在任务完成之前做预测时amount根本不存在预测结果自然和实际不一致。解决办法是把预测时需要的变量尽量在流程启动、或者上一节点办理时就写入流程变量不要拖到当前节点complete事件里才设置。第二条件表达式求值顺序问题。Flowable对多个outgoingFlows的求值顺序不一定会严格按照你流程图里的排列顺序。如果你的条件表达式中存在多个分支同时为true的情况而且用的是排他网关后面的多条连线引擎优先选择哪条不同版本的行为可能不太一样。这种情况建议用defaultFlow属性兜底并且保证条件分支之间互斥不要出现“既可以走A也可以走B”的模糊情况。5. 实测经验与扩展建议这套“任务节点动态预测”机制我在项目里跑了大半年最大的感受是它不是一个锦上添花的工具而是审批类系统中提升体验的关键节点。有了它前端不用等到用户点完“同意”才发现“哦原来这单子下一步是财务审批”而是能在用户点击前就告诉他“你提交后会先到部门经理当前部门经理是李四”。这种体验上的提升业务方是非常买账的。如果后续要在这个基础上扩展我还有两个方向可以继续推进。第一个方向是批量预测。现在是一次查询一个任务如果你要对一个流程实例的所有当前任务做预测可以考虑写一个批量版本按processInstanceId查出所有Task逐个执行预测逻辑汇总返回。这样在“驳回重填”“批量审批”的场景下特别好用用户一次就能看到整个流程后续会怎么走。第二个方向是历史路径回溯。反过来利用历史任务表ACT_HI_TASKINST可以画出这个流程实例在每一步实际经过了哪些节点、每个节点谁处理的、花了多久。把“动态预测”和“历史回溯”配合起来其实就是一个简化版的流程分析与监控系统。对一些没有上专门运维平台的团队来说这两块代码加起来也就半天工作量但能显著提升处理流程问题的效率。最后再分享一个小技巧。在开发阶段给小规模项目调试这种预测逻辑我习惯在单测里直接部署一个只含两个用户任务和一条连线的极简BPMN然后把预测接口跑一遍看返回的NodeName对不对。跑通之后再慢慢往流程图里加网关、加条件表达式这样定位问题会非常快比每次都用完整业务流程图去调试省太多时间。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Stitch + Remotion Walkthrough 视频合成检查清单:composition-checklist 完整解读与实战 2026/10/2 2:03:19

Stitch + Remotion Walkthrough 视频合成检查清单:composition-checklist 完整解读与实战

AI 技能AI 插件 【免费下载链接】stitch-skills A library of Agent Skills designed to work with the Stitch MCP server. Each skill follows the Agent Skills open standard, for compatibility with coding agents such as Antigravity, Gemini CLI, Claude Code, Cursor…

阅读更多 →
电赛智能送药小车硬件全解析:主控、驱动与抗干扰实战经验 2026/10/2 2:03:06

电赛智能送药小车硬件全解析:主控、驱动与抗干扰实战经验

作为一个带队参加过三届电赛的老油条,21年这道智能送药小车的控制类题目,说实话,难度不算顶天,但极其考验团队的硬件基本功和系统稳定性。当年我们实验室两支队伍都选了这道题,最后成绩却差了一截,拉开的差…

阅读更多 →
纯CSS美食网站源码拆解:从布局到动效的完整实战指南 2026/10/2 2:03:05

纯CSS美食网站源码拆解:从布局到动效的完整实战指南

简介:基于CSS的美食网站设计源码是一套面向网页设计初学者的完整前端练习项目,核心价值在于用纯HTML与CSS搭建一个展示美食信息、兼顾视觉与交互体验的静态站点。压缩包共21个文件,体积786KB,包含4个CSS样式表、2个HTML页面、1个P…

阅读更多 →
自研基于Raft的分布式KV存储:架构设计与核心机制解析 2026/10/2 2:02:59

自研基于Raft的分布式KV存储:架构设计与核心机制解析

我在去年年初启动了一个确实有点"自找麻烦"的项目:从零实现一个基于 Raft 协议的高性能分布式 KV 存储系统。当时团队里有人劝我直接用 etcd,也有人建议在 TiKV 上做二次开发,但最后我坚持走自研这条路,到现在这套系统已…

阅读更多 →
AI全栈实战 | 2.2-02 React 思维模型:数据不可变 vs Vue 数据可变,两种哲学把复杂度推给谁 2026/10/2 2:02:59

AI全栈实战 | 2.2-02 React 思维模型:数据不可变 vs Vue 数据可变,两种哲学把复杂度推给谁

上篇回顾:2.2-01 拆解了 Vue3 的 Proxy 响应式、组合式 API、组件通信、双向绑定本质。本篇讲 React——前端另一大框架。对后端工程师来说,React 的心智模型比 Vue 陡峭得多(JSX Hooks 不可变数据),但理解 React 能…

阅读更多 →
3D坐标系转换实战:旋转矩阵、齐次坐标与Python组合变换工具 2026/10/2 2:02:59

3D坐标系转换实战:旋转矩阵、齐次坐标与Python组合变换工具

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉