using IES.ExamServer.Helpers; using Microsoft.Extensions.Caching.Memory; using System.Diagnostics; using System.Net.NetworkInformation; using System.Net; using System.Runtime.InteropServices; using System.Text.Json.Nodes; using System.Text.RegularExpressions; using IES.ExamServer.Models; using System.Text; using IES.ExamServer.DI; using IES.ExamServer.Helper; using System; using System.Security.Principal; namespace IES.ExamServer.Services { public static class IndexService { /// /// 修改IP域名映射,以及处理证书是否安装的问题。 /// /// /// /// /// /// public static async Task<(int code, int code_cer ,int code_hosts,int code_zip, string msg)> ModifyHosts(string? ip,IMemoryCache _memoryCache,LiteDBFactory _liteDBFactory,CenterServiceConnectionService connectionService) { (string? hostsIp,string hostsMsg) = SystemScriptHelper.FindIpAddressForDomain("exam.habook.local"); int code = 0, code_cer = 0,code_hosts=0,code_zip=0 ; StringBuilder sb = new StringBuilder(); try { string batscriptPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "batscript"); if (!Directory.Exists(batscriptPath)) { Directory.CreateDirectory(batscriptPath); } string pathCerNew = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "batscript", "certificate.cer"); string pathBatNew = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "batscript", "install_certificate.bat"); if (!System.IO.File.Exists(pathCerNew) || !System.IO.File.Exists(pathBatNew)) { string pathCer = Path.Combine(Directory.GetCurrentDirectory(), "Configs", "cer", "certificate.cer"); System.IO.File.Copy(pathCer, pathCerNew); string pathBat = Path.Combine(Directory.GetCurrentDirectory(), "Configs", "cer", "install_certificate.bat"); System.IO.File.Copy(pathBat, pathBatNew); } var needInstall = SystemScriptHelper.CheckCertificate(pathCerNew); if (!needInstall) { if (SystemScriptHelper.IsAdministrator()) { var res = ProcessHelper.ExecuteProcess(pathBatNew); sb.Append(res.msg); code_cer = res.code; } else { code_cer = 401; sb.Append("请使用管理员身份运行本程序,如果已经安装过脚本请忽略!"); } } else { code_cer = 200; } //获取主站配置信息。 ServerDevice serverDevice = _memoryCache.Get(Constant._KeyServerDevice); var primaryNetworks= _liteDBFactory.GetLiteDatabase().GetCollection().FindAll().ToList(); Network? primaryNetwork = null; //传入的ip为不为空,切换 if (!string.IsNullOrWhiteSpace(ip) ) { if (serverDevice != null && serverDevice.networks.IsNotEmpty()) { primaryNetwork = serverDevice.networks.FindAll(x => ip.Equals(x.ip))?.FirstOrDefault(); } } else { if (serverDevice != null && serverDevice.networks.IsNotEmpty()) { //传入的ip为空,则尝试加载 已经配置过的主站域名ip if (primaryNetworks.IsNotEmpty()) { foreach (var network in primaryNetworks) { var exists = serverDevice.networks.FindAll(x => x.id!.Equals(network.id)); if (exists.IsNotEmpty()) { if (primaryNetwork == null) { primaryNetwork = network; } } } } //没有找到主站设置信息,则尝试赋值物理网卡配置信息 if (primaryNetwork == null) { primaryNetwork = serverDevice.networks.FirstOrDefault();//第一个是物理网卡 } } } if (primaryNetwork != null) { string pathBatHosts = Path.Combine(Directory.GetCurrentDirectory(), "Configs", "cer", "modify_hosts.bat"); string text = await System.IO.File.ReadAllTextAsync(pathBatHosts); // 使用正则表达式替换 IP 地址 string pattern = @"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"; string result = Regex.Replace(text, pattern, primaryNetwork.ip!); string pathBatHostsNew = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "batscript", "modify_hosts.bat"); await System.IO.File.WriteAllTextAsync(pathBatHostsNew, result); string pathBatStudent = Path.Combine(Directory.GetCurrentDirectory(), "Configs", "cer", "student_manual.bat"); string textStudent = await System.IO.File.ReadAllTextAsync(pathBatStudent); // 使用正则表达式替换 IP 地址 string resultStudent = Regex.Replace(textStudent, pattern, primaryNetwork.ip!); string pathBatStudentNew = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "batscript", "student_manual.bat"); await System.IO.File.WriteAllTextAsync(pathBatStudentNew, resultStudent); if (string.IsNullOrWhiteSpace(hostsIp) || !hostsIp.Equals(primaryNetwork.ip)) { if (SystemScriptHelper.IsAdministrator()) { var resHosts = ProcessHelper.ExecuteProcess(pathBatHostsNew); sb.Append(resHosts.msg); code_hosts = resHosts.code; } else { code_hosts = 401; sb.Append("请使用管理员身份执行本程序!"); } } else { code_hosts = 200; sb.Append("IP域名映射已存在,无需再次映射!"); } string scriptPath= Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "script"); if (!Directory.Exists(scriptPath)) { Directory.CreateDirectory(scriptPath); } string batscriptZipPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "script", "student_script.zip"); var res = ZipHelper.CreateZip(batscriptPath, batscriptZipPath); if (res.res) { code_zip = 200; sb.Append(res.msg); } else { code_zip = 400; sb.Append("脚本文件创建异常!"); } serverDevice!.networks.ForEach(x => { x.primary = 0; x.batscriptZip = null; if (x.id!.Equals(primaryNetwork.id)) { x.primary = code_hosts ==200? 1:0; x.batscriptZip = res.res? "script/student_script.zip" : null; } }); //更新设备的主站设备信息 connectionService.serverDevice = serverDevice; _memoryCache.Set(Constant._KeyServerDevice, serverDevice); _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(serverDevice); //清理后再保存,保证只有一条主站数据。 _liteDBFactory.GetLiteDatabase().GetCollection().DeleteAll(); _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(primaryNetwork); //所有执行成功 code = 200; sb.Append("证书安装成功,域名IP绑定成功,脚本文件创建成功!"); } } catch (Exception ex) { code = 500; //_logger.LogError($"域名IP绑定错误。{ex.Message},{ex.StackTrace}"); return (500,code_cer,code_hosts,code_zip, $"域名IP绑定错误,{ex.Message},{ex.StackTrace}"); } return (code, code_cer, code_hosts, code_zip, sb.ToString()); } public static async Task<(string content_response, HttpStatusCode code)> GetMusicServer_thirdLocalCacheConfig(IHttpClientFactory httpClientFactory, CenterServiceConnectionService _connectionService , string requestBody) { string content_response = string.Empty; HttpStatusCode code = HttpStatusCode.OK ; try { string url = _connectionService!.musicUrl!; var client = httpClientFactory.CreateClient(); //client.DefaultRequestHeaders.Add("Accept", "application/json, text/plain, */*"); client.DefaultRequestHeaders.Add("appId", Constant._MusicAIServerAppId); client.DefaultRequestHeaders.Add("encrypt", "1"); client.DefaultRequestHeaders.Add("schoolCode", _connectionService.serverDevice?.school?.id); var content = new StringContent(requestBody, Encoding.UTF8, "application/json"); // 发送 PUT 请求 HttpResponseMessage response = await client.PutAsync($"{url}/musicLocalCache/thirdLocalCacheConfig", content); // 处理响应 if (response.IsSuccessStatusCode) { content_response = await response.Content.ReadAsStringAsync(); //Console.WriteLine("Response: " + responseData); } else { // Console.WriteLine("Error: " + response.StatusCode); code=response.StatusCode; content_response = await response.Content.ReadAsStringAsync(); } } catch (Exception ex) { code= HttpStatusCode.InternalServerError; content_response = ex.Message; } return (content_response, code); } public static ServerDevice GetServerDevice( string remote,string region) { string hostName = $"{Environment.UserName}-{Dns.GetHostName()}"; string os = RuntimeInformation.OSDescription; //获取当前客户端的服务端口 string currentUserName = Environment.UserName; string MachineName =Environment.MachineName; //var s = Environment; ServerDevice device = new ServerDevice { name =hostName, userName=currentUserName, os= os,region=region,remote=remote }; int CpuCoreCount = 0; long MenemorySize = 0; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { CpuCoreCount = Environment.ProcessorCount; MenemorySize = MemoryInfo.GetTotalPhysicalMemory(); //using (PerformanceCounter pc = new PerformanceCounter("Memory", "Available Bytes")) //{ // // 获取可用内存 // AvailableMemory = (long)pc.NextValue(); //} //using (PerformanceCounter pc = new PerformanceCounter("Memory", "Committed Bytes")) //{ // // 获取可用内存 // CommittedMemory= (long)pc.NextValue(); //} //MenemorySize=AvailableMemory+CommittedMemory; // // 获取CPU核心数 //int processorCount = Environment.ProcessorCount; //Console.WriteLine("CPU 核心数: " + processorCount); //using (ManagementClass managementClass = new ManagementClass("Win32_Processor")) //{ // using (ManagementObjectCollection managementObjectCollection = managementClass.GetInstances()) // { // foreach (ManagementObject managementObject in managementObjectCollection) // { // CpuCoreCount += Convert.ToInt32(managementObject.Properties["NumberOfLogicalProcessors"].Value); // } // } //} //using (ManagementObjectSearcher searcher1 = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem")) //{ // foreach (ManagementObject obj in searcher1.Get()) // { // long MenemorySizes1 = Convert.ToInt64(obj["TotalPhysicalMemory"]); // break; // 只需要第一个结果 // } //} //using (ManagementClass mc = new ManagementClass("Win32_ComputerSystem")) //{ // using (ManagementObjectCollection moc = mc.GetInstances()) // { // foreach (ManagementObject mo in moc) // { // if (mo["TotalPhysicalMemory"]!= null) // { // MenemorySize = Convert.ToInt64(mo["TotalPhysicalMemory"]); // } // } // } //} if (Environment.Is64BitOperatingSystem) { device.bit="64"; } else { device.bit="32"; } //ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Name, MaxClockSpeed FROM Win32_Processor"); //foreach (ManagementObject mo in searcher.Get()) //{ // string? cpuName = mo["Name"].ToString(); // string? clockSpeed = mo["MaxClockSpeed"].ToString(); // //Console.WriteLine($"CPU 名称: {cpuName}"); // //Console.WriteLine($"CPU 主频: {clockSpeed} MHz"); // device.cpuInfos.Add(new CPUInfo { name = cpuName, hz = clockSpeed }); //} string cpuName = MemoryInfo.GetCpuName(); var clockSpeed = MemoryInfo.GetCpuSpeed(); device.cpuInfos.Add(new CPUInfo { name = cpuName, hz = $"{clockSpeed}" }); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { //int processorCount = Environment.ProcessorCount; // Console.WriteLine("CPU 核心数: " + processorCount); try { string cpuInfo = File.ReadAllText("/proc/cpuinfo"); string[] cpu_lines = cpuInfo.Split('\n'); CpuCoreCount= cpu_lines.Count(line => line.StartsWith("processor", StringComparison.OrdinalIgnoreCase)); string? cpuNameLine = cpuInfo.Split('\n').FirstOrDefault(line => line.StartsWith("model name")); string? clockSpeedLine = cpuInfo.Split('\n').FirstOrDefault(line => line.StartsWith("cpu MHz")); string cpuName = string.Empty; string clockSpeed = string.Empty; if (cpuNameLine!= null) { cpuName = cpuNameLine.Split(':').Last().Trim(); } if (clockSpeedLine!= null) { clockSpeed = clockSpeedLine.Split(':').Last().Trim(); } device.cpuInfos.Add(new CPUInfo { name = cpuName, hz = clockSpeed }); } catch (Exception ex) { } string[] mem_lines = File.ReadAllLines("/proc/meminfo"); var match = mem_lines.FirstOrDefault(line => line.StartsWith("MemTotal:")); if (match != null) { var matchResult = Regex.Match(match, @"\d+"); if (matchResult.Success) { MenemorySize= long.Parse(matchResult.Value); } } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { try { using (var process = new Process()) { process.StartInfo.FileName = "/usr/sbin/sysctl"; process.StartInfo.Arguments = "-n hw.ncpu"; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.Start(); string output = process.StandardOutput.ReadToEnd().Trim(); int coreCount; if (int.TryParse(output, out coreCount)) { CpuCoreCount= coreCount; } } } catch (Exception ex) { } try { using (var process = new Process()) { process.StartInfo.FileName = "/usr/sbin/sysctl"; process.StartInfo.Arguments = "-n hw.memsize"; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.Start(); string output = process.StandardOutput.ReadToEnd().Trim(); long memorySize; if (long.TryParse(output, out memorySize)) { MenemorySize= memorySize; } } } catch (Exception ex) { } try { using (var process = new Process()) { process.StartInfo.FileName = "/usr/sbin/sysctl"; process.StartInfo.Arguments = "-n machdep.cpu.brand_string"; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.Start(); string cpuName = process.StandardOutput.ReadToEnd().Trim(); process.StartInfo.FileName = "/usr/sbin/sysctl"; process.StartInfo.Arguments = "-n hw.cpu.frequency"; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.Start(); string clockSpeed = process.StandardOutput.ReadToEnd().Trim(); //Console.WriteLine($"CPU 名称: {cpuName}"); //Console.WriteLine($"CPU 主频: {clockSpeed} Hz"); device.cpuInfos.Add(new CPUInfo { name = cpuName, hz = clockSpeed }); } } catch (Exception ex) { Console.WriteLine($"出现错误: {ex.Message}"); } if (Environment.Is64BitOperatingSystem) { device.bit="64"; } else { device.bit="32"; } } if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { device.arch="ARM64"; } else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) { device.arch="ARM32"; } else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) { device.arch="X64"; } else if (RuntimeInformation.ProcessArchitecture == Architecture.X86) { device.arch="X86"; } else { device.arch=$"未知({device.arch})"; } //Console.WriteLine("CPU 核心数: " + CpuCoreCount+",RAM 大小:"+MenemorySize); device.cpu=CpuCoreCount; device.ram=MenemorySize; var nics = NetworkInterface.GetAllNetworkInterfaces(); foreach (var nic in nics) { int physical = 0; if (nic.OperationalStatus == OperationalStatus.Up) { if (IsPhysicalNetworkInterface(nic)) { physical = 1; } var name = $"{nic.Name}-{nic.Description}"; var mac = nic.GetPhysicalAddress().ToString(); var properties = nic.GetIPProperties(); var unicastAddresses = properties.UnicastAddresses; foreach (var unicast in unicastAddresses) { if (unicast.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { var ip = unicast.Address.ToString(); Network network = new Network() {id=ShaHashHelper.GetSHA1($"{mac}-{name}"), mac=mac, ip=ip, name= name, physical=physical }; if (!string.IsNullOrWhiteSpace(mac.ToString()) && !mac.Equals("000000000000")) { device.networks.Add(network); } } } } } //if (_url!.IsNotEmpty()) //{ // List ports = new List(); // foreach (var url in _url!) // { // Uri uri = new Uri(url); // device.uris.Add(new UriInfo { port= uri.Port, protocol= uri.Scheme }); // } //} //else //{ // throw new Exception("未获取到端口信息!"); //} var networks = device.networks.ToList(); if (device.networks.IsNotEmpty()) { var order= device.networks.OrderByDescending(x => x.physical).ToList(); //for (int i=0; i x.physical==1); if (physical.IsNotEmpty()) { networks=physical; } } StringBuilder sb= new StringBuilder(); sb.Append(device.name);//设备名称 sb.Append(device.os);//系统名称 sb.Append(device.bit);//系统位 sb.Append(device.arch);//指令架构 sb.Append(device.cpu);//cpu核心树 sb.Append(string.Join(",", device.cpuInfos.Select(x=>$"{x.name}{x.hz}")));//cpu名称和频率 sb.Append(device.ram);//内存 sb.Append(string.Join(",", networks.Select(x => $"{x.mac}-{x.name}")));//网卡地址和名称 //暂不使用远程ip和局域网内ip作为hash,可能发生变化 string hashData = ShaHashHelper.GetSHA256(sb.ToString()); device.deviceId=hashData; device.id= hashData; return device; } public static bool IsPhysicalNetworkInterface(NetworkInterface nic) { // 排除虚拟网卡的关键词 string[] virtualKeywords = { "virtual", "hyper-v", "virtualbox", "vmware", "veth","virbr", "tunnel", "docker", "loopback", "vpn","ppp", "slip", "bridge", "vnic", "vif", "tap", "vlan", "vswitch", "vxlan", "gre", "ipsec", "vrf", "vport", "vnet", "vmac", "vxnet"}; // 检查网卡描述或名称中是否包含虚拟关键词 string description = nic.Description.ToLower(); string name = nic.Name.ToLower(); foreach (var keyword in virtualKeywords) { if (description.Contains(keyword,StringComparison.OrdinalIgnoreCase) || name.Contains(keyword, StringComparison.OrdinalIgnoreCase)) { return false; // 是虚拟网卡 } } // 排除一些常见的虚拟网卡类型 if (nic.NetworkInterfaceType == NetworkInterfaceType.Loopback || nic.NetworkInterfaceType == NetworkInterfaceType.Tunnel || nic.NetworkInterfaceType == NetworkInterfaceType.Ppp || nic.NetworkInterfaceType == NetworkInterfaceType.Slip|| nic.NetworkInterfaceType == NetworkInterfaceType.Unknown ) { return false; // 是虚拟网卡 } // 默认认为是物理网卡 return true; } /// /// 直接获取设备。 /// /// /// /// /// /// public static string GetDevice(HttpContext httpContext, IMemoryCache _memoryCache) { string IP= GetIP(httpContext); var cookie = httpContext.Request.Cookies; string device =string.Empty; if (cookie != null) { ///设备是否存在 foreach (var ck in cookie) { if (ck.Key.Equals("device")) { //redis如果存在则 var fingerprint = ck.Value.Split("-"); if (fingerprint.Length == 2 && IP.Equals(fingerprint[1])) { if (!_memoryCache.TryGetValue($"device:{fingerprint[0]}:{IP}", out device)) { _memoryCache.Set($"device:{fingerprint[0]}:{IP}", $"{fingerprint[0]}-{IP}"); device = $"{fingerprint[0]}-{IP}"; } } } } } return device; } public static string GetIP(HttpContext httpContext) { var IpPort = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault(); if (string.IsNullOrEmpty(IpPort)) { IpPort = $"{httpContext.Connection.RemoteIpAddress}"; } if (IpPort.Contains("::")) { IpPort = "127.0.0.1"; } return IpPort; } /// /// 初始化设备 /// /// /// 浏览器指纹 /// /// /// /// public static string GetDeviceInit(this HttpContext httpContext, string fingerprint, string IP, IMemoryCache _memoryCache) { string device = $"{fingerprint}-{IP}"; List cookieString = new List(); var cookie = httpContext.Request.Cookies; int status = 1; if (cookie != null) { ///设备是否存在 foreach (var ck in cookie) { if (ck.Key.Equals("device")) { if (device.Contains("-") && device.Contains(".")) { //如果匹配的是fingerprint-IP 则是已经存在的。 if (ck.Value.Equals(device)) { //redis如果存在则 _memoryCache.TryGetValue($"device:{fingerprint}:{IP}", out string device_exist); // 返回的则应该是ck.Value=exist_device_exist的数据 if (device_exist!=null) { if (!string.IsNullOrWhiteSpace($"{device_exist}")) { //0是代表指纹和IP匹配,正常返回的 status = 1; } else { //需要新建 fingerprint-IP status = 1; } } else { status = 1; } } else { string ck_ip = ck.Value.Split("-")[1]; if (ck_ip.Equals(IP)) { //传入的指纹和cookie的不一致,仍然以cookie的为准。 status = 1; fingerprint = ck.Value.Split("-").First(); device = ck.Value; } } } else { //如果匹配的是fingerprint则是一个新的设备。 if (ck.Value.Equals(fingerprint)) { //检查设备是否被占用 //var device_exist = _azureRedis.GetRedisClient(8).HashExists($"device:{fingerprint}", IP); _memoryCache.TryGetValue($"device:{fingerprint}:{IP}", out JsonNode device_exist); if (device_exist!=null) { //需要新建 sha1(fingerprint+uuid)-IP status = 2; } else { //0是代表指纹和IP匹配,正常返回的 status = 1; } } else { //匹配的都不是,新设备。 status = 1; } } } else { cookieString.Add($"{ck.Key}{ck.Value}"); } } } /* httpContext.Request.Headers.TryGetValue("accept-language", out var accept_language); httpContext.Request.Headers.TryGetValue("sec-ch-ua", out var chua); httpContext.Request.Headers.TryGetValue("sec-ch-ua-platform", out var platform); httpContext.Request.Headers.TryGetValue("user-agent", out var useragent); httpContext.Request.Headers.TryGetValue("accept", out var accept); httpContext.Request.Headers.TryGetValue("accept-encoding", out var accept_encoding); device = ShaHashHelper.GetSHA1($"{IP}{accept_language}{chua}{platform}{useragent}{accept}{accept_encoding}{string.Join("", cookieString)}"); */ if (status == 2) { fingerprint = ShaHashHelper.GetSHA1(fingerprint + Guid.NewGuid().ToString()); device = $"{fingerprint}-{IP}"; } //else if (status == 1) //{ // device = $"{fingerprint}-{IP}"; //} //await _azureRedis.GetRedisClient(8).HashSetAsync($"device:{fingerprint}", IP, new { device }.ToJsonString()); //await _azureRedis.GetRedisClient(8).KeyExpireAsync($"device:{fingerprint}", device_timeSpan); _memoryCache.Set($"device:{fingerprint}:{IP}", device); httpContext.Response.Cookies.Append("device", device, new CookieOptions { HttpOnly = true, MaxAge = new TimeSpan(24 * 7, 0, 0) }); return device; } } public class UriInfo { public string? protocol { get; set; } public int port { get; set; } } }