ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Swagger UI 插件接入点(Plug Points)实战指南:覆盖核心逻辑、自定义组件与错误处理

Swagger UI 插件接入点(Plug Points)实战指南:覆盖核心逻辑、自定义组件与错误处理 Swagger UI 插件接入点Plug Points实战指南覆盖核心逻辑、自定义组件与错误处理【免费下载链接】swagger-uiSwagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.项目地址: https://gitcode.com/GitHub_Trending/sw/swagger-uiSwagger UI 通过插件系统暴露了绝大多数内部逻辑允许开发者在不改动源码的前提下覆盖、扩展或替换核心实现。本文以 docs/customization/plug-points.md 为主线结合仓库源码逐一剖析fn.opsFilter过滤函数、Logo 组件替换、JSON Schema 输入组件映射、Request Snippets 自定义生成器以及safe-render错误处理插件这五大接入点并给出可直接复制的完整代码示例。读完本文你将掌握用插件 API 定制 Swagger UI 行为的标准姿势并能安全地规避内部 API 变动带来的兼容性风险。插件接入点背后的系统模型在深入各个接入点之前先明确一个前提Swagger UI 的大部分内部逻辑都是通过插件系统暴露的而插件最终会被编译进应用运行时的一个 JavaScript 对象——system系统。系统持有 React 组件、绑定的 Redux action/reducer、Reselect 选择器、组件注册表以及getComponent、getStore等内置辅助函数。系统由presets与plugins两个配置项传入的插件迭代编译而成其中presets 是插件数组且presets中的插件总是先于plugins中的插件被编译自定义 presets 时需手动带上SwaggerUI.presets.apis以保证基线功能不被丢失详见 docs/customization/overview.md。插件可以暴露的挂载点非常丰富除components、fn、wrapComponents、wrapActions、wrapSelectors等常规键外还支持statePlugins、rootInjects、afterLoad等。本文聚焦 plug-points 文档重点讲述的五个接入点fn函数覆盖、组件替换、动态组件名映射、自定义代码生成器以及错误边界Error Boundary体系。若需了解插件 API 的完整形态可继续阅读 docs/customization/plugin-api.md 与 docs/customization/add-plugin.md。版本兼容性内部 API 不受语义化版本约束在开始覆写任何内部逻辑之前务必先理解 Swagger UI 的版本承诺。官方文档明确指出Swagger UI 的内部 API不属于公共契约的一部分它们可以在不改变主版本号的情况下发生变化。也就是说即使你的插件只是包裹、扩展、覆盖或消费了某个内部核心 API也不能指望它在未来的x.y.z版本中保持行为不变——它们只在patch 版本之间保证不变。因此文档给出的最佳实践是在你的应用中锁定 Swagger UI 的具体 minor 版本。通过 NPM 安装时可以使用波浪号~范围{ dependencies: { swagger-ui: ~3.11.0 } }~3.11.0表示只允许安装3.11.0 3.12.0的版本从而确保你依赖的内部 API 在 patch 升级中保持稳定。如果通过 CDN 引入同样应在 URL 中锁定到 minor 版本号。这条约定贯穿本文所有示例——覆写内部函数和组件都属于消费内部 API都应遵循此锁定策略。接入点一覆写fn.opsFilter实现自定义标签过滤当启用filter配置项后Swagger UI 会按用户输入的值过滤 tag 名称其底层依赖的就是默认的opsFilter函数。该默认实现位于 src/core/plugins/filter/opsFilter.jsexport default function(taggedOps, phrase) { return taggedOps.filter((tagObj, tag) tag.indexOf(phrase) ! -1) }它接收两个参数taggedOps按 tag 分组后的操作集合和phrase用户输入的过滤短语逻辑是保留名称中包含该短语的 tag。对应的过滤器插件在 src/core/plugins/filter/index.js 中通过fn: { opsFilter }挂载。如果你希望实现更复杂的过滤语义——例如多短语过滤——可以直接在自定义插件中覆盖这个fn键const MultiplePhraseFilterPlugin function() { return { fn: { opsFilter: (taggedOps, phrase) { const phrases phrase.split(, ) return taggedOps.filter((val, key) { return phrases.some(item key.indexOf(item) -1) }) } } } }该插件将用户输入的pet, store按逗号加空格拆分为短语数组只要 tag 名包含其中任意一个短语即被保留从而支持一次输入多个关键词。将MultiplePhraseFilterPlugin加入plugins配置数组并开启filter: true即可生效。由于opsFilter属于内部fnAPI请记得对 Swagger UI 做 minor 版本锁定。接入点二替换 Standalone 预设中的 Logo 组件使用Standalone Preset时Swagger UI 顶栏Top Bar会渲染默认的 Swagger UI Logo。默认 Logo 组件在 src/standalone/plugins/top-bar/components/Logo.jsx 中定义import React from react import SwaggerUILogo from ../assets/logo_small.svg const Logo () SwaggerUILogo height40 /而顶栏插件通过 src/standalone/plugins/top-bar/index.js 将Logo注册为系统组件const TopBarPlugin () ({ components: { Topbar: TopBar, Logo, DarkModeToggle }, })因为Logo是注册在组件表中的具名组件所以你可以通过插件 API 提供同名组件来整体替换它而无需触碰Topbar本身import React from react; const MyLogoPlugin { components: { Logo: () ( img altMy Logo height40 srcdata:image/svgxml;base64,PHN2ZyB3aWR0aD0iNTM3IiBoZWlnaHQ9IjEzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCiA8Zz4KICA8dGl0bGUTGF5ZXIgMTwvdGl0bGUCiAgPHRleHQgdHJhbnNmb3JtPSJtYXRyaXgoMy40Nzc2OSAwIDAgMy4yNjA2NyAtNjczLjEyOCAtNjkxLjk5MykiIHN0cm9rZT0iIzAwMCIgZm9udC1zdHlsZT0ibm9ybWFsIiBmb250LXdlaWdodD0ibm9ybWFsIiB4bWw6c3BhY2U9InByZXNlcnZlIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtZmFtaWx5PSInT3BlbiBTYW5zIEV4dHJhQm9sZCciIGZvbnQtc2l6ZT0iMjQiIGlkPSJzdmdfMSIgeT0iMjQxLjIyMTkyIiB4PSIxOTYuOTY5MjEiIHN0cm9rZS13aWR0aD0iMCIgZmlsbD0iIzYyYTAzZiITXkgTG9nbzwvdGV4dD4KICA8cGF0aCBpZD0ic3ZnXzIiIGQ9Im0zOTUuNjAyNSw1MS4xODM1OWw1My44Nzc3MSwwbDE2LjY0ODYzLC01MS4xODM1OGwxNi42NDg2NCw1MS4xODM1OGw1My44Nzc3LDBsLTQzLjU4NzksMzEuNjMyODNsMTYuNjQ5NDksNTEuMTgzNThsLTQzLjU4NzkyLC0zMS42MzM2OWwtNDMuNTg3OTEsMzEuNjMzNjlsMTYuNjQ5NDksLTUxLjE4MzU4bC00My41ODc5MiwtMzEuNjMyODN6IiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZT0iIzAwMCIgZmlsbD0iIzYyYTAzZiIvPgogPC9nPgo8L3N2Zz4/ ) } }示例中的MyLogoPlugin通过components键重新注册了Logo并以内联 SVG 作为图片来源。将其加入plugins: [MyLogoPlugin]即可生效。由于组件按名称在系统组件表中注册后注册的同名组件会覆盖先前的注册这正是组件替换类插件的工作原理——也解释了为何wrapComponents提供了更温和的包装式扩展保留原组件、仅增强行为适用于不希望完全丢弃默认实现的场景。接入点三JSON Schema 组件映射与自定义输入组件在 Swagger UI 中参数输入框以及application/x-www-form-urlencoded、multipart/*媒体类型的 request body 组件都是由所谓的JSON Schema 组件渲染的。Swagger UI 内部依据 OpenAPI 规范中 schema 的type如string、array和可选的format如date、uuid推导组件名称映射规则如下如果定义了 formatJsonSchema_${type}_${format}回退逻辑——当JsonSchema_${type}_${format}组件不存在或未定义 format 时JsonSchema_${type}最终兜底默认值JsonSchema_string这套命名推导逻辑真实存在于源码中。在 src/core/plugins/json-schema-5/components/json-schema-components.jsx 中可以看到按type/format组合查找组件并最终回退到JsonSchema_string的实现约第 5658 行数组项组件同样遵循JsonSchema_${schemaItemsType}_${schemaItemsFormat}→JsonSchema_${schemaItemsType}→JsonSchema_object的降级链约第 204210 行。该文件同时也默认导出了JsonSchema_string、JsonSchema_array、JsonSchema_boolean、JsonSchema_object等内置输入组件。利用这套命名映射你可以定义全新的输入组件或覆盖现有组件。下面是一个完整的Date-Picker 插件示例为type: string的日期类输入接入react-datepicker。映射关系分两种情况type: string format: date映射成功所需的组件名JsonSchema_string_datetype: string format: date-time映射成功所需的组件名JsonSchema_string_date-time由此需要提供两个组件并辅以简单逻辑在date场景下剥离时间部分import React from react; import DatePicker from react-datepicker; import react-datepicker/dist/react-datepicker.css; const JsonSchema_string_date (props) { const dateNumber Date.parse(props.value); const date dateNumber ? new Date(dateNumber) : new Date(); return ( DatePicker selected{date} onChange{d props.onChange(d.toISOString().substring(0, 10))} / ); } const JsonSchema_string_date_time (props) { const dateNumber Date.parse(props.value); const date dateNumber ? new Date(dateNumber) : new Date(); return ( DatePicker selected{date} onChange{d props.onChange(d.toISOString())} showTimeSelect timeFormatp dateFormatPp / ); } export const DateTimeSwaggerPlugin { components: { JsonSchema_string_date: JsonSchema_string_date, JsonSchema_string_date-time: JsonSchema_string_date_time } };两个组件都接收props.value当前输入值与props.onChange值变更回调前者在提交时用d.toISOString().substring(0, 10)只保留日期部分后者保留完整的toISOString()并启用时间选择。注意date-time组件名中含有连字符因此在对象字面量中必须以引号包裹键名。将DateTimeSwaggerPlugin放入plugins数组后OpenAPI 定义中所有type: stringformat: date/date-time的 schema 都会自动渲染为日期选择器。这一机制同样适用于其他 format——比如为format: uuid提供自定义JsonSchema_string_uuid输入框。接入点四为 Request Snippets 编写自定义代码生成器当配置requestSnippetsEnabled: true后Swagger UI 会在执行请求时展示比默认 curl 更细粒度的代码片段选项内置支持三种目标环境bash 下的 curlcmd 下的 curlpowershell 下的 curl这三个内置生成器的实现可在 src/core/plugins/request-snippets/fn.js 中找到分别对应requestSnippetGenerator_curl_bash、requestSnippetGenerator_curl_cmd、requestSnippetGenerator_curl_powershell它们共享一个curlify(request, escape, newLine, ext)核心函数通过不同的转义策略与换行符生成对应平台的命令插件入口 src/core/plugins/request-snippets/index.js 将这些函数连同RequestSnippets组件、requestSnippets状态插件selectors一并注册。UI 组件 request-snippets.jsx 渲染语言切换按钮并通过选择器调用当前激活生成器产出片段。如果你需要提供自己的生成器例如生成 Node.js 原生 HTTP 代码可以通过插件 API 完成。一个请求片段生成器由两部分构成在 Request Snippets 配置中注册的生成器元信息键、标题、语法高亮语言以及一个接收内部 request 对象并将其转换为目标代码片段的fn。二者通过requestSnippetGenerator_${key}命名约定关联——这一点在选择器实现 src/core/plugins/request-snippets/selectors.js 中有明确证据getSnippetGenerators会以fn[\requestSnippetGenerator_${key}] 查找每个配置键对应的生成函数。完整示例如下// 以唯一键 node_native 将生成器元信息加入 Request Snippets 配置 const snippetConfig { requestSnippetsEnabled: true, requestSnippets: { generators: { node_native: { title: NodeJs Native, syntax: javascript } } } } const SnippedGeneratorNodeJsPlugin { fn: { // 使用 requestSnippetGenerator_ 配置键 (node_native) 作为生成器函数名 requestSnippetGenerator_node_native: (request) { const url new Url(request.get(url)) let isMultipartFormDataRequest false const headers request.get(headers) if(headers headers.size) { request.get(headers).map((val, key) { isMultipartFormDataRequest isMultipartFormDataRequest || /^content-type$/i.test(key) /^multipart\/form-data$/i.test(val) }) } const packageStr url.protocol https: ? https : http let reqBody request.get(body) if (request.get(body)) { if (isMultipartFormDataRequest [POST, PUT, PATCH].includes(request.get(method))) { return throw new Error(\Currently unsupported content-type: /^multipart\\/form-data$/i\); } else { if (!Map.isMap(reqBody)) { if (typeof reqBody ! string) { reqBody JSON.stringify(reqBody) } } else { reqBody getStringBodyOfMap(request) } } } else if (!request.get(body) request.get(method) POST) { reqBody } const stringBody (reqBody || ) .replace(/\\n/g, \n) .replace(//g, \\) return const http require(${packageStr}); const options { method: ${request.get(method)}, hostname: ${url.host}, port: ${url.port || null}, path: ${url.pathname}${headers headers.size ? , headers: { ${request.get(headers).map((val, key) ${key}: ${val}).valueSeq().join(,\n )} } : } }; const req http.request(options, function (res) { const chunks []; res.on(data, function (chunk) { chunks.push(chunk); }); res.on(end, function () { const body Buffer.concat(chunks); console.log(body.toString()); }); }); ${reqBody ? \nreq.write(${stringBody}); : } req.end(); } } } const ui SwaggerUIBundle({ dom_id: #swagger-ui, deepLinking: true, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl, SnippedGeneratorNodeJsPlugin ], layout: StandaloneLayout, validatorUrl: https://validator.swagger.io/validator, url: https://petstore.swagger.io/v2/swagger.json, ...snippetConfig, })生成器函数接收的request是 Immutable.js 风格的内部请求对象通过request.get(url)、request.get(method)、request.get(headers)、request.get(body)取值。示例中先解析 URL 并检测是否为multipart/form-data请求对不支持的multipart/form-data的 POST/PUT/PATCH 直接返回错误代码body 为 Map 时调用getStringBodyOfMap序列化与内置 curl 生成器在 fn.js 中的处理方式一致最终拼装出 Node.jshttp/https模块的完整调用代码。...snippetConfig以展开方式合并requestSnippetsEnabled与requestSnippets.generators配置插件数组中加入SnippedGeneratorNodeJsPlugin后NodeJs Native 标签页便会出现在请求片段区域中。requestSnippetsEnabled等配置项的完整语义可参考 docs/usage/configuration.md。接入点五safe-render 插件与错误处理体系Swagger UI 内置了safe-render插件用于处理组件渲染错误并允许你接入、修改错误处理系统。它接受一个应被错误边界保护的组件名列表其公开 API 形如{ fn: { componentDidCatch, withErrorBoundary: withErrorBoundary(getSystem), }, components: { ErrorBoundary, Fallback, }, }safe-render插件会被 base 与 standalone 预设自动加载并且应当始终作为最后一个插件使用——即排在所有组件已知于系统之后。插件内部实现src/core/plugins/safe-render/index.js定义了默认受保护组件列表[ App, BaseLayout, VersionPragmaFilter, InfoContainer, ServersContainer, SchemesContainer, AuthorizeBtnContainer, FilterContainer, Operations, OperationContainer, parameters, responses, OperationServers, Models, ModelWrapper, Topbar, StandaloneLayout, onlineValidatorBadge ]从源码实现看safe-render/index.js中的默认列表为核心组件AppModelWrapper而文档中列出的Topbar、StandaloneLayout、onlineValidatorBadge三项由 standalone 侧的相关预设补充注入二者共同构成完整的受保护集合。如果你作为 Swagger UI 集成方维护着多个携带自定义组件的插件可以利用配置选项为更多组件开启错误边界保护const swaggerUI SwaggerUI({ url: https://petstore.swagger.io/v2/swagger.json, dom_id: #swagger-ui, plugins: [ () ({ components: { MyCustomComponent1: () my custom component, }, }), SwaggerUI.plugins.SafeRender({ fullOverride: true, // 仅采用这里定义的组件列表不再使用默认列表 componentList: [ MyCustomComponent1, ], }), ], });fullOverride: true表示完全抛弃默认列表只保护componentList中列出的组件不传或设为false时默认列表会与自定义列表合并。这一开关直接对应 safe-render/index.js 中的mergedComponentList fullOverride ? componentList : [...defaultComponentList, ...componentList]逻辑。SwaggerUI.plugins.SafeRender本身是一个工厂函数({componentList, fullOverride}) plugin因此传入配置对象即可实例化。safe-render 体系由四个可插拔单元组成componentDidCatch这是一个静态函数在某个组件抛出错误后被调用接收两个参数error—— 被抛出的错误对象info—— 一个对象其componentStack键包含哪个组件抛出了错误的信息即 React 错误边界的组件栈追踪。它的签名与 React 错误边界的componentDidCatch生命周期方法完全一致区别在于它是静态函数而非类方法。默认实现只是将错误打印到控制台src/core/plugins/safe-render/fn.jsxexport const componentDidCatch console.error;若要接入自己的错误上报逻辑例如 Bugsnag、Sentry只需新建一个覆盖componentDidCatch的插件const BugsnagErrorHandlerPlugin () { // init bugsnag return { fn: { componentDidCatch (error, info) { Bugsnag.notify(error); Bugsnag.notify(info); }, }, }; };注意文档示例中componentDidCatch (error, info) {...}是对象字面量内的缩写赋值的示意写法实际插件中应写为标准方法定义componentDidCatch: (error, info) { ... }。withErrorBoundary这是一个高阶组件HOC作用是把特定组件包裹进ErrorBoundary组件中。其实现src/core/plugins/safe-render/fn.jsx通过getComponent(ErrorBoundary)获取边界组件、用fn.getDisplayName计算目标组件名并妥善处理了类组件上mapStateToProps公开方法的透传避免包装破坏既有行为。你可以通过插件系统覆盖它以控制组件被 ErrorBoundary 包裹的方式。在 99.9% 的场景下你无需覆盖此函数——若确有需要请先阅读该函数的源码。Fallback当错误边界捕获到错误时会展示这个组件。默认实现非常朴素src/core/plugins/safe-render/components/fallback.jsximport React from react import PropTypes from prop-types const Fallback ({ name }) ( div classNamefallback iCould not render { name t ? this component : name }, see the console./i /div ) Fallback.propTypes { name: PropTypes.string.isRequired, } export default Fallback你可以自由覆盖它以匹配自己的视觉风格const CustomFallbackPlugin () ({ components: { Fallback: ({ name } ) This is my custom fallback. ${name} failed to render, }, }); const swaggerUI SwaggerUI({ url: https://petstore.swagger.io/v2/swagger.json, dom_id: #swagger-ui, plugins: [ CustomFallbackPlugin, ] });ErrorBoundary这是实现 React 错误边界的组件本体src/core/plugins/safe-render/components/error-boundary.jsx内部使用componentDidCatch与Fallback。当捕获到错误时它通过getComponent(Fallback)渲染 fallback 并调用fn.componentDidCatch(error, errorInfo)。与withErrorBoundary同理99.9% 的场景下无需覆盖该组件如需覆盖请先通读其源码。v4.3.0 起的行为变化在 Swagger UI v4.3.0 之前几乎所有组件都被错误边界保护一旦抛错就会展示Fallback组件。自 v4.3.0 起行为发生变化只有 safe-render 插件定义的组件才受保护并展示 fallback。如果组件树深处的某个小组件渲染失败抛错错误会冒泡到最近的错误边界由该边界展示Fallback并触发componentDidCatch。这意味着集成方需要明确为关键自定义组件开启保护见上文fullOverride示例而不是依赖全局兜底的旧行为。接入点选择策略与总结综合以上五个接入点可以归纳出 Swagger UI 插件化定制的几条选择原则接入点适用场景推荐方式fn.opsFilter自定义 tag 过滤语义多短语、正则等覆盖fn键实现同签名函数components.Logo替换顶栏 Logo 等具名展示组件以同名组件重新注册JsonSchema_${type}_${format}按 schema type/format 定制参数与表单输入控件注册遵循命名映射的组件requestSnippetGenerator_${key} 配置新增目标语言的请求代码片段生成器fnrequestSnippets.generators配置配对safe-render系列错误上报、fallback 视觉定制、保护自定义组件覆盖componentDidCatch/Fallback或实例化SafeRender工厂所有覆写内部 API 的插件都应搭配 minor 版本锁定如~3.11.0使用以规避内部契约变动。文中每个接入点的背后都有对应的源码文件可查证过滤函数见 src/core/plugins/filter/opsFilter.js、Logo 注册见 src/standalone/plugins/top-bar/index.js、Schema 组件映射见 src/core/plugins/json-schema-5/components/json-schema-components.jsx、片段生成器见 src/core/plugins/request-snippets/fn.js、错误处理见 src/core/plugins/safe-render。建议在动手定制前通读 docs/customization/plugin-api.md 与 docs/customization/overview.md全面掌握插件系统的完整能力边界。【免费下载链接】swagger-uiSwagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.项目地址: https://gitcode.com/GitHub_Trending/sw/swagger-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表