Picture.Cut.Service/Dolphin.ExamPictureCut.Appl.../Exams/ExamManager.cs

335 lines
15 KiB
C#

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<List<string>>(eto.TemplateId);
var templates = await Db.Queryable<GroupBookPaperTemplate>().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<TemplateJsonModel_DataArr>(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<PenOfflineData>()
.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<MarkingSettingObjective>()
.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<MarkingSettingSubjective>().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<Tuple<string, string>>();
var zgt = new List<Tuple<string, string, bool>>(); // 纸张Id, 题号, 是否跨页
var pageSerials = new List<string>(); // 需要计算的页
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<SKBitmap>();
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<MkExamResult>().Where(w => w.ExamSubjectId == eto.ExamSubjectId && w.StudentNo == penSerial).ExecuteCommandAsync();
await DbBiz.Updateable<SubjectiveMarkingResult>()
.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<SqlSugarClient> GetTenantDb(long tenantCode)
{
var tenant = await Db.Queryable<Tenant>().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,
});
}
}