CrazyIter_Bin 5 miesięcy temu
rodzic
commit
7347fd3c07

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

@@ -3,6 +3,7 @@ using IES.ExamServer.DI;
 using IES.ExamServer.DI.SignalRHost;
 using IES.ExamServer.Filters;
 using IES.ExamServer.Helper;
+using IES.ExamServer.Helpers;
 using IES.ExamServer.Models;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Caching.Memory;
@@ -26,7 +27,6 @@ namespace IES.ExamServer.Controllers
         private readonly DataCenterConnectionService _connectionService;
         private readonly int DelayMicro = 10;//微观数据延迟
         private readonly int DelayMacro = 100;//宏观数据延迟
-
         private readonly SignalRExamServerHub _signalRExamServerHub;
         public ManageController(LiteDBFactory liteDBFactory,ILogger<ManageController> logger, IConfiguration configuration,
             IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, DataCenterConnectionService connectionService,SignalRExamServerHub signalRExamServerHub)
@@ -43,11 +43,12 @@ namespace IES.ExamServer.Controllers
         [AuthToken("admin","teacher")]
         public async Task<IActionResult> DownloadPackage(JsonNode json)
         {
-            
+
             //C#.NET 6 后端与前端流式通信
             //https://www.doubao.com/chat/collection/687687510791426?type=Thread
             //下载日志记录:1.步骤,检查,2.获取描述信息,3.分类型,4下载文件,5.前端处理,6.返回结果 , 正在下载...==> [INFO]https://www.doubao.com/chat/collection/687687510791426?type=Thread [Size=180kb] Ok...
             //进度条 展示下载文件总大小和已下载,末尾展示 文件总个数和已下载个数
+            //https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js
             return Ok();
         }
         [HttpPost("check-short-code")]
@@ -56,6 +57,7 @@ namespace IES.ExamServer.Controllers
            
             string shortCode = $"{json["shortCode"]}";
             string evaluationId = $"{json["evaluationId"]}";
+            string deviceId = $"{json["deviceId"]}";
             Expression<Func<EvaluationClient, bool>> predicate = x => true;
 
             if (!string.IsNullOrEmpty(shortCode))
@@ -160,30 +162,159 @@ namespace IES.ExamServer.Controllers
                 status = 4;
                 _liteDBFactory.GetLiteDatabase().GetCollection<EvaluationClient>().Insert(evaluationLocal);
             }
-            List<int> file_intact = new List<int>();
+            List<string> file_error = new List<string>();
             if (evaluationLocal!=null)
             {
+
+
+                await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file, 
+                    new MessageContent {messageType=Constant._Message_type_message, status=0, content="开始检查评测信息文件.." });
                 //校验本地文件数据
                 string packagePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "package");
                 if (!Directory.Exists(packagePath))
                     Directory.CreateDirectory(packagePath);
                 string evaluationPath = Path.Combine(packagePath, evaluationLocal.id!);
-                await Task.Delay(DelayMacro);
-                if (!System.IO.File.Exists(Path.Combine(evaluationPath, "evaluation.json")))
+               // await Task.Delay(DelayMacro);
+                int msg_status = Constant._Message_status_info;
+                string path_evaluation = Path.Combine(evaluationPath, "evaluation.json");
+                if (!System.IO.File.Exists(path_evaluation))
                 {
-                    file_intact.Add(0);
+                    file_error.Add("evaluation");
+                    msg_status=Constant._Message_status_error;
                 }
-                await Task.Delay(DelayMacro);
-                if (!System.IO.File.Exists(Path.Combine(evaluationPath, "groupList.json")))
+                else 
                 {
-                    file_intact.Add(0);
+                    msg_status=Constant._Message_status_success;
                 }
-                await Task.Delay(DelayMacro);
-                if (!System.IO.File.Exists(Path.Combine(evaluationPath, "source.json")))
+                //数据格式:  [消息][信息/错误/警告][15:43]=>[开始检查评测信息文件...]
+                //数据格式:  [检查][成功/失败][15:43]=>[评测数据文件:/wwwroot/package/623a9fe6-5445-0938-ff77-aeb80066ef27/evaluation.json]
+                //数据格式:  [下载][成功/失败][15:43]=>[评测数据文件:/wwwroot/package/623a9fe6-5445-0938-ff77-aeb80066ef27/evaluation.json][1024kb][15ms]
+                await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file, 
+                    new MessageContent { messageType= Constant._Message_type_check, status=msg_status,content=$"评测数据文件:{path_evaluation}" });
+                //await Task.Delay(DelayMacro);
+                string path_groupList = Path.Combine(evaluationPath, "groupList.json");
+                msg_status =Constant._Message_status_info;
+                if (!System.IO.File.Exists(path_groupList))
                 {
-                    file_intact.Add(0);
+                    file_error.Add("groupList");
+                    msg_status=Constant._Message_status_error;
                 }
-                await Task.Delay(DelayMacro);
+                else
+                {
+                    msg_status=Constant._Message_status_success;
+                }
+                await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file, 
+                    new MessageContent { messageType= Constant._Message_type_check, status=msg_status, content=$"评测名单文件:{path_groupList}" });
+                //await Task.Delay(DelayMacro);
+                string path_source = Path.Combine(evaluationPath, "source.json");
+                msg_status = Constant._Message_status_info;
+                if (!System.IO.File.Exists(path_source))
+                {
+                    file_error.Add("source");
+                    msg_status=Constant._Message_status_error;
+                }
+                else
+                {
+                    msg_status=Constant._Message_status_success;
+                }
+                await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
+                    new MessageContent { messageType= Constant._Message_type_check, status=msg_status, content=$"评测原始数据:{path_source}" });
+               // await Task.Delay(DelayMacro);
+                msg_status =Constant._Message_status_info;
+                try {
+
+                    string evaluation_str = await System.IO.File.ReadAllTextAsync(path_evaluation);
+                    JsonNode? evaluation_data = JsonSerializer.Deserialize<JsonNode>(evaluation_str);
+                    
+                    if (evaluation_data!=null) 
+                    {
+                        EvaluationClient? evaluationClient = JsonSerializer.Deserialize<EvaluationClient>(evaluation_data["evaluationClient"]);
+                        if (evaluationClient!=null) 
+                        {
+                            if ((!string.IsNullOrWhiteSpace(evaluationLocal.blobHash) && evaluationLocal.blobHash.Equals(evaluationClient.blobHash))
+                                &&(evaluationLocal.blobTime==evaluationClient.blobTime)
+                                &&(evaluationLocal.blobCount== evaluationClient.blobCount)
+                                &&(evaluationLocal.blobSize== evaluationClient.blobSize)&& (evaluationLocal.dataTime==evaluationClient.dataTime)
+                                &&(evaluationLocal.dataSize==evaluationClient.dataSize)&&(evaluationLocal.webviewCount==evaluationClient.webviewCount)
+                                &&(evaluationLocal.webviewSize== evaluationClient.webviewSize)
+                                &&(evaluationLocal.webviewTime== evaluationClient.webviewTime)
+                                &&(!string.IsNullOrWhiteSpace(evaluationLocal.webviewPath)&&  evaluationLocal.webviewPath.Equals(evaluationClient.webviewPath)))
+                            {
+                                msg_status=1;
+                            }
+                            else
+                            {
+                                msg_status=Constant._Message_status_error;
+                            }
+                            await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
+                                new MessageContent { messageType=Constant._Message_type_message, status=msg_status, content="校验本地数据文件..." });
+                        }
+
+                        List<EvaluationExam>? evaluationExams = JsonSerializer.Deserialize<List<EvaluationExam>>(evaluation_data["evaluationExams"]);
+                        if (evaluationExams.IsNotEmpty()) 
+                        {
+                            string path_papers = Path.Combine(evaluationPath, "papers");
+                            var papers_files = FileHelper.ListAllFiles(path_papers);
+                            foreach (var evaluationExam in evaluationExams!)
+                            {
+
+                                int paperIndex = 0;
+                                foreach (var paper in evaluationExam.papers) 
+                                {
+                                    paperIndex++;
+                                    List<MessageContent> contents = new List<MessageContent>();
+                                    int paper_error_count = 0;
+                                    foreach (var blobInfo in paper.blobs)
+                                    {
+                                        msg_status=Constant._Message_status_info;
+                                        if (!string.IsNullOrWhiteSpace(blobInfo.path))
+                                        {
+                                           
+                                            var file = papers_files.Find(x => x.Contains(blobInfo.path));
+                                            if (file!=null)
+                                            {
+                                                msg_status=1;
+                                                msg_status=Constant._Message_status_success;
+                                            }
+                                            else {
+                                                msg_status=Constant._Message_status_error;
+                                                paper_error_count++;
+                                            }
+
+                                        }
+                                        else {
+                                            msg_status=Constant._Message_status_warning; ;
+                                            paper_error_count++;
+                                        }
+                                        contents.Add(new MessageContent { messageType=Constant._Message_type_check, status=msg_status, content=$"试卷文件信息:{paper.paperName}" });
+                                    }
+                                    int paper_msg_status = Constant. _Message_status_info;
+                                    if (paper_error_count>0)
+                                    {
+                                        paper_msg_status=Constant._Message_status_error;
+                                    }
+                                    else {
+                                        paper_msg_status=Constant._Message_status_success;
+                                    }
+                                    await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file, 
+                                        new MessageContent {
+                                            messageType=Constant._Message_type_message,
+                                            status=paper_msg_status,
+                                            content=$"试卷名称:[{paperIndex}]{evaluationExam.examName}-{evaluationExam.subjectName}-{paper.paperName}\r\n文件数量:{paper.blobs.Count()},检测成功数量:{contents.Count(x => x.status==Constant._Message_status_success)},检测异常数量{contents.Count(x => x.status==Constant._Message_status_error)}",
+                                            contents=contents
+                                        });
+                                }
+                            }
+                        }
+                    }
+                }
+                catch (Exception e) { 
+                
+                }
+                await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file, 
+                    new MessageContent { messageType=Constant._Message_type_message, status=0, content="提取评测数据文件..." });
+                
+
             }
 
             return Ok(new {code=200, evaluation= evaluationLocal,data,blob,webview,dataSize,blobSize,webviewSize,status });

+ 244 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/DI/CustomFileLoggerProvider.cs

@@ -0,0 +1,244 @@
+using Microsoft.Extensions.Logging;
+using System.Collections.Concurrent;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Unicode;
+using System.Threading.Channels;
+
+namespace IES.ExamServer.DI
+{
+    public class CustomFileLoggerProvider : ILoggerProvider
+    {
+        private readonly string _logDirectory;
+        private readonly bool _enableConsoleOutput;
+        private readonly string _timestamp;
+        private readonly ConcurrentDictionary<string, CustomFileLogger> _loggers = new ConcurrentDictionary<string, CustomFileLogger>();
+
+        public CustomFileLoggerProvider(string logDirectory, bool enableConsoleOutput)
+        {
+            _logDirectory = logDirectory;
+            _enableConsoleOutput = enableConsoleOutput;
+            _timestamp = DateTime.Now.ToString("yyMMddHH"); // 生成时间戳
+            if (!Directory.Exists(_logDirectory))
+            {
+                Directory.CreateDirectory(_logDirectory);
+            }
+        }
+
+        public ILogger CreateLogger(string categoryName)
+        {
+            return _loggers.GetOrAdd(categoryName, name => new CustomFileLogger(_logDirectory, name, _enableConsoleOutput, _timestamp));
+        }
+
+        public void Dispose()
+        {
+            _loggers.Clear();
+        }
+    }
+    public class CustomFileLogger : ILogger
+    {
+        private readonly string _logDirectory;
+        private readonly string _categoryName;
+        private readonly bool _enableConsoleOutput;
+        private static readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
+        private readonly List<string> _logBuffer = new List<string>();
+      //  private readonly Timer _flushTimer;
+        private readonly string _timestamp;
+        private readonly Channel<string> _consoleLogChannel;
+        private readonly Channel<string> _fileLogChannel;
+
+        public CustomFileLogger(string logDirectory, string categoryName, bool enableConsoleOutput, string timestamp)
+        {
+            _logDirectory = logDirectory;
+            _categoryName = categoryName;
+            _enableConsoleOutput = enableConsoleOutput;
+            // 每 5 秒刷新一次日志缓冲区
+           // _flushTimer = new Timer(FlushLogs, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
+            _timestamp=timestamp;
+            // 创建无界 Channel
+            _consoleLogChannel = Channel.CreateUnbounded<string>();
+            // 创建无界 Channel
+            _fileLogChannel = Channel.CreateUnbounded<string>();
+            // 启动后台任务处理控制台日志
+            _ = Task.Run(ProcessConsoleLogs);
+            // 启动后台任务处理文件日志
+            _ = Task.Run(ProcessFileLogs);
+        }
+
+        public IDisposable BeginScope<TState>(TState state) => null;
+
+        public bool IsEnabled(LogLevel logLevel) => true;
+
+        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+        {
+            var logMessage = formatter(state, exception);
+            var logFileName = $"{logLevel}_{_timestamp}.log"; // 按时间戳命名文件
+            var logFilePath = Path.Combine(_logDirectory, logFileName);
+
+            // 异步写入文件
+           // _ = WriteToFileAsync(logFilePath, logLevel, logMessage);
+            // 将日志消息写入 Channel(不阻塞)
+            _fileLogChannel.Writer.TryWrite($"{logFilePath}|||{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}{Environment.NewLine}");
+            // 输出到控制台
+            if (_enableConsoleOutput)
+            {
+                // Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}");
+                // _ = Task.Run(() => Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}")); 
+                _consoleLogChannel.Writer.TryWrite($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}");
+            }
+        }
+        private async Task ProcessConsoleLogs()
+        {
+            await foreach (var logMessage in _consoleLogChannel.Reader.ReadAllAsync())
+            {
+                Console.WriteLine(logMessage); // 输出到控制台
+            }
+        }
+        private async Task ProcessFileLogs()
+        {
+            await foreach (var logEntry in _fileLogChannel.Reader.ReadAllAsync())
+            {
+                try
+                {
+                    // 解析日志条目
+                    var parts = logEntry.Split("|||");
+                    var logFilePath = parts[0];
+                    var logMessage = parts[1];
+
+                    // 异步写入文件
+                    await File.AppendAllTextAsync(logFilePath, logMessage);
+                }
+                catch (Exception ex)
+                {
+                    // 处理文件写入异常
+                    // Console.WriteLine($"Failed to write log to file: {ex.Message}");
+                    _consoleLogChannel.Writer.TryWrite($"Failed to write log to file: {ex.Message}");
+                }
+            }
+        }
+     
+
+        //private async void FlushLogs(object state)
+        //{
+        //    List<string> logsToWrite;
+        //    lock (_logBuffer)
+        //    {
+        //        if (_logBuffer.Count == 0) return;
+        //        logsToWrite = new List<string>(_logBuffer);
+        //        _logBuffer.Clear();
+        //    }
+
+        //    var logFileName = $"{_categoryName}_{_timestamp}.log";
+        //    var logFilePath = Path.Combine(_logDirectory, logFileName);
+
+        //    await _fileLock.WaitAsync(); // 获取锁
+        //    try
+        //    {
+        //        await File.AppendAllLinesAsync(logFilePath, logsToWrite);
+        //    }
+        //    finally
+        //    {
+        //        _fileLock.Release(); // 释放锁
+        //    }
+        //}
+        public void Dispose()
+        {
+           // _flushTimer?.Change(Timeout.Infinite, 0); // 停止定时器
+           // FlushLogs(null); // 最后一次刷新日志
+        }
+    }
+    public static class LoggerExtensions
+    {
+        //private static readonly SemaphoreSlim _dataFileLock = new SemaphoreSlim(1, 1);
+        //private static readonly Channel<string> _fileLogChannel = Channel.CreateUnbounded<string>();
+        //private static readonly Channel<string> _consoleLogChannel = Channel.CreateUnbounded<string>();
+        private static readonly Channel<string> _fileLogChannel = Channel.CreateBounded<string>(new BoundedChannelOptions(10000)
+        {
+            FullMode = BoundedChannelFullMode.DropWrite // 
+        });
+
+        private static readonly Channel<string> _consoleLogChannel = Channel.CreateBounded<string>(new BoundedChannelOptions(10000)
+        {
+            //DropOldest: 此模式会移除并丢弃通道中最旧的那个数据项,以便为正在写入的新数据腾出空间。通道里最早进入的数据会被舍弃,从而让新的数据可以进入通道。
+            //DropNewest:  在这种模式下,为了给要写入的新数据项腾出空间,会移除并丢弃通道中最新的那个数据项。也就是说,新的数据会覆盖掉原本在通道里最新进入的元素,保证新数据能被写入。
+            //Wait:  当使用这种模式时,如果尝试向已满的有界通道写入数据,调用写入操作的线程会等待,直到通道中有空间可用,然后再完成写入操作。这意味着线程会被阻塞,直到可以成功放入新的数据项。
+            FullMode = BoundedChannelFullMode.DropWrite // 直接丢弃当前正要写入的这个数据项,通道中的内容保持不变,相当于放弃这次写入操作。
+        });
+        //懒加载
+        private static readonly Lazy<Task> _fileLogProcessor = new Lazy<Task>(() => Task.Run(ProcessFileLogs));
+        private static readonly Lazy<Task> _consoleLogProcessor = new Lazy<Task>(() => Task.Run(ProcessConsoleLogs));
+        private static readonly JsonSerializerOptions jsonSerializerOptions ;
+        static LoggerExtensions(){
+            jsonSerializerOptions= new JsonSerializerOptions { Encoder=JavaScriptEncoder.Create(UnicodeRanges.All) };
+        }
+
+        public static void LogData<T>(this ILogger logger, T data, string id)
+        {
+            // 确保后台任务已启动,现在就要用确保启动
+            _ = _fileLogProcessor.Value;
+            _ = _consoleLogProcessor.Value;
+            var logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Logs", "DataLogs");
+            if (!Directory.Exists(logDirectory))
+            {
+                Directory.CreateDirectory(logDirectory);
+            }
+
+            var logFilePath = Path.Combine(logDirectory, $"{id}.log");
+            var logMessage = System.Text.Json.JsonSerializer.Serialize(data, jsonSerializerOptions);
+            _fileLogChannel.Writer.TryWrite($"{logFilePath}|||{DateTime.Now:yyyy-MM-dd HH:mm:ss} [Data] {logMessage}{Environment.NewLine}");
+            //_ = Task.Run(async () =>
+            //{
+            //    await _dataFileLock.WaitAsync(); // 获取锁
+            //    try
+            //    {
+            //        await File.AppendAllTextAsync(logFilePath, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [Data] {logMessage}{Environment.NewLine}");
+            //    }
+            //    finally
+            //    {
+            //        _dataFileLock.Release(); // 释放锁
+            //    }
+            //});
+        }
+        static async Task ProcessFileLogs()
+        {
+            try
+            {
+                await foreach (var logEntry in _fileLogChannel.Reader.ReadAllAsync())
+                {
+                   
+                    try
+                    {
+                        // 解析日志条目
+                        var parts = logEntry.Split("|||");
+                        var logFilePath = parts[0];
+                        var logMessage = parts[1];
+
+                        // 异步写入文件
+                        await File.AppendAllTextAsync(logFilePath, logMessage, Encoding.UTF8);
+                    }
+                    catch (Exception ex)
+                    {
+                        // 处理文件写入异常
+                        // Console.WriteLine($"Failed to write log to file: {ex.Message}");
+                        _consoleLogChannel.Writer.TryWrite($"Failed to write log to file: {ex.Message}");
+                    }
+                }
+            }
+            catch (Exception ex) {
+                _consoleLogChannel.Writer.TryWrite($"Failed to write log to file: {ex.Message}");
+            }
+            
+            // 循环结束后,检查是否有剩余的日志条目
+          
+        }
+         
+        static async Task ProcessConsoleLogs()
+        {
+            await foreach (var logMessage in _consoleLogChannel.Reader.ReadAllAsync())
+            {
+                Console.WriteLine(logMessage); // 输出到控制台
+            }
+        }
+    }
+}

+ 28 - 19
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/DI/SignalRHost/SignalRExamServerHub.cs

@@ -30,28 +30,36 @@ namespace IES.ExamServer.DI.SignalRHost
 
         public async Task SendMessage(string clientId, string grant_type, MessageContent content )
         {
-            SignalRClient  client = _memoryCache.Get<SignalRClient>($"{Constant._KeySignalRClientClients}:{clientId}");  
-            if (client != null)
+            //双向检测是否连接。
+            SignalRClient signalRClient = _memoryCache.Get<SignalRClient>($"{Constant._KeySignalRClientClients}:{clientId}");
+            if (signalRClient!=null)
             {
-                switch (true)
-                {
-                    case bool when grant_type.Equals(Constant._Message_grant_type_check_file):
-                        {
-                            await Clients.Client(client.connid!).ReceiveMessage(new CheckFileMessageBody
+                signalRClient = _memoryCache.Get<SignalRClient>($"{Constant._KeySignalRClientConnects}:{signalRClient.connid}");
+            }
+            if (signalRClient != null)
+            {
+                try {
+                    switch (true)
+                    {
+                        case bool when grant_type.Equals(Constant._Message_grant_type_check_file):
                             {
-                                content = content.content,
-                                status = content.status,
-                                clientid = clientId,
-                                connid = client.connid,
-                                grant_type = grant_type,
-                                time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
-                                type = Constant._Message_grant_type_check_file,
-                            });
+                                await Clients.Client(signalRClient.connid!).ReceiveMessage(new CheckFileMessageBody
+                                {
+                                    content = content.content,
+                                    status = content.status,
+                                    clientid = clientId,
+                                    connid = signalRClient.connid,
+                                    grant_type = grant_type,
+                                    time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
+                                    type = Constant._Message_type_message,
+                                    contents = content.contents
+                                });
+                                break;
+                            }
+                        default:
                             break;
-                        }
-                    default:
-                        break;
-                }
+                    }
+                } catch { }
             }
         }
 
@@ -97,6 +105,7 @@ namespace IES.ExamServer.DI.SignalRHost
                                     type=Constant._Message_type_message,
                                     status = Constant._Message_status_success,
                                     time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
+                                    
                                 });
                                 break;
                             }

+ 2 - 2
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Helpers/CollectionHelper.cs

@@ -7,7 +7,7 @@
         /// </summary>
         /// <param name="collection"></param>
         /// <returns></returns>
-        public static bool IsEmpty<T>(this IEnumerable<T> collection)
+        public static bool IsEmpty<T>(this IEnumerable<T>? collection)
         {
             if (collection != null && collection.Any())
             {
@@ -23,7 +23,7 @@
         /// </summary>
         /// <param name="collection"></param>
         /// <returns></returns>
-        public static bool IsNotEmpty<T>(this IEnumerable<T> collection)
+        public static bool IsNotEmpty<T>(this IEnumerable<T>? collection)
         {
             if (collection != null && collection.Any())
             {

+ 37 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Helpers/FileHelper.cs

@@ -0,0 +1,37 @@
+namespace IES.ExamServer.Helpers
+{
+    public class FileHelper
+    {
+        /// <summary>
+        /// 列出文件夹下的所有子文件及子文件夹的子文件
+        /// </summary>
+        /// <param name="directoryPath"></param>
+        /// <param name="filter">获取指定匹配模式的文件,如后缀, local.json</param>
+        /// <returns></returns>
+        public static List<string> ListAllFiles(string directoryPath, string filter = null)
+        {
+            List<string> filePaths = new List<string>();
+            DirectoryInfo dirInfo = new DirectoryInfo(directoryPath);
+
+            // 获取目录下的所有文件(包括子目录中的文件)
+            FileInfo[] files = dirInfo.GetFiles("*", SearchOption.AllDirectories);
+
+            foreach (FileInfo file in files)
+            {
+                if (string.IsNullOrWhiteSpace(filter))
+                {
+                    filePaths.Add(file.FullName);
+                    continue;
+                }
+                else
+                {
+                    if (file.FullName.Contains(filter))
+                    {
+                        filePaths.Add(file.FullName);
+                    }
+                }
+            }
+            return filePaths;
+        }
+    }
+}

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

@@ -16,6 +16,7 @@
 	</ItemGroup>
 	
 	<ItemGroup>
+		<Folder Include="Logs\DataLogs\" />
 		<Folder Include="wwwroot\package\" />
 	</ItemGroup>
 	

+ 12 - 1
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Models/SignalRClient.cs

@@ -23,6 +23,10 @@
         /// </summary>
         public string? content { get; set; }
         /// <summary>
+        /// 批量消息
+        /// </summary>
+        public List<MessageContent> contents { get; set; } = new List<MessageContent>();    
+        /// <summary>
         /// 消息创建时间
         /// </summary>
         public virtual long time { get; set; }
@@ -76,7 +80,14 @@
         /// 消耗时间
         /// </summary>
         public long cost { get; set; }
-
+        /// <summary>
+        /// 消息类型
+        /// </summary>
+        public string? messageType {  get; set; }
+        /// <summary>
+        /// 批量信息
+        /// </summary>
+        public List<MessageContent> contents { get; set; } = new List<MessageContent>();
     }
  
     public class SignalRClient

+ 26 - 3
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Program.cs

@@ -16,6 +16,8 @@ using IES.ExamServer.Helpers;
 using Microsoft.Extensions.Hosting;
 using System.Security.Principal;
 using Microsoft.Extensions.FileProviders;
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
 
 namespace IES.ExamServer
 {
@@ -31,7 +33,7 @@ namespace IES.ExamServer
             //    Console.WriteLine("The application is already running.");
             //    return;
             //}
-           // ProcessHelper.CloseConhost();
+            //ProcessHelper.CloseConhost();
             //AppDomain.CurrentDomain.ProcessExit += OnExit;
             var builder = WebApplication.CreateBuilder(args);
 
@@ -46,7 +48,12 @@ namespace IES.ExamServer
             builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
             builder.Services.AddSpaStaticFiles(opt => opt.RootPath = "ClientApp/dist");
             // Add services to the container.
-            builder.Services.AddControllersWithViews();
+            builder.Services.AddControllersWithViews().AddJsonOptions(options =>
+            {
+                // 设置 JSON 序列化选项
+                options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); // 允许所有 Unicode 字符
+                options.JsonSerializerOptions.WriteIndented = true; // 格式化输出(可选)
+            }); ;
             builder.Services.AddHttpClient();
             builder.Services.AddSignalR();
             builder.Services.AddHttpContextAccessor();
@@ -78,7 +85,11 @@ namespace IES.ExamServer
                             .AllowAnyMethod();
                 });
             });
-           builder.Services.AddMvcFilter<AuthTokenActionFilter>();
+            builder.Services.AddMvcFilter<AuthTokenActionFilter>();
+            // 添加自定义日志提供程序
+            //builder.Logging.ClearProviders();
+            //bool enableConsoleOutput = true;
+            //builder.Logging.AddProvider(new CustomFileLoggerProvider(Path.Combine(Directory.GetCurrentDirectory(), "Logs"), enableConsoleOutput));
             var app = builder.Build();
 
             // Configure the HTTP request pipeline.
@@ -188,6 +199,7 @@ namespace IES.ExamServer
             {
                
                 var server = app.Services.GetService<IServer>();
+                var logger = app.Services.GetRequiredService<ILogger<Program>>();
                 var d = server?.Features.Get<IServerAddressesFeature>();
                 IEnumerable<string>? _url = server?.Features.Get<IServerAddressesFeature>()?.Addresses;
                 ServerDevice serverDevice = IndexService.GetServerDevice(remote, region, _url);
@@ -227,6 +239,7 @@ namespace IES.ExamServer
                 //}
                 //serverDevice.domainStatus=domainStatus;
                 //serverDevice.domain=domain;
+                logger.LogInformation($"服务端设备信息:{JsonSerializer.Serialize(serverDevice,options: new JsonSerializerOptions { Encoder =JavaScriptEncoder.Create(UnicodeRanges.All)})}");
                 cache.Set(Constant._KeyServerDevice, serverDevice);
             });
 
@@ -236,6 +249,16 @@ namespace IES.ExamServer
                 Console.WriteLine("The application is stopping. Performing cleanup...");
                 // 在这里添加清理资源、保存数据等逻辑
             });
+            app.MapGet("/hello",   (ILogger<Program> logger) =>
+            {
+                logger.LogInformation("This is an information log.");
+                logger.LogError("This is an error log.");
+
+                var data = new { Id = 123, Name = "Test Data服务端设备信息" };
+                  logger.LogData(data, data.Id.ToString());
+
+                return "Hello World!";
+            });
             await app.RunAsync();
         }
         //static void OnExit(object sender, EventArgs e)

+ 1 - 0
TEAMModelOS/Controllers/Both/EvaluationSyncInfoController.cs

@@ -58,6 +58,7 @@ namespace TEAMModelOS.Controllers.Both
         //#endif
         public async Task<IActionResult> FindSyncInfo(JsonNode request) 
         {
+
             return Ok();
         }