云原生CI/CDDevOps后端【免费下载链接】pipelineA cloud-native Pipeline resource.项目地址https://gitcode.com/gh_mirrors/pipelin/pipeline点击查看免费下载本文以vendor/github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry/autorest.mdAzure SDK for Go 中azcontainerregistry模块的 AutoRest 配置文件为骨架完整解读其 30 余条代码生成指令directive的设计意图与落地效果并对照仓库内实际生成的 Go 源码说明一份 456 行的 YAML 配置如何雕琢出一个 API 风格统一、命名规范、便于二次封装的容器注册表客户端。读完本文你将掌握 AutoRest 配置文件的整体结构、三类 directive 的写法以及裁剪 API 面、重命名操作、改造二进制参数、隐藏底层方法等常见定制模式可直接迁移到自己的 SDK 生成工程中。一、autorest.md 是什么代码生成器AutoRest的需求规格书AutoRest 是 Azure SDK 体系使用的 OpenAPISwagger代码生成工具链。它读取服务端发布的 OpenAPI 规范input-file按生成器的语言插件输出对应语言的客户端源码。而autorest.md正是这个过程的需求规格书它不直接参与业务逻辑却决定了最终 SDK 的形态——哪些操作保留、哪些删除、方法叫什么名字、参数类型是字符串还是枚举、二进制数据如何建模、分页结果如何返回。在 autorest.md 中配置被清晰地分成两部分Configuration声明输入规范、输出目录、生成器版本、模块名等全局参数Customizations通过directive对生成的代码逐一打磨覆盖操作删除、响应裁剪、模型字段增删、参数改造、命名统一等方方面面。在 Tekton Pipeline 仓库 中该文件随github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3以间接依赖// indirect形式被 vendor 进 go.mod。仓库自身的 OCI Bundle 解析pkg/remote/oci/resolver.go走的是go-containerregistry路线因此本文件在仓库中的价值更接近于可读的 SDK 生成史料 Azure 生态工具链参考但其中体现的配置与定制思想对任何基于 AutoRest 的工程都有直接借鉴意义。二、基础 Configuration 块逐项解析原文档的Configuration段定义如下input-file: https://github.com/Azure/azure-rest-api-specs/blob/c8d9a26a2857828e095903efa72512cf3a76c15d/specification/containerregistry/data-plane/Azure.ContainerRegistry/stable/2021-07-01/containerregistry.json license-header: MICROSOFT_MIT_NO_VERSION go: true clear-output-folder: false export-clients: true openapi-type: data-plane output-folder: ../azcontainerregistry use: autorest/go4.0.0-preview.60 honor-body-placement: true remove-unreferenced-types: true module-name: sdk/containers/azcontainerregistry module: github.com/Azure/azure-sdk-for-go/$(module-name) inject-spans: true各配置项的作用如下表配置项值含义与影响input-filecontainerregistry.json2021-07-01 stable指向 Azure Container Registry 数据平面data-plane的 OpenAPI 规范。固定到具体 commitc8d9a26a…保证生成结果可复现license-headerMICROSOFT_MIT_NO_VERSION生成文件头部的 License 模板对应生成代码中的 Copyright (c) Microsoft Corporation…Licensed under the MIT Licensegotrue启用 Go 语言生成器clear-output-folderfalse不清理输出目录允许手写代码如custom_client.go、blob_custom_client.go、authentication_policy.go与生成代码共存export-clientstrue导出客户端类型Client、BlobClient、AuthenticationClient使外部使用者可实例化openapi-typedata-plane声明这是数据平面 SDK区别于管理平面 management-planeoutput-folder../azcontainerregistry生成代码输出到模块根目录即本仓库的 vendor/github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistryuseautorest/go4.0.0-preview.60固定 Go 生成器版本这是保证生成结果确定性的关键honor-body-placementtrue尊重 Swagger 中请求体的放置约定remove-unreferenced-typestrue清理规范中未被任何操作引用的类型缩小 SDK 表面积module-name/modulesdk/containers/azcontainerregistry声明 Go module 路径生成代码内部 import 会基于此拼接inject-spanstrue为每个操作方法注入分布式追踪 span对应生成代码中的runtime.StartSpan对照实际生成代码可以验证这些配置的落地每个公开方法都以ctx, endSpan : runtime.StartSpan(ctx, Client.DeleteManifest, ...)开头client.go文件头固定为 MIT License 与 Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. 声明。三、Directive 体系三类定制手段总览Customizations段全部由directive组成。从原文档可归纳出 AutoRest directive 的三种作用对象Swagger 文档级from: swagger-document或具体 json 名在生成前改写 OpenAPI 规范本身例如删掉某操作的 response schema、给某定义加字段、把参数改成枚举操作重命名rename-operation把规范中的 operationId 重命名直接影响生成方法名生成文件级from: 具体文件名如client.go、blob_client.go、models_serde.go对已生成的 Go 源码做正则字符串替换实现API 层面的隐藏与打磨。三者组合使用构成了下文所有的定制逻辑。接下来按主题逐条拆解。四、裁剪 API 面删除多余操作与响应SDK 并非服务的 OpenAPI 的机械拷贝团队会主动裁剪不合时宜的 API 面1删除删除类操作的响应体directive: - from: swagger-document where: $[paths][/acr/v1/{name}] transform: delete $.delete[responses][202].schemaContainerRegistry_DeleteRepository的 202 响应 schema 被删掉生成代码便不再返回响应体——对应 client.go 中DeleteRepository返回空ClientDeleteRepositoryResponse{}。同理ContainerRegistryBlob_DeleteBlob路径/v2/{name}/blobs/{digest}的 202 schema 也被移除。2删除整个操作directive: - from: swagger-document where: $[paths][/oauth2/token] transform: delete $.getAuthentication_GetAcrAccessTokenFromLogin/oauth2/token的 GET被整体删除原因是服务团队不鼓励使用用户名/密码认证ContainerRegistry_CheckDockerV2Support/v2/的 GET也被删除。这解释了为何生成的 authentication_client.go 中只剩下ExchangeAADAccessTokenForACRRefreshToken与ExchangeACRRefreshTokenForACRAccessToken两个现代认证方法——认证全部走 AADAzure Active Directory令牌交换链路。五、模型与字段级塑形增删字段、补充文档OpenAPI 的模型定义并不总符合 SDK 的客户诉求原文档展示了三类典型调整删除无用字段——TagAttributesBase.properties.signed被删因为没有客户场景使用它directive: - from: swagger-document where: $.definitions.TagAttributesBase transform: delete $.properties.signed新增有用字段——为ManifestAttributesBase补充mediaTypeString描述 Media type for this Manifestdirective: - from: swagger-document where: $.definitions.ManifestAttributesBase transform: $.properties[mediaType] { type: string, description: Media type for this Manifest }这一点与 CHANGELOG.md 中 0.2.1 版本 AddConfigMediaTypeandMediaTypeproperties toManifestAttributes 的记录相互印证。补充类型文档——为枚举与令牌模型补上描述提升生成的 godoc 质量directive: - from: swagger-document where: $.definitions transform: $.ArtifactOperatingSystem.description The artifact platforms operating system.;以及为RefreshToken补 The ACR refresh token response.、为AccessToken补 The ACR access token response.。这些描述最终出现在 models.go 的ACRAccessToken/ACRRefreshToken注释中成为用户阅读 SDK 文档的第一手资料。六、参数改造API 版本、二进制流与排序枚举6.1 API 版本参数移出客户端directive: - from: swagger-document where: $.parameters.ApiVersionParameter transform: $.required true将api-version参数在规范中标记为必填后AutoRest 会把它提升到客户端内部而非每个方法参数——Client构造时统一携带。从生成代码可见请求构建函数内部直接reqQP.Set(api-version, 2021-07-01)client.go用户无需在每次调用时重复传入。6.2 Manifest 请求体改为二进制流directive: from: swagger-document where: $.parameters.ManifestBody transform: $.schema { type: string, format: binary }ManifestBody被建模为二进制流配合后文二进制参数与响应属性重命名的 directive最终生成的方法签名接收manifestData io.ReadCloser类型的原始流数据——客户端拿到的就是未解析的 manifest 原文便于上层自行校验与解析。6.3 orderby 参数改为强类型枚举原规范中 tags/manifests 列表接口的排序参数是裸字符串原文档通过两段几乎对称的transform将其改造为带x-ms-enum的枚举directive: - from: containerregistry.json where: $.paths[/acr/v1/{name}/_tags].get transform: $.parameters.splice(3, 1); $.parameters.push({ name: orderby, x-ms-client-name: OrderBy, in: query, required: false, x-ms-parameter-location: method, type: string, description: Sort options for ordering tags in a collection., enum: [none, timedesc, timeasc], x-ms-enum: { name: ArtifactTagOrderBy, values: [ { value: none, name: None, description: Do not provide an orderby value in the request. }, { value: timedesc, name: LastUpdatedOnDescending, description: Order tags by LastUpdatedOn field, from most recently updated to least recently updated. }, { value: timeasc, name: LastUpdatedOnAscending, description: Order tags by LastUpdatedOn field, from least recently updated to most recently updated. } ] } });注意这里使用了splice(3, 1)先移除位置 3 的旧参数再push新参数并把x-ms-parameter-location设为method作为方法级参数。/acr/v1/{name}/_manifests的 GET 操作做同样改造只是枚举名为ArtifactManifestOrderBy、描述针对 manifests。对照生成源码 constants.goArtifactManifestOrderBy的三个常量timeasc/timedesc/none与PossibleArtifactManifestOrderByValues()辅助函数一一对应。字符串到强类型枚举的转换使调用方在编译期就能避免非法排序值。七、分页操作Get* 到 List* 的重命名与 nextLink 处理7.1 语义统一分页操作统一命名为 List*directive: - rename-operation: from: ContainerRegistry_GetManifests to: ContainerRegistry_ListManifests - rename-operation: from: ContainerRegistry_GetRepositories to: ContainerRegistry_ListRepositories - rename-operation: from: ContainerRegistry_GetTags to: ContainerRegistry_ListTags三个分页操作从Get*重命名为List*符合分页集合用 List、单对象用 Get的 Azure SDK 命名惯例。从 client.go 可见ClientListManifestsResponse携带Manifests与Link字段。7.2 nextLink 端点拼接Swagger 中的分页链接通常是相对路径需要与客户端 endpoint 拼接。原文档用文件级正则替换解决directive: - from: - client.go where: $ transform: return $.replaceAll(/result\.Link val/g, val runtime.JoinPaths(client.endpoint, extractNextLink(val))\n\t\tresult.Link val);把生成代码中所有result.Link val替换为先经extractNextLink提取链接、再与 endpoint 拼接的版本。extractNextLink的手写实现位于 custom_client.gofunc extractNextLink(value string) string { return value[1:strings.Index(value, )] }它负责把形如https://.../acr/v1/repositories?last...n10; relnext的 HTTPLink头裁出真正的 URL。这里可以看到clear-output-folder: false与手写文件custom_client.go协同工作的完整链条生成器负责产出、手写代码负责补齐生成器无法表达的定制逻辑。八、Manifest 读写语义修正Upload 与流式返回8.1 CreateManifest → UploadManifest接受 OCI 媒体类型directive: from: swagger-document where: $.paths[/v2/{name}/manifests/{reference}].put transform: $.consumes.push(application/vnd.oci.image.manifest.v1json); delete $.responses[201].schema;一方面为 PUT 操作追加 OCI Image Manifest 媒体类型application/vnd.oci.image.manifest.v1json另一方面删掉 201 响应体。结合后文的重命名该操作最终以Client.UploadManifest对外暴露。8.2 GetManifest 返回原始流与 Docker-Content-Digest 头directive: from: swagger-document where: $.paths[/v2/{name}/manifests/{reference}].get.responses[200] transform: $.schema { type: string, format: file }; $.headers { Docker-Content-Digest: { type: string, description: Digest of the targeted content for the request. } };200 响应体被建模为文件流并把Docker-Content-Digest响应头映射进结果类型。这与前文ManifestBody的二进制化一脉相承manifest 的整体语义是原始字节 digest 元数据而不是反序列化后的强类型对象。8.3 MountBlob 补齐 202 响应directive: from: swagger-document where: $.paths[/v2/{name}/blobs/uploads/] transform: $.post[responses][202] $.post[responses][201];ContainerRegistryBlob_MountBlob的 POST 操作原本缺少 202配置把 201 的响应定义复制为 202保证异步挂载场景的响应被正确识别。九、命名统一工程操作、参数、类型、常量的全面 Rename一个 SDK 的易用性很大程度上取决于命名。原文档为此投入了大量 directive操作级重命名语义更精确directive: - rename-operation: from: ContainerRegistry_GetProperties to: ContainerRegistry_GetRepositoryProperties - rename-operation: from: ContainerRegistry_UpdateProperties to: ContainerRegistry_UpdateRepositoryProperties - rename-operation: from: ContainerRegistry_UpdateTagAttributes to: ContainerRegistry_UpdateTagProperties - rename-operation: from: ContainerRegistry_CreateManifest to: ContainerRegistry_UploadManifest参数级重命名消除泛化名directive: from: swagger-document where: $.parameters transform: $.DigestReference[x-ms-client-name] digest; $.TagReference[x-ms-client-name] tag;类型级重命名x-ms-client-name批量映射directive: - from: containerregistry.json where: $.definitions transform: $.TagAttributesBase[x-ms-client-name] TagAttributes; $.ManifestAttributesBase[x-ms-client-name] ManifestAttributes; ... $.AcrManifests[x-ms-client-name] Manifests;这段配置同时处理了集合包装类型Repositories的属性重命名为Names、AcrManifests的属性重命名为Attributes、删除TagList属性上多余的x-ms-client-name以及把分页参数QueryNum重命名为MaxNum。常量级重命名Acr→ACRdirective: - from: - *.go where: $ transform: return $.replaceAll(/Acr/g, ACR);对全部生成 Go 文件执行Acr→ACR的全局替换使AcrManifests变为ACRManifests、acrRefreshToken变为acrRefreshToken等符合 Azure Go SDK 的缩写大写惯例。十、二进制上传链路打磨隐藏底层方法、补齐 Content-RangeBlob 分块上传chunked upload是容器注册表客户端最复杂的流程之一原文档用了四段 directive 将其打磨成对用户友好的公开 API。1隐藏原生的 UploadChunk / CompleteUpload先在 Swagger 中删掉/{nextBlobUuidLink}PUT 操作的多余参数再对生成文件做大小写替换把公开方法降级为小写私有方法directive: - from: - blob_client.go where: $ transform: return $.replaceAll(/ UploadChunk/g, uploadChunk).replace(/\.UploadChunk/, .uploadChunk).replaceAll(/ CompleteUpload/g, completeUpload).replace(/\.CompleteUpload/, .completeUpload);同时把选项类型与公开方法一起改名让原生实现彻底藏起来。2为 PATCH 上传块补 Content-Range 请求头directive: - from: swagger-document where: $.paths[/{nextBlobUuidLink}].patch transform: $.parameters.push({ name: Content-Range, in: header, type: string, description: Range of bytes identifying the desired block of content represented by the body. Start must the end offset retrieved via status check plus one. Note that this is a non-standard use of the Content-Range header. });3在_custom_client.go中重构公开 API手写代码 blob_custom_client.go 提供了带BlobDigestCalculator的公开UploadChunk——它先用计算器包装读取流边读边算 sha256再调用私有的uploadChunk失败时调用blobDigestCalculator.restoreState()恢复哈希状态以便重试。CompleteUpload则把计算好的 digest 交给私有completeUploadfunc (client *BlobClient) UploadChunk(ctx context.Context, location string, chunkData io.ReadSeeker, blobDigestCalculator *BlobDigestCalculator, options *BlobClientUploadChunkOptions) (BlobClientUploadChunkResponse, error) { blobDigestCalculator.saveState() reader, err : blobDigestCalculator.wrapReader(chunkData) ... } func (client *BlobClient) CompleteUpload(ctx context.Context, location string, blobDigestCalculator *BlobDigestCalculator, options *BlobClientCompleteUploadOptions) (BlobClientCompleteUploadResponse, error) { return client.completeUpload(ctx, blobDigestCalculator.getDigest(), location, options) }4digest 校验与计算辅助手写文件 digest_helper.go 提供了NewDigestValidationReader下载时边读边校验 sha256EOF 时比对 digest不匹配返回ErrMismatchedHash与NewBlobDigestCalculator上传时增量计算 sha256 digest支持 saveState/restoreState 应对失败重试。这些能力在 CHANGELOG.md 中有明确记录AddDigestValidationReaderto help to do digest validation、Add state restore for hash calculator when upload fails。十一、收尾定制移除构造器、清理无用 Marshal 方法移除生成的构造函数因为构造逻辑完全由手写NewClient/NewBlobClient接管directive: - from: - authentication_client.go - client.go - blob_client.go where: $ transform: return $.replace(/(?:\/\/.*\s)func New.Client.\{\s(?:.\s)\}\s/, );配合clear-output-folder: false手写的 custom_client.go 中的NewClient完成了真正的初始化校验options.Cloud的 Audience 配置、创建认证客户端、注入认证策略authenticationPolicy、组装azcore.Client。从这里可以观察到生成 SDK 的常见分工——AutoRest 负责方法骨架手写代码负责依赖注入与认证装配。清理无用 MarshalJSON对 models_serde.go 执行一系列正则删除移除TagList、TagAttributes、Repositories、Manifests、ManifestAttributes、ContainerRepositoryProperties、ArtifactTagProperties、ArtifactManifestProperties、ArtifactManifestPlatform、acrRefreshToken、acrAccessToken等类型的 Marshal 方法——这些类型只出现在响应方向不需要序列化。十二、认证流程源码印证challenge 驱动的令牌交换虽然认证策略本身由手写代码实现而非 directive 生成但它是理解为何删除/oauth2/tokenGET 操作的关键背景。手写文件 authentication_policy.go 的注释完整描述了 challenge-based 认证五步流程发起请求收到 401 及WWW-Authenticate头含realm、service、scope从响应头解析 service 与 scope用 AAD 令牌向/oauth2/exchange换refreshToken用 refreshToken 向/oauth2/token换accessToken携带Bearer accessToken重放原始请求。同时refreshToken 按服务级缓存所有请求共享accessToken 按 scope 缓存accessTokenCache在连续调用同一 API 时命中。这正是 SDK 团队主张用 AAD 令牌而非用户名密码的底层原因——公开的AuthenticationClient只暴露 AAD 令牌交换链路。十三、在 Tekton Pipeline 仓库中的落地与阅读建议回到本仓库视角azcontainerregistry以 v0.2.3间接依赖出现在 go.mod其全部源码与配置文件被 vendor 至 vendor/github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry。若你想系统研读这份配置的产物建议按以下顺序先读 autorest.md 全文建立配置意图清单对照 client.go仓库/清单/tag/manifest 管理、blob_client.goblob 上传下载与 authentication_client.go令牌交换验证每条 directive 的落点再读手写文件 custom_client.go、blob_custom_client.go、authentication_policy.go、digest_helper.go理解生成骨架 手写补齐的协作模式用 CHANGELOG.md 串联版本演进0.1.0 初版 → 0.2.3 默认 Audience 调整回看配置文件的演化动机。结语一份autorest.md的价值远不止生成配置四个字。从本文可以看到Azure 团队用 YAML 完成了对一个数据平面服务的全维度塑造裁剪删除多余操作与响应、建模二进制流、枚举、响应头、命名Get→List、Acr→ACR、语义化参数、封装隐藏底层方法、补充手写实现、以及质量兜底span 注入、digest 校验。这种配置驱动 手写补齐的生成工程实践对任何正在构建或维护 SDK 的团队都是一份高质量参考——而本仓库恰好以 vendor 形式完整保留了它的全部施工图纸与施工结果值得反复研读对照。赞分享云原生CI/CDDevOps后端【免费下载链接】pipelineA cloud-native Pipeline resource.项目地址https://gitcode.com/gh_mirrors/pipelin/pipeline点击查看免费下载相关推荐AutoRest 代码生成配置深度解析Azure.ResourceManager 的 autorest.md 全参数详解AutoRest 代码生成配置深度解析Azure.ResourceManager 的 autorest.md 全参数详解 在 skills17/skills人工智能AI 技能AI 评测Benchmark开发工具Azure Blob Go SDK 的 autorest.md 代码生成配置解析distribution 仓库 azblob 驱动背后的生成管线Azure Blob Go SDK 的 autorest.md 代码生成配置解析distribution 仓库 azblob 驱动背后的生成管线 导读 本文围云原生存储vCluster 仓库内嵌的 Azure Blob Go SDK 代码生成配置文件 autorest.md 深度解析vCluster 仓库内嵌的 Azure Blob Go SDK 代码生成配置文件 autorest.md 深度解析 导读 本文围绕当前仓库 vendor 目录云原生集群管理虚拟化多集群上一篇NeoEloquent 源码解析CypherGrammar 如何将 Eloquent 翻译为 Cypher 查询下一篇如何用multi-agent-emergence-environments构建自定义多智能体环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
GD32串口ISP工具选型与烧录稳定性实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:38:27
华为员工职业发展困境与突破策略 1. 职场发展困境的深度剖析最近在职场社区看到一位华为14级员工的困惑分享,引发了我对职场发展瓶颈问题的思考。这位工作两年的年轻人在职业发展道路上遇到了典型的"成长焦虑"——与同龄人比较产生的落差感、对职业路径的迷茫、以及"润"&#x… · 2026/9/25 7:38:27
WordPress写真下载站整站源码实测:模板选型、部署避坑与运营全攻略 想靠WordPress做资源下载站或者写真图库类站点的人,十有八九会卡在同一关上:主题不好选,功能凑不齐,折腾半个月还在原地打转。我之前拿到一套号称“全新COS美女写真网站整站源码两套下载站模板”的WP源码包,实测下来发… · 2026/9/25 7:38:21
WarriorJS 塔楼攻略:深入 The Powder Keep——9 层倒计时炸弹与全向战斗的编程挑战 教育CLI 【免费下载链接】warriorjs 🏰 An exciting game of programming and Artificial Intelligence 项目地址: https://gitcode.com/gh_mirrors/wa/warriorjs 点击查看 免费下载 The Powder Keep(火药要塞)是 WarriorJS 中难… · 2026/9/25 8:04:04
昇腾Atlas 300V 24G上YOLO部署全攻略:从模型转换到推理优化 1. Atlas 300V 24G到底是不是运算加速卡?先把这个基础问题说透我先直接回应一下很多人搜“atlas 300v 24g 是运算加速卡吗”这个热词时真正的困惑。答案是:它是,但不是你想的那种“显卡”。Atlas 300V 24G是华为昇腾(Ascend&#… · 2026/9/25 8:03:58
jQuery对象与DOM对象互转:本质差异与实战避坑指南 写 jQuery 写了两三年,见过不少新同事第一个卡壳的地方不是复杂插件,反而是最基础的三个概念:$到底是什么、document.getElementById拿到的对象和$(#id)拿到的对象差在哪、为什么有时候能直接.val(),有时候又要[0]一下。这套对象体… · 2026/9/25 8:03:58
OPA 载荷摘要校验实战:用 Rego 的 crypto.md5 检测数据篡改 后端认证鉴权云原生 【免费下载链接】opa Open Policy Agent (OPA) is an open source, general-purpose policy engine. 项目地址: https://gitcode.com/gh_mirrors/op/opa 点击查看 免费下载 本指南聚焦 Open Policy Agent (OPA) 策略语言 Rego 中的一个实用场景… · 2026/9/25 8:03:58
使用 lego 通过 Amazon Lightsail DNS 完成 DNS-01 证书签发挑战 网络安全密码学 【免费下载链接】lego Lets Encrypt/ACME client and library written in Go 项目地址: https://gitcode.com/gh_mirrors/le/lego 点击查看 免费下载 本文以 lego 项目中 Amazon Lightsail DNS 提供者的官方文档为主体,结合其 lightsail… · 2026/9/25 8:03:52
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37