milimoe ccf75528cb
添加日志级别;添加匿名服务器监听;模组线程安全改进 (#41)
* 添加日志级别;添加匿名服务器监听(不要求客户端安装)

* 修复不同时间多客户端连接游戏模组时可能产生的线程安全问题

* 更新了匿名服务器令牌确认
2025-01-17 18:59:44 +08:00

316 lines
9.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Net.WebSockets;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Unicode;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.Logging.Console;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Controller;
using Milimoe.FunGame.Server.Model;
using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility;
using Milimoe.FunGame.WebAPI.Architecture;
using Milimoe.FunGame.WebAPI.Services;
WebAPIListener listener = new();
try
{
Console.Title = Config.ServerName;
Console.WriteLine(FunGameInfo.GetInfo(Config.FunGameType));
Config.AspNetCore = true;
ServerHelper.WriteLine("正在读取配置文件并初始化服务 . . .");
// 检查是否存在配置文件
if (!INIHelper.ExistINIFile())
{
ServerHelper.WriteLine("未检测到配置文件,将自动创建配置文件 . . .");
INIHelper.Init(Config.FunGameType);
ServerHelper.WriteLine("配置文件FunGame.ini创建成功请修改该配置文件然后重启服务器。");
Console.ReadKey();
return;
}
else
{
ServerHelper.GetServerSettings();
}
// 初始化命令菜单
ServerHelper.InitOrderList();
// 初始化SQLHelper
FunGameSystem.InitSQLHelper();
// 初始化MailSender
FunGameSystem.InitMailSender();
// 读取游戏模组
if (!FunGameSystem.GetGameModuleList())
{
ServerHelper.WriteLine("服务器似乎未安装任何游戏模组,请检查是否正确安装它们。");
}
// 读取Server插件
FunGameSystem.GetServerPlugins();
// 读取Web API插件
FunGameSystem.GetWebAPIPlugins();
// 创建单例
RESTfulAPIListener apiListener = new();
RESTfulAPIListener.Instance = apiListener;
ServerHelper.WriteLine("请输入 help 来获取帮助,按下 Ctrl+C 关闭服务器。");
ServerHelper.PrintFunGameTitle();
if (Config.ServerNotice != "")
Console.WriteLine("\r \n********** 服务器公告 **********\n\n" + Config.ServerNotice + "\n");
else
Console.WriteLine("无法读取服务器公告");
ServerHelper.WriteLine("正在启动 Web API 监听 . . .");
Console.WriteLine("\r ");
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// 读取扩展控制器
if (Config.WebAPIPluginLoader != null)
{
foreach (WebAPIPlugin plugin in Config.WebAPIPluginLoader.Plugins.Values)
{
Assembly? pluginAssembly = Assembly.GetAssembly(plugin.GetType());
if (pluginAssembly != null)
{
// 注册所有控制器
builder.Services.AddControllers().PartManager.ApplicationParts.Add(new AssemblyPart(pluginAssembly));
}
}
}
// 添加 JSON 转换器
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
foreach (JsonConverter converter in JsonTool.JsonSerializerOptions.Converters)
{
options.JsonSerializerOptions.Converters.Add(converter);
}
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "输入 Auth 返回的 BearerToken",
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
// 添加 CORS 服务
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// 添加 JWT 认证
builder.Services.AddScoped<JWTService>();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"] ?? "undefined"))
};
}).AddScheme<AuthenticationSchemeOptions, CustomBearerAuthenticationHandler>("CustomBearer", options => { });
builder.Logging.AddConsole(options =>
{
options.FormatterName = "CustomFormatter";
});
builder.Services.AddSingleton<ConsoleFormatter, CustomConsoleFormatter>();
WebApplication app = builder.Build();
// 启用 CORS
app.UseCors("AllowSpecificOrigin");
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
IExceptionHandlerFeature? contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
await context.Response.WriteAsync(new
{
context.Response.StatusCode,
Message = "Internal Server Error.",
Detailed = contextFeature.Error.Message
}.ToString() ?? "");
}
});
});
// 捕捉关闭程序事件
IHostApplicationLifetime lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(CloseServer);
// 启用 WebSockets 中间件
WebSocketOptions webSocketOptions = new()
{
KeepAliveInterval = TimeSpan.FromMinutes(2) // 设置 WebSocket 的保活间隔
};
app.UseWebSockets(webSocketOptions);
// 路由到 WebSocket 处理器
app.Map("/ws", WebSocketConnectionHandler);
// 开始监听连接
listener.BannedList.AddRange(Config.ServerBannedList);
Task order = Task.Factory.StartNew(GetConsoleOrder);
app.Run();
}
catch (Exception e)
{
ServerHelper.Error(e);
}
async Task GetConsoleOrder()
{
while (true)
{
string order = Console.ReadLine() ?? "";
ServerHelper.Type();
if (order != "")
{
order = order.ToLower();
switch (order)
{
default:
await ConsoleModel.Order(listener, order);
break;
}
}
}
}
async Task WebSocketConnectionHandler(HttpContext context)
{
string clientip = "";
try
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket instance = await context.WebSockets.AcceptWebSocketAsync();
clientip = context.Connection.RemoteIpAddress?.ToString() + ":" + context.Connection.RemotePort;
Guid token = Guid.NewGuid();
ServerWebSocket socket = new(listener, instance, clientip, clientip, token);
Config.ConnectingPlayerCount++;
bool isConnected = false;
bool isDebugMode = false;
// 开始处理客户端连接请求
IEnumerable<SocketObject> objs = [];
while (!objs.Any(o => o.SocketType == SocketMessageType.Connect))
{
objs = objs.Union(await socket.ReceiveAsync());
}
(isConnected, isDebugMode) = await ConnectController.Connect(listener, socket, token, clientip, objs.Where(o => o.SocketType == SocketMessageType.Connect));
if (isConnected)
{
ServerModel<ServerWebSocket> ClientModel = new(listener, socket, isDebugMode);
ClientModel.SetClientName(clientip);
await ClientModel.Start();
}
else
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
await socket.CloseAsync();
}
Config.ConnectingPlayerCount--;
}
else
{
context.Response.StatusCode = 400;
}
}
catch (Exception e)
{
if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
ServerHelper.Error(e);
}
}
void CloseServer()
{
FunGameSystem.CloseServer();
}