Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
namespace XWorld.Server.Gateway
{
// 独立内网 CDNKestrel 静态文件服务,HTTP 根 = root(发布产物 client/ 目录)。
// 仿 GameServerHost 的 Start/Stop 形态;缺失文件返回 404,无目录浏览。
public sealed class StaticFileServer
{
private readonly string _root;
private readonly int _port;
private readonly bool _bindAll;
private WebApplication _app;
public string HttpBaseUrl { get; private set; } // 例如 http://127.0.0.1:15081
public StaticFileServer(string root, int port = 15081, bool bindAllInterfaces = false)
{
if (string.IsNullOrWhiteSpace(root)) throw new ArgumentException("root 不能为空", nameof(root));
_root = Path.GetFullPath(root);
_port = port;
_bindAll = bindAllInterfaces;
}
public async Task StartAsync()
{
Directory.CreateDirectory(_root); // 根目录不存在则建空目录(GET 全 404,而非启动崩溃)
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.WebHost.ConfigureKestrel(o =>
o.Listen(_bindAll ? System.Net.IPAddress.Any : System.Net.IPAddress.Loopback, _port)); // 0 = 临时端口
var app = builder.Build();
var provider = new PhysicalFileProvider(_root);
var opts = new StaticFileOptions
{
FileProvider = provider,
ServeUnknownFileTypes = true, // .bytes/.unity3d/.sig 等无标准 MIME 也要发
DefaultContentType = "application/octet-stream",
};
app.UseStaticFiles(opts); // 仅静态文件,不开目录浏览;未命中落到下面的 404
await app.StartAsync();
_app = app;
string http = app.Services.GetRequiredService<IServer>()
.Features.Get<IServerAddressesFeature>().Addresses.FirstOrDefault()
?? throw new InvalidOperationException("Kestrel 已启动但未注册任何监听地址");
HttpBaseUrl = http;
}
public async Task StopAsync()
{
if (_app == null) return;
await _app.StopAsync();
await _app.DisposeAsync();
_app = null;
}
}
}