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,193 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.NetworkInformation;
using System.Text;
using Downloader;
using System.Text.Json.Nodes;
using PCL.Core.IO.Net;
using PCL.Core.IO.Net.Http;
namespace PCL.Network;
public static class Requester
{
public static void EnsureSuccess(HttpResponseMessage? response)
{
if (!response?.IsSuccessStatusCode ?? true)
throw new HttpResponseException(response);
}
public static async Task<string> FetchStringAsync(string url, RequestParam param = default)
{
return await FetchAsync(url, new FetchParam
{
Method = "GET",
Accept = param.Accept,
FallbackUrl = param.FallbackUrl,
UseBrowserUserAgent = param.UseBrowserUserAgent,
Timeout = param.Timeout,
Encoding = param.Encoding,
MakeLog = true
}, Math.Max(1, param.Retries == 0 ? 1 : param.Retries)).ConfigureAwait(false);
}
public static string FetchString(string url, RequestParam param = default)
{
return FetchStringAsync(url, param).GetAwaiter().GetResult();
}
public static async Task<JsonNode> FetchJsonAsync(string url, RequestParam param = default)
{
return ModBase.GetJson(await FetchStringAsync(url, param).ConfigureAwait(false));
}
public static async Task<T> FetchJsonAsync<T>(string url, RequestParam param = default) where T : JsonNode
{
return (T)await FetchJsonAsync(url, param).ConfigureAwait(false);
}
public static JsonNode FetchJson(string url, RequestParam param = default)
{
return FetchJsonAsync(url, param).GetAwaiter().GetResult();
}
public static T FetchJson<T>(string url, RequestParam param = default) where T : JsonNode
{
return FetchJsonAsync<T>(url, param).GetAwaiter().GetResult();
}
public static async Task<string> FetchAsync(string url, FetchParam param)
{
return await FetchAsync(url, param, 3).ConfigureAwait(false);
}
public static string Fetch(string url, FetchParam param)
{
return FetchAsync(url, param).GetAwaiter().GetResult();
}
public static string Fetch(string url)
{
return Fetch(url, new FetchParam { Method = "GET", Timeout = 30000, MakeLog = true });
}
private static async Task<string> FetchAsync(string url, FetchParam param, int retries)
{
var urls = new[] { url, param.FallbackUrl }.Where(u => !string.IsNullOrWhiteSpace(u)).Cast<string>().ToList();
Exception? lastException = null;
foreach (var currentUrl in urls)
{
for (var attempt = 0; attempt < Math.Max(1, retries); attempt++)
{
try
{
return await FetchOnceAsync(currentUrl, param).ConfigureAwait(false);
}
catch (Exception ex)
{
lastException = ex;
}
}
}
throw lastException ?? new HttpRequestException("请求失败");
}
private static async Task<string> FetchOnceAsync(string url, FetchParam param)
{
HttpResponseMessage? response = null;
var request = new HttpRequestMessage(ParseMethod(param.Method), RequestSigning.SecretCdnSign(url));
RequestSigning.SecretHeadersSign(url, ref request, param.UseBrowserUserAgent);
try
{
if (!string.IsNullOrWhiteSpace(param.Accept))
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(param.Accept));
if (param.Headers is not null)
foreach (var header in param.Headers)
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
if (SupportBody(request.Method) && param.Content is not null)
{
if (param.Content is HttpContent httpContent)
{
request.Content = httpContent;
}
else
{
var content = param.Content is string text ? text : param.Content.ToString() ?? "";
request.Content = new StringContent(content, param.Encoding ?? Encoding.UTF8,
param.ContentType ?? "application/json");
}
}
using var cts = new CancellationTokenSource();
cts.CancelAfter(param.Timeout <= 0 ? 30000 : param.Timeout);
response = await NetworkService.GetClient().SendAsync(request, cts.Token).ConfigureAwait(false);
EnsureSuccess(response);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
finally
{
if(!param.RequireContent) response?.Dispose();
request.Dispose();
}
}
public static async Task DownloadFileAsync(string url, string filePath)
{
await FileDownloader.DownloadAsync(url, filePath).ConfigureAwait(false);
}
public static async Task DownloadFileOnceAsync(string url, string filePath)
{
await FileDownloader.DownloadAsync(url, filePath).ConfigureAwait(false);
}
public static DownloadService CreateDownloadService(string url, bool useBrowserUserAgent = false)
{
var chunkCount = Math.Min(Math.Max(1, ModNet.NetTaskThreadLimit), 4);
return new DownloadService(new DownloadConfiguration
{
ChunkCount = chunkCount,
ParallelCount = chunkCount,
ParallelDownload = chunkCount > 1,
MaximumBytesPerSecond = ModNet.NetTaskSpeedLimitHigh > 0 ? ModNet.NetTaskSpeedLimitHigh : 0,
DownloadFileExtension = ModNet.netDownloadEnd,
EnableAutoResumeDownload = false,
MaximumMemoryBufferBytes = 256L * 1024 * 1024,
RequestConfiguration = DownloadRequestFactory.Create(url, useBrowserUserAgent)
});
}
public static HttpMethod ParseMethod(string? method)
{
return (method ?? "GET").ToUpperInvariant() switch
{
"POST" => HttpMethod.Post,
"PUT" => HttpMethod.Put,
"DELETE" => HttpMethod.Delete,
"PATCH" => HttpMethod.Patch,
"HEAD" => HttpMethod.Head,
_ => HttpMethod.Get
};
}
public static bool SupportBody(HttpMethod method)
{
return method == HttpMethod.Post || method == HttpMethod.Put || method == HttpMethod.Patch ||
method == HttpMethod.Delete;
}
public static int Ping(string ip)
{
try
{
var result = new Ping().Send(ip);
return result.Status == IPStatus.Success ? (int)result.RoundtripTime : -1;
}
catch
{
return -1;
}
}
}