API网关实战:用Go httputil实现反向代理与自定义网关
1. 从六个微服务到统一入口我为什么用 Go httputil 手写 API 网关前端同学第三次来找我的时候手里拿着六份接口文档。六个微服务六个域名端口跨域配置要写六遍认证逻辑每个服务各写一套改一次 Token 校验规则得改六个仓库。这个场景下API 网关不是「要不要做」的问题而是「怎么快速做出来」的问题。API 网关本质上是一个反向代理加上一堆横切逻辑。反向代理负责把外部请求转发到内部服务横切逻辑负责认证、限流、日志、路由。Go 标准库里的net/http/httputil包提供了ReverseProxy几行代码就能跑起一个反向代理再配合自定义的Director、ModifyResponse、ErrorHandler就能拼出一个生产可用的自定义网关。这篇文章适合谁正在做微服务拆分、需要统一入口的 Go 后端开发者想理解反向代理底层原理、不想直接上 Kong/APISIX 的团队以及需要把上游 endpoint 统一收口到某个 API 通道比如用统一 Key 管理多家模型服务的工程同学。我会从最简单的NewSingleHostReverseProxy开始一步步加上路径重写、Header 透传、超时控制、动态路由最后给出本地 curl 验证步骤以及如何把上游地址改到 TaoToken 的 API 通道完成联调。我试过直接用 Nginx 顶一阵子但 Nginx 改配置要 reload而且没法在里面写 Go 的认证逻辑。后来决定用 Go 自己写灵活性高认证、限流、埋点全用 Go 写团队里会 Go 的人都能维护。下面把完整过程拆开讲。2. TaoToken 前置准备统一 Key 与 API 通道的接入配置在讲网关代码之前先把上游 endpoint 的事情说清楚。很多团队的自建网关后面挂的不只是自家微服务还有外部 API 通道比如模型对话、代码补全这类服务。如果每个服务各自管一套 Key网关的认证和转发逻辑会变得很乱。比较合理的做法是网关统一持有上游通道的 Key内部服务不直接接触外部凭证。TaoToken 在这里扮演的就是「统一 API 通道」的角色。它提供一个兼容 OpenAI 风格的接口地址你可以在网关里把某条路由的上游 target 指向它由网关统一注入 Key。这样内部服务调用时只需要带内部 Token网关负责换成上游 Key 再转发。先拿到接入所需的三样东西Base URL、API Key、Model ID。Base URL 是https://taotoken.net/api注意这个地址不带任何查询参数是纯粹的 API 入口。API Key 在控制台的 API Keys 页面创建创建后只显示一次记得存到环境变量或配置中心不要硬编码进代码。控制台地址在这里https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentgateway_consoleAPI Keys 管理页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentgateway_apikeys接入文档里面有完整的请求格式和错误码说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentgateway_docModel ID 需要根据你实际要调用的模型来填在模型对话页面可以看到当前可用的模型列表https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentgateway_chat把这三样东西准备好之后网关的上游配置就可以写成这样一份 JSON放在config/upstream.json{ upstreams: [ { name: taotoken, base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, model_id: your-model-id, timeout_seconds: 120 } ] }注意api_key_env字段它指向的是环境变量名而不是 Key 本身。网关启动时从环境变量读取这样配置文件可以进版本库Key 不会泄露。启动前先导出环境变量export TAOTOKEN_API_KEYsk-你的实际Key如果你用的是 Claude Code 这类工具做本地联调它的配置里同样需要填 Base URL、Key、Model ID 三件套。Claude Code 的接入说明在文档里有专门章节地址是 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentgateway_claudecode前置准备到这里就够了。核心是三件事Base URL 用https://taotoken.net/apiKey 从环境变量读Model ID 按实际调用填。下面进入网关代码部分。3. 可复制的 ReverseProxy 配置路径重写、Header 透传与超时控制这一节给出可以直接复制运行的完整代码。我把它拆成三个文件main.go负责启动gateway.go负责网关核心逻辑routes.json负责路由配置。先看路由配置这是整个网关的「路由表」。[ { prefix: /api/user, target: http://127.0.0.1:8081, strip_prefix: true, timeout_seconds: 10 }, { prefix: /api/order, target: http://127.0.0.1:8082, strip_prefix: true, timeout_seconds: 10 }, { prefix: /api/ai, target: https://taotoken.net/api, strip_prefix: true, timeout_seconds: 120, inject_auth: true } ]strip_prefix为 true 时网关会把/api/user/profile转发成/profile给上游。inject_auth为 true 时网关会从环境变量读取 Key 并注入Authorization头内部服务不需要自己带外部凭证。timeout_seconds按路由区分AI 类接口给 120 秒普通接口 10 秒。下面是网关核心代码gateway.gopackage main import ( encoding/json log net net/http net/http/httputil net/url os strings sync time ) type RouteConfig struct { Prefix string json:prefix Target string json:target StripPrefix bool json:strip_prefix TimeoutSeconds int json:timeout_seconds InjectAuth bool json:inject_auth } type Gateway struct { mu sync.RWMutex routes map[string]*httputil.ReverseProxy } func NewGateway(configPath string) (*Gateway, error) { gw : Gateway{routes: make(map[string]*httputil.ReverseProxy)} if err : gw.LoadRoutes(configPath); err ! nil { return nil, err } return gw, nil } func (gw *Gateway) LoadRoutes(path string) error { data, err : os.ReadFile(path) if err ! nil { return err } var configs []RouteConfig if err : json.Unmarshal(data, configs); err ! nil { return err } newRoutes : make(map[string]*httputil.ReverseProxy) for _, cfg : range configs { target, err : url.Parse(cfg.Target) if err ! nil { log.Printf(跳过无效路由 %s: %v, cfg.Prefix, err) continue } prefix : cfg.Prefix strip : cfg.StripPrefix inject : cfg.InjectAuth timeout : time.Duration(cfg.TimeoutSeconds) * time.Second proxy : httputil.ReverseProxy{ Director: func(req *http.Request) { req.URL.Scheme target.Scheme req.URL.Host target.Host req.Host target.Host if strip { req.URL.Path strings.TrimPrefix(req.URL.Path, prefix) if req.URL.Path { req.URL.Path / } } // Header 透传保留原始 X-Forwarded-For追加当前客户端 IP clientIP, _, _ : net.SplitHostPort(req.RemoteAddr) if prior : req.Header.Get(X-Forwarded-For); prior ! { req.Header.Set(X-Forwarded-For, prior, clientIP) } else { req.Header.Set(X-Forwarded-For, clientIP) } req.Header.Set(X-Gateway, go-httputil-gateway) // 统一注入上游 Key if inject { key : os.Getenv(TAOTOKEN_API_KEY) if key ! { req.Header.Set(Authorization, Bearer key) } } }, ModifyResponse: func(resp *http.Response) error { resp.Header.Set(X-Gateway-Version, 1.0) return nil }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { log.Printf(上游错误 path%s err%v, r.URL.Path, err) w.WriteHeader(http.StatusBadGateway) w.Write([]byte({error:upstream unavailable})) }, Transport: http.Transport{ DialContext: (net.Dialer{ Timeout: 5 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, ResponseHeaderTimeout: timeout, IdleConnTimeout: 90 * time.Second, MaxIdleConns: 100, MaxIdleConnsPerHost: 20, }, } newRoutes[prefix] proxy } gw.mu.Lock() gw.routes newRoutes gw.mu.Unlock() log.Printf(加载了 %d 条路由, len(newRoutes)) return nil } func (gw *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { gw.mu.RLock() var bestMatch string for prefix : range gw.routes { if strings.HasPrefix(r.URL.Path, prefix) len(prefix) len(bestMatch) { bestMatch prefix } } proxy, ok : gw.routes[bestMatch] gw.mu.RUnlock() if !ok { w.WriteHeader(http.StatusNotFound) w.Write([]byte({error:route not found})) return } proxy.ServeHTTP(w, r) }main.go只负责启动package main import ( log net/http ) func main() { gw, err : NewGateway(routes.json) if err ! nil { log.Fatal(err) } log.Println(API 网关启动在 :8080) if err : http.ListenAndServe(:8080, gw); err ! nil { log.Fatal(err) } }几个关键点解释一下。Director里做路径重写和 Header 透传X-Forwarded-For采用追加而不是覆盖这样上游能看到完整的调用链。Transport里的ResponseHeaderTimeout是等待上游返回响应头的最大时间这个值设小了会导致大文件上传或长耗时接口被网关提前断开后面第五节会专门讲这个坑。inject_auth为 true 的路由网关统一从环境变量读 Key 注入内部服务不需要接触外部凭证。如果你需要更细粒度的超时控制比如按 Content-Length 区分大文件和小文件可以自定义一个RoundTrippertype SmartTransport struct { base *http.Transport } func (t *SmartTransport) RoundTrip(req *http.Request) (*http.Response, error) { transport : t.base.Clone() if req.ContentLength 10*1024*1024 { transport.ResponseHeaderTimeout 5 * time.Minute } else { transport.ResponseHeaderTimeout 10 * time.Second } return transport.RoundTrip(req) }把这个SmartTransport赋给ReverseProxy.Transport即可。这样大文件上传走长超时普通请求走短超时快速失败。4. 本地 curl 验证从启动到成功拿到上游响应代码写完了接下来验证。先准备一个最简单的上游服务用 Go 起一个返回 JSON 的 HTTP 服务监听 8081package main import ( encoding/json net/http ) func main() { http.HandleFunc(/profile, func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{ service: user-service, path: r.URL.Path, xff: r.Header.Get(X-Forwarded-For), gateway: r.Header.Get(X-Gateway), }) }) http.ListenAndServe(:8081, nil) }启动上游服务再启动网关go run upstream.go go run main.go网关日志会打印加载了 3 条路由和API 网关启动在 :8080。现在用 curl 验证路径重写和 Header 透传curl -i http://127.0.0.1:8080/api/user/profile预期返回{ service: user-service, path: /profile, xff: 127.0.0.1, gateway: go-httputil-gateway }注意path是/profile而不是/api/user/profile说明strip_prefix生效了。xff和gateway两个字段说明 Header 透传正常。响应头里应该能看到X-Gateway-Version: 1.0。再验证 AI 路由的 Key 注入。先确认环境变量已导出echo $TAOTOKEN_API_KEY然后请求 AI 路由curl -i -X POST http://127.0.0.1:8080/api/ai/v1/chat/completions \ -H Content-Type: application/json \ -d { model: your-model-id, messages: [{role: user, content: 你好}] }这条请求会被网关转发到https://taotoken.net/api/v1/chat/completions网关自动注入Authorization: Bearer $TAOTOKEN_API_KEY。如果返回 200 并且 body 里有正常的模型回复说明整条链路通了。如果返回 401先检查环境变量是否导出、Key 是否有效。验证路由不存在的情况curl -i http://127.0.0.1:8080/api/unknown/path预期返回 404 和{error:route not found}。验证上游不可用的情况把上游服务停掉再请求curl -i http://127.0.0.1:8080/api/user/profile预期返回 502 和{error:upstream unavailable}网关日志会打印具体的连接错误。这一步验证的是ErrorHandler是否生效。到这里一个具备路由转发、路径重写、Header 透传、Key 注入、超时控制、错误处理的反向代理型网关就跑起来了。整个过程不需要任何第三方依赖纯标准库。5. 常见报错排查401、local proxy failed、reading choices、OAuth网关跑起来之后实际联调中会遇到几类典型报错。这一节按报错信息对照排查每条都给出定位思路。401 Unauthorized。如果请求 AI 路由返回 401先看网关日志里转发出去的请求有没有带Authorization头。常见原因是环境变量没导出或者inject_auth字段拼写错了。检查routes.json里对应路由的inject_auth是否为 true再确认TAOTOKEN_API_KEY在当前 shell 里能echo出来。如果 Key 是从配置中心读的确认读取逻辑在Director执行之前已经完成。local proxy failed。这个报错通常出现在用某些客户端工具通过本地代理访问上游时。排查顺序先确认网关进程本身能直连上游用curl -v https://taotoken.net/api测试再确认网关的Transport没有配置错误的Proxy字段。如果你的环境里设置了HTTP_PROXY环境变量Go 的http.Transport默认会读取它可能导致请求走了一个不可用的本地代理。可以在Transport里显式设置Proxy: nil来绕过环境变量。reading choices 相关报错。这类报错一般出现在流式响应场景网关在读取上游 SSE 流时提前关闭了连接。原因是ResponseHeaderTimeout或IdleConnTimeout设置过短流式响应在两次数据块之间超过了空闲超时。解决办法是把流式路由的timeout_seconds调大并且在Transport里把IdleConnTimeout设为 0不限制或一个较大的值。另外确认ModifyResponse里没有对resp.Body做缓冲读取流式响应必须保持 Body 可流式读取。OAuth 相关报错。如果上游通道需要 OAuth 流程网关不能简单地注入静态 Key。这种情况下建议在网关里加一个 Token 刷新中间件定期用 refresh token 换新的 access token缓存到内存里Director执行时从缓存读取。注意刷新逻辑要加锁避免并发刷新。如果用的是 Claude Code 这类工具做本地联调它的 OAuth 流程在文档里有说明地址是 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentgateway_oauth连接被重置或超时。先看网关日志里的上游错误行里面会打印具体的 error。如果是context deadline exceeded说明ResponseHeaderTimeout到了调大对应路由的timeout_seconds。如果是connection refused说明上游地址或端口不对用curl直接测上游地址确认。如果是EOF通常是上游主动断开了连接检查上游服务的日志。路由匹配不到。网关用的是最长前缀匹配如果/api/user和/api/user/profile两条路由同时存在请求/api/user/profile/xxx会匹配到更长的那条。如果发现请求走了错误的路由检查routes.json里有没有前缀重叠的配置。另外注意strip_prefix为 true 时转发给上游的路径是去掉前缀之后的上游服务的路由注册要对应调整。排查的核心思路是先看网关日志再看上游日志最后用 curl 直接测上游。网关日志里我特意打印了path和err定位问题时先看这两项。6. 把上游 endpoint 收口到统一通道联调与后续扩展网关跑通之后最后一步是把上游 endpoint 统一收口。前面routes.json里已经有一条/api/ai路由指向https://taotoken.net/api并且开启了inject_auth。这样内部服务调用 AI 能力时只需要请求网关的/api/ai/v1/chat/completions不需要自己管 Key也不需要知道上游的真实地址。这种收口方式的好处有三个。第一Key 只在网关一处配置轮换 Key 时只改一个环境变量不用改所有服务。第二网关可以统一做限流和审计哪个服务调了多少次一目了然。第三上游地址变更时只改routes.json内部服务无感知。如果你需要长期跑编码类 Agent 或者高频调用可以考虑用 Coding Plan 来管理额度地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentgateway_codingplan联调完成后网关还可以继续扩展几个方向。一是加 JWT 认证中间件在ServeHTTP里路由匹配之前先校验内部 Token。二是加令牌桶限流按路由或按调用方维度限制 QPS。三是加请求日志和链路追踪把X-Request-ID透传到上游方便全链路排查。四是把routes.json换成从配置中心动态拉取配合文件监听实现热加载加新服务不用重启网关。代码层面的扩展点很清晰认证和限流加在ServeHTTP里proxy.ServeHTTP之前日志加在Director和ModifyResponse里动态路由加一个fsnotify监听routes.json变更后重新调用LoadRoutes。这些都不需要改动ReverseProxy的核心逻辑属于叠加式扩展。最后提醒一个实际部署时的细节网关的Transport建议全局复用一个实例不要每条路由都 new 一个否则连接池无法共享高并发下会频繁建连。把Transport提到Gateway结构体里作为字段所有路由共用MaxIdleConnsPerHost根据实际并发量调整。这个细节在压测时差异很明显连接复用做好之后网关本身的 CPU 占用会低很多。

相关新闻

SpringBoot+Redis实战:网球馆预订管理系统从设计到上线

SpringBoot+Redis实战:网球馆预订管理系统从设计到上线

1. 项目缘起:从一张排班表和电话簿开始的网球馆我最早接触到这个项目,是在朋友的网球馆里聊出来的需求。那家馆规模不大,但也不算小——6片室内场、4片室外场,工作日晚上和周末基本满场。前台有三个人轮班,墙上挂着一块…

2026/10/17 21:10:19 阅读更多 →
图像旋转与畸变矫正:双线性与双三次插值原理及工程优化

图像旋转与畸变矫正:双线性与双三次插值原理及工程优化

1. 从像素坐标到数学映射:图像扭曲旋转的底层逻辑很多人第一次接触图像旋转,脑子里想的是"把图片转个角度",代码里调一个cv::rotate或者cv::warpAffine就完事了。但真正自己动手写过重采样的人知道,旋转这件事的本质根本…

2026/10/17 21:10:19 阅读更多 →
OpenCV视频读取延迟根源与低延迟方案实战

OpenCV视频读取延迟根源与低延迟方案实战

1. 视频读取延迟的典型表现与问题定位用OpenCV做视频处理的人,几乎都遇到过这样一种情况:程序跑起来之后,画面显示总是比真实时间慢那么一点点,而且这个延迟不是偶发的,是稳定存在的。比如你对着摄像头挥手&#xff0c…

2026/10/17 21:10:19 阅读更多 →

最新新闻

从零打造OpenClaw:18步智能体开发指南(TaoToken统一Key接入版)

从零打造OpenClaw:18步智能体开发指南(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/17 21:56:57 阅读更多 →
opencode 安装 minimaxskill 报 401?把 endpoint 改到 TaoToken 的排查记录

opencode 安装 minimaxskill 报 401?把 endpoint 改到 TaoToken 的排查记录

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

2026/10/17 21:56:57 阅读更多 →
滑板底盘拆解报告:Rivian R1T技术解析与工程避坑指南

滑板底盘拆解报告:Rivian R1T技术解析与工程避坑指南

简介:这份滑板底盘行业专题研究报告,面向汽车产业研究者、投资分析人员及关注电动化技术路线的从业者,围绕滑板底盘这一一体化底盘架构展开系统梳理。报告从概念与发展背景切入,剖析上下车身解耦、线控系统、分布式电机驱动与电池…

2026/10/17 21:56:57 阅读更多 →
VOC XML标注可视化验证:从画框到数据质量防火墙

VOC XML标注可视化验证:从画框到数据质量防火墙

1. 这个需求到底在解决什么实际问题?“目标检测——将xml文件中标签(矩形框)在其原图片上显示并另存”,光看标题,很多人第一反应是:“不就是画个框吗?OpenCV几行代码的事。”但我在某高校计算机…

2026/10/17 21:56:57 阅读更多 →
豆瓣电影数据集清洗与推荐系统实战:从CSV到SQLite全流程

豆瓣电影数据集清洗与推荐系统实战:从CSV到SQLite全流程

简介:这份豆瓣电影数据集以SQLite数据库与CSV表格形式沉淀约10万条电影相关记录,面向数据分析初学者、推荐系统开发者及影视市场研究人员,可用于用户观影偏好挖掘、电影属性分析与影评情感洞察。包内共4个文件,两个csv分别承载用户…

2026/10/17 21:56:57 阅读更多 →
MapReduce工作机制详解:从作业提交到Shuffle调优的完整指南

MapReduce工作机制详解:从作业提交到Shuffle调优的完整指南

1. 从一次MapReduce任务卡死说起:为什么必须搞懂MR的工作机制刚接触Hadoop那会儿,我遇到过一个至今印象深刻的场景:一个看起来逻辑很简单的MapReduce任务,输入数据不过几百MB,却跑了将近四十分钟还没结束。打开Web UI一…

2026/10/17 21:55:56 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →