feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,228 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Win32;
using PCL.Core.App.IoC;
using PCL.Core.IO.Net;
using PCL.Core.IO.Net.Dns;
using PCL.Core.Logging;
using PCL.Core.Utils.OS;
using STUN.Client;
using Sentry;
using Sentry.Extensibility;
namespace PCL.Core.App.Essentials;
[LifecycleScope("telemetry", "遥测")]
[LifecycleService(LifecycleState.Running)]
public sealed partial class TelemetryService
{
private static void _InitSentry()
{
Context.Info("开始初始化 Sentry SDK");
var dsn = EnvironmentInterop.GetSecret("SENTRY_DSN");
if (dsn is null)
{
Context.Warn("未找到 Sentry DSN");
return;
}
var release = $"{Basics.VersionName}";
#if DEBUG
var environment = "Debug";
#else
var environment = "Production";
#endif
SentrySdk.Init(options =>
{
options.Dsn = dsn;
#if DEBUG
options.Debug = true;
#else
options.Debug = false;
#endif
options.SendDefaultPii = false;
options.IsGlobalModeEnabled = true;
options.AutoSessionTracking = true;
options.Release = release;
options.Environment = environment;
// 应该被直接丢弃不上报的异常类型
options.AddExceptionFilterForType<TimeoutException>();
options.AddExceptionFilterForType<HttpRequestException>();
options.AddExceptionFilterForType<WebException>();
options.AddExceptionFilterForType<TaskCanceledException>();
options.AddExceptionFilterForType<DirectoryNotFoundException>();
options.AddExceptionFilterForType<UnauthorizedAccessException>();
options.AddExceptionFilterForType<FileNotFoundException>();
// 细分类型的过滤器
options.AddExceptionFilter(new SocketExceptionFilter());
options.SetBeforeSend(@event => @event.Level is SentryLevel.Debug ? null : @event);
});
SentrySdk.ConfigureScope(scope =>
{
scope.User = new SentryUser
{
Id = Utils.Secret.Identify.LauncherId
};
});
Context.Info("Sentry SDK 初始化完成");
}
// 错误上报
public static void ReportException(Exception ex, string plain, LogLevel level)
{
var sentryEvent = new SentryEvent(ex)
{
Level = level.RealLevel() switch
{
LogLevel.Fatal => SentryLevel.Fatal,
LogLevel.Error => SentryLevel.Error,
LogLevel.Warning => SentryLevel.Warning,
LogLevel.Info => SentryLevel.Info,
LogLevel.Debug or LogLevel.Trace => SentryLevel.Debug,
_ => throw new ArgumentOutOfRangeException(nameof(level))
}
};
if (!string.IsNullOrWhiteSpace(plain))
{
sentryEvent.Message = new SentryMessage { Formatted = plain };
}
SentrySdk.CaptureEvent(sentryEvent);
}
// 设备环境上报
private static void _ReportDeviceEnvironment(TelemetryDeviceEnvironment content)
{
Context.Info("正在上报设备环境调查数据");
SentrySdk.ConfigureScope(scope =>
{
scope.Contexts["Telemetry"] = content;
});
try
{
SentrySdk.CaptureMessage("设备环境调查");
Context.Info("已发送设备环境调查数据");
}
catch(Exception ex)
{
Context.Error("设备环境调查数据发送失败,请检查网络连接以及使用的版本", ex);
}
}
// ReSharper disable UnusedAutoPropertyAccessor.Local
private class TelemetryDeviceEnvironment
{
public required string Tag { get; set; }
public required string Id { get; set; }
[JsonPropertyName("OS")] public required int Os { get; set; }
public required bool Is64Bit { get; set; }
[JsonPropertyName("IsARM64")] public required bool IsArm64 { get; set; }
public required string Launcher { get; set; }
public required string LauncherBranch {get; set; }
[JsonPropertyName("UsedOfficialPCL")] public required bool UsedOfficialPcl { get; set; }
[JsonPropertyName("UsedHMCL")] public required bool UsedHmcl { get; set; }
[JsonPropertyName("UsedBakaXL")] public required bool UsedBakaXl { get; set; }
public required ulong Memory { get; set; }
public required string? NatMapBehaviour { get; set; }
public required string? NatFilterBehaviour { get; set; }
[JsonPropertyName("IPv6Status")] public required string Ipv6Status { get; set; }
}
// ReSharper disable once InconsistentNaming
private const string STUN_SERVER_ADDR = "stun.miwifi.com";
// ReSharper restore UnusedAutoPropertyAccessor.Local
[LifecycleStart]
private static async Task _StartAsync()
{
if (!Config.System.Telemetry) return;
_InitSentry();
var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
// stun test
StunClient5389UDP? natTest = null;
var miWifiIps = await DnsQuery.Instance.QueryForIpAsync(STUN_SERVER_ADDR).ConfigureAwait(false);
try
{
miWifiIps ??= await Dns.GetHostAddressesAsync(STUN_SERVER_ADDR).ConfigureAwait(false);
} catch(Exception) { /* Ignore dns error */ }
if (miWifiIps is not null && miWifiIps.Length != 0)
{
natTest = new StunClient5389UDP(new IPEndPoint(miWifiIps.First(), 3478),
new IPEndPoint(IPAddress.Any, 0));
await natTest.QueryAsync().ConfigureAwait(false);
}
var telemetry = new TelemetryDeviceEnvironment
{
Tag = "Telemetry",
Id = Utils.Secret.Identify.LauncherId,
Os = Environment.OSVersion.Version.Build,
Is64Bit = Environment.Is64BitOperatingSystem,
IsArm64 = RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64),
Launcher = Basics.VersionName,
LauncherBranch = Config.Update.UpdateChannel switch
{
UpdateChannel.Release => "Release",
UpdateChannel.Beta => "Beta",
UpdateChannel.Dev => "Dev",
_ => "Unknown"
},
UsedOfficialPcl =
bool.TryParse(Registry.GetValue(@"HKEY_CURRENT_USER\Software\PCL", "SystemEula", "false") as string,
out var officialPcl) && officialPcl,
UsedHmcl = Directory.Exists(Path.Combine(appDataFolder, ".hmcl")),
UsedBakaXl = Directory.Exists(Path.Combine(appDataFolder, "BakaXL")),
Memory = KernelInterop.GetPhysicalMemoryBytes().Total,
NatMapBehaviour = natTest?.State.MappingBehavior.ToString(),
NatFilterBehaviour = natTest?.State.FilteringBehavior.ToString(),
Ipv6Status = NetworkInterfaceUtils.GetIPv6Status().ToString()
};
_ReportDeviceEnvironment(telemetry);
}
// 用来细分过滤 SocketException 的过滤器,我觉得应该除了遥测服务之外没有其他东西会用到这破玩意儿
private sealed class SocketExceptionFilter : IExceptionFilter
{
public bool Filter(Exception ex)
{
if (ex is SocketException socketEx)
{
return socketEx.SocketErrorCode is
SocketError.ConnectionRefused or
SocketError.TimedOut or
SocketError.HostNotFound or
SocketError.NetworkUnreachable or
SocketError.ConnectionReset;
}
return false;
}
}
[LifecycleStop]
private static void _StopAsync()
{
SentrySdk.Close();
}
}