CrazyIter_Bin 4 months ago
parent
commit
c88a5d7721

+ 12 - 2
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Controllers/IndexController.cs

@@ -23,9 +23,9 @@ namespace IES.ExamServer.Controllers
         private readonly IHttpClientFactory _httpClientFactory;
         private readonly IHttpClientFactory _httpClientFactory;
         private readonly IMemoryCache _memoryCache;
         private readonly IMemoryCache _memoryCache;
         private readonly ILogger<IndexController> _logger;
         private readonly ILogger<IndexController> _logger;
-        private readonly DataCenterConnectionService _connectionService;
+        private readonly CenterServiceConnectionService _connectionService;
         private readonly LiteDBFactory _liteDBFactory;
         private readonly LiteDBFactory _liteDBFactory;
-        public IndexController(ILogger<IndexController> logger, IConfiguration configuration, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache,DataCenterConnectionService connectionService, LiteDBFactory liteDBFactory)
+        public IndexController(ILogger<IndexController> logger, IConfiguration configuration, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache,CenterServiceConnectionService connectionService, LiteDBFactory liteDBFactory)
         {
         {
             _logger = logger;
             _logger = logger;
             _configuration=configuration;
             _configuration=configuration;
@@ -273,6 +273,7 @@ namespace IES.ExamServer.Controllers
         [HttpPost("login-init")]
         [HttpPost("login-init")]
         public async Task<IActionResult> LoginInit(JsonNode json)
         public async Task<IActionResult> LoginInit(JsonNode json)
         {
         {
+            
             var type = json["type"];
             var type = json["type"];
             string qrcode = string.Empty;
             string qrcode = string.Empty;
             string randomcode = "";
             string randomcode = "";
@@ -300,6 +301,15 @@ namespace IES.ExamServer.Controllers
                             qrcode = $"data:image/png;base64,{str}";
                             qrcode = $"data:image/png;base64,{str}";
                             int ttl = 60;
                             int ttl = 60;
                             _memoryCache.Set($"Login:ExamServer:{school?.id}:{randomcode}", randomcode, TimeSpan.FromSeconds(ttl));
                             _memoryCache.Set($"Login:ExamServer:{school?.id}:{randomcode}", randomcode, TimeSpan.FromSeconds(ttl));
+                            var device =IndexService.GetDevice(HttpContext,_memoryCache);
+                            _memoryCache.Set($"device:Login:ExamServer:{school?.id}:{randomcode}", device);
+                            try {
+                                string? NotifyUrl = _configuration.GetValue<string>("ExamServer:CenterUrl");
+                                if (_connectionService.notifyIsConnected) 
+                                {
+                                    
+                                }
+                            } catch (Exception e) { }
                             return Ok(new { ttl,code = 200, randomCode = randomcode, qrcode, type });
                             return Ok(new { ttl,code = 200, randomCode = randomcode, qrcode, type });
                         }
                         }
                     case bool when $"{type}".Equals("xqrcode"):
                     case bool when $"{type}".Equals("xqrcode"):

+ 4 - 4
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Controllers/ManageController.cs

@@ -26,12 +26,12 @@ namespace IES.ExamServer.Controllers
         private readonly IMemoryCache _memoryCache;
         private readonly IMemoryCache _memoryCache;
         private readonly ILogger<ManageController> _logger;
         private readonly ILogger<ManageController> _logger;
         private readonly LiteDBFactory _liteDBFactory;
         private readonly LiteDBFactory _liteDBFactory;
-        private readonly DataCenterConnectionService _connectionService;
+        private readonly CenterServiceConnectionService _connectionService;
         private readonly int DelayMicro = 10;//微观数据延迟
         private readonly int DelayMicro = 10;//微观数据延迟
         private readonly int DelayMacro = 100;//宏观数据延迟
         private readonly int DelayMacro = 100;//宏观数据延迟
         private readonly IHubContext<SignalRExamServerHub> _signalRExamServerHub;
         private readonly IHubContext<SignalRExamServerHub> _signalRExamServerHub;
         public ManageController(LiteDBFactory liteDBFactory,ILogger<ManageController> logger, IConfiguration configuration,
         public ManageController(LiteDBFactory liteDBFactory,ILogger<ManageController> logger, IConfiguration configuration,
-            IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, DataCenterConnectionService connectionService, IHubContext<SignalRExamServerHub> signalRExamServerHub)
+            IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, CenterServiceConnectionService connectionService, IHubContext<SignalRExamServerHub> signalRExamServerHub)
         {
         {
             _logger = logger;
             _logger = logger;
             _configuration=configuration;
             _configuration=configuration;
@@ -79,7 +79,7 @@ namespace IES.ExamServer.Controllers
             var token = GetAuthTokenInfo();
             var token = GetAuthTokenInfo();
             if (token.scope.Equals(ExamConstant.ScopeTeacher))
             if (token.scope.Equals(ExamConstant.ScopeTeacher))
             {
             {
-                if (_connectionService.dataCenterIsConnected) 
+                if (_connectionService.centerIsConnected) 
                 {
                 {
                     Teacher? teacher = _liteDBFactory.GetLiteDatabase().GetCollection<Teacher>().FindOne(x => x.id!.Equals(token.id));
                     Teacher? teacher = _liteDBFactory.GetLiteDatabase().GetCollection<Teacher>().FindOne(x => x.id!.Equals(token.id));
                     if (teacher != null)
                     if (teacher != null)
@@ -166,7 +166,7 @@ namespace IES.ExamServer.Controllers
             var token = GetAuthTokenInfo();
             var token = GetAuthTokenInfo();
             if (token.scope.Equals(ExamConstant.ScopeTeacher))
             if (token.scope.Equals(ExamConstant.ScopeTeacher))
             {
             {
-                if (_connectionService.dataCenterIsConnected)
+                if (_connectionService.centerIsConnected)
                 {
                 {
                     Teacher? teacher = _liteDBFactory.GetLiteDatabase().GetCollection<Teacher>().FindOne(x => x.id!.Equals(token.id));
                     Teacher? teacher = _liteDBFactory.GetLiteDatabase().GetCollection<Teacher>().FindOne(x => x.id!.Equals(token.id));
                     if (teacher != null)
                     if (teacher != null)

+ 20 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/DI/CenterServiceConnectionService.cs

@@ -0,0 +1,20 @@
+namespace IES.ExamServer.DI
+{
+    public class CenterServiceConnectionService
+    {
+        private bool _centerIsConnected;
+        private bool _notifyIsConnected;
+        public string? centerUrl { get; set; }
+        public string? notifyUrl { get; set; }
+        public bool notifyIsConnected
+        {
+            get { return string.IsNullOrWhiteSpace(notifyUrl) ? false : _notifyIsConnected; }
+            set { _notifyIsConnected = value; }
+        }
+        public bool centerIsConnected
+        {
+            get { return string.IsNullOrWhiteSpace(centerUrl) ? false : _centerIsConnected; }
+            set { _centerIsConnected = value; }
+        }
+    }
+}

+ 0 - 14
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/DI/DataCenterConnectionService.cs

@@ -1,14 +0,0 @@
-namespace IES.ExamServer.DI
-{
-    public class DataCenterConnectionService
-    {
-        private bool _dataCenterIsConnected;
-        public string? centerUrl { get; set; }
-
-        public bool dataCenterIsConnected
-        {
-            get { return string.IsNullOrWhiteSpace(centerUrl) ? false : _dataCenterIsConnected; }
-            set { _dataCenterIsConnected = value; }
-        }
-    }
-}

+ 50 - 51
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/DI/ServiceInitializer.cs

@@ -8,6 +8,8 @@ using System.Text.Json;
 using System.Text.Unicode;
 using System.Text.Unicode;
 using IES.ExamServer.Helper;
 using IES.ExamServer.Helper;
 using IES.ExamServer.Models;
 using IES.ExamServer.Models;
+using System.Security.Policy;
+using IES.ExamServer.Helpers;
 
 
 namespace IES.ExamServer.DI
 namespace IES.ExamServer.DI
 {
 {
@@ -17,7 +19,7 @@ namespace IES.ExamServer.DI
         private readonly IHttpClientFactory _clientFactory;
         private readonly IHttpClientFactory _clientFactory;
         private readonly LiteDBFactory _liteDBFactory;
         private readonly LiteDBFactory _liteDBFactory;
         private readonly IConfiguration _configuration;
         private readonly IConfiguration _configuration;
-        private readonly DataCenterConnectionService _connectionService;
+        private readonly CenterServiceConnectionService _connectionService;
         private readonly IHostApplicationLifetime _lifetime;
         private readonly IHostApplicationLifetime _lifetime;
         private readonly IServer _server;
         private readonly IServer _server;
         private readonly ILogger<ServiceInitializer> _logger;
         private readonly ILogger<ServiceInitializer> _logger;
@@ -26,7 +28,7 @@ namespace IES.ExamServer.DI
         IHttpClientFactory clientFactory,
         IHttpClientFactory clientFactory,
         LiteDBFactory liteDBFactory,
         LiteDBFactory liteDBFactory,
         IConfiguration configuration,
         IConfiguration configuration,
-        DataCenterConnectionService connectionService,
+        CenterServiceConnectionService connectionService,
         IHostApplicationLifetime lifetime,
         IHostApplicationLifetime lifetime,
         IServer server,
         IServer server,
         ILogger<ServiceInitializer> logger)
         ILogger<ServiceInitializer> logger)
@@ -44,13 +46,13 @@ namespace IES.ExamServer.DI
         public async Task InitializeAsync()
         public async Task InitializeAsync()
         {
         {
             JsonNode? data = null;
             JsonNode? data = null;
-            int hybrid = 0;
+            int hybrid = 0, notify=0;
             string remote = "127.0.0.1";
             string remote = "127.0.0.1";
             string region = "局域网·内网";
             string region = "局域网·内网";
-
+            string? centerUrl = _configuration.GetValue<string>("ExamServer:CenterUrl");
             try
             try
             {
             {
-                string? centerUrl = _configuration.GetValue<string>("ExamServer:CenterUrl");
+                
                 var httpClient = _clientFactory.CreateClient();
                 var httpClient = _clientFactory.CreateClient();
                 httpClient.Timeout = TimeSpan.FromSeconds(10);
                 httpClient.Timeout = TimeSpan.FromSeconds(10);
                 HttpResponseMessage message = await httpClient.PostAsJsonAsync($"{centerUrl}/core/system-info", new { });
                 HttpResponseMessage message = await httpClient.PostAsJsonAsync($"{centerUrl}/core/system-info", new { });
@@ -71,9 +73,26 @@ namespace IES.ExamServer.DI
                 // 云端服务连接失败
                 // 云端服务连接失败
                 hybrid = 0;
                 hybrid = 0;
             }
             }
+            string? notifyUrl = _configuration.GetValue<string>("ExamServer:NotifyUrl");
+            try
+            {
+                
+                var httpClient = _clientFactory.CreateClient();
+                httpClient.Timeout = TimeSpan.FromSeconds(10);
+                HttpResponseMessage message = await httpClient.PostAsJsonAsync($"{notifyUrl}/index/device-init", new { fp= Guid.NewGuid().ToString() });
+                if (message.IsSuccessStatusCode)
+                {
+                    notify = 1;
+                }
+            }
+            catch (Exception ex)
+            {
+                // 云端服务连接失败
+                notify = 0;
+            }
             if (hybrid==1)
             if (hybrid==1)
             {
             {
-                string? centerUrl = _configuration.GetValue<string>("ExamServer:CenterUrl");
+              
                 var httpClient = _clientFactory.CreateClient();
                 var httpClient = _clientFactory.CreateClient();
                 httpClient.Timeout = TimeSpan.FromSeconds(10);
                 httpClient.Timeout = TimeSpan.FromSeconds(10);
                 HttpResponseMessage message = await httpClient.GetAsync("https://teammodelos.blob.core.chinacloudapi.cn/0-public/schools.json");
                 HttpResponseMessage message = await httpClient.GetAsync("https://teammodelos.blob.core.chinacloudapi.cn/0-public/schools.json");
@@ -93,54 +112,34 @@ namespace IES.ExamServer.DI
                     throw new Exception($"Failed to download data. Status code: {message.StatusCode}");
                     throw new Exception($"Failed to download data. Status code: {message.StatusCode}");
                 }
                 }
             }
             }
-
+            _connectionService.notifyUrl = notify == 1 ? notifyUrl : null;
+            _connectionService.notifyIsConnected = notify == 1;
             // 单例模式存储云端数据中心连接状态
             // 单例模式存储云端数据中心连接状态
-            _connectionService.centerUrl = hybrid == 1 ? $"{data?["centerUrl"]}" : null;
-            _connectionService.dataCenterIsConnected = hybrid == 1;
-
+            _connectionService.centerUrl = hybrid == 1 ?centerUrl : null;
+            _connectionService.centerIsConnected = hybrid == 1;
+            ServerDevice serverDevice = IndexService.GetServerDevice(remote, region);
+            IEnumerable<School> schools = _liteDBFactory.GetLiteDatabase().GetCollection<School>().FindAll();
+            School? school = schools?.FirstOrDefault();
+            serverDevice.school = school;
+            _cache.Set(Constant._KeyServerDevice, serverDevice);
             _lifetime.ApplicationStarted.Register(() =>
             _lifetime.ApplicationStarted.Register(() =>
             {
             {
-                var addresses = _server.Features.Get<IServerAddressesFeature>()?.Addresses;
-                ServerDevice serverDevice = IndexService.GetServerDevice(remote, region, addresses);
-                IEnumerable<School> schools = _liteDBFactory.GetLiteDatabase().GetCollection<School>().FindAll();
-                School? school = schools?.FirstOrDefault();
-                serverDevice.school = school;
-                //int domainStatus =0;
-                //string domain = builder.Configuration.GetValue<string>("ExamClient:Domain");
-                //foreach (var network in serverDevice.networks) 
-                //{
-                //    try
-                //    {
-                //        string domain_entry = $"{network.ip}    {domain}";
-                //        string hostsFilePath = @"C:\Windows\System32\drivers\etc\hosts";
-                //        string content = File.ReadAllText(hostsFilePath, Encoding.UTF8);
-                //        if (!content.Contains(domain_entry))
-                //        {
-                //            content += Environment.NewLine + domain_entry;
-                //            // 使用管理员权限运行此程序,不然会抛出UnauthorizedAccessException
-                //            File.WriteAllText(hostsFilePath, content, Encoding.UTF8);
-                //            domainStatus=1;
-                //            // Console.WriteLine("Hosts file updated successfully.");
-                //        }
-                //        else
-                //        {
-                //            domainStatus=1;
-                //            //Console.WriteLine("The entry already exists in the hosts file.");
-                //        }
-                //    }
-                //    catch (UnauthorizedAccessException)
-                //    {
-                //        domainStatus=2;
-                //        // Console.WriteLine("You need to run this program with administrative privileges to modify the hosts file.");
-                //    }
-                //    catch (Exception ex)
-                //    {
-                //        domainStatus=0;
-                //        // Console.WriteLine($"An error occurred: {ex.Message}");
-                //    }
-                //}
-                //serverDevice.domainStatus=domainStatus;
-                //serverDevice.domain=domain;
+               var serverDevice=  _cache.Get<ServerDevice>(Constant._KeyServerDevice);
+                var _url = _server.Features.Get<IServerAddressesFeature>()?.Addresses;
+                if (_url!.IsNotEmpty())
+                {
+                    List<UriInfo> ports = new List<UriInfo>();
+                    foreach (var url in _url!)
+                    {
+                        Uri uri = new Uri(url);
+                        serverDevice.uris.Add(new UriInfo { port= uri.Port, protocol= uri.Scheme });
+                    }
+
+                }
+                else
+                {
+                    throw new Exception("未获取到端口信息!");
+                }
                 _logger.LogInformation($"服务端设备信息:{JsonSerializer.Serialize(serverDevice, options: new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) })}");
                 _logger.LogInformation($"服务端设备信息:{JsonSerializer.Serialize(serverDevice, options: new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) })}");
                 _cache.Set(Constant._KeyServerDevice, serverDevice);
                 _cache.Set(Constant._KeyServerDevice, serverDevice);
             });
             });

+ 143 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/DI/SignalRCloudClientHub.cs

@@ -0,0 +1,143 @@
+using IES.ExamServer.DI.SignalRHost;
+using IES.ExamServer.Helper;
+using IES.ExamServer.Models;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.AspNetCore.SignalR.Client;
+using Microsoft.Extensions.Caching.Memory;
+using System.Text;
+using System.Text.Json.Nodes;
+using System.Web;
+
+namespace IES.ExamServer.DI
+{
+    public class SignalRCloudClientHub : BackgroundService, IDisposable
+    {
+        private readonly IConfiguration _configuration;
+        private readonly ILogger<SignalRCloudClientHub> _logger;
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly IMemoryCache _memoryCache;
+        private readonly IHubContext<SignalRExamServerHub> _signalRExamServerHub;
+        public SignalRCloudClientHub(IConfiguration configuration, ILogger<SignalRCloudClientHub> logger, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache,
+            IHubContext<SignalRExamServerHub> signalRExamServerHub)
+        {
+
+            _configuration=configuration;
+            _logger=logger;
+            _httpClientFactory=httpClientFactory;
+            _memoryCache=memoryCache;
+            _signalRExamServerHub=signalRExamServerHub;
+        }
+        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            _memoryCache.TryGetValue(Constant._KeyServerDevice, out ServerDevice? server);
+            string? deviceId = server?.deviceId;
+            string? NotifyUrl = _configuration.GetValue<string>("ExamServer:NotifyUrl");
+            await StartHubConnectionAsync(deviceId, NotifyUrl);
+
+        }
+        private async Task StartHubConnectionAsync(string? deviceId,string? NotifyUrl)
+        {
+            var reconnectPolicy = new ExponentialBackoffReconnectPolicy(TimeSpan.FromSeconds(10), _logger); // 尝试重连的最大次数,这里使用 int.MaxValue 表示无限次
+            reconnectPolicy.MaxRetryCount = int.MaxValue;
+            HubConnection hubConnection = new HubConnectionBuilder()
+               .WithUrl($"{NotifyUrl}/signalr/remote/notify?grant_type={Constant._Message_grant_type_ies_qrcode_login}&deviceid={deviceId}&dingding=123") //only one slash
+               .WithAutomaticReconnect(reconnectPolicy)
+               .ConfigureLogging(logging =>
+               {
+                   logging.SetMinimumLevel(LogLevel.Information);
+                   logging.AddConsole();
+               })
+               .Build();
+            try
+            {
+                hubConnection.On<ConnectionMessageBody>("ReceiveConnection", (message) =>
+                {
+                    _logger.LogInformation($"连接成功:{message.ToJsonString()}");
+                    //重置重连次数。
+                    reconnectPolicy.Reset();
+                });
+                hubConnection.On<ConnectionMessageBody>("ReceiveMessage", async (message) =>
+                {
+                    if ($"{Constant._Message_grant_type_ies_qrcode_login}" .Equals(message?.grant_type))
+                    {
+                        string deviceId = string.Empty;
+                        var nodeData= message?.content?.ToObject<JsonNode>();
+                        if (nodeData!=null)
+                        {
+                            string randomcode = $"{nodeData["randomcode"]}";
+                            deviceId=_memoryCache.Get<string>($"device:{randomcode}");
+                        }
+                        await  _signalRExamServerHub.SendMessage(_memoryCache,_logger, deviceId, Constant._Message_grant_type_ies_qrcode_login,
+                          new MessageContent {dataId=deviceId,dataName="",messageType =Constant._Message_type_message, status=1,content=$"{message?.ToJsonString()}"});
+                    }
+                    else
+                    {
+                        _logger.LogInformation($"云端signalr数据格式不匹配,{message?.ToJsonString()}");
+                    }
+                });
+                await hubConnection.StartAsync();
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError("初次启动连接SignalR失败,等待重连......");
+                int retryCount = 0;
+                const int maxRetries = 360;
+                const int retryDelaySeconds = 10;
+                while (retryCount < maxRetries)
+                {
+                    try
+                    {
+                        await Task.Delay(retryDelaySeconds * 1000); // 等待一段时间后重试  
+                        await hubConnection.StartAsync();
+                        _logger.LogInformation("SignalR连接成功(重试后)!");
+                        break; // 连接成功,退出循环  
+                    }
+                    catch (Exception retryEx)
+                    {
+                        retryCount++;
+                        _logger.LogInformation($"SignalR连接重试失败: {retryEx.Message}。重试次数: {retryCount}/{maxRetries}");
+                        // 可以在这里决定是否因为某种原因停止重试  
+                        if (retryCount == maxRetries)
+                        {
+                            _logger.LogInformation("达到最大重试次数,停止重试。");
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    public class ExponentialBackoffReconnectPolicy : IRetryPolicy
+    {
+        private readonly TimeSpan _retryInterval;
+
+        private int _retryCount;
+        public int MaxRetryCount { get; set; } = int.MaxValue;
+        public readonly ILogger<SignalRCloudClientHub> _logger;
+        public ExponentialBackoffReconnectPolicy(TimeSpan retryInterval, ILogger<SignalRCloudClientHub> logger)
+        {
+            _retryInterval = retryInterval;
+            _retryCount = 0;
+            _logger = logger;
+        }
+
+        public TimeSpan? NextRetryDelay(RetryContext retryContext)
+        {
+            _logger.LogInformation($"重连次数: {_retryCount}");
+            if (_retryCount < MaxRetryCount)
+            {
+                _retryCount++;
+                // 计算下一次重连的延迟时间
+                return _retryInterval;
+            }
+            return null; // 达到最大重连次数后不再重连
+        }
+
+        public void Reset()
+        {
+            _retryCount = 0;
+        }
+    }
+}

+ 1 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Helpers/Constant.cs

@@ -15,6 +15,7 @@ namespace IES.ExamServer.Helper
        
        
         public static readonly string _X_Auth_AuthToken = "X-Auth-AuthToken";
         public static readonly string _X_Auth_AuthToken = "X-Auth-AuthToken";
         public static readonly string _Message_grant_type_check_file = "check_file";
         public static readonly string _Message_grant_type_check_file = "check_file";
+        public static readonly string _Message_grant_type_ies_qrcode_login = "ies_qrcode_login";
         public static readonly string _Message_type_message = "message";
         public static readonly string _Message_type_message = "message";
         public static readonly string _Message_type_check = "check";
         public static readonly string _Message_type_check = "check";
         public static readonly string _Message_type_download = "download";
         public static readonly string _Message_type_download = "download";

+ 1 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/IES.ExamServer.csproj

@@ -36,6 +36,7 @@
 	<ItemGroup>
 	<ItemGroup>
 		<PackageReference Include="AutoMapper" Version="13.0.1" />
 		<PackageReference Include="AutoMapper" Version="13.0.1" />
 		<PackageReference Include="LiteDB" Version="5.0.21" />
 		<PackageReference Include="LiteDB" Version="5.0.21" />
+		<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.36" />
 		<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
 		<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
 		<PackageReference Include="NLog" Version="5.3.4" />
 		<PackageReference Include="NLog" Version="5.3.4" />
 		<PackageReference Include="NLog.Extensions.Logging" Version="5.3.15" />
 		<PackageReference Include="NLog.Extensions.Logging" Version="5.3.15" />

+ 2 - 1
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Program.cs

@@ -87,7 +87,7 @@ namespace IES.ExamServer
             builder.Services.AddLiteDB(connections_LiteDB);
             builder.Services.AddLiteDB(connections_LiteDB);
             builder.Services.AddMemoryCache();
             builder.Services.AddMemoryCache();
             // 注册 ConnectionService 为单例
             // 注册 ConnectionService 为单例
-            builder.Services.AddSingleton<DataCenterConnectionService>();
+            builder.Services.AddSingleton<CenterServiceConnectionService>();
             builder.Services.AddSingleton<ServiceInitializer>();
             builder.Services.AddSingleton<ServiceInitializer>();
             builder.Services.AddCors(options =>
             builder.Services.AddCors(options =>
             {
             {
@@ -116,6 +116,7 @@ namespace IES.ExamServer
             // 添加日志服务
             // 添加日志服务
             builder.Logging.ClearProviders();
             builder.Logging.ClearProviders();
             builder.Logging.AddNLog();
             builder.Logging.AddNLog();
+            builder.Services.AddHostedService<SignalRCloudClientHub>();
             var app = builder.Build();
             var app = builder.Build();
 
 
             // Configure the HTTP request pipeline.
             // Configure the HTTP request pipeline.

+ 60 - 17
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Services/IndexService.cs

@@ -15,7 +15,7 @@ namespace IES.ExamServer.Services
 {
 {
     public static class IndexService
     public static class IndexService
     {
     {
-        public static ServerDevice GetServerDevice( string remote,string region,  IEnumerable<string>? _url)
+        public static ServerDevice GetServerDevice( string remote,string region)
         {
         {
             string hostName = $"{Environment.UserName}-{Dns.GetHostName()}";
             string hostName = $"{Environment.UserName}-{Dns.GetHostName()}";
             string os = RuntimeInformation.OSDescription;
             string os = RuntimeInformation.OSDescription;
@@ -260,28 +260,71 @@ namespace IES.ExamServer.Services
                     }
                     }
                 }
                 }
             }
             }
-            if (_url!.IsNotEmpty())
-            {
-                List<UriInfo> ports = new List<UriInfo>();
-                foreach (var url in _url!)
-                {
-                    Uri uri = new Uri(url);
-                    device.uris.Add(new UriInfo { port= uri.Port, protocol= uri.Scheme });
-                }
+            //if (_url!.IsNotEmpty())
+            //{
+            //    List<UriInfo> ports = new List<UriInfo>();
+            //    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("未获取到端口信息!");
-            }
+            //}
+            //else
+            //{
+            //    throw new Exception("未获取到端口信息!");
+            //}
             string hashData = ShaHashHelper.GetSHA1($"{device.name}-{device.remote}-{string.Join(",", device.uris.Select(x => $"{x.port}-{x.protocol}"))}-{device.os}-{string.Join(",", device.networks.Select(x => $"{x.mac}-{x.ip}"))}");
             string hashData = ShaHashHelper.GetSHA1($"{device.name}-{device.remote}-{string.Join(",", device.uris.Select(x => $"{x.port}-{x.protocol}"))}-{device.os}-{string.Join(",", device.networks.Select(x => $"{x.mac}-{x.ip}"))}");
             device.deviceId=hashData;
             device.deviceId=hashData;
             return device;
             return device;
         }
         }
 
 
 
 
-
-
+        /// <summary>
+        /// 直接获取设备。
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <param name="IP"></param>
+        /// <param name="device_timeSpan"></param>
+        /// <param name="_azureRedis"></param>
+        /// <returns></returns>
+        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]))
+                        {
+                            _memoryCache.TryGetValue<string>($"device:{fingerprint}:{IP}", out  device);
+                            
+                        }
+                    }
+                }
+            }
+            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;
+        }
 
 
         /// <summary>
         /// <summary>
         /// 初始化设备
         /// 初始化设备
@@ -398,7 +441,7 @@ namespace IES.ExamServer.Services
             //}
             //}
             //await _azureRedis.GetRedisClient(8).HashSetAsync($"device:{fingerprint}", IP, new { device }.ToJsonString());
             //await _azureRedis.GetRedisClient(8).HashSetAsync($"device:{fingerprint}", IP, new { device }.ToJsonString());
             //await _azureRedis.GetRedisClient(8).KeyExpireAsync($"device:{fingerprint}", device_timeSpan);
             //await _azureRedis.GetRedisClient(8).KeyExpireAsync($"device:{fingerprint}", device_timeSpan);
-            _memoryCache.Set($"device:{fingerprint}:{IP}", new { device });
+            _memoryCache.Set($"device:{fingerprint}:{IP}", device);
             httpContext.Response.Cookies.Append("device", device, new CookieOptions { HttpOnly = true, MaxAge = new TimeSpan(24 * 7, 0, 0) });
             httpContext.Response.Cookies.Append("device", device, new CookieOptions { HttpOnly = true, MaxAge = new TimeSpan(24 * 7, 0, 0) });
             return device;
             return device;
         }
         }

+ 2 - 1
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/appsettings.json

@@ -27,7 +27,8 @@
   "ExamServer": {
   "ExamServer": {
     "Timeout": 30000,
     "Timeout": 30000,
     "Delay": 500,
     "Delay": 500,
-    "CenterUrl": "https://www.teammodel.cn" //https://www.teammodel.cn,https://localhost:5001
+    "CenterUrl": "https://www.teammodel.cn", //https://www.teammodel.cn,https://localhost:5001
+    "NotifyUrl": "https://www.winteach.cn"
   },
   },
   "ExamClient": {
   "ExamClient": {
     "Domain": "edge-exam.habook.cn"
     "Domain": "edge-exam.habook.cn"

+ 4 - 0
TEAMModelOS/Controllers/System/CoreController.cs

@@ -47,6 +47,7 @@ using System.Text.Json.Nodes;
 using TEAMModelOS.Services;
 using TEAMModelOS.Services;
 using static TEAMModelOS.Controllers.Client.HiTAControlller;
 using static TEAMModelOS.Controllers.Client.HiTAControlller;
 using DocumentFormat.OpenXml.Office2010.Excel;
 using DocumentFormat.OpenXml.Office2010.Excel;
+using System.Net.Http.Json;
 
 
 namespace TEAMModelOS.Controllers
 namespace TEAMModelOS.Controllers
 {
 {
@@ -443,6 +444,9 @@ namespace TEAMModelOS.Controllers
                     //await _dingDing.SendBotMsg(join.ToJsonString(), GroupNames.成都开发測試群組);
                     //await _dingDing.SendBotMsg(join.ToJsonString(), GroupNames.成都开发測試群組);
                     var id = jwt.Payload.Sub;
                     var id = jwt.Payload.Sub;
                     await _azureRedis.GetRedisClient(8).StringSetAsync($"HiTA:{join.randomcode}", id, expiry: new TimeSpan(0, 0, 30));
                     await _azureRedis.GetRedisClient(8).StringSetAsync($"HiTA:{join.randomcode}", id, expiry: new TimeSpan(0, 0, 30));
+                    try {
+                        await _httpClientFactory.CreateClient().PostAsJsonAsync($"https://www.winteach.cn/third/ies/qrcode-login-notify", new { randomcode = join.randomcode,tmdid=id});
+                    } catch (Exception ex) { }
                     return Ok(new { msg = "关闭弹窗以获取登录信息" ,code=200});
                     return Ok(new { msg = "关闭弹窗以获取登录信息" ,code=200});
                 }
                 }
                 else { return Ok(new { code = 400, msg = $"参数错误{join.ToJsonString()}" }); }
                 else { return Ok(new { code = 400, msg = $"参数错误{join.ToJsonString()}" }); }