CI / Go Backend (push) Canceled after 0s
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
160 lines
6.3 KiB
C#
160 lines
6.3 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using PCL.Core.App.Cli;
|
|
using PCL.Core.App.IoC;
|
|
using PCL.Core.Utils.OS;
|
|
using PCL.Core.Utils.Secret;
|
|
using PCL.Core.Utils;
|
|
|
|
namespace PCL.Core.App.Essentials;
|
|
|
|
/// <summary>
|
|
/// 命令处理委托
|
|
/// </summary>
|
|
/// <param name="model">命令模型</param>
|
|
/// <param name="isCallback">
|
|
/// 指示该委托是否由已注册的回调触发,若是,则代表可能由 RPC
|
|
/// 或用户后续操作而非当前进程的命令行参数触发,应注意鉴权问题
|
|
/// </param>
|
|
public delegate void CommandHandler(CommandLine model, bool isCallback);
|
|
|
|
[LifecycleService(LifecycleState.BeforeLoading, Priority = int.MaxValue)]
|
|
[LifecycleScope("startup", "基本信息", false)]
|
|
public sealed partial class StartupService
|
|
{
|
|
private static Exception _GetUninitializedException() => new InvalidOperationException("Not initialized");
|
|
|
|
/// <summary>
|
|
/// 解析后的命令行模型实例
|
|
/// </summary>
|
|
/// <exception cref="Exception">尚未初始化完成</exception>
|
|
public static CommandLine CommandLine
|
|
{
|
|
get => field ?? throw _GetUninitializedException();
|
|
private set;
|
|
} = null!;
|
|
|
|
private static readonly Dictionary<string, CommandLine> _UnhandledCommandMap = [];
|
|
private static readonly ConcurrentDictionary<string, CommandHandler> _HandleCallbackMap = [];
|
|
|
|
/// <summary>
|
|
/// 未处理的子命令
|
|
/// </summary>
|
|
public static IReadOnlyDictionary<string, CommandLine> UnhandledCommands => _UnhandledCommandMap.AsReadOnly();
|
|
|
|
/// <summary>
|
|
/// 处理一个子命令
|
|
/// </summary>
|
|
/// <param name="command">子命令</param>
|
|
/// <param name="handler">用于处理命令的委托,传入 <see langword="null"/> 则触发已注册的处理回调</param>
|
|
/// <param name="registerCallback">指定是否注册该委托为处理回调</param>
|
|
/// <returns>是否执行成功,若子命令不存在或未注册任何处理回调则不成功</returns>
|
|
public static bool TryHandleCommand(
|
|
string command,
|
|
CommandHandler? handler = null,
|
|
bool registerCallback = false)
|
|
{
|
|
var isCallback = false;
|
|
if (handler is null)
|
|
{
|
|
_HandleCallbackMap.TryGetValue(command, out handler);
|
|
if (handler is null) return false;
|
|
isCallback = true;
|
|
}
|
|
else if (registerCallback) _HandleCallbackMap.TryAdd(command, handler);
|
|
lock (_UnhandledCommandMap)
|
|
{
|
|
_UnhandledCommandMap.TryGetValue(command, out var model);
|
|
if (model is null) return false;
|
|
// remove all related commands
|
|
foreach (var x in _UnhandledCommandMap.Keys.Where(x => x.StartsWith(command)).ToList())
|
|
_UnhandledCommandMap.Remove(x);
|
|
// run handler
|
|
try { handler(model, isCallback); }
|
|
catch (Exception ex) { Context.Warn($"Exception thrown while handle command: {command}", ex); }
|
|
return true;
|
|
}
|
|
}
|
|
|
|
[LifecycleStart]
|
|
private static void _LogBasicInfo()
|
|
{
|
|
var info = new StringBuilder();
|
|
info.Append("\n版本: ").Append(Basics.Metadata.Version).Append(" (").Append(GetArchitectureName(RuntimeInformation.ProcessArchitecture)).Append(')');
|
|
info.Append("\n路径: ").Append(Basics.ExecutablePath);
|
|
info.Append("\n命令行参数:");
|
|
if (Basics.CommandLineArguments.Length == 0) info.Append(" []");
|
|
else foreach (var x in Basics.CommandLineArguments) info.Append("\n - ").Append(x);
|
|
info.Append("\n系统版本: ").Append(Environment.OSVersion.Version).Append(" (").Append(GetArchitectureName(RuntimeInformation.OSArchitecture)).Append(')');
|
|
var memory = KernelInterop.GetPhysicalMemoryBytes();
|
|
const int memoryDiv = 1024 * 1024;
|
|
info.Append("\n可用内存: ").Append(memory.Available / memoryDiv).Append('/').Append(memory.Total / memoryDiv).Append(" MiB");
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
var cp = Encoding.GetEncoding(0);
|
|
info.Append("\n默认代码页: ").Append(cp.EncodingName).Append(" (").Append(cp.CodePage).Append(')');
|
|
info.Append("\n管理员身份: ").Append(ProcessInterop.IsAdmin());
|
|
info.Append("\n识别码: ").Append(Identify.LauncherId);
|
|
Context.Info(info.ToString());
|
|
return;
|
|
string GetArchitectureName(Architecture arch) => arch switch
|
|
{
|
|
Architecture.X64 => "x64",
|
|
Architecture.Arm64 => "ARM64",
|
|
_ => arch.ToString()
|
|
};
|
|
}
|
|
|
|
[LifecycleStart]
|
|
private static void _ParseCommandLineArgs()
|
|
{
|
|
IEnumerable<SubcommandDefinition> subcommands = [
|
|
("update", [("execute"), ("success"), ("failed")]),
|
|
("activate", []),
|
|
("promote", []),
|
|
];
|
|
Context.Debug("正在解析命令行参数...");
|
|
var c = CommandLine.Parse(Basics.FullCommandLineArguments, subcommands);
|
|
var prefix = new StringBuilder();
|
|
while (true)
|
|
{
|
|
_UnhandledCommandMap[prefix.ToString()] = c;
|
|
if (c.Subcommand is null) break;
|
|
if (prefix.Length != 0) prefix.Append('.');
|
|
prefix.Append(c.Subcommand.CommandText);
|
|
c = c.Subcommand;
|
|
}
|
|
_UnhandledCommandMap.Remove("", out c!);
|
|
CommandLine = c;
|
|
}
|
|
|
|
[RegisterRpc("cli")]
|
|
public static RpcResponse OnRpcCommand(string? argument, string? content, bool indent)
|
|
{
|
|
if (content is null) return RpcResponse.Err("Must provide valid JSON model");
|
|
try
|
|
{
|
|
var models = JsonSerializer.Deserialize<Dictionary<string, CommandLine>>(content, JsonCompat.SerializerOptions);
|
|
if (models is null) return RpcResponse.Err("Invalid JSON: empty/null content");
|
|
Task.Run(() =>
|
|
{
|
|
foreach (var (command, model) in models)
|
|
{
|
|
_UnhandledCommandMap[command] = model;
|
|
TryHandleCommand(command);
|
|
}
|
|
});
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
return RpcResponse.Err($"Invalid JSON: {ex.Message}");
|
|
}
|
|
return RpcResponse.EmptySuccess;
|
|
}
|
|
}
|