为服务器统一数据访问连接 (#37)

* 添加 Web API 和 RESTful API 模式;
* 添加 SQLite 模式;
* 添加 ISocketMessageProcessor 和 ISocketListener<> 接口,用于统一数据访问;
* 重做了 ISocketModel;
* 完善了 WebSocket 的连接模式。
This commit is contained in:
milimoe 2024-10-04 12:39:15 +08:00 committed by GitHub
parent cea0f393f7
commit 14ff58f4f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2408 additions and 1005 deletions

View File

@ -1,7 +1,7 @@
using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Model;
namespace Milimoe.FunGame.Server.Controller namespace Milimoe.FunGame.Server.Controller
{ {
@ -9,11 +9,11 @@ namespace Milimoe.FunGame.Server.Controller
{ {
public TwoFactorAuthenticator Login2FA = new(); public TwoFactorAuthenticator Login2FA = new();
private readonly ServerModel Server; private readonly IServerModel Server;
private readonly SQLHelper SQLHelper; private readonly SQLHelper SQLHelper;
private readonly MailSender? MailSender; private readonly MailSender? MailSender;
public Authenticator(ServerModel Server, SQLHelper SQLHelper, MailSender? MailSender) : base(SQLHelper) public Authenticator(IServerModel Server, SQLHelper SQLHelper, MailSender? MailSender) : base(SQLHelper)
{ {
this.Server = Server; this.Server = Server;
this.SQLHelper = SQLHelper; this.SQLHelper = SQLHelper;

View File

@ -0,0 +1,134 @@
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility;
namespace Milimoe.FunGame.Server.Controller
{
public class ConnectController
{
/// <summary>
/// 因为异步函数无法使用 ref 变量,因此使用元组返回
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="listener"></param>
/// <param name="socket"></param>
/// <param name="token"></param>
/// <param name="clientip"></param>
/// <param name="objs"></param>
/// <returns>[0]isConnected[1]isDebugMode</returns>
public static async Task<(bool, bool)> Connect<T>(ISocketListener<T> listener, ISocketMessageProcessor socket, Guid token, string clientip, IEnumerable<SocketObject> objs) where T : ISocketMessageProcessor
{
try
{
bool isConnected = false;
bool isDebugMode = false;
foreach (SocketObject obj in objs)
{
if (obj.SocketType == SocketMessageType.Connect)
{
if (Config.ConnectingPlayerCount + Config.OnlinePlayerCount > Config.MaxPlayers)
{
await SendRefuseConnect(socket, "服务器可接受的连接数量已上限!");
ServerHelper.WriteLine("服务器可接受的连接数量已上限!", InvokeMessageType.Core);
return (isConnected, isDebugMode);
}
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 正在连接服务器 . . .", InvokeMessageType.Core);
if (IsIPBanned(listener, clientip))
{
await SendRefuseConnect(socket, "服务器已拒绝黑名单用户连接。");
ServerHelper.WriteLine("检测到 " + ServerHelper.MakeClientName(clientip) + " 为黑名单用户,已禁止其连接!", InvokeMessageType.Core);
return (isConnected, isDebugMode);
}
ServerHelper.WriteLine("[" + SocketSet.GetTypeString(obj.SocketType) + "] " + ServerHelper.MakeClientName(socket.ClientIP), InvokeMessageType.Core);
// 读取参数
// 参数1客户端的游戏模组列表没有服务器的需要拒绝
string[] modes = obj.GetParam<string[]>(0) ?? [];
// 参数2客户端是否开启了开发者模式开启开发者模式部分功能不可用
isDebugMode = obj.GetParam<bool>(1);
if (isDebugMode) ServerHelper.WriteLine("客户端已开启开发者模式");
string msg = "";
List<string> ClientDontHave = [];
string strDontHave = string.Join("\r\n", Config.GameModuleSupported.Where(mode => !modes.Contains(mode)));
if (strDontHave != "")
{
strDontHave = "客户端缺少服务器所需的模组:" + strDontHave;
ServerHelper.WriteLine(strDontHave, InvokeMessageType.Core);
msg += strDontHave;
}
if (msg == "" && await socket.SendAsync(SocketMessageType.Connect, true, msg, token, Config.ServerName, Config.ServerNotice) == SocketResult.Success)
{
isConnected = true;
ServerHelper.WriteLine(ServerHelper.MakeClientName(socket.ClientIP) + " <- " + "已确认连接", InvokeMessageType.Core);
return (isConnected, isDebugMode);
}
else if (msg != "" && await socket.SendAsync(SocketMessageType.Connect, false, msg) == SocketResult.Success)
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(socket.ClientIP) + " <- " + "拒绝连接", InvokeMessageType.Core);
return (isConnected, isDebugMode);
}
else
{
ServerHelper.WriteLine("无法传输数据,与客户端的连接可能丢失。", InvokeMessageType.Core);
return (isConnected, isDebugMode);
}
}
}
await SendRefuseConnect(socket, "服务器已拒绝连接。");
ServerHelper.WriteLine("客户端发送了不符合FunGame规定的字符拒绝连接。", InvokeMessageType.Core);
return (isConnected, isDebugMode);
}
catch (Exception e)
{
ServerHelper.Error(e);
await SendRefuseConnect(socket, "请勿发送错误的数据请保持FunGame版本为最新。");
throw new SocketWrongInfoException();
}
}
/// <summary>
/// 回复拒绝连接消息
/// </summary>
/// <param name="socket"></param>
/// <param name="msg"></param>
/// <returns></returns>
private static async Task<bool> SendRefuseConnect(ISocketMessageProcessor socket, string msg)
{
// 发送消息给客户端
msg = "连接被拒绝,如有疑问请联系服务器管理员:" + msg;
if (await socket.SendAsync(SocketMessageType.Connect, false, msg) == SocketResult.Success)
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(socket.ClientIP) + " <- " + "已拒绝连接", InvokeMessageType.Core);
return true;
}
else
{
ServerHelper.WriteLine("无法传输数据,与客户端的连接可能丢失。", InvokeMessageType.Core);
return false;
}
}
/// <summary>
/// 判断是否是黑名单里的IP
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="server"></param>
/// <param name="ip"></param>
/// <returns></returns>
private static bool IsIPBanned<T>(ISocketListener<T> server, string ip) where T : ISocketMessageProcessor
{
string[] strs = ip.Split(":");
if (strs.Length == 2 && server.BannedList.Contains(strs[0]))
{
return true;
}
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,20 +16,34 @@
<FileVersion>1.0</FileVersion> <FileVersion>1.0</FileVersion>
<AssemblyName>FunGameServer</AssemblyName> <AssemblyName>FunGameServer</AssemblyName>
<RootNamespace>Milimoe.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace> <RootNamespace>Milimoe.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
<NoWarn>1701;1702;IDE0130</NoWarn>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
<NoWarn>1701;1702;IDE0130</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Libraries\**" />
<EmbeddedResource Remove="Libraries\**" />
<None Remove="Libraries\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="logo.ico" /> <Content Include="logo.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
<PackageReference Include="MySql.Data" Version="9.0.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FunGame.Implement\FunGame.Implement.csproj" /> <ProjectReference Include="..\FunGame.Implement\FunGame.Implement.csproj" />
</ItemGroup> </ItemGroup>
@ -38,15 +52,6 @@
<Reference Include="FunGame.Core"> <Reference Include="FunGame.Core">
<HintPath>..\..\FunGame.Core\bin\Debug\net8.0\FunGame.Core.dll</HintPath> <HintPath>..\..\FunGame.Core\bin\Debug\net8.0\FunGame.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="MySql.Data">
<HintPath>..\bin\Debug\net7.0\MySql.Data.dll</HintPath>
</Reference>
<Reference Include="System.Configuration.ConfigurationManager">
<HintPath>..\bin\Debug\net7.0\System.Configuration.ConfigurationManager.dll</HintPath>
</Reference>
<Reference Include="System.Security.Permissions">
<HintPath>..\bin\Debug\net7.0\System.Security.Permissions.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,10 +1,8 @@
using System.Collections; using Milimoe.FunGame;
using System.Collections.Generic;
using Milimoe.FunGame;
using Milimoe.FunGame.Core.Api.Utility; 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.Common.Network;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Controller;
using Milimoe.FunGame.Server.Model; using Milimoe.FunGame.Server.Model;
using Milimoe.FunGame.Server.Others; using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility; using Milimoe.FunGame.Server.Utility;
@ -13,7 +11,8 @@ Console.Title = Config.ServerName;
Console.WriteLine(FunGameInfo.GetInfo(Config.FunGameType)); Console.WriteLine(FunGameInfo.GetInfo(Config.FunGameType));
bool Running = true; bool Running = true;
ServerSocket? ListeningSocket = null; SocketListener? SocketListener = null;
HTTPListener? WebSocketListener = null;
StartServer(); StartServer();
@ -32,7 +31,7 @@ while (Running)
Running = false; Running = false;
break; break;
case OrderDictionary.Restart: case OrderDictionary.Restart:
if (ListeningSocket == null) if (SocketListener is null || WebSocketListener is null)
{ {
ServerHelper.WriteLine("重启服务器"); ServerHelper.WriteLine("重启服务器");
StartServer(); StartServer();
@ -40,7 +39,14 @@ while (Running)
else ServerHelper.WriteLine("服务器正在运行,拒绝重启!"); else ServerHelper.WriteLine("服务器正在运行,拒绝重启!");
break; break;
default: default:
ConsoleModel.Order(ListeningSocket, order); if (SocketListener != null)
{
await ConsoleModel.Order(SocketListener, order);
}
else
{
await ConsoleModel.Order(WebSocketListener, order);
}
break; break;
} }
} }
@ -51,7 +57,7 @@ Console.ReadKey();
void StartServer() void StartServer()
{ {
Task t = Task.Factory.StartNew(() => TaskUtility.NewTask(async () =>
{ {
try try
{ {
@ -60,7 +66,7 @@ void StartServer()
ServerHelper.InitOrderList(); ServerHelper.InitOrderList();
// 读取游戏模组 // 读取游戏模组
if (!GetGameModuleList()) if (!Config.GetGameModuleList())
{ {
ServerHelper.WriteLine("服务器似乎未安装任何游戏模组,请检查是否正确安装它们。"); ServerHelper.WriteLine("服务器似乎未安装任何游戏模组,请检查是否正确安装它们。");
} }
@ -83,11 +89,17 @@ void StartServer()
// 创建全局SQLHelper // 创建全局SQLHelper
Config.InitSQLHelper(); Config.InitSQLHelper();
// 使用Socket还是WebSocket
bool useWebSocket = Config.UseWebSocket;
if (!useWebSocket)
{
// 创建监听 // 创建监听
ListeningSocket = ServerSocket.StartListening(); SocketListener listener = SocketListener.StartListening(Config.ServerPort, Config.MaxPlayers);
SocketListener = listener;
// 开始监听连接 // 开始监听连接
AddBannedList(ListeningSocket); listener.BannedList.AddRange(Config.ServerBannedList);
ServerHelper.WriteLine("Listen -> " + Config.ServerPort); ServerHelper.WriteLine("Listen -> " + Config.ServerPort);
ServerHelper.WriteLine("服务器启动成功,开始监听 . . ."); ServerHelper.WriteLine("服务器启动成功,开始监听 . . .");
@ -98,36 +110,114 @@ void StartServer()
while (Running) while (Running)
{ {
ClientSocket socket; ServerSocket socket;
string clientip = ""; string clientip = "";
try try
{ {
Guid token = Guid.NewGuid(); Guid token = Guid.NewGuid();
socket = ListeningSocket.Accept(token); socket = listener.Accept(token);
TaskUtility.NewTask(async () =>
{
clientip = socket.ClientIP; clientip = socket.ClientIP;
Config.ConnectingPlayerCount++; Config.ConnectingPlayerCount++;
// 开始处理客户端连接请求 bool isConnected = false;
bool isDebugMode = false; bool isDebugMode = false;
if (Connect(socket, token, clientip, ref isDebugMode))
// 开始处理客户端连接请求
SocketObject[] objs = socket.Receive();
(isConnected, isDebugMode) = await ConnectController.Connect(listener, socket, token, clientip, objs);
if (isConnected)
{ {
ServerModel ClientModel = new(ListeningSocket, socket, Running, isDebugMode); ServerModel<ServerSocket> ClientModel = new(listener, socket, isDebugMode);
Task t = Task.Factory.StartNew(() =>
{
ClientModel.Start();
});
ClientModel.SetClientName(clientip); ClientModel.SetClientName(clientip);
Task t = Task.Run(ClientModel.Start);
} }
else else
{ {
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core); ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
} }
Config.ConnectingPlayerCount--; Config.ConnectingPlayerCount--;
} }).OnError(e =>
catch (Exception e)
{ {
if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0; if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core); ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
ServerHelper.Error(e); ServerHelper.Error(e);
});
}
catch (Exception e)
{
ServerHelper.Error(e);
}
}
}
else
{
if (Config.WebSocketAddress == "*")
{
ServerHelper.WriteLine("WebSocket 监听 * 地址要求权限提升,如果提示拒绝访问请以管理员身份运行服务器。", InvokeMessageType.Warning);
}
// 创建监听
HTTPListener listener = HTTPListener.StartListening(Config.WebSocketAddress, Config.WebSocketPort, Config.WebSocketSubUrl, Config.WebSocketSSL);
WebSocketListener = listener;
// 开始监听连接
listener.BannedList.AddRange(Config.ServerBannedList);
ServerHelper.WriteLine("Listen -> " + listener.Instance.Prefixes.First());
ServerHelper.WriteLine("服务器启动成功,开始监听 . . .");
if (Config.ServerNotice != "")
ServerHelper.WriteLine("\n\n********** 服务器公告 **********\n\n" + Config.ServerNotice + "\n");
else
ServerHelper.WriteLine("无法读取服务器公告");
while (Running)
{
ServerWebSocket socket;
string clientip = "";
try
{
Guid token = Guid.NewGuid();
socket = await listener.Accept(token);
TaskUtility.NewTask(async () =>
{
clientip = socket.ClientIP;
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);
Task t = Task.Run(ClientModel.Start);
}
else
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
await socket.CloseAsync();
}
Config.ConnectingPlayerCount--;
}).OnError(e =>
{
if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
ServerHelper.Error(e);
});
}
catch (Exception e)
{
ServerHelper.Error(e);
}
} }
} }
} }
@ -135,158 +225,31 @@ void StartServer()
{ {
if (e.Message.Equals(new ServerErrorException().Message)) if (e.Message.Equals(new ServerErrorException().Message))
{ {
if (ListeningSocket != null) if (SocketListener != null)
{ {
ListeningSocket.Close(); SocketListener.Close();
ListeningSocket = null; SocketListener = null;
}
if (WebSocketListener != null)
{
WebSocketListener.Close();
WebSocketListener = null;
} }
} }
ServerHelper.Error(e); ServerHelper.Error(e);
} }
finally finally
{ {
if (ListeningSocket != null) if (SocketListener != null)
{ {
ListeningSocket.Close(); SocketListener.Close();
ListeningSocket = null; SocketListener = null;
}
if (WebSocketListener != null)
{
WebSocketListener.Close();
WebSocketListener = null;
} }
} }
}); });
} }
bool GetGameModuleList()
{
List<string> supported = [];
// 构建AddonController
Hashtable delegates = [];
delegates.Add("WriteLine", new Action<string>(msg => ServerHelper.WriteLine(msg, InvokeMessageType.GameModule)));
delegates.Add("Error", new Action<Exception>(ServerHelper.Error));
// 读取modules目录下的模组
Config.GameModuleLoader = GameModuleLoader.LoadGameModules(Config.FunGameType, delegates);
foreach (GameModule module in Config.GameModuleLoader.AssociatedServers.Keys)
{
bool check = true;
// 检查模组是否存在对应的模组服务器
if (Config.GameModuleLoader.AssociatedServers[module] is null)
{
ServerHelper.WriteLine("[GameModule] Load Failed: " + module.Name + " 缺少模组服务器");
check = false;
}
// 检查模组是否有相对应的地图
if (!Config.GameModuleLoader.Maps.ContainsKey(module.DefaultMap))
{
ServerHelper.WriteLine("[GameModule] Load Failed: " + module + " 没有找到相对应的地图,加载失败");
check = false;
}
if (check)
{
supported.Add(module.Name);
}
}
// 设置全局
Config.GameModuleSupported = supported.Distinct().ToArray();
foreach (string modename in Config.GameModuleSupported)
{
ServerHelper.WriteLine("[GameModule] Loaded: " + modename);
}
return Config.GameModuleSupported.Length > 0;
}
bool Connect(ClientSocket socket, Guid token, string clientip, ref bool isDebugMode)
{
// 接收客户端消息
foreach (SocketObject read in socket.Receive())
{
if (read.SocketType == SocketMessageType.Connect)
{
if (Config.ConnectingPlayerCount + Config.OnlinePlayerCount > Config.MaxPlayers)
{
SendRefuseConnect(socket, "服务器可接受的连接数量已上限!");
ServerHelper.WriteLine("服务器可接受的连接数量已上限!", InvokeMessageType.Core);
return false;
}
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 正在连接服务器 . . .", InvokeMessageType.Core);
if (IsIPBanned(ListeningSocket, clientip))
{
SendRefuseConnect(socket, "服务器已拒绝黑名单用户连接。");
ServerHelper.WriteLine("检测到 " + ServerHelper.MakeClientName(clientip) + " 为黑名单用户,已禁止其连接!", InvokeMessageType.Core);
return false;
}
ServerHelper.WriteLine("[" + SocketSet.GetTypeString(read.SocketType) + "] " + ServerHelper.MakeClientName(socket.ClientIP), InvokeMessageType.Core);
// 读取参数
// 参数1客户端的游戏模组列表没有服务器的需要拒绝
string[] modes = read.GetParam<string[]>(0) ?? [];
// 参数2客户端是否开启了开发者模式开启开发者模式部分功能不可用
isDebugMode = read.GetParam<bool>(1);
if (isDebugMode) ServerHelper.WriteLine("客户端已开启开发者模式");
string msg = "";
List<string> ClientDontHave = [];
string strDontHave = string.Join("\r\n", Config.GameModuleSupported.Where(mode => !modes.Contains(mode)));
if (strDontHave != "")
{
strDontHave = "客户端缺少服务器所需的模组:" + strDontHave;
ServerHelper.WriteLine(strDontHave, InvokeMessageType.Core);
msg += strDontHave;
}
if (msg == "" && socket.Send(SocketMessageType.Connect, true, msg, token, Config.ServerName, Config.ServerNotice) == SocketResult.Success)
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(socket.ClientIP) + " <- " + "已确认连接", InvokeMessageType.Core);
return true;
}
else if (msg != "" && socket.Send(SocketMessageType.Connect, false, msg) == SocketResult.Success)
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(socket.ClientIP) + " <- " + "拒绝连接", InvokeMessageType.Core);
return false;
}
else
{
ServerHelper.WriteLine("无法传输数据,与客户端的连接可能丢失。", InvokeMessageType.Core);
return false;
}
}
}
SendRefuseConnect(socket, "服务器已拒绝连接。");
ServerHelper.WriteLine("客户端发送了不符合FunGame规定的字符拒绝连接。", InvokeMessageType.Core);
return false;
}
bool SendRefuseConnect(ClientSocket socket, string msg)
{
// 发送消息给客户端
msg = "连接被拒绝,如有疑问请联系服务器管理员:" + msg;
if (socket.Send(SocketMessageType.Connect, false, msg) == SocketResult.Success)
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(socket.ClientIP) + " <- " + "已拒绝连接", InvokeMessageType.Core);
return true;
}
else
{
ServerHelper.WriteLine("无法传输数据,与客户端的连接可能丢失。", InvokeMessageType.Core);
return false;
}
}
void AddBannedList(ServerSocket server)
{
string[] bans = Config.ServerBannedList.Split(',');
foreach (string banned in bans)
{
server.BannedList.Add(banned.Trim());
}
}
bool IsIPBanned(ServerSocket server, string ip)
{
string[] strs = ip.Split(":");
if (strs.Length == 2 && server.BannedList.Contains(strs[0]))
{
return true;
}
return false;
}

View File

@ -1,5 +1,4 @@
using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Server.Others; using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility; using Milimoe.FunGame.Server.Utility;
@ -7,7 +6,7 @@ namespace Milimoe.FunGame.Server.Model
{ {
public class ConsoleModel public class ConsoleModel
{ {
public static void Order(ServerSocket? server, string order) public static async Task Order<T>(ISocketListener<T>? server, string order) where T : ISocketMessageProcessor
{ {
try try
{ {
@ -19,7 +18,7 @@ namespace Milimoe.FunGame.Server.Model
string client = Console.ReadLine() ?? ""; string client = Console.ReadLine() ?? "";
if (client != "" && server != null) if (client != "" && server != null)
{ {
((ServerModel)server.GetClient(client))?.Kick("您已被服务器管理员踢出此服务器。"); await Kick((ServerModel<T>)server.ClientList[client]);
} }
break; break;
} }
@ -29,7 +28,7 @@ namespace Milimoe.FunGame.Server.Model
string user = Console.ReadLine() ?? ""; string user = Console.ReadLine() ?? "";
if (user != "" && server != null) if (user != "" && server != null)
{ {
((ServerModel)server.GetUser(user))?.ForceLogOut("您已被服务器管理员强制下线。"); await ForceLogOut((ServerModel<T>)server.UserList[user]);
} }
break; break;
} }
@ -46,7 +45,7 @@ namespace Milimoe.FunGame.Server.Model
ShowUsers(server); ShowUsers(server);
break; break;
case OrderDictionary.Help: case OrderDictionary.Help:
ServerHelper.WriteLine("Milimoe -> 帮助"); ShowHelp();
break; break;
} }
} }
@ -56,7 +55,17 @@ namespace Milimoe.FunGame.Server.Model
} }
} }
private static void ShowClients(ServerSocket? server) public static async Task Kick<T>(ServerModel<T> clientModel) where T : ISocketMessageProcessor
{
await clientModel.Kick("您已被服务器管理员踢出此服务器。");
}
public static async Task ForceLogOut<T>(ServerModel<T> clientModel) where T : ISocketMessageProcessor
{
await clientModel.ForceLogOut("您已被服务器管理员强制下线。");
}
public static void ShowClients<T>(ISocketListener<T>? server) where T : ISocketMessageProcessor
{ {
if (server != null) if (server != null)
{ {
@ -69,7 +78,7 @@ namespace Milimoe.FunGame.Server.Model
} }
} }
private static void ShowUsers(ServerSocket? server) public static void ShowUsers<T>(ISocketListener<T>? server) where T : ISocketMessageProcessor
{ {
if (server != null) if (server != null)
{ {
@ -81,5 +90,10 @@ namespace Milimoe.FunGame.Server.Model
} }
} }
} }
public static void ShowHelp()
{
ServerHelper.WriteLine("Milimoe -> 帮助");
}
} }
} }

View File

@ -1,5 +1,4 @@
using System.Collections; using System.Data;
using System.Data;
using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Entity;
@ -14,88 +13,67 @@ using Milimoe.FunGame.Server.Utility;
namespace Milimoe.FunGame.Server.Model namespace Milimoe.FunGame.Server.Model
{ {
public class ServerModel : IServerModel public class ServerModel<T> : IServerModel where T : ISocketMessageProcessor
{ {
/** /**
* Public * Public
*/ */
public bool Running => _Running; public bool Running => _running;
public ClientSocket? Socket => _Socket; public ISocketMessageProcessor Socket { get; }
public string ClientName => _ClientName; public ISocketListener<T> Listener { get; }
public User User => _User; public DataRequestController<T> DataRequestController { get; }
public Room Room public Guid Token => Socket?.Token ?? Guid.Empty;
{ public string ClientName => _clientName;
get => _Room; public User User { get; set; } = General.UnknownUserInstance;
set => _Room = value; public Room InRoom { get; set; } = General.HallInstance;
} public SQLHelper? SQLHelper => _sqlHelper;
public MySQLHelper? SQLHelper => _SQLHelper; public MailSender? MailSender => _mailer;
public MailSender? MailSender => _MailSender;
public bool IsDebugMode { get; } public bool IsDebugMode { get; }
public GameModuleServer? NowGamingServer { get; set; } = null;
/** /**
* Private * protected
*/ */
private GameModuleServer? NowGamingServer = null; protected bool _running = true;
protected int _failedTimes = 0; // 超过一定次数断开连接
protected string _clientName = "";
protected SQLHelper? _sqlHelper = null;
protected MailSender? _mailer = null;
protected string _username = "";
protected long _loginTime = 0;
protected long _logoutTime = 0;
private ClientSocket? _Socket = null; public ServerModel(ISocketListener<T> server, ISocketMessageProcessor socket, bool isDebugMode)
private bool _Running = false;
private User _User = General.UnknownUserInstance;
private Room _Room = General.HallInstance;
private string _ClientName = "";
public MySQLHelper? _SQLHelper = null;
public MailSender? _MailSender = null;
private Guid CheckLoginKey = Guid.Empty;
private int FailedTimes = 0; // 超过一定次数断开连接
private string UserName = "";
private DataSet DsUser = new();
private readonly Guid Token;
private readonly ServerSocket Server;
private readonly DataRequestController DataRequestController;
private long LoginTime;
private long LogoutTime;
private bool IsMatching;
public ServerModel(ServerSocket server, ClientSocket socket, bool running, bool isDebugMode)
{ {
Server = server; Listener = server;
_Socket = socket; Socket = socket;
_Running = running;
Token = socket.Token;
this.IsDebugMode = isDebugMode;
if (Config.SQLMode) _SQLHelper = new(this);
_MailSender = SmtpHelper.GetMailSender();
DataRequestController = new(this); DataRequestController = new(this);
IsDebugMode = isDebugMode;
if (Config.SQLMode == SQLMode.MySQL) _sqlHelper = new MySQLHelper(this);
else if (Config.SQLMode == SQLMode.SQLite) _sqlHelper = Config.SQLHelper;
else ServerHelper.WriteLine("SQL 服务处于关闭状态", InvokeMessageType.Warning);
_mailer = SmtpHelper.GetMailSender();
} }
public bool Read(ClientSocket socket) public virtual async Task<bool> SocketMessageHandler(ISocketMessageProcessor socket, SocketObject obj)
{ {
// 接收客户端消息 // 读取收到的消息
try SocketMessageType type = obj.SocketType;
{ Guid token = obj.Token;
// 禁止GameModuleServer调用
if ((IServerModel)this is GameModuleServer) throw new NotSupportedException("请勿在GameModuleServer类中调用此方法");
SocketObject[] SocketObjects = socket.Receive();
if (SocketObjects.Length == 0)
{
ServerHelper.WriteLine(GetClientName() + " 发送了空信息。");
return false;
}
SocketObject SocketObject = SocketObjects[0];
SocketMessageType type = SocketObject.SocketType;
Guid token = SocketObject.Token;
object[] args = SocketObject.Parameters;
string msg = ""; string msg = "";
// 验证Token // 验证Token
if (type != SocketMessageType.HeartBeat && token != Token) if (type != SocketMessageType.HeartBeat && token != socket.Token)
{ {
ServerHelper.WriteLine(GetClientName() + " 使用了非法方式传输消息,服务器拒绝回应 -> [" + SocketSet.GetTypeString(type) + "]"); ServerHelper.WriteLine(GetClientName() + " 使用了非法方式传输消息,服务器拒绝回应 -> [" + SocketSet.GetTypeString(type) + "]");
return false; return false;
} }
if (type == SocketMessageType.HeartBeat)
{
return await HeartBeat();
}
if (type == SocketMessageType.EndGame) if (type == SocketMessageType.EndGame)
{ {
NowGamingServer = null; NowGamingServer = null;
@ -104,17 +82,17 @@ namespace Milimoe.FunGame.Server.Model
if (type == SocketMessageType.DataRequest) if (type == SocketMessageType.DataRequest)
{ {
return DataRequestHandler(socket, SocketObject); return await DataRequestHandler(obj);
}
if (type == SocketMessageType.GamingRequest)
{
return await GamingRequestHandler(obj);
} }
if (type == SocketMessageType.Gaming) if (type == SocketMessageType.Gaming)
{ {
return GamingMessageHandler(socket, SocketObject); return await GamingMessageHandler(obj);
}
if (type == SocketMessageType.HeartBeat)
{
return HeartBeat(socket);
} }
switch (type) switch (type)
@ -124,81 +102,136 @@ namespace Milimoe.FunGame.Server.Model
msg = "你已成功断开与服务器的连接: " + Config.ServerName + "。 "; msg = "你已成功断开与服务器的连接: " + Config.ServerName + "。 ";
break; break;
} }
return Send(socket, type, msg);
} return await Send(type, msg);
catch (Exception e)
{
ServerHelper.WriteLine(GetClientName() + " 没有回应。");
ServerHelper.Error(e);
return false;
}
} }
public bool DataRequestHandler(ClientSocket socket, SocketObject SocketObject) public async Task<bool> HeartBeat()
{
bool result = await Send(SocketMessageType.HeartBeat);
if (!result)
{
ServerHelper.WriteLine("[ " + _username + " ] " + nameof(HeartBeat) + ": " + result, InvokeMessageType.Error);
}
return result;
}
protected async Task<bool> DataRequestHandler(SocketObject obj)
{ {
if (SQLHelper != null) if (SQLHelper != null)
{ {
Hashtable result = []; Dictionary<string, object> result = [];
Guid requestID = Guid.Empty;
DataRequestType type = DataRequestType.UnKnown; DataRequestType type = DataRequestType.UnKnown;
if (SocketObject.Parameters.Length > 0) if (obj.Parameters.Length > 0)
{ {
try try
{ {
type = SocketObject.GetParam<DataRequestType>(0); type = obj.GetParam<DataRequestType>(0);
Hashtable data = SocketObject.GetParam<Hashtable>(1) ?? []; requestID = obj.GetParam<Guid>(1);
Dictionary<string, object> data = obj.GetParam<Dictionary<string, object>>(2) ?? [];
result = DataRequestController.GetResultData(type, data); result = await DataRequestController.GetResultData(type, data);
} }
catch (Exception e) catch (Exception e)
{ {
ServerHelper.Error(e); ServerHelper.Error(e);
SQLHelper.Rollback(); SQLHelper.Rollback();
return Send(socket, SocketMessageType.DataRequest, type, result); return await Send(SocketMessageType.DataRequest, type, requestID, result);
} }
} }
return Send(socket, SocketMessageType.DataRequest, type, result); bool sendResult = await Send(SocketMessageType.DataRequest, type, requestID, result);
if (!sendResult)
{
ServerHelper.WriteLine("[ " + _username + " ] " + nameof(DataRequestHandler) + ": " + sendResult, InvokeMessageType.Error);
}
return sendResult;
} }
ServerHelper.WriteLine("[ " + _username + " ] " + nameof(DataRequestHandler) + ": " + false, InvokeMessageType.Error);
return false; return false;
} }
public bool GamingMessageHandler(ClientSocket socket, SocketObject SocketObject) protected async Task<bool> GamingRequestHandler(SocketObject obj)
{ {
if (NowGamingServer != null) if (NowGamingServer != null)
{ {
Hashtable result = []; Dictionary<string, object> result = [];
Guid requestID = Guid.Empty;
GamingType type = GamingType.None; GamingType type = GamingType.None;
if (SocketObject.Parameters.Length > 0) if (obj.Parameters.Length > 0)
{ {
try try
{ {
type = SocketObject.GetParam<GamingType>(0); type = obj.GetParam<GamingType>(0);
Hashtable data = SocketObject.GetParam<Hashtable>(1) ?? []; requestID = obj.GetParam<Guid>(1);
Dictionary<string, object> data = obj.GetParam<Dictionary<string, object>>(2) ?? [];
result = NowGamingServer.GamingMessageHandler(UserName, type, data); result = await NowGamingServer.GamingMessageHandler(_username, type, data);
} }
catch (Exception e) catch (Exception e)
{ {
ServerHelper.Error(e); ServerHelper.Error(e);
return Send(socket, SocketMessageType.Gaming, type, result); return await Send(SocketMessageType.GamingRequest, type, requestID, result);
} }
} }
return Send(socket, SocketMessageType.Gaming, type, result); bool sendResult = await Send(SocketMessageType.GamingRequest, type, requestID, result);
if (!sendResult)
{
ServerHelper.WriteLine("[ " + _username + " ] " + nameof(GamingRequestHandler) + ": " + sendResult, InvokeMessageType.Error);
}
return sendResult;
} }
ServerHelper.WriteLine("[ " + _username + " ] " + nameof(GamingRequestHandler) + ": " + false, InvokeMessageType.Error);
return false; return false;
} }
public bool Send(ClientSocket socket, SocketMessageType type, params object[] objs) protected async Task<bool> GamingMessageHandler(SocketObject obj)
{
if (NowGamingServer != null)
{
Dictionary<string, object> result = [];
GamingType type = GamingType.None;
if (obj.Parameters.Length > 0)
{
try
{
type = obj.GetParam<GamingType>(0);
Dictionary<string, object> data = obj.GetParam<Dictionary<string, object>>(1) ?? [];
result = await NowGamingServer.GamingMessageHandler(_username, type, data);
}
catch (Exception e)
{
ServerHelper.Error(e);
return await Send(SocketMessageType.Gaming, type, result);
}
}
bool sendResult = await Send(SocketMessageType.Gaming, type, result);
if (!sendResult)
{
ServerHelper.WriteLine("[ " + _username + " ] " + nameof(GamingMessageHandler) + ": " + sendResult, InvokeMessageType.Error);
}
return sendResult;
}
ServerHelper.WriteLine("[ " + _username + " ] " + nameof(GamingMessageHandler) + ": " + false, InvokeMessageType.Error);
return false;
}
public virtual async Task<bool> Send(SocketMessageType type, params object[] objs)
{ {
// 发送消息给客户端 // 发送消息给客户端
try try
{ {
if (socket.Send(type, objs) == SocketResult.Success) if (await Socket.SendAsync(type, objs) == SocketResult.Success)
{ {
switch (type) switch (type)
{ {
@ -207,14 +240,15 @@ namespace Milimoe.FunGame.Server.Model
break; break;
case SocketMessageType.Disconnect: case SocketMessageType.Disconnect:
RemoveUser(); RemoveUser();
Close(); await Close();
break; break;
case SocketMessageType.Chat: case SocketMessageType.Chat:
return true; return true;
} }
object obj = objs[0]; if (objs.Length > 0 && objs[0] is string str && str != "")
if (obj.GetType() == typeof(string) && (string)obj != "") {
ServerHelper.WriteLine("[" + SocketSet.GetTypeString(type) + "] " + GetClientName() + " <- " + obj, InvokeMessageType.Core); ServerHelper.WriteLine("[" + SocketSet.GetTypeString(type) + "] " + GetClientName() + " <- " + str, InvokeMessageType.Core);
}
return true; return true;
} }
throw new CanNotSendToClientException(); throw new CanNotSendToClientException();
@ -227,18 +261,36 @@ namespace Milimoe.FunGame.Server.Model
} }
} }
public void Start() public async Task SendClients(IEnumerable<IServerModel> clients, SocketMessageType type, params object[] objs)
{ {
if ((IServerModel)this is GameModuleServer) throw new NotSupportedException("请勿在GameModuleServer类中调用此方法"); // 禁止GameModuleServer调用 // 发送消息给多个客户端
Task StreamReader = Task.Factory.StartNew(CreateStreamReader); try
Task PeriodicalQuerier = Task.Factory.StartNew(CreatePeriodicalQuerier); {
foreach (IServerModel client in clients)
{
if (client.Socket != null)
{
await client.Socket.SendAsync(type, objs);
}
}
}
catch (Exception e)
{
ServerHelper.Error(e);
}
}
public async Task Start()
{
TaskUtility.NewTask(CreatePeriodicalQuerier);
await CreateStreamReader();
} }
public void SetClientName(string ClientName) public void SetClientName(string ClientName)
{ {
_ClientName = ClientName; _clientName = ClientName;
// 添加客户端到列表中 // 添加客户端到列表中
Server.AddClient(_ClientName, this); Listener.ClientList.Add(_clientName, this);
Config.OnlinePlayerCount++; Config.OnlinePlayerCount++;
GetUsersCount(); GetUsersCount();
} }
@ -248,55 +300,18 @@ namespace Milimoe.FunGame.Server.Model
return ServerHelper.MakeClientName(ClientName, User); return ServerHelper.MakeClientName(ClientName, User);
} }
public void PreLogin(DataSet dsuser, string username, Guid checkloginkey) public void SendSystemMessage(ShowMessageType showtype, string msg, string title, int autoclose, params string[] usernames)
{ {
DsUser = dsuser; foreach (IServerModel serverTask in Listener.UserList.Where(model => usernames.Length > 0 && usernames.Contains(model.User.Username)))
UserName = username; {
CheckLoginKey = checkloginkey; if (serverTask != null && serverTask.Socket != null)
{
serverTask.Send(SocketMessageType.System, showtype, msg, title, autoclose);
} }
public void CheckLogin()
{
// 创建User对象
_User = Factory.GetUser(DsUser);
// 检查有没有重复登录的情况
KickUser();
// 添加至玩家列表
AddUser();
GetUsersCount();
// CheckLogin
LoginTime = DateTime.Now.Ticks;
SQLHelper?.Execute(UserQuery.Update_CheckLogin(UserName, _Socket?.ClientIP.Split(':')[0] ?? "127.0.0.1"));
}
public bool IsLoginKey(Guid key)
{
return key == CheckLoginKey;
}
public void LogOut()
{
// 从玩家列表移除
RemoveUser();
GetUsersCount();
CheckLoginKey = Guid.Empty;
}
public void ForceLogOut(string msg, string username = "")
{
ServerModel serverTask = (ServerModel)Server.GetUser(username == "" ? UserName : username);
if (serverTask.Socket != null)
{
serverTask.Room = General.HallInstance;
foreach (Room room in Config.RoomList.Cast<Room>())
{
QuitRoom(room.Roomid, room.RoomMaster.Id == User.Id);
}
serverTask.Send(serverTask.Socket, SocketMessageType.ForceLogout, msg);
} }
} }
public bool QuitRoom(string roomid, bool isMaster) public async Task<bool> QuitRoom(string roomid, bool isMaster)
{ {
bool result; bool result;
@ -306,21 +321,21 @@ namespace Milimoe.FunGame.Server.Model
// 是否是房主 // 是否是房主
if (isMaster) if (isMaster)
{ {
List<User> users = Config.RoomList.GetPlayerList(roomid); List<User> users = [.. Config.RoomList[roomid].UserAndIsReady.Keys];
if (users.Count > 0) // 如果此时房间还有人,更新房主 if (users.Count > 0) // 如果此时房间还有人,更新房主
{ {
User NewMaster = users[0]; User NewMaster = users[0];
Room.RoomMaster = NewMaster; Room.RoomMaster = NewMaster;
SQLHelper?.Execute(RoomQuery.Update_QuitRoom(roomid, User.Id, NewMaster.Id)); SQLHelper?.Execute(RoomQuery.Update_QuitRoom(roomid, User.Id, NewMaster.Id));
this.Room = General.HallInstance; this.InRoom = General.HallInstance;
UpdateRoomMaster(Room, true); await UpdateRoomMaster(Room, true);
result = true; result = true;
} }
else // 没人了就解散房间 else // 没人了就解散房间
{ {
Config.RoomList.RemoveRoom(roomid); Config.RoomList.RemoveRoom(roomid);
SQLHelper?.Execute(RoomQuery.Delete_QuitRoom(roomid, User.Id)); SQLHelper?.Execute(RoomQuery.Delete_QuitRoom(roomid, User.Id));
this.Room = General.HallInstance; this.InRoom = General.HallInstance;
ServerHelper.WriteLine("[ " + GetClientName() + " ] 解散了房间 " + roomid); ServerHelper.WriteLine("[ " + GetClientName() + " ] 解散了房间 " + roomid);
result = true; result = true;
} }
@ -328,236 +343,86 @@ namespace Milimoe.FunGame.Server.Model
// 不是房主直接退出房间 // 不是房主直接退出房间
else else
{ {
this.Room = General.HallInstance; this.InRoom = General.HallInstance;
UpdateRoomMaster(Room); await UpdateRoomMaster(Room);
result = true; result = true;
} }
return result; return result;
} }
public void Kick(string msg, string clientname = "") public async Task UpdateRoomMaster(Room room, bool isUpdateRoomMaster = false)
{ {
// 将客户端踢出服务器 foreach (IServerModel Client in Listener.ClientList.Where(c => c != null && c.User.Id != 0 && room.Roomid == c.InRoom?.Roomid))
ServerModel serverTask = (ServerModel)Server.GetClient(clientname == "" ? ClientName : clientname);
if (serverTask.Socket != null)
{ {
serverTask.Room = General.HallInstance; await Client.Send(SocketMessageType.Chat, User.Username, DateTimeUtility.GetNowShortTime() + " [ " + User.Username + " ] 离开了房间。");
foreach (Room room in Config.RoomList.Cast<Room>()) if (isUpdateRoomMaster && room.RoomMaster?.Id != 0 && room.Roomid != "-1")
{ {
QuitRoom(room.Roomid, room.RoomMaster.Id == User.Id); await Client.Send(SocketMessageType.UpdateRoomMaster, room);
} }
}
}
public async Task Kick(string msg)
{
await QuitRoom(InRoom.Roomid, InRoom.RoomMaster.Id == User.Id);
RemoveUser(); RemoveUser();
serverTask.Send(serverTask.Socket, SocketMessageType.Disconnect, msg); InRoom = General.HallInstance;
} await Send(SocketMessageType.Disconnect, msg);
Close(); await Close();
} }
public void Chat(string msg) public async Task ForceLogOut(string msg)
{ {
ServerHelper.WriteLine(msg); await QuitRoom(InRoom.Roomid, InRoom.RoomMaster.Id == User.Id);
foreach (ServerModel Client in Server.ClientList.Cast<ServerModel>()) InRoom = General.HallInstance;
{ await Send(SocketMessageType.ForceLogout, msg);
if (Room.Roomid == Client.Room.Roomid)
{
if (Client != null && User.Id != 0)
{
Client.Send(Client.Socket!, SocketMessageType.Chat, User.Username, DateTimeUtility.GetNowShortTime() + msg);
}
}
}
} }
public void SendSystemMessage(ShowMessageType showtype, string msg, string title, int autoclose, params string[] usernames) public async Task ForceLogOutDuplicateLogonUser()
{
foreach (ServerModel serverTask in Server.UserList.Cast<ServerModel>().Where(model => usernames.Length > 0 && usernames.Contains(model.UserName)))
{
if (serverTask != null && serverTask.Socket != null)
{
serverTask.Send(serverTask.Socket, SocketMessageType.System, showtype, msg, title, autoclose);
}
}
}
public void StartGame(string roomid, List<User> users, params string[] usernames)
{
Room room = General.HallInstance;
if (roomid != "-1")
{
room = Config.RoomList[roomid];
}
if (room.Roomid == "-1") return;
// 启动服务器
TaskUtility.NewTask(() =>
{
if (Config.GameModuleLoader != null && Config.GameModuleLoader.ServerModules.ContainsKey(room.GameModule))
{
NowGamingServer = Config.GameModuleLoader.GetServerMode(room.GameModule);
Dictionary<string, IServerModel> others = Server.UserList.Cast<IServerModel>().Where(model => usernames.Contains(model.User.Username) && model.User.Username != UserName).ToDictionary(k => k.User.Username, v => v);
if (NowGamingServer.StartServer(room.GameModule, room, users, this, others))
{
foreach (ServerModel serverTask in Server.UserList.Cast<ServerModel>().Where(model => usernames.Contains(model.User.Username)))
{
if (serverTask != null && serverTask.Socket != null)
{
Config.RoomList.CancelReady(roomid, serverTask.User);
serverTask.Send(serverTask.Socket, SocketMessageType.StartGame, room, users);
}
}
}
}
});
}
public void IntoRoom(string roomid)
{
Room = Config.RoomList[roomid];
foreach (ServerModel Client in Server.ClientList.Cast<ServerModel>().Where(c => c != null && c.Socket != null && roomid == c.Room.Roomid))
{
if (User.Id != 0)
{
Client.Send(Client.Socket!, SocketMessageType.Chat, User.Username, DateTimeUtility.GetNowShortTime() + " [ " + User.Username + " ] 进入了房间。");
}
}
}
public void UpdateRoomMaster(Room Room, bool bolIsUpdateRoomMaster = false)
{
foreach (ServerModel Client in Server.ClientList.Cast<ServerModel>().Where(c => c != null && c.Socket != null && Room.Roomid == c.Room.Roomid))
{
if (User.Id != 0)
{
Client.Send(Client.Socket!, SocketMessageType.Chat, User.Username, DateTimeUtility.GetNowShortTime() + " [ " + User.Username + " ] 离开了房间。");
if (bolIsUpdateRoomMaster && Room.RoomMaster?.Id != 0 && Room.Roomid != "-1")
{
Client.Send(Client.Socket!, SocketMessageType.UpdateRoomMaster, Room);
}
}
}
}
public bool HeartBeat(ClientSocket socket)
{
return Send(socket, SocketMessageType.HeartBeat, "");
}
public void StartMatching(RoomType type, User user)
{
IsMatching = true;
ServerHelper.WriteLine(GetClientName() + " 开始匹配。类型:" + RoomSet.GetTypeString(type));
TaskUtility.NewTask(async () =>
{
if (IsMatching)
{
Room room = await MatchingRoom(type, user);
if (IsMatching && Socket != null)
{
Send(Socket, SocketMessageType.MatchRoom, room);
}
IsMatching = false;
}
}).OnError(e =>
{
ServerHelper.Error(e);
IsMatching = false;
});
}
public void StopMatching()
{
if (IsMatching)
{
ServerHelper.WriteLine(GetClientName() + " 取消了匹配。");
IsMatching = false;
}
}
private async Task<Room> MatchingRoom(RoomType roomtype, User user)
{
int i = 1;
int time = 0;
while (IsMatching)
{
// 先列出符合条件的房间
List<Room> targets = Config.RoomList.ListRoom.Where(r => r.RoomType == roomtype).ToList();
// 匹配Elo
foreach (Room room in targets)
{
// 计算房间平均Elo
List<User> players = Config.RoomList.GetPlayerList(room.Roomid);
if (players.Count > 0)
{
decimal avgelo = players.Sum(u => u.Statistics.EloStats.ContainsKey(0) ? u.Statistics.EloStats[0] : 0M) / players.Count;
decimal userelo = user.Statistics.EloStats.ContainsKey(0) ? user.Statistics.EloStats[0] : 0M;
if (userelo >= avgelo - (300 * i) && userelo <= avgelo + (300 * i))
{
return room;
}
}
}
if (!IsMatching) break;
// 等待10秒
await Task.Delay(10 * 1000);
time += 10 * 1000;
if (time >= 50 * 1000)
{
// 50秒后不再匹配Elo直接返回第一个房间
if (targets.Count > 0)
{
return targets[0];
}
break;
}
i++;
}
return General.HallInstance;
}
private void KickUser()
{ {
if (User.Id != 0) if (User.Id != 0)
{ {
string user = User.Username; string user = User.Username;
if (Server.ContainsUser(user)) if (Listener.UserList.ContainsKey(user))
{ {
ServerHelper.WriteLine("OnlinePlayers: 玩家 " + user + " 重复登录!"); ServerHelper.WriteLine("OnlinePlayers: 玩家 " + user + " 重复登录!");
ForceLogOut("您的账号在别处登录,已强制下线。"); await ForceLogOut("您的账号在别处登录,已强制下线。");
} }
} }
} }
private bool AddUser() public bool AddUser()
{ {
if (User.Id != 0 && this != null) if (User.Id != 0 && this != null)
{ {
Server.AddUser(User.Username, this); Listener.UserList.Add(User.Username, this);
UserName = User.Username; _username = User.Username;
ServerHelper.WriteLine("OnlinePlayers: 玩家 " + User.Username + " 已添加"); ServerHelper.WriteLine("OnlinePlayers: 玩家 " + User.Username + " 已添加");
// 更新最后登录时间、IP地址
_loginTime = DateTime.Now.Ticks;
SQLHelper?.Execute(UserQuery.Update_CheckLogin(_username, Socket?.ClientIP.Split(':')[0] ?? "127.0.0.1"));
return true; return true;
} }
return false; return false;
} }
private bool RemoveUser() public bool RemoveUser()
{ {
if (User.Id != 0 && this != null) if (User.Id != 0 && this != null)
{ {
LogoutTime = DateTime.Now.Ticks; _logoutTime = DateTime.Now.Ticks;
int TotalMinutes = Convert.ToInt32((new DateTime(LogoutTime) - new DateTime(LoginTime)).TotalMinutes); int TotalMinutes = Convert.ToInt32((new DateTime(_logoutTime) - new DateTime(_loginTime)).TotalMinutes);
SQLHelper?.Execute(UserQuery.Update_GameTime(User.Username, TotalMinutes)); SQLHelper?.Execute(UserQuery.Update_GameTime(User.Username, TotalMinutes));
if (SQLHelper?.Result == SQLResult.Success) if (SQLHelper != null && SQLHelper.Result == SQLResult.Success)
{ {
ServerHelper.WriteLine("OnlinePlayers: 玩家 " + User.Username + " 本次已游玩" + TotalMinutes + "分钟"); ServerHelper.WriteLine("OnlinePlayers: 玩家 " + User.Username + " 本次已游玩" + TotalMinutes + "分钟");
} }
else ServerHelper.WriteLine("OnlinePlayers: 无法更新玩家 " + User.Username + " 的游戏时长"); else ServerHelper.WriteLine("OnlinePlayers: 无法更新玩家 " + User.Username + " 的游戏时长");
if (Server.RemoveUser(User.Username)) if (Listener.UserList.Remove(User.Username))
{ {
ServerHelper.WriteLine("OnlinePlayers: 玩家 " + User.Username + " 已移除"); ServerHelper.WriteLine("OnlinePlayers: 玩家 " + User.Username + " 已移除");
_User = General.UnknownUserInstance; User = General.UnknownUserInstance;
return true; return true;
} }
else ServerHelper.WriteLine("OnlinePlayers: 移除玩家 " + User.Username + " 失败"); else ServerHelper.WriteLine("OnlinePlayers: 移除玩家 " + User.Username + " 失败");
@ -565,37 +430,65 @@ namespace Milimoe.FunGame.Server.Model
return false; return false;
} }
private void GetUsersCount() public void GetUsersCount()
{ {
ServerHelper.WriteLine($"目前在线客户端数量: {Server.ClientCount}(已登录的玩家数量:{Server.UserCount}"); ServerHelper.WriteLine($"{Listener.Name} 的目前在线客户端数量: {Listener.ClientList.Count}(已登录的玩家数量:{Listener.UserList.Count}");
} }
private void CreateStreamReader() protected virtual async Task<bool> Read(ISocketMessageProcessor socket)
{ {
Thread.Sleep(20); // 接收客户端消息
try
{
SocketObject[] objs = await socket.ReceiveAsync();
if (objs.Length == 0)
{
ServerHelper.WriteLine(GetClientName() + " 发送了空信息。");
return false;
}
foreach (SocketObject obj in objs)
{
await SocketMessageHandler(socket, obj);
}
return true;
}
catch (Exception e)
{
ServerHelper.WriteLine(GetClientName() + " 没有回应。");
ServerHelper.Error(e);
return false;
}
}
protected async Task CreateStreamReader()
{
await Task.Delay(20);
ServerHelper.WriteLine("Creating: StreamReader -> " + GetClientName() + " ...OK"); ServerHelper.WriteLine("Creating: StreamReader -> " + GetClientName() + " ...OK");
while (Running) while (Running)
{ {
if (Socket != null) if (Socket != null)
{ {
if (!Read(Socket)) if (!await Read(Socket))
{ {
FailedTimes++; _failedTimes++;
if (FailedTimes >= Config.MaxConnectionFaileds) if (_failedTimes >= Config.MaxConnectionFaileds)
{ {
RemoveUser(); RemoveUser();
Close(); await Close();
ServerHelper.WriteLine(GetClientName() + " Error -> Too Many Faileds."); ServerHelper.WriteLine(GetClientName() + " Error -> Too Many Faileds.");
ServerHelper.WriteLine(GetClientName() + " Close -> StreamReader is Closed."); ServerHelper.WriteLine(GetClientName() + " Close -> StreamReader is Closed.");
break; break;
} }
} }
else if (FailedTimes - 1 >= 0) FailedTimes--; else if (_failedTimes - 1 >= 0) _failedTimes--;
} }
else else
{ {
RemoveUser(); RemoveUser();
Close(); await Close();
ServerHelper.WriteLine(GetClientName() + " Error -> Socket is Closed."); ServerHelper.WriteLine(GetClientName() + " Error -> Socket is Closed.");
ServerHelper.WriteLine(GetClientName() + " Close -> StringStream is Closed."); ServerHelper.WriteLine(GetClientName() + " Close -> StringStream is Closed.");
break; break;
@ -603,41 +496,40 @@ namespace Milimoe.FunGame.Server.Model
} }
} }
private void CreatePeriodicalQuerier() protected async Task CreatePeriodicalQuerier()
{ {
Thread.Sleep(20); await Task.Delay(20);
ServerHelper.WriteLine("Creating: PeriodicalQuerier -> " + GetClientName() + " ...OK"); ServerHelper.WriteLine("Creating: PeriodicalQuerier -> " + GetClientName() + " ...OK");
while (Running) while (Running)
{ {
// 每两小时触发一次SQL服务器的心跳查询防止SQL服务器掉线 // 每两小时触发一次SQL服务器的心跳查询防止SQL服务器掉线
try try
{ {
Thread.Sleep(2 * 1000 * 3600); await Task.Delay(2 * 1000 * 3600);
SQLHelper?.ExecuteDataSet(UserQuery.Select_IsExistUsername(UserName)); SQLHelper?.ExecuteDataSet(UserQuery.Select_IsExistUsername(_username));
} }
catch (Exception e) catch (Exception e)
{ {
ServerHelper.Error(e); ServerHelper.Error(e);
RemoveUser(); RemoveUser();
Close(); await Close();
ServerHelper.WriteLine(GetClientName() + " Error -> Socket is Closed."); ServerHelper.WriteLine(GetClientName() + " Error -> Socket is Closed.");
ServerHelper.WriteLine(GetClientName() + " Close -> StringStream is Closed."); ServerHelper.WriteLine(GetClientName() + " Close -> StringStream is Closed.");
} }
} }
} }
private void Close() protected async Task Close()
{ {
try try
{ {
SQLHelper?.Close(); SQLHelper?.Close();
_SQLHelper = null; _sqlHelper = null;
MailSender?.Dispose(); MailSender?.Dispose();
_MailSender = null; _mailer = null;
Socket?.Close(); await Socket.CloseAsync();
_Socket = null; _running = false;
_Running = false; Listener.ClientList.Remove(ClientName);
Server.RemoveClient(ClientName);
Config.OnlinePlayerCount--; Config.OnlinePlayerCount--;
GetUsersCount(); GetUsersCount();
} }

View File

@ -2,11 +2,13 @@
using System.Text; using System.Text;
using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Library.SQLScript.Common; using Milimoe.FunGame.Core.Library.SQLScript.Common;
using Milimoe.FunGame.Core.Library.SQLScript.Entity; using Milimoe.FunGame.Core.Library.SQLScript.Entity;
using Milimoe.FunGame.Core.Model; using Milimoe.FunGame.Core.Model;
using Milimoe.FunGame.Server.Utility; using Milimoe.FunGame.Server.Utility;
using Milimoe.FunGame.Server.Utility.DataUtility;
namespace Milimoe.FunGame.Server.Others namespace Milimoe.FunGame.Server.Others
{ {
@ -18,10 +20,35 @@ namespace Milimoe.FunGame.Server.Others
public static string ServerName { get; set; } = "FunGame Server"; public static string ServerName { get; set; } = "FunGame Server";
/// <summary> /// <summary>
/// 默认端口 /// Socket 端口
/// </summary> /// </summary>
public static int ServerPort { get; set; } = 22222; public static int ServerPort { get; set; } = 22222;
/// <summary>
/// 使用 WebSocket
/// </summary>
public static bool UseWebSocket { get; set; } = false;
/// <summary>
/// WebSocket 监听地址
/// </summary>
public static string WebSocketAddress { get; set; } = "localhost";
/// <summary>
/// WebSocket 端口
/// </summary>
public static int WebSocketPort { get; set; } = 22222;
/// <summary>
/// WebSocket 监听子路径
/// </summary>
public static string WebSocketSubUrl { get; set; } = "ws";
/// <summary>
/// WebSocket 开启 SSL
/// </summary>
public static bool WebSocketSSL { get; set; } = false;
/// <summary> /// <summary>
/// 默认状态1可连接 0不可连接 -1不可用 /// 默认状态1可连接 0不可连接 -1不可用
/// </summary> /// </summary>
@ -50,7 +77,7 @@ namespace Milimoe.FunGame.Server.Others
/// <summary> /// <summary>
/// 禁止连接的黑名单 /// 禁止连接的黑名单
/// </summary> /// </summary>
public static string ServerBannedList { get; set; } = ""; public static List<string> ServerBannedList { get; set; } = [];
/// <summary> /// <summary>
/// 最多接受连接的玩家数量 /// 最多接受连接的玩家数量
@ -95,7 +122,7 @@ namespace Milimoe.FunGame.Server.Others
/// <summary> /// <summary>
/// 是否运行数据库模式 /// 是否运行数据库模式
/// </summary> /// </summary>
public static bool SQLMode { get; set; } = false; public static SQLMode SQLMode { get; set; } = SQLMode.None;
/// <summary> /// <summary>
/// Server实际安装的模组 /// Server实际安装的模组
@ -114,7 +141,7 @@ namespace Milimoe.FunGame.Server.Others
{ {
get get
{ {
if (_SQLHelper is null) throw new MySQLConfigException(); if (_SQLHelper is null) throw new SQLServiceException();
return _SQLHelper; return _SQLHelper;
} }
} }
@ -127,27 +154,78 @@ namespace Milimoe.FunGame.Server.Others
public static void InitSQLHelper() public static void InitSQLHelper()
{ {
try try
{
if (INIHelper.ExistINIFile())
{
if (INIHelper.ReadINI("MySQL", "UseMySQL").Trim() == "true")
{ {
_SQLHelper = new MySQLHelper("", false); _SQLHelper = new MySQLHelper("", false);
if (((MySQLHelper)_SQLHelper).Connection != null) if (((MySQLHelper)_SQLHelper).Connection != null)
{ {
SQLMode = true; SQLMode = _SQLHelper.Mode;
ServerLogin(); ServerLogin();
ClearRoomList(); ClearRoomList();
} }
} }
else if (INIHelper.ReadINI("SQLite", "UseSQLite").Trim() == "true")
{
_SQLHelper = new SQLiteHelper();
SQLMode = _SQLHelper.Mode;
ServerLogin();
ClearRoomList();
}
else
{
SQLMode = SQLMode.None;
ServerHelper.WriteLine("未开启 SQL 服务,某些请求将无法处理。", InvokeMessageType.Warning);
}
}
}
catch (Exception e) catch (Exception e)
{ {
ServerHelper.Error(e); ServerHelper.Error(e);
} }
} }
public static bool GetGameModuleList()
{
List<string> supported = [];
// 构建AddonController
Hashtable delegates = [];
delegates.Add("WriteLine", new Action<string>(msg => ServerHelper.WriteLine(msg, InvokeMessageType.GameModule)));
delegates.Add("Error", new Action<Exception>(ServerHelper.Error));
// 读取modules目录下的模组
GameModuleLoader = GameModuleLoader.LoadGameModules(FunGameType, delegates);
foreach (GameModuleServer module in GameModuleLoader.ModuleServers.Values)
{
bool check = true;
// 检查模组是否有相对应的地图
if (!GameModuleLoader.Maps.ContainsKey(module.DefaultMap))
{
ServerHelper.WriteLine("GameModule Load Failed: " + module + " 没有找到相对应的地图,加载失败", InvokeMessageType.Error);
check = false;
}
if (check)
{
supported.Add(module.Name);
}
}
// 设置全局
GameModuleSupported = supported.Distinct().ToArray();
foreach (string modename in GameModuleSupported)
{
ServerHelper.WriteLine("Loaded: " + modename, InvokeMessageType.GameModule);
}
return GameModuleSupported.Length > 0;
}
/// <summary> /// <summary>
/// 服务器启动登记 /// 服务器启动登记
/// </summary> /// </summary>
public static void ServerLogin() public static void ServerLogin()
{ {
if (SQLMode) if (SQLMode != SQLMode.None)
{ {
SQLHelper.Execute(ServerLoginLogs.Insert_ServerLoginLogs(ServerName, ServerKey)); SQLHelper.Execute(ServerLoginLogs.Insert_ServerLoginLogs(ServerName, ServerKey));
} }
@ -158,7 +236,7 @@ namespace Milimoe.FunGame.Server.Others
/// </summary> /// </summary>
public static void ClearRoomList() public static void ClearRoomList()
{ {
if (SQLMode) if (SQLMode != SQLMode.None)
{ {
SQLHelper.Execute(RoomQuery.Delete_Rooms()); SQLHelper.Execute(RoomQuery.Delete_Rooms());
} }

View File

@ -0,0 +1,52 @@
using Milimoe.FunGame.Core.Api.Utility;
namespace Milimoe.FunGame.Server.Utility
{
public class ConnectProperties
{
public static string Name { get; set; } = "";
public static string DataSource { get; set; } = "";
public static string Port { get; set; } = "";
public static string DataBase { get; set; } = "";
public static string User { get; set; } = "";
public static string Password { get; set; } = "";
/// <summary>
/// 读取MySQL服务器配置文件
/// </summary>
/// <returns></returns>
public static string GetConnectPropertiesForMySQL()
{
if (Name == "" && DataSource == "" && Port == "" && DataBase == "" && User == "" && Password == "")
{
if (INIHelper.ExistINIFile())
{
DataSource = INIHelper.ReadINI("MySQL", "DBServer");
Port = INIHelper.ReadINI("MySQL", "DBPort");
DataBase = INIHelper.ReadINI("MySQL", "DBName");
User = INIHelper.ReadINI("MySQL", "DBUser");
Password = INIHelper.ReadINI("MySQL", "DBPassword");
}
else ServerHelper.Error(new MySQLConfigException());
}
return "data source = " + DataSource + "; port = " + Port + "; database = " + DataBase + "; user = " + User + "; password = " + Password + "; charset = utf8mb4;";
}
/// <summary>
/// 读取SQLite服务器配置文件
/// </summary>
/// <returns></returns>
public static string GetConnectPropertiesForSQLite()
{
if (DataSource == "")
{
if (INIHelper.ExistINIFile())
{
DataSource = INIHelper.ReadINI("SQLite", "DataSource");
}
else ServerHelper.Error(new SQLServiceException());
}
return "data source=" + DataSource;
}
}
}

View File

@ -26,6 +26,10 @@ namespace Milimoe.FunGame.Server.Utility
Console.ForegroundColor = ConsoleColor.Yellow; Console.ForegroundColor = ConsoleColor.Yellow;
prefix = "[Api] "; prefix = "[Api] ";
break; break;
case InvokeMessageType.Warning:
Console.ForegroundColor = ConsoleColor.Yellow;
prefix = "[Warning] ";
break;
case InvokeMessageType.Interface: case InvokeMessageType.Interface:
Console.ForegroundColor = ConsoleColor.Magenta; Console.ForegroundColor = ConsoleColor.Magenta;
prefix = "[Interface] "; prefix = "[Interface] ";
@ -62,15 +66,18 @@ namespace Milimoe.FunGame.Server.Utility
public static void Write(string msg, InvokeMessageType type = InvokeMessageType.System) public static void Write(string msg, InvokeMessageType type = InvokeMessageType.System)
{ {
if (msg.Trim() != "") Console.Write("\r" + GetPrefix(type) + msg + "> "); if (msg.Trim() != "") Console.Write("\r" + GetPrefix(type) + msg + "> ");
Console.ResetColor();
} }
public static void WriteLine(string msg, InvokeMessageType type = InvokeMessageType.System) public static void WriteLine(string msg, InvokeMessageType type = InvokeMessageType.System)
{ {
if (msg.Trim() != "") Console.Write("\r" + GetPrefix(type) + msg + "\n\r> "); if (msg.Trim() != "") Console.Write("\r" + GetPrefix(type) + msg + "\n\r> ");
Console.ResetColor();
} }
public static void Type() public static void Type()
{ {
Console.ResetColor();
Console.Write("\r> "); Console.Write("\r> ");
} }
@ -86,7 +93,7 @@ namespace Milimoe.FunGame.Server.Utility
private static Hashtable GetServerSettingHashtable() private static Hashtable GetServerSettingHashtable()
{ {
Hashtable settings = new(); Hashtable settings = [];
if (INIHelper.ExistINIFile()) if (INIHelper.ExistINIFile())
{ {
settings.Add("Name", INIHelper.ReadINI("Server", "Name")); settings.Add("Name", INIHelper.ReadINI("Server", "Name"));
@ -99,6 +106,11 @@ namespace Milimoe.FunGame.Server.Utility
settings.Add("OfficialMail", INIHelper.ReadINI("ServerMail", "OfficialMail")); settings.Add("OfficialMail", INIHelper.ReadINI("ServerMail", "OfficialMail"));
settings.Add("SupportMail", INIHelper.ReadINI("ServerMail", "SupportMail")); settings.Add("SupportMail", INIHelper.ReadINI("ServerMail", "SupportMail"));
settings.Add("Port", Convert.ToInt32(INIHelper.ReadINI("Socket", "Port"))); settings.Add("Port", Convert.ToInt32(INIHelper.ReadINI("Socket", "Port")));
settings.Add("UseWebSocket", Convert.ToBoolean(INIHelper.ReadINI("Socket", "UseWebSocket")));
settings.Add("WebSocketAddress", Convert.ToString(INIHelper.ReadINI("Socket", "WebSocketAddress")));
settings.Add("WebSocketPort", Convert.ToInt32(INIHelper.ReadINI("Socket", "WebSocketPort")));
settings.Add("WebSocketSubUrl", Convert.ToString(INIHelper.ReadINI("Socket", "WebSocketSubUrl")));
settings.Add("WebSocketSSL", Convert.ToBoolean(INIHelper.ReadINI("Socket", "WebSocketSSL")));
settings.Add("MaxPlayer", Convert.ToInt32(INIHelper.ReadINI("Socket", "MaxPlayer"))); settings.Add("MaxPlayer", Convert.ToInt32(INIHelper.ReadINI("Socket", "MaxPlayer")));
settings.Add("MaxConnectFailed", Convert.ToInt32(INIHelper.ReadINI("Socket", "MaxConnectFailed"))); settings.Add("MaxConnectFailed", Convert.ToInt32(INIHelper.ReadINI("Socket", "MaxConnectFailed")));
} }
@ -124,7 +136,7 @@ namespace Milimoe.FunGame.Server.Utility
if (Describe != null) Config.ServerDescription = Describe; if (Describe != null) Config.ServerDescription = Describe;
if (Notice != null) Config.ServerNotice = Notice; if (Notice != null) Config.ServerNotice = Notice;
if (Key != null) Config.ServerKey = Key; if (Key != null) Config.ServerKey = Key;
if (BannedList != null) Config.ServerBannedList = BannedList; if (BannedList != null) Config.ServerBannedList = BannedList.Split(',').Select(s => s.Trim()).ToList();
string? OfficialMail = (string?)settings["OfficialMail"]; string? OfficialMail = (string?)settings["OfficialMail"];
string? SupportMail = (string?)settings["SupportMail"]; string? SupportMail = (string?)settings["SupportMail"];
@ -134,18 +146,28 @@ namespace Milimoe.FunGame.Server.Utility
int? Status = (int?)settings["Status"]; int? Status = (int?)settings["Status"];
int? Port = (int?)settings["Port"]; int? Port = (int?)settings["Port"];
bool? UseWebSocket = (bool?)settings["UseWebSocket"];
string? WebSocketAddress = (string?)settings["WebSocketAddress"];
int? WebSocketPort = (int?)settings["WebSocketPort"];
string? WebSocketSubUrl = (string?)settings["WebSocketSubUrl"];
bool? WebSocketSSL = (bool?)settings["WebSocketSSL"];
int? MaxPlayer = (int?)settings["MaxPlayer"]; int? MaxPlayer = (int?)settings["MaxPlayer"];
int? MaxConnectFailed = (int?)settings["MaxConnectFailed"]; int? MaxConnectFailed = (int?)settings["MaxConnectFailed"];
if (Status != null) Config.ServerStatus = (int)Status; if (Status != null) Config.ServerStatus = (int)Status;
if (Port != null) Config.ServerPort = (int)Port; if (Port != null) Config.ServerPort = (int)Port;
if (UseWebSocket != null) Config.UseWebSocket = (bool)UseWebSocket;
if (WebSocketAddress != null) Config.WebSocketAddress = WebSocketAddress;
if (WebSocketPort != null) Config.WebSocketPort = (int)WebSocketPort;
if (WebSocketSubUrl != null) Config.WebSocketSubUrl = WebSocketSubUrl;
if (WebSocketSSL != null) Config.WebSocketSSL = (bool)WebSocketSSL;
if (MaxPlayer != null) Config.MaxPlayers = (int)MaxPlayer; if (MaxPlayer != null) Config.MaxPlayers = (int)MaxPlayer;
if (MaxConnectFailed != null) Config.MaxConnectionFaileds = (int)MaxConnectFailed; if (MaxConnectFailed != null) Config.MaxConnectionFaileds = (int)MaxConnectFailed;
} }
} }
catch (Exception e) catch (Exception e)
{ {
ServerHelper.WriteLine(e.StackTrace ?? ""); Error(e);
} }
} }
@ -192,7 +214,7 @@ namespace Milimoe.FunGame.Server.Utility
if (SmtpPort > 0) return new MailSender(SenderMailAddress, SenderName, SenderPassword, SmtpHost, SmtpPort, OpenSSL); if (SmtpPort > 0) return new MailSender(SenderMailAddress, SenderName, SenderPassword, SmtpHost, SmtpPort, OpenSSL);
} }
} }
ServerHelper.WriteLine("Smtp服务处于关闭状态"); ServerHelper.WriteLine("SMTP 服务处于关闭状态", InvokeMessageType.Warning);
return null; return null;
} }
throw new SmtpHelperException(); throw new SmtpHelperException();

View File

@ -1,41 +1,8 @@
using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Model;
using Milimoe.FunGame.Core.Model;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
namespace Milimoe.FunGame.Server.Utility.DataUtility namespace Milimoe.FunGame.Server.Utility.DataUtility
{ {
public class ConnectProperties
{
public static string Name { get; set; } = "";
public static string DataSource { get; set; } = "";
public static string Port { get; set; } = "";
public static string DataBase { get; set; } = "";
public static string User { get; set; } = "";
public static string Password { get; set; } = "";
/// <summary>
/// 读取MySQL服务器配置文件
/// </summary>
/// <returns></returns>
public static string GetConnectProperties()
{
if (Name == "" && DataSource == "" && Port == "" && DataBase == "" && User == "" && Password == "")
{
if (INIHelper.ExistINIFile())
{
DataSource = INIHelper.ReadINI("MySQL", "DBServer");
Port = INIHelper.ReadINI("MySQL", "DBPort");
DataBase = INIHelper.ReadINI("MySQL", "DBName");
User = INIHelper.ReadINI("MySQL", "DBUser");
Password = INIHelper.ReadINI("MySQL", "DBPassword");
return "data source = " + DataSource + "; port = " + Port + "; database = " + DataBase + "; user = " + User + "; password = " + Password + "; charset = utf8mb4;";
}
else ServerHelper.Error(new MySQLConfigException());
}
return "data source = " + DataSource + "; port = " + Port + "; database = " + DataBase + "; user = " + User + "; password = " + Password + "; charset = utf8mb4;";
}
}
public class MySQLConnection public class MySQLConnection
{ {
public MySqlConnection? Connection public MySqlConnection? Connection
@ -87,15 +54,15 @@ namespace Milimoe.FunGame.Server.Utility.DataUtility
{ {
try try
{ {
string _GetConnection = ConnectProperties.GetConnectProperties(); string connectionString = ConnectProperties.GetConnectPropertiesForMySQL();
if (_GetConnection != null) if (connectionString != null)
{ {
string[] DataSetting = _GetConnection.Split(";"); string[] strings = connectionString.Split(";");
if (DataSetting.Length > 1 && DataSetting[0].Length > 14 && DataSetting[1].Length > 8) if (strings.Length > 1 && strings[0].Length > 14 && strings[1].Length > 8)
{ {
ServerHelper.WriteLine("Connect -> MySQL://" + DataSetting[0][14..] + ":" + DataSetting[1][8..]); ServerHelper.WriteLine("Connect -> MySQL://" + strings[0][14..] + ":" + strings[1][8..]);
} }
_Connection = new MySqlConnection(_GetConnection); _Connection = new MySqlConnection(connectionString);
_Connection.Open(); _Connection.Open();
if (_Connection.State == System.Data.ConnectionState.Open) if (_Connection.State == System.Data.ConnectionState.Open)
{ {

View File

@ -1,8 +1,8 @@
using System.Data; using System.Data;
using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model; using Milimoe.FunGame.Core.Model;
using Milimoe.FunGame.Server.Model;
using Milimoe.FunGame.Server.Others; using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility.DataUtility; using Milimoe.FunGame.Server.Utility.DataUtility;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
@ -12,6 +12,7 @@ namespace Milimoe.FunGame.Server.Utility
public class MySQLHelper : SQLHelper public class MySQLHelper : SQLHelper
{ {
public override FunGameInfo.FunGame FunGameType => Config.FunGameType; public override FunGameInfo.FunGame FunGameType => Config.FunGameType;
public override SQLMode Mode => SQLMode.MySQL;
public override string Script { get; set; } = ""; public override string Script { get; set; } = "";
public override CommandType CommandType { get; set; } = CommandType.Text; public override CommandType CommandType { get; set; } = CommandType.Text;
public override SQLResult Result => _Result; public override SQLResult Result => _Result;
@ -27,7 +28,7 @@ namespace Milimoe.FunGame.Server.Utility
private DataSet _DataSet = new(); private DataSet _DataSet = new();
private MySQLConnection? _Connection; private MySQLConnection? _Connection;
private MySqlTransaction? _Transaction; private MySqlTransaction? _Transaction;
private readonly ServerModel? ServerModel; private readonly IServerModel? ServerModel;
private readonly bool _IsOneTime = false; private readonly bool _IsOneTime = false;
/// <summary> /// <summary>
@ -123,7 +124,7 @@ namespace Milimoe.FunGame.Server.Utility
/// 创建为SocketModel服务的SQLHelper /// 创建为SocketModel服务的SQLHelper
/// </summary> /// </summary>
/// <param name="ServerModel">SocketModel</param> /// <param name="ServerModel">SocketModel</param>
public MySQLHelper(ServerModel ServerModel) public MySQLHelper(IServerModel ServerModel)
{ {
this.ServerModel = ServerModel; this.ServerModel = ServerModel;
Script = ""; Script = "";

View File

@ -0,0 +1,229 @@
using System.Data;
using Microsoft.Data.Sqlite;
using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Server.Utility.DataUtility
{
public class SQLiteHelper : SQLHelper
{
public override FunGameInfo.FunGame FunGameType { get; } = FunGameInfo.FunGame.FunGame_Server;
public override SQLMode Mode { get; } = SQLMode.SQLite;
public override string Script { get; set; } = "";
public override CommandType CommandType { get; set; } = CommandType.Text;
public override SQLResult Result => _result;
public override SQLServerInfo ServerInfo => _serverInfo ?? SQLServerInfo.Create();
public override int UpdateRows => _updateRows;
public override DataSet DataSet => _dataSet;
private readonly SqliteConnection _connection;
private SqliteTransaction? _transaction;
private DataSet _dataSet = new();
private SQLResult _result = SQLResult.NotFound;
private readonly SQLServerInfo? _serverInfo;
private int _updateRows = 0;
private readonly string _connectionString = "";
public SQLiteHelper(string script = "", CommandType type = CommandType.Text)
{
Script = script;
CommandType = type;
_connectionString = ConnectProperties.GetConnectPropertiesForSQLite();
string[] strings = _connectionString.Split("=");
if (strings.Length > 1)
{
ServerHelper.WriteLine("Connect -> SQLite://" + strings[1]);
_serverInfo = SQLServerInfo.Create(database: strings[1]);
}
_connection = new SqliteConnection(_connectionString);
}
/// <summary>
/// 打开数据库连接
/// </summary>
private void OpenConnection()
{
if (_connection.State != ConnectionState.Open)
{
_connection.Open();
}
}
/// <summary>
/// 关闭数据库连接
/// </summary>
public override void Close()
{
_transaction?.Dispose();
if (_connection.State != ConnectionState.Closed)
{
_connection.Close();
}
}
/// <summary>
/// 执行一个命令
/// </summary>
/// <returns></returns>
public override int Execute()
{
return Execute(Script);
}
/// <summary>
/// 执行一个指定的命令
/// </summary>
/// <param name="script"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public override int Execute(string script)
{
try
{
OpenConnection();
ServerHelper.WriteLine("SQLQuery -> " + script, InvokeMessageType.Api);
using SqliteCommand command = new(script, _connection);
command.CommandType = CommandType;
if (_transaction != null)
{
command.Transaction = _transaction;
}
_updateRows = command.ExecuteNonQuery();
_result = SQLResult.Success;
Close();
return UpdateRows;
}
catch (Exception ex)
{
_result = SQLResult.Fail;
throw new Exception($"SQL execution failed: {ex.Message}", ex);
}
}
/// <summary>
/// 查询DataSet
/// </summary>
/// <returns></returns>
public override DataSet ExecuteDataSet()
{
return ExecuteDataSet(Script);
}
/// <summary>
/// 执行指定的命令查询DataSet
/// </summary>
/// <param name="script"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public override DataSet ExecuteDataSet(string script)
{
try
{
OpenConnection();
ServerHelper.WriteLine("SQLQuery -> " + script, InvokeMessageType.Api);
using SqliteCommand command = new(script, _connection)
{
CommandType = CommandType
};
using SqliteDataReader reader = command.ExecuteReader();
_dataSet = new();
DataTable table = new();
table.Load(reader);
_dataSet.Tables.Add(table);
Close();
return _dataSet;
}
catch (Exception ex)
{
_result = SQLResult.Fail;
throw new Exception($"SQL execution failed: {ex.Message}", ex);
}
}
/// <summary>
/// 执行指定的命令查询DataRow
/// </summary>
/// <returns></returns>
public override DataRow? ExecuteDataRow()
{
return ExecuteDataRow(Script);
}
/// <summary>
/// 执行指定的命令查询DataRow
/// </summary>
/// <param name="script"></param>
/// <returns></returns>
public override DataRow? ExecuteDataRow(string script)
{
OpenConnection();
ServerHelper.WriteLine("SQLQuery -> " + script, InvokeMessageType.Api);
DataSet dataSet = ExecuteDataSet(script);
if (dataSet.Tables.Count > 0 && dataSet.Tables[0].Rows.Count > 0)
{
Close();
return dataSet.Tables[0].Rows[0];
}
Close();
return null;
}
/// <summary>
/// 创建一个SQL事务
/// </summary>
public override void NewTransaction()
{
OpenConnection();
_transaction = _connection.BeginTransaction();
}
/// <summary>
/// 提交事务
/// </summary>
/// <exception cref="Exception"></exception>
public override void Commit()
{
try
{
_transaction?.Commit();
Close();
_result = SQLResult.Success;
}
catch (Exception ex)
{
_result = SQLResult.Fail;
throw new Exception($"Transaction commit failed: {ex.Message}", ex);
}
}
/// <summary>
/// 回滚事务
/// </summary>
/// <exception cref="Exception"></exception>
public override void Rollback()
{
try
{
_transaction?.Rollback();
Close();
_result = SQLResult.Success;
}
catch (Exception ex)
{
_result = SQLResult.Fail;
throw new Exception($"Transaction rollback failed: {ex.Message}", ex);
}
}
/// <summary>
/// 资源清理
/// </summary>
public void Dispose()
{
_transaction?.Dispose();
_connection.Dispose();
}
}
}

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="FunGameServer"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 清单选项
如果想要更改 Windows 用户帐户控制级别,请使用
以下节点之一替换 requestedExecutionLevel 节点。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
元素。
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
Windows 版本的列表。取消评论适当的元素,
Windows 将自动选择最兼容的环境。 -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI无需
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@ -0,0 +1,49 @@
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.WebAPI.Architecture
{
public class RESTfulAPI(Guid token, string clientip, string clientname) : ISocketMessageProcessor
{
public Type InstanceType => typeof(RESTfulAPI);
public Guid Token { get; init; } = token;
public string ClientIP { get; init; } = clientip;
public string ClientName { get; init; } = clientname;
public void Close()
{
}
public async Task CloseAsync()
{
await Task.Delay(100);
}
public SocketObject[] Receive()
{
return [];
}
public async Task<SocketObject[]> ReceiveAsync()
{
await Task.Delay(100);
return [];
}
public SocketResult Send(SocketMessageType type, params object[] objs)
{
return SocketResult.Success;
}
public async Task<SocketResult> SendAsync(SocketMessageType type, params object[] objs)
{
await Task.Delay(100);
return SocketResult.Success;
}
}
}

View File

@ -0,0 +1,21 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
namespace Milimoe.FunGame.WebAPI.Architecture
{
public class RESTfulAPIListener : ISocketListener<RESTfulAPI>
{
public string Name => "RESTfulAPIListener";
public ConcurrentModelList<IServerModel> ClientList { get; } = [];
public ConcurrentModelList<IServerModel> UserList { get; } = [];
public List<string> BannedList { get; } = [];
public void Close()
{
}
}
}

View File

@ -0,0 +1,22 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Network;
namespace Milimoe.FunGame.WebAPI.Architecture
{
public class WebAPIListener : ISocketListener<ServerWebSocket>
{
public string Name => "WebAPIListener";
public ConcurrentModelList<IServerModel> ClientList { get; } = [];
public ConcurrentModelList<IServerModel> UserList { get; } = [];
public List<string> BannedList { get; } = [];
public void Close()
{
}
}
}

View File

@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.WebAPI.Architecture;
using Milimoe.FunGame.WebAPI.Models;
namespace Milimoe.FunGame.WebAPI.Controllers
{
[ApiController]
[Route("[controller]")]
[Authorize]
public class PostDataController(ILogger<PostDataController> logger) : ControllerBase
{
public static Dictionary<Guid, SocketObject> ResultDatas { get; } = [];
private readonly ILogger<PostDataController> _logger = logger;
[HttpPost("{username}", Name = "username")]
public async Task<IActionResult> Post(string username, [FromBody] SocketObject obj)
{
try
{
RESTfulAPIListener? apiListener = Singleton.Get<RESTfulAPIListener>();
if (apiListener != null && apiListener.UserList.ContainsKey(username))
{
RESTfulAPIModel model = (RESTfulAPIModel)apiListener.UserList[username];
if (model.LastRequestID == Guid.Empty)
{
Guid uid = Guid.NewGuid();
model.LastRequestID = uid;
await model.SocketMessageHandler(model.Socket, obj);
model.LastRequestID = Guid.Empty;
if (ResultDatas.TryGetValue(uid, out SocketObject list))
{
return Ok(list);
}
else
{
return NotFound();
}
}
else
{
Ok(new SocketObject(SocketMessageType.System, model.Token, "请求未执行完毕,请等待!"));
}
}
return NotFound();
}
catch (Exception e)
{
_logger.LogError(e, "Error during post data");
return StatusCode(500, "服务器内部错误");
}
}
}
}

View File

@ -0,0 +1,74 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.SQLScript.Entity;
using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility;
using Milimoe.FunGame.WebAPI.Architecture;
using Milimoe.FunGame.WebAPI.Models;
using Milimoe.FunGame.WebAPI.Services;
namespace Milimoe.FunGame.WebAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class UserController(JWTService jwtTokenService) : ControllerBase
{
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginModel loginModel)
{
string msg = "用户名或密码不正确。";
string clientip = HttpContext.Connection.RemoteIpAddress?.ToString() + ":" + HttpContext.Connection.RemotePort;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 通过 RESTful API 连接至服务器,正在登录 . . .", Core.Library.Constant.InvokeMessageType.Core);
string username = loginModel.Username;
string password = loginModel.Password;
RESTfulAPIListener? apiListener = Singleton.Get<RESTfulAPIListener>();
if (apiListener != null)
{
// 移除旧模型
if (apiListener.UserList.ContainsKey(username))
{
await apiListener.UserList[username].Send(Core.Library.Constant.SocketMessageType.Disconnect);
}
// 创建新模型
if (!apiListener.UserList.ContainsKey(username))
{
Config.ConnectingPlayerCount++;
RESTfulAPIModel model = new(apiListener, clientip);
model.SetClientName(clientip);
// 创建User对象
if (model.SQLHelper != null)
{
model.SQLHelper.ExecuteDataSet(UserQuery.Select_Users_LoginQuery(username, password));
Core.Entity.User user = Factory.GetUser(model.SQLHelper?.DataSet ?? new());
if (user.Id != 0)
{
model.User = user;
// 检查有没有重复登录的情况
await model.ForceLogOutDuplicateLogonUser();
// 添加至玩家列表
model.AddUser();
model.GetUsersCount();
string token = jwtTokenService.GenerateToken(username);
Config.ConnectingPlayerCount--;
return Ok(new { BearerToken = token, InternalToken = model.Token });
}
}
else msg = "服务器暂时无法处理登录请求。";
await model.Send(Core.Library.Constant.SocketMessageType.Disconnect);
}
}
Config.ConnectingPlayerCount--;
ServerHelper.WriteLine(msg, Core.Library.Constant.InvokeMessageType.Core);
return Unauthorized(msg);
}
[HttpPost("refresh")]
[Authorize]
public IActionResult Refresh([FromBody] LoginModel request)
{
return Ok(jwtTokenService.GenerateToken(request.Username));
}
}
}

View File

@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Milimoe.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
<BaseOutputPath>..\bin\</BaseOutputPath>
<AssemblyName>FunGameWebAPI</AssemblyName>
<ApplicationIcon>Images\logo.ico</ApplicationIcon>
<Authors>Milimoe</Authors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<Content Include="Images\logo.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FunGame.Server\FunGame.Server.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="FunGame.Core">
<HintPath>..\..\FunGame.Core\bin\Debug\net8.0\FunGame.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Update="Images\logo.ico">
<PackagePath>\</PackagePath>
<Pack>True</Pack>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@FunGame.WebAPI_HostAddress = http://localhost:5117
GET {{FunGame.WebAPI_HostAddress}}/weatherforecast/
Accept: application/json
###

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,8 @@
namespace Milimoe.FunGame.WebAPI.Models
{
public class LoginModel(string username, string password)
{
public string Username { get; set; } = username;
public string Password { get; set; } = password;
}
}

View File

@ -0,0 +1,72 @@
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Model;
using Milimoe.FunGame.Server.Utility;
using Milimoe.FunGame.WebAPI.Architecture;
using Milimoe.FunGame.WebAPI.Controllers;
namespace Milimoe.FunGame.WebAPI.Models
{
public class RESTfulAPIModel(ISocketListener<RESTfulAPI> server, string clientip) : ServerModel<RESTfulAPI>(server, new RESTfulAPI(Guid.NewGuid(), clientip, clientip), false)
{
public Guid LastRequestID { get; set; } = Guid.Empty;
public List<SocketObject> ToBeSent { get; set; } = [];
public override async Task<bool> Send(SocketMessageType type, params object[] objs)
{
if (type == SocketMessageType.Disconnect || type == SocketMessageType.ForceLogout)
{
RemoveUser();
await Close();
return true;
}
if (type != SocketMessageType.HeartBeat)
{
SocketObject obj = new(type, Token, objs);
if (LastRequestID != Guid.Empty)
{
return PostDataController.ResultDatas.TryAdd(LastRequestID, obj);
}
else
{
ToBeSent.Add(obj);
return true;
}
}
return false;
}
public override async Task<bool> SocketMessageHandler(ISocketMessageProcessor socket, SocketObject obj)
{
// 读取收到的消息
SocketMessageType type = obj.SocketType;
Guid token = obj.Token;
string msg = "";
// 验证Token
if (type != SocketMessageType.HeartBeat && token != socket.Token)
{
ServerHelper.WriteLine(GetClientName() + " 使用了非法方式传输消息,服务器拒绝回应 -> [" + SocketSet.GetTypeString(type) + "]");
return false;
}
if (type == SocketMessageType.DataRequest)
{
return await DataRequestHandler(obj);
}
if (type == SocketMessageType.GamingRequest)
{
return await GamingRequestHandler(obj);
}
if (type == SocketMessageType.Gaming)
{
return await GamingMessageHandler(obj);
}
return await Send(type, msg);
}
}
}

267
FunGame.WebAPI/Program.cs Normal file
View File

@ -0,0 +1,267 @@
using System.Net.WebSockets;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Unicode;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Common.JsonConverter;
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));
ServerHelper.WriteLine("正在读取配置文件并初始化服务 . . .");
// 初始化命令菜单
ServerHelper.InitOrderList();
// 读取游戏模组
if (!Config.GetGameModuleList())
{
ServerHelper.WriteLine("服务器似乎未安装任何游戏模组,请检查是否正确安装它们。");
}
// 检查是否存在配置文件
if (!INIHelper.ExistINIFile())
{
ServerHelper.WriteLine("未检测到配置文件,将自动创建配置文件 . . .");
INIHelper.Init(Config.FunGameType);
ServerHelper.WriteLine("配置文件FunGame.ini创建成功请修改该配置文件然后重启服务器。");
Console.ReadKey();
return;
}
else
{
ServerHelper.GetServerSettings();
}
// 创建单例
RESTfulAPIListener apiListener = new();
Singleton.Add(apiListener);
ServerHelper.WriteLine("请输入 help 来获取帮助,输入 quit 关闭服务器。");
// 创建全局SQLHelper
Config.InitSQLHelper();
ServerHelper.WriteLine("正在启动 Web API 监听 . . .");
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
options.JsonSerializerOptions.Converters.Add(new DateTimeConverter());
options.JsonSerializerOptions.Converters.Add(new DataTableConverter());
options.JsonSerializerOptions.Converters.Add(new DataSetConverter());
options.JsonSerializerOptions.Converters.Add(new UserConverter());
options.JsonSerializerOptions.Converters.Add(new RoomConverter());
options.JsonSerializerOptions.Converters.Add(new CharacterConverter());
options.JsonSerializerOptions.Converters.Add(new MagicResistanceConverter());
options.JsonSerializerOptions.Converters.Add(new EquipSlotConverter());
options.JsonSerializerOptions.Converters.Add(new SkillConverter());
options.JsonSerializerOptions.Converters.Add(new EffectConverter());
options.JsonSerializerOptions.Converters.Add(new ItemConverter());
});
// 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(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"))
};
});
WebApplication app = builder.Build();
// 启用 CORS
app.UseCors("AllowSpecificOrigin");
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
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() ?? "");
}
});
});
// 启用 WebSockets 中间件
WebSocketOptions webSocketOptions = new()
{
KeepAliveInterval = TimeSpan.FromMinutes(2) // 设置 WebSocket 的保活间隔
};
app.UseWebSockets(webSocketOptions);
// 路由到 WebSocket 处理器
app.Map("/ws", WebSocketConnectionHandler);
// 开始监听连接
listener.BannedList.AddRange(Config.ServerBannedList);
if (Config.ServerNotice != "")
Console.WriteLine("\n\n********** 服务器公告 **********\n\n" + Config.ServerNotice + "\n");
else
Console.WriteLine("无法读取服务器公告");
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);
}
}

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:45590",
"sslPort": 44356
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5117",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7162;http://localhost:5117",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,33 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.WebAPI.Services
{
public class JWTService(IConfiguration configuration)
{
public string GenerateToken(string username)
{
// 创建一个包含用户信息的声明
Claim[] claims = [
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
];
// 获取密钥和发行者
SymmetricSecurityKey key = new(General.DefaultEncoding.GetBytes(configuration["Jwt:Key"] ?? "undefined"));
SigningCredentials creds = new(key, SecurityAlgorithms.HmacSha256);
JwtSecurityToken token = new(
issuer: configuration["Jwt:Issuer"],
audience: configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(30), // 设置过期时间
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,19 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1AndHttp2AndHttp3"
}
},
"Jwt": {
"Key": "166afec8ff6e0c3a647c7230294ea10be39d5d217f37aa5195f795017403da730ce6313790335b4975d7387c14aaa06c52d1cd90b5ef47d1831b6d7d524a12bf",
"Issuer": "FunGame",
"Audience": "FunGame Web API"
}
}

View File

@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunGame.Server", "FunGame.S
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunGame.Implement", "FunGame.Implement\FunGame.Implement.csproj", "{F5BACA36-3DE2-450A-8518-E5DC29991875}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunGame.Implement", "FunGame.Implement\FunGame.Implement.csproj", "{F5BACA36-3DE2-450A-8518-E5DC29991875}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunGame.WebAPI", "FunGame.WebAPI\FunGame.WebAPI.csproj", "{F4C6F3E8-84EA-49B4-99B7-A886C56C44F2}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -21,6 +23,10 @@ Global
{F5BACA36-3DE2-450A-8518-E5DC29991875}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5BACA36-3DE2-450A-8518-E5DC29991875}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5BACA36-3DE2-450A-8518-E5DC29991875}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5BACA36-3DE2-450A-8518-E5DC29991875}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5BACA36-3DE2-450A-8518-E5DC29991875}.Release|Any CPU.Build.0 = Release|Any CPU {F5BACA36-3DE2-450A-8518-E5DC29991875}.Release|Any CPU.Build.0 = Release|Any CPU
{F4C6F3E8-84EA-49B4-99B7-A886C56C44F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4C6F3E8-84EA-49B4-99B7-A886C56C44F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4C6F3E8-84EA-49B4-99B7-A886C56C44F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4C6F3E8-84EA-49B4-99B7-A886C56C44F2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE