From 14ff58f4f4a0c0a2a41be993ec0301ea3001ff26 Mon Sep 17 00:00:00 2001
From: milimoe <110188673+milimoe@users.noreply.github.com>
Date: Fri, 4 Oct 2024 12:39:15 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=BB=9F?=
=?UTF-8?q?=E4=B8=80=E6=95=B0=E6=8D=AE=E8=AE=BF=E9=97=AE=E8=BF=9E=E6=8E=A5?=
=?UTF-8?q?=20(#37)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 添加 Web API 和 RESTful API 模式;
* 添加 SQLite 模式;
* 添加 ISocketMessageProcessor 和 ISocketListener<> 接口,用于统一数据访问;
* 重做了 ISocketModel;
* 完善了 WebSocket 的连接模式。
---
FunGame.Server/Controllers/Authenticator.cs | 6 +-
.../Controllers/ConnectController.cs | 134 +++
.../Controllers/DataRequestController.cs | 955 +++++++++++-------
FunGame.Server/FunGame.Server.csproj | 23 +-
FunGame.Server/Main.cs | 335 +++---
FunGame.Server/Models/ConsoleModel.cs | 28 +-
FunGame.Server/Models/ServerModel.cs | 656 +++++-------
FunGame.Server/Others/Config.cs | 100 +-
FunGame.Server/Utilities/ConnectProperties.cs | 52 +
FunGame.Server/Utilities/General.cs | 30 +-
.../Utilities/{ => MySQL}/MySQLConnection.cs | 47 +-
.../Utilities/{ => MySQL}/MySQLHelper.cs | 7 +-
.../Utilities/{ => MySQL}/MySQLManager.cs | 0
.../Utilities/SQLite/SQLiteHelper.cs | 229 +++++
FunGame.Server/app.manifest | 79 ++
FunGame.WebAPI/Architecture/RESTfulAPI.cs | 49 +
.../Architecture/RESTfulAPIListener.cs | 21 +
FunGame.WebAPI/Architecture/WebAPIListener.cs | 22 +
.../Controllers/PostDataController.cs | 58 ++
FunGame.WebAPI/Controllers/UserController.cs | 74 ++
FunGame.WebAPI/FunGame.WebAPI.csproj | 48 +
FunGame.WebAPI/FunGame.WebAPI.http | 6 +
FunGame.WebAPI/Images/logo.ico | Bin 0 -> 16958 bytes
FunGame.WebAPI/Models/LoginModel.cs | 8 +
FunGame.WebAPI/Models/RESTfulAPIModel.cs | 72 ++
FunGame.WebAPI/Program.cs | 267 +++++
FunGame.WebAPI/Properties/launchSettings.json | 41 +
FunGame.WebAPI/Services/JWTService.cs | 33 +
FunGame.WebAPI/appsettings.Development.json | 8 +
FunGame.WebAPI/appsettings.json | 19 +
FunGameServer.sln | 6 +
31 files changed, 2408 insertions(+), 1005 deletions(-)
create mode 100644 FunGame.Server/Controllers/ConnectController.cs
create mode 100644 FunGame.Server/Utilities/ConnectProperties.cs
rename FunGame.Server/Utilities/{ => MySQL}/MySQLConnection.cs (53%)
rename FunGame.Server/Utilities/{ => MySQL}/MySQLHelper.cs (96%)
rename FunGame.Server/Utilities/{ => MySQL}/MySQLManager.cs (100%)
create mode 100644 FunGame.Server/Utilities/SQLite/SQLiteHelper.cs
create mode 100644 FunGame.Server/app.manifest
create mode 100644 FunGame.WebAPI/Architecture/RESTfulAPI.cs
create mode 100644 FunGame.WebAPI/Architecture/RESTfulAPIListener.cs
create mode 100644 FunGame.WebAPI/Architecture/WebAPIListener.cs
create mode 100644 FunGame.WebAPI/Controllers/PostDataController.cs
create mode 100644 FunGame.WebAPI/Controllers/UserController.cs
create mode 100644 FunGame.WebAPI/FunGame.WebAPI.csproj
create mode 100644 FunGame.WebAPI/FunGame.WebAPI.http
create mode 100644 FunGame.WebAPI/Images/logo.ico
create mode 100644 FunGame.WebAPI/Models/LoginModel.cs
create mode 100644 FunGame.WebAPI/Models/RESTfulAPIModel.cs
create mode 100644 FunGame.WebAPI/Program.cs
create mode 100644 FunGame.WebAPI/Properties/launchSettings.json
create mode 100644 FunGame.WebAPI/Services/JWTService.cs
create mode 100644 FunGame.WebAPI/appsettings.Development.json
create mode 100644 FunGame.WebAPI/appsettings.json
diff --git a/FunGame.Server/Controllers/Authenticator.cs b/FunGame.Server/Controllers/Authenticator.cs
index 0799c0c..1423d01 100644
--- a/FunGame.Server/Controllers/Authenticator.cs
+++ b/FunGame.Server/Controllers/Authenticator.cs
@@ -1,7 +1,7 @@
using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
+using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant;
-using Milimoe.FunGame.Server.Model;
namespace Milimoe.FunGame.Server.Controller
{
@@ -9,11 +9,11 @@ namespace Milimoe.FunGame.Server.Controller
{
public TwoFactorAuthenticator Login2FA = new();
- private readonly ServerModel Server;
+ private readonly IServerModel Server;
private readonly SQLHelper SQLHelper;
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.SQLHelper = SQLHelper;
diff --git a/FunGame.Server/Controllers/ConnectController.cs b/FunGame.Server/Controllers/ConnectController.cs
new file mode 100644
index 0000000..4e3de9c
--- /dev/null
+++ b/FunGame.Server/Controllers/ConnectController.cs
@@ -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
+ {
+ ///
+ /// 因为异步函数无法使用 ref 变量,因此使用元组返回
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// [0]isConnected;[1]isDebugMode
+ public static async Task<(bool, bool)> Connect(ISocketListener listener, ISocketMessageProcessor socket, Guid token, string clientip, IEnumerable 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(0) ?? [];
+ // 参数2:客户端是否开启了开发者模式,开启开发者模式部分功能不可用
+ isDebugMode = obj.GetParam(1);
+ if (isDebugMode) ServerHelper.WriteLine("客户端已开启开发者模式");
+
+ string msg = "";
+ List 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();
+ }
+ }
+
+ ///
+ /// 回复拒绝连接消息
+ ///
+ ///
+ ///
+ ///
+ private static async Task 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;
+ }
+ }
+
+ ///
+ /// 判断是否是黑名单里的IP
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static bool IsIPBanned(ISocketListener server, string ip) where T : ISocketMessageProcessor
+ {
+ string[] strs = ip.Split(":");
+ if (strs.Length == 2 && server.BannedList.Contains(strs[0]))
+ {
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/FunGame.Server/Controllers/DataRequestController.cs b/FunGame.Server/Controllers/DataRequestController.cs
index 6a694e0..5fef652 100644
--- a/FunGame.Server/Controllers/DataRequestController.cs
+++ b/FunGame.Server/Controllers/DataRequestController.cs
@@ -1,8 +1,8 @@
-using System.Collections;
-using System.Data;
+using System.Data;
using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
+using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Library.SQLScript.Common;
using Milimoe.FunGame.Core.Library.SQLScript.Entity;
@@ -12,29 +12,47 @@ using Milimoe.FunGame.Server.Utility;
namespace Milimoe.FunGame.Server.Controller
{
- public class DataRequestController
+ ///
+ /// 继承自
+ ///
+ ///
+ public class DataRequestController where T : ISocketMessageProcessor
{
- public ServerModel Server { get; }
- public MySQLHelper SQLHelper => Server.SQLHelper ?? throw new MySQLConfigException();
+ public ServerModel Server { get; }
+ public SQLHelper? SQLHelper => Server.SQLHelper;
public MailSender? MailSender => Server.MailSender;
- public Authenticator Authenticator { get; }
- public DataRequestType LastRequest => _LastRequest;
+ public Authenticator? Authenticator { get; }
+ public DataRequestType LastRequest => _lastRequest;
- private string ForgetVerify = "";
- private string RegVerify = "";
- private DataRequestType _LastRequest = DataRequestType.UnKnown;
- private readonly bool[] isReadyCheckCD = [false, false];
+ private string _forgetVerify = "";
+ private string _regVerify = "";
+ private DataRequestType _lastRequest = DataRequestType.UnKnown;
+ private readonly bool[] _isReadyCheckCD = [false, false];
+ protected DataSet _dsUser = new();
+ protected string _username = "";
+ protected Guid _checkLoginKey = Guid.Empty;
+ protected bool _isMatching;
- public DataRequestController(ServerModel server)
+ ///
+ /// 数据请求控制器
+ ///
+ ///
+ public DataRequestController(ServerModel server)
{
Server = server;
- Authenticator = new(Server, SQLHelper, MailSender);
+ if (SQLHelper != null) Authenticator = new(Server, SQLHelper, MailSender);
}
- public Hashtable GetResultData(DataRequestType type, Hashtable data)
+ ///
+ /// 处理客户端的数据请求
+ ///
+ ///
+ ///
+ ///
+ public async Task> GetResultData(DataRequestType type, Dictionary data)
{
- Hashtable result = [];
- _LastRequest = type;
+ Dictionary result = [];
+ _lastRequest = type;
switch (type)
{
@@ -58,11 +76,11 @@ namespace Milimoe.FunGame.Server.Controller
break;
case DataRequestType.Main_IntoRoom:
- IntoRoom(data, result);
+ await IntoRoom(data, result);
break;
case DataRequestType.Main_QuitRoom:
- QuitRoom(data, result);
+ await QuitRoom(data, result);
break;
case DataRequestType.Main_MatchRoom:
@@ -70,7 +88,7 @@ namespace Milimoe.FunGame.Server.Controller
break;
case DataRequestType.Main_Chat:
- Chat(data);
+ await Chat(data);
break;
case DataRequestType.Main_Ready:
@@ -85,12 +103,12 @@ namespace Milimoe.FunGame.Server.Controller
StartGame(data, result);
break;
- case DataRequestType.Reg_GetRegVerifyCode:
+ case DataRequestType.Reg_Reg:
Reg(data, result);
break;
case DataRequestType.Login_Login:
- Login(data, result);
+ await Login(data, result);
break;
case DataRequestType.Login_GetFindPasswordVerifyCode:
@@ -123,24 +141,27 @@ namespace Milimoe.FunGame.Server.Controller
///
/// 退出登录
///
- ///
- ///
- private void LogOut(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void LogOut(Dictionary requestData, Dictionary resultData)
{
string msg = "";
Guid key = Guid.Empty;
- if (RequestData.Count >= 1)
+ if (requestData.Count >= 1)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- key = DataRequest.GetHashtableJsonObject(RequestData, "key");
- if (Server.IsLoginKey(key))
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ key = DataRequest.GetDictionaryJsonObject(requestData, "key");
+ if (IsLoginKey(key))
{
- Server.LogOut();
+ // 从玩家列表移除
+ Server.RemoveUser();
+ Server.GetUsersCount();
+ _checkLoginKey = Guid.Empty;
msg = "你已成功退出登录! ";
}
}
- ResultData.Add("msg", msg);
- ResultData.Add("key", key);
+ resultData.Add("msg", msg);
+ resultData.Add("key", key);
}
#endregion
@@ -150,242 +171,264 @@ namespace Milimoe.FunGame.Server.Controller
///
/// 获取公告
///
- ///
- private void GetServerNotice(Hashtable ResultData)
+ ///
+ private void GetServerNotice(Dictionary resultData)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- ResultData.Add("notice", Config.ServerNotice);
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ resultData.Add("notice", Config.ServerNotice);
}
///
/// 创建房间
///
- ///
- ///
- private void CreateRoom(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void CreateRoom(Dictionary requestData, Dictionary resultData)
{
Room room = General.HallInstance;
- if (RequestData.Count >= 3)
+ if (requestData.Count >= 3)
{
- RoomType type = DataRequest.GetHashtableJsonObject(RequestData, "roomtype");
- string GameModule = DataRequest.GetHashtableJsonObject(RequestData, "GameModule") ?? "";
- string gamemap = DataRequest.GetHashtableJsonObject(RequestData, "gamemap") ?? "";
- bool isrank = DataRequest.GetHashtableJsonObject(RequestData, "isrank");
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest) + " : " + RoomSet.GetTypeString(type) + " (" + string.Join(", ", [GameModule, gamemap]) + ")", InvokeMessageType.DataRequest);
- if (GameModule == "" || gamemap == "")
+ RoomType type = DataRequest.GetDictionaryJsonObject(requestData, "roomtype");
+ string gamemodule = DataRequest.GetDictionaryJsonObject(requestData, "gamemoduleserver") ?? "";
+ string gamemap = DataRequest.GetDictionaryJsonObject(requestData, "gamemap") ?? "";
+ bool isrank = DataRequest.GetDictionaryJsonObject(requestData, "isrank");
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest) + " : " + RoomSet.GetTypeString(type) + " (" + string.Join(", ", [gamemodule, gamemap]) + ")", InvokeMessageType.DataRequest);
+ if (gamemodule == "" || gamemap == "" || Config.GameModuleLoader is null || !Config.GameModuleLoader.ModuleServers.ContainsKey(gamemodule) || !Config.GameModuleLoader.Maps.ContainsKey(gamemap))
{
ServerHelper.WriteLine("缺少对应的模组或地图,无法创建房间。");
- ResultData.Add("room", room);
+ resultData.Add("room", room);
return;
}
- User user = DataRequest.GetHashtableJsonObject(RequestData, "master") ?? Factory.GetUser();
- string password = DataRequest.GetHashtableJsonObject(RequestData, "password") ?? "";
+ User user = DataRequest.GetDictionaryJsonObject(requestData, "master") ?? Factory.GetUser();
+ string password = DataRequest.GetDictionaryJsonObject(requestData, "password") ?? "";
+ int maxusers = DataRequest.GetDictionaryJsonObject(requestData, "maxusers");
if (user.Id != 0)
{
- string roomid = Verification.CreateVerifyCode(VerifyCodeType.MixVerifyCode, 7).ToUpper();
- SQLHelper.Execute(RoomQuery.Insert_CreateRoom(roomid, user.Id, type, GameModule, gamemap, isrank, password ?? ""));
- if (SQLHelper.Result == SQLResult.Success)
+ string roomid;
+ while (true)
{
- ServerHelper.WriteLine("[CreateRoom] Master: " + user.Username + " RoomID: " + roomid);
- SQLHelper.ExecuteDataSet(RoomQuery.Select_IsExistRoom(roomid));
- if (SQLHelper.Result == SQLResult.Success && SQLHelper.DataSet.Tables[0].Rows.Count > 0)
+ // 防止重复
+ roomid = Verification.CreateVerifyCode(VerifyCodeType.MixVerifyCode, 7).ToUpper();
+ if (Config.RoomList.GetRoom(roomid).Roomid == "-1")
{
- room = Factory.GetRoom(SQLHelper.DataSet.Tables[0].Rows[0], user);
- Config.RoomList.AddRoom(room);
+ break;
+ }
+ }
+ if (roomid != "-1" && SQLHelper != null)
+ {
+ SQLHelper.Execute(RoomQuery.Insert_CreateRoom(roomid, user.Id, type, gamemodule, gamemap, isrank, password, maxusers));
+ if (SQLHelper.Result == SQLResult.Success)
+ {
+ ServerHelper.WriteLine("[CreateRoom] Master: " + user.Username + " RoomID: " + roomid);
+ SQLHelper.ExecuteDataSet(RoomQuery.Select_IsExistRoom(roomid));
+ if (SQLHelper.Result == SQLResult.Success && SQLHelper.DataSet.Tables[0].Rows.Count > 0)
+ {
+ room = Factory.GetRoom(SQLHelper.DataSet.Tables[0].Rows[0], user);
+ Config.RoomList.AddRoom(room);
+ }
}
}
}
}
- ResultData.Add("room", room);
+ resultData.Add("room", room);
}
///
/// 更新房间列表
///
- ///
- private void UpdateRoom(Hashtable ResultData)
+ ///
+ private void UpdateRoom(Dictionary resultData)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- ResultData.Add("rooms", Config.RoomList.ListRoom); // 传RoomList
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ resultData.Add("rooms", Config.RoomList.ListRoom); // 传RoomList
}
///
/// 退出房间,并更新房主
///
- ///
- ///
- private void QuitRoom(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private async Task QuitRoom(Dictionary requestData, Dictionary resultData)
{
bool result = false;
- if (RequestData.Count >= 2)
+ if (requestData.Count >= 2)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- string roomid = DataRequest.GetHashtableJsonObject(RequestData, "roomid") ?? "-1";
- bool isMaster = DataRequest.GetHashtableJsonObject(RequestData, "isMaster");
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ string roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1";
+ bool isMaster = DataRequest.GetDictionaryJsonObject(requestData, "isMaster");
if (roomid != "-1" && Config.RoomList.IsExist(roomid))
{
- result = Server.QuitRoom(roomid, isMaster);
+ result = await Server.QuitRoom(roomid, isMaster);
}
}
- ResultData.Add("result", result);
+ resultData.Add("result", result);
}
///
/// 进入房间
///
- ///
- ///
- private void IntoRoom(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private async Task IntoRoom(Dictionary requestData, Dictionary resultData)
{
bool result = false;
- if (RequestData.Count >= 1)
+ if (requestData.Count >= 1)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- string roomid = DataRequest.GetHashtableJsonObject(RequestData, "roomid") ?? "-1";
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ string roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1";
if (roomid != "-1")
{
- SQLHelper.ExecuteDataSet(RoomQuery.Select_IsExistRoom(roomid));
- if (SQLHelper.Success)
+ if (SQLHelper != null)
{
- Config.RoomList.IntoRoom(roomid, Server.User);
- Server.IntoRoom(roomid);
- result = true;
- }
- else
- {
- Config.RoomList.RemoveRoom(roomid);
+ SQLHelper.ExecuteDataSet(RoomQuery.Select_IsExistRoom(roomid));
+ if (SQLHelper.Success)
+ {
+ Config.RoomList.IntoRoom(roomid, Server.User);
+ Server.InRoom = Config.RoomList[roomid];
+ await Server.SendClients(Server.Listener.ClientList.Where(c => c != null && roomid == c.InRoom.Roomid && c.User.Id != 0),
+ SocketMessageType.Chat, Server.User.Username, DateTimeUtility.GetNowShortTime() + " [ " + Server.User.Username + " ] 进入了房间。");
+ result = true;
+ }
+ else
+ {
+ Config.RoomList.RemoveRoom(roomid);
+ }
}
}
}
- ResultData.Add("result", result);
+ resultData.Add("result", result);
}
///
/// 匹配房间
///
- ///
- ///
- private void MatchRoom(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void MatchRoom(Dictionary requestData, Dictionary resultData)
{
bool result = true;
- if (RequestData.Count >= 1)
+ if (requestData.Count >= 1)
{
- bool iscancel = DataRequest.GetHashtableJsonObject(RequestData, "iscancel");
+ bool iscancel = DataRequest.GetDictionaryJsonObject(requestData, "iscancel");
if (!iscancel)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest) + " : Start", InvokeMessageType.DataRequest);
- RoomType type = DataRequest.GetHashtableJsonObject(RequestData, "roomtype");
- User user = DataRequest.GetHashtableJsonObject(RequestData, "matcher") ?? Factory.GetUser();
- Server.StartMatching(type, user);
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest) + " : Start", InvokeMessageType.DataRequest);
+ RoomType type = DataRequest.GetDictionaryJsonObject(requestData, "roomtype");
+ User user = DataRequest.GetDictionaryJsonObject(requestData, "matcher") ?? Factory.GetUser();
+ StartMatching(type, user);
}
else
{
// 取消匹配
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest) + " : Cancel", InvokeMessageType.DataRequest);
- Server.StopMatching();
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest) + " : Cancel", InvokeMessageType.DataRequest);
+ StopMatching();
}
}
- ResultData.Add("result", result);
+ resultData.Add("result", result);
}
///
/// 设置已准备状态
///
- ///
- ///
- private void SetReady(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void SetReady(Dictionary requestData, Dictionary resultData)
{
bool result = false;
string roomid = "-1";
- if (RequestData.Count >= 1)
+ if (requestData.Count >= 1)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- roomid = DataRequest.GetHashtableJsonObject(RequestData, "roomid") ?? "-1";
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1";
User user = Server.User;
- if (roomid != "-1" && user.Id != 0 && !Config.RoomList.GetReadyPlayerList(roomid).Contains(user))
+ if (roomid != "-1" && user.Id != 0 && user.Id != Config.RoomList.GetRoomMaster(roomid).Id && !Config.RoomList.GetReadyUserList(roomid).Contains(user))
{
Config.RoomList.SetReady(roomid, user);
result = true;
}
}
- ResultData.Add("result", result);
- ResultData.Add("ready", Config.RoomList.GetReadyPlayerList(roomid));
- ResultData.Add("notready", Config.RoomList.GetNotReadyPlayerList(roomid));
+ resultData.Add("result", result);
+ resultData.Add("ready", Config.RoomList.GetReadyUserList(roomid));
+ resultData.Add("notready", Config.RoomList.GetNotReadyUserList(roomid));
}
///
/// 取消已准备状态
///
- ///
- ///
- private void CancelReady(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void CancelReady(Dictionary requestData, Dictionary resultData)
{
bool result = false;
string roomid = "-1";
- if (RequestData.Count >= 1)
+ if (requestData.Count >= 1)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- roomid = DataRequest.GetHashtableJsonObject(RequestData, "roomid") ?? "-1";
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1";
User user = Server.User;
- if (roomid != "-1" && user.Id != 0 && Config.RoomList.GetReadyPlayerList(roomid).Contains(user))
+ if (roomid != "-1" && user.Id != 0 && user.Id != Config.RoomList.GetRoomMaster(roomid).Id && Config.RoomList.GetReadyUserList(roomid).Contains(user))
{
Config.RoomList.CancelReady(roomid, user);
result = true;
}
}
- ResultData.Add("result", result);
- ResultData.Add("ready", Config.RoomList.GetReadyPlayerList(roomid));
- ResultData.Add("notready", Config.RoomList.GetNotReadyPlayerList(roomid));
+ resultData.Add("result", result);
+ resultData.Add("ready", Config.RoomList.GetReadyUserList(roomid));
+ resultData.Add("notready", Config.RoomList.GetNotReadyUserList(roomid));
}
///
/// 发送聊天消息
///
- ///
- private void Chat(Hashtable RequestData)
+ ///
+ private async Task Chat(Dictionary requestData)
{
- if (RequestData.Count >= 1)
+ if (requestData.Count >= 1)
{
- string msg = DataRequest.GetHashtableJsonObject(RequestData, "msg") ?? "";
- if (msg.Trim() != "") Server.Chat(msg);
+ string msg = DataRequest.GetDictionaryJsonObject(requestData, "msg") ?? "";
+ if (msg.Trim() != "")
+ {
+ await Server.SendClients(Server.Listener.ClientList.Where(c => c != null && Server.InRoom.Roomid == c.InRoom.Roomid && c.User.Id != 0),
+ SocketMessageType.Chat, Server.User.Username, msg);
+ }
}
}
///
/// 开始游戏
///
- ///
- ///
- private void StartGame(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void StartGame(Dictionary requestData, Dictionary resultData)
{
bool result = false;
- if (RequestData.Count >= 2)
+ if (requestData.Count >= 2)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- string roomid = DataRequest.GetHashtableJsonObject(RequestData, "roomid") ?? "-1";
- bool isMaster = DataRequest.GetHashtableJsonObject(RequestData, "isMaster");
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ string roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1";
+ bool isMaster = DataRequest.GetDictionaryJsonObject(requestData, "isMaster");
if (roomid != "-1")
{
if (isMaster)
{
- string[] usernames = Config.RoomList.GetNotReadyPlayerList(roomid).Select(user => user.Username).ToArray();
+ string[] usernames = Config.RoomList.GetNotReadyUserList(roomid).Select(user => user.Username).ToArray();
if (usernames.Length > 0)
{
- if (isReadyCheckCD[0] == false)
+ if (_isReadyCheckCD[0] == false)
{
// 提醒玩家准备
Server.SendSystemMessage(ShowMessageType.None, "还有玩家尚未准备,无法开始游戏。", "", 0, Server.User.Username);
Server.SendSystemMessage(ShowMessageType.Tip, "房主即将开始游戏,请准备!", "请准备就绪", 10, usernames);
- isReadyCheckCD[0] = true;
+ _isReadyCheckCD[0] = true;
TaskUtility.RunTimer(() =>
{
- isReadyCheckCD[0] = false;
+ _isReadyCheckCD[0] = false;
}, 15000);
}
else
@@ -395,7 +438,7 @@ namespace Milimoe.FunGame.Server.Controller
}
else
{
- List users = Config.RoomList.GetPlayerList(roomid);
+ List users = Config.RoomList.GetUsers(roomid);
if (users.Count < 2)
{
Server.SendSystemMessage(ShowMessageType.None, "玩家数量不足,无法开始游戏。", "", 0, Server.User.Username);
@@ -404,20 +447,20 @@ namespace Milimoe.FunGame.Server.Controller
{
usernames = users.Select(user => user.Username).ToArray();
Server.SendSystemMessage(ShowMessageType.None, "所有玩家均已准备,游戏将在10秒后开始。", "", 0, usernames);
- Server.StartGame(roomid, users, usernames);
+ StartGame(roomid, users, usernames);
result = true;
}
}
}
- else if (isReadyCheckCD[1] == false)
+ else if (_isReadyCheckCD[1] == false)
{
// 提醒房主开始游戏
Server.SendSystemMessage(ShowMessageType.None, "已提醒房主立即开始游戏。", "", 0, Server.User.Username);
Server.SendSystemMessage(ShowMessageType.Tip, "房间中的玩家已请求你立即开始游戏。", "请求开始", 10, Config.RoomList[roomid].RoomMaster.Username);
- isReadyCheckCD[1] = true;
+ _isReadyCheckCD[1] = true;
TaskUtility.RunTimer(() =>
{
- isReadyCheckCD[1] = false;
+ _isReadyCheckCD[1] = false;
}, 15000);
}
else
@@ -426,7 +469,42 @@ namespace Milimoe.FunGame.Server.Controller
}
}
}
- ResultData.Add("result", result);
+ resultData.Add("result", result);
+ }
+
+ private void StartGame(string roomid, List 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.ModuleServers.ContainsKey(room.GameModule))
+ {
+ Server.NowGamingServer = Config.GameModuleLoader.GetServerMode(room.GameModule);
+ Dictionary all = Server.Listener.UserList.Cast().ToDictionary(k => k.User.Username, v => v);
+ // 给其他玩家赋值模组服务器
+ foreach (IServerModel model in all.Values.Where(s => s.User.Username != Server.User.Username))
+ {
+ model.NowGamingServer = Server.NowGamingServer;
+ }
+ if (Server.NowGamingServer.StartServer(room.GameModule, room, users, Server, all))
+ {
+ foreach (IServerModel serverTask in Server.Listener.UserList.Where(model => usernames.Contains(model.User.Username)))
+ {
+ if (serverTask != null && serverTask.Socket != null)
+ {
+ Config.RoomList.CancelReady(roomid, serverTask.User);
+ serverTask.Send(SocketMessageType.StartGame, room, users);
+ }
+ }
+ }
+ }
+ });
}
#endregion
@@ -436,139 +514,142 @@ namespace Milimoe.FunGame.Server.Controller
///
/// 接收并验证注册验证码
///
- ///
- ///
- private void Reg(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void Reg(Dictionary requestData, Dictionary resultData)
{
string msg = "";
RegInvokeType returnType = RegInvokeType.None;
bool success = false;
- if (RequestData.Count >= 4)
+ if (requestData.Count >= 4)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- string username = DataRequest.GetHashtableJsonObject(RequestData, "username") ?? "";
- string password = DataRequest.GetHashtableJsonObject(RequestData, "password") ?? "";
- string email = DataRequest.GetHashtableJsonObject(RequestData, "email") ?? "";
- string verifycode = DataRequest.GetHashtableJsonObject(RequestData, "verifycode") ?? "";
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ string username = DataRequest.GetDictionaryJsonObject(requestData, "username") ?? "";
+ string password = DataRequest.GetDictionaryJsonObject(requestData, "password") ?? "";
+ string email = DataRequest.GetDictionaryJsonObject(requestData, "email") ?? "";
+ string verifycode = DataRequest.GetDictionaryJsonObject(requestData, "verifycode") ?? "";
- // 如果没发验证码,就生成验证码
- if (verifycode.Trim() == "")
+ if (SQLHelper != null)
{
- // 先检查账号是否重复
- SQLHelper.ExecuteDataSet(UserQuery.Select_IsExistUsername(username));
- if (SQLHelper.Result == SQLResult.Success)
+ // 如果没发验证码,就生成验证码
+ if (verifycode.Trim() == "")
{
- ServerHelper.WriteLine(Server.GetClientName() + " 账号已被注册");
- msg = "此账号名已被使用!";
- returnType = RegInvokeType.DuplicateUserName;
- }
- else
- {
- // 检查邮箱是否重复
- SQLHelper.ExecuteDataSet(UserQuery.Select_IsExistEmail(email));
+ // 先检查账号是否重复
+ SQLHelper.ExecuteDataSet(UserQuery.Select_IsExistUsername(username));
if (SQLHelper.Result == SQLResult.Success)
{
- ServerHelper.WriteLine(Server.GetClientName() + " 邮箱已被注册");
- msg = "此邮箱已被注册!";
- returnType = RegInvokeType.DuplicateEmail;
+ ServerHelper.WriteLine(Server.GetClientName() + " 账号已被注册");
+ msg = "此账号名已被使用!";
+ returnType = RegInvokeType.DuplicateUserName;
}
else
{
- // 检查验证码是否发送过
- SQLHelper.ExecuteDataSet(RegVerifyCodes.Select_HasSentRegVerifyCode(username, email));
+ // 检查邮箱是否重复
+ SQLHelper.ExecuteDataSet(UserQuery.Select_IsExistEmail(email));
if (SQLHelper.Result == SQLResult.Success)
{
- DateTime RegTime = (DateTime)SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime];
- string RegVerifyCode = (string)SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegVerifyCode];
- if ((DateTime.Now - RegTime).TotalMinutes < 10)
- {
- ServerHelper.WriteLine(Server.GetClientName() + $" 十分钟内已向{email}发送过验证码:{RegVerifyCode}");
- }
- returnType = RegInvokeType.InputVerifyCode;
+ ServerHelper.WriteLine(Server.GetClientName() + " 邮箱已被注册");
+ msg = "此邮箱已被注册!";
+ returnType = RegInvokeType.DuplicateEmail;
}
else
{
- // 发送验证码,需要先删除之前过期的验证码
- SQLHelper.NewTransaction();
- SQLHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(username, email));
- RegVerify = Verification.CreateVerifyCode(VerifyCodeType.NumberVerifyCode, 6);
- SQLHelper.Execute(RegVerifyCodes.Insert_RegVerifyCode(username, email, RegVerify));
+ // 检查验证码是否发送过
+ SQLHelper.ExecuteDataSet(RegVerifyCodes.Select_HasSentRegVerifyCode(username, email));
if (SQLHelper.Result == SQLResult.Success)
{
- SQLHelper.Commit();
- if (MailSender != null)
+ DateTime RegTime = (DateTime)SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime];
+ string RegVerifyCode = (string)SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegVerifyCode];
+ if ((DateTime.Now - RegTime).TotalMinutes < 10)
{
- // 发送验证码
- string ServerName = Config.ServerName;
- string Subject = $"[{ServerName}] FunGame 注册验证码";
- string Body = $"亲爱的 {username},
感谢您注册[{ServerName}],您的验证码是 {RegVerify} ,10分钟内有效,请及时输入!
{ServerName}
{DateTimeUtility.GetDateTimeToString(TimeType.LongDateOnly)}";
- string[] To = [email];
- if (MailSender.Send(MailSender.CreateMail(Subject, Body, System.Net.Mail.MailPriority.Normal, true, To)) == MailSendResult.Success)
- {
- ServerHelper.WriteLine(Server.GetClientName() + $" 已向{email}发送验证码:{RegVerify}");
- }
- else
- {
- ServerHelper.WriteLine(Server.GetClientName() + " 无法发送验证码", InvokeMessageType.Error);
- ServerHelper.WriteLine(MailSender.ErrorMsg, InvokeMessageType.Error);
- }
- }
- else // 不使用MailSender的情况
- {
- ServerHelper.WriteLine(Server.GetClientName() + $" 验证码为:{RegVerify},请服务器管理员告知此用户");
+ ServerHelper.WriteLine(Server.GetClientName() + $" 十分钟内已向{email}发送过验证码:{RegVerifyCode}");
}
returnType = RegInvokeType.InputVerifyCode;
}
- else SQLHelper.Rollback();
- }
- }
- }
- }
- else
- {
- // 先检查验证码
- SQLHelper.ExecuteDataSet(RegVerifyCodes.Select_RegVerifyCode(username, email, verifycode));
- if (SQLHelper.Result == SQLResult.Success)
- {
- // 检查验证码是否过期
- DateTime RegTime = (DateTime)SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime];
- if ((DateTime.Now - RegTime).TotalMinutes >= 10)
- {
- ServerHelper.WriteLine(Server.GetClientName() + " 验证码已过期");
- msg = "此验证码已过期,请重新注册。";
- SQLHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(username, email));
- }
- else
- {
- // 注册
- if (RegVerify.Equals(SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegVerifyCode]))
- {
- SQLHelper.NewTransaction();
- ServerHelper.WriteLine("[Reg] UserName: " + username + " Email: " + email);
- SQLHelper.Execute(UserQuery.Insert_Register(username, password, email, Server.Socket?.ClientIP ?? ""));
- if (SQLHelper.Result == SQLResult.Success)
- {
- success = true;
- msg = "注册成功!请牢记您的账号与密码!";
- SQLHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(username, email));
- SQLHelper.Commit();
- }
else
{
- SQLHelper.Rollback();
- msg = "服务器无法处理您的注册,注册失败!";
+ // 发送验证码,需要先删除之前过期的验证码
+ SQLHelper.NewTransaction();
+ SQLHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(username, email));
+ _regVerify = Verification.CreateVerifyCode(VerifyCodeType.NumberVerifyCode, 6);
+ SQLHelper.Execute(RegVerifyCodes.Insert_RegVerifyCode(username, email, _regVerify));
+ if (SQLHelper.Result == SQLResult.Success)
+ {
+ SQLHelper.Commit();
+ if (MailSender != null)
+ {
+ // 发送验证码
+ string ServerName = Config.ServerName;
+ string Subject = $"[{ServerName}] FunGame 注册验证码";
+ string Body = $"亲爱的 {username},
感谢您注册[{ServerName}],您的验证码是 {_regVerify} ,10分钟内有效,请及时输入!
{ServerName}
{DateTimeUtility.GetDateTimeToString(TimeType.LongDateOnly)}";
+ string[] To = [email];
+ if (MailSender.Send(MailSender.CreateMail(Subject, Body, System.Net.Mail.MailPriority.Normal, true, To)) == MailSendResult.Success)
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + $" 已向{email}发送验证码:{_regVerify}");
+ }
+ else
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + " 无法发送验证码", InvokeMessageType.Error);
+ ServerHelper.WriteLine(MailSender.ErrorMsg, InvokeMessageType.Error);
+ }
+ }
+ else // 不使用MailSender的情况
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + $" 验证码为:{_regVerify},请服务器管理员告知此用户");
+ }
+ returnType = RegInvokeType.InputVerifyCode;
+ }
+ else SQLHelper.Rollback();
}
}
- else msg = "验证码不正确,请重新输入!";
}
}
- else if (SQLHelper.Result == SQLResult.NotFound) msg = "验证码不正确,请重新输入!";
- else msg = "服务器无法处理您的注册,注册失败!";
+ else
+ {
+ // 先检查验证码
+ SQLHelper.ExecuteDataSet(RegVerifyCodes.Select_RegVerifyCode(username, email, verifycode));
+ if (SQLHelper.Result == SQLResult.Success)
+ {
+ // 检查验证码是否过期
+ DateTime RegTime = (DateTime)SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime];
+ if ((DateTime.Now - RegTime).TotalMinutes >= 10)
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + " 验证码已过期");
+ msg = "此验证码已过期,请重新注册。";
+ SQLHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(username, email));
+ }
+ else
+ {
+ // 注册
+ if (_regVerify.Equals(SQLHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegVerifyCode]))
+ {
+ SQLHelper.NewTransaction();
+ ServerHelper.WriteLine("[Reg] Username: " + username + " Email: " + email);
+ SQLHelper.Execute(UserQuery.Insert_Register(username, password, email, Server.Socket?.ClientIP ?? ""));
+ if (SQLHelper.Result == SQLResult.Success)
+ {
+ success = true;
+ msg = "注册成功!请牢记您的账号与密码!";
+ SQLHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(username, email));
+ SQLHelper.Commit();
+ }
+ else
+ {
+ SQLHelper.Rollback();
+ msg = "服务器无法处理您的注册,注册失败!";
+ }
+ }
+ else msg = "验证码不正确,请重新输入!";
+ }
+ }
+ else if (SQLHelper.Result == SQLResult.NotFound) msg = "验证码不正确,请重新输入!";
+ else msg = "服务器无法处理您的注册,注册失败!";
+ }
}
}
- ResultData.Add("msg", msg);
- ResultData.Add("type", returnType);
- ResultData.Add("success", success);
+ resultData.Add("msg", msg);
+ resultData.Add("type", returnType);
+ resultData.Add("success", success);
}
#endregion
@@ -578,26 +659,26 @@ namespace Milimoe.FunGame.Server.Controller
///
/// 登录
///
- ///
- ///
- private void Login(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private async Task Login(Dictionary requestData, Dictionary resultData)
{
string msg = "";
User user = Factory.GetUser();
- if (RequestData.Count >= 4)
+ if (requestData.Count >= 4)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- string username = DataRequest.GetHashtableJsonObject(RequestData, "username") ?? "";
- string password = DataRequest.GetHashtableJsonObject(RequestData, "password") ?? "";
- string autokey = DataRequest.GetHashtableJsonObject(RequestData, "autokey") ?? "";
- Guid key = DataRequest.GetHashtableJsonObject(RequestData, "key");
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ string username = DataRequest.GetDictionaryJsonObject(requestData, "username") ?? "";
+ string password = DataRequest.GetDictionaryJsonObject(requestData, "password") ?? "";
+ string autokey = DataRequest.GetDictionaryJsonObject(requestData, "autokey") ?? "";
+ Guid key = DataRequest.GetDictionaryJsonObject(requestData, "key");
// CheckLogin的情况
if (key != Guid.Empty)
{
- if (Server.IsLoginKey(key))
+ if (IsLoginKey(key))
{
- Server.CheckLogin();
+ await CheckLogin();
user = Server.User;
}
else ServerHelper.WriteLine("客户端发送了错误的秘钥,不允许本次登录。");
@@ -607,166 +688,212 @@ namespace Milimoe.FunGame.Server.Controller
// 验证登录
if (username != null && password != null)
{
- ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] UserName: " + username);
- SQLHelper.ExecuteDataSet(UserQuery.Select_Users_LoginQuery(username, password));
- if (SQLHelper.Result == SQLResult.Success)
+ ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] Username: " + username);
+ if (SQLHelper != null)
{
- DataSet DsUser = SQLHelper.DataSet;
- if (autokey.Trim() != "")
+ SQLHelper.ExecuteDataSet(UserQuery.Select_Users_LoginQuery(username, password));
+ if (SQLHelper.Result == SQLResult.Success)
{
- SQLHelper.ExecuteDataSet(UserQuery.Select_CheckAutoKey(username, autokey));
- if (SQLHelper.Result == SQLResult.Success)
+ DataSet dsUser = SQLHelper.DataSet;
+ if (autokey.Trim() != "")
{
- ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] AutoKey: 已确认");
- }
- else
- {
- msg = "AutoKey不正确,拒绝自动登录!";
- ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] " + msg);
+ SQLHelper.ExecuteDataSet(UserQuery.Select_CheckAutoKey(username, autokey));
+ if (SQLHelper.Result == SQLResult.Success)
+ {
+ ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] AutoKey: 已确认");
+ }
+ else
+ {
+ msg = "AutoKey不正确,拒绝自动登录!";
+ ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] " + msg);
+ }
}
+ key = Guid.NewGuid();
+ PreLogin(dsUser, username, key);
+ resultData.Add("key", key);
+ }
+ else
+ {
+ msg = "用户名或密码不正确。";
+ ServerHelper.WriteLine(msg);
}
- key = Guid.NewGuid();
- Server.PreLogin(DsUser, username, key);
- ResultData.Add("key", key);
- }
- else
- {
- msg = "用户名或密码不正确。";
- ServerHelper.WriteLine(msg);
}
}
}
}
- ResultData.Add("msg", msg);
- ResultData.Add("user", user);
+ resultData.Add("msg", msg);
+ resultData.Add("user", user);
+ }
+
+ ///
+ /// 预登录
+ ///
+ ///
+ ///
+ ///
+ private void PreLogin(DataSet dsuser, string username, Guid checkloginkey)
+ {
+ _dsUser = dsuser;
+ _username = username;
+ _checkLoginKey = checkloginkey;
+ }
+
+ ///
+ /// 确认登录
+ ///
+ private async Task CheckLogin()
+ {
+ // 创建User对象
+ Server.User = Factory.GetUser(_dsUser);
+ // 检查有没有重复登录的情况
+ await Server.ForceLogOutDuplicateLogonUser();
+ // 添加至玩家列表
+ Server.AddUser();
+ Server.GetUsersCount();
+ }
+
+ ///
+ /// 检查LoginKey
+ ///
+ ///
+ ///
+ private bool IsLoginKey(Guid key)
+ {
+ return key == _checkLoginKey;
}
///
/// 接收并验证找回密码时的验证码
///
- ///
- ///
- private void ForgetPassword(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void ForgetPassword(Dictionary requestData, Dictionary resultData)
{
string msg = "无法找回您的密码,请稍后再试。"; // 返回的验证信息
- if (RequestData.Count >= 3)
+ if (requestData.Count >= 3)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- string username = DataRequest.GetHashtableJsonObject(RequestData, ForgetVerifyCodes.Column_Username) ?? "";
- string email = DataRequest.GetHashtableJsonObject(RequestData, ForgetVerifyCodes.Column_Email) ?? "";
- string verifycode = DataRequest.GetHashtableJsonObject(RequestData, ForgetVerifyCodes.Column_ForgetVerifyCode) ?? "";
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ string username = DataRequest.GetDictionaryJsonObject(requestData, ForgetVerifyCodes.Column_Username) ?? "";
+ string email = DataRequest.GetDictionaryJsonObject(requestData, ForgetVerifyCodes.Column_Email) ?? "";
+ string verifycode = DataRequest.GetDictionaryJsonObject(requestData, ForgetVerifyCodes.Column_ForgetVerifyCode) ?? "";
// 客户端发来了验证码就进行验证,没有发就生成
if (verifycode.Trim() != "")
{
// 先检查验证码
- SQLHelper.ExecuteDataSet(ForgetVerifyCodes.Select_ForgetVerifyCode(username, email, verifycode));
- if (SQLHelper.Result == SQLResult.Success)
+ if (SQLHelper != null)
{
- // 检查验证码是否过期
- DateTime SendTime = (DateTime)SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_SendTime];
- if ((DateTime.Now - SendTime).TotalMinutes >= 10)
+ SQLHelper.ExecuteDataSet(ForgetVerifyCodes.Select_ForgetVerifyCode(username, email, verifycode));
+ if (SQLHelper.Result == SQLResult.Success)
{
- ServerHelper.WriteLine(Server.GetClientName() + " 验证码已过期");
- msg = "此验证码已过期,请重新找回密码。";
- SQLHelper.Execute(ForgetVerifyCodes.Delete_ForgetVerifyCode(username, email));
- }
- else
- {
- // 检查验证码是否正确
- if (ForgetVerify.Equals(SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_ForgetVerifyCode]))
+ // 检查验证码是否过期
+ DateTime SendTime = (DateTime)SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_SendTime];
+ if ((DateTime.Now - SendTime).TotalMinutes >= 10)
{
- ServerHelper.WriteLine("[ForgerPassword] UserName: " + username + " Email: " + email);
+ ServerHelper.WriteLine(Server.GetClientName() + " 验证码已过期");
+ msg = "此验证码已过期,请重新找回密码。";
SQLHelper.Execute(ForgetVerifyCodes.Delete_ForgetVerifyCode(username, email));
- msg = "";
}
- else msg = "验证码不正确,请重新输入!";
+ else
+ {
+ // 检查验证码是否正确
+ if (_forgetVerify.Equals(SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_ForgetVerifyCode]))
+ {
+ ServerHelper.WriteLine("[ForgerPassword] Username: " + username + " Email: " + email);
+ SQLHelper.Execute(ForgetVerifyCodes.Delete_ForgetVerifyCode(username, email));
+ msg = "";
+ }
+ else msg = "验证码不正确,请重新输入!";
+ }
}
+ else msg = "验证码不正确,请重新输入!";
}
- else msg = "验证码不正确,请重新输入!";
}
else
{
// 检查账号和邮箱是否匹配
- SQLHelper.ExecuteDataSet(UserQuery.Select_CheckEmailWithUsername(username, email));
- if (SQLHelper.Result != SQLResult.Success)
+ if (SQLHelper != null)
{
- msg = "此邮箱未绑定此账号,请重试!";
- }
- else
- {
- // 检查验证码是否发送过和是否过期
- SQLHelper.ExecuteDataSet(ForgetVerifyCodes.Select_HasSentForgetVerifyCode(username, email));
- if (SQLHelper.Result != SQLResult.Success || (DateTime.Now - ((DateTime)SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_SendTime])).TotalMinutes >= 10)
+ SQLHelper.ExecuteDataSet(UserQuery.Select_CheckEmailWithUsername(username, email));
+ if (SQLHelper.Result != SQLResult.Success)
{
- // 发送验证码,需要先删除之前过期的验证码
- SQLHelper.Execute(ForgetVerifyCodes.Delete_ForgetVerifyCode(username, email));
- ForgetVerify = Verification.CreateVerifyCode(VerifyCodeType.NumberVerifyCode, 6);
- SQLHelper.Execute(ForgetVerifyCodes.Insert_ForgetVerifyCode(username, email, ForgetVerify));
- if (SQLHelper.Result == SQLResult.Success)
- {
- if (MailSender != null)
- {
- // 发送验证码
- string ServerName = Config.ServerName;
- string Subject = $"[{ServerName}] FunGame 找回密码验证码";
- string Body = $"亲爱的 {username},
您正在找回[{ServerName}]账号的密码,您的验证码是 {ForgetVerify} ,10分钟内有效,请及时输入!
{ServerName}
{DateTimeUtility.GetDateTimeToString(TimeType.LongDateOnly)}";
- string[] To = [email];
- if (MailSender.Send(MailSender.CreateMail(Subject, Body, System.Net.Mail.MailPriority.Normal, true, To)) == MailSendResult.Success)
- {
- ServerHelper.WriteLine(Server.GetClientName() + $" 已向{email}发送验证码:{ForgetVerify}");
- msg = "";
- }
- else
- {
- ServerHelper.WriteLine(Server.GetClientName() + " 无法发送验证码");
- ServerHelper.WriteLine(MailSender.ErrorMsg);
- }
- }
- else // 不使用MailSender的情况
- {
- ServerHelper.WriteLine(Server.GetClientName() + $" 验证码为:{ForgetVerify},请服务器管理员告知此用户");
- msg = "";
- }
- }
+ msg = "此邮箱未绑定此账号,请重试!";
}
else
{
- // 发送过验证码且验证码没有过期
- string ForgetVerifyCode = (string)SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_ForgetVerifyCode];
- ServerHelper.WriteLine(Server.GetClientName() + $" 十分钟内已向{email}发送过验证码:{ForgetVerifyCode}");
- msg = "";
+ // 检查验证码是否发送过和是否过期
+ SQLHelper.ExecuteDataSet(ForgetVerifyCodes.Select_HasSentForgetVerifyCode(username, email));
+ if (SQLHelper.Result != SQLResult.Success || (DateTime.Now - ((DateTime)SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_SendTime])).TotalMinutes >= 10)
+ {
+ // 发送验证码,需要先删除之前过期的验证码
+ SQLHelper.Execute(ForgetVerifyCodes.Delete_ForgetVerifyCode(username, email));
+ _forgetVerify = Verification.CreateVerifyCode(VerifyCodeType.NumberVerifyCode, 6);
+ SQLHelper.Execute(ForgetVerifyCodes.Insert_ForgetVerifyCode(username, email, _forgetVerify));
+ if (SQLHelper.Result == SQLResult.Success)
+ {
+ if (MailSender != null)
+ {
+ // 发送验证码
+ string ServerName = Config.ServerName;
+ string Subject = $"[{ServerName}] FunGame 找回密码验证码";
+ string Body = $"亲爱的 {username},
您正在找回[{ServerName}]账号的密码,您的验证码是 {_forgetVerify} ,10分钟内有效,请及时输入!
{ServerName}
{DateTimeUtility.GetDateTimeToString(TimeType.LongDateOnly)}";
+ string[] To = [email];
+ if (MailSender.Send(MailSender.CreateMail(Subject, Body, System.Net.Mail.MailPriority.Normal, true, To)) == MailSendResult.Success)
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + $" 已向{email}发送验证码:{_forgetVerify}");
+ msg = "";
+ }
+ else
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + " 无法发送验证码");
+ ServerHelper.WriteLine(MailSender.ErrorMsg);
+ }
+ }
+ else // 不使用MailSender的情况
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + $" 验证码为:{_forgetVerify},请服务器管理员告知此用户");
+ msg = "";
+ }
+ }
+ }
+ else
+ {
+ // 发送过验证码且验证码没有过期
+ string ForgetVerifyCode = (string)SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_ForgetVerifyCode];
+ ServerHelper.WriteLine(Server.GetClientName() + $" 十分钟内已向{email}发送过验证码:{ForgetVerifyCode}");
+ msg = "";
+ }
}
}
}
}
- ResultData.Add("msg", msg);
+ resultData.Add("msg", msg);
}
///
/// 更新用户的密码
///
- ///
- ///
- private void UpdatePassword(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void UpdatePassword(Dictionary requestData, Dictionary resultData)
{
string msg = "无法更新您的密码,请稍后再试。";
- if (RequestData.Count >= 2)
+ if (requestData.Count >= 2)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- string username = DataRequest.GetHashtableJsonObject(RequestData, UserQuery.Column_Username) ?? "";
- string password = DataRequest.GetHashtableJsonObject(RequestData, UserQuery.Column_Password) ?? "";
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ string username = DataRequest.GetDictionaryJsonObject(requestData, UserQuery.Column_Username) ?? "";
+ string password = DataRequest.GetDictionaryJsonObject(requestData, UserQuery.Column_Password) ?? "";
if (username.Trim() != "" && password.Trim() != "")
{
Server.SQLHelper?.Execute(UserQuery.Update_Password(username, password));
- if (SQLHelper.Success)
+ if (SQLHelper?.Success ?? false)
{
// 更新成功返回空值
msg = "";
}
}
}
- ResultData.Add("msg", msg);
+ resultData.Add("msg", msg);
}
#endregion
@@ -776,17 +903,125 @@ namespace Milimoe.FunGame.Server.Controller
///
/// 获取房间内玩家数量
///
- ///
- ///
- private void GetRoomPlayerCount(Hashtable RequestData, Hashtable ResultData)
+ ///
+ ///
+ private void GetRoomPlayerCount(Dictionary requestData, Dictionary resultData)
{
string roomid = "-1";
- if (RequestData.Count >= 1)
+ if (requestData.Count >= 1)
{
- ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_LastRequest), InvokeMessageType.DataRequest);
- roomid = DataRequest.GetHashtableJsonObject(RequestData, "roomid") ?? "-1";
+ ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest);
+ roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1";
}
- ResultData.Add("count", Config.RoomList.GetPlayerCount(roomid));
+ resultData.Add("count", Config.RoomList.GetUserCount(roomid));
+ }
+
+ ///
+ /// 开始匹配
+ ///
+ ///
+ ///
+ private void StartMatching(RoomType type, User user)
+ {
+ _isMatching = true;
+ ServerHelper.WriteLine(Server.GetClientName() + " 开始匹配。类型:" + RoomSet.GetTypeString(type));
+ TaskUtility.NewTask(async () =>
+ {
+ if (_isMatching)
+ {
+ Room room = await MatchingRoom(type, user);
+ if (_isMatching && Server.Socket != null)
+ {
+ await Server.Send(SocketMessageType.MatchRoom, room);
+ }
+ _isMatching = false;
+ }
+ }).OnError(e =>
+ {
+ ServerHelper.Error(e);
+ _isMatching = false;
+ });
+ }
+
+ ///
+ /// 终止匹配
+ ///
+ private void StopMatching()
+ {
+ if (_isMatching)
+ {
+ ServerHelper.WriteLine(Server.GetClientName() + " 取消了匹配。");
+ _isMatching = false;
+ }
+ }
+
+ ///
+ /// 匹配线程
+ ///
+ ///
+ ///
+ ///
+ private async Task MatchingRoom(RoomType roomtype, User user)
+ {
+ int i = 1; // Elo扩大系数
+ double time = 0; // 已经匹配的时间
+ double expandInterval = 10; // 扩大匹配范围的间隔时间
+ double maxTime = 50; // 最大匹配时间
+
+ while (_isMatching)
+ {
+ // 匹配房间类型(如果是All,则匹配所有房间)
+ List targets;
+ if (roomtype == RoomType.All)
+ {
+ targets = [.. Config.RoomList.ListRoom.Where(r => r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching)];
+ }
+ else
+ {
+ targets = [.. Config.RoomList.ListRoom.Where(r => (r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching) && r.RoomType == roomtype)];
+ }
+
+ // 如果匹配停止,则退出
+ if (!_isMatching) break;
+
+ foreach (Room room in targets)
+ {
+ // 获取当前房间的玩家列表
+ List players = Config.RoomList.GetUsers(room.Roomid);
+ if (players.Count > 0)
+ {
+ // 计算房间平均Elo
+ double avgElo = players.Sum(u => u.Statistics.EloStats.TryGetValue(0, out double value) ? value : 0) / players.Count;
+ double userElo = user.Statistics.EloStats.TryGetValue(0, out double userValue) ? userValue : 0;
+
+ // 匹配Elo范围,随着时间增加,范围逐渐扩大
+ if (userElo >= avgElo - (300 * i) && userElo <= avgElo + (300 * i))
+ {
+ // 找到匹配的房间,立即返回
+ return room;
+ }
+ }
+ }
+
+ // 如果匹配停止,则退出
+ if (!_isMatching) break;
+
+ // 检查是否已经过了10秒,扩大匹配范围
+ if (time >= expandInterval * i)
+ {
+ i++;
+ }
+ // 达到最大匹配时间后不再匹配Elo,直接返回第一个房间
+ if (time >= maxTime)
+ {
+ return targets.FirstOrDefault() ?? General.HallInstance;
+ }
+
+ await Task.Delay(100);
+ time += 0.1;
+ }
+
+ return General.HallInstance;
}
#endregion
diff --git a/FunGame.Server/FunGame.Server.csproj b/FunGame.Server/FunGame.Server.csproj
index c37dfb7..0fa6df9 100644
--- a/FunGame.Server/FunGame.Server.csproj
+++ b/FunGame.Server/FunGame.Server.csproj
@@ -16,20 +16,34 @@
1.0
FunGameServer
Milimoe.$(MSBuildProjectName.Replace(" ", "_"))
+ app.manifest
embedded
+ 1701;1702;IDE0130
embedded
+ 1701;1702;IDE0130
+
+
+
+
+
+
+
+
+
+
+
@@ -38,15 +52,6 @@
..\..\FunGame.Core\bin\Debug\net8.0\FunGame.Core.dll
-
- ..\bin\Debug\net7.0\MySql.Data.dll
-
-
- ..\bin\Debug\net7.0\System.Configuration.ConfigurationManager.dll
-
-
- ..\bin\Debug\net7.0\System.Security.Permissions.dll
-
diff --git a/FunGame.Server/Main.cs b/FunGame.Server/Main.cs
index c1c79de..45147f5 100644
--- a/FunGame.Server/Main.cs
+++ b/FunGame.Server/Main.cs
@@ -1,10 +1,8 @@
-using System.Collections;
-using System.Collections.Generic;
-using Milimoe.FunGame;
+using Milimoe.FunGame;
using Milimoe.FunGame.Core.Api.Utility;
-using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
+using Milimoe.FunGame.Server.Controller;
using Milimoe.FunGame.Server.Model;
using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility;
@@ -13,7 +11,8 @@ Console.Title = Config.ServerName;
Console.WriteLine(FunGameInfo.GetInfo(Config.FunGameType));
bool Running = true;
-ServerSocket? ListeningSocket = null;
+SocketListener? SocketListener = null;
+HTTPListener? WebSocketListener = null;
StartServer();
@@ -32,7 +31,7 @@ while (Running)
Running = false;
break;
case OrderDictionary.Restart:
- if (ListeningSocket == null)
+ if (SocketListener is null || WebSocketListener is null)
{
ServerHelper.WriteLine("重启服务器");
StartServer();
@@ -40,7 +39,14 @@ while (Running)
else ServerHelper.WriteLine("服务器正在运行,拒绝重启!");
break;
default:
- ConsoleModel.Order(ListeningSocket, order);
+ if (SocketListener != null)
+ {
+ await ConsoleModel.Order(SocketListener, order);
+ }
+ else
+ {
+ await ConsoleModel.Order(WebSocketListener, order);
+ }
break;
}
}
@@ -51,7 +57,7 @@ Console.ReadKey();
void StartServer()
{
- Task t = Task.Factory.StartNew(() =>
+ TaskUtility.NewTask(async () =>
{
try
{
@@ -60,7 +66,7 @@ void StartServer()
ServerHelper.InitOrderList();
// 读取游戏模组
- if (!GetGameModuleList())
+ if (!Config.GetGameModuleList())
{
ServerHelper.WriteLine("服务器似乎未安装任何游戏模组,请检查是否正确安装它们。");
}
@@ -83,51 +89,135 @@ void StartServer()
// 创建全局SQLHelper
Config.InitSQLHelper();
- // 创建监听
- ListeningSocket = ServerSocket.StartListening();
+ // 使用Socket还是WebSocket
+ bool useWebSocket = Config.UseWebSocket;
- // 开始监听连接
- AddBannedList(ListeningSocket);
- ServerHelper.WriteLine("Listen -> " + Config.ServerPort);
- ServerHelper.WriteLine("服务器启动成功,开始监听 . . .");
-
- if (Config.ServerNotice != "")
- ServerHelper.WriteLine("\n\n********** 服务器公告 **********\n\n" + Config.ServerNotice + "\n");
- else
- ServerHelper.WriteLine("无法读取服务器公告");
-
- while (Running)
+ if (!useWebSocket)
{
- ClientSocket socket;
- string clientip = "";
- try
+ // 创建监听
+ SocketListener listener = SocketListener.StartListening(Config.ServerPort, Config.MaxPlayers);
+ SocketListener = listener;
+
+ // 开始监听连接
+ listener.BannedList.AddRange(Config.ServerBannedList);
+ ServerHelper.WriteLine("Listen -> " + Config.ServerPort);
+ ServerHelper.WriteLine("服务器启动成功,开始监听 . . .");
+
+ if (Config.ServerNotice != "")
+ ServerHelper.WriteLine("\n\n********** 服务器公告 **********\n\n" + Config.ServerNotice + "\n");
+ else
+ ServerHelper.WriteLine("无法读取服务器公告");
+
+ while (Running)
{
- Guid token = Guid.NewGuid();
- socket = ListeningSocket.Accept(token);
- clientip = socket.ClientIP;
- Config.ConnectingPlayerCount++;
- // 开始处理客户端连接请求
- bool isDebugMode = false;
- if (Connect(socket, token, clientip, ref isDebugMode))
+ ServerSocket socket;
+ string clientip = "";
+ try
{
- ServerModel ClientModel = new(ListeningSocket, socket, Running, isDebugMode);
- Task t = Task.Factory.StartNew(() =>
+ Guid token = Guid.NewGuid();
+ socket = listener.Accept(token);
+
+ TaskUtility.NewTask(async () =>
{
- ClientModel.Start();
+ clientip = socket.ClientIP;
+ Config.ConnectingPlayerCount++;
+ bool isConnected = false;
+ bool isDebugMode = false;
+
+ // 开始处理客户端连接请求
+ SocketObject[] objs = socket.Receive();
+ (isConnected, isDebugMode) = await ConnectController.Connect(listener, socket, token, clientip, objs);
+ if (isConnected)
+ {
+ ServerModel ClientModel = new(listener, socket, isDebugMode);
+ ClientModel.SetClientName(clientip);
+ Task t = Task.Run(ClientModel.Start);
+ }
+ else
+ {
+ ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
+ }
+ Config.ConnectingPlayerCount--;
+ }).OnError(e =>
+ {
+ if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
+ ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
+ ServerHelper.Error(e);
});
- ClientModel.SetClientName(clientip);
}
- else
+ catch (Exception e)
{
- ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
+ ServerHelper.Error(e);
}
- Config.ConnectingPlayerCount--;
}
- catch (Exception e)
+ }
+ else
+ {
+ if (Config.WebSocketAddress == "*")
{
- if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
- ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
- ServerHelper.Error(e);
+ 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 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 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 (ListeningSocket != null)
+ if (SocketListener != null)
{
- ListeningSocket.Close();
- ListeningSocket = null;
+ SocketListener.Close();
+ SocketListener = null;
+ }
+ if (WebSocketListener != null)
+ {
+ WebSocketListener.Close();
+ WebSocketListener = null;
}
}
ServerHelper.Error(e);
}
finally
{
- if (ListeningSocket != null)
+ if (SocketListener != null)
{
- ListeningSocket.Close();
- ListeningSocket = null;
+ SocketListener.Close();
+ SocketListener = null;
+ }
+ if (WebSocketListener != null)
+ {
+ WebSocketListener.Close();
+ WebSocketListener = null;
}
}
});
}
-
-bool GetGameModuleList()
-{
- List supported = [];
- // 构建AddonController
- Hashtable delegates = [];
- delegates.Add("WriteLine", new Action(msg => ServerHelper.WriteLine(msg, InvokeMessageType.GameModule)));
- delegates.Add("Error", new Action(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(0) ?? [];
- // 参数2:客户端是否开启了开发者模式,开启开发者模式部分功能不可用
- isDebugMode = read.GetParam(1);
- if (isDebugMode) ServerHelper.WriteLine("客户端已开启开发者模式");
-
- string msg = "";
- List 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;
-}
\ No newline at end of file
diff --git a/FunGame.Server/Models/ConsoleModel.cs b/FunGame.Server/Models/ConsoleModel.cs
index ada59b8..344f11c 100644
--- a/FunGame.Server/Models/ConsoleModel.cs
+++ b/FunGame.Server/Models/ConsoleModel.cs
@@ -1,5 +1,4 @@
using Milimoe.FunGame.Core.Interface.Base;
-using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility;
@@ -7,7 +6,7 @@ namespace Milimoe.FunGame.Server.Model
{
public class ConsoleModel
{
- public static void Order(ServerSocket? server, string order)
+ public static async Task Order(ISocketListener? server, string order) where T : ISocketMessageProcessor
{
try
{
@@ -19,7 +18,7 @@ namespace Milimoe.FunGame.Server.Model
string client = Console.ReadLine() ?? "";
if (client != "" && server != null)
{
- ((ServerModel)server.GetClient(client))?.Kick("您已被服务器管理员踢出此服务器。");
+ await Kick((ServerModel)server.ClientList[client]);
}
break;
}
@@ -29,7 +28,7 @@ namespace Milimoe.FunGame.Server.Model
string user = Console.ReadLine() ?? "";
if (user != "" && server != null)
{
- ((ServerModel)server.GetUser(user))?.ForceLogOut("您已被服务器管理员强制下线。");
+ await ForceLogOut((ServerModel)server.UserList[user]);
}
break;
}
@@ -46,7 +45,7 @@ namespace Milimoe.FunGame.Server.Model
ShowUsers(server);
break;
case OrderDictionary.Help:
- ServerHelper.WriteLine("Milimoe -> 帮助");
+ ShowHelp();
break;
}
}
@@ -56,7 +55,17 @@ namespace Milimoe.FunGame.Server.Model
}
}
- private static void ShowClients(ServerSocket? server)
+ public static async Task Kick(ServerModel clientModel) where T : ISocketMessageProcessor
+ {
+ await clientModel.Kick("您已被服务器管理员踢出此服务器。");
+ }
+
+ public static async Task ForceLogOut(ServerModel clientModel) where T : ISocketMessageProcessor
+ {
+ await clientModel.ForceLogOut("您已被服务器管理员强制下线。");
+ }
+
+ public static void ShowClients(ISocketListener? server) where T : ISocketMessageProcessor
{
if (server != null)
{
@@ -69,7 +78,7 @@ namespace Milimoe.FunGame.Server.Model
}
}
- private static void ShowUsers(ServerSocket? server)
+ public static void ShowUsers(ISocketListener? server) where T : ISocketMessageProcessor
{
if (server != null)
{
@@ -81,5 +90,10 @@ namespace Milimoe.FunGame.Server.Model
}
}
}
+
+ public static void ShowHelp()
+ {
+ ServerHelper.WriteLine("Milimoe -> 帮助");
+ }
}
}
diff --git a/FunGame.Server/Models/ServerModel.cs b/FunGame.Server/Models/ServerModel.cs
index 6c96c04..7567de2 100644
--- a/FunGame.Server/Models/ServerModel.cs
+++ b/FunGame.Server/Models/ServerModel.cs
@@ -1,5 +1,4 @@
-using System.Collections;
-using System.Data;
+using System.Data;
using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
@@ -14,191 +13,225 @@ using Milimoe.FunGame.Server.Utility;
namespace Milimoe.FunGame.Server.Model
{
- public class ServerModel : IServerModel
+ public class ServerModel : IServerModel where T : ISocketMessageProcessor
{
/**
* Public
*/
- public bool Running => _Running;
- public ClientSocket? Socket => _Socket;
- public string ClientName => _ClientName;
- public User User => _User;
- public Room Room
- {
- get => _Room;
- set => _Room = value;
- }
- public MySQLHelper? SQLHelper => _SQLHelper;
- public MailSender? MailSender => _MailSender;
+ public bool Running => _running;
+ public ISocketMessageProcessor Socket { get; }
+ public ISocketListener Listener { get; }
+ public DataRequestController DataRequestController { get; }
+ public Guid Token => Socket?.Token ?? Guid.Empty;
+ public string ClientName => _clientName;
+ public User User { get; set; } = General.UnknownUserInstance;
+ public Room InRoom { get; set; } = General.HallInstance;
+ public SQLHelper? SQLHelper => _sqlHelper;
+ public MailSender? MailSender => _mailer;
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;
- 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)
+ public ServerModel(ISocketListener server, ISocketMessageProcessor socket, bool isDebugMode)
{
- Server = server;
- _Socket = socket;
- _Running = running;
- Token = socket.Token;
- this.IsDebugMode = isDebugMode;
- if (Config.SQLMode) _SQLHelper = new(this);
- _MailSender = SmtpHelper.GetMailSender();
+ Listener = server;
+ Socket = socket;
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 SocketMessageHandler(ISocketMessageProcessor socket, SocketObject obj)
{
- // 接收客户端消息
- try
+ // 读取收到的消息
+ SocketMessageType type = obj.SocketType;
+ Guid token = obj.Token;
+ string msg = "";
+
+ // 验证Token
+ if (type != SocketMessageType.HeartBeat && token != socket.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 = "";
-
- // 验证Token
- if (type != SocketMessageType.HeartBeat && token != Token)
- {
- ServerHelper.WriteLine(GetClientName() + " 使用了非法方式传输消息,服务器拒绝回应 -> [" + SocketSet.GetTypeString(type) + "]");
- return false;
- }
-
- if (type == SocketMessageType.EndGame)
- {
- NowGamingServer = null;
- return true;
- }
-
- if (type == SocketMessageType.DataRequest)
- {
- return DataRequestHandler(socket, SocketObject);
- }
-
- if (type == SocketMessageType.Gaming)
- {
- return GamingMessageHandler(socket, SocketObject);
- }
-
- if (type == SocketMessageType.HeartBeat)
- {
- return HeartBeat(socket);
- }
-
- switch (type)
- {
- case SocketMessageType.Disconnect:
- ServerHelper.WriteLine("[" + SocketSet.GetTypeString(SocketMessageType.DataRequest) + "] " + GetClientName() + " -> Disconnect", InvokeMessageType.Core);
- msg = "你已成功断开与服务器的连接: " + Config.ServerName + "。 ";
- break;
- }
- return Send(socket, type, msg);
- }
- catch (Exception e)
- {
- ServerHelper.WriteLine(GetClientName() + " 没有回应。");
- ServerHelper.Error(e);
+ ServerHelper.WriteLine(GetClientName() + " 使用了非法方式传输消息,服务器拒绝回应 -> [" + SocketSet.GetTypeString(type) + "]");
return false;
}
+
+ if (type == SocketMessageType.HeartBeat)
+ {
+ return await HeartBeat();
+ }
+
+ if (type == SocketMessageType.EndGame)
+ {
+ NowGamingServer = null;
+ return true;
+ }
+
+ if (type == SocketMessageType.DataRequest)
+ {
+ return await DataRequestHandler(obj);
+ }
+
+ if (type == SocketMessageType.GamingRequest)
+ {
+ return await GamingRequestHandler(obj);
+ }
+
+ if (type == SocketMessageType.Gaming)
+ {
+ return await GamingMessageHandler(obj);
+ }
+
+ switch (type)
+ {
+ case SocketMessageType.Disconnect:
+ ServerHelper.WriteLine("[" + SocketSet.GetTypeString(SocketMessageType.DataRequest) + "] " + GetClientName() + " -> Disconnect", InvokeMessageType.Core);
+ msg = "你已成功断开与服务器的连接: " + Config.ServerName + "。 ";
+ break;
+ }
+
+ return await Send(type, msg);
}
- public bool DataRequestHandler(ClientSocket socket, SocketObject SocketObject)
+ public async Task HeartBeat()
+ {
+ bool result = await Send(SocketMessageType.HeartBeat);
+ if (!result)
+ {
+ ServerHelper.WriteLine("[ " + _username + " ] " + nameof(HeartBeat) + ": " + result, InvokeMessageType.Error);
+ }
+ return result;
+ }
+
+ protected async Task DataRequestHandler(SocketObject obj)
{
if (SQLHelper != null)
{
- Hashtable result = [];
+ Dictionary result = [];
+ Guid requestID = Guid.Empty;
DataRequestType type = DataRequestType.UnKnown;
- if (SocketObject.Parameters.Length > 0)
+ if (obj.Parameters.Length > 0)
{
try
{
- type = SocketObject.GetParam(0);
- Hashtable data = SocketObject.GetParam(1) ?? [];
+ type = obj.GetParam(0);
+ requestID = obj.GetParam(1);
+ Dictionary data = obj.GetParam>(2) ?? [];
- result = DataRequestController.GetResultData(type, data);
+ result = await DataRequestController.GetResultData(type, data);
}
catch (Exception e)
{
ServerHelper.Error(e);
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;
}
- public bool GamingMessageHandler(ClientSocket socket, SocketObject SocketObject)
+ protected async Task GamingRequestHandler(SocketObject obj)
{
if (NowGamingServer != null)
{
- Hashtable result = [];
+ Dictionary result = [];
+ Guid requestID = Guid.Empty;
GamingType type = GamingType.None;
- if (SocketObject.Parameters.Length > 0)
+ if (obj.Parameters.Length > 0)
{
try
{
- type = SocketObject.GetParam(0);
- Hashtable data = SocketObject.GetParam(1) ?? [];
+ type = obj.GetParam(0);
+ requestID = obj.GetParam(1);
+ Dictionary data = obj.GetParam>(2) ?? [];
- result = NowGamingServer.GamingMessageHandler(UserName, type, data);
+ result = await NowGamingServer.GamingMessageHandler(_username, type, data);
}
catch (Exception 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;
}
- public bool Send(ClientSocket socket, SocketMessageType type, params object[] objs)
+ protected async Task GamingMessageHandler(SocketObject obj)
+ {
+ if (NowGamingServer != null)
+ {
+ Dictionary result = [];
+ GamingType type = GamingType.None;
+
+ if (obj.Parameters.Length > 0)
+ {
+ try
+ {
+ type = obj.GetParam(0);
+ Dictionary data = obj.GetParam>(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 Send(SocketMessageType type, params object[] objs)
{
// 发送消息给客户端
try
{
- if (socket.Send(type, objs) == SocketResult.Success)
+ if (await Socket.SendAsync(type, objs) == SocketResult.Success)
{
switch (type)
{
@@ -207,14 +240,15 @@ namespace Milimoe.FunGame.Server.Model
break;
case SocketMessageType.Disconnect:
RemoveUser();
- Close();
+ await Close();
break;
case SocketMessageType.Chat:
return true;
}
- object obj = objs[0];
- if (obj.GetType() == typeof(string) && (string)obj != "")
- ServerHelper.WriteLine("[" + SocketSet.GetTypeString(type) + "] " + GetClientName() + " <- " + obj, InvokeMessageType.Core);
+ if (objs.Length > 0 && objs[0] is string str && str != "")
+ {
+ ServerHelper.WriteLine("[" + SocketSet.GetTypeString(type) + "] " + GetClientName() + " <- " + str, InvokeMessageType.Core);
+ }
return true;
}
throw new CanNotSendToClientException();
@@ -227,18 +261,36 @@ namespace Milimoe.FunGame.Server.Model
}
}
- public void Start()
- {
- if ((IServerModel)this is GameModuleServer) throw new NotSupportedException("请勿在GameModuleServer类中调用此方法"); // 禁止GameModuleServer调用
- Task StreamReader = Task.Factory.StartNew(CreateStreamReader);
- Task PeriodicalQuerier = Task.Factory.StartNew(CreatePeriodicalQuerier);
+ public async Task SendClients(IEnumerable clients, SocketMessageType type, params object[] objs)
+ {
+ // 发送消息给多个客户端
+ try
+ {
+ 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)
{
- _ClientName = ClientName;
+ _clientName = ClientName;
// 添加客户端到列表中
- Server.AddClient(_ClientName, this);
+ Listener.ClientList.Add(_clientName, this);
Config.OnlinePlayerCount++;
GetUsersCount();
}
@@ -248,55 +300,18 @@ namespace Milimoe.FunGame.Server.Model
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;
- UserName = username;
- CheckLoginKey = checkloginkey;
- }
-
- 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)
+ foreach (IServerModel serverTask in Listener.UserList.Where(model => usernames.Length > 0 && usernames.Contains(model.User.Username)))
{
- serverTask.Room = General.HallInstance;
- foreach (Room room in Config.RoomList.Cast())
+ if (serverTask != null && serverTask.Socket != null)
{
- QuitRoom(room.Roomid, room.RoomMaster.Id == User.Id);
+ serverTask.Send(SocketMessageType.System, showtype, msg, title, autoclose);
}
- serverTask.Send(serverTask.Socket, SocketMessageType.ForceLogout, msg);
}
}
- public bool QuitRoom(string roomid, bool isMaster)
+ public async Task QuitRoom(string roomid, bool isMaster)
{
bool result;
@@ -306,21 +321,21 @@ namespace Milimoe.FunGame.Server.Model
// 是否是房主
if (isMaster)
{
- List users = Config.RoomList.GetPlayerList(roomid);
+ List users = [.. Config.RoomList[roomid].UserAndIsReady.Keys];
if (users.Count > 0) // 如果此时房间还有人,更新房主
{
User NewMaster = users[0];
Room.RoomMaster = NewMaster;
SQLHelper?.Execute(RoomQuery.Update_QuitRoom(roomid, User.Id, NewMaster.Id));
- this.Room = General.HallInstance;
- UpdateRoomMaster(Room, true);
+ this.InRoom = General.HallInstance;
+ await UpdateRoomMaster(Room, true);
result = true;
}
else // 没人了就解散房间
{
Config.RoomList.RemoveRoom(roomid);
SQLHelper?.Execute(RoomQuery.Delete_QuitRoom(roomid, User.Id));
- this.Room = General.HallInstance;
+ this.InRoom = General.HallInstance;
ServerHelper.WriteLine("[ " + GetClientName() + " ] 解散了房间 " + roomid);
result = true;
}
@@ -328,236 +343,86 @@ namespace Milimoe.FunGame.Server.Model
// 不是房主直接退出房间
else
{
- this.Room = General.HallInstance;
- UpdateRoomMaster(Room);
+ this.InRoom = General.HallInstance;
+ await UpdateRoomMaster(Room);
result = true;
}
return result;
}
- public void Kick(string msg, string clientname = "")
+ public async Task UpdateRoomMaster(Room room, bool isUpdateRoomMaster = false)
{
- // 将客户端踢出服务器
- ServerModel serverTask = (ServerModel)Server.GetClient(clientname == "" ? ClientName : clientname);
- if (serverTask.Socket != null)
+ foreach (IServerModel Client in Listener.ClientList.Where(c => c != null && c.User.Id != 0 && room.Roomid == c.InRoom?.Roomid))
{
- serverTask.Room = General.HallInstance;
- foreach (Room room in Config.RoomList.Cast())
+ await Client.Send(SocketMessageType.Chat, User.Username, DateTimeUtility.GetNowShortTime() + " [ " + User.Username + " ] 离开了房间。");
+ if (isUpdateRoomMaster && room.RoomMaster?.Id != 0 && room.Roomid != "-1")
{
- QuitRoom(room.Roomid, room.RoomMaster.Id == User.Id);
- }
- RemoveUser();
- serverTask.Send(serverTask.Socket, SocketMessageType.Disconnect, msg);
- }
- Close();
- }
-
- public void Chat(string msg)
- {
- ServerHelper.WriteLine(msg);
- foreach (ServerModel Client in Server.ClientList.Cast())
- {
- if (Room.Roomid == Client.Room.Roomid)
- {
- if (Client != null && User.Id != 0)
- {
- Client.Send(Client.Socket!, SocketMessageType.Chat, User.Username, DateTimeUtility.GetNowShortTime() + msg);
- }
+ await Client.Send(SocketMessageType.UpdateRoomMaster, room);
}
}
}
- public void SendSystemMessage(ShowMessageType showtype, string msg, string title, int autoclose, params string[] usernames)
+ public async Task Kick(string msg)
{
- foreach (ServerModel serverTask in Server.UserList.Cast().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);
- }
- }
+ await QuitRoom(InRoom.Roomid, InRoom.RoomMaster.Id == User.Id);
+ RemoveUser();
+ InRoom = General.HallInstance;
+ await Send(SocketMessageType.Disconnect, msg);
+ await Close();
}
- public void StartGame(string roomid, List users, params string[] usernames)
+ public async Task ForceLogOut(string msg)
{
- 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 others = Server.UserList.Cast().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().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);
- }
- }
- }
- }
- });
+ await QuitRoom(InRoom.Roomid, InRoom.RoomMaster.Id == User.Id);
+ InRoom = General.HallInstance;
+ await Send(SocketMessageType.ForceLogout, msg);
}
- public void IntoRoom(string roomid)
- {
- Room = Config.RoomList[roomid];
- foreach (ServerModel Client in Server.ClientList.Cast().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().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 MatchingRoom(RoomType roomtype, User user)
- {
- int i = 1;
- int time = 0;
- while (IsMatching)
- {
- // 先列出符合条件的房间
- List targets = Config.RoomList.ListRoom.Where(r => r.RoomType == roomtype).ToList();
-
- // 匹配Elo
- foreach (Room room in targets)
- {
- // 计算房间平均Elo
- List 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()
+ public async Task ForceLogOutDuplicateLogonUser()
{
if (User.Id != 0)
{
string user = User.Username;
- if (Server.ContainsUser(user))
+ if (Listener.UserList.ContainsKey(user))
{
ServerHelper.WriteLine("OnlinePlayers: 玩家 " + user + " 重复登录!");
- ForceLogOut("您的账号在别处登录,已强制下线。");
+ await ForceLogOut("您的账号在别处登录,已强制下线。");
}
}
}
- private bool AddUser()
+ public bool AddUser()
{
if (User.Id != 0 && this != null)
{
- Server.AddUser(User.Username, this);
- UserName = User.Username;
+ Listener.UserList.Add(User.Username, this);
+ _username = 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 false;
}
- private bool RemoveUser()
+ public bool RemoveUser()
{
if (User.Id != 0 && this != null)
{
- LogoutTime = DateTime.Now.Ticks;
- int TotalMinutes = Convert.ToInt32((new DateTime(LogoutTime) - new DateTime(LoginTime)).TotalMinutes);
+ _logoutTime = DateTime.Now.Ticks;
+ int TotalMinutes = Convert.ToInt32((new DateTime(_logoutTime) - new DateTime(_loginTime)).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 + "分钟");
}
else ServerHelper.WriteLine("OnlinePlayers: 无法更新玩家 " + User.Username + " 的游戏时长");
- if (Server.RemoveUser(User.Username))
+ if (Listener.UserList.Remove(User.Username))
{
ServerHelper.WriteLine("OnlinePlayers: 玩家 " + User.Username + " 已移除");
- _User = General.UnknownUserInstance;
+ User = General.UnknownUserInstance;
return true;
}
else ServerHelper.WriteLine("OnlinePlayers: 移除玩家 " + User.Username + " 失败");
@@ -565,37 +430,65 @@ namespace Milimoe.FunGame.Server.Model
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 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");
while (Running)
{
if (Socket != null)
{
- if (!Read(Socket))
+ if (!await Read(Socket))
{
- FailedTimes++;
- if (FailedTimes >= Config.MaxConnectionFaileds)
+ _failedTimes++;
+ if (_failedTimes >= Config.MaxConnectionFaileds)
{
RemoveUser();
- Close();
+ await Close();
ServerHelper.WriteLine(GetClientName() + " Error -> Too Many Faileds.");
ServerHelper.WriteLine(GetClientName() + " Close -> StreamReader is Closed.");
break;
}
}
- else if (FailedTimes - 1 >= 0) FailedTimes--;
+ else if (_failedTimes - 1 >= 0) _failedTimes--;
}
else
{
RemoveUser();
- Close();
+ await Close();
ServerHelper.WriteLine(GetClientName() + " Error -> Socket is Closed.");
ServerHelper.WriteLine(GetClientName() + " Close -> StringStream is Closed.");
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");
while (Running)
{
// 每两小时触发一次SQL服务器的心跳查询,防止SQL服务器掉线
try
{
- Thread.Sleep(2 * 1000 * 3600);
- SQLHelper?.ExecuteDataSet(UserQuery.Select_IsExistUsername(UserName));
+ await Task.Delay(2 * 1000 * 3600);
+ SQLHelper?.ExecuteDataSet(UserQuery.Select_IsExistUsername(_username));
}
catch (Exception e)
{
ServerHelper.Error(e);
RemoveUser();
- Close();
+ await Close();
ServerHelper.WriteLine(GetClientName() + " Error -> Socket is Closed.");
ServerHelper.WriteLine(GetClientName() + " Close -> StringStream is Closed.");
}
}
}
- private void Close()
+ protected async Task Close()
{
try
{
SQLHelper?.Close();
- _SQLHelper = null;
+ _sqlHelper = null;
MailSender?.Dispose();
- _MailSender = null;
- Socket?.Close();
- _Socket = null;
- _Running = false;
- Server.RemoveClient(ClientName);
+ _mailer = null;
+ await Socket.CloseAsync();
+ _running = false;
+ Listener.ClientList.Remove(ClientName);
Config.OnlinePlayerCount--;
GetUsersCount();
}
diff --git a/FunGame.Server/Others/Config.cs b/FunGame.Server/Others/Config.cs
index c2f2de0..8ca94e5 100644
--- a/FunGame.Server/Others/Config.cs
+++ b/FunGame.Server/Others/Config.cs
@@ -2,11 +2,13 @@
using System.Text;
using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
+using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Library.SQLScript.Common;
using Milimoe.FunGame.Core.Library.SQLScript.Entity;
using Milimoe.FunGame.Core.Model;
using Milimoe.FunGame.Server.Utility;
+using Milimoe.FunGame.Server.Utility.DataUtility;
namespace Milimoe.FunGame.Server.Others
{
@@ -18,10 +20,35 @@ namespace Milimoe.FunGame.Server.Others
public static string ServerName { get; set; } = "FunGame Server";
///
- /// 默认端口
+ /// Socket 端口
///
public static int ServerPort { get; set; } = 22222;
+ ///
+ /// 使用 WebSocket
+ ///
+ public static bool UseWebSocket { get; set; } = false;
+
+ ///
+ /// WebSocket 监听地址
+ ///
+ public static string WebSocketAddress { get; set; } = "localhost";
+
+ ///
+ /// WebSocket 端口
+ ///
+ public static int WebSocketPort { get; set; } = 22222;
+
+ ///
+ /// WebSocket 监听子路径
+ ///
+ public static string WebSocketSubUrl { get; set; } = "ws";
+
+ ///
+ /// WebSocket 开启 SSL
+ ///
+ public static bool WebSocketSSL { get; set; } = false;
+
///
/// 默认状态:1可连接 0不可连接 -1不可用
///
@@ -50,7 +77,7 @@ namespace Milimoe.FunGame.Server.Others
///
/// 禁止连接的黑名单
///
- public static string ServerBannedList { get; set; } = "";
+ public static List ServerBannedList { get; set; } = [];
///
/// 最多接受连接的玩家数量
@@ -95,7 +122,7 @@ namespace Milimoe.FunGame.Server.Others
///
/// 是否运行数据库模式
///
- public static bool SQLMode { get; set; } = false;
+ public static SQLMode SQLMode { get; set; } = SQLMode.None;
///
/// Server实际安装的模组
@@ -114,7 +141,7 @@ namespace Milimoe.FunGame.Server.Others
{
get
{
- if (_SQLHelper is null) throw new MySQLConfigException();
+ if (_SQLHelper is null) throw new SQLServiceException();
return _SQLHelper;
}
}
@@ -128,12 +155,30 @@ namespace Milimoe.FunGame.Server.Others
{
try
{
- _SQLHelper = new MySQLHelper("", false);
- if (((MySQLHelper)_SQLHelper).Connection != null)
+ if (INIHelper.ExistINIFile())
{
- SQLMode = true;
- ServerLogin();
- ClearRoomList();
+ if (INIHelper.ReadINI("MySQL", "UseMySQL").Trim() == "true")
+ {
+ _SQLHelper = new MySQLHelper("", false);
+ if (((MySQLHelper)_SQLHelper).Connection != null)
+ {
+ SQLMode = _SQLHelper.Mode;
+ ServerLogin();
+ 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)
@@ -142,12 +187,45 @@ namespace Milimoe.FunGame.Server.Others
}
}
+ public static bool GetGameModuleList()
+ {
+ List supported = [];
+ // 构建AddonController
+ Hashtable delegates = [];
+ delegates.Add("WriteLine", new Action(msg => ServerHelper.WriteLine(msg, InvokeMessageType.GameModule)));
+ delegates.Add("Error", new Action(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;
+ }
+
///
/// 服务器启动登记
///
public static void ServerLogin()
{
- if (SQLMode)
+ if (SQLMode != SQLMode.None)
{
SQLHelper.Execute(ServerLoginLogs.Insert_ServerLoginLogs(ServerName, ServerKey));
}
@@ -158,7 +236,7 @@ namespace Milimoe.FunGame.Server.Others
///
public static void ClearRoomList()
{
- if (SQLMode)
+ if (SQLMode != SQLMode.None)
{
SQLHelper.Execute(RoomQuery.Delete_Rooms());
}
diff --git a/FunGame.Server/Utilities/ConnectProperties.cs b/FunGame.Server/Utilities/ConnectProperties.cs
new file mode 100644
index 0000000..0cd08ee
--- /dev/null
+++ b/FunGame.Server/Utilities/ConnectProperties.cs
@@ -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; } = "";
+
+ ///
+ /// 读取MySQL服务器配置文件
+ ///
+ ///
+ 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;";
+ }
+
+ ///
+ /// 读取SQLite服务器配置文件
+ ///
+ ///
+ public static string GetConnectPropertiesForSQLite()
+ {
+ if (DataSource == "")
+ {
+ if (INIHelper.ExistINIFile())
+ {
+ DataSource = INIHelper.ReadINI("SQLite", "DataSource");
+ }
+ else ServerHelper.Error(new SQLServiceException());
+ }
+ return "data source=" + DataSource;
+ }
+ }
+}
diff --git a/FunGame.Server/Utilities/General.cs b/FunGame.Server/Utilities/General.cs
index 4d79b0e..cad0202 100644
--- a/FunGame.Server/Utilities/General.cs
+++ b/FunGame.Server/Utilities/General.cs
@@ -26,6 +26,10 @@ namespace Milimoe.FunGame.Server.Utility
Console.ForegroundColor = ConsoleColor.Yellow;
prefix = "[Api] ";
break;
+ case InvokeMessageType.Warning:
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ prefix = "[Warning] ";
+ break;
case InvokeMessageType.Interface:
Console.ForegroundColor = ConsoleColor.Magenta;
prefix = "[Interface] ";
@@ -62,15 +66,18 @@ namespace Milimoe.FunGame.Server.Utility
public static void Write(string msg, InvokeMessageType type = InvokeMessageType.System)
{
if (msg.Trim() != "") Console.Write("\r" + GetPrefix(type) + msg + "> ");
+ Console.ResetColor();
}
public static void WriteLine(string msg, InvokeMessageType type = InvokeMessageType.System)
{
if (msg.Trim() != "") Console.Write("\r" + GetPrefix(type) + msg + "\n\r> ");
+ Console.ResetColor();
}
public static void Type()
{
+ Console.ResetColor();
Console.Write("\r> ");
}
@@ -86,7 +93,7 @@ namespace Milimoe.FunGame.Server.Utility
private static Hashtable GetServerSettingHashtable()
{
- Hashtable settings = new();
+ Hashtable settings = [];
if (INIHelper.ExistINIFile())
{
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("SupportMail", INIHelper.ReadINI("ServerMail", "SupportMail"));
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("MaxConnectFailed", Convert.ToInt32(INIHelper.ReadINI("Socket", "MaxConnectFailed")));
}
@@ -124,7 +136,7 @@ namespace Milimoe.FunGame.Server.Utility
if (Describe != null) Config.ServerDescription = Describe;
if (Notice != null) Config.ServerNotice = Notice;
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? SupportMail = (string?)settings["SupportMail"];
@@ -134,18 +146,28 @@ namespace Milimoe.FunGame.Server.Utility
int? Status = (int?)settings["Status"];
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? MaxConnectFailed = (int?)settings["MaxConnectFailed"];
if (Status != null) Config.ServerStatus = (int)Status;
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 (MaxConnectFailed != null) Config.MaxConnectionFaileds = (int)MaxConnectFailed;
}
}
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);
}
}
- ServerHelper.WriteLine("Smtp服务处于关闭状态");
+ ServerHelper.WriteLine("SMTP 服务处于关闭状态", InvokeMessageType.Warning);
return null;
}
throw new SmtpHelperException();
diff --git a/FunGame.Server/Utilities/MySQLConnection.cs b/FunGame.Server/Utilities/MySQL/MySQLConnection.cs
similarity index 53%
rename from FunGame.Server/Utilities/MySQLConnection.cs
rename to FunGame.Server/Utilities/MySQL/MySQLConnection.cs
index 6ca0cec..79fe54c 100644
--- a/FunGame.Server/Utilities/MySQLConnection.cs
+++ b/FunGame.Server/Utilities/MySQL/MySQLConnection.cs
@@ -1,41 +1,8 @@
-using Milimoe.FunGame.Core.Api.Utility;
-using Milimoe.FunGame.Core.Model;
+using Milimoe.FunGame.Core.Model;
using MySql.Data.MySqlClient;
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; } = "";
-
- ///
- /// 读取MySQL服务器配置文件
- ///
- ///
- 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 MySqlConnection? Connection
@@ -87,15 +54,15 @@ namespace Milimoe.FunGame.Server.Utility.DataUtility
{
try
{
- string _GetConnection = ConnectProperties.GetConnectProperties();
- if (_GetConnection != null)
+ string connectionString = ConnectProperties.GetConnectPropertiesForMySQL();
+ if (connectionString != null)
{
- string[] DataSetting = _GetConnection.Split(";");
- if (DataSetting.Length > 1 && DataSetting[0].Length > 14 && DataSetting[1].Length > 8)
+ string[] strings = connectionString.Split(";");
+ 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();
if (_Connection.State == System.Data.ConnectionState.Open)
{
diff --git a/FunGame.Server/Utilities/MySQLHelper.cs b/FunGame.Server/Utilities/MySQL/MySQLHelper.cs
similarity index 96%
rename from FunGame.Server/Utilities/MySQLHelper.cs
rename to FunGame.Server/Utilities/MySQL/MySQLHelper.cs
index ee1f6a2..7db2d5f 100644
--- a/FunGame.Server/Utilities/MySQLHelper.cs
+++ b/FunGame.Server/Utilities/MySQL/MySQLHelper.cs
@@ -1,8 +1,8 @@
using System.Data;
using Milimoe.FunGame.Core.Api.Transmittal;
+using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
-using Milimoe.FunGame.Server.Model;
using Milimoe.FunGame.Server.Others;
using Milimoe.FunGame.Server.Utility.DataUtility;
using MySql.Data.MySqlClient;
@@ -12,6 +12,7 @@ namespace Milimoe.FunGame.Server.Utility
public class MySQLHelper : SQLHelper
{
public override FunGameInfo.FunGame FunGameType => Config.FunGameType;
+ public override SQLMode Mode => SQLMode.MySQL;
public override string Script { get; set; } = "";
public override CommandType CommandType { get; set; } = CommandType.Text;
public override SQLResult Result => _Result;
@@ -27,7 +28,7 @@ namespace Milimoe.FunGame.Server.Utility
private DataSet _DataSet = new();
private MySQLConnection? _Connection;
private MySqlTransaction? _Transaction;
- private readonly ServerModel? ServerModel;
+ private readonly IServerModel? ServerModel;
private readonly bool _IsOneTime = false;
///
@@ -123,7 +124,7 @@ namespace Milimoe.FunGame.Server.Utility
/// 创建为SocketModel服务的SQLHelper
///
/// SocketModel
- public MySQLHelper(ServerModel ServerModel)
+ public MySQLHelper(IServerModel ServerModel)
{
this.ServerModel = ServerModel;
Script = "";
diff --git a/FunGame.Server/Utilities/MySQLManager.cs b/FunGame.Server/Utilities/MySQL/MySQLManager.cs
similarity index 100%
rename from FunGame.Server/Utilities/MySQLManager.cs
rename to FunGame.Server/Utilities/MySQL/MySQLManager.cs
diff --git a/FunGame.Server/Utilities/SQLite/SQLiteHelper.cs b/FunGame.Server/Utilities/SQLite/SQLiteHelper.cs
new file mode 100644
index 0000000..9b0ca92
--- /dev/null
+++ b/FunGame.Server/Utilities/SQLite/SQLiteHelper.cs
@@ -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);
+ }
+
+ ///
+ /// 打开数据库连接
+ ///
+ private void OpenConnection()
+ {
+ if (_connection.State != ConnectionState.Open)
+ {
+ _connection.Open();
+ }
+ }
+
+ ///
+ /// 关闭数据库连接
+ ///
+ public override void Close()
+ {
+ _transaction?.Dispose();
+ if (_connection.State != ConnectionState.Closed)
+ {
+ _connection.Close();
+ }
+ }
+
+ ///
+ /// 执行一个命令
+ ///
+ ///
+ public override int Execute()
+ {
+ return Execute(Script);
+ }
+
+ ///
+ /// 执行一个指定的命令
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 查询DataSet
+ ///
+ ///
+ public override DataSet ExecuteDataSet()
+ {
+ return ExecuteDataSet(Script);
+ }
+
+ ///
+ /// 执行指定的命令查询DataSet
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 执行指定的命令查询DataRow
+ ///
+ ///
+ public override DataRow? ExecuteDataRow()
+ {
+ return ExecuteDataRow(Script);
+ }
+
+ ///
+ /// 执行指定的命令查询DataRow
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// 创建一个SQL事务
+ ///
+ public override void NewTransaction()
+ {
+ OpenConnection();
+ _transaction = _connection.BeginTransaction();
+ }
+
+ ///
+ /// 提交事务
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 回滚事务
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 资源清理
+ ///
+ public void Dispose()
+ {
+ _transaction?.Dispose();
+ _connection.Dispose();
+ }
+ }
+}
diff --git a/FunGame.Server/app.manifest b/FunGame.Server/app.manifest
new file mode 100644
index 0000000..04ee0ee
--- /dev/null
+++ b/FunGame.Server/app.manifest
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FunGame.WebAPI/Architecture/RESTfulAPI.cs b/FunGame.WebAPI/Architecture/RESTfulAPI.cs
new file mode 100644
index 0000000..1a919e5
--- /dev/null
+++ b/FunGame.WebAPI/Architecture/RESTfulAPI.cs
@@ -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 ReceiveAsync()
+ {
+ await Task.Delay(100);
+ return [];
+ }
+
+ public SocketResult Send(SocketMessageType type, params object[] objs)
+ {
+ return SocketResult.Success;
+ }
+
+ public async Task SendAsync(SocketMessageType type, params object[] objs)
+ {
+ await Task.Delay(100);
+ return SocketResult.Success;
+ }
+ }
+}
diff --git a/FunGame.WebAPI/Architecture/RESTfulAPIListener.cs b/FunGame.WebAPI/Architecture/RESTfulAPIListener.cs
new file mode 100644
index 0000000..5b16b99
--- /dev/null
+++ b/FunGame.WebAPI/Architecture/RESTfulAPIListener.cs
@@ -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
+ {
+ public string Name => "RESTfulAPIListener";
+
+ public ConcurrentModelList ClientList { get; } = [];
+
+ public ConcurrentModelList UserList { get; } = [];
+
+ public List BannedList { get; } = [];
+
+ public void Close()
+ {
+
+ }
+ }
+}
diff --git a/FunGame.WebAPI/Architecture/WebAPIListener.cs b/FunGame.WebAPI/Architecture/WebAPIListener.cs
new file mode 100644
index 0000000..de01a78
--- /dev/null
+++ b/FunGame.WebAPI/Architecture/WebAPIListener.cs
@@ -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
+ {
+ public string Name => "WebAPIListener";
+
+ public ConcurrentModelList ClientList { get; } = [];
+
+ public ConcurrentModelList UserList { get; } = [];
+
+ public List BannedList { get; } = [];
+
+ public void Close()
+ {
+
+ }
+ }
+}
diff --git a/FunGame.WebAPI/Controllers/PostDataController.cs b/FunGame.WebAPI/Controllers/PostDataController.cs
new file mode 100644
index 0000000..2821c57
--- /dev/null
+++ b/FunGame.WebAPI/Controllers/PostDataController.cs
@@ -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 logger) : ControllerBase
+ {
+ public static Dictionary ResultDatas { get; } = [];
+
+ private readonly ILogger _logger = logger;
+
+ [HttpPost("{username}", Name = "username")]
+ public async Task Post(string username, [FromBody] SocketObject obj)
+ {
+ try
+ {
+ RESTfulAPIListener? apiListener = Singleton.Get();
+ 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, "ڲ");
+ }
+ }
+ }
+}
diff --git a/FunGame.WebAPI/Controllers/UserController.cs b/FunGame.WebAPI/Controllers/UserController.cs
new file mode 100644
index 0000000..7f5b63f
--- /dev/null
+++ b/FunGame.WebAPI/Controllers/UserController.cs
@@ -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 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();
+ 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));
+ }
+ }
+}
diff --git a/FunGame.WebAPI/FunGame.WebAPI.csproj b/FunGame.WebAPI/FunGame.WebAPI.csproj
new file mode 100644
index 0000000..545abd7
--- /dev/null
+++ b/FunGame.WebAPI/FunGame.WebAPI.csproj
@@ -0,0 +1,48 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Milimoe.$(MSBuildProjectName.Replace(" ", "_"))
+ ..\bin\
+ FunGameWebAPI
+ Images\logo.ico
+ Milimoe
+
+
+
+ embedded
+
+
+
+ embedded
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\FunGame.Core\bin\Debug\net8.0\FunGame.Core.dll
+
+
+
+
+
+ \
+ True
+
+
+
+
diff --git a/FunGame.WebAPI/FunGame.WebAPI.http b/FunGame.WebAPI/FunGame.WebAPI.http
new file mode 100644
index 0000000..adae99e
--- /dev/null
+++ b/FunGame.WebAPI/FunGame.WebAPI.http
@@ -0,0 +1,6 @@
+@FunGame.WebAPI_HostAddress = http://localhost:5117
+
+GET {{FunGame.WebAPI_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/FunGame.WebAPI/Images/logo.ico b/FunGame.WebAPI/Images/logo.ico
new file mode 100644
index 0000000000000000000000000000000000000000..4082874accbeb80cd336bcb14ff9004ad35920ff
GIT binary patch
literal 16958
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