mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-06 13:26:25 +08:00
299 lines
14 KiB
C#
299 lines
14 KiB
C#
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using SqlSugar;
|
|
using RainOpsMini.Helpers;
|
|
using RainOpsMini.Models;
|
|
using RainOpsMini.Services;
|
|
using RainOpsMini.Hubs;
|
|
using RainOpsMini.Plugins;
|
|
using RainOpsMini;
|
|
using RainOpsMini.Models;
|
|
using RainOpsMini.Helpers.RCON;
|
|
using RainOpsMini.Helpers.SquadGameLog;
|
|
|
|
namespace RainOpsMini.Apis
|
|
{
|
|
public static class ServerApi
|
|
{
|
|
public static void MapServerApis(this IEndpointRouteBuilder app)
|
|
{
|
|
// API: 注册 SignalR 集线器
|
|
app.MapHub<ChatHub>("/chatHub");
|
|
app.MapHub<PresenceHub>("/presenceHub");
|
|
|
|
// API: 获取服务器信息
|
|
app.MapGet("/api/server-info", (RconDataCache cache, HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "server.view") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
var data = cache.GetData();
|
|
// Get game times from cache
|
|
var gameTimes = cache.GetGameTimes().ToDictionary(k => k.Key, v => v.Value / 60);
|
|
|
|
// Ensure all online players have game time from DB if missing in cache
|
|
if (data.Players != null)
|
|
{
|
|
var onlineSteamIds = data.Players
|
|
.Where(p => !string.IsNullOrEmpty(p.SteamId) && !gameTimes.ContainsKey(p.SteamId))
|
|
.Select(p => p.SteamId)
|
|
.ToList();
|
|
|
|
if (onlineSteamIds.Any())
|
|
{
|
|
try
|
|
{
|
|
using (var db = DbHelper.GetInstance())
|
|
{
|
|
var profiles = db.Queryable<PlayerProfile>()
|
|
.Where(p => onlineSteamIds.Contains(p.SteamID))
|
|
.Select(p => new { p.SteamID, p.SquadPlaytimeHours })
|
|
.ToList();
|
|
|
|
foreach (var p in profiles)
|
|
{
|
|
if (!gameTimes.ContainsKey(p.SteamID))
|
|
{
|
|
gameTimes[p.SteamID] = p.SquadPlaytimeHours;
|
|
// Update cache to avoid DB hit next time
|
|
cache.UpdateGameTime(p.SteamID, p.SquadPlaytimeHours * 60);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error fetching missing game times: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to get tickets from SquadGameLog if available
|
|
int team1Tickets = 0;
|
|
int team2Tickets = 0;
|
|
// Logic for tickets omitted for now as not requested, but placeholder variables exist
|
|
|
|
return Results.Json(new
|
|
{
|
|
serverInfo = data.ServerInfo,
|
|
squads = data.Squads,
|
|
players = data.Players,
|
|
gameTimes = gameTimes,
|
|
tickets = new { team1 = team1Tickets, team2 = team2Tickets },
|
|
lastUpdated = data.ServerInfo != null ? cache.LastUpdated.ToString("yyyy-MM-dd HH:mm:ss") : null,
|
|
interval = cache.RefreshIntervalSeconds
|
|
});
|
|
});
|
|
|
|
// API: 获取最近的聊天记录
|
|
// 从数据库中查询最近 300 条聊天日志
|
|
app.MapGet("/api/rcon/chat", (HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "chat") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
try
|
|
{
|
|
using (var db = DbHelper.GetInstance())
|
|
{
|
|
var history = db.Queryable<ChatLog>()
|
|
.OrderByDescending(x => x.Timestamp)
|
|
.Take(300)
|
|
.ToList();
|
|
return Results.Json(history);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Fallback to empty list or error
|
|
Console.WriteLine($"Error fetching chat history: {ex.Message}");
|
|
return Results.Json(new List<ChatLog>());
|
|
}
|
|
});
|
|
|
|
// API: 获取分页聊天记录
|
|
// 支持搜索功能,按时间倒序排列
|
|
app.MapGet("/api/rcon/chat/history", (int? page, int? pageSize, string? search, HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "chat") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
int p = page ?? 1;
|
|
int ps = pageSize ?? 50;
|
|
if (ps > 100) ps = 100;
|
|
|
|
try
|
|
{
|
|
using (var db = DbHelper.GetInstance())
|
|
{
|
|
var query = db.Queryable<ChatLog>();
|
|
if (!string.IsNullOrEmpty(search))
|
|
{
|
|
query = query.Where(x => x.Message.Contains(search) || x.Name.Contains(search) || x.SteamId.Contains(search));
|
|
}
|
|
|
|
int total = 0;
|
|
var list = query.OrderByDescending(x => x.Timestamp)
|
|
.ToPageList(p, ps, ref total);
|
|
|
|
return Results.Ok(new { total, page = p, pageSize = ps, items = list });
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Results.BadRequest(new { error = ex.Message });
|
|
}
|
|
});
|
|
|
|
// API: 发送调试聊天消息
|
|
// 仅用于测试 SignalR 推送功能
|
|
app.MapPost("/api/debug/chat", (ChatMessage msg, IHubContext<ChatHub> hub, HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "chat") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
if (string.IsNullOrEmpty(msg.Type)) msg.Type = "ChatAll";
|
|
if (string.IsNullOrEmpty(msg.Name)) msg.Name = "DebugUser";
|
|
if (string.IsNullOrEmpty(msg.Message)) msg.Message = "Test Message";
|
|
msg.Timestamp = DateTime.Now;
|
|
|
|
Program.RconCache.AddChatMessage(msg);
|
|
hub.Clients.All.SendAsync("ReceiveChat", msg);
|
|
return Results.Ok(msg);
|
|
});
|
|
|
|
// API: 执行 RCON 命令
|
|
// 发送任意 RCON 命令到游戏服务器并返回结果
|
|
app.MapPost("/api/command", async (CommandRequest req, HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "rcon") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
if (Program.Client == null || !Program.Client.IsConnected) return Results.Json(new { error = "Not connected" }, statusCode: 503);
|
|
if (string.IsNullOrWhiteSpace(req.Command)) return Results.BadRequest();
|
|
|
|
string response = await Program.Client.SendCommandAsync(req.Command);
|
|
return Results.Text(response);
|
|
});
|
|
|
|
// API: 设置 RCON 刷新间隔
|
|
app.MapPost("/api/rcon/interval", (IntervalRequest req, HttpContext ctx, RconDataCache cache) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "configs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
cache.RefreshIntervalSeconds = req.Seconds;
|
|
return Results.Ok(new { interval = cache.RefreshIntervalSeconds });
|
|
});
|
|
|
|
// API: 获取 RCON 刷新间隔
|
|
app.MapGet("/api/rcon/interval", (HttpContext ctx, RconDataCache cache) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "configs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
return Results.Json(new { interval = cache.RefreshIntervalSeconds });
|
|
});
|
|
|
|
// API: 修改阵营票数
|
|
app.MapPost("/api/rcon/tickets", (TicketRequest req, HttpContext ctx, PluginManager pm) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "configs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
try
|
|
{
|
|
var ticketPlugin = pm.GetPlugin("阵营票数查询") as TicketQueryPlugin;
|
|
if (ticketPlugin == null || !ticketPlugin.IsEnabled) return Results.BadRequest(new { error = "Plugin disabled or not found" });
|
|
|
|
int current = ticketPlugin.SetTickets(req.TeamId, req.Tickets);
|
|
return Results.Ok(new { tickets = current });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Results.BadRequest(new { error = ex.Message });
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// API: 更换地图
|
|
// 发送 RCON 命令切换到指定地图层
|
|
app.MapPost("/api/map/change", async (CommandRequest req, HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "maps") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
if (string.IsNullOrWhiteSpace(req.Command)) return Results.BadRequest();
|
|
|
|
// Security check: ensure command is map related
|
|
if (!req.Command.StartsWith("AdminSetNextLayer", StringComparison.OrdinalIgnoreCase) &&
|
|
!req.Command.StartsWith("AdminChangeLayer", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return Results.BadRequest(new { error = "Invalid map command" });
|
|
}
|
|
|
|
if (Program.Client == null || !Program.Client.IsConnected) return Results.Json(new { error = "Not connected" }, statusCode: 503);
|
|
|
|
string response = await Program.Client.SendCommandAsync(req.Command);
|
|
return Results.Text(response);
|
|
});
|
|
|
|
// 配置文件管理 API
|
|
var configBaseDir = Path.Combine(Directory.GetCurrentDirectory(), "SquadGame", "ServerConfig");
|
|
|
|
// API: 获取配置文件列表
|
|
// 列出 ServerConfig 目录下的所有文件
|
|
app.MapGet("/api/config/files", (HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "configs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
if (!Directory.Exists(configBaseDir))
|
|
{
|
|
return Results.NotFound(new { error = "Config directory not found" });
|
|
}
|
|
|
|
var files = Directory.GetFiles(configBaseDir)
|
|
.Select(Path.GetFileName)
|
|
.Where(f => f.EndsWith(".cfg") || f.EndsWith(".txt") || f.EndsWith(".ini"))
|
|
.ToList();
|
|
|
|
return Results.Ok(files);
|
|
});
|
|
|
|
// API: 读取配置文件内容
|
|
// 读取指定配置文件的文本内容
|
|
app.MapGet("/api/config/read", (string filename, HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "configs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
if (string.IsNullOrEmpty(filename) || filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
|
|
{
|
|
return Results.BadRequest(new { error = "Invalid filename" });
|
|
}
|
|
|
|
string path = Path.Combine(configBaseDir, filename);
|
|
if (!File.Exists(path)) return Results.NotFound();
|
|
|
|
return Results.Text(File.ReadAllText(path));
|
|
});
|
|
|
|
// API: 保存配置文件
|
|
// 将内容写入指定配置文件,包含权限检查
|
|
app.MapPost("/api/config/save", (ConfigSaveRequest req, HttpContext ctx) =>
|
|
{
|
|
if (!ctx.User.HasClaim("Permission", "configs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
|
|
|
if (string.IsNullOrEmpty(req.Filename) || req.Filename.Contains("..") || req.Filename.Contains("/") || req.Filename.Contains("\\"))
|
|
{
|
|
return Results.BadRequest(new { error = "Invalid filename" });
|
|
}
|
|
|
|
string path = Path.Combine(configBaseDir, req.Filename);
|
|
|
|
// Security check: Only allow editing files in the config directory
|
|
if (!path.StartsWith(configBaseDir)) return Results.BadRequest(new { error = "Access denied" });
|
|
|
|
try
|
|
{
|
|
File.WriteAllText(path, req.Content);
|
|
return Results.Ok();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Results.BadRequest(new { error = $"保存配置失败: {ex.Message}" });
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|