mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-06 05:16:26 +08:00
374 lines
17 KiB
C#
374 lines
17 KiB
C#
using Microsoft.AspNetCore.Builder;
|
||
using Microsoft.AspNetCore.Http;
|
||
using SqlSugar;
|
||
using RainOpsMini.Helpers;
|
||
using RainOpsMini.Models;
|
||
using RainOpsMini.Services;
|
||
using RainOpsMini.Models;
|
||
using System.Text.Json;
|
||
using RainOpsMini;
|
||
using RainOpsMini.Helpers.SquadGameLog;
|
||
using RainOpsMini.Plugins;
|
||
|
||
namespace RainOpsMini.Apis
|
||
{
|
||
public static class PlayerApi
|
||
{
|
||
public static void MapPlayerApis(this IEndpointRouteBuilder app)
|
||
{
|
||
// API: 获取玩家列表
|
||
// 用于玩家库页面,支持按名称或 ID 搜索
|
||
app.MapGet("/api/players", (int? page, int? pageSize, string? search, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "logs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
int p = page ?? 1;
|
||
int ps = pageSize ?? 20;
|
||
|
||
try
|
||
{
|
||
using (var db = DbHelper.GetInstance())
|
||
{
|
||
var query = db.Queryable<PlayerProfile>();
|
||
if (!string.IsNullOrEmpty(search))
|
||
{
|
||
query = query.Where(x => x.LastName.Contains(search) || x.SteamID.Contains(search) || x.EOSID.Contains(search));
|
||
}
|
||
|
||
int total = 0;
|
||
var list = query.OrderByDescending(x => x.LastSeen)
|
||
.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: 获取玩家会话历史
|
||
// 查询指定玩家最近 30 天的游戏会话记录
|
||
app.MapGet("/api/players/{steamId}/sessions", (string steamId, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "logs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
try
|
||
{
|
||
using (var db = DbHelper.GetInstance())
|
||
{
|
||
var thirtyDaysAgo = DateTime.Now.AddDays(-30);
|
||
var list = db.Queryable<PlayerSession>()
|
||
.Where(x => x.SteamID == steamId && x.StartTime >= thirtyDaysAgo)
|
||
.OrderByDescending(x => x.StartTime)
|
||
.ToList();
|
||
|
||
return Results.Ok(list);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return Results.BadRequest(new { error = ex.Message });
|
||
}
|
||
});
|
||
|
||
// API: 检测同 IP 在线玩家
|
||
// 找出当前在线玩家中 IP 地址相同的群体
|
||
app.MapGet("/api/players/duplicate-ips", (HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "players") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true")
|
||
return Results.Unauthorized();
|
||
|
||
try
|
||
{
|
||
// 1. 获取在线玩家列表副本
|
||
// 规则 4: Program.RconCache中的数据使用的时候需要完全拷贝一份,不能直接使用引用
|
||
var onlinePlayers = Program.RconCache.GetData().Players;
|
||
|
||
if (onlinePlayers == null || onlinePlayers.Count == 0)
|
||
return Results.Ok(new List<object>());
|
||
|
||
var onlineSteamIds = onlinePlayers
|
||
.Select(p => p.SteamId)
|
||
.Where(s => !string.IsNullOrEmpty(s))
|
||
.Distinct()
|
||
.ToList();
|
||
|
||
if (onlineSteamIds.Count == 0)
|
||
return Results.Ok(new List<object>());
|
||
|
||
using (var db = DbHelper.GetInstance())
|
||
{
|
||
// 2. 查询这些玩家最近一次连接的 IP
|
||
var logs = db.Queryable<Log_NewPlayerConnect>()
|
||
.Where(x => onlineSteamIds.Contains(x.SteamID))
|
||
.OrderBy(x => x.LogTime, OrderByType.Desc)
|
||
.Select(x => new { x.SteamID, x.IPAddress, x.LogTime })
|
||
.ToList();
|
||
|
||
// 构建映射: SteamID -> 最新 IP
|
||
var playerIps = new Dictionary<string, string>();
|
||
foreach (var log in logs)
|
||
{
|
||
if (!playerIps.ContainsKey(log.SteamID) && !string.IsNullOrEmpty(log.IPAddress))
|
||
{
|
||
playerIps[log.SteamID] = log.IPAddress;
|
||
}
|
||
}
|
||
|
||
// 3. 按 IP 分组并过滤出数量大于 1 的组
|
||
var ipGroups = playerIps
|
||
.GroupBy(x => x.Value)
|
||
.Where(g => g.Count() > 1)
|
||
.ToList();
|
||
|
||
if (!ipGroups.Any())
|
||
return Results.Ok(new List<object>());
|
||
|
||
// 4. 构建返回结果
|
||
var result = new List<object>();
|
||
foreach (var group in ipGroups)
|
||
{
|
||
var ip = group.Key;
|
||
var steamIds = group.Select(x => x.Key).ToList();
|
||
|
||
// 查找这些 SteamID 对应的当前在线玩家信息
|
||
var playersInfo = onlinePlayers
|
||
.Where(p => steamIds.Contains(p.SteamId))
|
||
.Select(p => new
|
||
{
|
||
p.Name,
|
||
p.SteamId,
|
||
p.TeamId,
|
||
p.SquadId
|
||
})
|
||
.ToList();
|
||
|
||
result.Add(new
|
||
{
|
||
ip = ip,
|
||
count = playersInfo.Count,
|
||
players = playersInfo
|
||
});
|
||
}
|
||
|
||
return Results.Ok(result);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return Results.BadRequest(new { error = ex.Message });
|
||
}
|
||
});
|
||
|
||
// API: 更新玩家游戏时长
|
||
// 手动设置玩家的累计游戏时间 (分钟)
|
||
app.MapPost("/api/players/{steamId}/gametime", (string steamId, GameTimeUpdateModel model, GameTimeQueryPlugin plugin, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "players.gametime") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
try
|
||
{
|
||
// 更新插件缓存 (插件内部会同步到数据库)
|
||
plugin.SetPlayerGameTime(steamId, model.Hours * 60);
|
||
|
||
return Results.Ok();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return Results.BadRequest(new { error = ex.Message });
|
||
}
|
||
});
|
||
|
||
// API: 刷新玩家游戏时长
|
||
// 强制重新计算玩家的游戏时间
|
||
app.MapPost("/api/players/{steamId}/gametime/refresh", async (string steamId, GameTimeQueryPlugin plugin, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "players.gametime") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
try
|
||
{
|
||
int minutes = await plugin.RefreshPlayerGameTime(steamId);
|
||
return Results.Ok(new { minutes, hours = minutes / 60 });
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return Results.BadRequest(new { error = ex.Message });
|
||
}
|
||
});
|
||
|
||
// API: 获取玩家本局战斗数据
|
||
// 查询指定玩家在当前对局中的击杀和死亡记录
|
||
app.MapGet("/api/player-battle-stats", (string steamId, HttpContext ctx) =>
|
||
{
|
||
if (ctx.User.Identity?.IsAuthenticated != true) return Results.Unauthorized();
|
||
|
||
// 获取当前对局开始时间
|
||
var serverInfo = Program.RconCache.GetData()?.ServerInfo;
|
||
if (serverInfo == null) return Results.Ok(new { kills = new List<object>(), deaths = new List<object>() });
|
||
|
||
int playTimeSeconds = serverInfo.PlayTime_I;
|
||
// 计算匹配开始时间 = 当前时间 - 游戏进行时间 - 缓冲时间(5分钟)
|
||
DateTime matchStartTime = DateTime.Now.AddSeconds(-playTimeSeconds - 300);
|
||
|
||
using (var db = DbHelper.GetInstance())
|
||
{
|
||
// 查询击杀记录
|
||
var kills = db.Queryable<Log_Die>()
|
||
.Where(x => x.AttackerSteamID == steamId && x.LogTime >= matchStartTime)
|
||
.OrderBy(x => x.LogTime, OrderByType.Desc)
|
||
.Select(x => new {
|
||
Time = x.LogTime,
|
||
VictimName = x.VictimName,
|
||
VictimSteamId = x.VictimSteamID,
|
||
Weapon = x.WeaponType,
|
||
IsTK = x.IsTK,
|
||
Damage = x.DamageValue
|
||
})
|
||
.ToList();
|
||
|
||
// 查询死亡记录
|
||
var deaths = db.Queryable<Log_Die>()
|
||
.Where(x => x.VictimSteamID == steamId && x.LogTime >= matchStartTime)
|
||
.OrderBy(x => x.LogTime, OrderByType.Desc)
|
||
.Select(x => new {
|
||
Time = x.LogTime,
|
||
AttackerName = x.AttackerName,
|
||
AttackerSteamId = x.AttackerSteamID,
|
||
Weapon = x.WeaponType,
|
||
IsTK = x.IsTK,
|
||
Damage = x.DamageValue
|
||
})
|
||
.ToList();
|
||
|
||
return Results.Ok(new { kills, deaths });
|
||
}
|
||
});
|
||
|
||
// API: 封禁玩家
|
||
// 将玩家添加到封禁列表并踢出服务器
|
||
app.MapPost("/api/players/ban", async (BanRequest req, BanService banService, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "bans.add") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
string adminName = ctx.User.Identity?.Name ?? "Admin";
|
||
|
||
try
|
||
{
|
||
// 执行封禁逻辑 (添加到数据库/配置文件)
|
||
await banService.BanPlayerAsync(req.SteamId, req.Reason, req.Duration, adminName);
|
||
|
||
// 如果需要踢出玩家
|
||
if (Program.Client != null && Program.Client.IsConnected)
|
||
{
|
||
await Program.Client.SendCommandAsync($"AdminKick \"{req.SteamId}\" \"{req.Reason}\"");
|
||
}
|
||
|
||
return Results.Ok();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return Results.BadRequest(new { error = ex.Message });
|
||
}
|
||
});
|
||
|
||
// API: 踢出玩家
|
||
// 发送 RCON 命令踢出玩家
|
||
app.MapPost("/api/players/kick", async (KickRequest req, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "kick") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
if (Program.Client != null && Program.Client.IsConnected)
|
||
{
|
||
string response = await Program.Client.SendCommandAsync($"AdminKick \"{req.SteamId}\" \"{req.Reason}\"");
|
||
return Results.Ok(new { response });
|
||
}
|
||
return Results.Json(new { error = "RCON not connected" }, statusCode: 503);
|
||
});
|
||
|
||
// API: 获取玩家详细信息
|
||
// 查询单个玩家的完整资料
|
||
app.MapGet("/api/players/{steamId}", (string steamId, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "logs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
try
|
||
{
|
||
using (var db = DbHelper.GetInstance())
|
||
{
|
||
var player = db.Queryable<PlayerProfile>().First(x => x.SteamID == steamId);
|
||
if (player == null) return Results.NotFound();
|
||
return Results.Ok(player);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return Results.BadRequest(new { error = ex.Message });
|
||
}
|
||
});
|
||
|
||
// API: 获取玩家 TK 记录
|
||
// 查询指定玩家的 TeamKill 历史
|
||
app.MapGet("/api/players/{steamId}/teamkill", (string steamId, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "logs") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
try
|
||
{
|
||
using (var db = DbHelper.GetInstance())
|
||
{
|
||
var list = db.Queryable<Log_Die>()
|
||
.Where(x => x.AttackerSteamID == steamId && x.IsTK == true)
|
||
.OrderByDescending(x => x.LogTime)
|
||
.Take(50) // Limit to last 50 TKs
|
||
.ToList();
|
||
|
||
return Results.Ok(list);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return Results.BadRequest(new { error = ex.Message });
|
||
}
|
||
});
|
||
|
||
// API: 警告玩家
|
||
// 发送 RCON 警告消息
|
||
app.MapPost("/api/players/warn", async (KickRequest req, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "players.warn") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
if (string.IsNullOrWhiteSpace(req.SteamId)) return Results.BadRequest("SteamID required");
|
||
|
||
if (Program.Client != null && Program.Client.IsConnected)
|
||
{
|
||
string msg = string.IsNullOrWhiteSpace(req.Reason) ? "Admin Warning" : req.Reason;
|
||
await Program.Client.SendCommandAsync($"AdminWarn \"{req.SteamId}\" \"{msg}\"");
|
||
return Results.Ok(new { message = "Player warned" });
|
||
}
|
||
|
||
return Results.Json(new { error = "RCON not connected" }, statusCode: 503);
|
||
});
|
||
|
||
// API: 处死玩家
|
||
// 发送 RCON 命令处死玩家 (Force Kill)
|
||
app.MapPost("/api/players/kill", async (KickRequest req, HttpContext ctx) =>
|
||
{
|
||
if (!ctx.User.HasClaim("Permission", "players.kill") && !ctx.User.HasClaim("Permission", "all") && ctx.User.FindFirst("IsSuperAdmin")?.Value != "true") return Results.Unauthorized();
|
||
|
||
if (string.IsNullOrWhiteSpace(req.SteamId)) return Results.BadRequest("SteamID required");
|
||
|
||
if (Program.Client != null && Program.Client.IsConnected)
|
||
{
|
||
string reason = string.IsNullOrWhiteSpace(req.Reason) ? "Admin Kill" : req.Reason;
|
||
await Program.Client.SendCommandAsync($"AdminForceKill \"{req.SteamId}\" \"{reason}\"");
|
||
return Results.Ok(new { message = "Player killed" });
|
||
}
|
||
|
||
return Results.Json(new { error = "RCON not connected" }, statusCode: 503);
|
||
});
|
||
}
|
||
}
|
||
}
|