Files
squad-rain-ops-mini/Apis/PlayerApi.cs

374 lines
17 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
}
}
}