網站首頁 編程語言 正文
背景
盡管.net6已經發布很久了,但是公司的項目由于種種原因依舊基于.net Framework。伴隨著版本迭代,后端的api接口不斷增多,每次在聯調的時候,前端開發叫苦不迭:“小胖,你們的swagger頁面越來越卡了,快優化優化!”。
先查看swagger頁面加載耗時:
以上分別是:
- v1加載了兩次
- 重新編譯程序后打開swagger頁面,加載v1(api json)竟然耗時兩分多鐘。
- 第一次完整加載頁面后重新刷新頁面,再次查看swagger的耗時,這次明顯頁面加載速度提升了不少,但依舊不盡人人意,json返回后渲染耗時太久。
探察&解決
swagger加載的卡慢問題,萌生了優化swagger的想法,剛開始按傳統技能在網絡上搜索了一大圈依舊未找到解決方案。幸好swashbuckle開源,還能自己動手分析了。先下載好源碼GitHub - domaindrivendev/Swashbuckle.WebApi: Seamlessly adds a swagger to WebApi projects!
一、先看看v1加載慢,卻要加載兩次。
從上面的圖上不難發現第二次v1的加載是跟在lang.js后面,而lang.js實際上就是用來做漢化。打開項目中這個文件
原來是為了添加控制器注釋,重新訪問后端取一次接口文檔。在查看了源碼js后,得到一個更簡單的方式,頁面的漢化翻譯,是在數據取完頁面已經渲染后才進行的,可直接使用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);
}
}
});
},
修改完文件以后,再看看頁面的加載,已經不會重復去訪問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,實現了GetSwagger方法自定義返回數據,在這個方法里可以得知,實際上對api文檔是有做緩存處理,v1加載的數據也就是這個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;
}
調試程序的時候,swashbuckle提供的GetSwagger方法占據了大量的耗時。將源碼Swashbuckle.Core引用進來,重新打開swagger時會有個小問題,資源文件都報404錯誤,這個是因為嵌入資源文件沒有找到
<ItemGroup>
<EmbeddedResource Include="..\swagger-ui\dist\**\*.*">
<LogicalName>%(RecursiveDir)%(FileName)%(Extension)</LogicalName>
<InProject>false</InProject>
</EmbeddedResource>
</ItemGroup>
根據路徑查看,swagger-ui下是空白的。將從其他地方找到的或者從反編譯文件里整理出來的文件放到該目錄下,并將swagger-ui作為依賴項,重新編譯項目后swagger頁面加載資源文件就正常了。(如果有遇到依舊找不到資源文件的情況,重新再添加一次依賴項編譯項目即可)
接下來就可以開始調試了,經過一番波折,最終將元兇定位到了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));
剛開始嘗試用多線程的方式進行處理,盡管確實能夠縮短獲取json數據的時間,但依舊有兩個問題:
線程不安全,時不時頁面會報錯即使能快速返回json數據,頁面渲染耗慢的問題依舊未解決。正如前面我們的項目中GetSwagger是使用到緩存的,在重新刷新swagger時,依舊存在卡慢問題。
三、將需返回json數據
優化swagger加載,需要同時考慮到前端渲染頁面以及后端梳理json數據所導致的頁面加載慢問題。有什么好的辦法么?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));
}
}
相對應的修改ISwagger接口,以及接口的實現類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));
自己項目中關于ISwagger實現也要修改,然后開始重新編譯自己的項目,重新打開swagger頁面,頁面在后端編譯后第一次打開也非常迅速。默認打開的是Account控制器下的接口,如果切換到其他控制器下的接口只需要在url后加入對應的/Controller
四、修改Swagger頁面
以上我們已經把頁面的加載慢的問題解決了,但在切換控制器上是否過于麻煩,能不能提升前端開發人員的使用體驗,提供一個下拉列表選擇是不是更好呢?繼續干!
找到源碼目錄下的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)加入填充下拉數據的js代碼以及添加下拉框觸發事件
找到SwaggerUi.Views.HeaderView,添加下拉事件
重新編譯后,刷新頁面試試效果,可以下拉選擇分組
結語
原文鏈接:https://www.cnblogs.com/yuluowuhun/p/16155150.html
相關推薦
- 2022-12-25 sql中的if和else使用及說明_MsSql
- 2022-10-14 wget -c 斷點續傳命令
- 2022-07-21 React樣式沖突問題
- 2022-05-11 C++程序代碼優化的方法實例大全_C 語言
- 2022-05-29 利用Python將list列表寫入文件并讀取的方法匯總_python
- 2022-11-23 Python中列表的基本操作匯總_python
- 2023-12-19 Nacos修改服務實例權重時報錯caused: errCode: 500, errMsg: do m
- 2022-04-25 .NET避免裝箱的方法_實用技巧
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支