?
This commit is contained in:
30
Api/DebugController.cs
Normal file
30
Api/DebugController.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CatLink.Api
|
||||
{
|
||||
[ApiController]
|
||||
[Route("debug")]
|
||||
public class DebugController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<DebugController> _logger;
|
||||
|
||||
public DebugController(ILogger<DebugController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetDebugInfo()
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
uptime = Environment.TickCount64,
|
||||
os = Environment.OSVersion.ToString(),
|
||||
processorCount = Environment.ProcessorCount,
|
||||
workingSet = Environment.WorkingSet,
|
||||
gcMemory = GC.GetTotalMemory(false)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
189
Api/FutariLobby.cs
Normal file
189
Api/FutariLobby.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using CatLink.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CatLink.Api
|
||||
{
|
||||
[ApiController]
|
||||
[Route("recruit")]
|
||||
public class FutariLobby : ControllerBase
|
||||
{
|
||||
private static readonly Dictionary<uint, RecruitRecord> _recruits = new();
|
||||
private static readonly object _recruitsLock = new();
|
||||
private static readonly string _logFilePath = "recruit.log";
|
||||
private readonly ILogger<FutariLobby> _logger;
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public FutariLobby(ILogger<FutariLobby> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("start")]
|
||||
public IActionResult StartRecruit([FromBody] JsonElement json)
|
||||
{
|
||||
try
|
||||
{
|
||||
var record = JsonSerializer.Deserialize<RecruitRecord>(json.GetRawText(), _jsonOptions);
|
||||
if (record == null)
|
||||
{
|
||||
throw new ApiException(400, "Invalid request body");
|
||||
}
|
||||
|
||||
record.Time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
var key = record.RecruitInfo.MechaInfo.IpAddress;
|
||||
|
||||
lock (_recruitsLock)
|
||||
{
|
||||
var isNew = !_recruits.ContainsKey(key);
|
||||
_recruits[key] = record;
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
LogRecruit("START", record);
|
||||
}
|
||||
}
|
||||
|
||||
// Hash user IDs (commented out - client already sends numeric IDs)
|
||||
// if (record.RecruitInfo.MechaInfo.UserIDs != null)
|
||||
// {
|
||||
// record.RecruitInfo.MechaInfo.UserIDs = record.RecruitInfo.MechaInfo.UserIDs
|
||||
// .Select(HashUserId)
|
||||
// .ToList();
|
||||
// }
|
||||
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
return StatusCode(ex.Code, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error starting recruit");
|
||||
return StatusCode(500, "Internal server error");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("finish")]
|
||||
public IActionResult FinishRecruit([FromBody] JsonElement json)
|
||||
{
|
||||
try
|
||||
{
|
||||
var record = JsonSerializer.Deserialize<RecruitRecord>(json.GetRawText(), _jsonOptions);
|
||||
if (record == null)
|
||||
{
|
||||
throw new ApiException(400, "Invalid request body");
|
||||
}
|
||||
|
||||
var key = record.RecruitInfo.MechaInfo.IpAddress;
|
||||
|
||||
lock (_recruitsLock)
|
||||
{
|
||||
if (!_recruits.ContainsKey(key))
|
||||
{
|
||||
throw new ApiException(404, "Recruit not found");
|
||||
}
|
||||
|
||||
var existing = _recruits[key];
|
||||
if (existing.Keychip != record.Keychip)
|
||||
{
|
||||
throw new ApiException(403, "Keychip mismatch");
|
||||
}
|
||||
|
||||
_recruits.Remove(key);
|
||||
LogRecruit("FINISH", existing);
|
||||
}
|
||||
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
return StatusCode(ex.Code, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error finishing recruit");
|
||||
return StatusCode(500, "Internal server error");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("list")]
|
||||
public IActionResult GetRecruitList()
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var timeout = 30000; // 30 seconds
|
||||
|
||||
List<RecruitRecord> records;
|
||||
|
||||
lock (_recruitsLock)
|
||||
{
|
||||
// Remove expired records
|
||||
var expiredKeys = _recruits
|
||||
.Where(kvp => now - kvp.Value.Time > timeout)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var key in expiredKeys)
|
||||
{
|
||||
_recruits.Remove(key);
|
||||
}
|
||||
|
||||
records = _recruits.Values.ToList();
|
||||
}
|
||||
|
||||
// Return NDJSON format (newline-delimited JSON)
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var record in records)
|
||||
{
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(record, new System.Text.Json.JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
|
||||
});
|
||||
sb.AppendLine(json);
|
||||
}
|
||||
|
||||
return Content(sb.ToString(), "text/plain");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting recruit list");
|
||||
return StatusCode(500, "Internal server error");
|
||||
}
|
||||
}
|
||||
|
||||
private static string HashUserId(long userId)
|
||||
{
|
||||
var hash = MD5.HashData(Encoding.UTF8.GetBytes(userId.ToString()));
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private void LogRecruit(string action, RecruitRecord record)
|
||||
{
|
||||
try
|
||||
{
|
||||
var line = $"[{DateTimeOffset.UtcNow:yyyy-MM-dd HH:mm:ss}] {action} {record.RecruitInfo.MechaInfo.IpAddress} {record.Keychip} MusicID={record.RecruitInfo.MusicID}";
|
||||
|
||||
lock (_recruitsLock)
|
||||
{
|
||||
System.IO.File.AppendAllText(_logFilePath, line + Environment.NewLine);
|
||||
}
|
||||
|
||||
_logger.LogInformation(line);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error writing to recruit log");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Api/InfoController.cs
Normal file
39
Api/InfoController.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CatLink.Api
|
||||
{
|
||||
public class RelayInfoResponse
|
||||
{
|
||||
[JsonPropertyName("relayHost")]
|
||||
public string RelayHost { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("relayPort")]
|
||||
public int RelayPort { get; set; }
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("info")]
|
||||
public class InfoController : ControllerBase
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public InfoController(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetInfo()
|
||||
{
|
||||
var host = _configuration.GetValue<string>("Host") ?? "localhost";
|
||||
var relayPort = _configuration.GetValue<int>("RelayPort");
|
||||
|
||||
return Ok(new RelayInfoResponse
|
||||
{
|
||||
RelayHost = host,
|
||||
RelayPort = relayPort
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user