基于DAG的节点调度系统:从JSON到拓扑排序的完整实践
任务编排这事儿后端同学基本都会撞上一次。一堆计算节点、彼此带依赖关系你总得让它们按照正确顺序跑起来——先加载数据、再清洗、再做聚合顺序错了结果就是错的。我最近手写了一个基于有向无环图DAG的节点调度系统输入是JSON描述文件内部做依赖分析最后靠拓扑排序把执行顺序算出来保证不出现节点互相等待的死锁。今天把这套实现从设计到落地完整拆给大家包括JSON格式怎么定义、依赖关系怎么建模、Kahn算法怎么落地以及我实际踩过的几个坑。1. 为什么需要“节点调度”这套东西1.1 依赖关系是什么我跟很多同事聊过大家第一反应都是“这不就是排个序吗”实际动手才会发现麻烦的不是排序而是怎么准确地把业务里的先后关系变成可计算的结构。举个例子。你有三条任务A任务负责从数据库抽取原始数据B任务负责清洗空值C任务负责聚合统计。那C依赖BB依赖A这个谁都能看出来。但如果任务一多起来几十个、上百个节点相互纠缠靠肉眼排列是不现实的。更麻烦的是依赖关系一旦交叉成环——A依赖B、B依赖C、C又依赖A——没有任何一个节点可以先行执行系统就陷入了“循环等待”这就是调度里最典型的死锁场景。所以这个系统要解决的第一个问题就是把节点之间的依赖关系表达清楚并且让计算机自动判断这个依赖图到底能不能求出合理的执行顺序。1.2 为什么选择DAG来做依赖建模图论里有向无环图这个结构天然适配任务依赖场景。有向指的是每条边都有方向代表“谁是谁的前置条件”。A指向B就说明B必须在A完成后才能开始。无环指的是图中不存在一个节点沿着边走一圈回到自身的情况一旦存在环就等于出现了逻辑上的“先有鸡还是先有蛋”。生活里你照着菜谱做菜本身就是一条DAG洗菜要等买菜切菜要等洗菜炒菜要等切菜和备好调料。正常菜谱不会出现“炒菜之前要等出锅”否则后厨就卡死了。任务调度也是一样的道理用DAG建模的语义足够直观而且图论里对DAG的研究非常成熟直接有现成的算法可用来求拓扑序。1.3 系统的输入与输出整个系统的运作主线非常清晰加载从JSON文件读取所有计算节点的描述信息。建图把节点和依赖关系转化成程序内部的图数据结构。分析校验节点引用是否完整、依赖关系是否构成环。排序通过拓扑排序算法产出一个满足所有依赖约束的执行顺序。输出下游调度引擎只要照着这个顺序逐个执行就不会出现依赖未满足的情况。这套思路不挑领域。你做数据处理、微服务编排、定时任务管理、构建流程只要业务里存在“前置依赖”它就能适用。1.4 这类系统在真实世界长什么样很多算法和数据流水线平台核心调度模块就是这个思路做的。构建系统里一个个源码包之间也有依赖编译器得等依赖库先编译完成才能编译当前模块数据处理里抽取、清洗、转换、加载每个阶段内部还可以继续拆成细粒度任务靠DAG来编排微服务启动时服务之间也有依赖基础设施层必须比业务层先就绪。这套实现本质上是给这些场景提供一个最小可运行的核心逻辑保持通用你完全可以在此基础上扩展自己的业务字段。2. JSON数据格式怎么设计才不坑2.1 节点与依赖的最小描述集输入的数据结构我设计得尽量简单一个顶层对象里面是nodes数组每个节点包含id、type、params、dependencies字段。{ nodes: [ { id: load_data, type: reader, params: { source: mysql://xxx }, dependencies: [] }, { id: clean_data, type: transform, params: { drop_null: true }, dependencies: [load_data] }, { id: aggregate, type: writer, params: { target: hdfs://yyy }, dependencies: [clean_data, load_data] } ] }字段含义如下表所示字段名说明是否必填id节点唯一标识全文件内不能重复是type节点类型可由业务方自行定义是params执行参数传给节点运行时的配置信息否dependencies该节点的上游依赖节点id列表否注意dependencies是“当前节点依赖哪些上游”不是“谁依赖当前节点”。用这个单方向的表述可以避免数据冗余。很多第一次实现的人会忍不住把上下游关系双侧都维护一遍结果一旦增删节点两边的数据很容易对不上后患无穷。2.2 加载与基础校验加载这块没什么玄机Python里json库直接读取。但校验一定要前置我见过太多系统在运行到一半时才发现JSON里引用了不存在的节点。import json def load_file(path): with open(path, r, encodingutf-8) as f: data json.load(f) return data[nodes]接下来校验两件事id是否重复dependencies里引用的节点是否真实存在。def validate_nodes(nodes): node_ids set() for node in nodes: node_id node[id] if node_id in node_ids: raise ValueError(duplicated node id: node_id) node_ids.add(node_id) for node in nodes: for dep in node.get(dependencies, []): if dep not in node_ids: raise ValueError( fnode {node[id]} references missing node: {dep} )重复id会直接导致后面的图结构混乱因为容器存不下两个一样的键引用缺失的节点则说明JSON文件内部不一致必须尽早暴露。2.3 为什么用JSON而不是硬编码有人会问直接用Python写一个执行列表不是更省事吗为什么绕一圈去解析JSON关键差异在“配置跟代码分离”。JSON描述的是任务拓扑可以被平台的其他模块读取比如做个可视化页面把节点和边渲染出来也可以让业务人员通过前端界面拖拽生成。哪怕后续要换语言实现调度引擎JSON依然可以无缝衔接。硬编码的依赖只在当前代码里成立没法做持久化、没法做版本管理。JSON唯一的短板是没法写注释字段语义需要文档约定。为此我习惯在加载后立刻做schema级校验把非法配置挡在门外。3. 从JSON到图依赖分析的建模过程3.1 用邻接表和入度表表达依赖建图时我最常用的结构是两种邻接表dependents和入度表in_degree。邻接表记录“当前节点出发可以走到哪些下游节点”。比如load_data往下走会到达clean_data和aggregate所以dependents[load_data]里就有这两个节点。入度表记录“当前节点被多少上游节点依赖”它描述的是一个节点真正执行前需要等待的依赖数量。clean_data的入度是1因为只有load_data一个上游aggregate的入度是2因为load_data和clean_data都是它的上游。from collections import defaultdict def build_graph(nodes): node_map {} for node in nodes: node_map[node[id]] node dependents defaultdict(list) in_degree defaultdict(int) for node in nodes: node_id node[id] deps node.get(dependencies, []) # 入度就是dependencies数组的长度 in_degree[node_id] len(deps) # 每条依赖边dep - node_id for dep in deps: dependents[dep].append(node_id) return node_map, dependents, in_degree这里最容易犯迷糊的点是边的方向。我明确约定dependencies里存的是“上游节点id”所以建图的时候每条依赖关系是从dep指向node_id的。把这个方向反了后面拓扑排序出来的顺序就会完全反过来而且不好排查。3.2 为什么没用邻接矩阵节点数量上来以后邻接矩阵就是灾难。假设有一千个节点邻接矩阵就要存一百万条边的状态而且大部分位置都是空的。邻接表只存真实存在的依赖边空间复杂度跟边的数量成正比绝大多数场景下都划算。加上我们还要修改入度值字典结构访问也是常数级整体效率和简洁性都能兼顾。3.3 环检测先行DFS三色标记法虽然拓扑排序最后也能发现环但到那时候图已经遍历到一半错误定位难度高。我单独写了一个静态环检测采用了最常见的DFS三色标记法。思路是给每个节点涂上三种颜色白色表示未访问灰色表示正在栈中黑色表示已经访问完成。如果DFS过程中又遇到一个灰色节点说明沿着当前路径绕了一圈环就存在。def detect_cycle(nodes): WHITE, GRAY, BLACK 0, 1, 2 graph {} for node in nodes: graph[node[id]] [] for dep in node.get(dependencies, []): # 指向当前节点的上游不必保留因为只遍历下游 pass for node in nodes: node_id node[id] for dep in node.get(dependencies, []): graph[dep].append(node_id) color {node[id]: WHITE for node in nodes} def dfs(node_id): color[node_id] GRAY for next_id in graph[node_id]: if color[next_id] GRAY: return False if color[next_id] WHITE: if not dfs(next_id): return False color[node_id] BLACK return True for node in nodes: node_id node[id] if color[node_id] WHITE: if not dfs(node_id): raise ValueError(cycle detected, please check dependencies) return True这个检测的价值在于它可以在进入拓扑排序前把配置出错的DAG优先拦截下来保证后面拿到的图一定是合法的。4. 拓扑排序输出无死锁执行顺序的关键算法4.1 Kahn算法的完整实现我采用的是最常见的Kahn算法思路非常直观反复从图中找出入度为0的节点把它放入结果序列然后把它指向的所有下游节点入度减1相当于删掉了这条边。减到0的节点继续入队直到所有节点都被处理完。from collections import deque def topo_sort(nodes): node_map, dependents, in_degree build_graph(nodes) queue deque( node_id for node_id, degree in in_degree.items() if degree 0 ) order [] while queue: node_id queue.popleft() order.append(node_id) for next_id in dependents[node_id]: in_degree[next_id] - 1 if in_degree[next_id] 0: queue.append(next_id) if len(order) ! len(node_map): raise RuntimeError(cycle detected, no topo order exists) return order这段代码不长但它是整套系统的核心。队列里放着当前“已经满足全部前置条件”的节点只有上游全部跑完的节点才有资格进入队列。弹出一个节点本质上等于这个节点已经可以执行了执行完后释放它下游节点的等待名额。4.2 为什么按这个顺序执行必然无死锁死锁在执行层面的本质是“每个节点都在等一个永远不会先完成的节点”。放在DAG里就是存在一条循环依赖链。拓扑排序能成功的前提是图里没有环。Kahn算法最后会判断结果长度是否等于节点总数少了节点就意味着有些节点的入度永远无法清零环一定存在。对于一个合法的DAG来说拓扑序有一条性质如果存在边u指向v那么在拓扑序列中u一定排在v的前面。这就保证了执行到任意节点v时它的所有上游节点u都已经执行完毕v一定可以安全开工。整个序列不会出现“我等你、你等我”的互锁状态。对执行引擎来说拿到这个order之后按顺序逐个执行不会出现依赖缺失。这是对“无死锁”的一个非常务实的保障。4.3 处理并列节点拓扑序不唯一的问题真实场景里入度为0的节点往往不止一个。比如上图中load_data和init_config互不依赖它们谁先执行都合理。Kahn算法在队列里弹出的顺序取决于入队顺序和使用的数据结构。我实际采用的是稳定排序策略在入度为0的节点入队前先按节点id排序这样做的好处是执行顺序是确定的。同一份JSON无论在哪台机器上跑出来的顺序都一致排查问题时你只需要看id即可复现不会出现“我这边顺序跟你那边不一样”的鬼故事。如果你希望业务上有优先级可以再扩展一个priority字段参与排序。但先想清楚一事实拓扑排序解决的问题是“合法性”不是“最优性”。5. 调度执行与工程落地5.1 从排序结果到执行引擎拓扑排序结果只是一种“候选顺序”真正的执行引擎仍然需要管理每个节点的状态变化。我的做法是给每个节点维护一个状态机pending等待→ ready就绪→ running运行中→ success成功或 failed失败然后照着拓扑序启动节点。def run(nodes, order, executor): status {node[id]: pending for node in nodes} for node_id in order: node next(n for n in nodes if n[id] node_id) print(f[start] {node_id} begins) try: executor(node) status[node_id] success except Exception as exc: status[node_id] failed print(f[error] {node_id} failed: {exc}) return False return True这个版本是简化串行执行。所有节点按拓扑序一个接一个跑保证下游启动时上游已成功。如果你有并行需求可以改成ready队列触发式模型每跑完一个节点把它的下游入度减1减到0的节点立刻丢进一个线程池这样吞吐量能上去但核心还是基于同一套依赖分析的结果。5.2 失败、重试与超时看起来像死锁的隐藏杀手拓扑序解决的是“依赖死锁”但实际跑批时还会遇到另一种假死锁节点执行超时卡在running状态导致下游永远等不到ready信号。从外部看起来跟依赖死锁一模一样。但其实你的DAG是合法的问题出在单个节点没有释放资源。我的处理办法是给每个节点的executor包一层超时控制。超过阈值直接标记失败并且视策略决定是否重试。重试也要设计好最多重试N次重试之间加退避间隔。如果重试后仍然失败可以选择终止整个流程或者跳过该节点进入失败表具体取决于业务容忍度。这部分的取舍建议在调度系统的最外层做配置不要在核心代码里写死。5.3 把DAG画出来调试光靠打印拓扑序几十个节点以后就看不明白了。我写了一个很小的函数把图导出成Graphviz的DOT格式然后用现成工具转成图片排查依赖关系特别管用。digraph DAG { load_data - clean_data; load_data - aggregate; clean_data - aggregate; }对应的生成代码也很简单def to_dot(nodes): lines [digraph DAG {] for node in nodes: for dep in node.get(dependencies, []): lines.append(f {dep} - {node[id]};) lines.append(}) return \n.join(lines)有了可视化你可以直观看到哪些节点分支多、哪些节点是瓶颈、哪个环节形成了环。后期优化并行度时这张图能提供非常好的决策参考。6. 踩坑实录我遇到过的诡异问题6.1 入度表方向搞反整个执行顺序反了有一次我把dependencies当成了“下游列表”结果建出来的边方向跟实际依赖完全相反。拓扑排序确实成功了但排序结果把下游放到了前面执行到聚合节点时上游清洗节点还没跑数据直接读了个寂寞。后来排查了好久才发现根因就是对“dependencies”这个字段语义的理解出了偏差。给个自查技巧写一个最小测试用例两个节点A没有依赖B依赖A。如果拓扑序是A在前、B在后说明方向正确调换过来直接检查建图代码。6.2 环的定位只有“有环”提示等于没排查初次实现时Kahn算法报了一个“cycle detected”就直接结束了。几百个节点的大图光知道有环根本没法查。后来我在DFS环检测里增加了路径收集检测到灰色节点时把当前栈中的完整路径返回并打印出来。def find_cycle_path(nodes): WHITE, GRAY, BLACK 0, 1, 2 graph {} for node in nodes: graph.setdefault(node[id], []) for dep in node.get(dependencies, []): graph.setdefault(dep, []).append(node[id]) color {node[id]: WHITE for node in nodes} stack [] def dfs(node_id): color[node_id] GRAY stack.append(node_id) for next_id in graph[node_id]: if color[next_id] GRAY: cycle stack[stack.index(next_id):] [next_id] return cycle if color[next_id] WHITE: path dfs(next_id) if path: return path stack.pop() color[node_id] BLACK return None for node in nodes: if color[node[id]] WHITE: path dfs(node[id]) if path: return path return None打印出来的环路径能直接指出是哪几个id在互相等待调整JSON配置就有的放矢了。6.3 null、空数组与重复边JSON设计时很多节点没有依赖。如果前端生成的JSON里dependencies为null而不是[]直接执行len(deps)就会报TypeError。我的加载函数里统一做了兜底deps node.get(dependencies) or []还有一个隐蔽问题同一依赖重复出现。比如dependencies: [a, a]入度被算成2。后面a跑完入度只减到1永远不会变成0下游节点就永远等不到启动。表面看像“卡死”实际是数据格式不合法。这种问题要在validate阶段就过滤重复项或者建图时用set去重。6.4 大数据量下的性能表现很多人问几千个节点跑起来会不会很慢。Kahn算法的时间复杂度是O(VE)V是节点数E是依赖边数只需要常数级队列操作所以非常快。我实测过上万节点的图从加载到拓扑排序输出耗时在毫秒级性能瓶颈几乎不在排序阶段而在下游执行逻辑本身。这也是这套思路适合做中央调度核心的原因。7. 最后的实践建议写到这核心代码和思路都拆完了。我自己反复做过类似的项目后最大的感受是算法不复杂真正的复杂度在于所有外围约束的聚合。JSON的schema、字段方向定义、校验策略、失败重试规则这些事每一样都可能让一个看似精妙的调度器崩溃。所以有一个习惯我很推荐保留每次改动任务配置之后跑一遍离线校验脚本把JSON加载、建图、环检测、拓扑排序全部执行一次任何异常第一时间暴露。我就是靠这个习惯避免了多次把带环的配置直接推上线导致任务全部死锁的惨剧。你如果也在做类似的调度系统建议先把这套最小闭环跑通再逐步加并发、加重试、加可视化后面的路会顺畅很多。

相关新闻

全国乡镇级行政区划shp数据实战:从坐标系选型到PostGIS空间查询

全国乡镇级行政区划shp数据实战:从坐标系选型到PostGIS空间查询

简介:全国乡镇级行政区划shp数据面向GIS分析、城乡规划、人文地理研究及地图制作用户,提供覆盖我国乡镇与街道级别的行政区域边界矢量数据,可用于人口经济统计、区域规划、服务设施定位与应急响应等场景。资源包共7个文件,约94.37…

2026/10/12 1:51:01 阅读更多 →
MyBatis动态代理与Mapper接口原理深度解析

MyBatis动态代理与Mapper接口原理深度解析

“你的Mapper接口明明没有实现类,为什么Spring容器里能注入进去,调方法还能正常返回数据?” —— 这是我带过的很多新人都会问的问题。其实答案不复杂:你看到的Mapper接口,在运行时被动态代理成了一个代理对象。真正干…

2026/10/12 1:51:01 阅读更多 →
基于YOLOv8的社区电动车进电梯预警系统:从模型训练到RK3588边缘部署全链路

基于YOLOv8的社区电动车进电梯预警系统:从模型训练到RK3588边缘部署全链路

简介:这份资源面向计算机视觉与人工智能方向的在校学生、教师及企业开发者,提供一套基于YOLOv8的社区电动车进电梯预警系统完整实现,可用于毕业设计、课程设计、大作业或项目初期立项演示。压缩包共97个文件,约24.21MB&#xff0c…

2026/10/12 1:50:00 阅读更多 →

最新新闻

高校学籍管理系统落地实战:从需求文档到数据库设计与状态流转

高校学籍管理系统落地实战:从需求文档到数据库设计与状态流转

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

2026/10/12 2:40:30 阅读更多 →
数据库课程设计航空订票系统:从ER图到MySQL事务与并发避坑指南

数据库课程设计航空订票系统:从ER图到MySQL事务与并发避坑指南

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

2026/10/12 2:40:30 阅读更多 →
串口转网络实战:TCP/UDP模式选型、心跳重连与粘包处理

串口转网络实战:TCP/UDP模式选型、心跳重连与粘包处理

简介:这份资源面向工业控制、嵌入式开发与物联网方向的工程师及学习者,聚焦串口通信与网络通信之间的双向转换问题。内容围绕RS-232、RS-485等串口标准与TCP/IP协议栈的对接展开,涵盖串口数据帧与网络数据包的互转原理、TCP与UDP在可靠性与实…

2026/10/12 2:40:30 阅读更多 →
Docker镜像导出为tar文件并跨服务器加载的实践指南

Docker镜像导出为tar文件并跨服务器加载的实践指南

直接开始写吧。见过太多人在这上面翻车了——不是导出的时候选错命令,就是另一台机器上加载完发现容器跑不起来。我搞运维这么多年,被这玩意儿坑过好多次,也帮不少人收拾过烂摊子。虽然标题里写的“Docker导出镜像为.tar文件并在另一台服务器…

2026/10/12 2:40:30 阅读更多 →
AVCaptureSession视频流捕获实战:设备枚举、帧回调与后台持续运行

AVCaptureSession视频流捕获实战:设备枚举、帧回调与后台持续运行

简介:本资源是一个基于FFmpeg API实现音视频采集与同步处理的完整C工程,面向多媒体开发初学者及iOS/macOS平台音视频应用开发者,解决摄像头图像与麦克风音频实时采集、OpenGL预览、H.264/AAC编码、MP4封装及视音频时间戳同步等核心问题。压缩…

2026/10/12 2:40:30 阅读更多 →
UIUC CS241中文讲义:系统编程从malloc到并发服务器实战指南

UIUC CS241中文讲义:系统编程从malloc到并发服务器实战指南

简介:《UIUC CS241系统编程中文讲义》是一份由ApacheCN社区翻译的开源学习资料,面向希望深入理解系统编程、操作系统底层机制和C语言与Linux交互的开发者。项目源自UIUC经典课程CS241,内容涵盖进程与线程、虚拟内存、并发同步、文件系统、网络…

2026/10/12 2:39:29 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/11 14:36:54 阅读更多 →