优化启动参数解析方式

This commit is contained in:
root 2024-12-28 00:31:48 +08:00
parent 5090f7e7a6
commit 9743cf1460
15 changed files with 320 additions and 65 deletions

View file

@ -0,0 +1,26 @@
namespace iFileProxy.Attributes
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class CommandLineArgumentAttribute : Attribute
{
/// <summary>
/// 长参数名 如: --dev-logger
/// </summary>
public string FullArgumentName { get; set; } = string.Empty;
/// <summary>
/// 短参数名 如: -d
/// </summary>
public string ShortArgumentName { get; set; } = new string(' ',4);
/// <summary>
/// 参数帮助文本
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 是否需要参数值
/// </summary>
public bool NeedValue { get; set; } = false;
}
}

View file

@ -1,36 +0,0 @@
using iFileProxy.Config;
using iFileProxy.Helpers;
using iFileProxy.Services;
namespace iFileProxy.Handlers
{
public class CmdArgsHandler
{
public static void ParseArgs(string[] args, IServiceProvider serviceProvider)
{
if (args.Length == 0)
return;
Dictionary<string, string> helpStrings = [];
helpStrings.Add("--init-db", "初始化数据库");
helpStrings.Add("--dev-logger", "启用开发者logger");
helpStrings.Add("--disable-startup-check", "禁用启动时程序配置自检");
helpStrings.Add("--url=<Url>", "设置程序app.run() Url 如: http://0.0.0.0:1145");
if (args.Contains("-h") || args.Contains("--help"))
{
Console.WriteLine("命令行参数帮助: ");
foreach (var hs in helpStrings)
{
Console.WriteLine($"\t{hs.Key}\t{hs.Value}");
}
Environment.Exit(0);
}
CommandLineArgsHelper cmdlineHelper = new(args);
if (cmdlineHelper.GetBooleanValue("--init-db"))
{
serviceProvider.GetRequiredService<DatabaseGateService>().TryInitialDB();
}
}
}
}

View file

@ -0,0 +1,15 @@
using iFileProxy.Attributes;
using iFileProxy.Interfaces;
namespace iFileProxy.Handlers.CommandLine
{
[CommandLineArgument(FullArgumentName = "--bind-url", ShortArgumentName = "-B", Description = "自定义绑定URL", NeedValue = true)]
public class CustomBindUrl(string[] args) : ICommandLineHandler
{
public void Process()
{
Console.WriteLine("当前正使用自定义URL绑定");
// 具体的实现在Program.cs中 此处仅作提示
}
}
}

View file

@ -0,0 +1,15 @@
using iFileProxy.Attributes;
using iFileProxy.Interfaces;
namespace iFileProxy.Handlers.CommandLine
{
[CommandLineArgument(FullArgumentName = "--disable-startup-check", ShortArgumentName = "-D", Description = "禁用配置文件启动前校验")]
public class DisablePreStartupCheck(string[] args) : ICommandLineHandler
{
public void Process()
{
Console.WriteLine("已禁用启动前配置文件校验");
// 具体的实现在Program.cs中 此处仅作提示
}
}
}

View file

@ -0,0 +1,15 @@
using iFileProxy.Attributes;
using iFileProxy.Interfaces;
namespace iFileProxy.Handlers.CommandLine
{
[CommandLineArgument(FullArgumentName = "--dev-logging", ShortArgumentName = "-d", Description = "启用dev级别日志记录")]
public class EnableDevLogger(string[] args) : ICommandLineHandler
{
public void Process()
{
Console.WriteLine("developer logging is enabled.");
// 具体的实现在SerilogConfig.cs中 此处仅作提示
}
}
}

View file

@ -0,0 +1,39 @@
using iFileProxy.Attributes;
using iFileProxy.Helpers;
using iFileProxy.Interfaces;
using iFileProxy.Services;
using System.Reflection;
namespace iFileProxy.Handlers.CommandLine
{
[CommandLineArgument(FullArgumentName ="--help",ShortArgumentName = "-h", Description = "显示帮助文本")]
public class HelpHandler : ICommandLineHandler
{
string[] _args = [];
public HelpHandler(string[] args) {
_args = args;
}
public void Process()
{
Dictionary<string,string> helpText = CommandLineArgumentDispatcher.GetHelpTextDict();
// 找到最长的键长度
int maxKeyLength = helpText.Keys.Max(key => key.Length);
// 打印表头
Console.WriteLine("参数(短参数, 长参数)\t解释");
// 打印每一行的帮助信息,确保对齐
foreach (var ht in helpText)
{
Console.WriteLine($" {ht.Key}{new string(' ', maxKeyLength - ht.Key.Length)}\t{ht.Value}");
}
Console.WriteLine(new string('\n',2));
Console.WriteLine($"程序编译日期(UTC+8): {MasterHelper.GetBuildTime(Assembly.GetExecutingAssembly())}");
Console.WriteLine();
Environment.Exit(0 );
}
}
}

View file

@ -0,0 +1,21 @@
using iFileProxy.Attributes;
using iFileProxy.Interfaces;
using iFileProxy.Services;
namespace iFileProxy.Handlers.CommandLine
{
[CommandLineArgument(FullArgumentName = "--init-db", ShortArgumentName = "-i", Description = "初始化数据库表结构")]
public class InitDBHandler : ICommandLineHandler
{
string[] _cmdArgs = [];
public InitDBHandler(string[] args) {
_cmdArgs = args;
}
public void Process()
{
DatabaseGateService gateService = new(new AppConfigService());
gateService.TryInitialDB();
Environment.Exit(0);
}
}
}

View file

@ -0,0 +1,90 @@
using iFileProxy.Attributes;
using iFileProxy.Config;
using iFileProxy.Helpers;
using iFileProxy.Interfaces;
using Serilog;
using System.Reflection;
namespace iFileProxy.Handlers
{
/// <summary>
/// 命令行参数调度器
/// </summary>
public class CommandLineArgumentDispatcher
{
private readonly static Serilog.ILogger _logger = Log.Logger.ForContext<CommandLineArgumentDispatcher>();
// 获取当前执行的程序集
static Assembly assembly = Assembly.GetExecutingAssembly();
// 获取程序集中所有的类型
static Type[] types = [];
public CommandLineArgumentDispatcher()
{
LoadAssemblyData();
}
static void LoadAssemblyData()
{
assembly = Assembly.GetExecutingAssembly();
types = assembly.GetTypes();
}
public static void Parse(string[] args)
{
LoadAssemblyData();
// 遍历所有类型,查找带有 CommandLineArgumentAttribute 的类
foreach (Type type in types)
{
// 检查类型是否具有 CommandLineArgumentAttribute
var attribute = type.GetCustomAttribute<CommandLineArgumentAttribute>();
if (attribute != null && (args.Contains(attribute.FullArgumentName) || args.Contains(attribute.ShortArgumentName)))
{
var commandLineHandler = (ICommandLineHandler?)Activator.CreateInstance(type, [args]);
commandLineHandler?.Process();
}
}
}
public static Dictionary<string, string> GetHelpTextDict()
{
static string GetHelpText(CommandLineArgumentAttribute attribute)
{
if (attribute.NeedValue && attribute.FullArgumentName.Trim() == string.Empty) // 需要值且没有长参数
{
return $"{attribute.ShortArgumentName}=<value>";
}
else if (attribute.NeedValue && attribute.ShortArgumentName.Trim() == string.Empty)// 需要值且没有短参数
{
return $"{attribute.ShortArgumentName} {attribute.FullArgumentName}=<value>";
}
else if (attribute.NeedValue && attribute.FullArgumentName.Trim() != "" && attribute.ShortArgumentName.Trim() != "") // 都需要
{
return $"{attribute.ShortArgumentName}=<value>{attribute.FullArgumentName}=<value>";
}
else if (attribute.FullArgumentName.Trim() == "" && attribute.ShortArgumentName.Trim() == "") // 都没有
{
throw new ArgumentException("该参数长短参数名均为空 请联系软件开发者反馈");
}
else if (!attribute.NeedValue && attribute.FullArgumentName.Trim() == string.Empty)
return attribute.ShortArgumentName ;
else if (!attribute.NeedValue && attribute.ShortArgumentName.Trim() == string.Empty)
return $"{attribute.ShortArgumentName} {attribute.FullArgumentName}";
else
return $"{attribute.ShortArgumentName}{attribute.FullArgumentName}";
}
Dictionary<string, string> r = [];
foreach (var handler in types)
{
var h = handler.GetCustomAttribute<CommandLineArgumentAttribute>();
if (h != null)
{
r.Add(GetHelpText(h), h.Description);
}
}
return r;
}
}
}

View file

@ -23,13 +23,13 @@ namespace iFileProxy.Helpers
var parts = arg.Split(['='], 2);
if (parts.Length == 2)
{
parameters[parts[0].TrimStart('-')] = parts[1];
parameters[parts[0]] = parts[1];
}
}
else
{
// 如果参数是没有值的标志性参数(比如 --dev-logger
parameters[arg.TrimStart('-')] = "true";
parameters[arg] = "true";
}
}
@ -39,12 +39,34 @@ namespace iFileProxy.Helpers
// 获取指定参数的值若无值则返回null
public string? GetStringValue(string key)
{
return _parameters.ContainsKey(key) ? _parameters[key] : null;
// 如果 key 包含多个等效键(用 | 分隔),则拆分为数组
string[] keys = key.Contains("|")
? key.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
: [key];
foreach (var k in keys)
{
if (_parameters.TryGetValue(k, out string? value))
{
return value;
}
}
// 如果没有任何键匹配,默认返回 null
return null;
}
// 获取指定参数的布尔值,若参数值是 "true" 则返回true否则返回false
public bool GetBooleanValue(string key)
{
string[] args = [];
if (key.Contains("|"))
args = key.Split('|',StringSplitOptions.RemoveEmptyEntries|StringSplitOptions.TrimEntries);
foreach (var arg in args)
{
if (_parameters.ContainsKey(arg) && _parameters[arg].Equals("true", StringComparison.CurrentCultureIgnoreCase))
return true;
}
return _parameters.ContainsKey(key) && _parameters[key].Equals("true", StringComparison.CurrentCultureIgnoreCase);
}
}

View file

@ -1,7 +1,9 @@
using iFileProxy.Config;
using iFileProxy.Models;
using Serilog;
using System.Globalization;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
@ -228,5 +230,25 @@ namespace iFileProxy.Helpers
return exp1.IsMatch(url) || exp2.IsMatch(url) || exp3.IsMatch(url) || exp4.IsMatch(url) || exp5.IsMatch(url);
}
// copy from https://summerlight.com/archives/184
public static DateTime GetBuildTime(Assembly assembly)
{
const string buildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(buildVersionMetadataPrefix, StringComparison.Ordinal);
if (index > 0)
{
value = value[(index + buildVersionMetadataPrefix.Length)..];
if (DateTime.TryParseExact(value, "yyyyMMddHHmmssZ", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
return result;
}
}
return default;
}
}
}

View file

@ -0,0 +1,9 @@
using iFileProxy.Services;
namespace iFileProxy.Interfaces
{
public interface ICommandLineHandler
{
public void Process();
}
}

View file

@ -8,6 +8,7 @@ using Serilog;
using System.Text;
using MySql.Data.MySqlClient;
using iFileProxy.Middlewares;
using System.Reflection;
namespace iFileProxy
{
@ -23,9 +24,24 @@ namespace iFileProxy
CommandLineArgsHelper argsHelper = new(args);
// 解析命令行参数
CommandLineArgumentDispatcher.Parse(args);
if (argsHelper.GetBooleanValue("--version") || argsHelper.GetBooleanValue("-v"))
{
var buildTime = MasterHelper.GetBuildTime(Assembly.GetEntryAssembly());
StringBuilder sb = new StringBuilder();
sb.AppendLine(new string('=', 10));
sb.AppendLine($"Build DateTime:\t{buildTime}");
sb.AppendLine(new string('=', 10));
Console.WriteLine(sb.ToString());
Environment.Exit(0);
}
Console.Write(" "); // 补全日志第一行开头的空白
var builder = WebApplication.CreateBuilder(args);
var builder = WebApplication.CreateBuilder();
// CORS配置
builder.Services.AddCors(options =>
@ -81,14 +97,11 @@ namespace iFileProxy
// 初始化缓存管理服务
LocalCacheManager localCacheManager = new(app.Services);
// 解析命令行参数
CmdArgsHandler.ParseArgs(args, app.Services);
// 开启配置热重载
app.Services.GetRequiredService<AppConfigService>().EnableHotReload();
if (!argsHelper.GetBooleanValue("disable-startup-check"))
if (!argsHelper.GetBooleanValue("--disable-startup-check|-D"))
// 初始化验证配置文件
AppConfig.CheckAppConfig(app.Services);
@ -141,8 +154,8 @@ namespace iFileProxy
var dbGateService = app.Services.GetRequiredService<DatabaseGateService>();
SerilogConfig.CreateLogger(dbGateService, args);
if (!argsHelper.GetStringValue("url").IsNullOrEmpty())
app.Run(argsHelper.GetStringValue("url"));
if (!argsHelper.GetStringValue("--bind-url|-B").IsNullOrEmpty())
app.Run(argsHelper.GetStringValue("--bind-url|-B"));
else
app.Run();
}

View file

@ -15,7 +15,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<_TargetId>Folder</_TargetId>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ProjectGuid>e343bd8a-27ed-47e2-b50d-e3000730e65e</ProjectGuid>
<SelfContained>false</SelfContained>
<PublishSingleFile>true</PublishSingleFile>

View file

@ -26,7 +26,7 @@
var loggerConfiguration = new LoggerConfiguration();
if (args != null && new CommandLineArgsHelper(args).GetBooleanValue("dev-logger"))
if (args != null && new CommandLineArgsHelper(args).GetBooleanValue("--dev-logging|-d"))
{
loggerConfiguration.MinimumLevel.Debug();
}

View file

@ -1,22 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySql.Data" Version="9.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
</ItemGroup>
<PropertyGroup>
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmssZ"))</SourceRevisionId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySql.Data" Version="9.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
</ItemGroup>
</Project>