diff --git a/Dolphin.ExamPictureCut.Application/Exams/ExamManager.cs b/Dolphin.ExamPictureCut.Application/Exams/ExamManager.cs index 9c09fb1..3d28a28 100644 --- a/Dolphin.ExamPictureCut.Application/Exams/ExamManager.cs +++ b/Dolphin.ExamPictureCut.Application/Exams/ExamManager.cs @@ -20,21 +20,29 @@ public class ExamManager : DomainService, IExamManager { private readonly ISqlSugarClient Db; private readonly IBlobContainer _blobContainer; + private readonly SKPaint skPaint = new SKPaint + { + Color = SKColors.Black, + IsAntialias = true, // 抗锯齿 + Style = SKPaintStyle.Stroke, + StrokeWidth = 1, + StrokeCap = SKStrokeCap.Round, + }; 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; + Logger.LogInformation("{ExamSubjectId} {penSerial} 开始收集...", eto.ExamSubjectId, penSerial); + var guid = GuidGenerator.Create().ToString("N"); 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(); + .Select(s => new { s.Id, s.PaperId, s.PartId, s.PageIndex, s.ImgUrl, s.QueData, s.Order }).ToListAsync(); var paperInfo = templates.Select(s => { var data = JsonConvert.DeserializeObject(s.QueData); @@ -45,6 +53,8 @@ public class ExamManager : DomainService, IExamManager PartId = s.PartId, PageIndex = s.PageIndex, QueData = data.queData, + ImgUrl = s.ImgUrl, + Sort = s.Order, }; }).OrderBy(s => s.PartId).ThenBy(s => s.PageIndex).ToList(); @@ -80,7 +90,89 @@ public class ExamManager : DomainService, IExamManager IsObjectiveQuestion = true, GroupNo = guid, }).ToListAsync(); + + var gotoCount = 0; + dotPenOriginalImg: var zgtSettingDtls = await DbBiz.Queryable().Where(w => w.ExamSubjectId == eto.ExamSubjectId).ToListAsync(); + + // 割原题 + if (!zgtSettingDtls.Any(s => s.DotPenOriginalImg.IsNotNullOrEmpty())) + { + if (gotoCount >= 1) + { + Logger.LogError("割原题超出次数限制,收集终止"); + return; + } + + var redisLockKey = "LockKey:" + eto.ExamSubjectId; + var redisLock = await RedisHelper.GetAsync(redisLockKey); + if (string.IsNullOrEmpty(redisLock)) + { + await RedisHelper.SetAsync(redisLockKey, "1"); + foreach (var paper in paperInfo) + { + var imgStream = await paper.ImgUrl.GetStreamAsync(); + var bitmap = SKBitmap.Decode(imgStream); + + var nextPaper = paperInfo.FirstOrDefault(w => w.PartId == paper.PartId && w.PageIndex == paper.PageIndex + 1); + + foreach (var que in paper.QueData) + { + if (que.type != "2") continue; + if (que.options == null || que.options.Count == 0) continue; + + var area = que.options[0].AnswerArea; + var areaTop = area.pxTop; + var areaHeight = area.pxHeight; + var sourceRect = new SKRect(0, areaTop, bitmap.Width, areaTop + areaHeight); + + var height = areaHeight; + + SKBitmap nextImgBitmap = null; + SKRect nextSourceRect = new(); + var queOnNextPaper = nextPaper?.QueData.FirstOrDefault(w => w.no == que.no); + if (queOnNextPaper?.options?.Count >= 1) + { + var nextImgStream = await nextPaper.ImgUrl.GetStreamAsync(); + nextImgBitmap = SKBitmap.Decode(nextImgStream); + + var nextArea = queOnNextPaper.options[0].AnswerArea; + var nextAreaTop = nextArea.pxTop; + var nextAreaHeight = nextArea.pxHeight; + height += nextAreaHeight; + + nextSourceRect = new SKRect(0, nextAreaTop, bitmap.Width, nextAreaTop + nextAreaHeight); + } + + using (var newBitmap = new SKBitmap(bitmap.Width, height.SSWR(), SKColorType.Rgba8888, SKAlphaType.Premul)) + { + using (var canvas = new SKCanvas(newBitmap)) + { + canvas.DrawBitmap(bitmap, sourceRect, new SKRect(0, 0, bitmap.Width, areaHeight)); + if (nextImgBitmap != null) + canvas.DrawBitmap(nextImgBitmap, nextSourceRect, new SKRect(0, areaHeight, bitmap.Width, height)); + } + + var dtl = zgtSettingDtls.FirstOrDefault(w => w.QuestionNum == que.no); + dtl.DotPenOriginalImg = $"que/{eto.ExamSubjectId}/{paper.Sort}/{que.no}.png"; + await _blobContainer.SaveAsync(dtl.DotPenOriginalImg, newBitmap.Encode(SKEncodedImageFormat.Png, 100).ToArray(), true); + } + } + } + await Db.Updateable(zgtSettingDtls).UpdateColumns(s => s.DotPenOriginalImg).Where(w => w.DotPenOriginalImg.IsNotNullOrEmpty()).ExecuteCommandAsync(); + await RedisHelper.SetAsync(redisLockKey, "0"); + } + else + { + while (await RedisHelper.GetAsync(redisLockKey) == "1") + { + Thread.Sleep(1000); + } + gotoCount++; + goto dotPenOriginalImg; + } + } + var zgtDtls = zgtSettingDtls.Select(s => new SubjectiveMarkingResult { Id = YitIdHelper.NextId(), @@ -96,31 +188,6 @@ public class ExamManager : DomainService, IExamManager 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(); // 需要计算的页 @@ -205,8 +272,6 @@ public class ExamManager : DomainService, IExamManager 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) @@ -239,10 +304,10 @@ public class ExamManager : DomainService, IExamManager 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 queImgUrl = zgtSettingDtls.FirstOrDefault(w => w.QuestionNum == queNo).DotPenOriginalImg; + var imgStream = await _blobContainer.GetAsync(queImgUrl); var queBitmap = SKBitmap.Decode(imgStream); using (var canvas = new SKCanvas(queBitmap)) @@ -260,57 +325,30 @@ public class ExamManager : DomainService, IExamManager canvas.DrawPoints(skPointMode, points, skPaint); } - stuAnswer = $"questionAnswer/{eto.JobId}/{studentId}/{paperJobPage}/{paper.PartId}-{queNo}.png"; - JobExtension.UploadFileToCloud(stuAnswer, queBitmap.Encode(SKEncodedImageFormat.Png, 100).ToArray()); + stuAnswer = $"queAnswer/{eto.ExamSubjectId}/{penSerial}/{paper.Sort}/{queNo}.png"; + await _blobContainer.SaveAsync(stuAnswer, queBitmap.Encode(SKEncodedImageFormat.Png, 100).ToArray(), true); } - 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(); + + await DbBiz.UseTranAsync(async () => + { + // 删除 + 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 && w.IsDeleted == false) + .ExecuteCommandAsync(); + // 新增 + await DbBiz.Insertable(kgtDtls).ExecuteCommandAsync(); + await DbBiz.Insertable(zgtDtls).ExecuteCommandAsync(); + }); } public async Task ExamAnnotate(ExamAnnotateEto eto) diff --git a/Dolphin.ExamPictureCut.Application/Services/ExamAppService.cs b/Dolphin.ExamPictureCut.Application/Services/ExamAppService.cs index be30a4e..78950bd 100644 --- a/Dolphin.ExamPictureCut.Application/Services/ExamAppService.cs +++ b/Dolphin.ExamPictureCut.Application/Services/ExamAppService.cs @@ -12,6 +12,6 @@ public class ExamAppService : DolphinAppService public async Task Test() { - await _examManager.ExamStudentGather(new() { SchoolId = 1 }); + await _examManager.ExamStudentGather(new() { SchoolId = 1, StudentExamNum = "BP2-3G3-07K-C0", TemplateId = "[]" }); } } diff --git a/Dolphin.ExamPictureCut.Core/Dolphin.ExamPictureCut.Core.csproj b/Dolphin.ExamPictureCut.Core/Dolphin.ExamPictureCut.Core.csproj index ffba384..9649f0a 100644 --- a/Dolphin.ExamPictureCut.Core/Dolphin.ExamPictureCut.Core.csproj +++ b/Dolphin.ExamPictureCut.Core/Dolphin.ExamPictureCut.Core.csproj @@ -16,7 +16,7 @@ - + diff --git a/Dolphin.ExamPictureCut.Core/Exams/Dto/TemplateJsonModelDto.cs b/Dolphin.ExamPictureCut.Core/Exams/Dto/TemplateJsonModelDto.cs index 035f103..d8d936a 100644 --- a/Dolphin.ExamPictureCut.Core/Exams/Dto/TemplateJsonModelDto.cs +++ b/Dolphin.ExamPictureCut.Core/Exams/Dto/TemplateJsonModelDto.cs @@ -24,6 +24,8 @@ public class PaperQueData public string PaperId { get; set; } public long PartId { get; set; } public int PageIndex { get; set; } + public string ImgUrl { get; set; } + public int Sort { get; set; } public List QueData { get; set; } } public class TemplateJsonModel_QueData diff --git a/Dolphin.ExamPictureCut.Core/Extensions/RectExt.cs b/Dolphin.ExamPictureCut.Core/Extensions/RectExt.cs index 90482f3..95e9054 100644 --- a/Dolphin.ExamPictureCut.Core/Extensions/RectExt.cs +++ b/Dolphin.ExamPictureCut.Core/Extensions/RectExt.cs @@ -5,6 +5,16 @@ namespace Dolphin.ExamPictureCut.Extensions; public static class RectExt { + /// + /// 四舍五入 + /// + /// + /// + public static int SSWR(this float px) + { + return (int)Math.Round(px, 0, MidpointRounding.AwayFromZero); + } + public static float MMToPX(this float MM) { return MM * 96 / 25.4F; diff --git a/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.Development.json b/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.Development.json index e85d9b0..8236e45 100644 --- a/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.Development.json +++ b/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.Development.json @@ -32,6 +32,6 @@ "Endpoint": "https://oss-cn-chengdu.aliyuncs.com", "RegionId": "oss-cn-chengdu", "ContainerName": "mk-xk-test", - "CustomHostUrl": "//mk-xk-test.23544.com" + "Host": "//mk-xk-test.23544.com" } } diff --git a/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.json b/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.json index c4f5077..5ef0e80 100644 --- a/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.json +++ b/Dolphin.ExamPictureCut.HttpApi.Host/appsettings.json @@ -32,6 +32,6 @@ "Endpoint": "https://oss-cn-chengdu.aliyuncs.com", "RegionId": "oss-cn-chengdu", "ContainerName": "mk-xk", - "CustomHostUrl": "//mk-xk.23544.com" + "Host": "//mk-xk.23544.com" } }