using Dolphin.ExamPictureCut.Domains.Basic; using Dolphin.ExamPictureCut.Domains.Biz; using Dolphin.ExamPictureCut.Domains.Quest; using Dolphin.ExamPictureCut.Exams.Dto; using Dolphin.ExamPictureCut.Extensions; using Flurl.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using NoFurion; using NoFurion.SqlSugar; using SkiaSharp; using SqlSugar; using Volo.Abp.BlobStoring; using Volo.Abp.Domain.Services; using Yitter.IdGenerator; namespace Dolphin.ExamPictureCut.Exams; public class ExamManager : DomainService, IExamManager { private readonly ISqlSugarClient Db; private readonly IBlobContainer _blobContainer; public ExamManager(ISqlSugarClient db, IBlobContainer blobContainer) { Db = db; _blobContainer = blobContainer; } [AutoTran] public async Task ExamStudentGather(ExamStudentGatherEto eto) { var guid = GuidGenerator.Create().ToString("N"); var penSerial = eto.StudentExamNum; var templateIds = JsonConvert.DeserializeObject>(eto.TemplateId); var templates = await Db.Queryable().Where(w => templateIds.Contains(w.Id)) .Select(s => new { s.Id, s.PaperId, s.PartId, s.PageIndex, s.ImgUrl, s.QueData }).ToListAsync(); var paperInfo = templates.Select(s => { var data = JsonConvert.DeserializeObject(s.QueData); return new PaperQueData { TemplateId = s.Id, PaperId = s.PaperId, PartId = s.PartId, PageIndex = s.PageIndex, QueData = data.queData, }; }).OrderBy(s => s.PartId).ThenBy(s => s.PageIndex).ToList(); var paperIds = templates.Select(s => s.PaperId).ToList(); // 获取点阵数据 var lattices = await Db.Queryable() .Where(w => w.PenSerial == penSerial && paperIds.Contains(w.PageSerial) && w.logType == LogType.作业) .Select(s => new PenOfflineData { PageSerial = s.PageSerial, CX = s.CX, CY = s.CY, Time = s.Time, strokeIndex = s.strokeIndex, }).ToListAsync(); if (lattices.Count == 0) { Logger.LogInformation("{ExamSubjectId} {penSerial} 无点阵数据", eto.ExamSubjectId, penSerial); return; } var DbBiz = await GetTenantDb(eto.SchoolId); var kgtDtls = await DbBiz.Queryable() .Where(w => w.ExamSubjectId == eto.ExamSubjectId) .Select(s => new MkExamResult { Id = YitIdHelper.NextId(), StudentNo = penSerial, ExamId = eto.ExamId, ExamSubjectId = eto.ExamSubjectId, QuestionNumber = s.QuestionNum, IsObjectiveQuestion = true, GroupNo = guid, }).ToListAsync(); var zgtSettingDtls = await DbBiz.Queryable().Where(w => w.ExamSubjectId == eto.ExamSubjectId).ToListAsync(); var zgtDtls = zgtSettingDtls.Select(s => new SubjectiveMarkingResult { Id = YitIdHelper.NextId(), ExamSubjectId = eto.ExamSubjectId, ExamSubjectSchoolId = eto.ExamSubjectSchoolId, StudentExamNum = penSerial, QuestionNum = s.QuestionNum, TotalScore = s.Score, SubQuestionCount = s.SubQuestionCount, SubQuestionDetail = s.SubQuestionDetail, GroupNo = guid, BigQuestionNum = s.BigQuestionNum, IsExcess = s.IsExcess, }).ToList(); // 割原题 if (!zgtSettingDtls.Any(s => s.DotPenOriginalImg.IsNotNullOrEmpty())) { var redisLockKey = "LockKey:" + eto.ExamSubjectId; var redisLock = await RedisHelper.GetAsync(redisLockKey); if (string.IsNullOrEmpty(redisLock)) { await RedisHelper.SetAsync(redisLockKey, "1"); foreach (var tmp in templates) { var imgStream = await tmp.ImgUrl.GetStreamAsync(); var bitmap = SKBitmap.Decode(imgStream); } await RedisHelper.SetAsync(redisLockKey, "0"); } else { while (await RedisHelper.GetAsync(redisLockKey) == "1") { Thread.Sleep(500); } } } var kgt = new List>(); var zgt = new List>(); // 纸张Id, 题号, 是否跨页 var pageSerials = new List(); // 需要计算的页 foreach (var paper in paperInfo) { var paperLatts = lattices.Where(w => w.PageSerial == paper.PaperId).ToList(); foreach (var que in paper.QueData) { if (que.type == "1") // 客观题 { if (que.options.Any() && que.options.Any(opt => paperLatts.Any(s => RectExt.IsRectContainsLattice(opt.AnswerArea, s)))) { pageSerials.Add(paper.PaperId); kgt.Add(new(paper.PaperId, que.no)); } } else if (que.type == "2") // 主观题 { if (que.options.Any() && paperLatts.Any(s => RectExt.IsRectContainsLattice(que.options[0].AnswerArea, s))) { pageSerials.Add(paper.PaperId); var ky = false; var quePaperId = paper.PaperId; var queOthPaper = paperInfo.FirstOrDefault(w => w.PartId == paper.PartId && w.PageIndex != paper.PageIndex && w.QueData.Any(s => s.no == que.no)); if (queOthPaper != null) { ky = true; if (paper.PageIndex > queOthPaper.PageIndex) { pageSerials.Add(queOthPaper.PaperId); quePaperId = queOthPaper.PaperId; } } if (!zgt.Any(s => s.Item1 == quePaperId && s.Item2 == que.no)) zgt.Add(new(quePaperId, que.no, ky)); } } } } var kgtPapers = kgt.GroupBy(s => s.Item1).Select(s => s.Key).ToList(); // 客观题处理 foreach (var paperId in kgtPapers) { var paper = paperInfo.FirstOrDefault(w => w.PaperId == paperId); var paperLatts = lattices.Where(w => w.PageSerial == paperId).ToList(); var queNos = kgt.Where(w => w.Item1 == paperId).Select(s => s.Item2).ToList(); foreach (var queNo in queNos) { var queInfo = paper.QueData.FirstOrDefault(w => w.no == queNo); var queLatts = paperLatts.Where(w => queInfo.options.Any(s => RectExt.IsRectContainsLattice(s.AnswerArea, w)) || queInfo.resetPoint.Any(s => RectExt.IsRectContainsLattice(s, w))).ToList(); long? resetTime = null; if (queInfo.resetPoint.Any()) { var resetLatts = queLatts.Where(w => RectExt.IsRectContainsLattice(queInfo.resetPoint[0], w)).ToList(); resetTime = resetLatts.Any() ? resetLatts.Max(w => w.Time) : null; } var stuAnswer = ""; // 遍历每个选项 foreach (var option in queInfo.options) { if (!option.point.Any()) continue; // 该选项无坐标数据 var choose = queLatts.WhereIF(resetTime.HasValue, w => w.Time > resetTime).Where(a => RectExt.IsRectContainsLattice(option.point[0], a)); if (!choose.Any()) continue; stuAnswer += option.option; } var dtl = kgtDtls.FirstOrDefault(w => w.QuestionNumber == queNo); if (dtl != null) { dtl.QuestionValue = stuAnswer; } } } var zgtPapers = zgt.GroupBy(s => s.Item1).Select(s => s.Key).ToList(); // 主观题处理 foreach (var paperId in zgtPapers) { var paper = paperInfo.FirstOrDefault(w => w.PaperId == paperId); var paperJobPage = templateIds.FindIndex(s => s == paper.TemplateId) + 1; var paperLatts = lattices.Where(w => w.PageSerial == paperId).ToList(); var paperPicture = $"questionAnswer/{eto.JobId}/{studentId}/{paperJobPage}/{paper.PartId}.png"; var questionPaperPicture = string.Empty; var ques = zgt.Where(w => w.Item1 == paperId).ToList(); foreach (var que in ques) { var queNo = que.Item2; var queInfo = paper.QueData.FirstOrDefault(w => w.no == queNo); var answerArea = queInfo.options[0].AnswerArea; var queLatts = paperLatts.Where(w => RectExt.IsRectContainsLattice(answerArea, w)) .Select(s => new SubjectiveLatt() { Stroke = s.strokeIndex, X = s.CX.AUToPX(), Y = s.CY.AUToPX() - answerArea.pxTop, Time = s.Time, }).ToList(); if (que.Item3) // 跨页 { var queOnNextPaper = paperInfo.FirstOrDefault(w => w.PartId == paper.PartId && w.PageIndex == paper.PageIndex + 1 && w.QueData.Any(s => s.no == queNo)); var queInfoOnNextPaper = queOnNextPaper.QueData.FirstOrDefault(w => w.no == queNo); var answerAreaOnNextPaper = queInfoOnNextPaper.options[0].AnswerArea; var queLattsOnNextPaper = lattices.Where(w => w.PageSerial == queOnNextPaper.PaperId && RectExt.IsRectContainsLattice(answerAreaOnNextPaper, w)) .Select(s => new SubjectiveLatt() { Stroke = s.strokeIndex, X = s.CX.AUToPX(), Y = s.CY.AUToPX() + answerArea.pxHeight - answerAreaOnNextPaper.pxTop, Time = s.Time, }).ToList(); queLatts.AddRange(queLattsOnNextPaper); } var jobDtl = dtls.FirstOrDefault(w => w.SectionId == paper.PartId && w.QuestionNo == queNo); var stuAnswer = ""; var imgStream = await $"{App.Configuration["Aliyun:OssJob:Host"]}/{jobDtl.QuestionPicture}".GetStreamAsync(); var queBitmap = SKBitmap.Decode(imgStream); using (var canvas = new SKCanvas(queBitmap)) { // 一笔一笔的画上去 var strokeIndexs = queLatts.GroupBy(g => g.Stroke).Select(s => s.Key).ToList(); foreach (var stroke in strokeIndexs) { var points = queLatts.Where(w => w.Stroke == stroke).OrderBy(s => s.Time).Select(s => new SKPoint(s.X, s.Y)).ToArray(); var skPointMode = SKPointMode.Polygon; if (points.Length == 1) skPointMode = SKPointMode.Points; else if (points.Length == 2) skPointMode = SKPointMode.Lines; canvas.DrawPoints(skPointMode, points, skPaint); } stuAnswer = $"questionAnswer/{eto.JobId}/{studentId}/{paperJobPage}/{paper.PartId}-{queNo}.png"; JobExtension.UploadFileToCloud(stuAnswer, queBitmap.Encode(SKEncodedImageFormat.Png, 100).ToArray()); } questionPaperPicture = jobDtl.QuestionPaperPicture; jobDtl.StudentAnswers = stuAnswer; var dtl = zgtDtls.FirstOrDefault(w => w.QuestionNum == queNo); if (dtl != null) { dtl.StudentAnswer = stuAnswer; } } // 大图处理(拼接答案) var paperZgtDtls = dtls.Where(w => w.SectionId == paper.PartId && w.QuestionType == JobQuestionTypeEnum.主观题 && w.QuestionPaperPicture == questionPaperPicture) .OrderBy(s => SqlFunc.ToInt32(s.QuestionNo)).ToList(); var bitmaps = new List(); foreach (var zgtDtl in paperZgtDtls) { var studentAnswer = zgtDtl.StudentAnswers; var url = studentAnswer.IsNotNullOrEmpty() ? studentAnswer : zgtDtl.QuestionPicture; var imgStream = await $"{App.Configuration["Aliyun:OssJob:Host"]}/{url}".GetStreamAsync(); var bitmap = SKBitmap.Decode(imgStream); bitmaps.Add(bitmap); } var width = bitmaps.FirstOrDefault()?.Width ?? 0; var height = bitmaps.Sum(s => s.Height); using (var paperBitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul)) { using (var canvas = new SKCanvas(paperBitmap)) { var top = 0; foreach (var bitmap in bitmaps) { canvas.DrawBitmap(bitmap, 0, top); top += bitmap.Height; } } JobExtension.UploadFileToCloud(paperPicture, paperBitmap.Encode(SKEncodedImageFormat.Png, 100).ToArray()); } } // 删除 await DbBiz.Deleteable().Where(w => w.ExamSubjectId == eto.ExamSubjectId && w.StudentNo == penSerial).ExecuteCommandAsync(); await DbBiz.Updateable() .SetColumns(s => s.IsDeleted == true) .Where(w => w.ExamSubjectId == eto.ExamSubjectId && w.StudentExamNum == penSerial) .ExecuteCommandAsync(); // 新增 await DbBiz.Insertable(kgtDtls).ExecuteCommandAsync(); await DbBiz.Insertable(zgtDtls).ExecuteCommandAsync(); } public async Task ExamAnnotate(ExamAnnotateEto eto) { } public async Task GetTenantDb(long tenantCode) { var tenant = await Db.Queryable().Where(w => w.TenantCode == tenantCode).FirstAsync(); ExceptionExt.ThrowIf(tenant == null, $"{nameof(tenant)} is null with ${tenantCode}"); var config = new SqlSugarConfig(); return new SqlSugarClient(new ConnectionConfig() { DbType = DbType.MySql, ConnectionString = tenant.ConnectionString, IsAutoCloseConnection = true, ConfigureExternalServices = config.ExtService, }); } }