[feat]新增项目NewLife.Cube.Swagger,打包swagger相关设置

This commit is contained in:
大石头 2024-08-21 14:42:44 +08:00
parent f62d5d4523
commit 1f5fce3c9d
9 changed files with 281 additions and 104 deletions

View File

@ -38,6 +38,7 @@ jobs:
run: |
dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.Cube/NewLife.Cube.csproj
dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.CubeNC/NewLife.CubeNC.csproj
dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.Cube.Swagger/NewLife.Cube.Swagger.csproj
- name: Publish
run: |

View File

@ -24,6 +24,7 @@ jobs:
run: |
dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.Cube/NewLife.Cube.csproj
dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.CubeNC/NewLife.CubeNC.csproj
dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.Cube.Swagger/NewLife.Cube.Swagger.csproj
#dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.Cube.AdminLTE/NewLife.Cube.AdminLTE.csproj
#dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.Cube.ElementUI/NewLife.Cube.ElementUI.csproj
dotnet pack --no-build --version-suffix ${{ env.VERSION }} -c Release -o out NewLife.Cube.LayuiAdmin/NewLife.Cube.LayuiAdmin.csproj

View File

@ -23,12 +23,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
<PackageReference Include="NewLife.Stardust.Extensions" Version="3.0.2024.806" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NewLife.Cube.Swagger\NewLife.Cube.Swagger.csproj" />
<ProjectReference Include="..\NewLife.Cube\NewLife.Cube.csproj" />
</ItemGroup>

View File

@ -6,6 +6,7 @@ using Microsoft.OpenApi.Models;
using NewLife;
using NewLife.Cube;
using NewLife.Cube.Entity;
using NewLife.Cube.Swagger;
using NewLife.Cube.WebMiddleware;
using NewLife.Log;
using Swashbuckle.AspNetCore.SwaggerGen;
@ -23,75 +24,7 @@ TracerMiddleware.Tracer = star?.Tracer;
builder.Services.AddControllers();
var oauthConfigs = OAuthConfig.GetValids(GrantTypes.AuthorizationCode);
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerConfigureOptions>();
builder.Services.AddSwaggerGen(options =>
{
// 解决 NewLife.Setting 与 XCode.Setting 冲突的问题
options.CustomSchemaIds(type => type.FullName);
var xml = "NewLife.Cube.xml".GetFullPath();
if (File.Exists(xml)) options.IncludeXmlComments(xml, true);
options.SwaggerDoc("v1", new OpenApiInfo { Title = "第三代魔方", Description = "第三代魔方WebApi接口用于前后端分离。" });
//options.SwaggerDoc("Basic", new OpenApiInfo { Version = "basic", Title = "基础模块" });
//options.SwaggerDoc("Admin", new OpenApiInfo { Version = "admin", Title = "系统管理" });
//options.SwaggerDoc("Cube", new OpenApiInfo { Version = "cube", Title = "魔方管理" });
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (apiDesc.ActionDescriptor is not ControllerActionDescriptor controller) return false;
var groups = controller.ControllerTypeInfo.GetCustomAttributes(true).OfType<IApiDescriptionGroupNameProvider>().Select(e => e.GroupName).ToList();
if (docName == "v1" && (groups == null || groups.Count == 0)) return true;
return groups != null && groups.Any(e => e == docName);
});
if (oauthConfigs.Count > 0)
{
var cfg = oauthConfigs[0];
var flow = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(cfg.Server),
TokenUrl = new Uri(!cfg.AccessServer.IsNullOrEmpty() ? cfg.AccessServer : cfg.Server),
//Scopes = new Dictionary<String, String>
//{
// { "api1", "Access to API #1" }
//}
};
options.AddSecurityDefinition("OAuth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows { AuthorizationCode = flow }
});
//options.OperationFilter<AuthorizeCheckOperationFilter>();
}
else
{
// 定义JwtBearer认证方式
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
{
Description = "输入登录成功后取得的令牌",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
// 声明一个Scheme注意下面的Id要和上面AddSecurityDefinition中的参数name一致
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
// 注册全局认证(所有的接口都可以使用认证)
options.AddSecurityRequirement(new OpenApiSecurityRequirement() { [scheme] = [] });
}
});
services.AddCubeSwagger();
services.AddCube();
@ -100,39 +33,7 @@ var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
{
app.UseSwagger();
//app.UseSwaggerUI();
app.UseSwaggerUI(options =>
{
//options.SwaggerEndpoint("/swagger/Basic/swagger.json", "Basic");
//options.SwaggerEndpoint("/swagger/Admin/swagger.json", "Admin");
//options.SwaggerEndpoint("/swagger/Cube/swagger.json", "Cube");
//options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
// 设置路由前缀为空直接访问站点根目录即可看到SwaggerUI
options.RoutePrefix = String.Empty;
var groups = app.Services.GetRequiredService<IApiDescriptionGroupCollectionProvider>().ApiDescriptionGroups.Items;
foreach (var description in groups)
{
var group = description.GroupName;
if (group.IsNullOrEmpty()) group = "v1";
options.SwaggerEndpoint($"/swagger/{group}/swagger.json", group);
}
// 设置OAuth2认证
if (oauthConfigs.Count > 0)
{
var cfg = oauthConfigs[0];
//options.OAuthConfigObject = new()
//{
// AppName = cfg.Name,
// ClientId = cfg.AppId,
// ClientSecret = cfg.Secret,
//};
options.OAuthClientId(cfg.AppId);
options.OAuthClientSecret(cfg.Secret);
if (!cfg.Scope.IsNullOrEmpty()) options.OAuthScopes(cfg.Scope.Split(","));
}
});
app.UseCubeSwagger();
}
app.UseCube(builder.Environment);

View File

@ -0,0 +1,75 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<AssemblyTitle>魔方Swagger包</AssemblyTitle>
<Description>Web快速开发平台搭建管理后台灵活可扩展内部集成了用户权限管理、模板继承、SSO登录、OAuth服务端、数据导出与分享等多个功能模块在真实项目中经历过单表100亿数据添删改查的考验。</Description>
<Company>新生命开发团队</Company>
<Copyright>©2002-2024 NewLife</Copyright>
<VersionPrefix>6.1</VersionPrefix>
<VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
<Version>$(VersionPrefix).$(VersionSuffix)</Version>
<FileVersion>$(Version)</FileVersion>
<AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
<Deterministic>false</Deterministic>
<OutputPath>..\Bin3</OutputPath>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\Doc\newlife.snk</AssemblyOriginatorKeyFile>
<NoWarn>1701;1702;NU5104;NETSDK1138;CS7035</NoWarn>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
</PropertyGroup>
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>NewLife.Cube.Swagger</PackageId>
<Authors>$(Company)</Authors>
<PackageProjectUrl>https://newlifex.com/cube</PackageProjectUrl>
<PackageIcon>leaf.png</PackageIcon>
<RepositoryUrl>https://github.com/NewLifeX/NewLife.Cube</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>新生命团队;X组件;NewLife;$(AssemblyName)</PackageTags>
<PackageReleaseNotes>第三代魔方,前后端分离</PackageReleaseNotes>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageReadmeFile>Readme.MD</PackageReadmeFile>
</PropertyGroup>
<ItemGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="NewLife.Stardust.Extensions" Version="3.0.2024.806" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net7.0'">
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.20" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
</ItemGroup>
<ItemGroup>
<Content Include="..\Doc\leaf.png" Link="leaf.png" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<None Include="..\Readme.MD">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NewLife.Cube\NewLife.Cube.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
{
"profiles": {
"NewLife.Cube.Swagger": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:48767;http://localhost:48768"
}
}
}

View File

@ -0,0 +1,49 @@
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace NewLife.Cube.Swagger;
/// <summary>自动为每个文档分组引入Swagger</summary>
public class SwaggerConfigureOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiDescriptionGroupCollectionProvider provider;
/// <summary>实例化</summary>
/// <param name="provider"></param>
public SwaggerConfigureOptions(IApiDescriptionGroupCollectionProvider provider) => this.provider = provider;
/// <summary>自动配置添加分组文档</summary>
/// <param name="options"></param>
public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiDescriptionGroups.Items)
{
if (description.GroupName.IsNullOrEmpty()) continue;
// 遍历控制器,找到区域读取其描述
OpenApiInfo? info = null;
foreach (var apiDesc in description.Items)
{
if (apiDesc.ActionDescriptor is not ControllerActionDescriptor controller) continue;
var area = controller.ControllerTypeInfo.GetCustomAttribute<AreaAttribute>();
if (area != null)
{
info = new OpenApiInfo
{
Title = area.GetType().GetDisplayName(),
Description = area.GetType().GetDescription()?.Replace("\n", "<br/>")
};
break;
}
}
options.SwaggerDoc(description.GroupName, info);
}
}
}

View File

@ -0,0 +1,132 @@
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using NewLife.Cube.Entity;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace NewLife.Cube.Swagger;
/// <summary>Swagger服务</summary>
public static class SwaggerService
{
/// <summary>添加魔方Swagger服务</summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddCubeSwagger(this IServiceCollection services)
{
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerConfigureOptions>();
services.AddSwaggerGen(options =>
{
// 解决 NewLife.Setting 与 XCode.Setting 冲突的问题
options.CustomSchemaIds(type => type.FullName);
var xml = "NewLife.Cube.xml".GetFullPath();
if (File.Exists(xml)) options.IncludeXmlComments(xml, true);
options.SwaggerDoc("v1", new OpenApiInfo { Title = "第三代魔方", Description = "第三代魔方WebApi接口用于前后端分离。" });
//options.SwaggerDoc("Basic", new OpenApiInfo { Version = "basic", Title = "基础模块" });
//options.SwaggerDoc("Admin", new OpenApiInfo { Version = "admin", Title = "系统管理" });
//options.SwaggerDoc("Cube", new OpenApiInfo { Version = "cube", Title = "魔方管理" });
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (apiDesc.ActionDescriptor is not ControllerActionDescriptor controller) return false;
var groups = controller.ControllerTypeInfo.GetCustomAttributes(true).OfType<IApiDescriptionGroupNameProvider>().Select(e => e.GroupName).ToList();
if (docName == "v1" && (groups == null || groups.Count == 0)) return true;
return groups != null && groups.Any(e => e == docName);
});
var oauthConfigs = OAuthConfig.GetValids(GrantTypes.AuthorizationCode);
if (oauthConfigs.Count > 0)
{
var cfg = oauthConfigs[0];
var flow = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(cfg.Server),
TokenUrl = new Uri(!cfg.AccessServer.IsNullOrEmpty() ? cfg.AccessServer : cfg.Server),
//Scopes = new Dictionary<String, String>
//{
// { "api1", "Access to API #1" }
//}
};
options.AddSecurityDefinition("OAuth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows { AuthorizationCode = flow }
});
//options.OperationFilter<AuthorizeCheckOperationFilter>();
}
else
{
// 定义JwtBearer认证方式
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
{
Description = "输入登录成功后取得的令牌",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
// 声明一个Scheme注意下面的Id要和上面AddSecurityDefinition中的参数name一致
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
// 注册全局认证(所有的接口都可以使用认证)
options.AddSecurityRequirement(new OpenApiSecurityRequirement() { [scheme] = [] });
}
});
return services;
}
/// <summary>使用魔方Swagger服务</summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseCubeSwagger(this IApplicationBuilder app)
{
app.UseSwagger();
//app.UseSwaggerUI();
app.UseSwaggerUI(options =>
{
//options.SwaggerEndpoint("/swagger/Basic/swagger.json", "Basic");
//options.SwaggerEndpoint("/swagger/Admin/swagger.json", "Admin");
//options.SwaggerEndpoint("/swagger/Cube/swagger.json", "Cube");
//options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
// 设置路由前缀为空直接访问站点根目录即可看到SwaggerUI
options.RoutePrefix = String.Empty;
var groups = app.ApplicationServices.GetRequiredService<IApiDescriptionGroupCollectionProvider>().ApiDescriptionGroups.Items;
foreach (var description in groups)
{
var group = description.GroupName;
if (group.IsNullOrEmpty()) group = "v1";
options.SwaggerEndpoint($"/swagger/{group}/swagger.json", group);
}
// 设置OAuth2认证
var oauthConfigs = OAuthConfig.GetValids(GrantTypes.AuthorizationCode);
if (oauthConfigs.Count > 0)
{
var cfg = oauthConfigs[0];
//options.OAuthConfigObject = new()
//{
// AppName = cfg.Name,
// ClientId = cfg.AppId,
// ClientSecret = cfg.Secret,
//};
options.OAuthClientId(cfg.AppId);
options.OAuthClientSecret(cfg.Secret);
if (!cfg.Scope.IsNullOrEmpty()) options.OAuthScopes(cfg.Scope.Split(","));
}
});
return app;
}
}

View File

@ -49,6 +49,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebApi版", "WebApi版", "{
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "测试", "测试", "{12E138A8-D4DF-4CAD-8205-897D3A7AE95C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewLife.Cube.Swagger", "NewLife.Cube.Swagger\NewLife.Cube.Swagger.csproj", "{1C23DA32-4108-4E9F-BB6C-FA61378C7E38}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -111,6 +113,10 @@ Global
{D800A693-B55C-40F3-B80E-C0BE2EC3E79C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D800A693-B55C-40F3-B80E-C0BE2EC3E79C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D800A693-B55C-40F3-B80E-C0BE2EC3E79C}.Release|Any CPU.Build.0 = Release|Any CPU
{1C23DA32-4108-4E9F-BB6C-FA61378C7E38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C23DA32-4108-4E9F-BB6C-FA61378C7E38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C23DA32-4108-4E9F-BB6C-FA61378C7E38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C23DA32-4108-4E9F-BB6C-FA61378C7E38}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -130,6 +136,7 @@ Global
{0A1E77F0-7811-4188-A9B6-0F5FAFB76420} = {844550DD-07A5-44C9-A4E9-6FE64FA62CF9}
{6BE97641-E557-42BB-810A-6CBFE9CB28AD} = {88104F4C-A44C-493A-8DE7-A1A5A09C754F}
{D800A693-B55C-40F3-B80E-C0BE2EC3E79C} = {88104F4C-A44C-493A-8DE7-A1A5A09C754F}
{1C23DA32-4108-4E9F-BB6C-FA61378C7E38} = {88104F4C-A44C-493A-8DE7-A1A5A09C754F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6AEA440B-0D90-4F6E-BA9A-D42B27A78D7B}