關(guān)于Swagger優(yōu)化的實戰(zhàn)記錄
背景
盡管.net6已經(jīng)發(fā)布很久了,但是公司的項目由于種種原因依舊基于.net Framework。伴隨著版本迭代,后端的api接口不斷增多,每次在聯(lián)調(diào)的時候,前端開發(fā)叫苦不迭:“小胖,你們的swagger頁面越來越卡了,快優(yōu)化優(yōu)化!”。
先查看swagger頁面加載耗時:
以上分別是:
- v1加載了兩次
- 重新編譯程序后打開swagger頁面,加載v1(api json)竟然耗時兩分多鐘。
- 第一次完整加載頁面后重新刷新頁面,再次查看swagger的耗時,這次明顯頁面加載速度提升了不少,但依舊不盡人人意,json返回后渲染耗時太久。
探察&解決
swagger加載的卡慢問題,萌生了優(yōu)化swagger的想法,剛開始按傳統(tǒng)技能在網(wǎng)絡(luò)上搜索了一大圈依舊未找到解決方案。幸好swashbuckle開源,還能自己動手分析了。先下載好源碼GitHub - domaindrivendev/Swashbuckle.WebApi: Seamlessly adds a swagger to WebApi projects!
一、先看看v1加載慢,卻要加載兩次。
從上面的圖上不難發(fā)現(xiàn)第二次v1的加載是跟在lang.js后面,而lang.js實際上就是用來做漢化。打開項目中這個文件
原來是為了添加控制器注釋,重新訪問后端取一次接口文檔。在查看了源碼js后,得到一個更簡單的方式,頁面的漢化翻譯,是在數(shù)據(jù)取完頁面已經(jīng)渲染后才進(jìn)行的,可直接使用window.swaggerApi.swaggerObject.ControllerDesc。
setControllerSummary: function () { var summaryDict = window.swaggerApi.swaggerObject.ControllerDesc; var id, controllerName, strSummary; $("#resources_container .resource").each(function (i, item) { id = $(item).attr("id"); if (id) { controllerName = id.substring(9); try { strSummary = summaryDict[controllerName]; if (strSummary) { $(item).children(".heading").children(".options").first().prepend('<li class="controller-summary" style="color:green;" title="' + strSummary + '">' + strSummary + '</li>'); } } catch (e) { console.log(e); } } }); },
修改完文件以后,再看看頁面的加載,已經(jīng)不會重復(fù)去訪問v1。
二、接下來處理v1加載慢
先看看項目的的swagger配置:
GlobalConfiguration.Configuration .EnableSwagger(c => { c.IncludeXmlComments(GetXmlCommentsPath(thisAssembly.GetName().Name)); c.IncludeXmlComments(GetXmlCommentsPath("xxxx.Api.Dto")); c.SingleApiVersion("v1", "xxxx.Api"); c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider)); })
配置不多,其中有個CachingSwaggerProvider,實現(xiàn)了GetSwagger方法自定義返回數(shù)據(jù),在這個方法里可以得知,實際上對api文檔是有做緩存處理,v1加載的數(shù)據(jù)也就是這個SwaggerDocument。這也意味著,v1加載慢的原因出在這里。
public SwaggerDocument GetSwagger(string rootUrl, string apiVersion) { var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion); SwaggerDocument srcDoc = null; //只讀取一次 if (!_cache.TryGetValue(cacheKey, out srcDoc)) { srcDoc = (_swaggerProvider as Swashbuckle.Swagger.SwaggerGenerator).GetSwagger(rootUrl, apiVersion); srcDoc.vendorExtensions = new Dictionary<string, object> { { "ControllerDesc", GetControllerDesc() } }; _cache.TryAdd(cacheKey, srcDoc); } return srcDoc; }
調(diào)試程序的時候,swashbuckle提供的GetSwagger方法占據(jù)了大量的耗時。將源碼Swashbuckle.Core引用進(jìn)來,重新打開swagger時會有個小問題,資源文件都報404錯誤,這個是因為嵌入資源文件沒有找到
<ItemGroup> <EmbeddedResource Include="..\swagger-ui\dist\**\*.*"> <LogicalName>%(RecursiveDir)%(FileName)%(Extension)</LogicalName> <InProject>false</InProject> </EmbeddedResource> </ItemGroup>
根據(jù)路徑查看,swagger-ui下是空白的。將從其他地方找到的或者從反編譯文件里整理出來的文件放到該目錄下,并將swagger-ui作為依賴項,重新編譯項目后swagger頁面加載資源文件就正常了。(如果有遇到依舊找不到資源文件的情況,重新再添加一次依賴項編譯項目即可)
接下來就可以開始調(diào)試了,經(jīng)過一番波折,最終將元兇定位到了SwaggerGenerator中GetSwagger方法里獲取paths這個地方,實際上就是在使用CreatePathItem的時候耗時過久
var paths = GetApiDescriptionsFor(apiVersion) .Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.IsObsolete())) .OrderBy(_options.GroupingKeySelector, _options.GroupingKeyComparer) .GroupBy(apiDesc => apiDesc.RelativePathSansQueryString()) .ToDictionary(group => "/" + group.Key, group => CreatePathItem(group, schemaRegistry));
剛開始嘗試用多線程的方式進(jìn)行處理,盡管確實能夠縮短獲取json數(shù)據(jù)的時間,但依舊有兩個問題:
線程不安全,時不時頁面會報錯即使能快速返回json數(shù)據(jù),頁面渲染耗慢的問題依舊未解決。正如前面我們的項目中GetSwagger是使用到緩存的,在重新刷新swagger時,依舊存在卡慢問題。
三、將需返回json數(shù)據(jù)
優(yōu)化swagger加載,需要同時考慮到前端渲染頁面以及后端梳理json數(shù)據(jù)所導(dǎo)致的頁面加載慢問題。有什么好的辦法么?swashbuckle core版本是支持分組的,但是項目使用的Framework版本不支持,既然不支持,就直接改造源碼,按控制器分組,說干就干:
找到HttpConfigurationExtensions類的EnableSwagger方法,這個方法用來配置路由
public static SwaggerEnabledConfiguration EnableSwagger( this HttpConfiguration httpConfig, string routeTemplate, Action<SwaggerDocsConfig> configure = null) { var config = new SwaggerDocsConfig(); if (configure != null) configure(config); httpConfig.Routes.MapHttpRoute( name: "swagger_docs" + routeTemplate, routeTemplate: routeTemplate, defaults: null, constraints: new { apiVersion = @".+" }, handler: new SwaggerDocsHandler(config) ); //配置控制器路由 string controllRouteTemplate=DefaultRouteTemplate+"/{controller}"; httpConfig.Routes.MapHttpRoute( name: "swagger_docs" + controllRouteTemplate, routeTemplate: controllRouteTemplate, defaults: null, constraints: new { apiVersion = @".+" }, handler: new SwaggerDocsHandler(config) ); return new SwaggerEnabledConfiguration( httpConfig, config.GetRootUrl, config.GetApiVersions().Select(version => routeTemplate.Replace("{apiVersion}", version))); }
接下來找到SwaggerDocsHandler類,修改SendAsync方法,獲取controller,并將controller傳遞到GetSwagger中
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var swaggerProvider = _config.GetSwaggerProvider(request); var rootUrl = _config.GetRootUrl(request); var apiVersion = request.GetRouteData().Values["apiVersion"].ToString(); var controller = request.GetRouteData().Values["controller"]?.ToString(); if (string.IsNullOrEmpty(controller)) { controller = "Account"; } try { var swaggerDoc = swaggerProvider.GetSwagger(rootUrl, apiVersion, controller); var content = ContentFor(request, swaggerDoc); return TaskFor(new HttpResponseMessage { Content = content }); } catch (UnknownApiVersion ex) { return TaskFor(request.CreateErrorResponse(HttpStatusCode.NotFound, ex)); } }
相對應(yīng)的修改ISwagger接口,以及接口的實現(xiàn)類SwaggerGenerator,增加按Controller篩選
public interface ISwaggerProvider { SwaggerDocument GetSwagger(string rootUrl, string apiVersion,string controller); }
SwaggerGenerator的GetSwagger修改:
var temps = GetApiDescriptionsFor(apiVersion) .Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.IsObsolete())); if (string.IsNullOrEmpty(controller) == false) { temps = temps.Where(apiDesc => apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower() == controller.ToLower()); } var paths = temps .OrderBy(_options.GroupingKeySelector, _options.GroupingKeyComparer) .GroupBy(apiDesc => apiDesc.RelativePathSansQueryString()) .ToDictionary(group => "/" + group.Key, group => CreatePathItem(group, schemaRegistry));
自己項目中關(guān)于ISwagger實現(xiàn)也要修改,然后開始重新編譯自己的項目,重新打開swagger頁面,頁面在后端編譯后第一次打開也非常迅速。默認(rèn)打開的是Account控制器下的接口,如果切換到其他控制器下的接口只需要在url后加入對應(yīng)的/Controller
四、修改Swagger頁面
以上我們已經(jīng)把頁面的加載慢的問題解決了,但在切換控制器上是否過于麻煩,能不能提升前端開發(fā)人員的使用體驗,提供一個下拉列表選擇是不是更好呢?繼續(xù)干!
找到源碼目錄下的SwaggerUi\CustomAssets\Index.html文件,添加一個id為select_baseUrl的select下拉選擇框,并將input_baseurl輸入框隱藏
修改swagger-ui-js下的window.SwaggerUi的render方法(要記得將index.html中的swagger-ui-min-js的引用改為swagger-ui-js)加入填充下拉數(shù)據(jù)的js代碼以及添加下拉框觸發(fā)事件
找到SwaggerUi.Views.HeaderView,添加下拉事件
重新編譯后,刷新頁面試試效果,可以下拉選擇分組
結(jié)語
到此這篇關(guān)于Swagger優(yōu)化的文章就介紹到這了,更多相關(guān)Swagger優(yōu)化實戰(zhàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決ASP.NET回傳后div滾動條位置復(fù)位的問題(利用隱藏控件原理)
這篇文章主要介紹了解決ASP.NET回傳后div滾動條位置復(fù)位的問題,中心思想是用一個隱藏控件保存當(dāng)前scorll值?;貍骰貋砗蟾鶕?jù)scrollTop的值,然后在Page_Load中重新設(shè)置scrollTop2014-01-01ASP.NET性能優(yōu)化之構(gòu)建自定義文件緩存
ASP.NET的輸出緩存(即靜態(tài)HTML)在.NET4.0前一直是基于內(nèi)存的。這意味著如果我們的站點含有大量的緩存,則很容易消耗掉本機(jī)內(nèi)存。2011-09-09ASP.NET MVC中為DropDownListFor設(shè)置選中項的方法
這篇文章主要介紹了ASP.NET MVC中為DropDownListFor設(shè)置選中項的方法,需要的朋友可以參考下2014-10-10ASP.NET動態(tài)增加HTML元素的方法實例小結(jié)
這篇文章主要介紹了ASP.NET動態(tài)增加HTML元素的方法,結(jié)合實例形式總結(jié)分析了asp.net針對樣式、Meta、js等元素動態(tài)增加相關(guān)操作技巧,需要的朋友可以參考下2017-01-01實現(xiàn)Asp與Asp.Net共享Session的方法
這篇文章主要介紹了實現(xiàn)Asp與Asp.Net共享Session的方法,需要的朋友可以參考下2014-08-08asp.net下實現(xiàn)支持文件分塊多點異步上傳的 Web Services
asp.net下實現(xiàn)支持文件分塊多點異步上傳的 Web Services...2007-04-04asp.net sql 數(shù)據(jù)庫處理函數(shù)命令
asp.net sql 數(shù)據(jù)庫處理函數(shù)命令 ,需要的朋友可以參考下。2009-10-10.net任務(wù)調(diào)度框架Hangfire簡介
這篇文章介紹了.net任務(wù)調(diào)度框架Hangfire的簡單使用方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07