Compare commits
2 Commits
fix/cross-
...
fb76f49ec0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb76f49ec0 | ||
|
|
f8fa34edbc |
@@ -4,10 +4,39 @@ namespace MMOserver.Api;
|
|||||||
// API 응답(BossRaidAccessResponse)을 직접 노출하지 않고 이걸로 매핑해서 반환
|
// API 응답(BossRaidAccessResponse)을 직접 노출하지 않고 이걸로 매핑해서 반환
|
||||||
public sealed class BossRaidResult
|
public sealed class BossRaidResult
|
||||||
{
|
{
|
||||||
public int RoomId { get; init; }
|
public int RoomId
|
||||||
public string SessionName { get; init; } = string.Empty;
|
{
|
||||||
public int BossId { get; init; }
|
get;
|
||||||
public List<string> Players { get; init; } = new();
|
init;
|
||||||
public string Status { get; init; } = string.Empty;
|
}
|
||||||
public Dictionary<string, string> Tokens { get; init; } = new();
|
|
||||||
|
public string SessionName
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
init;
|
||||||
|
} = string.Empty;
|
||||||
|
|
||||||
|
public int BossId
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
init;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> Players
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
init;
|
||||||
|
} = new();
|
||||||
|
|
||||||
|
public string Status
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
init;
|
||||||
|
} = string.Empty;
|
||||||
|
|
||||||
|
public Dictionary<string, string> Tokens
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
init;
|
||||||
|
} = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ public class RestApi : Singleton<RestApi>
|
|||||||
// 성공 시 BossRaidResult 반환, 실패/거절 시 null 반환
|
// 성공 시 BossRaidResult 반환, 실패/거절 시 null 반환
|
||||||
public async Task<BossRaidResult?> BossRaidAccesssAsync(List<string> userNames, int bossId)
|
public async Task<BossRaidResult?> BossRaidAccesssAsync(List<string> userNames, int bossId)
|
||||||
{
|
{
|
||||||
string url = AppConfig.RestApi.BaseUrl + "/api/internal/bossraid/entry";
|
string url = AppConfig.RestApi.BaseUrl + AppConfig.RestApi.BossRaidAccess;
|
||||||
|
|
||||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++)
|
for (int attempt = 1; attempt <= MAX_RETRY; attempt++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, new { usernames = userNames, bossId });
|
HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, new { usernames = userNames, bossId = bossId });
|
||||||
|
|
||||||
// 401: API 키 인증 실패
|
// 401: API 키 인증 실패
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
@@ -88,20 +88,13 @@ public class RestApi : Singleton<RestApi>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 400: 입장 조건 미충족 (레벨 부족 등)
|
// 400: 입장 조건 미충족 (레벨 부족, 이미 진행중 등)
|
||||||
if (response.StatusCode == HttpStatusCode.BadRequest)
|
if (response.StatusCode == HttpStatusCode.BadRequest)
|
||||||
{
|
{
|
||||||
Log.Warning("[RestApi] 보스 레이드 입장 거절 (400) BossId={BossId}", bossId);
|
Log.Warning("[RestApi] 보스 레이드 입장 거절 (400) BossId={BossId}", bossId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 409: 이미 진행 중이거나 슬롯 충돌
|
|
||||||
if (response.StatusCode == HttpStatusCode.Conflict)
|
|
||||||
{
|
|
||||||
Log.Warning("[RestApi] 보스 레이드 충돌 (409) BossId={BossId} - 이미 진행 중이거나 슬롯 없음", bossId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
BossRaidAccessResponse? raw = await response.Content.ReadFromJsonAsync<BossRaidAccessResponse>();
|
BossRaidAccessResponse? raw = await response.Content.ReadFromJsonAsync<BossRaidAccessResponse>();
|
||||||
@@ -118,7 +111,7 @@ public class RestApi : Singleton<RestApi>
|
|||||||
BossId = raw.BossId,
|
BossId = raw.BossId,
|
||||||
Players = raw.Players,
|
Players = raw.Players,
|
||||||
Status = raw.Status ?? string.Empty,
|
Status = raw.Status ?? string.Empty,
|
||||||
Tokens = raw.Tokens ?? new()
|
Tokens = raw.Tokens
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (attempt < MAX_RETRY)
|
catch (Exception ex) when (attempt < MAX_RETRY)
|
||||||
@@ -173,10 +166,10 @@ public class RestApi : Singleton<RestApi>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[JsonPropertyName("tokens")]
|
[JsonPropertyName("tokens")]
|
||||||
public Dictionary<string, string>? Tokens
|
public Dictionary<string, string> Tokens
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
} = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ public sealed class ServerConfig
|
|||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int ChannelCount
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
public ServerConfig(IConfigurationSection section)
|
public ServerConfig(IConfigurationSection section)
|
||||||
{
|
{
|
||||||
Port = int.Parse(section["Port"] ?? throw new InvalidOperationException("Server:Port is required in config.json"));
|
Port = int.Parse(section["Port"] ?? throw new InvalidOperationException("Server:Port is required in config.json"));
|
||||||
|
ChannelCount = int.Parse(section["ChannelCount"] ?? "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +54,11 @@ public sealed class RestApiConfig
|
|||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string BossRaidAccess
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
public string ApiKey
|
public string ApiKey
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -57,6 +68,7 @@ public sealed class RestApiConfig
|
|||||||
{
|
{
|
||||||
BaseUrl = section["BaseUrl"] ?? throw new InvalidOperationException("RestApi:BaseUrl is required in config.json");
|
BaseUrl = section["BaseUrl"] ?? throw new InvalidOperationException("RestApi:BaseUrl is required in config.json");
|
||||||
VerifyToken = section["VerifyToken"] ?? throw new InvalidOperationException("RestApi:BaseUrl is required in config.json");
|
VerifyToken = section["VerifyToken"] ?? throw new InvalidOperationException("RestApi:BaseUrl is required in config.json");
|
||||||
|
BossRaidAccess = section["BossRaidAccess"] ?? throw new InvalidOperationException("RestApi:BaseUrl is required in config.json");
|
||||||
ApiKey = section["ApiKey"] ?? throw new InvalidOperationException("RestApi:ApiKey is required in config.json");
|
ApiKey = section["ApiKey"] ?? throw new InvalidOperationException("RestApi:ApiKey is required in config.json");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using LiteNetLib;
|
using LiteNetLib;
|
||||||
|
using MMOserver.Config;
|
||||||
using MMOserver.Utils;
|
using MMOserver.Utils;
|
||||||
|
|
||||||
namespace MMOserver.Game.Channel;
|
namespace MMOserver.Game.Channel;
|
||||||
@@ -8,16 +9,12 @@ public class ChannelManager : Singleton<ChannelManager>
|
|||||||
// 채널 관리
|
// 채널 관리
|
||||||
private Dictionary<int, Channel> channels = new Dictionary<int, Channel>();
|
private Dictionary<int, Channel> channels = new Dictionary<int, Channel>();
|
||||||
|
|
||||||
// 보스 레이드 채널
|
|
||||||
private readonly int bossChannelStart = 10000;
|
|
||||||
private readonly int bossChannelSize = 10;
|
|
||||||
|
|
||||||
// 채널별 유저 관리 (유저 key, 채널 val)
|
// 채널별 유저 관리 (유저 key, 채널 val)
|
||||||
private Dictionary<int, int> connectUsers = new Dictionary<int, int>();
|
private Dictionary<int, int> connectUsers = new Dictionary<int, int>();
|
||||||
|
|
||||||
public ChannelManager()
|
public ChannelManager()
|
||||||
{
|
{
|
||||||
Initializer();
|
Initializer(AppConfig.Server.ChannelCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initializer(int channelSize = 1)
|
public void Initializer(int channelSize = 1)
|
||||||
@@ -26,13 +23,6 @@ public class ChannelManager : Singleton<ChannelManager>
|
|||||||
{
|
{
|
||||||
channels.Add(i, new Channel(i));
|
channels.Add(i, new Channel(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 보스 채널 생성
|
|
||||||
for (int i = 1; i <= bossChannelSize; i++)
|
|
||||||
{
|
|
||||||
int bossChannel = i + bossChannelStart;
|
|
||||||
channels.Add(bossChannel, new Channel(bossChannel));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Channel GetChannel(int channelId)
|
public Channel GetChannel(int channelId)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class GameServer : ServerBase
|
|||||||
{
|
{
|
||||||
AccTokenPacket accTokenPacket = Serializer.Deserialize<AccTokenPacket>(new ReadOnlyMemory<byte>(payload));
|
AccTokenPacket accTokenPacket = Serializer.Deserialize<AccTokenPacket>(new ReadOnlyMemory<byte>(payload));
|
||||||
string token = accTokenPacket.Token;
|
string token = accTokenPacket.Token;
|
||||||
string username = "";
|
string? username = "";
|
||||||
tokenHash.TryGetValue(token, out int hashKey);
|
tokenHash.TryGetValue(token, out int hashKey);
|
||||||
if (hashKey <= 1000)
|
if (hashKey <= 1000)
|
||||||
{
|
{
|
||||||
@@ -122,7 +122,7 @@ public class GameServer : ServerBase
|
|||||||
{
|
{
|
||||||
// 신규 연결: 웹서버에 JWT 검증 요청
|
// 신규 연결: 웹서버에 JWT 검증 요청
|
||||||
username = await RestApi.Instance.VerifyTokenAsync(token);
|
username = await RestApi.Instance.VerifyTokenAsync(token);
|
||||||
if (username == null)
|
if (username == null || username.Trim().Length <= 0)
|
||||||
{
|
{
|
||||||
Log.Warning("[Server] 토큰 검증 실패 - 연결 거부 PeerId={Id}", peer.Id);
|
Log.Warning("[Server] 토큰 검증 실패 - 연결 거부 PeerId={Id}", peer.Id);
|
||||||
userUuidGenerator.Release(hashKey);
|
userUuidGenerator.Release(hashKey);
|
||||||
@@ -406,12 +406,7 @@ public class GameServer : ServerBase
|
|||||||
Mp = player.Mp,
|
Mp = player.Mp,
|
||||||
MaxMp = player.MaxMp,
|
MaxMp = player.MaxMp,
|
||||||
Position = new Position { X = player.PosX, Y = player.PosY, Z = player.PosZ },
|
Position = new Position { X = player.PosX, Y = player.PosY, Z = player.PosZ },
|
||||||
RotY = player.RotY,
|
RotY = player.RotY
|
||||||
Experience = player.Experience,
|
|
||||||
NextExp = player.NextExp,
|
|
||||||
AttackPower = player.AttackPower,
|
|
||||||
AttackRange = player.AttackRange,
|
|
||||||
SprintMultiplier = player.SprintMultiplier
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,17 +574,12 @@ public class GameServer : ServerBase
|
|||||||
|
|
||||||
if (memberPeer != null)
|
if (memberPeer != null)
|
||||||
{
|
{
|
||||||
// 세션에서 username 조회
|
|
||||||
string nickname = memberPeer.Tag is Session s && !string.IsNullOrEmpty(s.UserName)
|
|
||||||
? s.UserName
|
|
||||||
: memberId.ToString();
|
|
||||||
|
|
||||||
// 새 채널에 유저 추가
|
// 새 채널에 유저 추가
|
||||||
Player newPlayer = new()
|
Player newPlayer = new()
|
||||||
{
|
{
|
||||||
HashKey = memberId,
|
HashKey = memberId,
|
||||||
PlayerId = memberId,
|
PlayerId = memberId,
|
||||||
Nickname = nickname
|
Nickname = memberId.ToString()
|
||||||
};
|
};
|
||||||
cm.AddUser(packet.ChannelId, memberId, newPlayer, memberPeer);
|
cm.AddUser(packet.ChannelId, memberId, newPlayer, memberPeer);
|
||||||
|
|
||||||
@@ -1083,16 +1073,14 @@ public class GameServer : ServerBase
|
|||||||
List<string> userNames = new List<string>();
|
List<string> userNames = new List<string>();
|
||||||
foreach (int memberId in party.PartyMemberIds)
|
foreach (int memberId in party.PartyMemberIds)
|
||||||
{
|
{
|
||||||
Player? member = channel.GetPlayer(memberId);
|
Player? memberPlayer = channel.GetPlayer(memberId);
|
||||||
if (member == null)
|
if (memberPlayer != null)
|
||||||
{
|
{
|
||||||
continue;
|
userNames.Add(memberPlayer.Nickname);
|
||||||
}
|
}
|
||||||
|
|
||||||
userNames.Add(member.Nickname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BossRaidResult? result = await RestApi.Instance.BossRaidAccesssAsync(userNames, packet.RaidId);
|
BossRaidResult? result = await RestApi.Instance.BossRaidAccesssAsync(userNames, 1);
|
||||||
|
|
||||||
// 입장 실패
|
// 입장 실패
|
||||||
if (result == null || result.BossId <= 0)
|
if (result == null || result.BossId <= 0)
|
||||||
@@ -1159,13 +1147,17 @@ public class GameServer : ServerBase
|
|||||||
|
|
||||||
SendTo(memberPeer, PacketSerializer.Serialize((ushort)PacketCode.CHANGE_MAP, response));
|
SendTo(memberPeer, PacketSerializer.Serialize((ushort)PacketCode.CHANGE_MAP, response));
|
||||||
|
|
||||||
// 각 파티원에게 레이드 입장 정보 전달 (본인의 토큰 포함)
|
// 모두에게 레이드로 이동 (할당된 실제 레이드 맵 ID 전달)
|
||||||
string? memberToken = null;
|
if (result.Tokens.ContainsKey(memberPlayer.Nickname.Trim()))
|
||||||
result.Tokens?.TryGetValue(memberPlayer.Nickname, out memberToken);
|
{
|
||||||
SendTo(memberPeer,
|
SendTo(peer,
|
||||||
PacketSerializer.Serialize((ushort)PacketCode.INTO_BOSS_RAID,
|
PacketSerializer.Serialize((ushort)PacketCode.INTO_BOSS_RAID,
|
||||||
new IntoBossRaidPacket
|
new IntoBossRaidPacket
|
||||||
{ RaidId = assignedRaidMapId, IsSuccess = true, Session = result.SessionName, Token = memberToken }));
|
{
|
||||||
|
RaidId = assignedRaidMapId, IsSuccess = true, Session = result.SessionName,
|
||||||
|
Token = result.Tokens[memberPlayer.Nickname.Trim()]
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Debug("[GameServer] INTO_BOSS_RAID HashKey={Key} PartyId={PartyId} AssignedRaidMapId={RaidId}", hashKey, party.PartyId,
|
Log.Debug("[GameServer] INTO_BOSS_RAID HashKey={Key} PartyId={PartyId} AssignedRaidMapId={RaidId}", hashKey, party.PartyId,
|
||||||
|
|||||||
@@ -75,38 +75,6 @@ public class Player
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 경험치
|
|
||||||
public int Experience
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int NextExp
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 전투 스탯
|
|
||||||
public float AttackPower
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float AttackRange
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float SprintMultiplier
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 현재 위치한 맵 ID
|
// 현재 위치한 맵 ID
|
||||||
public int CurrentMapId
|
public int CurrentMapId
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"Server": {
|
"Server": {
|
||||||
"Port": 9500
|
"Port": 9500,
|
||||||
|
"ChannelCount": 5
|
||||||
},
|
},
|
||||||
"RestApi": {
|
"RestApi": {
|
||||||
"BaseUrl": "https://a301.api.tolelom.xyz",
|
"BaseUrl": "https://a301.api.tolelom.xyz",
|
||||||
"VerifyToken": "/api/internal/auth/verify",
|
"VerifyToken": "/api/internal/auth/verify",
|
||||||
|
"BossRaidAccess": "/api/internal/bossraid/entry",
|
||||||
"ApiKey": "017f15b28143fc67d2e5bed283c37d2da858b9f294990a5334238e055e3f5425"
|
"ApiKey": "017f15b28143fc67d2e5bed283c37d2da858b9f294990a5334238e055e3f5425"
|
||||||
},
|
},
|
||||||
"Database": {
|
"Database": {
|
||||||
|
|||||||
Reference in New Issue
Block a user