diff --git a/FunGame.Server/Controllers/DataRequestController.cs b/FunGame.Server/Controllers/DataRequestController.cs index 996a4a3..b760d61 100644 --- a/FunGame.Server/Controllers/DataRequestController.cs +++ b/FunGame.Server/Controllers/DataRequestController.cs @@ -4,13 +4,13 @@ using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Library.Common.Addon; -using Milimoe.FunGame.Core.Library.Common.Event; using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.SQLScript.Common; using Milimoe.FunGame.Core.Library.SQLScript.Entity; using Milimoe.FunGame.Server.Model; using Milimoe.FunGame.Server.Others; using Milimoe.FunGame.Server.Services; +using ProjectRedbud.FunGame.SQLQueryExtension; namespace Milimoe.FunGame.Server.Controller { @@ -51,6 +51,7 @@ namespace Milimoe.FunGame.Server.Controller { Dictionary result = []; _lastRequest = type; + ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); switch (type) { @@ -117,7 +118,8 @@ namespace Milimoe.FunGame.Server.Controller UpdatePassword(data, result); break; - case DataRequestType.Room_GetRoomSettings: + case DataRequestType.Room_UpdateRoomSettings: + UpdateRoomSettings(data, result); break; case DataRequestType.Room_GetRoomPlayerCount: @@ -125,6 +127,75 @@ namespace Milimoe.FunGame.Server.Controller break; case DataRequestType.Room_UpdateRoomMaster: + await UpdateRoomMaster(data, result); + break; + + case DataRequestType.UserCenter_UpdateUser: + UpdateUser(data, result); + break; + + case DataRequestType.UserCenter_UpdatePassword: + UpdatePassword(data, result); + break; + + case DataRequestType.UserCenter_DailySignIn: + DailySignIn(result); + break; + + case DataRequestType.Inventory_GetStore: + GetStore(data, result); + break; + + case DataRequestType.Inventory_GetMarket: + GetMarket(data, result); + break; + + case DataRequestType.Inventory_StoreBuy: + StoreBuy(data, result); + break; + + case DataRequestType.Inventory_MarketBuy: + MarketBuy(data, result); + break; + + case DataRequestType.Inventory_UpdateInventory: + UpdateInventory(data, result); + break; + + case DataRequestType.Inventory_Use: + Use(data, result); + break; + + case DataRequestType.Inventory_StoreSell: + StoreSell(data, result); + break; + + case DataRequestType.Inventory_MarketSell: + MarketSell(data, result); + break; + + case DataRequestType.Inventory_MarketDelist: + MarketDelist(data, result); + break; + + case DataRequestType.Inventory_UpdateMarketPrice: + UpdateMarketPrice(data, result); + break; + + case DataRequestType.Inventory_GetOffer: + GetOffer(data, result); + break; + + case DataRequestType.Inventory_MakeOffer: + MakeOffer(data, result); + break; + + case DataRequestType.Inventory_ReviseOffer: + ReviseOffer(data, result); + break; + + case DataRequestType.Inventory_RespondOffer: + RespondOffer(data, result); break; default: @@ -147,7 +218,6 @@ namespace Milimoe.FunGame.Server.Controller Guid key = Guid.Empty; if (requestData.Count >= 1) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); key = DataRequest.GetDictionaryJsonObject(requestData, "key"); if (Server.IsLoginKey(key)) { @@ -169,9 +239,8 @@ namespace Milimoe.FunGame.Server.Controller /// 获取公告 /// /// - private void GetServerNotice(Dictionary resultData) + private static void GetServerNotice(Dictionary resultData) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); resultData.Add("notice", Config.ServerNotice); } @@ -185,12 +254,12 @@ namespace Milimoe.FunGame.Server.Controller Room room = General.HallInstance; if (requestData.Count >= 3) { - 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 == "" || FunGameSystem.GameModuleLoader is null || !FunGameSystem.GameModuleLoader.ModuleServers.ContainsKey(gamemodule) || !FunGameSystem.GameModuleLoader.Maps.ContainsKey(gamemap)) + RoomType type = DataRequest.GetDictionaryJsonObject(requestData, "roomType"); + string gameModule = DataRequest.GetDictionaryJsonObject(requestData, "moduleServer") ?? ""; + string gameMap = DataRequest.GetDictionaryJsonObject(requestData, "map") ?? ""; + bool isRank = DataRequest.GetDictionaryJsonObject(requestData, "isRank"); + ServerHelper.WriteLine("[CreateRoom] " + RoomSet.GetTypeString(type) + " (" + string.Join(", ", [gameModule, gameMap]) + ")", InvokeMessageType.DataRequest); + if (gameModule == "" || gameMap == "" || FunGameSystem.GameModuleLoader is null || !FunGameSystem.GameModuleLoader.ModuleServers.ContainsKey(gameModule) || !FunGameSystem.GameModuleLoader.Maps.ContainsKey(gameMap)) { ServerHelper.WriteLine("缺少对应的模组或地图,无法创建房间。"); resultData.Add("room", room); @@ -198,7 +267,7 @@ namespace Milimoe.FunGame.Server.Controller } User user = DataRequest.GetDictionaryJsonObject(requestData, "master") ?? Factory.GetUser(); string password = DataRequest.GetDictionaryJsonObject(requestData, "password") ?? ""; - int maxusers = DataRequest.GetDictionaryJsonObject(requestData, "maxusers"); + int maxusers = DataRequest.GetDictionaryJsonObject(requestData, "maxUsers"); if (user.Id != 0) { @@ -214,14 +283,14 @@ namespace Milimoe.FunGame.Server.Controller } if (roomid != "-1" && SQLHelper != null) { - SQLHelper.Execute(RoomQuery.Insert_CreateRoom(SQLHelper, roomid, user.Id, type, gamemodule, gamemap, isrank, password, maxusers)); + SQLHelper.Execute(RoomQuery.Insert_CreateRoom(SQLHelper, 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(SQLHelper, roomid)); - if (SQLHelper.Result == SQLResult.Success && SQLHelper.DataSet.Tables[0].Rows.Count > 0) + DataRow? dr = SQLHelper.ExecuteDataRow(RoomQuery.Select_IsExistRoom(SQLHelper, roomid)); + if (dr != null) { - room = Factory.GetRoom(SQLHelper.DataSet.Tables[0].Rows[0], user); + room = Factory.GetRoom(dr, user); FunGameSystem.RoomList.AddRoom(room); } } @@ -235,9 +304,8 @@ namespace Milimoe.FunGame.Server.Controller /// 更新房间列表 /// /// - private void UpdateRoom(Dictionary resultData) + private static void UpdateRoom(Dictionary resultData) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); resultData.Add("rooms", FunGameSystem.RoomList.ListRoom); // 传RoomList } @@ -251,7 +319,6 @@ namespace Milimoe.FunGame.Server.Controller bool result = false; if (requestData.Count >= 2) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); string roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; bool isMaster = DataRequest.GetDictionaryJsonObject(requestData, "isMaster"); @@ -273,7 +340,6 @@ namespace Milimoe.FunGame.Server.Controller bool result = false; if (requestData.Count >= 1) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); string roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; if (roomid != "-1") @@ -285,6 +351,7 @@ namespace Milimoe.FunGame.Server.Controller { FunGameSystem.RoomList.IntoRoom(roomid, Server.User); Server.InRoom = FunGameSystem.RoomList[roomid]; + Server.User.OnlineState = OnlineState.InRoom; 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; @@ -309,18 +376,18 @@ namespace Milimoe.FunGame.Server.Controller bool result = true; if (requestData.Count >= 1) { - bool iscancel = DataRequest.GetDictionaryJsonObject(requestData, "iscancel"); + bool iscancel = DataRequest.GetDictionaryJsonObject(requestData, "isCancel"); if (!iscancel) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest) + " : Start", InvokeMessageType.DataRequest); - RoomType type = DataRequest.GetDictionaryJsonObject(requestData, "roomtype"); + ServerHelper.WriteLine("[MatchRoom] 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); + ServerHelper.WriteLine("[MatchRoom] Cancel", InvokeMessageType.DataRequest); StopMatching(); } } @@ -338,7 +405,6 @@ namespace Milimoe.FunGame.Server.Controller string roomid = "-1"; if (requestData.Count >= 1) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; User user = Server.User; @@ -364,7 +430,6 @@ namespace Milimoe.FunGame.Server.Controller string roomid = "-1"; if (requestData.Count >= 1) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; User user = Server.User; @@ -406,7 +471,6 @@ namespace Milimoe.FunGame.Server.Controller bool result = false; if (requestData.Count >= 2) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); string roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; bool isMaster = DataRequest.GetDictionaryJsonObject(requestData, "isMaster"); @@ -482,12 +546,15 @@ namespace Milimoe.FunGame.Server.Controller { if (FunGameSystem.GameModuleLoader != null && FunGameSystem.GameModuleLoader.ModuleServers.ContainsKey(room.GameModule)) { + room.RoomState = RoomState.Gaming; Server.NowGamingServer = FunGameSystem.GameModuleLoader.GetServerMode(room.GameModule); + Server.User.OnlineState = OnlineState.Gaming; 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; + model.User.OnlineState = OnlineState.Gaming; } GamingObject obj = new(room, users, Server, all); if (Server.NowGamingServer.StartServer(obj)) @@ -522,7 +589,6 @@ namespace Milimoe.FunGame.Server.Controller bool success = false; if (requestData.Count >= 4) { - 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") ?? ""; @@ -549,7 +615,6 @@ namespace Milimoe.FunGame.Server.Controller /// private async Task Login(Dictionary requestData, Dictionary resultData) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); string msg = ""; User user = Factory.GetUser(); @@ -569,18 +634,6 @@ namespace Milimoe.FunGame.Server.Controller ServerHelper.WriteLine("客户端提供的参数不足。", InvokeMessageType.DataRequest, LogLevel.Warning); } - LoginEventArgs eventArgs = new(username, password, autokey); - FunGameSystem.ServerPluginLoader?.OnBeforeLoginEvent(this, eventArgs); - FunGameSystem.WebAPIPluginLoader?.OnBeforeLoginEvent(this, eventArgs); - if (eventArgs.Cancel) - { - msg = $"{DataRequestSet.GetTypeString(_lastRequest)} 请求已取消。{(eventArgs.EventMsg != "" ? $"原因:{eventArgs.EventMsg}" : "")}"; - ServerHelper.WriteLine(msg); - resultData.Add("msg", msg); - resultData.Add("user", user); - return; - } - // CheckLogin的情况 if (key != Guid.Empty) { @@ -606,8 +659,6 @@ namespace Milimoe.FunGame.Server.Controller } } - FunGameSystem.ServerPluginLoader?.OnAfterLoginEvent(this, eventArgs); - FunGameSystem.WebAPIPluginLoader?.OnAfterLoginEvent(this, eventArgs); resultData.Add("msg", msg); resultData.Add("user", user); } @@ -622,10 +673,9 @@ namespace Milimoe.FunGame.Server.Controller string msg = "无法找回您的密码,请稍后再试。"; // 返回的验证信息 if (requestData.Count >= 3) { - 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) ?? ""; + string username = DataRequest.GetDictionaryJsonObject(requestData, "username") ?? ""; + string email = DataRequest.GetDictionaryJsonObject(requestData, "email") ?? ""; + string verifycode = DataRequest.GetDictionaryJsonObject(requestData, "forgetVerifyCode") ?? ""; // 客户端发来了验证码就进行验证,没有发就生成 if (verifycode.Trim() != "") @@ -633,11 +683,11 @@ namespace Milimoe.FunGame.Server.Controller // 先检查验证码 if (SQLHelper != null) { - SQLHelper.ExecuteDataSet(ForgetVerifyCodes.Select_ForgetVerifyCode(SQLHelper, username, email, verifycode)); - if (SQLHelper.Result == SQLResult.Success) + DataRow? dr = SQLHelper.ExecuteDataRow(ForgetVerifyCodes.Select_ForgetVerifyCode(SQLHelper, username, email, verifycode)); + if (dr != null) { // 检查验证码是否过期 - if (!DateTime.TryParse(SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_SendTime].ToString(), out DateTime SendTime)) + if (!DateTime.TryParse(dr[ForgetVerifyCodes.Column_SendTime].ToString(), out DateTime SendTime)) { SendTime = General.DefaultTime; } @@ -650,7 +700,7 @@ namespace Milimoe.FunGame.Server.Controller else { // 检查验证码是否正确 - if (verifycode.Equals(SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_ForgetVerifyCode])) + if (verifycode.Equals(dr[ForgetVerifyCodes.Column_ForgetVerifyCode])) { ServerHelper.WriteLine("[ForgerPassword] Username: " + username + " Email: " + email); SQLHelper.Execute(ForgetVerifyCodes.Delete_ForgetVerifyCode(SQLHelper, username, email)); @@ -675,8 +725,8 @@ namespace Milimoe.FunGame.Server.Controller else { // 检查验证码是否发送过和是否过期 - SQLHelper.ExecuteDataSet(ForgetVerifyCodes.Select_HasSentForgetVerifyCode(SQLHelper, username, email)); - if (SQLHelper.Result != SQLResult.Success || (DateTime.TryParse(SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_SendTime].ToString(), out DateTime SendTime) && (DateTime.Now - SendTime).TotalMinutes >= 10)) + DataRow? dr = SQLHelper.ExecuteDataRow(ForgetVerifyCodes.Select_HasSentForgetVerifyCode(SQLHelper, username, email)); + if (dr is null || (DateTime.TryParse(dr[ForgetVerifyCodes.Column_SendTime].ToString(), out DateTime SendTime) && (DateTime.Now - SendTime).TotalMinutes >= 10)) { // 发送验证码,需要先删除之前过期的验证码 SQLHelper.Execute(ForgetVerifyCodes.Delete_ForgetVerifyCode(SQLHelper, username, email)); @@ -688,8 +738,8 @@ namespace Milimoe.FunGame.Server.Controller { // 发送验证码 string ServerName = Config.ServerName; - string Subject = $"[{ServerName}] FunGame 找回密码验证码"; - string Body = $"亲爱的 {username},
您正在找回[{ServerName}]账号的密码,您的验证码是 {forgetVerify} ,10分钟内有效,请及时输入!

{ServerName}
{DateTimeUtility.GetDateTimeToString(TimeType.LongDateOnly)}"; + string Subject = $"[{ServerName}] 找回密码验证码"; + 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) { @@ -704,7 +754,7 @@ namespace Milimoe.FunGame.Server.Controller } else // 不使用MailSender的情况 { - ServerHelper.WriteLine(Server.GetClientName() + $" 验证码为:{forgetVerify},请服务器管理员告知此用户"); + ServerHelper.WriteLine(Server.GetClientName() + $" 验证码为:{forgetVerify},但因 SMTP 服务未开启,请服务器管理员告知此用户"); msg = ""; } } @@ -712,7 +762,7 @@ namespace Milimoe.FunGame.Server.Controller else { // 发送过验证码且验证码没有过期 - string ForgetVerifyCode = (string)SQLHelper.DataSet.Tables[0].Rows[0][ForgetVerifyCodes.Column_ForgetVerifyCode]; + string ForgetVerifyCode = (string)dr[ForgetVerifyCodes.Column_ForgetVerifyCode]; ServerHelper.WriteLine(Server.GetClientName() + $" 十分钟内已向{email}发送过验证码:{ForgetVerifyCode}"); msg = ""; } @@ -723,54 +773,113 @@ namespace Milimoe.FunGame.Server.Controller resultData.Add("msg", msg); } - /// - /// 更新用户的密码 - /// - /// - /// - private void UpdatePassword(Dictionary requestData, Dictionary resultData) - { - string msg = "无法更新您的密码,请稍后再试。"; - if (requestData.Count >= 2) - { - 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() != "") - { - FunGameSystem.UpdateUserKey(username); - password = password.Encrypt(FunGameSystem.GetUserKey(username)); - SQLHelper?.Execute(UserQuery.Update_Password(SQLHelper, username, password)); - if (SQLHelper?.Success ?? false) - { - // 更新成功返回空值 - msg = ""; - } - } - } - resultData.Add("msg", msg); - } - #endregion #region Room + /// + /// 更新房间设置 + /// + /// + /// + private void UpdateRoomSettings(Dictionary requestData, Dictionary resultData) + { + bool result = false; + string msg = ""; + string room = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; + RoomType newType = DataRequest.GetDictionaryJsonObject(requestData, "type"); + string newPassword = DataRequest.GetDictionaryJsonObject(requestData, "password") ?? ""; + int newMaxUsers = DataRequest.GetDictionaryJsonObject(requestData, "maxUsers"); + string newModule = DataRequest.GetDictionaryJsonObject(requestData, "module") ?? ""; + string newMap = DataRequest.GetDictionaryJsonObject(requestData, "map") ?? ""; + User user = Server.User; + if (room != "-1" && FunGameSystem.RoomList.IsExist(room)) + { + Room r = FunGameSystem.RoomList[room]; + if (user.Id != r.RoomMaster.Id) + { + msg = "更新失败,只有房主才可以更新房间设置。"; + } + else + { + result = true; + ServerHelper.WriteLine("[UpdateRoomSettings] User: " + user.Username + " RoomID: " + r.Roomid); + if (r.RoomState == RoomState.Created) + { + r.RoomType = newType; + r.Password = newPassword; + r.MaxUsers = newMaxUsers; + r.GameModule = newModule; + r.GameMap = newMap; + } + else + { + msg = "更新失败,只能在房间状态稳定时更新其设置。"; + } + } + } + resultData.Add("result", result); + resultData.Add("msg", msg); + resultData.Add("room", room); + } + /// /// 获取房间内玩家数量 /// /// /// - private void GetRoomPlayerCount(Dictionary requestData, Dictionary resultData) + private static void GetRoomPlayerCount(Dictionary requestData, Dictionary resultData) { string roomid = "-1"; if (requestData.Count >= 1) { - ServerHelper.WriteLine(Server.GetClientName() + " -> " + DataRequestSet.GetTypeString(_lastRequest), InvokeMessageType.DataRequest); roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; } resultData.Add("count", FunGameSystem.RoomList.GetUserCount(roomid)); } + /// + /// 更新房主 + /// + /// + /// + /// + private async Task UpdateRoomMaster(Dictionary requestData, Dictionary resultData) + { + bool result = false; + + if (requestData.Count >= 2) + { + string roomid = DataRequest.GetDictionaryJsonObject(requestData, "roomid") ?? "-1"; + User newMaster = DataRequest.GetDictionaryJsonObject(requestData, "newMaster") ?? Factory.GetUser(); + + if (roomid != "-1" && FunGameSystem.RoomList.IsExist(roomid) && newMaster.Id != 0) + { + Room room = FunGameSystem.RoomList[roomid]; + User oldMaster = room.RoomMaster; + room.RoomMaster = newMaster; + result = true; + + if (SQLHelper != null) + { + SQLHelper.UpdateRoomMaster(roomid, newMaster.Id); + if (SQLHelper.Result == SQLResult.Success) + { + await Server.SendClients(Server.Listener.ClientList.Where(c => c != null && c.InRoom.Roomid == roomid), SocketMessageType.UpdateRoomMaster, room); + ServerHelper.WriteLine($"[UpdateRoomMaster] RoomID: {roomid} 房主变更: {oldMaster.Username} -> {newMaster.Username}"); + } + } + } + } + else + { + ServerHelper.WriteLine("客户端提供的参数不足。", InvokeMessageType.DataRequest, LogLevel.Warning); + } + + // 返回结果 + resultData.Add("result", result); + } + /// /// 开始匹配 /// @@ -779,6 +888,7 @@ namespace Milimoe.FunGame.Server.Controller private void StartMatching(RoomType type, User user) { _isMatching = true; + if (user.OnlineState == OnlineState.Online) user.OnlineState = OnlineState.Matching; ServerHelper.WriteLine(Server.GetClientName() + " 开始匹配。类型:" + RoomSet.GetTypeString(type)); TaskUtility.NewTask(async () => { @@ -806,6 +916,7 @@ namespace Milimoe.FunGame.Server.Controller if (_isMatching) { ServerHelper.WriteLine(Server.GetClientName() + " 取消了匹配。"); + if (Server.User.OnlineState == OnlineState.Matching) Server.User.OnlineState = OnlineState.Online; _isMatching = false; } } @@ -822,18 +933,32 @@ namespace Milimoe.FunGame.Server.Controller double time = 0; // 已经匹配的时间 double expandInterval = 10; // 扩大匹配范围的间隔时间 double maxTime = 50; // 最大匹配时间 + bool isRefreshRoom = false; // 是否刷新房间列表 + + // 匹配房间类型(如果是All,则匹配所有房间) + List targets; + if (roomtype == RoomType.All) + { + targets = [.. FunGameSystem.RoomList.ListRoom.Where(r => r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching)]; + } + else + { + targets = [.. FunGameSystem.RoomList.ListRoom.Where(r => (r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching) && r.RoomType == roomtype)]; + } while (_isMatching) { - // 匹配房间类型(如果是All,则匹配所有房间) - List targets; - if (roomtype == RoomType.All) + if (isRefreshRoom) { - targets = [.. FunGameSystem.RoomList.ListRoom.Where(r => r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching)]; - } - else - { - targets = [.. FunGameSystem.RoomList.ListRoom.Where(r => (r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching) && r.RoomType == roomtype)]; + isRefreshRoom = false; + if (roomtype == RoomType.All) + { + targets = [.. FunGameSystem.RoomList.ListRoom.Where(r => r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching)]; + } + else + { + targets = [.. FunGameSystem.RoomList.ListRoom.Where(r => (r.RoomState == RoomState.Created || r.RoomState == RoomState.Matching) && r.RoomType == roomtype)]; + } } // 如果匹配停止,则退出 @@ -865,6 +990,8 @@ namespace Milimoe.FunGame.Server.Controller if (time >= expandInterval * i) { i++; + // 刷新房间列表 + isRefreshRoom = true; } // 达到最大匹配时间后不再匹配Elo,直接返回第一个房间 if (time >= maxTime) @@ -880,5 +1007,937 @@ namespace Milimoe.FunGame.Server.Controller } #endregion + + #region UserCenter + + /// + /// 更新用户(全部数据) + /// + /// + /// + private void UpdateUser(Dictionary requestData, Dictionary resultData) + { + string msg = "无法更新用户数据,请稍后再试。"; + if (SQLHelper != null && requestData.Count > 0) + { + User user = DataRequest.GetDictionaryJsonObject(requestData, "user") ?? Factory.GetUser(); + SQLHelper.UpdateUser(user); + if (SQLHelper.Success) + { + msg = ""; + } + } + resultData.Add("msg", msg); + } + + /// + /// 更新用户的密码 + /// + /// + /// + private void UpdatePassword(Dictionary requestData, Dictionary resultData) + { + string msg = "无法更新您的密码,请稍后再试。"; + if (requestData.Count >= 2) + { + string username = DataRequest.GetDictionaryJsonObject(requestData, "username") ?? ""; + string password = DataRequest.GetDictionaryJsonObject(requestData, "password") ?? ""; + if (username.Trim() != "" && password.Trim() != "") + { + FunGameSystem.UpdateUserKey(username); + password = password.Encrypt(FunGameSystem.GetUserKey(username)); + SQLHelper?.UpdatePassword(username, password); + if (SQLHelper?.Success ?? false) + { + // 更新成功返回空值 + msg = ""; + } + } + } + resultData.Add("msg", msg); + } + + /// + /// 每日签到 + /// + /// + private void DailySignIn(Dictionary resultData) + { + if (SQLHelper != null) + { + long userId = Server.User.Id; + if (userId != 0) + { + DataRow? dr = SQLHelper.ExecuteDataRow(UserSignIns.Select_GetUserSignIn(SQLHelper, userId)); + if (dr != null) + { + int days = Convert.ToInt32(dr[UserSignIns.Column_Days]) + 1; + bool isSigned = Convert.ToInt32(dr[UserSignIns.Column_IsSigned]) != 0; + if (dr[UserSignIns.Column_LastTime] != DBNull.Value && DateTime.TryParseExact(dr[UserSignIns.Column_LastTime].ToString(), General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime dt)) + { + if (isSigned) + { + resultData.Add("msg", "今天已经签到过了,请明天再来。"); + return; + } + if ((DateTime.Now - dt).TotalDays > 1) + { + days = 1; + } + } + SQLHelper.Execute(UserSignIns.Update_UserSignIn(SQLHelper, userId, days)); + if (SQLHelper.Success) + { + resultData.Add("msg", $"签到成功!你已经连续签到 {days} 天!"); + return; + } + } + } + } + resultData.Add("msg", "签到失败!"); + } + + #endregion + + #region Inventory + + /// + /// 获取商店信息 + /// + /// + /// + private void GetStore(Dictionary requestData, Dictionary resultData) + { + List stores = []; + + if (requestData.Count > 0) + { + long[] ids = DataRequest.GetDictionaryJsonObject(requestData, "ids") ?? []; + stores = SQLHelper?.GetStoresWithGoods(ids) ?? []; + } + + resultData.Add("result", stores.Count > 0); + resultData.Add("stores", stores); + } + + /// + /// 获取市场信息 + /// + /// + /// + private void GetMarket(Dictionary requestData, Dictionary resultData) + { + List markets = []; + + if (requestData.Count > 0) + { + long[] users = DataRequest.GetDictionaryJsonObject(requestData, "users") ?? []; + MarketItemState state = DataRequest.GetDictionaryJsonObject(requestData, "state"); + + if (users.Length > 0) + { + foreach (long userid in users) + { + markets.AddRange(SQLHelper?.GetAllMarketsItem(userid, state) ?? []); + } + } + else + { + markets = SQLHelper?.GetAllMarketsItem(0, state) ?? []; + } + + long[] items = DataRequest.GetDictionaryJsonObject(requestData, "items") ?? []; + if (items.Length > 0) + { + markets = [.. markets.Where(i => items.Contains(i.Id))]; + } + } + + resultData.Add("result", markets.Count > 0); + resultData.Add("markets", markets); + } + + /// + /// 购买物品(商店) + /// + /// + /// + private void StoreBuy(Dictionary requestData, Dictionary resultData) + { + bool result = true; + double totalCost = 0; + List buyResult = []; + if (SQLHelper != null && requestData.Count > 0) + { + long storeid = DataRequest.GetDictionaryJsonObject(requestData, "storeid"); + long userid = DataRequest.GetDictionaryJsonObject(requestData, "userid"); + string currency = DataRequest.GetDictionaryJsonObject(requestData, "currency") ?? ""; + Dictionary counts = DataRequest.GetDictionaryJsonObject>(requestData, "counts") ?? []; + bool ignore = DataRequest.GetDictionaryJsonObject(requestData, "ignore"); + + Store? store = SQLHelper.GetStore(storeid); + User? user = SQLHelper.GetUserById(userid, true); + if (store != null && user != null) + { + try + { + SQLHelper.NewTransaction(); + + foreach (long goodsId in counts.Keys) + { + Goods goods = store.Goods[goodsId]; + int count = counts[goodsId]; + if (count > goods.Stock) + { + result = false; + buyResult.Add($"购买失败,原因:库存不足,当前库存为:{goods.Stock},购买数量:{count}。"); + continue; + } + if (goods.GetPrice(currency, out double price)) + { + bool subResult = true; + bool useCredits = true; + double totalPrice = price * count; + if (currency == General.GameplayEquilibriumConstant.InGameCurrency && user.Inventory.Credits >= totalPrice) + { + user.Inventory.Credits -= totalPrice; + } + else + { + subResult = false; + buyResult.Add($"购买失败,原因:需要花费 {totalPrice} {General.GameplayEquilibriumConstant.InGameCurrency},但是您只有 {user.Inventory.Credits} {General.GameplayEquilibriumConstant.InGameCurrency}。"); + } + if (currency == General.GameplayEquilibriumConstant.InGameMaterial && user.Inventory.Materials >= totalPrice) + { + user.Inventory.Materials -= totalPrice; + useCredits = false; + } + else + { + subResult = false; + buyResult.Add($"购买失败,原因:需要花费 {totalPrice} {General.GameplayEquilibriumConstant.InGameMaterial},但是您只有 {user.Inventory.Credits} {General.GameplayEquilibriumConstant.InGameMaterial}。"); + } + if (subResult) + { + goods.Stock -= count; + totalCost += totalPrice; + ProcessStoreBuy(goods, useCredits, price, count, user); + buyResult.Add($"成功消费:{totalPrice} {currency},购买了 {count} 个 {goods.Name}。"); + } + else + { + result = false; + } + } + } + + if (result || (!result && ignore)) + { + SQLHelper.UpdateInventory(user.Inventory); + } + + if (SQLHelper.Success) + { + SQLHelper.Commit(); + } + else + { + SQLHelper.Rollback(); + } + } + catch (Exception e) + { + SQLHelper.Rollback(); + ServerHelper.Error(e); + buyResult.Add("暂时无法处理此购买,请稍后再试。"); + } + } + } + resultData.Add("result", result); + resultData.Add("msg", string.Join("\r\n", buyResult)); + } + + /// + /// 处理商店购买 + /// + /// + /// + /// + /// + /// + private static void ProcessStoreBuy(Goods goods, bool useCredits, double price, int count, User user) + { + foreach (Item item in goods.Items) + { + for (int i = 0; i < count; i++) + { + Item newItem = item.Copy(); + newItem.IsTradable = false; + newItem.NextTradableTime = DateTimeUtility.GetTradableTime(); + newItem.Price = useCredits ? Calculation.Round2Digits(price * 0.35) : Calculation.Round2Digits(price * 7); + newItem.User = user; + newItem.EntityState = EntityState.Added; + user.Inventory.Items.Add(newItem); + } + } + } + + /// + /// 购买物品(市场) + /// + /// + /// + private void MarketBuy(Dictionary requestData, Dictionary resultData) + { + bool result = false; + string msg = "无法购买此物品,请稍后再试。"; + if (SQLHelper != null && requestData.Count > 0) + { + Guid itemGuid = DataRequest.GetDictionaryJsonObject(requestData, "itemGuid"); + + MarketItem? marketItem = SQLHelper.GetMarketItem(itemGuid); + if (marketItem != null) + { + long userid = DataRequest.GetDictionaryJsonObject(requestData, "userid"); + double price = DataRequest.GetDictionaryJsonObject(requestData, "price"); + + try + { + User? buyer = SQLHelper.GetUserById(userid, true); + User? itemUser = SQLHelper.GetUserById(marketItem.User.Id, true); + if (itemUser != null && buyer != null && itemUser.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + if (buyer.Inventory.Credits >= price) + { + buyer.Inventory.Credits -= price; + double fee = Calculation.Round2Digits(price * 0.15); + itemUser.Inventory.Credits += price - fee; + result = true; + } + else + { + msg = $"购买失败,原因:需要花费 {price} {General.GameplayEquilibriumConstant.InGameCurrency},但是您只有 {buyer.Inventory.Credits} {General.GameplayEquilibriumConstant.InGameCurrency}。"; + } + + if (result) + { + SQLHelper.NewTransaction(); + + try + { + item.EntityState = EntityState.Deleted; + SQLHelper.DeleteMarketItem(itemGuid); + + Item newItem = item.Copy(); + newItem.IsTradable = false; + newItem.NextTradableTime = DateTimeUtility.GetTradableTime(); + newItem.User = buyer; + newItem.EntityState = EntityState.Added; + buyer.Inventory.Items.Add(newItem); + + SQLHelper.UpdateInventory(itemUser.Inventory); + SQLHelper.UpdateInventory(buyer.Inventory); + } + catch + { + result = false; + } + + if (result) + { + msg = $"成功消费:{price} {General.GameplayEquilibriumConstant.InGameCurrency},购买了 {itemUser.Username} 出售的 {item.Name}。"; + SQLHelper.Commit(); + } + else + { + SQLHelper.Rollback(); + } + } + } + } + catch (Exception e) + { + SQLHelper.Rollback(); + ServerHelper.Error(e); + msg = "暂时无法处理此购买,请稍后再试。"; + } + } + else + { + msg = "目标物品不存在。"; + } + } + resultData.Add("result", result); + resultData.Add("msg", msg); + } + + /// + /// 更新库存 + /// + /// + /// + private void UpdateInventory(Dictionary requestData, Dictionary resultData) + { + string msg = "无法更新库存数据,请稍后再试。"; + if (SQLHelper != null && requestData.Count > 0) + { + Inventory inventory = DataRequest.GetDictionaryJsonObject(requestData, "inventory") ?? Factory.GetInventory(); + bool fullUpdate = DataRequest.GetDictionaryJsonObject(requestData, "fullUpdate"); + SQLHelper.UpdateInventory(inventory, fullUpdate); + if (SQLHelper.Success) + { + msg = ""; + } + } + resultData.Add("msg", msg); + } + + /// + /// 使用物品 + /// + /// + /// + private void Use(Dictionary requestData, Dictionary resultData) + { + bool result = false; + string msg = "无法使用此物品,请稍后再试。"; + if (SQLHelper != null && requestData.Count > 0) + { + Guid itemGuid = DataRequest.GetDictionaryJsonObject(requestData, "itemGuid"); + long userid = DataRequest.GetDictionaryJsonObject(requestData, "userid"); + Character[] targets = DataRequest.GetDictionaryJsonObject(requestData, "targets") ?? []; + long useCount = DataRequest.GetDictionaryJsonObject(requestData, "useCount"); + User? user = SQLHelper.GetUserById(userid, true); + if (user != null && user.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + // 暂定标准实现是传这两个参数,作用目标和使用数量 + Dictionary args = new() + { + { "targets", targets }, + { "useCount", useCount } + }; + bool used = item.UseItem(args); + if (used) + { + SQLHelper.UpdateInventory(user.Inventory); + } + } + if (result) + { + msg = ""; + } + } + resultData.Add("result", result); + resultData.Add("msg", msg); + } + + /// + /// 出售物品(商店) + /// + /// + /// + private void StoreSell(Dictionary requestData, Dictionary resultData) + { + bool result = false; + string msg = "无法出售此物品,请稍后再试。"; + if (SQLHelper != null && requestData.Count > 0) + { + Guid itemGuid = DataRequest.GetDictionaryJsonObject(requestData, "itemGuid"); + long userid = DataRequest.GetDictionaryJsonObject(requestData, "userid"); + User? user = SQLHelper.GetUserById(userid, true); + if (user != null && user.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + if (!item.IsSellable) + { + msg = $"此物品无法出售{(item.NextSellableTime != DateTime.MinValue ? $",此物品将在 {item.NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售" : "")}。"; + } + else + { + double reward = item.Price; + user.Inventory.Credits += reward; + item.EntityState = EntityState.Deleted; + SQLHelper.UpdateInventory(user.Inventory); + } + } + if (result) + { + msg = ""; + } + } + resultData.Add("msg", msg); + } + + /// + /// 出售物品(市场) + /// + /// + /// + private void MarketSell(Dictionary requestData, Dictionary resultData) + { + string msg = "无法上架此物品,请稍后再试。"; + if (requestData.Count > 0) + { + Guid itemGuid = DataRequest.GetDictionaryJsonObject(requestData, "itemGuid"); + long userid = DataRequest.GetDictionaryJsonObject(requestData, "userid"); + double price = DataRequest.GetDictionaryJsonObject(requestData, "price"); + User? user = SQLHelper?.GetUserById(userid, true); + if (user != null && user.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + if (!item.IsSellable) + { + msg = $"此物品无法出售{(item.NextSellableTime != DateTime.MinValue ? $",此物品将在 {item.NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售" : "")}。"; + } + else + { + SQLHelper?.AddMarketItem(itemGuid, userid, price); + if (SQLHelper?.Success ?? false) + { + msg = ""; + } + } + } + } + resultData.Add("msg", msg); + } + + /// + /// 下架市场物品 + /// + /// + /// + private void MarketDelist(Dictionary requestData, Dictionary resultData) + { + string msg = "无法下架市场物品,请稍后再试。"; + if (requestData.Count > 0) + { + long userid = DataRequest.GetDictionaryJsonObject(requestData, "userid"); + if (userid != 0) + { + SQLHelper?.DeleteMarketItemByUserId(userid); + } + else + { + Guid itemGuid = DataRequest.GetDictionaryJsonObject(requestData, "itemGuid"); + if (itemGuid != Guid.Empty) SQLHelper?.DeleteMarketItem(itemGuid); + } + if (SQLHelper?.Success ?? false) + { + msg = ""; + } + } + resultData.Add("msg", msg); + } + + /// + /// 更新市场价格 + /// + /// + /// + private void UpdateMarketPrice(Dictionary requestData, Dictionary resultData) + { + string msg = "无法更新市场价格,请稍后再试。"; + if (requestData.Count > 0) + { + Guid itemGuid = DataRequest.GetDictionaryJsonObject(requestData, "itemGuid"); + double price = DataRequest.GetDictionaryJsonObject(requestData, "price"); + SQLHelper?.UpdateMarketItemPrice(itemGuid, price); + if (SQLHelper?.Success ?? false) + { + msg = ""; + } + } + resultData.Add("msg", msg); + } + + /// + /// 获取交易报价 + /// + /// + /// + private void GetOffer(Dictionary requestData, Dictionary resultData) + { + string msg = "无法获取报价,请稍后再试。"; + if (SQLHelper != null && requestData.Count >= 1) + { + long offerId = DataRequest.GetDictionaryJsonObject(requestData, "id"); + bool apiQuery = DataRequest.GetDictionaryJsonObject(requestData, "apiQuery"); + Offer? offer = SQLHelper.GetOffer(offerId); + if (offer != null) + { + // 检查当前用户是否有权限查看(报价创建者或接收者)允许管理员使用 API 查询报价 + long userId = Server.User.Id; + if ((apiQuery && Server.User.IsAdmin) || offer.Offeror == userId || offer.Offeree == userId) + { + resultData.Add("offer", offer); + msg = ""; + } + else + { + msg = "您无权查看此报价。"; + } + } + else + { + msg = "报价不存在。"; + } + } + resultData.Add("msg", msg); + } + + /// + /// 创建交易报价 + /// + /// + /// + private void MakeOffer(Dictionary requestData, Dictionary resultData) + { + string msg = "无法创建报价,请稍后再试。"; + if (SQLHelper != null && requestData.Count >= 1) + { + long offeree = DataRequest.GetDictionaryJsonObject(requestData, "offeree"); + long offeror = Server.User.Id; + if (offeror != 0 && offeree != 0 && offeror != offeree) + { + SQLHelper.AddOffer(offeror, offeree); + if (SQLHelper.Success) + { + long offerId = SQLHelper.LastInsertId; + Offer? offer = SQLHelper.GetOffer(offerId); + if (offer != null) + { + resultData.Add("offer", offer); + msg = ""; + } + } + } + else + { + msg = "无效的用户ID。"; + } + } + resultData.Add("msg", msg); + } + + /// + /// 修改交易报价 + /// + /// + /// + private void ReviseOffer(Dictionary requestData, Dictionary resultData) + { + string msg = "无法修改报价,请稍后再试。"; + if (SQLHelper != null && requestData.Count >= 3) + { + long offerId = DataRequest.GetDictionaryJsonObject(requestData, "id"); + OfferActionType action = DataRequest.GetDictionaryJsonObject(requestData, "action"); + List offerorItems = DataRequest.GetDictionaryJsonObject>(requestData, "offerorItems") ?? []; + List offereeItems = DataRequest.GetDictionaryJsonObject>(requestData, "offereeItems") ?? []; + long userId = Server.User.Id; + + Offer? offer = SQLHelper.GetOffer(offerId); + if (offer != null && (offer.Offeror == userId || offer.Offeree == userId)) + { + try + { + SQLHelper.NewTransaction(); + + bool isOfferor = offer.Offeror == userId; + bool canProceed = false; + + // 根据 action 处理状态 + switch (action) + { + case OfferActionType.OfferorRevise: + if (isOfferor && (offer.Status == OfferState.Created || offer.Status == OfferState.Negotiating)) + { + SQLHelper.UpdateOfferStatus(offerId, OfferState.PendingOfferorConfirmation); + canProceed = true; + } + else msg = "当前状态不允许发起方修改。"; + break; + + case OfferActionType.OfferorConfirm: + if (isOfferor && offer.Status == OfferState.PendingOfferorConfirmation) + { + SQLHelper.UpdateOfferStatus(offerId, OfferState.OfferorConfirmed); + canProceed = true; + } + else msg = "当前状态不允许发起方确认。"; + break; + + case OfferActionType.OfferorSend: + if (isOfferor && offer.Status == OfferState.OfferorConfirmed) + { + SQLHelper.UpdateOfferStatus(offerId, OfferState.Sent); + canProceed = true; + } + else msg = "当前状态不允许发起方发送。"; + break; + + case OfferActionType.OfferorCancel: + if (isOfferor && offer.Status != OfferState.Completed && offer.Status != OfferState.Rejected && offer.Status != OfferState.Cancelled && offer.Status != OfferState.Expired) + { + SQLHelper.DeleteOfferItemsBackupByOfferId(offerId); + SQLHelper.UpdateOfferStatus(offerId, OfferState.Cancelled); + canProceed = true; + } + else msg = "当前状态不允许发起方取消。"; + break; + + case OfferActionType.OfferorAccept: + if (isOfferor && offer.Status == OfferState.Negotiating) + { + SQLHelper.UpdateOfferStatus(offerId, OfferState.NegotiationAccepted); + canProceed = true; + } + else msg = "当前状态不允许发起方同意。"; + break; + + case OfferActionType.OffereeRevise: + // 接收方修改报价 + if (!isOfferor && offer.NegotiatedTimes >= 3) + { + msg = "当前协商次数已达上限(3次),不允许接收方修改。"; + } + else if (!isOfferor && (offer.Status == OfferState.Sent || offer.Status == OfferState.NegotiationAccepted)) + { + // 备份 + SQLHelper.BackupOfferItem(offer); + SQLHelper.UpdateOfferStatus(offerId, OfferState.PendingOffereeConfirmation); + canProceed = true; + } + else msg = "当前状态不允许接收方修改。"; + break; + + case OfferActionType.OffereeConfirm: + if (!isOfferor && offer.Status == OfferState.PendingOffereeConfirmation) + { + SQLHelper.UpdateOfferStatus(offerId, OfferState.OffereeConfirmed); + canProceed = true; + } + else msg = "当前状态不允许接收方确认。"; + break; + + case OfferActionType.OffereeSend: + if (!isOfferor && (offer.Status == OfferState.OffereeConfirmed)) + { + if (offer.NegotiatedTimes < 3) + { + SQLHelper.UpdateOfferStatus(offerId, OfferState.Negotiating); + SQLHelper.UpdateOfferNegotiatedTimes(offerId, offer.NegotiatedTimes + 1); + canProceed = true; + } + else msg = "当前协商次数已达上限(3次),不允许接收方发送。"; + } + else msg = "当前状态不允许接收方修改。"; + break; + + default: + msg = "无效的操作类型。"; + break; + } + + if (canProceed) + { + // 更新物品,同时对物品进行检查 + User? offeree = SQLHelper.GetUserById(offer.Offeree, true); + User? offeror = SQLHelper.GetUserById(offer.Offeror, true); + if (offeree != null && offeror != null) + { + SQLHelper.DeleteOfferItemsByOfferId(offerId); + foreach (Guid itemGuid in offerorItems) + { + if (offeror.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + if (!item.IsTradable) + { + msg = $"此物品无法交易{(item.NextTradableTime != DateTime.MinValue ? $",此物品将在 {item.NextTradableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可交易" : "")}。"; + break; + } + else + { + SQLHelper.AddOfferItem(offerId, offer.Offeror, item.Guid); + } + } + } + foreach (Guid itemGuid in offereeItems) + { + if (offeree.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + if (!item.IsTradable) + { + msg = $"此物品无法交易{(item.NextTradableTime != DateTime.MinValue ? $",此物品将在 {item.NextTradableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可交易" : "")}。"; + break; + } + else + { + SQLHelper.AddOfferItem(offerId, offer.Offeree, item.Guid); + } + } + } + + if (msg != "") + { + offer = SQLHelper.GetOffer(offerId); + if (offer != null) + { + SQLHelper.Commit(); + resultData.Add("offer", offer); + msg = ""; + } + } + } + } + + if (msg != "") + { + SQLHelper.Rollback(); + } + } + catch (Exception e) + { + SQLHelper.Rollback(); + ServerHelper.Error(e); + msg = "修改报价时发生错误,请稍后再试。"; + } + } + else + { + msg = "报价不存在或您无权修改。"; + } + } + resultData.Add("msg", msg); + } + + /// + /// 回应交易报价 + /// + /// + /// + private void RespondOffer(Dictionary requestData, Dictionary resultData) + { + string msg = "无法回应报价,请稍后再试。"; + if (SQLHelper != null && requestData.Count >= 2) // 只需要 offerId 和 action + { + long offerId = DataRequest.GetDictionaryJsonObject(requestData, "id"); + OfferActionType action = DataRequest.GetDictionaryJsonObject(requestData, "action"); + long userId = Server.User.Id; + + Offer? offer = SQLHelper.GetOffer(offerId); + if (offer != null && offer.Offeree == userId) + { + bool canProceed = false; + bool isNegotiating = false; + + try + { + SQLHelper.NewTransaction(); + + // 根据 action 处理状态 + switch (action) + { + case OfferActionType.OffereeAccept: + if (offer.Status == OfferState.Sent || offer.Status == OfferState.Negotiating || offer.Status == OfferState.NegotiationAccepted) + { + if (offer.Status == OfferState.Negotiating) + { + isNegotiating = true; + } + SQLHelper.UpdateOfferStatus(offerId, OfferState.Completed); + SQLHelper.UpdateOfferFinishTime(offerId, DateTime.Now); + canProceed = true; + } + else msg = "当前状态不允许接受。"; + break; + + case OfferActionType.OffereeReject: + if (offer.Status == OfferState.Sent || offer.Status == OfferState.Negotiating || offer.Status == OfferState.NegotiationAccepted) + { + SQLHelper.UpdateOfferStatus(offerId, OfferState.Rejected); + SQLHelper.UpdateOfferFinishTime(offerId, DateTime.Now); + canProceed = true; + } + else msg = "当前状态不允许拒绝。"; + break; + + default: + msg = "无效的操作类型。"; + break; + } + + if (canProceed) + { + offer = SQLHelper.GetOffer(offerId, isNegotiating); + if (offer != null) + { + if (offer.Status == OfferState.Completed) + { + User? offeree = SQLHelper.GetUserById(offer.Offeree, true); + User? offeror = SQLHelper.GetUserById(offer.Offeror, true); + if (offeree != null && offeror != null) + { + foreach (Guid itemGuid in offer.OffereeItems) + { + if (offeree.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + item.EntityState = EntityState.Deleted; + + Item newItem = item.Copy(); + newItem.User = offeror; + newItem.IsSellable = false; + newItem.IsTradable = false; + newItem.NextSellableTime = DateTimeUtility.GetTradableTime(); + newItem.NextTradableTime = DateTimeUtility.GetTradableTime(); + newItem.EntityState = EntityState.Added; + offeror.Inventory.Items.Add(newItem); + } + } + foreach (Guid itemGuid in offer.OfferorItems) + { + if (offeror.Inventory.Items.FirstOrDefault(i => i.Guid == itemGuid) is Item item) + { + item.EntityState = EntityState.Deleted; + + Item newItem = item.Copy(); + newItem.User = offeree; + newItem.IsSellable = false; + newItem.IsTradable = false; + newItem.NextSellableTime = DateTimeUtility.GetTradableTime(); + newItem.NextTradableTime = DateTimeUtility.GetTradableTime(); + newItem.EntityState = EntityState.Added; + offeree.Inventory.Items.Add(newItem); + } + } + SQLHelper.UpdateInventory(offeror.Inventory); + SQLHelper.UpdateInventory(offeree.Inventory); + SQLHelper.Commit(); + resultData.Add("offer", offer); + msg = ""; + } + } + } + } + + if (msg != "") + { + SQLHelper.Rollback(); + } + } + catch (Exception e) + { + SQLHelper.Rollback(); + ServerHelper.Error(e); + msg = "回应报价时发生错误,请稍后再试。"; + } + } + else + { + msg = "报价不存在或您无权回应。"; + } + } + resultData.Add("msg", msg); + } + + #endregion } } diff --git a/FunGame.Server/FunGame.Server.csproj b/FunGame.Server/FunGame.Server.csproj index d2b577e..1b225d9 100644 --- a/FunGame.Server/FunGame.Server.csproj +++ b/FunGame.Server/FunGame.Server.csproj @@ -5,8 +5,6 @@ net9.0 enable enable - logo.ico - logo.ico ..\bin FunGameServer Milimoe @@ -17,6 +15,7 @@ FunGameServer Milimoe.$(MSBuildProjectName.Replace(" ", "_")) app.manifest + logo.ico @@ -47,14 +46,8 @@ + - - - True - \ - - - diff --git a/FunGame.Server/Images/logo.ico b/FunGame.Server/Images/logo.ico deleted file mode 100644 index 4082874..0000000 Binary files a/FunGame.Server/Images/logo.ico and /dev/null differ diff --git a/FunGame.Server/Main.cs b/FunGame.Server/Main.cs index 665eb49..662dfbb 100644 --- a/FunGame.Server/Main.cs +++ b/FunGame.Server/Main.cs @@ -1,5 +1,4 @@ -using Milimoe.FunGame; -using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Library.Common.Network; using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Server.Controller; @@ -23,6 +22,28 @@ Console.CancelKeyPress += (sender, e) => Environment.Exit(0); // 退出程序 }; +FunGameSystem.CloseListener += async () => +{ + if (SocketListener != null) + { + foreach (ServerModel model in SocketListener.ClientList.Cast>()) + { + await model.Kick("服务器正在关闭。"); + } + SocketListener.Close(); + SocketListener = null; + } + if (WebSocketListener != null) + { + foreach (ServerModel model in WebSocketListener.ClientList.Cast>()) + { + await model.Kick("服务器正在关闭。"); + } + WebSocketListener.Close(); + WebSocketListener = null; + } +}; + while (Running) { string order = Console.ReadLine() ?? ""; @@ -30,21 +51,23 @@ while (Running) if (order != "" && Running) { order = order.ToLower(); + if (FunGameSystem.OrderList.TryGetValue(order, out Action? action) && action != null) + { + action(order); + } switch (order) { case OrderDictionary.Quit: case OrderDictionary.Exit: case OrderDictionary.Close: - Running = false; CloseServer(); break; case OrderDictionary.Restart: if (SocketListener is null || WebSocketListener is null) { - ServerHelper.WriteLine("重启服务器"); StartServer(); } - else ServerHelper.WriteLine("服务器正在运行,拒绝重启!"); + else ServerHelper.WriteLine("服务器正在运行,请手动结束服务器进程再启动!"); break; default: if (SocketListener != null) @@ -60,9 +83,6 @@ while (Running) } } -ServerHelper.WriteLine("服务器已关闭,按任意键退出程序。"); -Console.ReadKey(); - void StartServer() { TaskUtility.NewTask(async () => @@ -105,8 +125,8 @@ void StartServer() ServerHelper.WriteLine("请输入 help 来获取帮助,按下 Ctrl+C 关闭服务器。"); - // 初始化用户密钥列表 - FunGameSystem.InitUserKeys(); + // 初始化服务器其他配置文件 + FunGameSystem.InitOtherConfig(); ServerHelper.PrintFunGameTitle(); @@ -246,38 +266,18 @@ void StartServer() } catch (Exception e) { - if (e.Message.Equals(new ServerErrorException().Message)) - { - if (SocketListener != null) - { - SocketListener.Close(); - SocketListener = null; - } - if (WebSocketListener != null) - { - WebSocketListener.Close(); - WebSocketListener = null; - } - } ServerHelper.Error(e); + CloseServer(); } finally { - if (SocketListener != null) - { - SocketListener.Close(); - SocketListener = null; - } - if (WebSocketListener != null) - { - WebSocketListener.Close(); - WebSocketListener = null; - } + CloseServer(); } }); } void CloseServer() { + Running = false; FunGameSystem.CloseServer(); } diff --git a/FunGame.Server/Models/ConsoleModel.cs b/FunGame.Server/Models/ConsoleModel.cs index 411510f..ab19d95 100644 --- a/FunGame.Server/Models/ConsoleModel.cs +++ b/FunGame.Server/Models/ConsoleModel.cs @@ -1,7 +1,13 @@ -using Milimoe.FunGame.Core.Interface.Base; +using System.Text; +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.Common.Addon; +using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Server.Others; using Milimoe.FunGame.Server.Services; +using ProjectRedbud.FunGame.SQLQueryExtension; namespace Milimoe.FunGame.Server.Model { @@ -45,9 +51,6 @@ namespace Milimoe.FunGame.Server.Model case OrderDictionary.ShowUsers2: ShowUsers(server); break; - case OrderDictionary.Help: - ShowHelp(); - break; default: break; } @@ -109,9 +112,90 @@ namespace Milimoe.FunGame.Server.Model } } - public static void ShowHelp() + public static void FirstRunRegAdmin() { - ServerHelper.WriteLine("Milimoe -> 帮助"); + using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper() ?? throw new SQLServiceException(); + ServerHelper.WriteLine("首次启动需要注册管理员账号,请按提示输入信息!", InvokeMessageType.Core); + string username, password, email; + ServerHelper.Write("请输入管理员用户名:", InvokeMessageType.Core); + while (true) + { + username = Console.ReadLine() ?? ""; + int usernameLength = NetworkUtility.GetUserNameLength(username); + if (usernameLength < 3 || usernameLength > 12) + { + ServerHelper.WriteLine("账号名长度不符合要求:3~12个字符数(一个中文2个字符)", InvokeMessageType.Error); + } + else + { + break; + } + } + ServerHelper.Write("请输入管理员邮箱:", InvokeMessageType.Core); + while (true) + { + email = Console.ReadLine() ?? ""; + if (!NetworkUtility.IsEmail(email)) + { + ServerHelper.WriteLine("这不是一个邮箱地址!", InvokeMessageType.Error); + } + else + { + break; + } + } + ServerHelper.Write("请输入管理员密码:", InvokeMessageType.Core); + while (true) + { + StringBuilder passwordBuilder = new(); + ConsoleKeyInfo key; + + do + { + key = Console.ReadKey(true); + + if (key.Key == ConsoleKey.Enter) + { + Console.WriteLine(); + break; + } + else if (key.Key == ConsoleKey.Backspace) + { + if (passwordBuilder.Length > 0) + { + passwordBuilder.Remove(passwordBuilder.Length - 1, 1); + Console.Write("\b \b"); + } + } + else if (!char.IsControl(key.KeyChar)) + { + passwordBuilder.Append(key.KeyChar); + Console.Write("*"); + } + } while (true); + + password = passwordBuilder.ToString(); + + if (password.Length < 6 || password.Length > 15) + { + ServerHelper.WriteLine("密码长度不符合要求:6~15个字符数", InvokeMessageType.Error); + } + else + { + break; + } + } + (string msg, RegInvokeType type, bool success) = DataRequestService.RegisterUser(sql, username, password, email, "localhost"); + ServerHelper.WriteLine(msg, InvokeMessageType.Core); + if (success) + { + User? user = sql.GetUserByUsernameAndEmail(username, email); + if (user != null) + { + user.IsAdmin = true; + sql.UpdateUser(user); + } + } } } } diff --git a/FunGame.Server/Models/ServerModel.cs b/FunGame.Server/Models/ServerModel.cs index 83ffd66..8237150 100644 --- a/FunGame.Server/Models/ServerModel.cs +++ b/FunGame.Server/Models/ServerModel.cs @@ -11,6 +11,7 @@ using Milimoe.FunGame.Core.Library.SQLScript.Entity; using Milimoe.FunGame.Server.Controller; using Milimoe.FunGame.Server.Others; using Milimoe.FunGame.Server.Services; +using ProjectRedbud.FunGame.SQLQueryExtension; namespace Milimoe.FunGame.Server.Model { @@ -82,6 +83,8 @@ namespace Milimoe.FunGame.Server.Model NowGamingServer.CloseAnonymousServer(this); } NowGamingServer = null; + User.OnlineState = OnlineState.InRoom; + if (User.Id == InRoom.RoomMaster.Id) InRoom.RoomState = RoomState.Created; return true; } @@ -382,44 +385,47 @@ namespace Milimoe.FunGame.Server.Model public async Task QuitRoom(string roomid, bool isMaster) { bool result; + SQLHelper?.NewTransaction(); FunGameSystem.RoomList.CancelReady(roomid, User); FunGameSystem.RoomList.QuitRoom(roomid, User); Room Room = FunGameSystem.RoomList[roomid] ?? General.HallInstance; + User.OnlineState = OnlineState.Online; // 是否是房主 - if (isMaster) + if (isMaster && Room.Roomid != "-1") { List users = [.. FunGameSystem.RoomList[roomid].UserAndIsReady.Keys]; - if (users.Count > 0) // 如果此时房间还有人,更新房主 + User? newRoomMaster = null; + if (users.Count > 0) newRoomMaster = users[0]; + SQLHelper?.QuitRoomByRoomMaster(roomid, User.Id, newRoomMaster?.Id); + if (newRoomMaster != null) { - User NewMaster = users[0]; - Room.RoomMaster = NewMaster; - SQLHelper?.Execute(RoomQuery.Update_QuitRoom(SQLHelper, roomid, User.Id, NewMaster.Id)); - this.InRoom = General.HallInstance; - await UpdateRoomMaster(Room, true); - result = true; + Room.RoomMaster = users[0]; + ServerHelper.WriteLine("[ " + GetClientName() + " ] 退出了房间 " + roomid + ",并更新房主为:" + newRoomMaster); + await NotifyQuitRoom(Room, true); } - else // 没人了就解散房间 + else { FunGameSystem.RoomList.RemoveRoom(roomid); - SQLHelper?.Execute(RoomQuery.Delete_QuitRoom(SQLHelper, roomid, User.Id)); - this.InRoom = General.HallInstance; ServerHelper.WriteLine("[ " + GetClientName() + " ] 解散了房间 " + roomid); - result = true; } + InRoom = General.HallInstance; + result = true; } // 不是房主直接退出房间 else { this.InRoom = General.HallInstance; - await UpdateRoomMaster(Room); + await NotifyQuitRoom(Room); result = true; } + SQLHelper?.Commit(); + return result; } - public async Task UpdateRoomMaster(Room room, bool isUpdateRoomMaster = false) + public async Task NotifyQuitRoom(Room room, bool isUpdateRoomMaster = false) { foreach (IServerModel Client in Listener.ClientList.Where(c => c != null && c.User.Id != 0 && room.Roomid == c.InRoom?.Roomid)) { @@ -473,6 +479,11 @@ namespace Milimoe.FunGame.Server.Model { // 创建User对象 User = Factory.GetUser(_dsUser); + if (SQLHelper?.GetUserById(User.Id, true, true) is User real) + { + User = real; + } + User.OnlineState = OnlineState.Online; // 检查有没有重复登录的情况 await ForceLogOutDuplicateLogonUser(); // 添加至玩家列表 @@ -499,6 +510,7 @@ namespace Milimoe.FunGame.Server.Model { if (User.Id != 0 && this != null) { + User.OnlineState = OnlineState.Offline; _checkLoginKey = Guid.Empty; _logoutTime = DateTime.Now.Ticks; int TotalMinutes = Convert.ToInt32((new DateTime(_logoutTime) - new DateTime(_loginTime)).TotalMinutes); diff --git a/FunGame.Server/Services/DataRequestService.cs b/FunGame.Server/Services/DataRequestService.cs index b0a5890..2c9d2ad 100644 --- a/FunGame.Server/Services/DataRequestService.cs +++ b/FunGame.Server/Services/DataRequestService.cs @@ -6,14 +6,25 @@ using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.SQLScript.Common; using Milimoe.FunGame.Core.Library.SQLScript.Entity; using Milimoe.FunGame.Server.Others; +using ProjectRedbud.FunGame.SQLQueryExtension; namespace Milimoe.FunGame.Server.Services { public class DataRequestService { + /// + /// 获取插件取消请求的原因 + /// + /// + /// + /// + private static string GetPluginCancelString(DataRequestType type, GeneralEventArgs e) => $"{DataRequestSet.GetTypeString(type)} 请求已取消。{(e.EventMsg != "" ? $"原因:{e.EventMsg}" : "")}"; + + #region Register + public static (string Msg, RegInvokeType RegInvokeType, bool Success) Reg(object sender, string username, string password, string email, string verifyCode, string clientIP = "") { - string msg = ""; + string msg; RegInvokeType type = RegInvokeType.None; bool success = false; string clientName = ServerHelper.MakeClientName(clientIP); @@ -23,131 +34,21 @@ namespace Milimoe.FunGame.Server.Services FunGameSystem.WebAPIPluginLoader?.OnBeforeRegEvent(sender, eventArgs); if (eventArgs.Cancel) { - msg = $"{DataRequestSet.GetTypeString(DataRequestType.Reg_Reg)} 请求已取消。{(eventArgs.EventMsg != "" ? $"原因:{eventArgs.EventMsg}" : "")}"; + msg = GetPluginCancelString(DataRequestType.Reg_Reg, eventArgs); ServerHelper.WriteLine(msg, InvokeMessageType.DataRequest, LogLevel.Warning); return (eventArgs.EventMsg, RegInvokeType.None, false); } using SQLHelper? sqlHelper = Factory.OpenFactory.GetSQLHelper(); using MailSender? mailSender = Factory.OpenFactory.GetMailSender(); + if (sqlHelper != null) { - // 如果没发验证码,就生成验证码 - if (verifyCode.Trim() == "") - { - // 先检查账号是否重复 - sqlHelper.ExecuteDataSet(UserQuery.Select_IsExistUsername(sqlHelper, username)); - if (sqlHelper.Result == SQLResult.Success) - { - ServerHelper.WriteLine(clientName + " 账号已被注册"); - msg = "此账号名已被使用!"; - type = RegInvokeType.DuplicateUserName; - } - else - { - // 检查邮箱是否重复 - sqlHelper.ExecuteDataSet(UserQuery.Select_IsExistEmail(sqlHelper, email)); - if (sqlHelper.Result == SQLResult.Success) - { - ServerHelper.WriteLine(clientName + " 邮箱已被注册"); - msg = "此邮箱已被注册!"; - type = RegInvokeType.DuplicateEmail; - } - else - { - // 检查验证码是否发送过 - sqlHelper.ExecuteDataSet(RegVerifyCodes.Select_HasSentRegVerifyCode(sqlHelper, username, email)); - if (sqlHelper.Result == SQLResult.Success && DateTime.TryParse(sqlHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime].ToString(), out DateTime RegTime) && (DateTime.Now - RegTime).TotalMinutes < 10) - { - string RegVerifyCode = sqlHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegVerifyCode].ToString() ?? ""; - ServerHelper.WriteLine(clientName + $" 十分钟内已向{email}发送过验证码:{RegVerifyCode}"); - type = RegInvokeType.InputVerifyCode; - } - else - { - // 发送验证码,需要先删除之前过期的验证码 - sqlHelper.NewTransaction(); - sqlHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(sqlHelper, username, email)); - string regVerify = Verification.CreateVerifyCode(VerifyCodeType.NumberVerifyCode, 6); - sqlHelper.Execute(RegVerifyCodes.Insert_RegVerifyCode(sqlHelper, 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(clientName + $" 已向{email}发送验证码:{regVerify}"); - } - else - { - ServerHelper.WriteLine(clientName + " 无法发送验证码", InvokeMessageType.Error); - ServerHelper.WriteLine(mailSender.ErrorMsg, InvokeMessageType.Error); - } - } - else // 不使用MailSender的情况 - { - ServerHelper.WriteLine(clientName + $" 验证码为:{regVerify},请服务器管理员告知此用户"); - } - type = RegInvokeType.InputVerifyCode; - } - else sqlHelper.Rollback(); - } - } - } - } - else - { - // 先检查验证码 - sqlHelper.ExecuteDataSet(RegVerifyCodes.Select_RegVerifyCode(sqlHelper, username, email, verifyCode)); - if (sqlHelper.Result == SQLResult.Success) - { - if (!DateTime.TryParse(sqlHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime].ToString(), out DateTime RegTime)) - { - RegTime = General.DefaultTime; - } - // 检查验证码是否过期 - if ((DateTime.Now - RegTime).TotalMinutes >= 10) - { - ServerHelper.WriteLine(clientName + " 验证码已过期"); - msg = "此验证码已过期,请重新注册。"; - sqlHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(sqlHelper, username, email)); - type = RegInvokeType.None; - } - else - { - // 注册 - if (verifyCode.Equals(sqlHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegVerifyCode])) - { - sqlHelper.NewTransaction(); - ServerHelper.WriteLine("[Reg] Username: " + username + " Email: " + email); - FunGameSystem.UpdateUserKey(username); - password = password.Encrypt(FunGameSystem.GetUserKey(username)); - sqlHelper.Execute(UserQuery.Insert_Register(sqlHelper, username, password, email, clientIP)); - if (sqlHelper.Result == SQLResult.Success) - { - success = true; - msg = "注册成功!请牢记您的账号与密码!"; - sqlHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(sqlHelper, username, email)); - sqlHelper.Commit(); - } - else - { - sqlHelper.Rollback(); - msg = "服务器无法处理您的注册,注册失败!"; - } - } - else msg = "验证码不正确,请重新输入!"; - } - } - else if (sqlHelper.Result == SQLResult.NotFound) msg = "验证码不正确,请重新输入!"; - else msg = "服务器无法处理您的注册,注册失败!"; - } + (msg, type, success) = ProcessRegistration(sqlHelper, mailSender, username, password, email, verifyCode, clientIP, clientName); + } + else + { + msg = "服务器无法处理您的注册,注册失败!"; } eventArgs.Success = success; @@ -157,11 +58,152 @@ namespace Milimoe.FunGame.Server.Services return (msg, type, success); } + internal static (string Msg, RegInvokeType Type, bool Success) ProcessRegistration(SQLHelper sqlHelper, MailSender? mailSender, string username, string password, string email, string verifyCode, string clientIP, string clientName) + { + if (string.IsNullOrWhiteSpace(verifyCode)) + { + return HandleNoVerifyCode(sqlHelper, mailSender, username, email, clientName); + } + else + { + return HandleWithVerifyCode(sqlHelper, username, password, email, verifyCode, clientIP, clientName); + } + } + + internal static (string Msg, RegInvokeType Type, bool Success) HandleNoVerifyCode(SQLHelper sqlHelper, MailSender? mailSender, string username, string email, string clientName) + { + // 先检查账号是否重复 + sqlHelper.ExecuteDataSet(UserQuery.Select_IsExistUsername(sqlHelper, username)); + if (sqlHelper.Result == SQLResult.Success) + { + ServerHelper.WriteLine(clientName + " 账号已被注册"); + return ("此账号名已被使用!", RegInvokeType.DuplicateUserName, false); + } + + // 检查邮箱是否重复 + sqlHelper.ExecuteDataSet(UserQuery.Select_IsExistEmail(sqlHelper, email)); + if (sqlHelper.Result == SQLResult.Success) + { + ServerHelper.WriteLine(clientName + " 邮箱已被注册"); + return ("此邮箱已被注册!", RegInvokeType.DuplicateEmail, false); + } + + // 检查验证码是否发送过 + sqlHelper.ExecuteDataSet(RegVerifyCodes.Select_HasSentRegVerifyCode(sqlHelper, username, email)); + if (sqlHelper.Result == SQLResult.Success && DateTime.TryParse(sqlHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime].ToString(), out DateTime RegTime) && (DateTime.Now - RegTime).TotalMinutes < 10) + { + string RegVerifyCode = sqlHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegVerifyCode].ToString() ?? ""; + ServerHelper.WriteLine(clientName + $" 十分钟内已向{email}发送过验证码:{RegVerifyCode}"); + return ("", RegInvokeType.InputVerifyCode, false); + } + + // 发送验证码 + return SendVerificationCode(sqlHelper, mailSender, username, email, clientName); + } + + internal static (string Msg, RegInvokeType Type, bool Success) SendVerificationCode(SQLHelper sqlHelper, MailSender? mailSender, string username, string email, string clientName) + { + sqlHelper.NewTransaction(); + sqlHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(sqlHelper, username, email)); + string regVerify = Verification.CreateVerifyCode(VerifyCodeType.NumberVerifyCode, 6); + sqlHelper.Execute(RegVerifyCodes.Insert_RegVerifyCode(sqlHelper, username, email, regVerify)); + + if (sqlHelper.Result == SQLResult.Success) + { + sqlHelper.Commit(); + + if (mailSender != null) + { + // 发送验证码 + string ServerName = Config.ServerName; + string Subject = $"[{ServerName}] 注册验证码"; + 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(clientName + $" 已向{email}发送验证码:{regVerify}"); + } + else + { + ServerHelper.WriteLine(clientName + " 无法发送验证码", InvokeMessageType.Error); + ServerHelper.WriteLine(mailSender.ErrorMsg, InvokeMessageType.Error); + } + } + else // 不使用MailSender的情况 + { + ServerHelper.WriteLine(clientName + $" 验证码为:{regVerify},请服务器管理员告知此用户"); + } + return ("", RegInvokeType.InputVerifyCode, false); + } + else + { + sqlHelper.Rollback(); + return ("发送验证码失败!", RegInvokeType.None, false); + } + } + + internal static (string Msg, RegInvokeType Type, bool Success) HandleWithVerifyCode(SQLHelper sqlHelper, string username, string password, string email, string verifyCode, string clientIP, string clientName) + { + // 先检查验证码 + sqlHelper.ExecuteDataSet(RegVerifyCodes.Select_RegVerifyCode(sqlHelper, username, email, verifyCode)); + if (sqlHelper.Result == SQLResult.Success) + { + if (!DateTime.TryParse(sqlHelper.DataSet.Tables[0].Rows[0][RegVerifyCodes.Column_RegTime].ToString(), out DateTime RegTime)) + { + RegTime = General.DefaultTime; + } + + // 检查验证码是否过期 + if ((DateTime.Now - RegTime).TotalMinutes >= 10) + { + ServerHelper.WriteLine(clientName + " 验证码已过期"); + sqlHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(sqlHelper, username, email)); + return ("此验证码已过期,请重新注册。", RegInvokeType.None, false); + } + + // 注册 + return RegisterUser(sqlHelper, username, password, email, clientIP); + } + else if (sqlHelper.Result == SQLResult.NotFound) + { + return ("验证码不正确,请重新输入!", RegInvokeType.None, false); + } + else + { + return ("服务器无法处理您的注册,注册失败!", RegInvokeType.None, false); + } + } + + internal static (string Msg, RegInvokeType Type, bool Success) RegisterUser(SQLHelper sqlHelper, string username, string password, string email, string clientIP) + { + sqlHelper.NewTransaction(); + ServerHelper.WriteLine("[Reg] Username: " + username + " Email: " + email); + FunGameSystem.UpdateUserKey(username); + password = password.Encrypt(FunGameSystem.GetUserKey(username)); + sqlHelper.RegisterUser(username, password, email, clientIP); + + if (sqlHelper.Result == SQLResult.Success) + { + sqlHelper.Execute(RegVerifyCodes.Delete_RegVerifyCode(sqlHelper, username, email)); + sqlHelper.Commit(); + return ("注册成功!请牢记您的账号与密码!", RegInvokeType.None, true); + } + else + { + sqlHelper.Rollback(); + return ("服务器无法处理您的注册,注册失败!", RegInvokeType.None, false); + } + } + + #endregion + + #region Login + public static (bool Success, DataSet DataSet, string Msg, Guid Key) PreLogin(object sender, string username, string password, string autokey = "") { bool success = false; DataSet dsUser = new(); - string msg = "用户名或密码不正确。"; + string msg; Guid key = Guid.Empty; LoginEventArgs eventArgs = new(username, password, autokey); @@ -169,43 +211,12 @@ namespace Milimoe.FunGame.Server.Services FunGameSystem.WebAPIPluginLoader?.OnBeforeLoginEvent(sender, eventArgs); if (eventArgs.Cancel) { - msg = $"{DataRequestSet.GetTypeString(DataRequestType.Login_Login)} 请求已取消。{(eventArgs.EventMsg != "" ? $"原因:{eventArgs.EventMsg}" : "")}"; + msg = GetPluginCancelString(DataRequestType.Login_Login, eventArgs); ServerHelper.WriteLine(msg, InvokeMessageType.DataRequest, LogLevel.Warning); return (success, dsUser, eventArgs.EventMsg, key); } - // 验证登录 - if (username != "" && password != "") - { - password = password.Encrypt(FunGameSystem.GetUserKey(username)); - ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] Username: " + username); - using SQLHelper? sqlHelper = Factory.OpenFactory.GetSQLHelper(); - if (sqlHelper != null) - { - sqlHelper.ExecuteDataSet(UserQuery.Select_Users_LoginQuery(sqlHelper, username, password)); - if (sqlHelper.Result == SQLResult.Success) - { - dsUser = sqlHelper.DataSet; - key = Guid.NewGuid(); - success = true; - msg = ""; - if (autokey.Trim() != "") - { - sqlHelper.ExecuteDataSet(UserQuery.Select_CheckAutoKey(sqlHelper, username, autokey)); - if (sqlHelper.Result == SQLResult.Success) - { - ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] AutoKey: 已确认"); - } - else - { - success = false; - msg = "AutoKey 不正确,拒绝自动登录!"; - ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] " + msg); - } - } - } - } - } + (success, dsUser, msg, key) = ProcessLogin(username, password, autokey); eventArgs.Success = success; FunGameSystem.ServerPluginLoader?.OnAfterLoginEvent(sender, eventArgs); @@ -214,5 +225,72 @@ namespace Milimoe.FunGame.Server.Services ServerHelper.WriteLine(msg, InvokeMessageType.Core); return (success, dsUser, msg, key); } + + internal static (bool Success, DataSet DataSet, string Msg, Guid Key) ProcessLogin(string username, string password, string autokey) + { + bool success = false; + DataSet dsUser = new(); + string msg = "用户名或密码不正确。"; + Guid key = Guid.Empty; + + if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) + { + password = password.Encrypt(FunGameSystem.GetUserKey(username)); + ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] Username: " + username); + + using SQLHelper? sqlHelper = Factory.OpenFactory.GetSQLHelper(); + if (sqlHelper != null) + { + (success, dsUser, msg, key) = ValidateLogin(sqlHelper, username, password, autokey); + } + else + { + msg = "无法连接到数据库,登录失败!"; + } + } + + return (success, dsUser, msg, key); + } + + internal static (bool Success, DataSet DataSet, string Msg, Guid Key) ValidateLogin(SQLHelper sqlHelper, string username, string password, string autokey) + { + bool success = false; + DataSet dsUser = new(); + string msg = "用户名或密码不正确。"; + Guid key = Guid.NewGuid(); + + sqlHelper.ExecuteDataSet(UserQuery.Select_Users_LoginQuery(sqlHelper, username, password)); + if (sqlHelper.Result == SQLResult.Success) + { + dsUser = sqlHelper.DataSet; + success = true; + msg = ""; + + if (!string.IsNullOrWhiteSpace(autokey)) + { + (success, msg) = CheckAutoKey(sqlHelper, username, autokey); + } + } + + return (success, dsUser, msg, key); + } + + internal static (bool Success, string Msg) CheckAutoKey(SQLHelper sqlHelper, string username, string autokey) + { + sqlHelper.ExecuteDataSet(UserQuery.Select_CheckAutoKey(sqlHelper, username, autokey)); + if (sqlHelper.Result == SQLResult.Success) + { + ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] AutoKey: 已确认"); + return (true, ""); + } + else + { + string msg = "AutoKey 不正确,拒绝自动登录!"; + ServerHelper.WriteLine("[" + DataRequestSet.GetTypeString(DataRequestType.Login_Login) + "] " + msg); + return (false, msg); + } + } + + #endregion } } diff --git a/FunGame.Server/Services/DataUtility/MySQLHelper.cs b/FunGame.Server/Services/DataUtility/MySQLHelper.cs index 54efb97..773bd62 100644 --- a/FunGame.Server/Services/DataUtility/MySQLHelper.cs +++ b/FunGame.Server/Services/DataUtility/MySQLHelper.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Data.Common; using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Model; @@ -9,14 +10,59 @@ namespace Milimoe.FunGame.Server.Services.DataUtility { public class MySQLHelper : SQLHelper { + /// + /// FunGame 类型 + /// public override FunGameInfo.FunGame FunGameType { get; } = FunGameInfo.FunGame.FunGame_Server; + + /// + /// 使用的数据库类型 + /// public override SQLMode Mode { get; } = SQLMode.MySQL; + + /// + /// SQL 脚本 + /// public override string Script { get; set; } = ""; + + /// + /// 命令类型 + /// public override CommandType CommandType { get; set; } = CommandType.Text; + + /// + /// 数据库事务 + /// + public override DbTransaction? Transaction => _transaction; + + /// + /// 执行结果 + /// public override SQLResult Result => _result; + + /// + /// SQL 服务器信息 + /// public override SQLServerInfo ServerInfo => _serverInfo ?? SQLServerInfo.Create(); - public override int UpdateRows => _updateRows; + + /// + /// 上一次执行命令影响的行数 + /// + public override int AffectedRows => _affectedRows; + + /// + /// 上一次执行的命令是 Insert 时,返回的自增 ID,大于 0 有效 + /// + public override long LastInsertId => _lastInsertId; + + /// + /// 上一次执行命令的查询结果集 + /// public override DataSet DataSet => _dataSet; + + /// + /// SQL 语句参数 + /// public override Dictionary Parameters { get; } = []; private readonly string _connectionString = ""; @@ -25,7 +71,8 @@ namespace Milimoe.FunGame.Server.Services.DataUtility private DataSet _dataSet = new(); private SQLResult _result = SQLResult.NotFound; private readonly SQLServerInfo? _serverInfo; - private int _updateRows = 0; + private int _affectedRows = 0; + private long _lastInsertId = 0; public MySQLHelper(string script = "", CommandType type = CommandType.Text) { @@ -69,7 +116,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } /// - /// 执行一个命令 + /// 执行现有命令() /// /// public override int Execute() @@ -104,14 +151,24 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } if (_transaction != null) command.Transaction = _transaction; - _updateRows = command.ExecuteNonQuery(); - _result = SQLResult.Success; + ReSet(); + _affectedRows = command.ExecuteNonQuery(); + if (_affectedRows > 0) + { + _result = SQLResult.Success; + if (script.Contains(Core.Library.SQLScript.Constant.Command_Insert, StringComparison.OrdinalIgnoreCase)) + { + _lastInsertId = command.LastInsertedId; + if (_lastInsertId < 0) _lastInsertId = 0; + } + } + else _result = SQLResult.Fail; if (localTransaction) Commit(); } catch (Exception e) { if (localTransaction) Rollback(); - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -119,11 +176,75 @@ namespace Milimoe.FunGame.Server.Services.DataUtility if (localTransaction) Close(); if (ClearParametersAfterExecute) Parameters.Clear(); } - return UpdateRows; + return AffectedRows; } /// - /// 查询DataSet + /// 异步执行现有命令() + /// + /// + public override async Task ExecuteAsync() + { + return await ExecuteAsync(Script); + } + + /// + /// 异步执行一个指定的命令 + /// + /// + /// + public override async Task ExecuteAsync(string script) + { + bool localTransaction = _transaction == null; + + try + { + if (localTransaction) + { + NewTransaction(); + } + + OpenConnection(); + Script = script; + ServerHelper.WriteLine("SQLQuery -> " + script, InvokeMessageType.Api); + using MySqlCommand command = new(script, _connection); + command.CommandType = CommandType; + foreach (KeyValuePair param in Parameters) + { + command.Parameters.AddWithValue(param.Key, param.Value); + } + if (_transaction != null) command.Transaction = _transaction; + + ReSet(); + _affectedRows = await command.ExecuteNonQueryAsync(); + if (_affectedRows > 0) + { + _result = SQLResult.Success; + if (script.Contains(Core.Library.SQLScript.Constant.Command_Insert, StringComparison.OrdinalIgnoreCase)) + { + _lastInsertId = command.LastInsertedId; + if (_lastInsertId < 0) _lastInsertId = 0; + } + } + else _result = SQLResult.Fail; + if (localTransaction) Commit(); + } + catch (Exception e) + { + if (localTransaction) Rollback(); + _result = SQLResult.SQLError; + ServerHelper.Error(e); + } + finally + { + if (localTransaction) Close(); + if (ClearParametersAfterExecute) Parameters.Clear(); + } + return AffectedRows; + } + + /// + /// 执行现有命令()查询 DataSet /// /// public override DataSet ExecuteDataSet() @@ -132,7 +253,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } /// - /// 执行指定的命令查询DataSet + /// 执行指定的命令查询 DataSet /// /// /// @@ -161,12 +282,13 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } if (_transaction != null) command.Transaction = _transaction; + ReSet(); MySqlDataAdapter adapter = new() { SelectCommand = command }; _dataSet = new(); - adapter.Fill(_dataSet); + _affectedRows = adapter.Fill(_dataSet); if (localTransaction) Commit(); @@ -175,7 +297,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility catch (Exception e) { if (localTransaction) Rollback(); - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -187,26 +309,68 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } /// - /// 检查数据库是否存在 + /// 异步执行现有命令()查询 DataSet /// /// - public override bool DatabaseExists() + public override async Task ExecuteDataSetAsync() { + return await ExecuteDataSetAsync(Script); + } + + /// + /// 异步执行指定的命令查询 DataSet + /// + /// + /// + public override async Task ExecuteDataSetAsync(string script) + { + bool localTransaction = _transaction == null; + try { - ExecuteDataSet(Core.Library.SQLScript.Common.Configs.Select_GetConfig(this, "Initialization")); - return Success; + if (localTransaction) + { + NewTransaction(); + } + + OpenConnection(); + Script = script; + ServerHelper.WriteLine("SQLQuery -> " + script, InvokeMessageType.Api); + + using MySqlCommand command = new(script, _connection) + { + CommandType = CommandType + }; + foreach (KeyValuePair param in Parameters) + { + command.Parameters.AddWithValue(param.Key, param.Value); + } + if (_transaction != null) command.Transaction = _transaction; + + ReSet(); + MySqlDataAdapter adapter = new() + { + SelectCommand = command + }; + _dataSet = new(); + _affectedRows = await adapter.FillAsync(_dataSet); + + if (localTransaction) Commit(); + + _result = _dataSet.Tables.Cast().Any(table => table.Rows.Count > 0) ? SQLResult.Success : SQLResult.NotFound; } catch (Exception e) { + if (localTransaction) Rollback(); + _result = SQLResult.SQLError; ServerHelper.Error(e); - _result = SQLResult.Fail; - return false; } finally { - Close(); + if (localTransaction) Close(); + if (ClearParametersAfterExecute) Parameters.Clear(); } + return _dataSet; } /// @@ -233,7 +397,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } catch (Exception e) { - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -254,7 +418,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } catch (Exception e) { - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -263,12 +427,35 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } } + /// + /// 检查数据库是否存在 + /// + /// + public override bool DatabaseExists() + { + try + { + ExecuteDataSet(Core.Library.SQLScript.Common.Configs.Select_GetConfig(this, "Initialization")); + return Success; + } + catch (Exception e) + { + ServerHelper.Error(e); + _result = SQLResult.SQLError; + return false; + } + finally + { + Close(); + } + } + private bool _isDisposed = false; /// /// 资源清理 /// - public void Dispose(bool disposing) + private void Dispose(bool disposing) { if (!_isDisposed) { @@ -289,5 +476,13 @@ namespace Milimoe.FunGame.Server.Services.DataUtility Dispose(true); GC.SuppressFinalize(this); } + + private void ReSet() + { + _result = SQLResult.NotFound; + _affectedRows = 0; + _lastInsertId = 0; + DataSet.Clear(); + } } } diff --git a/FunGame.Server/Services/DataUtility/SQLiteHelper.cs b/FunGame.Server/Services/DataUtility/SQLiteHelper.cs index 57cce4f..510d396 100644 --- a/FunGame.Server/Services/DataUtility/SQLiteHelper.cs +++ b/FunGame.Server/Services/DataUtility/SQLiteHelper.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Data.Common; using Microsoft.Data.Sqlite; using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Library.Constant; @@ -9,14 +10,59 @@ namespace Milimoe.FunGame.Server.Services.DataUtility { public class SQLiteHelper : SQLHelper { + /// + /// FunGame 类型 + /// public override FunGameInfo.FunGame FunGameType { get; } = FunGameInfo.FunGame.FunGame_Server; + + /// + /// 使用的数据库类型 + /// public override SQLMode Mode { get; } = SQLMode.SQLite; + + /// + /// SQL 脚本 + /// public override string Script { get; set; } = ""; + + /// + /// 命令类型 + /// public override CommandType CommandType { get; set; } = CommandType.Text; + + /// + /// 数据库事务 + /// + public override DbTransaction? Transaction => _transaction; + + /// + /// 执行结果 + /// public override SQLResult Result => _result; + + /// + /// SQL 服务器信息 + /// public override SQLServerInfo ServerInfo => _serverInfo ?? SQLServerInfo.Create(); - public override int UpdateRows => _updateRows; + + /// + /// 上一次执行命令影响的行数 + /// + public override int AffectedRows => _affectedRows; + + /// + /// 上一次执行的命令是 Insert 时,返回的自增 ID,大于 0 有效 + /// + public override long LastInsertId => _lastInsertId; + + /// + /// 上一次执行命令的查询结果集 + /// public override DataSet DataSet => _dataSet; + + /// + /// SQL 语句参数 + /// public override Dictionary Parameters { get; } = []; private readonly string _connectionString = ""; @@ -25,7 +71,8 @@ namespace Milimoe.FunGame.Server.Services.DataUtility private DataSet _dataSet = new(); private SQLResult _result = SQLResult.NotFound; private readonly SQLServerInfo? _serverInfo; - private int _updateRows = 0; + private int _affectedRows = 0; + private long _lastInsertId = 0; public SQLiteHelper(string script = "", CommandType type = CommandType.Text) { @@ -67,7 +114,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } /// - /// 执行一个命令 + /// 执行现有命令() /// /// public override int Execute() @@ -102,14 +149,26 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } if (_transaction != null) command.Transaction = _transaction; - _updateRows = command.ExecuteNonQuery(); - _result = SQLResult.Success; + ReSet(); + _affectedRows = command.ExecuteNonQuery(); + if (_affectedRows > 0) + { + _result = SQLResult.Success; + if (script.Contains(Core.Library.SQLScript.Constant.Command_Insert, StringComparison.OrdinalIgnoreCase)) + { + using SqliteCommand idCommand = new("SELECT last_insert_rowid()", _connection); + if (_transaction != null) idCommand.Transaction = _transaction; + _lastInsertId = (long?)idCommand.ExecuteScalar() ?? 0; + if (_lastInsertId < 0) _lastInsertId = 0; + } + } + else _result = SQLResult.Fail; if (localTransaction) Commit(); } catch (Exception e) { if (localTransaction) Rollback(); - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -117,11 +176,77 @@ namespace Milimoe.FunGame.Server.Services.DataUtility if (localTransaction) Close(); if (ClearParametersAfterExecute) Parameters.Clear(); } - return UpdateRows; + return AffectedRows; } /// - /// 查询DataSet + /// 异步执行现有命令() + /// + /// + public override async Task ExecuteAsync() + { + return await ExecuteAsync(Script); + } + + /// + /// 异步执行一个指定的命令 + /// + /// + /// + public override async Task ExecuteAsync(string script) + { + bool localTransaction = _transaction == null; + + try + { + if (localTransaction) + { + NewTransaction(); + } + + OpenConnection(); + Script = script; + ServerHelper.WriteLine("SQLQuery -> " + script, InvokeMessageType.Api); + using SqliteCommand command = new(script, _connection); + command.CommandType = CommandType; + foreach (KeyValuePair param in Parameters) + { + command.Parameters.AddWithValue(param.Key, param.Value); + } + if (_transaction != null) command.Transaction = _transaction; + + ReSet(); + _affectedRows = await command.ExecuteNonQueryAsync(); + if (_affectedRows > 0) + { + _result = SQLResult.Success; + if (script.Contains(Core.Library.SQLScript.Constant.Command_Insert, StringComparison.OrdinalIgnoreCase)) + { + using SqliteCommand idCommand = new("SELECT last_insert_rowid()", _connection); + if (_transaction != null) idCommand.Transaction = _transaction; + _lastInsertId = (long?)await idCommand.ExecuteScalarAsync() ?? 0; + if (_lastInsertId < 0) _lastInsertId = 0; + } + } + else _result = SQLResult.Fail; + if (localTransaction) Commit(); + } + catch (Exception e) + { + if (localTransaction) Rollback(); + _result = SQLResult.SQLError; + ServerHelper.Error(e); + } + finally + { + if (localTransaction) Close(); + if (ClearParametersAfterExecute) Parameters.Clear(); + } + return AffectedRows; + } + + /// + /// 执行现有命令()查询 DataSet /// /// public override DataSet ExecuteDataSet() @@ -130,7 +255,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } /// - /// 执行指定的命令查询DataSet + /// 执行指定的命令查询 DataSet /// /// /// @@ -158,6 +283,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } if (_transaction != null) command.Transaction = _transaction; + ReSet(); using SqliteDataReader reader = command.ExecuteReader(); _dataSet = new(); do @@ -174,7 +300,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility catch (Exception e) { if (localTransaction) Rollback(); - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -186,7 +312,73 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } /// - /// 创建一个SQL事务 + /// 异步执行现有命令()查询 DataSet + /// + /// + public override async Task ExecuteDataSetAsync() + { + return await ExecuteDataSetAsync(Script); + } + + /// + /// 异步执行指定的命令查询 DataSet + /// + /// + /// + public override async Task ExecuteDataSetAsync(string script) + { + bool localTransaction = _transaction == null; + + try + { + if (localTransaction) + { + NewTransaction(); + } + + OpenConnection(); + Script = script; + ServerHelper.WriteLine("SQLQuery -> " + script, InvokeMessageType.Api); + using SqliteCommand command = new(script, _connection) + { + CommandType = CommandType + }; + foreach (KeyValuePair param in Parameters) + { + command.Parameters.AddWithValue(param.Key, param.Value); + } + if (_transaction != null) command.Transaction = _transaction; + + ReSet(); + using SqliteDataReader reader = await command.ExecuteReaderAsync(); + _dataSet = new(); + do + { + DataTable table = new(); + table.Load(reader); + _dataSet.Tables.Add(table); + } while (!reader.IsClosed && reader.NextResult()); + + if (localTransaction) Commit(); + + _result = _dataSet.Tables.Cast().Any(table => table.Rows.Count > 0) ? SQLResult.Success : SQLResult.NotFound; + } + catch (Exception e) + { + if (localTransaction) Rollback(); + _result = SQLResult.SQLError; + ServerHelper.Error(e); + } + finally + { + if (localTransaction) Close(); + if (ClearParametersAfterExecute) Parameters.Clear(); + } + return _dataSet; + } + + /// + /// 创建一个 SQL 事务 /// public override void NewTransaction() { @@ -209,7 +401,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } catch (Exception e) { - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -230,7 +422,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility } catch (Exception e) { - _result = SQLResult.Fail; + _result = SQLResult.SQLError; ServerHelper.Error(e); } finally @@ -253,7 +445,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility catch (Exception e) { ServerHelper.Error(e); - _result = SQLResult.Fail; + _result = SQLResult.SQLError; return false; } finally @@ -267,7 +459,7 @@ namespace Milimoe.FunGame.Server.Services.DataUtility /// /// 资源清理 /// - public void Dispose(bool disposing) + private void Dispose(bool disposing) { if (!_isDisposed) { @@ -288,5 +480,13 @@ namespace Milimoe.FunGame.Server.Services.DataUtility Dispose(true); GC.SuppressFinalize(this); } + + private void ReSet() + { + _result = SQLResult.NotFound; + _affectedRows = 0; + _lastInsertId = 0; + DataSet.Clear(); + } } } diff --git a/FunGame.Server/Services/FunGameSystem.cs b/FunGame.Server/Services/FunGameSystem.cs index 4b01e45..bf9f788 100644 --- a/FunGame.Server/Services/FunGameSystem.cs +++ b/FunGame.Server/Services/FunGameSystem.cs @@ -1,11 +1,11 @@ -using System.Collections; -using Milimoe.FunGame.Core.Api.Transmittal; +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.Model; using Milimoe.FunGame.Server.Others; using Milimoe.FunGame.Server.Services.DataUtility; @@ -13,10 +13,13 @@ namespace Milimoe.FunGame.Server.Services { public class FunGameSystem { + public delegate Task CloseListenerHandler(); + public static event CloseListenerHandler? CloseListener; + /// /// 服务器指令列表 /// - public static Hashtable OrderList { get; } = []; + public static Dictionary> OrderList { get; } = []; /// /// 在线房间列表 @@ -46,6 +49,11 @@ namespace Milimoe.FunGame.Server.Services /// /// 服务器配置 /// + public static PluginConfig LocalConfig { get; set; } = new("system", "local"); + + /// + /// 数据库配置 + /// public static PluginConfig SQLConfig { get; set; } = new("system", "sqlconfig"); /// @@ -53,6 +61,11 @@ namespace Milimoe.FunGame.Server.Services /// public const string FunGameWebAPITokenID = "fungame_web_api"; + /// + /// API Secret 文件名 + /// + public const string APISecretFileName = ".apisecret"; + /// /// 初始化数据库连接器 /// @@ -225,7 +238,7 @@ namespace Milimoe.FunGame.Server.Services /// public static void ServerLogin(SQLHelper sqlHelper) { - sqlHelper.Execute(ServerLoginLogs.Insert_ServerLoginLogs(sqlHelper, Config.ServerName, Config.ServerKey)); + sqlHelper.Execute(ServerLoginLogs.Insert_ServerLoginLog(sqlHelper, Config.ServerName, Config.ServerKey)); } /// @@ -237,10 +250,12 @@ namespace Milimoe.FunGame.Server.Services } /// - /// 初始化用户密钥列表 + /// 初始化服务器其他配置文件 /// - public static void InitUserKeys() + public static void InitOtherConfig() { + LocalConfig.LoadConfig(); + LocalConfig.SaveConfig(); UserKeys.LoadConfig(); UserKeys.SaveConfig(); } @@ -274,11 +289,12 @@ namespace Milimoe.FunGame.Server.Services /// 检查是否存在 API Secret Key /// /// - public static bool IsAPISecretKeyExist(string key) + public static bool APISecretKeyExists(string key) { using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper(); if (sql != null) { + key = Encryption.HmacSha256(key, Encryption.FileSha256(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, APISecretFileName))); sql.ExecuteDataSet(ApiTokens.Select_GetAPISecretKey(sql, key)); if (sql.Result == SQLResult.Success) { @@ -289,54 +305,42 @@ namespace Milimoe.FunGame.Server.Services } /// - /// 获取 API Secret Key - /// - /// - public static string GetAPISecretKey(string token) - { - using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper(); - if (sql != null) - { - sql.ExecuteDataSet(ApiTokens.Select_GetAPIToken(sql, token)); - if (sql.Result == SQLResult.Success) - { - return sql.DataSet.Tables[0].Rows[0][ApiTokens.Column_SecretKey].ToString() ?? ""; - } - } - return ""; - } - - /// - /// 设置 API Secret Key + /// 创建 API Secret Key /// /// /// /// - public static void SetAPISecretKey(string token, string reference1 = "", string reference2 = "", SQLHelper? sqlHelper = null) + public static string CreateAPISecretKey(string token, string reference1 = "", string reference2 = "", SQLHelper? sqlHelper = null) { bool useSQLHelper = sqlHelper != null; sqlHelper ??= Factory.OpenFactory.GetSQLHelper(); string key = Encryption.GenerateRandomString(); - if (sqlHelper != null) + string enKey = Encryption.HmacSha256(key, Encryption.FileSha256(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, APISecretFileName))); + if (sqlHelper != null && enKey != "") { sqlHelper.ExecuteDataSet(ApiTokens.Select_GetAPIToken(sqlHelper, token)); if (sqlHelper.Success) { - sqlHelper.Execute(ApiTokens.Update_APIToken(sqlHelper, token, key, reference1, reference2)); + sqlHelper.Execute(ApiTokens.Update_APIToken(sqlHelper, token, enKey, reference1, reference2)); } else { - sqlHelper.Execute(ApiTokens.Insert_APIToken(sqlHelper, token, key, reference1, reference2)); + sqlHelper.Execute(ApiTokens.Insert_APIToken(sqlHelper, token, enKey, reference1, reference2)); } } + else + { + ServerHelper.WriteLine($"API Secret Key '{token}' 创建失败,未连接到数据库或者找不到加密秘钥。", InvokeMessageType.Error); + } if (!useSQLHelper) { sqlHelper?.Dispose(); } + return key; } /// - /// 创建 SQL 服务后需要做的事 + /// 创建 SQL 服务后需要做的事,包括数据库初始化,API 初始化,首次建立管理员账户等等 /// /// public static void AfterCreateSQLService(SQLHelper sqlHelper) @@ -354,7 +358,12 @@ namespace Milimoe.FunGame.Server.Services { mysqlHelper.ExecuteSqlFile(AppDomain.CurrentDomain.BaseDirectory + "fungame.sql"); } - SetAPISecretKey(FunGameWebAPITokenID, sqlHelper: sqlHelper); + ConsoleModel.FirstRunRegAdmin(); + using StreamWriter sw = File.CreateText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, APISecretFileName)); + sw.WriteLine(Encryption.GenerateRandomString()); + sw.Flush(); + sw.Close(); + ServerHelper.WriteLine($"已生成一个默认的 API Token,Token ID: {FunGameWebAPITokenID}, Secret Key: 【{CreateAPISecretKey(FunGameWebAPITokenID, sqlHelper: sqlHelper)}】,请妥善保管方括号内的内容,仅显示一次。如遗忘需要使用管理员账号重置。"); sqlHelper.Execute(Configs.Insert_Config(sqlHelper, "Initialization", FunGameInfo.FunGame_Version, "SQL Service Installed.")); SQLConfig.Clear(); SQLConfig.Add("Initialized", true); @@ -367,6 +376,10 @@ namespace Milimoe.FunGame.Server.Services sqlHelper.Dispose(); } + /// + /// 数据库是否存在 + /// + /// public static bool DatabaseExists() { SQLConfig.LoadConfig(); @@ -403,6 +416,8 @@ namespace Milimoe.FunGame.Server.Services plugin.Controller.Close(); } } + // 停止所有正在运行的监听 + CloseListener?.Invoke(); } } } diff --git a/FunGame.Server/Services/General.cs b/FunGame.Server/Services/General.cs index a3201bc..efe0dcc 100644 --- a/FunGame.Server/Services/General.cs +++ b/FunGame.Server/Services/General.cs @@ -234,11 +234,11 @@ namespace Milimoe.FunGame.Server.Services public static void InitOrderList() { FunGameSystem.OrderList.Clear(); - FunGameSystem.OrderList.Add(OrderDictionary.Help, "Milimoe -> 帮助"); - FunGameSystem.OrderList.Add(OrderDictionary.Quit, "关闭服务器"); - FunGameSystem.OrderList.Add(OrderDictionary.Exit, "关闭服务器"); - FunGameSystem.OrderList.Add(OrderDictionary.Close, "关闭服务器"); - FunGameSystem.OrderList.Add(OrderDictionary.Restart, "重启服务器"); + FunGameSystem.OrderList.Add(OrderDictionary.Help, s => WriteLine("Milimoe -> 帮助")); + FunGameSystem.OrderList.Add(OrderDictionary.Quit, s => WriteLine("关闭服务器")); + FunGameSystem.OrderList.Add(OrderDictionary.Exit, s => WriteLine("关闭服务器")); + FunGameSystem.OrderList.Add(OrderDictionary.Close, s => WriteLine("关闭服务器")); + FunGameSystem.OrderList.Add(OrderDictionary.Restart, s => WriteLine("重启服务器")); } } diff --git a/FunGame.Server/logo.ico b/FunGame.Server/logo.ico index 4082874..b40ed3e 100644 Binary files a/FunGame.Server/logo.ico and b/FunGame.Server/logo.ico differ diff --git a/FunGame.WebAPI/Controllers/DataRequestController.cs b/FunGame.WebAPI/Controllers/DataRequestController.cs index 4da774d..d8ddfcf 100644 --- a/FunGame.WebAPI/Controllers/DataRequestController.cs +++ b/FunGame.WebAPI/Controllers/DataRequestController.cs @@ -19,6 +19,7 @@ namespace Milimoe.FunGame.WebAPI.Controllers { PayloadModel response = new() { + Event = "data_request", RequestType = payload.RequestType }; diff --git a/FunGame.WebAPI/Controllers/GamingRequestController.cs b/FunGame.WebAPI/Controllers/GamingRequestController.cs index 28e6247..2fb71c9 100644 --- a/FunGame.WebAPI/Controllers/GamingRequestController.cs +++ b/FunGame.WebAPI/Controllers/GamingRequestController.cs @@ -19,6 +19,7 @@ namespace Milimoe.FunGame.WebAPI.Controllers { PayloadModel response = new() { + Event = "gaming_request", RequestType = payload.RequestType }; if (model.RequestID == Guid.Empty) diff --git a/FunGame.WebAPI/Controllers/InventoryController.cs b/FunGame.WebAPI/Controllers/InventoryController.cs new file mode 100644 index 0000000..d9daf44 --- /dev/null +++ b/FunGame.WebAPI/Controllers/InventoryController.cs @@ -0,0 +1,437 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.Core.Library.SQLScript.Entity; +using Milimoe.FunGame.WebAPI.Models; + +namespace Milimoe.FunGame.WebAPI.Controllers +{ + [ApiController] + [Route("[controller]")] + [Authorize] + public class InventoryController(RESTfulAPIModel model, ILogger logger) : ControllerBase + { + private readonly ILogger _logger = logger; + + /// + /// ȡ̵ + /// + [HttpGet("getstore")] + public async Task GetStore(long[] ids) + { + PayloadModel response = new() + { + Event = "inventory_getstore", + RequestType = DataRequestType.Inventory_GetStore + }; + + try + { + Dictionary data = new() + { + { "ids", ids } + }; + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_GetStore, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ȡг + /// + [HttpGet("getmarket")] + public async Task GetMarket(long[]? users = null, MarketItemState state = MarketItemState.Listed, long[]? items = null) + { + PayloadModel response = new() + { + Event = "inventory_getmarket", + RequestType = DataRequestType.Inventory_GetMarket + }; + + try + { + Dictionary data = new() + { + { "users", users ?? [] }, + { "state", state }, + { "items", items ?? [] } + }; + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_GetMarket, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ̵깺Ʒ + /// + [HttpPost("storebuy")] + public async Task StoreBuy([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_storebuy", + RequestType = DataRequestType.Inventory_StoreBuy + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_StoreBuy, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// гƷ + /// + [HttpPost("marketbuy")] + public async Task MarketBuy([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_marketbuy", + RequestType = DataRequestType.Inventory_MarketBuy + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_MarketBuy, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ¿ + /// + [HttpPost("updateinventory")] + public async Task UpdateInventory([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_updateinventory", + RequestType = DataRequestType.Inventory_UpdateInventory + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_UpdateInventory, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ʹÿƷ + /// + [HttpPost("useitem")] + public async Task Use([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_use", + RequestType = DataRequestType.Inventory_Use + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_Use, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ̵Ʒ + /// + [HttpPost("storesell")] + public async Task StoreSell([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_storesell", + RequestType = DataRequestType.Inventory_StoreSell + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_StoreSell, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// гƷ + /// + [HttpPost("marketsell")] + public async Task MarketSell([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_marketsell", + RequestType = DataRequestType.Inventory_MarketSell + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_MarketSell, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ¼гƷ + /// + [HttpPost("marketdelist")] + public async Task MarketDelist([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_marketdelist", + RequestType = DataRequestType.Inventory_MarketDelist + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_MarketDelist, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// г۸ + /// + [HttpPost("updatemarketprice")] + public async Task UpdateMarketPrice([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_updatemarketprice", + RequestType = DataRequestType.Inventory_UpdateMarketPrice + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_UpdateMarketPrice, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ȡױ + /// + [HttpGet("getoffer")] + public async Task GetOffer([FromQuery] long offerId) + { + PayloadModel response = new() + { + Event = "inventory_getoffer", + RequestType = DataRequestType.Inventory_GetOffer + }; + + try + { + Dictionary data = new() + { + { OffersQuery.Column_Id, offerId }, + { "apiQuery", true } + }; + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_GetOffer, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ױ + /// + [HttpPost("makeoffer")] + public async Task MakeOffer([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_makeoffer", + RequestType = DataRequestType.Inventory_MakeOffer + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_MakeOffer, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ޸Ľױ + /// + [HttpPost("reviseoffer")] + public async Task ReviseOffer([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_reviseoffer", + RequestType = DataRequestType.Inventory_ReviseOffer + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_ReviseOffer, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// Ӧױ + /// + [HttpPost("respondoffer")] + public async Task RespondOffer([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "inventory_respondoffer", + RequestType = DataRequestType.Inventory_RespondOffer + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Inventory_RespondOffer, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + } +} diff --git a/FunGame.WebAPI/Controllers/MainController.cs b/FunGame.WebAPI/Controllers/MainController.cs new file mode 100644 index 0000000..b5bf73a --- /dev/null +++ b/FunGame.WebAPI/Controllers/MainController.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.WebAPI.Models; + +namespace Milimoe.FunGame.WebAPI.Controllers +{ + [ApiController] + [Route("[controller]")] + [Authorize] + public class MainController(RESTfulAPIModel model, ILogger logger) : ControllerBase + { + private readonly ILogger _logger = logger; + + /// + /// ȡ + /// + [HttpGet("notice")] + public async Task GetNotice() + { + PayloadModel response = new() + { + Event = "main_getnotice", + RequestType = DataRequestType.Main_GetNotice + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_GetNotice, []); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// Ϣ + /// + [HttpPost("chat")] + public async Task Chat([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "main_chat", + RequestType = DataRequestType.Main_Chat + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_Chat, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + } +} diff --git a/FunGame.WebAPI/Controllers/RoomController.cs b/FunGame.WebAPI/Controllers/RoomController.cs new file mode 100644 index 0000000..bf75009 --- /dev/null +++ b/FunGame.WebAPI/Controllers/RoomController.cs @@ -0,0 +1,340 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.WebAPI.Models; + +namespace Milimoe.FunGame.WebAPI.Controllers +{ + [ApiController] + [Route("[controller]")] + [Authorize] + public class RoomController(RESTfulAPIModel model, ILogger logger) : ControllerBase + { + private readonly ILogger _logger = logger; + + /// + /// + /// + [HttpPost("create")] + public async Task CreateRoom([FromBody] Dictionary data) + { + string msg = "ʱ޷"; + PayloadModel response = new() + { + Event = "room_create", + RequestType = DataRequestType.Main_CreateRoom + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_CreateRoom, data); + if (result.TryGetValue("room", out object? value) && value is Room room && room.Roomid != "-1") + { + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + msg = "ʧܣ"; + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = msg; + return StatusCode(500, response); + } + + /// + /// ȡ/·б + /// + [HttpGet("list")] + public async Task GetRoomList() + { + PayloadModel response = new() + { + Event = "room_list", + RequestType = DataRequestType.Main_UpdateRoom + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_UpdateRoom, []); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// 뷿 + /// + [HttpPost("join")] + public async Task IntoRoom([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_join", + RequestType = DataRequestType.Main_IntoRoom + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_IntoRoom, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ˳ + /// + [HttpPost("quit")] + public async Task QuitRoom([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_quit", + RequestType = DataRequestType.Main_QuitRoom + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_QuitRoom, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ƥ䷿ + /// + [HttpPost("match")] + public async Task MatchRoom([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_match", + RequestType = DataRequestType.Main_MatchRoom + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_MatchRoom, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ׼״̬ + /// + [HttpPost("ready")] + public async Task SetReady([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_ready", + RequestType = DataRequestType.Main_Ready + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_Ready, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ȡ׼״̬ + /// + [HttpPost("cancelready")] + public async Task CancelReady([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_cancelready", + RequestType = DataRequestType.Main_CancelReady + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_CancelReady, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ʼϷ + /// + [HttpPost("start")] + public async Task StartGame([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_start", + RequestType = DataRequestType.Main_StartGame + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Main_StartGame, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// · + /// + [HttpPost("updateroomsettings")] + public async Task UpdateRoomSettings([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_updatesettings", + RequestType = DataRequestType.Room_UpdateRoomSettings + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Room_UpdateRoomSettings, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ȡ + /// + [HttpPost("playercount")] + public async Task GetRoomPlayerCount([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_playercount", + RequestType = DataRequestType.Room_GetRoomPlayerCount + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Room_GetRoomPlayerCount, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// · + /// + [HttpPost("updatemaster")] + public async Task UpdateRoomMaster([FromBody] Dictionary data) + { + PayloadModel response = new() + { + Event = "room_updatemaster", + RequestType = DataRequestType.Room_UpdateRoomMaster + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.Room_UpdateRoomMaster, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + } +} diff --git a/FunGame.WebAPI/Controllers/UserCenterController.cs b/FunGame.WebAPI/Controllers/UserCenterController.cs new file mode 100644 index 0000000..49cc9a1 --- /dev/null +++ b/FunGame.WebAPI/Controllers/UserCenterController.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.Core.Library.SQLScript.Entity; +using Milimoe.FunGame.WebAPI.Models; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Milimoe.FunGame.WebAPI.Controllers +{ + [ApiController] + [Route("[controller]")] + [Authorize] + public class UserCenterController(RESTfulAPIModel model, ILogger logger) : ControllerBase + { + private readonly ILogger _logger = logger; + + /// + /// û + /// + [HttpPut("updateuser")] + public async Task UpdateUser(Dictionary data) + { + PayloadModel response = new() + { + Event = "user_update", + RequestType = DataRequestType.UserCenter_UpdateUser + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.UserCenter_UpdateUser, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// + /// + [HttpPut("updatepassword")] + public async Task UpdatePassword(Dictionary data) + { + PayloadModel response = new() + { + Event = "user_updatepassword", + RequestType = DataRequestType.UserCenter_UpdatePassword + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.UserCenter_UpdatePassword, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + + /// + /// ÿǩ + /// + [HttpPost("dailysignin")] + public async Task DailySignIn(Dictionary data) + { + PayloadModel response = new() + { + Event = "user_dailysignin", + RequestType = DataRequestType.UserCenter_DailySignIn + }; + + try + { + Dictionary result = await model.DataRequestController.GetResultData(DataRequestType.UserCenter_DailySignIn, data); + response.StatusCode = 200; + response.Data = result; + return Ok(response); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + } + + response.StatusCode = 500; + response.Message = "ʱ޷"; + return StatusCode(500, response); + } + } +} diff --git a/FunGame.WebAPI/Controllers/UserController.cs b/FunGame.WebAPI/Controllers/UserController.cs index f5cdf20..626688d 100644 --- a/FunGame.WebAPI/Controllers/UserController.cs +++ b/FunGame.WebAPI/Controllers/UserController.cs @@ -28,6 +28,13 @@ namespace Milimoe.FunGame.WebAPI.Controllers [Authorize(AuthenticationSchemes = "APIBearer")] public IActionResult Register([FromBody] RegDTO dto) { + string msg = "服务器暂时无法处理注册请求。"; + PayloadModel response = new() + { + Event = "user_register", + RequestType = DataRequestType.Reg_Reg + }; + try { string clientIP = HttpContext.Connection.RemoteIpAddress?.ToString() + ":" + HttpContext.Connection.RemotePort; @@ -37,39 +44,40 @@ namespace Milimoe.FunGame.WebAPI.Controllers string email = dto.Email; string verifycode = dto.VerifyCode; - (string msg, RegInvokeType type, bool success) = DataRequestService.Reg(apiListener, username, password, email, verifycode, clientIP); + (msg, RegInvokeType type, bool success) = DataRequestService.Reg(apiListener, username, password, email, verifycode, clientIP); - return Ok(new PayloadModel() + response.StatusCode = 200; + response.Message = msg; + response.Data = new() { - RequestType = DataRequestType.Reg_Reg, - StatusCode = 200, - Message = msg, - Data = new() - { - { "success", success }, - { "type", type } - } - }); + { "success", success }, + { "type", type } + }; + return Ok(response); } catch (Exception e) { logger.LogError("Error: {e}", e); } - return BadRequest("服务器暂时无法处理注册请求。"); + + response.StatusCode = 500; + response.Message = msg; + return StatusCode(500, response); } [HttpPost("login")] public async Task Login([FromBody] LoginDTO dto) { + string msg = "服务器暂时无法处理登录请求。"; Config.ConnectingPlayerCount++; + PayloadModel response = new() + { + Event = "user_login", + RequestType = DataRequestType.Login_Login + }; + try { - PayloadModel response = new() - { - RequestType = DataRequestType.Login_Login - }; - string msg = "服务器暂时无法处理登录请求。"; - string clientIP = HttpContext.Connection.RemoteIpAddress?.ToString() + ":" + HttpContext.Connection.RemotePort; ServerHelper.WriteLine(ServerHelper.MakeClientName(clientIP) + " 通过 RESTful API 连接至服务器,正在登录 . . .", InvokeMessageType.Core); string username = dto.Username; @@ -109,21 +117,26 @@ namespace Milimoe.FunGame.WebAPI.Controllers Config.ConnectingPlayerCount--; logger.LogError("Error: {e}", e); } - return BadRequest(); + + response.StatusCode = 500; + response.Message = msg; + return StatusCode(500, response); } [HttpPost("logout")] [Authorize] public async Task LogOut() { + string msg = "退出登录失败!服务器可能暂时无法处理此请求。"; + PayloadModel response = new() + { + Event = "user_logout", + RequestType = DataRequestType.RunTime_Logout + }; + try { - PayloadModel response = new() - { - RequestType = DataRequestType.RunTime_Logout - }; string username = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? ""; - if (apiListener.UserList.ContainsKey(username)) { RESTfulAPIModel model = (RESTfulAPIModel)apiListener.UserList[username]; @@ -134,16 +147,15 @@ namespace Milimoe.FunGame.WebAPI.Controllers response.StatusCode = 200; return Ok(response); } - - response.Message = "退出登录失败!"; - response.StatusCode = 400; - return BadRequest(response); } catch (Exception e) { logger.LogError("Error: {e}", e); } - return BadRequest(); + + response.StatusCode = 500; + response.Message = msg; + return StatusCode(500, response); } [HttpPost("refresh")] @@ -163,7 +175,7 @@ namespace Milimoe.FunGame.WebAPI.Controllers { logger.LogError("Error: {e}", e); } - return BadRequest(); + return StatusCode(500); } private async Task CheckConnection(string username, string clientIP) diff --git a/FunGame.WebAPI/FunGame.WebAPI.csproj b/FunGame.WebAPI/FunGame.WebAPI.csproj index e86a67e..0c5b16e 100644 --- a/FunGame.WebAPI/FunGame.WebAPI.csproj +++ b/FunGame.WebAPI/FunGame.WebAPI.csproj @@ -7,8 +7,8 @@ Milimoe.$(MSBuildProjectName.Replace(" ", "_")) ..\bin\ FunGameWebAPI - Images\logo.ico Milimoe + logo.ico @@ -20,7 +20,7 @@ - + @@ -32,17 +32,11 @@ + - - - \ - True - - - diff --git a/FunGame.WebAPI/Images/logo.ico b/FunGame.WebAPI/Images/logo.ico deleted file mode 100644 index 4082874..0000000 Binary files a/FunGame.WebAPI/Images/logo.ico and /dev/null differ diff --git a/FunGame.WebAPI/Models/PayloadModel.cs b/FunGame.WebAPI/Models/PayloadModel.cs index ab9a777..711c61e 100644 --- a/FunGame.WebAPI/Models/PayloadModel.cs +++ b/FunGame.WebAPI/Models/PayloadModel.cs @@ -2,6 +2,11 @@ { public class PayloadModel where T : struct, Enum { + /// + /// 业务事件 + /// + public string Event { get; set; } = ""; + /// /// 请求类型 /// diff --git a/FunGame.WebAPI/Program.cs b/FunGame.WebAPI/Program.cs index 9192c7b..e7bf6f5 100644 --- a/FunGame.WebAPI/Program.cs +++ b/FunGame.WebAPI/Program.cs @@ -62,8 +62,8 @@ try // 读取 Server 插件 FunGameSystem.GetServerPlugins(); - // 初始化用户密钥列表 - FunGameSystem.InitUserKeys(); + // 初始化服务器其他配置文件 + FunGameSystem.InitOtherConfig(); // Add services to the container. WebApplicationBuilder builder = WebApplication.CreateBuilder(args); @@ -215,12 +215,17 @@ try IExceptionHandlerFeature? contextFeature = context.Features.Get(); if (contextFeature != null) { - await context.Response.WriteAsync(new + await context.Response.WriteAsync(NetworkUtility.JsonSerialize(new PayloadModel() { - context.Response.StatusCode, + Event = "system_error", + RequestType = DataRequestType.UnKnown, + StatusCode = context.Response.StatusCode, Message = "Internal Server Error.", - Detailed = contextFeature.Error.Message - }.ToString() ?? ""); + Data = new() + { + { "msg", contextFeature.Error.Message } + } + })); } }); }); @@ -242,6 +247,16 @@ try // 开始监听连接 listener.BannedList.AddRange(Config.ServerBannedList); + FunGameSystem.CloseListener += async () => + { + foreach (ServerModel model in listener.ClientList.Cast>()) + { + await model.Kick("服务器正在关闭。"); + } + listener.Close(); + await app.StopAsync(); + }; + Task order = Task.Factory.StartNew(GetConsoleOrder); app.Run(); @@ -260,8 +275,20 @@ async Task GetConsoleOrder() if (order != "") { order = order.ToLower(); + if (FunGameSystem.OrderList.TryGetValue(order, out Action? action) && action != null) + { + action(order); + } switch (order) { + case OrderDictionary.Quit: + case OrderDictionary.Exit: + case OrderDictionary.Close: + CloseServer(); + break; + case OrderDictionary.Restart: + ServerHelper.WriteLine("服务器正在运行,请手动结束服务器进程再启动!"); + break; default: await ConsoleModel.Order(listener, order); break; diff --git a/FunGame.WebAPI/Services/APIBearerTokenHandler.cs b/FunGame.WebAPI/Services/APIBearerTokenHandler.cs index d12bc51..92d93b3 100644 --- a/FunGame.WebAPI/Services/APIBearerTokenHandler.cs +++ b/FunGame.WebAPI/Services/APIBearerTokenHandler.cs @@ -25,7 +25,7 @@ namespace Milimoe.FunGame.WebAPI.Services string key = authorizationHeader["Bearer ".Length..].Trim(); // 验证 API Bearer Token - if (key == "" || !FunGameSystem.IsAPISecretKeyExist(key)) + if (key == "" || !FunGameSystem.APISecretKeyExists(key)) { await Task.CompletedTask; return AuthenticateResult.Fail("Invalid Token."); diff --git a/FunGame.WebAPI/logo.ico b/FunGame.WebAPI/logo.ico new file mode 100644 index 0000000..b40ed3e Binary files /dev/null and b/FunGame.WebAPI/logo.ico differ diff --git a/FunGameServer.sln b/FunGameServer.sln index f7c0a2f..d00eaac 100644 --- a/FunGameServer.sln +++ b/FunGameServer.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunGame.WebAPI", "FunGame.W EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunGame.Core", "..\FunGame.Core\FunGame.Core.csproj", "{33CAC80F-8394-41DB-B5AF-5E91123E6C84}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunGame.SQLQueryExtension", "..\FunGame.Extension\FunGame.SQLQueryExtension\FunGame.SQLQueryExtension.csproj", "{9EEB3474-B9A1-4E5E-BEF0-14F30D81873C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {33CAC80F-8394-41DB-B5AF-5E91123E6C84}.Debug|Any CPU.Build.0 = Debug|Any CPU {33CAC80F-8394-41DB-B5AF-5E91123E6C84}.Release|Any CPU.ActiveCfg = Release|Any CPU {33CAC80F-8394-41DB-B5AF-5E91123E6C84}.Release|Any CPU.Build.0 = Release|Any CPU + {9EEB3474-B9A1-4E5E-BEF0-14F30D81873C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9EEB3474-B9A1-4E5E-BEF0-14F30D81873C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9EEB3474-B9A1-4E5E-BEF0-14F30D81873C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9EEB3474-B9A1-4E5E-BEF0-14F30D81873C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE