mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-05 21:15:52 +08:00
945 lines
37 KiB
C#
945 lines
37 KiB
C#
using Microsoft.AspNetCore.Builder;
|
||
using SqlSugar;
|
||
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using Microsoft.Extensions.Caching.Memory;
|
||
using Microsoft.Extensions.Hosting;
|
||
using Microsoft.Extensions.FileProviders;
|
||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||
using Microsoft.AspNetCore.Authentication;
|
||
using System.Security.Claims;
|
||
using System.Reflection;
|
||
using RainOpsMini.Models;
|
||
using System.Text.Json;
|
||
using System.Threading.Tasks;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System;
|
||
using System.IO;
|
||
|
||
using RainOpsMini.Helpers.SquadGameLog;
|
||
using RainOpsMini.Helpers;
|
||
using RainOpsMini.Helpers.RCON;
|
||
using RainOpsMini.Services;
|
||
using RainOpsMini.Plugins;
|
||
using RainOpsMini.Hubs;
|
||
using Microsoft.AspNetCore.SignalR;
|
||
using RainOpsMini.Apis;
|
||
|
||
namespace RainOpsMini
|
||
{
|
||
using RainOpsMini.Models;
|
||
using RainOpsMini;
|
||
|
||
public class Program
|
||
{
|
||
/// <summary>
|
||
/// RCON配置类,用于存储连接信息和Web服务配置
|
||
/// </summary>
|
||
public class RconConfig
|
||
{
|
||
/// <summary>
|
||
/// RCON服务器IP地址
|
||
/// </summary>
|
||
public string Ip { get; set; } = "";
|
||
/// <summary>
|
||
/// RCON端口
|
||
/// </summary>
|
||
public int Port { get; set; }
|
||
/// <summary>
|
||
/// RCON密码
|
||
/// </summary>
|
||
public string Password { get; set; } = "";
|
||
/// <summary>
|
||
/// Web管理界面端口,默认5000
|
||
/// </summary>
|
||
public int WebPort { get; set; } = 5000;
|
||
/// <summary>
|
||
/// Web管理界面用户名
|
||
/// </summary>
|
||
public string WebUser { get; set; } = "";
|
||
/// <summary>
|
||
/// Web管理界面密码
|
||
/// </summary>
|
||
public string WebPassword { get; set; } = "";
|
||
}
|
||
|
||
// 全局配置实例
|
||
public static RconConfig Config = new RconConfig();
|
||
// 配置文件路径
|
||
public static string ConfigPath = "rcon_Config.json";
|
||
// RCON客户端实例
|
||
static RconClient? client;
|
||
// 公开访问的RCON客户端属性
|
||
public static RconClient? Client => client;
|
||
// SignalR Hub上下文,用于发送实时消息
|
||
public static IHubContext<ChatHub>? ChatHubContext { get; set; }
|
||
// 静态缓存实例,在静态上下文和DI容器之间共享
|
||
public static RconDataCache RconCache = new RconDataCache();
|
||
|
||
// 插件监听原始RCON消息的事件
|
||
public static event Action<string>? OnRconMessageReceived;
|
||
// 系统是否已配置标记
|
||
public static bool isConfigured = false;
|
||
|
||
static async Task Main(string[] args)
|
||
{
|
||
// 1. 初始化数据库
|
||
// 确保数据库表结构存在,这是程序运行的基础
|
||
try
|
||
{
|
||
Console.WriteLine("====== STARTING DATABASE INITIALIZATION ======");
|
||
DbHelper.InitDatabase();
|
||
Console.WriteLine("====== DATABASE INITIALIZATION COMPLETED ======");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 数据库初始化失败,打印严重错误并退出程序
|
||
Console.WriteLine($"\n\n[FATAL ERROR] Database initialization failed. The program cannot start.");
|
||
Console.WriteLine($"Error: {ex.Message}");
|
||
Console.WriteLine("Please check 'db_init_error.log' for details.");
|
||
Console.WriteLine("Press any key to exit...");
|
||
Console.ReadKey();
|
||
return;
|
||
}
|
||
|
||
// 2. 启动 Squad 游戏日志监听(后台任务)
|
||
// 负责实时解析游戏日志文件,捕捉击杀、聊天等关键事件
|
||
Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
SquadLogHelper squadLogHelper = new SquadLogHelper();
|
||
|
||
// 确定日志文件路径,当前默认为程序运行目录下的 SquadGame/Saved/Logs/SquadGame.log
|
||
// 建议后续改为可配置路径
|
||
string logpath = Environment.CurrentDirectory + "/SquadGame/Saved/Logs/SquadGame.log";
|
||
squadLogHelper.Run(logpath);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"日志监听启动失败: {ex.Message}");
|
||
}
|
||
});
|
||
|
||
// 3. 确定 Web 服务端口
|
||
// 默认端口 23368
|
||
int webPort = 23368;
|
||
|
||
// 尝试从命令行参数获取端口,优先级最高
|
||
if (args.Length > 0 && int.TryParse(args[0], out int p) && p > 0 && p < 65536)
|
||
{
|
||
webPort = p;
|
||
}
|
||
|
||
// 加载配置文件 rcon_Config.json
|
||
LoadConfig();
|
||
|
||
// 端口优先级逻辑:命令行参数 > 配置文件 > 默认值
|
||
// 如果指定了命令行参数,则覆盖配置中的端口
|
||
if (Config.WebPort == 0) Config.WebPort = webPort;
|
||
else if (args.Length > 0) Config.WebPort = webPort;
|
||
|
||
// 自动检测端口占用,如果被占用则自动+1查找
|
||
Config.WebPort = GetAvailablePort(Config.WebPort);
|
||
|
||
// 4. 初始化 RCON 连接
|
||
// 如果系统已配置(非首次运行),则尝试连接 RCON
|
||
if (isConfigured)
|
||
{
|
||
await InitializeRcon();
|
||
}
|
||
|
||
// 启动后台任务:保持 RCON 连接活跃并自动重连
|
||
_ = KeepAliveAndReconnectTask();
|
||
|
||
// 5. 配置 Web 应用程序构建器
|
||
var builder = WebApplication.CreateBuilder(args);
|
||
// 优化控制台体验:设置日志级别为 Warning,减少常规日志干扰控制台交互
|
||
builder.Logging.SetMinimumLevel(LogLevel.Warning);
|
||
|
||
// 注册 RCON 客户端(注意:这里可能为空或占位符,后续会更新)
|
||
builder.Services.AddSingleton(client ?? new RconClient("", 0, "")); // 占位,避免 DI 报错
|
||
|
||
// 注册缓存和 RCON 轮询服务
|
||
builder.Services.AddMemoryCache();
|
||
builder.Services.AddSingleton(RconCache);
|
||
builder.Services.AddHostedService<RconPollingService>(); // RCON 状态轮询
|
||
builder.Services.AddHostedService<VipExpirationCheckService>(); // VIP 过期检查
|
||
builder.Services.AddHostedService<ServerInfoStorageService>(); // 服务器历史信息存储
|
||
builder.Services.AddHostedService<DatabaseCleanupService>(); // 数据库清理任务
|
||
builder.Services.AddSingleton<OnlineUserService>(); // 在线玩家服务
|
||
builder.Services.AddSingleton<BanService>(); // 封禁管理服务
|
||
builder.Services.AddSingleton<BanSyncState>(); // 封禁同步状态
|
||
builder.Services.AddHostedService<BanSyncService>(); // 封禁同步后台服务
|
||
|
||
// 注册玩家数据维护服务
|
||
builder.Services.AddHostedService<PlayerMaintenanceService>();
|
||
|
||
// 注册 FRP 内网穿透管理服务
|
||
builder.Services.AddSingleton<FrpManager>();
|
||
|
||
// 注册插件系统 (使用扩展方法自动扫描注册)
|
||
builder.Services.AddAllPlugins();
|
||
|
||
// 注册 SignalR (实时通信)
|
||
builder.Services.AddSignalR();
|
||
|
||
// 配置 Cookie 认证服务
|
||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||
.AddCookie(options =>
|
||
{
|
||
options.LoginPath = "/login.html";
|
||
options.ExpireTimeSpan = TimeSpan.FromDays(1);
|
||
});
|
||
builder.Services.AddAuthorization();
|
||
|
||
// 配置 JSON 序列化选项 (忽略大小写)
|
||
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
|
||
{
|
||
options.SerializerOptions.PropertyNameCaseInsensitive = true;
|
||
});
|
||
|
||
// 6. 构建并配置 Web 应用管道
|
||
var app = builder.Build();
|
||
|
||
// 获取 ChatHub 上下文,供非 Hub 类使用
|
||
ChatHubContext = app.Services.GetRequiredService<IHubContext<ChatHub>>();
|
||
|
||
// 开启认证中间件
|
||
app.UseAuthentication();
|
||
app.UseAuthorization();
|
||
|
||
|
||
|
||
// 7. 配置静态文件服务
|
||
// 优先使用物理目录下的 wwwroot(便于开发调试),如果不存在则使用嵌入式资源
|
||
IFileProvider fileProvider = new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly(), "wwwroot");
|
||
string physicalWwwroot = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
|
||
if (Directory.Exists(physicalWwwroot))
|
||
{
|
||
fileProvider = new CompositeFileProvider(
|
||
new PhysicalFileProvider(physicalWwwroot),
|
||
fileProvider
|
||
);
|
||
}
|
||
|
||
// 启用默认文件映射(如 index.html)
|
||
app.UseDefaultFiles(new DefaultFilesOptions
|
||
{
|
||
FileProvider = fileProvider
|
||
});
|
||
|
||
// 8. 自定义请求拦截中间件
|
||
// 处理系统初始化检查、页面重定向和全局权限验证
|
||
app.Use(async (context, next) =>
|
||
{
|
||
var path = context.Request.Path.Value?.ToLower();
|
||
|
||
// 场景A: 系统未初始化
|
||
// 强制重定向到安装向导 (setup.html),除非正在访问安装相关接口
|
||
if (!isConfigured)
|
||
{
|
||
if (path == "/setup.html" || path == "/api/setup" || path == "/api/setup/detect-Config")
|
||
{
|
||
await next();
|
||
return;
|
||
}
|
||
context.Response.Redirect("/setup.html");
|
||
return;
|
||
}
|
||
|
||
// 场景B: 系统已初始化
|
||
// 禁止再次访问安装向导,重定向回首页
|
||
if (path == "/setup.html" || path == "/api/setup" || path == "/api/setup/detect-Config")
|
||
{
|
||
context.Response.Redirect("/");
|
||
return;
|
||
}
|
||
|
||
// 场景C: 公开资源访问
|
||
// 放行登录页面、登录接口和验证码接口
|
||
if (path == "/login.html" || path == "/api/login" || path == "/api/captcha")
|
||
{
|
||
await next();
|
||
return;
|
||
}
|
||
|
||
// 场景D: 需要认证的资源
|
||
// 检查用户是否已登录
|
||
if (!context.User.Identity?.IsAuthenticated ?? true)
|
||
{
|
||
// 特殊处理 SignalR 连接 (chatHub)
|
||
// 允许协商请求通过,SignalR 内部会处理或依赖 Cookie
|
||
if (path?.StartsWith("/chatHub") == true || path?.StartsWith("/presenceHub") == true)
|
||
{
|
||
await next();
|
||
return;
|
||
}
|
||
|
||
// 如果是 API 请求,返回 401 未授权状态码
|
||
if (path?.StartsWith("/api/") == true)
|
||
{
|
||
context.Response.StatusCode = 401;
|
||
return;
|
||
}
|
||
|
||
// 如果是普通页面访问,重定向到登录页
|
||
context.Response.Redirect("/login.html");
|
||
return;
|
||
}
|
||
|
||
// 已认证,放行请求
|
||
await next();
|
||
});
|
||
|
||
// 启用静态文件中间件
|
||
app.UseStaticFiles(new StaticFileOptions
|
||
{
|
||
FileProvider = fileProvider
|
||
});
|
||
|
||
// API 路由
|
||
|
||
// Register Modular APIs
|
||
app.MapSystemApis();
|
||
app.MapAuthApis();
|
||
app.MapUserApis();
|
||
app.MapVipApis();
|
||
app.MapPlayerApis();
|
||
app.MapServerApis();
|
||
app.MapLogApis();
|
||
app.MapFrpApis();
|
||
app.MapBanApis();
|
||
|
||
// Ensure PluginManager is initialized and Configs are loaded
|
||
try
|
||
{
|
||
app.Services.GetRequiredService<PluginManager>();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[Warning] PluginManager initialization failed: {ex.Message}");
|
||
}
|
||
|
||
Console.WriteLine($"Web 服务启动中: http://localhost:{Config.WebPort}");
|
||
|
||
|
||
|
||
// FRP no longer auto-starts at application startup.
|
||
// Use console command "frp start" or /api/frp/start when needed.
|
||
|
||
// 9. 启动 Web 服务
|
||
// 使用异步非阻塞方式启动,以便主线程可以继续运行控制台交互循环
|
||
var webTask = app.RunAsync($"http://*:{Config.WebPort}");
|
||
|
||
// 10. 启动控制台交互循环
|
||
// 允许用户在控制台直接输入命令进行管理
|
||
await RunConsoleLoop(app);
|
||
|
||
// 当控制台退出时,优雅停止 Web 服务
|
||
await app.StopAsync();
|
||
await webTask;
|
||
}
|
||
|
||
// 控制台交互主循环
|
||
// 允许用户在控制台直接输入命令进行管理
|
||
static async Task RunConsoleLoop(WebApplication app)
|
||
{
|
||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||
Console.WriteLine("--------------------------------------------------");
|
||
Console.WriteLine($" RainOPS Mini 控制台已就绪 v{version}");
|
||
Console.WriteLine(" 输入 'help' 查看可用命令");
|
||
Console.WriteLine("--------------------------------------------------");
|
||
|
||
while (true)
|
||
{
|
||
string statusIndicator = (client != null && client.IsConnected) ? "Online" : "Offline";
|
||
Console.Write($"RainOPS [{statusIndicator}]> ");
|
||
string? input = Console.ReadLine();
|
||
|
||
if (string.IsNullOrWhiteSpace(input)) continue;
|
||
|
||
string cmd = input.Trim();
|
||
|
||
switch (cmd.ToLower())
|
||
{
|
||
case "help":
|
||
PrintHelp();
|
||
break;
|
||
case "1":
|
||
case "Config":
|
||
ShowConfig();
|
||
break;
|
||
case "2":
|
||
case "setrcon":
|
||
await UpdateRconConfig();
|
||
break;
|
||
case "3":
|
||
case "setwebport":
|
||
UpdateWebPort();
|
||
break;
|
||
case "4":
|
||
case "showlogin":
|
||
ShowLoginInfo();
|
||
break;
|
||
case "5":
|
||
case "resetlogin":
|
||
ResetLoginInfo();
|
||
break;
|
||
case "setinterval":
|
||
Console.WriteLine("请输入刷新间隔 (10-60 秒):");
|
||
string? intStr = Console.ReadLine();
|
||
if (int.TryParse(intStr, out int interval))
|
||
{
|
||
var cache = app.Services.GetService<RconDataCache>();
|
||
if (cache != null)
|
||
{
|
||
cache.RefreshIntervalSeconds = interval;
|
||
Console.WriteLine($"刷新频率已设置为 {cache.RefreshIntervalSeconds} 秒");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("输入无效");
|
||
}
|
||
break;
|
||
case "status":
|
||
ShowStatus(app);
|
||
break;
|
||
case "frp":
|
||
Console.WriteLine("用法: frp start | frp stop | frp restart");
|
||
break;
|
||
case "frp start":
|
||
var frpStart = app.Services.GetService<FrpManager>();
|
||
if (frpStart != null)
|
||
{
|
||
try {
|
||
frpStart.Start();
|
||
Console.WriteLine("FRP 任务已启动,日志将显示在下方。");
|
||
} catch (Exception ex) {
|
||
Console.WriteLine($"启动失败: {ex.Message}");
|
||
}
|
||
}
|
||
break;
|
||
case "frp stop":
|
||
var frpStop = app.Services.GetService<FrpManager>();
|
||
if (frpStop != null)
|
||
{
|
||
frpStop.Stop();
|
||
Console.WriteLine("FRP 任务已停止。");
|
||
}
|
||
break;
|
||
case "frp restart":
|
||
var frpRestart = app.Services.GetService<FrpManager>();
|
||
if (frpRestart != null)
|
||
{
|
||
try {
|
||
frpRestart.Restart();
|
||
Console.WriteLine("FRP 任务已重启。");
|
||
} catch (Exception ex) {
|
||
Console.WriteLine($"重启失败: {ex.Message}");
|
||
}
|
||
}
|
||
break;
|
||
case "connect":
|
||
await ManualConnect();
|
||
break;
|
||
case "exit":
|
||
case "quit":
|
||
Console.WriteLine("正在退出程序...");
|
||
return;
|
||
default:
|
||
// 尝试作为 RCON 命令发送到游戏服务器
|
||
if (client != null && client.IsConnected)
|
||
{
|
||
try
|
||
{
|
||
// Console.WriteLine($"[发送] {cmd}");
|
||
string response = await client.SendCommandAsync(cmd);
|
||
if (!string.IsNullOrWhiteSpace(response))
|
||
{
|
||
Console.WriteLine(response);
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("(无返回数据)");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[错误] 命令执行失败: {ex.Message}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("未知命令,且 RCON 未连接。输入 'help' 查看帮助。");
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 显示系统状态
|
||
// 打印 RCON 连接、Web 端口、FRP 状态等信息
|
||
static void ShowStatus(WebApplication app)
|
||
{
|
||
Console.WriteLine("------------- 系统状态 -------------");
|
||
Console.WriteLine($" RCON 连接状态 : {(client?.IsConnected == true ? "已连接" : "未连接")}");
|
||
if (client?.IsConnected == true)
|
||
{
|
||
Console.WriteLine($" RCON 目标 : {Config.Ip}:{Config.Port}");
|
||
}
|
||
Console.WriteLine($" Web 服务端口 : {Config.WebPort}");
|
||
|
||
var frp = app.Services.GetService<FrpManager>();
|
||
if (frp != null)
|
||
{
|
||
Console.WriteLine($" FRP 运行状态 : {(frp.IsRunning ? "运行中" : "未运行")}");
|
||
}
|
||
Console.WriteLine("------------------------------------");
|
||
}
|
||
|
||
// 手动连接 RCON
|
||
// 尝试根据当前配置重新连接游戏服务器
|
||
static async Task ManualConnect()
|
||
{
|
||
Console.WriteLine("正在尝试连接 RCON...");
|
||
if (!isConfigured) LoadConfig();
|
||
|
||
if (string.IsNullOrEmpty(Config.Ip))
|
||
{
|
||
Console.WriteLine("错误: 未配置 RCON IP。请先使用 'Config' 或 'setrcon' 进行配置。");
|
||
return;
|
||
}
|
||
|
||
await InitializeRcon();
|
||
}
|
||
|
||
// 打印帮助信息
|
||
// 显示控制台支持的所有命令
|
||
static void PrintHelp()
|
||
{
|
||
Console.WriteLine("================ 功能菜单 ================");
|
||
Console.WriteLine(" [直接输入 RCON 命令即可发送到服务器]");
|
||
Console.WriteLine(" status - 查看连接状态");
|
||
Console.WriteLine(" connect - 手动尝试连接 RCON");
|
||
Console.WriteLine(" 1. 查看配置文件全部信息 (或输入 Config)");
|
||
Console.WriteLine(" - 显示服务器IP、端口、RCON密码、登录名、登录密码等");
|
||
Console.WriteLine(" 2. 修改 RCON 信息 (或输入 setrcon)");
|
||
Console.WriteLine(" 3. 修改 WEB 端口 (或输入 setwebport)");
|
||
Console.WriteLine(" 4. 查看登录名和密码 (或输入 showlogin)");
|
||
Console.WriteLine(" 5. 重置登录名和密码 (或输入 resetlogin)");
|
||
Console.WriteLine(" setinterval <seconds> - 设置 RCON 数据刷新频率 (10-60秒)");
|
||
Console.WriteLine(" frp start - 启动 FRP 任务");
|
||
Console.WriteLine(" frp stop - 停止 FRP 任务");
|
||
Console.WriteLine(" frp restart - 重启 FRP 任务");
|
||
Console.WriteLine(" help - 显示此帮助信息");
|
||
Console.WriteLine(" exit - 退出程序");
|
||
Console.WriteLine("==========================================");
|
||
}
|
||
|
||
// 显示配置信息
|
||
// 打印当前加载的所有配置项
|
||
static void ShowConfig()
|
||
{
|
||
Console.WriteLine("------------- 当前配置信息 -------------");
|
||
Console.WriteLine($" RCON 服务器IP : {Config.Ip}");
|
||
Console.WriteLine($" RCON 端口 : {Config.Port}");
|
||
Console.WriteLine($" RCON 密码 : {Config.Password}");
|
||
Console.WriteLine($" WEB 端口 : {Config.WebPort}");
|
||
Console.WriteLine($" WEB 登录名 : {Config.WebUser}");
|
||
Console.WriteLine($" WEB 登录密码 : {Config.WebPassword}");
|
||
Console.WriteLine("----------------------------------------");
|
||
}
|
||
|
||
// 显示登录凭据
|
||
// 打印当前的 Web 登录用户名和密码
|
||
static void ShowLoginInfo()
|
||
{
|
||
Console.WriteLine("------------- 登录凭据 -------------");
|
||
Console.WriteLine($" 登录名 : {Config.WebUser}");
|
||
Console.WriteLine($" 登录密码 : {Config.WebPassword}");
|
||
Console.WriteLine("------------------------------------");
|
||
}
|
||
|
||
// 修改 RCON 配置
|
||
// 交互式修改 IP、端口和密码
|
||
static async Task UpdateRconConfig()
|
||
{
|
||
Console.WriteLine(">>> 修改 RCON 配置 (直接回车保持原值) <<<");
|
||
|
||
Console.Write($"输入新IP [{Config.Ip}]: ");
|
||
string? ip = Console.ReadLine();
|
||
if (!string.IsNullOrWhiteSpace(ip)) Config.Ip = ip.Trim();
|
||
|
||
Console.Write($"输入新端口 [{Config.Port}]: ");
|
||
string? portStr = Console.ReadLine();
|
||
if (!string.IsNullOrWhiteSpace(portStr) && int.TryParse(portStr, out int p)) Config.Port = p;
|
||
|
||
Console.Write($"输入新密码 [{Config.Password}]: ");
|
||
string? pass = Console.ReadLine();
|
||
if (!string.IsNullOrWhiteSpace(pass)) Config.Password = pass.Trim();
|
||
|
||
SaveConfigToFile();
|
||
Console.WriteLine("配置已保存。正在重新初始化 RCON 连接...");
|
||
|
||
isConfigured = true;
|
||
await InitializeRcon();
|
||
}
|
||
|
||
// 修改 Web 端口
|
||
// 修改 Web 服务监听端口(需重启生效)
|
||
static void UpdateWebPort()
|
||
{
|
||
Console.WriteLine(">>> 修改 WEB 端口 <<<");
|
||
Console.Write($"输入新端口 [{Config.WebPort}]: ");
|
||
string? portStr = Console.ReadLine();
|
||
if (!string.IsNullOrWhiteSpace(portStr) && int.TryParse(portStr, out int p) && p > 0 && p < 65536)
|
||
{
|
||
Config.WebPort = p;
|
||
SaveConfigToFile();
|
||
Console.WriteLine($"端口已修改为 {p}。");
|
||
Console.WriteLine("注意:Web 端口修改需要重启程序才能生效!");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("端口无效或未修改。");
|
||
}
|
||
}
|
||
|
||
// 重置 Web 登录信息
|
||
// 修改或生成新的 Web 登录凭据
|
||
static void ResetLoginInfo()
|
||
{
|
||
Console.WriteLine(">>> 重置 Web 登录信息 <<<");
|
||
|
||
Console.Write($"输入新用户名 (回车保持 '{Config.WebUser}'): ");
|
||
string? user = Console.ReadLine();
|
||
if (!string.IsNullOrWhiteSpace(user)) Config.WebUser = user.Trim();
|
||
|
||
Console.Write("输入新密码 (回车自动生成随机密码): ");
|
||
string? pass = Console.ReadLine();
|
||
if (string.IsNullOrWhiteSpace(pass))
|
||
{
|
||
pass = GenerateRandomPassword();
|
||
Console.WriteLine($"已生成随机密码: {pass}");
|
||
}
|
||
else
|
||
{
|
||
if (!ValidatePassword(pass))
|
||
{
|
||
Console.WriteLine("密码强度不足(需至少包含字母、数字、符号中的两种,且长度>=6)。是否强制使用?(y/n)");
|
||
string? confirm = Console.ReadLine();
|
||
if (confirm?.Trim().ToLower() != "y")
|
||
{
|
||
Console.WriteLine("取消修改。");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
Config.WebPassword = pass;
|
||
SaveConfigToFile();
|
||
Console.WriteLine("登录信息已更新并保存。");
|
||
}
|
||
|
||
// 保存配置到文件
|
||
// 将内存中的 Config 对象序列化并写入 JSON 文件
|
||
public static void SaveConfigToFile()
|
||
{
|
||
try
|
||
{
|
||
string json = JsonSerializer.Serialize(Config, new JsonSerializerOptions { WriteIndented = true });
|
||
File.WriteAllText(ConfigPath, json);
|
||
Console.WriteLine("[系统] 配置文件已更新。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[错误] 保存配置文件失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 加载配置文件
|
||
// 从 JSON 文件读取配置,如果不存在则提示
|
||
static void LoadConfig()
|
||
{
|
||
if (!File.Exists(ConfigPath))
|
||
{
|
||
Console.WriteLine("未检测到配置文件,请通过 Web 页面进行初始化配置,或在控制台使用命令配置。");
|
||
isConfigured = false;
|
||
}
|
||
else
|
||
{
|
||
try
|
||
{
|
||
string json = File.ReadAllText(ConfigPath);
|
||
var loadedConfig = JsonSerializer.Deserialize<RconConfig>(json);
|
||
if (loadedConfig != null)
|
||
{
|
||
Config = loadedConfig;
|
||
isConfigured = true;
|
||
Console.WriteLine($"已加载配置: RCON IP={Config.Ip}, Port={Config.Port}");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("配置文件格式错误,需重新配置。");
|
||
isConfigured = false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"读取配置文件出错: {ex.Message}");
|
||
isConfigured = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 初始化 RCON 连接
|
||
// 建立连接并设置事件监听器
|
||
public static async Task InitializeRcon()
|
||
{
|
||
if (!string.IsNullOrEmpty(Config.Ip) && Config.Port > 0)
|
||
{
|
||
// 如果已有连接,尝试断开并清理资源
|
||
client?.Dispose();
|
||
client = new RconClient(Config.Ip, Config.Port, Config.Password);
|
||
|
||
// 监听 RCON 服务端主动推送的消息 (如聊天、击杀日志)
|
||
client.OnMessageReceived += (msg) =>
|
||
{
|
||
// 1. 通知插件系统
|
||
try
|
||
{
|
||
OnRconMessageReceived?.Invoke(msg);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[Error] Plugin RCON message handler failed: {ex.Message}");
|
||
}
|
||
|
||
// 2. 解析聊天消息
|
||
var chatMsg = RconParser.ParseChatMessage(msg);
|
||
if (chatMsg != null)
|
||
{
|
||
// 尝试从缓存中补充玩家的队伍和分队信息
|
||
var player = RconCache.GetPlayer(chatMsg.SteamId, chatMsg.EosId);
|
||
if (player != null)
|
||
{
|
||
chatMsg.TeamId = player.TeamId;
|
||
chatMsg.SquadId = player.SquadId;
|
||
}
|
||
|
||
// 添加到内存缓存
|
||
RconCache.AddChatMessage(chatMsg);
|
||
|
||
// 3. 保存到数据库 (SQLite)
|
||
try
|
||
{
|
||
var chatLog = new ChatLog
|
||
{
|
||
Type = chatMsg.Type,
|
||
EosId = chatMsg.EosId,
|
||
SteamId = chatMsg.SteamId,
|
||
Name = chatMsg.Name,
|
||
Message = chatMsg.Message,
|
||
Timestamp = chatMsg.Timestamp,
|
||
TeamId = chatMsg.TeamId,
|
||
SquadId = chatMsg.SquadId
|
||
};
|
||
using (var db = DbHelper.GetInstance())
|
||
{
|
||
db.Insertable(chatLog).ExecuteCommand();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[Error] Failed to save chat to DB: {ex.Message}");
|
||
}
|
||
|
||
Console.WriteLine($"[{chatMsg.Type}] {chatMsg.Name}: {chatMsg.Message}");
|
||
|
||
// 4. 通过 SignalR 广播给前端
|
||
if (ChatHubContext != null)
|
||
{
|
||
_ = ChatHubContext.Clients.All.SendAsync("ReceiveChat", chatMsg);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 非聊天消息,直接打印
|
||
Console.WriteLine($"[RCON Push] {msg}");
|
||
}
|
||
};
|
||
|
||
//client.OnLog += (log) => Console.WriteLine($"[RCON Log] {log}");
|
||
|
||
try
|
||
{
|
||
await client.ConnectAsync();
|
||
Console.WriteLine($"[系统] RCON 客户端已连接 ({Config.Ip}:{Config.Port})");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"[警告] RCON 连接失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
static int GetAvailablePort(int startPort)
|
||
{
|
||
int port = startPort;
|
||
while (IsPortInUse(port))
|
||
{
|
||
Console.WriteLine($"[系统] 端口 {port} 已被占用,尝试使用 {port + 10}...");
|
||
port += 10;
|
||
if (port > 65535)
|
||
{
|
||
throw new Exception("无法找到可用端口");
|
||
}
|
||
}
|
||
if (port != startPort)
|
||
{
|
||
Console.WriteLine($"[系统] 最终使用端口: {port}");
|
||
}
|
||
return port;
|
||
}
|
||
|
||
static bool IsPortInUse(int port)
|
||
{
|
||
bool inUse = false;
|
||
System.Net.NetworkInformation.IPGlobalProperties ipProperties = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties();
|
||
System.Net.IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
|
||
foreach (System.Net.IPEndPoint endPoint in ipEndPoints)
|
||
{
|
||
if (endPoint.Port == port)
|
||
{
|
||
inUse = true;
|
||
break;
|
||
}
|
||
}
|
||
return inUse;
|
||
}
|
||
|
||
static async Task KeepAliveAndReconnectTask()
|
||
{
|
||
while (true)
|
||
{
|
||
if (client != null && !client.IsConnected)
|
||
{
|
||
// Console.WriteLine("[系统] 检测到 RCON 未连接,正在尝试连接...");
|
||
try
|
||
{
|
||
await client.ConnectAsync();
|
||
// Console.WriteLine("[系统] RCON 重连成功");
|
||
}
|
||
catch
|
||
{
|
||
// 静默失败,等待下次重试
|
||
}
|
||
}
|
||
|
||
await Task.Delay(5000);
|
||
}
|
||
}
|
||
|
||
public static bool ValidatePassword(string pwd)
|
||
{
|
||
if (string.IsNullOrEmpty(pwd) || pwd.Length < 6) return false;
|
||
|
||
int types = 0;
|
||
if (pwd.Any(char.IsDigit)) types++;
|
||
if (pwd.Any(char.IsLetter)) types++;
|
||
if (pwd.Any(c => !char.IsLetterOrDigit(c))) types++;
|
||
|
||
return types >= 2;
|
||
}
|
||
|
||
public static string GenerateRandomPassword()
|
||
{
|
||
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||
const string digits = "0123456789";
|
||
const string symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||
|
||
var random = new Random();
|
||
var password = new char[12];
|
||
|
||
// 确保至少包含各类字符以满足要求
|
||
password[0] = chars[random.Next(chars.Length)];
|
||
password[1] = digits[random.Next(digits.Length)];
|
||
password[2] = symbols[random.Next(symbols.Length)];
|
||
|
||
string allChars = chars + digits + symbols;
|
||
for (int i = 3; i < 12; i++)
|
||
{
|
||
password[i] = allChars[random.Next(allChars.Length)];
|
||
}
|
||
|
||
// 打乱顺序
|
||
return new string(password.OrderBy(x => random.Next()).ToArray());
|
||
}
|
||
}
|
||
|
||
public class CommandRequest
|
||
{
|
||
public string Command { get; set; } = string.Empty;
|
||
}
|
||
|
||
public class LoginRequest
|
||
{
|
||
public string Username { get; set; } = string.Empty;
|
||
public string Password { get; set; } = string.Empty;
|
||
public string CaptchaId { get; set; } = string.Empty;
|
||
public string CaptchaCode { get; set; } = string.Empty;
|
||
}
|
||
|
||
public class SetupRequest
|
||
{
|
||
public string Ip { get; set; } = "";
|
||
public int Port { get; set; }
|
||
public string Password { get; set; } = "";
|
||
public string WebUser { get; set; } = "";
|
||
public string WebPassword { get; set; } = "";
|
||
}
|
||
|
||
public class ConfigSaveRequest
|
||
{
|
||
public string Filename { get; set; } = "";
|
||
public string Content { get; set; } = "";
|
||
}
|
||
|
||
public class PasswordResetRequest
|
||
{
|
||
public string NewPassword { get; set; } = string.Empty;
|
||
}
|
||
|
||
public class BanRequest
|
||
{
|
||
public string SteamId { get; set; } = "";
|
||
public string Reason { get; set; } = "";
|
||
public string Duration { get; set; } = "perm";
|
||
}
|
||
|
||
public class KickRequest
|
||
{
|
||
public string SteamId { get; set; } = "";
|
||
public string Reason { get; set; } = "";
|
||
}
|
||
|
||
public class IntervalRequest
|
||
{
|
||
public int Seconds { get; set; }
|
||
}
|
||
|
||
public class TicketRequest
|
||
{
|
||
public int TeamId { get; set; }
|
||
public int Tickets { get; set; }
|
||
}
|
||
|
||
public class GameTimeUpdateModel
|
||
{
|
||
public int Hours { get; set; }
|
||
}
|
||
}
|
||
|