Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: .NET Core Build

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
include:
- rid: linux-x64
profile: linux-x64
- rid: linux-arm64
profile: linux-arm64
- rid: win-x64
profile: win-x64
- rid: win-x86
profile: win-x86
- rid: win-arm64
profile: win-arm64

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

- name: Restore dependencies
run: dotnet restore sharwapi.Core.csproj

- name: Publish
run: dotnet publish sharwapi.Core.csproj /p:PublishProfile=Properties/PublishProfiles/${{ matrix.profile }}.pubxml --configuration Release --output ./publish/${{ matrix.rid }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: sharwapi-core-${{ matrix.rid }}
path: ./publish/${{ matrix.rid }}
85 changes: 85 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Publish

on:
push:
tags: ["v*"]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- rid: linux-x64
profile: linux-x64
- rid: linux-arm64
profile: linux-arm64
- rid: win-x64
profile: win-x64
- rid: win-x86
profile: win-x86
- rid: win-arm64
profile: win-arm64

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

- name: Restore dependencies
run: dotnet restore sharwapi.Core.csproj

- name: Publish
run: dotnet publish sharwapi.Core.csproj /p:PublishProfile=Properties/PublishProfiles/${{ matrix.profile }}.pubxml --configuration Release --output ./publish/${{ matrix.rid }}

- name: Prepare package content
run: |
mkdir -p ./package/${{ matrix.rid }}

if [ -f "./publish/${{ matrix.rid }}/sharwapi.Core" ]; then
cp "./publish/${{ matrix.rid }}/sharwapi.Core" "./package/${{ matrix.rid }}/"
fi

if [ -f "./publish/${{ matrix.rid }}/sharwapi.Core.exe" ]; then
cp "./publish/${{ matrix.rid }}/sharwapi.Core.exe" "./package/${{ matrix.rid }}/"
fi

if [ ! -f "./publish/${{ matrix.rid }}/appsettings.json" ]; then
echo "appsettings.json not found in publish output for ${{ matrix.rid }}"
exit 1
fi
cp "./publish/${{ matrix.rid }}/appsettings.json" "./package/${{ matrix.rid }}/"

- name: Archive artifact
run: tar -czf ${{ matrix.rid }}.tar.gz -C ./package/${{ matrix.rid }} .

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.rid }}
path: ${{ matrix.rid }}.tar.gz

release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
merge-multiple: true

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
files: ./artifacts/*.tar.gz
generate_release_notes: true
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,6 @@ FodyWeavers.xsd

# Custom

appsettings.Development.json
appsettings.Development.json

!Properties/PublishProfiles/*.pubxml
52 changes: 52 additions & 0 deletions Modules/Configuration/AppConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.Extensions.Configuration;

namespace sharwapi.Core.Modules.Configuration;

/// <summary>
/// 应用程序配置构建器
/// 负责构建和管理应用程序的配置信息
/// </summary>
public static class AppConfiguration
{
/// <summary>
/// 构建应用程序配置
/// </summary>
/// <returns>配置构建器实例</returns>
public static IConfigurationBuilder Build()
{
return new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
}

/// <summary>
/// 获取 API 名称
/// </summary>
/// <param name="configuration">应用程序配置</param>
/// <returns>API 名称</returns>
public static string GetApiName(IConfiguration configuration)
{
return configuration.GetValue<string>("ApiInfo:Name") ?? "CoreAPI";
}

/// <summary>
/// 获取 API 版本
/// </summary>
/// <param name="configuration">应用程序配置</param>
/// <returns>API 版本</returns>
public static string GetApiVersion(IConfiguration configuration)
{
return configuration.GetValue<string>("ApiInfo:Version") ?? "0.0.0";
}

/// <summary>
/// 获取路由前缀重写配置值
/// </summary>
/// <param name="configuration">应用程序配置</param>
/// <param name="pluginName">插件名称</param>
/// <returns>路由前缀重写值</returns>
public static string? GetRouteOverride(IConfiguration configuration, string pluginName)
{
return configuration.GetValue<string>($"RouteOverride:{pluginName}");
}
}
127 changes: 127 additions & 0 deletions Modules/Hosting/ApplicationHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;
using sharwapi.Core.Modules.Configuration;
using sharwapi.Core.Modules.Hosting;
using sharwapi.Core.Modules.Logging;
using sharwapi.Core.Modules.Middleware;
using sharwapi.Core.Modules.PluginManagement;
using sharwapi.Core.Modules.Routing;
using sharwapi.Core.Modules.Services;
using sharwapi.Contracts.Core;

namespace sharwapi.Core.Modules.Hosting;

/// <summary>
/// 应用程序主机
/// 负责组装和启动整个应用程序
/// </summary>
public class ApplicationHost
{
private readonly string[] _args;
private IConfiguration _configuration = null!;
private WebApplication _app = null!;
private DateTime _startTime;
private string _apiName = null!;
private string _apiVersion = null!;

/// <summary>
/// 初始化应用程序主机
/// </summary>
/// <param name="args">命令行参数</param>
public ApplicationHost(string[] args)
{
_args = args;
_startTime = DateTime.UtcNow;
}

/// <summary>
/// 构建应用程序
/// </summary>
/// <returns>应用程序主机实例</returns>
public ApplicationHost Build()
{
// 1. 构建配置
_configuration = AppConfiguration.Build().Build();

// 2. 初始化日志
Logger.Initialize(_configuration);
Log.Information("Starting web host");

// 3. 创建 WebApplicationBuilder
var builder = WebApplication.CreateBuilder(_args);

// 4. 配置主机
builder.Host.UseSerilogLogging();
builder.ConfigureHostOptions();

// 5. 获取 API 信息
_apiName = AppConfiguration.GetApiName(_configuration);
_apiVersion = AppConfiguration.GetApiVersion(_configuration);

// 6. 加载插件
var pluginLogger = new SerilogLoggerFactory(Log.Logger).CreateLogger("PluginLoader");
var pluginLoader = new PluginLoader(_configuration, pluginLogger);
var plugins = pluginLoader.LoadPlugins();

// 7. 检查依赖
var dependencyChecker = new PluginDependencyChecker(pluginLogger);
plugins = dependencyChecker.CheckDependencies(plugins);

// 8. 注册服务
// 将插件集合注入到 DI 容器(作为单例),插件实现可从容器中获取此集合
builder.Services.AddSingleton(plugins);

var serviceRegistrar = new PluginServiceRegistrar(builder.Services, pluginLogger);
serviceRegistrar.AddSwaggerServices(_apiName, _apiVersion);
serviceRegistrar.RegisterPluginServices(plugins, _configuration);

// 9. 构建应用
_app = builder.Build();

// 10. 配置中间件
ExceptionHandling.Configure(_app);
MiddlewarePipeline.Configure(_app, plugins);

// 在开发环境中启用 Swagger UI
if (_app.Environment.IsDevelopment())
{
_app.Logger.LogInformation("Enabling Swagger UI (Development Mode)...");
_app.UseSwagger();
_app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", $"{_apiName} {_apiVersion}");
options.RoutePrefix = "swagger";
});
}

// 11. 注册路由
EndpointRegistration.RegisterPluginRoutes(_app, plugins, _configuration);
EndpointRegistration.RegisterRootEndpoint(_app, _apiName, _apiVersion, _startTime);

return this;
}

/// <summary>
/// 运行应用程序
/// </summary>
public void Run()
{
try
{
_app.Run();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
}
33 changes: 33 additions & 0 deletions Modules/Hosting/HostingExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Extensions.Hosting;
using Serilog;

namespace sharwapi.Core.Modules.Hosting;

/// <summary>
/// 主机扩展方法
/// </summary>
public static class HostingExtensions
{
/// <summary>
/// 使用 Serilog 接管系统日志
/// </summary>
/// <param name="hostBuilder">主机构建器</param>
/// <returns>主机构建器</returns>
public static IHostBuilder UseSerilogLogging(this IHostBuilder hostBuilder)
{
return hostBuilder.UseSerilog();
}

/// <summary>
/// 配置主机选项
/// </summary>
/// <param name="builder">Web 应用程序构建器</param>
/// <param name="shutdownTimeoutSeconds">关闭超时时间(秒)</param>
public static void ConfigureHostOptions(this WebApplicationBuilder builder, int shutdownTimeoutSeconds = 30)
{
builder.Services.Configure<HostOptions>(opts =>
{
opts.ShutdownTimeout = TimeSpan.FromSeconds(shutdownTimeoutSeconds);
});
}
}
23 changes: 23 additions & 0 deletions Modules/Logging/Logger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.Extensions.Configuration;
using Serilog;

namespace sharwapi.Core.Modules.Logging;

/// <summary>
/// 日志服务
/// 提供应用程序的日志记录功能,通过 appsettings.json 中的 Serilog 节点进行配置。
/// </summary>
public static class Logger
{
/// <summary>
/// 初始化全局 Serilog 配置。
/// 日志输出目标(Console、File 等)均从 appsettings.json 的 Serilog 节点读取,无需硬编码。
/// </summary>
/// <param name="configuration">应用程序配置对象</param>
public static void Initialize(IConfiguration configuration)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
}
Loading