using ICSharpCode.SharpZipLib.GZip; using IES.ExamLibrary.Models; 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 IES.ExamServer.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using System; using System.Diagnostics.Eventing.Reader; using System.Diagnostics.Metrics; using System.Linq.Expressions; using System.Net.Http; using System.Net.Http.Json; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; using static IES.ExamServer.Services.ManageService; using static System.Reflection.Metadata.BlobBuilder; namespace IES.ExamServer.Controllers { [ApiController] [Route("manage")] public class ManageController : BaseController { private readonly IConfiguration _configuration; private readonly IHttpClientFactory _httpClientFactory; private readonly IMemoryCache _memoryCache; private readonly ILogger _logger; private readonly LiteDBFactory _liteDBFactory; private readonly CenterServiceConnectionService _connectionService; private readonly int DelayMicro = 10;//微观数据延迟 private readonly int DelayMacro = 100;//宏观数据延迟 private readonly IHubContext _signalRExamServerHub; private readonly DataQueue _dataQueue; public ManageController(LiteDBFactory liteDBFactory, ILogger logger, IConfiguration configuration, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, CenterServiceConnectionService connectionService, IHubContext signalRExamServerHub, DataQueue dataQueue) { _logger = logger; _configuration=configuration; _httpClientFactory=httpClientFactory; _memoryCache=memoryCache; _liteDBFactory=liteDBFactory; _connectionService=connectionService; _signalRExamServerHub=signalRExamServerHub; _dataQueue=dataQueue; } /// /// 导出数据 ///通过线上回传数据需要鉴权验证等操作。 ///通过离线包回传数据需要加密操作 /// /// /// [HttpPost("export-evaluation-result")] [AuthToken("admin", "teacher", "visitor")] public IActionResult ExportEvaluationResult(JsonNode json) { string evaluationId = $"{json["evaluationId"]}"; string shortCode = $"{json["shortCode"]}"; string openCode = $"{json["openCode"]}"; string? loginToken = HttpContext.GetXAuth("AuthToken"); EvaluationClient? evaluationClient = _liteDBFactory.GetLiteDatabase().GetCollection() .FindOne(x => x.id!.Equals(evaluationId) && x.openCode!.Equals(openCode)); //已作答 int answerCount = 0; //未作答 int absentCount = 0; List subjectPushDatas = new List(); List subjectPushNew = new List(); if (evaluationClient!=null) { var datas = _liteDBFactory.GetLiteDatabase().GetCollection() .Find(x => !string.IsNullOrWhiteSpace(x.evaluationId) && x.evaluationId!.Equals(evaluationId)).ToList(); if (datas.IsNotEmpty()) { subjectPushDatas.AddRange(datas); } var data = _liteDBFactory.GetLiteDatabase().GetCollection() .Find(x => !string.IsNullOrWhiteSpace(x.evaluationId) && x.evaluationId.Equals(evaluationId)); if (data.IsNotEmpty()) { foreach (var item in data) { foreach (var subjectResult in item.subjectResults) { if (subjectResult.finished==1) { SubjectPushData subjectPushData = new SubjectPushData(subjectResult, item); subjectPushData.loginToken=loginToken; if (!datas.Exists(x => x.id!.Equals(subjectPushData.id))) { //数据丢失导致的推送SubjectPushData 没有被保存的问题 subjectPushDatas.Add(subjectPushData); subjectPushNew.Add(subjectPushData); } } else { absentCount++; } } } } if (subjectPushNew.IsNotEmpty()) { _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(subjectPushNew); } } answerCount=subjectPushDatas.Count(x => x.finished==1 && x.answers.IsNotEmpty()); absentCount+=subjectPushDatas.Count()-answerCount; //TODO 以及增加 作答文件的相关数据。 return Ok(new { answerCount, absentCount, subjectPushDatas }); } /// /// 手动推送 ///通过线上回传数据需要鉴权验证等操作。 ///通过离线包回传数据需要加密操作 /// /// /// [HttpPost("manual-push")] [AuthToken("admin", "teacher", "visitor")] public async Task ManualPush(JsonNode json) { string evaluationId = $"{json["evaluationId"]}"; //string shortCode = $"{json["shortCode"]}"; string openCode = $"{json["openCode"]}"; string deviceId = $"{json["deviceId"]}"; EvaluationClient? evaluationClient = _liteDBFactory.GetLiteDatabase().GetCollection() .FindOne(x => x.id!.Equals(evaluationId) && x.openCode!.Equals(openCode)); int count = 0; string? loginToken = HttpContext.GetXAuth("AuthToken"); if (evaluationClient!=null) { var datas = _liteDBFactory.GetLiteDatabase().GetCollection() .Find(x => !string.IsNullOrWhiteSpace(x.evaluationId) && x.evaluationId!.Equals(evaluationId)).ToList(); if (datas.IsNotEmpty()) { foreach (var item in datas) { if (item.pushed<=1 && item.finished==1) { item.order=count; item.loginToken=loginToken; count++; //_logger.LogInformation($"推送数据加入队列=>>序号:{subjectPushData.order}--学号:{item.studentId}--姓名:{item.studentName}--科目:{subjectResult.subjectName}"); await _dataQueue.TryAddAsync(item); } } } var data = _liteDBFactory.GetLiteDatabase().GetCollection() .Find(x =>!string.IsNullOrWhiteSpace(x.evaluationId) && x.evaluationId.Equals(evaluationId) ); List subjectPushDatas = new List(); if (data.IsNotEmpty()) { foreach (var item in data) { foreach (var subjectResult in item.subjectResults) { if ( subjectResult.finished==1) { SubjectPushData subjectPushData= new SubjectPushData(subjectResult, item); subjectPushData.order=count; subjectPushData.loginToken=loginToken; if (!datas.Exists(x => x.id!.Equals(subjectPushData.id))) { //数据丢失导致的推送SubjectPushData 没有被保存的问题 subjectPushDatas.Add(subjectPushData); //未被推送的数据 if (subjectPushData.pushed<=1 && subjectPushData.finished==1) { count++; subjectPushData.pushed=1; await _dataQueue.TryAddAsync(subjectPushData); } } } } } } if (subjectPushDatas.IsNotEmpty()) { _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(subjectPushDatas); } } return Ok(new { code=200, message="推送成功!", count }); } /// /// 清理缓存,列出缓存占用空间,type =list列出,type=clear清理,不能清理近期及正在激活的数据,并且提示清理中暂未上传或者导出的数据。 /// /// /// [HttpPost("clean-cache")] [AuthToken("admin", "teacher", "visitor")] public async Task CleanCache(JsonNode json) { return Ok(); } //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 /* int data = 0,blob=0, groupList=0 { "evaluationId":"idssss", "shortCode":"1234567890", "ownerId":"hbcn/tmdid", "data":1, "blob":1, "groupList":1 } */ //如果要访问中心,则需要教师登录联网。 [HttpPost("download-package-music")] [AuthToken("admin", "teacher", "visitor")] public async Task DownloadPackageMusic(JsonNode json) { return Ok(); } [HttpPost("download-package")] [AuthToken("admin", "teacher", "visitor")] public async Task DownloadPackage(JsonNode json) { var token = GetAuthTokenInfo(); //检查试卷文件完整性 List successMsgs = new List(); List errorMsgs = new List(); EvaluationCheckFileResult result = new EvaluationCheckFileResult() { successMsgs=successMsgs, errorMsgs=errorMsgs }; if (token.scope.Equals(ExamConstant.ScopeTeacher) || token.scope.Equals(ExamConstant.ScopeVisitor)) { if (_connectionService.centerIsConnected) { Teacher? teacher = _liteDBFactory.GetLiteDatabase().GetCollection().FindOne(x => x.id!.Equals(token.id)); string id = $"{json["evaluationId"]}"; string shortCode = $"{json["shortCode"]}"; string deviceId = $"{json["deviceId"]}"; EvaluationClient? evaluationClient = _liteDBFactory.GetLiteDatabase().GetCollection().FindOne(x => x.id!.Equals(id) && x.shortCode!.Equals(shortCode)); if (teacher != null && evaluationClient!= null) { int msg_status = Constant._Message_status_info; string msg_content = msg_status.Equals(Constant._Message_status_success) ? "成功" : "失败"; await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, status = 0, step=1, content = "检测云端数据是否匹配..." }); var dataInfo= await GetEvaluationFromCenter(GetXAuthToken(), _configuration, _httpClientFactory, shortCode, evaluationClient.id!); if (dataInfo.centerCode.Equals("200")&& dataInfo.evaluationCloud!=null) { await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, status = Constant._Message_status_success, step = 1, content = $"云端数据检测结果:{dataInfo.centerMsg},状态:{dataInfo.centerCode}" },true); string? CenterUrl = _configuration.GetValue("ExamServer:CenterUrl"); var client = _httpClientFactory.CreateClient(); if (client.DefaultRequestHeaders.Contains(Constant._X_Auth_AuthToken)) { client.DefaultRequestHeaders.Remove(Constant._X_Auth_AuthToken); } client.DefaultRequestHeaders.Add(Constant._X_Auth_AuthToken, teacher.x_auth_token); HttpResponseMessage message = await client.PostAsJsonAsync($"{CenterUrl}/blob/sas-read", new { containerName = $"{dataInfo.evaluationCloud.ownerId}" }); string sas = string.Empty; string url = string.Empty; string cnt = string.Empty; if (message.IsSuccessStatusCode) { //url sas timeout name string content = await message.Content.ReadAsStringAsync(); JsonNode? jsonNode = content.ToObject(); if (jsonNode != null) { sas = $"{jsonNode["sas"]}"; cnt = $"{jsonNode["name"]}"; url = $"{jsonNode["url"]}"; msg_status = Constant._Message_status_success; } else { msg_status = Constant._Message_status_error; } } else { msg_status = Constant._Message_status_error; } msg_content = msg_status.Equals(Constant._Message_status_success) ? "成功" : "失败"; await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, status = msg_status, step = 1, content = $"获取云端下载授权=>{msg_content}" }, true); var httpClient = _httpClientFactory.CreateClient(); string packagePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "package"); string evaluationPath = Path.Combine(packagePath, dataInfo.evaluationCloud.id!); //删除文件夹 FileHelper.DeleteFolder(evaluationPath); string evaluationDataPath = Path.Combine(evaluationPath, "data"); if (!Directory.Exists(evaluationDataPath)) { Directory.CreateDirectory(evaluationDataPath); } await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 1, finish=1,status = Constant._Message_status_info, content = $"下载前清理资源" }); string evaluationData = string.Empty; { //evaluation string evaluationUrl = $"{url}/{cnt}/package/{json["evaluationId"]}/data/evaluation.json?{sas}"; HttpResponseMessage dataMessage = await httpClient.GetAsync(evaluationUrl); if (dataMessage.IsSuccessStatusCode) { var content = await dataMessage.Content.ReadAsStringAsync(); evaluationData=content; string path_evaluation = Path.Combine(evaluationDataPath, "evaluation.json"); await System.IO.File.WriteAllTextAsync(path_evaluation, content); successMsgs.Add("评测信息文件evaluation.json文件下载成功!"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 2, finish = 1, status = Constant._Message_status_success, content = $"评测信息文件evaluation.json文件下载成功!" }); } else { errorMsgs.Add("评测信息文件evaluation.json文件下载失败!"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 2, finish = 1, status = Constant._Message_status_error, content = $"评测信息文件evaluation.json文件下载失败!" }, true); } } //{ // //source.json // string sourceUrl = $"{url}/{cnt}/package/{json["evaluationId"]}/data/source.json?{sas}"; // HttpResponseMessage dataMessage = await httpClient.GetAsync(sourceUrl); // if (dataMessage.IsSuccessStatusCode) // { // var content = await dataMessage.Content.ReadAsStringAsync(); // string path_source = Path.Combine(evaluationDataPath, "source.json"); // await System.IO.File.WriteAllTextAsync(path_source, content); // successMsgs.Add("评测数据原始文件source.json文件下载成功!"); // await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, // new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, status = Constant._Message_status_success, content = $"评测数据原始文件source.json文件下载成功!" }); // } // else // { // errorMsgs.Add("评测数据原始文件source.json文件下载失败!"); // await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, // new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, status = Constant._Message_status_error, content = $"评测数据原始文件source.json文件下载失败!" }, true); // } //} { //grouplist.json string grouplistUrl = $"{url}/{cnt}/package/{json["evaluationId"]}/data/grouplist.json?{sas}"; HttpResponseMessage groupListMessage = await httpClient.GetAsync(grouplistUrl); if (groupListMessage.IsSuccessStatusCode) { var content = await groupListMessage.Content.ReadAsStringAsync(); string path_groupList = Path.Combine(evaluationDataPath, "grouplist.json"); await System.IO.File.WriteAllTextAsync(path_groupList, content); successMsgs.Add("评测名单grouplist.json文件下载成功!"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 3, finish = 1, status = Constant._Message_status_success, content = $"评测名单grouplist.json文件下载成功!" }); } else { errorMsgs.Add("评测名单grouplist.json文件下载失败!"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 3, finish = 1, status = Constant._Message_status_error, content = $"评测名单grouplist.json文件下载失败!" }, true); } } { //下载试卷文件 List? evaluationExams = evaluationData.ToObject()?["evaluationExams"]?.ToObject>(); int blobCount = evaluationExams!.SelectMany(x => x.papers).SelectMany(x=>x.blobs).Count(); int currCount = 0; foreach (var evaluationExam in evaluationExams!) { foreach (var evaluationPaper in evaluationExam.papers) { string path_paper = Path.Combine(evaluationPath, $"papers/{evaluationPaper.paperId}"); if (!Directory.Exists(path_paper)) { Directory.CreateDirectory(path_paper); } //最多开启10个线程并行下载 var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 5 }; // 使用 Parallel.ForEachAsync 并行处理每个 blob await Parallel.ForEachAsync(evaluationPaper.blobs, parallelOptions, async (blob, cancellationToken) => { currCount++; double size = Math.Round(blob.size * 1.0 / 1024 / 1024, 2); string? fileName = Path.GetFileName(blob.path); try { // 下载 Blob 文件到本地 //httpClient.Timeout = TimeSpan.FromSeconds(300); HttpResponseMessage blobMessage = await httpClient.GetAsync($"{url}/{cnt}/{blob.path}?{sas}", cancellationToken); if (blobMessage.IsSuccessStatusCode) { byte[] bytes = await blobMessage.Content.ReadAsByteArrayAsync(cancellationToken); string? extension = Path.GetExtension(blob.path); if (extension!=null) { if (extension.Equals(extension.ToUpper())) { string? fileNameWithoutExtension = Path.GetFileNameWithoutExtension(blob.path); await System.IO.File.WriteAllBytesAsync(Path.Combine(path_paper, $"{fileNameWithoutExtension!}_1{extension}"), bytes, cancellationToken); } else { await System.IO.File.WriteAllBytesAsync(Path.Combine(path_paper, fileName!), bytes, cancellationToken); } } await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 4, status = Constant._Message_status_success, content = $"[进度:{currCount}/{blobCount}][大小:{size}kb]{evaluationExam.subjectName},{evaluationPaper.paperName},{fileName}文件下载成功。" }); } else { string? error = await blobMessage.Content.ReadAsStringAsync(cancellationToken); errorMsgs.Add($"{evaluationExam.subjectName},{evaluationPaper.paperName},{blob.path}文件下载失败,{blobMessage.StatusCode},{error}"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 4, status = Constant._Message_status_error, content = $"[进度:{currCount}/{blobCount}][大小:{size}kb]{evaluationExam.subjectName},{evaluationPaper.paperName},{fileName}文件下载失败,{blobMessage.StatusCode},{error}" }, true); // Console.WriteLine($"Error downloading {blob.path},{blobMessage.StatusCode},{error}"); } } catch (Exception ex) { errorMsgs.Add($"{evaluationExam.subjectName},{evaluationPaper.paperName},{blob.path}文件下载错误,{ex.Message}"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 4, status = Constant._Message_status_error, content = $"[进度:{currCount}/{blobCount}][大小:{size}kb]{evaluationExam.subjectName},{evaluationPaper.paperName},{fileName}文件下载错误,{ex.Message}" }, true); // 处理异常 //Console.WriteLine($"Error downloading {blob.path}: {ex.Message}"); } }); } } } _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(dataInfo.evaluationCloud!); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 4, finish = 1, status = Constant._Message_status_info, content = $"评测试卷下载完成" }); (successMsgs, errorMsgs) = await ManageService.CheckFile(dataInfo.evaluationCloud!, successMsgs, errorMsgs, _signalRExamServerHub, _memoryCache, _logger, deviceId, evaluationPath, Constant._Message_grant_type_download_file,step:5); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 5, finish = 1, status = Constant._Message_status_info, content = $"评测完整性验证完成" }); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 6, status = Constant._Message_status_info, content = $"正在创建压缩包,请稍等..." }); //下载完成后,对数据进行检查,然后在加密压缩。 string zipPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "zip"); if (!Directory.Exists(zipPath)) { Directory.CreateDirectory(zipPath); } string zipFilePath = Path.Combine(zipPath, $"{dataInfo.evaluationCloud.id}-{dataInfo.evaluationCloud.blobHash}.zip"); var zipInfo = ZipHelper.CreatePasswordProtectedZip(evaluationPath, zipFilePath, dataInfo.evaluationCloud.openCode!); if (zipInfo.res) { successMsgs.Add("评测数据压缩包创建成功!"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 6, finish = 1, status = Constant._Message_status_success, content = $"评测数据压缩包创建成功!" }); } else { errorMsgs.Add("评测数据压缩包创建失败!"); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 6, finish = 1, status = Constant._Message_status_error, content = $"评测数据压缩包创建失败!" }, true); } } else { string content = $"云端数据检测结果:{dataInfo.centerMsg},状态:{dataInfo.centerCode}"; errorMsgs.Add(content); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_download_file, new MessageContent { dataId = evaluationClient.id, dataName = evaluationClient.name, messageType = Constant._Message_type_message, step = 1, finish = 1, status = Constant._Message_status_error, content = content }, true); } } else { string teacherMsg = teacher==null ? "未找到教师信息" : ""; string evaluationMsg = evaluationClient==null ? "未找到评测信息" : ""; errorMsgs.Add($"用户信息或未找到评测信息!{teacherMsg}{evaluationMsg}"); } } else { errorMsgs.Add($"云端数据中心未连接"); } } else { errorMsgs.Add($"请使用教师或访客账号登录!"); } result.checkError=result.errorMsgs.Count(); result.checkSuccess=result.successMsgs.Count(); result.checkTotal=result.checkSuccess+result.checkError; if (result.errorMsgs.Count()==0) { return Ok(new { code = 200, msg = "下载成功!", result }); } else { return Ok(new { code = 1, msg = "下载失败!", result }); } } /// /// 输入开卷码,查询评测信息,并返回数据。 /// /// /// [HttpPost("open-evaluation")] [AuthToken("admin", "teacher", "visitor")] public async Task OpenEvaluation(JsonNode json) { string deviceId = $"{json["deviceId"]}"; string evaluationId = $"{json["evaluationId"]}"; string openCode = $"{json["openCode"]}"; string shortCode = $"{json["shortCode"]}"; var token = GetAuthTokenInfo(); List successMsgs = new List(); List errorMsgs = new List(); EvaluationCheckFileResult result = new EvaluationCheckFileResult() { successMsgs=successMsgs, errorMsgs =errorMsgs }; if (!string.IsNullOrEmpty(openCode) && !string.IsNullOrEmpty(evaluationId)) { EvaluationClient? evaluationLocal = _liteDBFactory.GetLiteDatabase().GetCollection().FindOne(x => x.id!.Equals(evaluationId) && x.openCode!.Equals(openCode) && x.shortCode!.Equals(shortCode)); if (evaluationLocal!=null) { //ManageService.CheckData(evaluationLocal, null, successMsgs, errorMsgs, _liteDBFactory); await _signalRExamServerHub.SendMessage(_memoryCache, _logger, deviceId, Constant._Message_grant_type_check_file, new MessageContent { dataId=evaluationLocal.id, dataName=evaluationLocal.name, messageType=Constant._Message_type_message, status=0, content="开始检查评测信息文件.." }); //判断文件包的压缩包是否存在。 string zipPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "zip"); //校验本地文件数据 string packagePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "package"); string evaluationPath = Path.Combine(packagePath, evaluationLocal.id!); if (System.IO.File.Exists(Path.Combine(zipPath, $"{evaluationLocal.id}-{evaluationLocal.blobHash}.zip"))) { string key = $"{Constant._KeyEvaluationZipExtract}:{evaluationId}:{openCode}"; successMsgs.Add("加载评测试卷文件包!"); //string keyData = _memoryCache.Get(key); //if (!string.IsNullOrWhiteSpace(keyData)) //{ //} //else { //} //删除文件夹 FileHelper.DeleteFolder(evaluationPath); if (!Directory.Exists(evaluationPath)) { Directory.CreateDirectory(evaluationPath); } //解压文件包 var extractRes = await ZipHelper.ExtractPasswordProtectedZip(Path.Combine(zipPath, $"{evaluationLocal.id}-{evaluationLocal.blobHash}.zip"), evaluationPath, evaluationLocal.openCode!, _signalRExamServerHub,_memoryCache,_logger,deviceId,evaluationLocal); if (extractRes.res) { _memoryCache.Set(key,key,TimeSpan.FromSeconds(30)); successMsgs.Add("评测试卷文件包解压成功!"); (successMsgs, errorMsgs) = await ManageService.CheckFile(evaluationLocal, successMsgs, errorMsgs, _signalRExamServerHub, _memoryCache, _logger, deviceId, evaluationPath, Constant._Message_grant_type_check_file); } else { errorMsgs.Add("评测试卷文件包解压失败!"); //return Ok(new { code = 3, msg = "评测试卷文件包解压失败!" }); } } else { errorMsgs.Add("评测试卷文件包不存在!"); //return Ok(new { code = 3, msg = "评测试卷文件包不存在!" }); } } else { errorMsgs.Add("未找到评测信息!"); // return Ok(new { code = 2, msg = "未找到评测信息!" }); } } else { errorMsgs.Add("评测ID或提取码均未填写!"); // return Ok(new { code = 1, msg = "评测ID或提取码均未填写!" }); } result.checkTotal = result.errorMsgs.Count() + result.successMsgs.Count(); result.checkError= result.errorMsgs.Count(); result.checkSuccess = result.successMsgs.Count(); if (result.errorMsgs.Count()==0) { return Ok(new { code = 200, msg = "校验成功!", result }); } else { return Ok(new { code = 1, msg = "校验失败!", result }); } } /// /// 检查数据包是否有更新,zip是否存在,数据文件是否完整,名单文件是否完整。 /// 如果有智音模块,则需要检查智音音乐缓存是否完整。 /// /// /// [HttpPost("check-evaluation")] [AuthToken("admin", "teacher", "visitor")] public async Task CheckEvaluation(JsonNode json) { string shortCode = $"{json["shortCode"]}"; string evaluationId = $"{json["evaluationId"]}"; string checkCenter = $"{json["checkCenter"]}"; string deviceId = $"{json["deviceId"]}"; string centerCode = string.Empty, centerMsg = string.Empty; Expression> predicate = x => true; var token = GetAuthTokenInfo(); if (!string.IsNullOrEmpty(shortCode) || !string.IsNullOrEmpty(evaluationId)) { if (!string.IsNullOrEmpty(shortCode)) { predicate= predicate.And(x => !string.IsNullOrWhiteSpace(x.shortCode) && x.shortCode.Equals(shortCode)); } if (!string.IsNullOrWhiteSpace(evaluationId)) { predicate= predicate.And(x => x.id!.Equals(evaluationId)); } } else { return Ok(new { code = 400, msg = "评测ID或提取码均未填写!" }); } IEnumerable? evaluationClients = _liteDBFactory.GetLiteDatabase().GetCollection().Find(predicate); EvaluationClient? evaluationLocal = null; EvaluationClient? evaluationCloud = null; if (evaluationClients!=null && evaluationClients.Count()>0) { evaluationLocal= evaluationClients.First(); } List successMsgs = new List(); List errorMsgs = new List(); //从数据中心搜索 if ("1".Equals($"{checkCenter}")) { if (_connectionService.centerIsConnected) { if (token.scope.Equals(ExamConstant.ScopeTeacher) || token.scope.Equals(ExamConstant.ScopeVisitor))//由于已经绑定学校,访客教师也可以访问中心。 { Teacher? teacher = _liteDBFactory.GetLiteDatabase().GetCollection().FindOne(x => x.id!.Equals(token.id)); if (teacher != null) { (evaluationCloud, centerCode, centerMsg)= await ManageService.GetEvaluationFromCenter(GetXAuthToken(), _configuration,_httpClientFactory,shortCode,evaluationId); } else { centerCode = $"401"; centerMsg = "当前登录账号未找到"; } } } else { centerCode = $"404"; centerMsg = "云端数据中心未连接"; } if (centerCode.Equals("200")) { successMsgs.Add($"云端数据检测结果:{centerMsg},状态:{centerCode}"); } else { errorMsgs.Add($"云端数据检测结果:{centerMsg},状态:{centerCode}"); } } EvaluationCheckDataResult checkDataResult; (checkDataResult, evaluationLocal) = ManageService.CheckData( evaluationLocal, evaluationCloud, successMsgs, errorMsgs, _liteDBFactory); Dictionary? evaluation = null; if (evaluationLocal!=null) { string zipPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "zip"); //判断文件包的压缩包是否存在。 if (!System.IO.File.Exists(Path.Combine(zipPath, $"{evaluationLocal.id}-{evaluationLocal.blobHash}.zip"))) { checkDataResult.zip =1; checkDataResult.blob=1; checkDataResult.data=1; checkDataResult.groupList=1; checkDataResult.blobSize=evaluationLocal.blobSize; checkDataResult.dataSize=evaluationLocal.dataSize; checkDataResult.studentCount=evaluationLocal.studentCount; errorMsgs.Add($"评测文件包压缩包不存在,需要重新下载评测!"); } var properties = evaluationLocal.GetType().GetProperties(); evaluation = new Dictionary(); foreach (var property in properties) { if (!property.Name.Equals("openCode")) { evaluation[property.Name] = property.GetValue(evaluationLocal); } } } return Ok(new { code = 200, evaluation = evaluation, result = checkDataResult }); } /// /// 获取当前评测的开考设置信息 /// /// /// [HttpPost("list-evaluation-round")] [AuthToken("admin", "teacher", "visitor")] public IActionResult ListEvaluationRound(JsonNode json) { string evaluationId = $"{json["evaluationId"]}"; string openCode = $"{json["openCode"]}"; string shortCode = $"{json["shortCode"]}"; EvaluationClient? evaluationClient = _liteDBFactory.GetLiteDatabase().GetCollection() .FindOne(x => x.id!.Equals(evaluationId) && x.shortCode!.Equals(shortCode) && x.openCode!.Equals(openCode)); if (evaluationClient!=null) { IEnumerable? settings = _liteDBFactory.GetLiteDatabase().GetCollection().Find(x => x.evaluationId!.Equals(evaluationClient.id)).OrderByDescending(x => x.activate).ThenByDescending(x => x.startline).ThenByDescending(x => x.createTime); if (settings.IsNotEmpty()) { return Ok(new { code = 200, msg = "OK", settings = settings }); } else { return Ok(new { code = 2, msg = "未设置开考信息!" }); } } else { return Ok(new { code = 1, msg = "未找到评测信息!" }); } } /// /// 获取当前评测的开考设置信息 /// /// /// [HttpPost("load-evaluation-round")] [AuthToken("admin", "teacher", "visitor")] public IActionResult LoadEvaluationRound(JsonNode json) { string evaluationId = $"{json["evaluationId"]}"; string openCode = $"{json["openCode"]}"; string shortCode = $"{json["shortCode"]}"; string settingId = $"{json["settingId"]}"; EvaluationClient? evaluationClient = _liteDBFactory.GetLiteDatabase().GetCollection() .FindOne(x => x.id!.Equals(evaluationId) && x.shortCode!.Equals(shortCode) && x.openCode!.Equals(openCode)); EvaluationRoundSetting? setting = null; if (evaluationClient!=null) { setting = _liteDBFactory.GetLiteDatabase().GetCollection().FindOne(x =>x.id!.Equals(settingId) && x.evaluationId!.Equals(evaluationClient.id)); if (setting!=null) { IEnumerable? results = null; var members = _liteDBFactory.GetLiteDatabase().GetCollection().Find(x => x.evaluationId!.Equals(evaluationClient.id) && x.roundId!.Equals(setting.id)); //并获取学生的作答信息 //顺便返回本轮的学生名单 if (members!=null && members.Count()>0) { results = _liteDBFactory.GetLiteDatabase().GetCollection() .Find(x => members.Select(x => x.id).Contains(x.studentId)&&!string.IsNullOrWhiteSpace(x.evaluationId) && x.evaluationId.Equals(evaluationClient.id)); if (results.Count()==members.Count()) { return Ok(new { code = 200, setting, results }); } else { return Ok(new { code = 200,msg="学生作答信息数量不匹配", setting, results }); } } else { return Ok(new { code = 200,msg="未分配学生名单,或名单没有学生!", setting , results }); } } else { return Ok(new { code = 2, msg = "未设置开考信息!" }); } } else { return Ok(new { code = 1, msg = "未找到评测信息!" }); } } /// /// 设置评测开考信息(本轮名单,计时规则,分配试卷等) /// /// /// [HttpPost("setting-evaluation-round")] [AuthToken("admin", "teacher", "visitor")] public IActionResult SettingEvaluationRound(JsonNode json) { EvaluationRoundSetting? setting = json.ToObject(); if (setting!=null) { var db = _liteDBFactory.GetLiteDatabase(); var collection = db.GetCollection() ; //&& x.openCode!.Equals($"{json["openCode"]}")&& x.shortCode!.Equals($"{json["shortCode"]}") string shortCode = $"{json["shortCode"]}"; string openCode = $"{json["openCode"]}"; EvaluationClient ? evaluationClient = collection.FindOne(x => x.id!.Equals(setting.evaluationId) && !string.IsNullOrWhiteSpace(x.shortCode) && x.shortCode.Equals(x.shortCode) && !string.IsNullOrWhiteSpace(x.openCode) && x.openCode.Equals(openCode) ); if (evaluationClient!=null) { IEnumerable settings = _liteDBFactory.GetLiteDatabase().GetCollection().Find(x => x.evaluationId!.Equals(evaluationClient.id)); if (settings != null && settings.Count() > 0) { var datas = settings.ToList(); foreach (EvaluationRoundSetting item in datas) { item.activate = 0; } _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(datas); } /// 判断是否包含所有分组 bool isAllContained = setting.groupList.All(x => evaluationClient.grouplist.Any(y => y.id == x.id)); if (isAllContained && evaluationClient.grouplist.IsNotEmpty()) { var ids= setting.groupList.Select(x => x.id).OrderBy(x=>x); //增加排序,保证id的唯一性 setting.id=ShaHashHelper.GetSHA1($"{evaluationClient.id}_{string.Join("", ids) }"); setting.createTime= DateTimeOffset.Now.ToUnixTimeMilliseconds(); _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(setting); _liteDBFactory.GetLiteDatabase().GetCollection().Upsert(evaluationClient); /// 分配试卷 var(roundStudentPapers, members, results, code, msg)= ManageService.AssignStudentPaper(evaluationClient, setting,_connectionService, _liteDBFactory, _logger); //var (roundStudentPapers, members, results,code,msg) = AssignStudentPaper(evaluationClient, setting); return Ok(new { code = code, msg =msg , setting, results, roundStudentPapers }); } else { return Ok(new { code = 3, msg = "开考名单不在当前评测中!" }); } } else { return Ok(new { code = 2, msg = "未找到评测,请确认评测ID、提取码、开卷码是否正确!" }); } } else { return Ok(new { code = 1, msg = "未完善设置信息!" }); } } /// /// 加载本地的活动列表 /// /// /// [HttpPost("list-local-evaluation")] [AuthToken("admin", "teacher", "visitor")] public IActionResult ListLocalEvaluation(JsonNode json) { if (!string.IsNullOrWhiteSpace($"{_connectionService.serverDevice?.school?.id}")) { string code = $"{_connectionService!.serverDevice!.school!.id}"; IEnumerable? evaluationClients = _liteDBFactory.GetLiteDatabase().GetCollection().Find(x => x.ownerId!.Equals(code)).OrderByDescending(x => x.stime); if (evaluationClients != null) { var result = evaluationClients.Select(client => { var properties = client.GetType().GetProperties(); var anonymousObject = new Dictionary(); foreach (var property in properties) { if (!property.Name.Equals("openCode")) { anonymousObject[property.Name] = property.GetValue(client); } } return anonymousObject; }); return Ok(new { code = 200, evaluation = result }); } else { return Ok(new { code = 200, evaluation = new List() }); } } else { return Ok(new { code = 200, evaluation = new List() }); } } } }