From 549ec66f80fd634201b139a81b1bc14033eeeba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Sun, 17 Aug 2025 16:26:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=80=83=E8=AF=95?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Dto/ExamClassInfoDto.cs | 121 +++++++++ .../Controllers/ExamClassInfoController.cs | 229 ++++++++++++++++++ .../Controllers/ExamController.cs | 26 ++ .../Controllers/ExamUserInfoController.cs | 26 ++ Learn.Archives.API/Learn.Archives.API.csproj | 1 + Learn.Archives.API/appsettings.json | 2 +- Learn.Archives.Core/Common/AppCommon.cs | 20 +- Learn.Archives.Core/Common/LiveUserInfo.cs | 8 + .../Learn.Archives.Core.csproj | 1 + Learn.Archives.Core/Model/Exam.cs | 9 +- Learn.Archives.Core/Model/ExamClassInfo.cs | 54 ++++- Learn.Archives.Core/Model/ExamUserInfo.cs | 23 +- 12 files changed, 495 insertions(+), 25 deletions(-) create mode 100644 Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs create mode 100644 Learn.Archives.API/Controllers/ExamClassInfoController.cs create mode 100644 Learn.Archives.API/Controllers/ExamController.cs create mode 100644 Learn.Archives.API/Controllers/ExamUserInfoController.cs diff --git a/Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs b/Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs new file mode 100644 index 0000000..aca79be --- /dev/null +++ b/Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs @@ -0,0 +1,121 @@ +using MiniExcelLibs.Attributes; + +namespace Learn.Archives.API.Controllers.Dto +{ + public class ImportDto + { + /// + /// 导入文件 + /// + public IFormFile? File { get; set; } + /// + /// 考试id + /// + public long eId { get; set; } + + } + /// + /// 导入的错误结果 + /// + public class ImportExamInfoError: ImportExamInfo + { + /// + /// 学校 + /// + [ExcelColumnName("错误信息")] + public string? Error { get; set; } + } + /// + /// 导入考试成绩 + /// + public class ImportExamInfo + { + /// + /// 学校 + /// + [ExcelColumnName("学校")] + public string School { get; set; } + /// + /// 年级 + /// + [ExcelColumnName("年级")] + public string Grade { get; set; } + /// + /// 云校班级号 + /// + [ExcelColumnName("云校班级号")] + public string Class { get; set; } + /// + /// + /// + [ExcelColumnName("班级类型")] + public string ClassType { get; set; } + /// + /// 学生姓名0. + /// + [ExcelColumnName("学生姓名")] + public string Student { get; set; } + + + + /// + /// 语文 + /// + [ExcelColumnName("语文")] + public decimal 语文 { get; set; } + + /// + /// 数学 + /// + [ExcelColumnName("数学")] + public decimal 数学 { get; set; } + + /// + /// 英语 + /// + [ExcelColumnName("英语")] + public decimal 英语 { get; set; } + + /// + /// 物理 + /// + [ExcelColumnName("物理")] + public decimal 物理 { get; set; } + + /// + /// 化学 + /// + [ExcelColumnName("化学")] + public decimal 化学 { get; set; } + + /// + /// 生物 + /// + [ExcelColumnName("生物")] + public decimal 生物 { get; set; } + + /// + /// 政治 + /// + [ExcelColumnName("政治")] + public decimal 政治 { get; set; } + + /// + /// 历史 + /// + [ExcelColumnName("历史")] + public decimal 历史 { get; set; } + + /// + /// 地理 + /// + [ExcelColumnName("地理")] + public decimal 地理 { get; set; } + + /// + /// 赋分后总分 + /// + [ExcelColumnName("赋分后总分")] + public decimal 赋分后总分 { get; set; } + } +} diff --git a/Learn.Archives.API/Controllers/ExamClassInfoController.cs b/Learn.Archives.API/Controllers/ExamClassInfoController.cs new file mode 100644 index 0000000..badd468 --- /dev/null +++ b/Learn.Archives.API/Controllers/ExamClassInfoController.cs @@ -0,0 +1,229 @@ +using Learn.Archives.API.Controllers.Dto; +using Learn.Archives.API.Expand; +using Learn.Archives.Core.Common; +using Learn.Archives.Core.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using MiniExcelLibs; +using System.Diagnostics; +using System.Security.Claims; +using UserCenter.Model; +using UserCenter.Model.Common; +using UserCenter.Model.Enum; + +namespace Learn.Archives.API.Controllers +{ + /// + /// 年级控制器 + /// + public class ExamClassInfoController : BackController + { + readonly Repository baseService; + readonly Repository examService; + readonly Repository schoolService; + readonly Repository examUserInfoService; + readonly LiveUserInfo userInfo; + readonly IHttpContextAccessor accessor; + public ExamClassInfoController(Repository baseService, LiveUserInfo userInfo, IHttpContextAccessor httpContext, Repository examUserInfoService, Repository examService, Repository schoolService) : base(baseService) + { + this.baseService = baseService; + this.userInfo = userInfo; + this.accessor = httpContext; + this.examUserInfoService = examUserInfoService; + this.examService = examService; + this.schoolService = schoolService; + } + [NonAction] + private Dictionary? ImportExamInfoSubjectDic(ImportExamInfo info) + { + return new Dictionary() + { + { SubjectEnum.语文, info.语文}, + { SubjectEnum.数学, info.数学}, + { SubjectEnum.英语, info.英语}, + { SubjectEnum.物理, info.物理}, + { SubjectEnum.化学, info.化学}, + { SubjectEnum.生物, info.生物}, + { SubjectEnum.政治, info.政治}, + { SubjectEnum.历史, info.历史}, + { SubjectEnum.地理, info.地理}, + }; + } + /// + /// 导入考试信息 + /// + /// + /// + [HttpPost, ResultIgnore] + [HttpLogEnable] + public async Task Import(ImportDto dto) + { + var exam = await examService.GetByIdAsync(dto.eId); + var fl = dto.File!=null? dto .File: accessor.HttpContext?.Request.Form.Files[0]; + if (exam == null || fl == null) Oh.ModelError("为传入有效的数据"); + if (!Path.GetExtension(fl.FileName).Equals(".xlsx", StringComparison.OrdinalIgnoreCase)) + Oh.ModelError("请选择导入文件为.xlsx的后缀名!"); + //分析excel + IEnumerable dataList; + using var stream = new MemoryStream(); + { + await fl.CopyToAsync(stream); + dataList = stream.Query() + .Where(s => string.IsNullOrEmpty(s.Student)); + } + if (dataList == null || dataList.Count() == 0) + Oh.ModelError("导入失败:无有效数据"); + //处理数据 + var errorExcelInfo = new List(); + var insertUserInfo = new List(); + var insertClassInfo = new List(); + var db = schoolService.Context; + foreach (var schoolArr in dataList.GroupBy(s => s.School)) + { + var school = await schoolService.GetFirstAsync(s => s.Name == schoolArr.Key); + if (school == null) Oh.ModelError($"导入失败:学校 [{schoolArr.Key}] 未找到!"); + var gradeInfo = GradeHelper.GetStudentGradeBaseByGrade(schoolArr.First().Grade); + if (gradeInfo == null) Oh.ModelError($"导入失败:学校 [{schoolArr.Key}] 年级[{schoolArr.First().Grade}]不符合规范!"); + //学校下的所属班级 + var classArr = await db.Queryable().Where(c => c.SchoolId == school.Id && + c.GradeLevel == gradeInfo.GradeLevel && + c.GraduationYear == gradeInfo.GradeYear && !c.DeleteState).ToArrayAsync(); + var userDic = await db.Queryable() + .InnerJoin((s, c) => c.SchoolId == s.Id) + .InnerJoin((s, c, p) => p.ClassId == c.Id) + .InnerJoin((s, c, p, pr) => pr.PositionId == p.Id) + .InnerJoin((s, c, p, pr, u) => u.Id == pr.UserId) + .Where((s, c, p, pr, u) => + s.Id == school.Id && + c.GradeLevel == gradeInfo.GradeLevel && + c.GraduationYear == gradeInfo.GradeYear && + s.Enable && p.Enable && pr.Enable && + p.DeleteState != false && c.DeleteState != false && u.DeleteState != false && s.DeleteState != false) + .Select((s, c, p, pr, u) => new + { + Name = c.Name + c.Type.ToString() + u.RealName, + u.Id, + }).ToDictionaryAsync(s => s.Id, s => s.Name); + //处理学生成绩数据 + var userList = dataList.Select(s => + { + var classInfo = classArr + .FirstOrDefault(x => x.Name == s.Class && x.Type == s.ClassType.ToEnum()); + if (classInfo == null) + { + s.Error = "未能匹配班级"; + errorExcelInfo.Add(s); + return null; + } + var grade = GradeHelper.GetStudentGradeBaseByGrade(s.Grade); + var sub = ImportExamInfoSubjectDic(s); + var name = s.Class + s.ClassType.ToEnum().GetHashCode() + s.Student; + if (userDic.ContainsKey(name)) + { + s.Error = "未能匹配到年级班级下对应的学生"; + errorExcelInfo.Add(s); + return null; + } + var uid = userDic[name]; + return new ExamUserInfo() + { + ExamId = exam.Id, + ExamName = exam.Name, + GradeYear = grade.GradeYear, + GradeLevel = grade.GradeLevel, + TestPaperType = exam.TestPaperType, + UserId = (long)userDic[name], + ClassId = classInfo.Id, + SchoolId = classInfo.SchoolId, + Type = exam.Type, + SubjectDic = sub, + AssignScore = s.赋分后总分, + AssignRanking = 0, + }; + }).ToList(); + insertUserInfo.AddRange(userList); + + + foreach (var classUserArr in insertUserInfo.GroupBy(s => s.ClassId)) + { + var classInfo = classArr.First(s => s.Id == classUserArr.Key); + var eCInfo = new ExamClassInfo() + { + ExamId = exam.Id, + ExamName = exam.Name, + SchoolId = classInfo.SchoolId, + SchoolName = school.Name, + ClassId = classInfo.Id, + ClassName = classInfo.Name, + GradeLevel = classInfo.GradeLevel, + GradeYear = classInfo.GraduationYear, + PeopleCount = classUserArr.Count(), + MinScore = int.MaxValue, + EntryPersonId = userInfo.Id, + EntryPerson = userInfo.Name, + }; + insertClassInfo.Add(eCInfo); + var avgTotal = 0m; + foreach (var eUserInfo in classUserArr) + { + var v = eUserInfo.AssignScore; + //上线人数 + if (v>=exam.ScoreLine) + eCInfo.OnLineCount ++; + //最大小分 + if (v < eCInfo.MinScore) + eCInfo.MinScore = v; + else if(v > eCInfo.MaxScore) + eCInfo.MaxScore = v; + avgTotal += v;//追加得分 + } + //总分平均分 + eCInfo.Average = avgTotal / eCInfo.PeopleCount; + //计算上线率 + eCInfo.OnLineRate = eCInfo.OnLineCount / eCInfo.PeopleCount; + //处理学生班级排名 + var i = 0; + foreach (var item in classUserArr.OrderByDescending(s => s.AssignScore) + .GroupBy(s => s.AssignScore)) + { + foreach (var u in item) + u.AssignRanking =++ i; + } + } + + }; + + + { //计算年级上线率排名 + var i = 0; + foreach (var item in insertClassInfo.OrderByDescending(s => s.OnLineRate) + .GroupBy(s => s.OnLineRate)) + { + foreach (var u in item) + u.OnLineRanking = ++i; + } + + //计算年级平均分排名 + i = 0; + foreach (var item in insertClassInfo.OrderByDescending(s => s.Average) + .GroupBy(s => s.Average)) + { + foreach (var u in item) + u.AverageRank = ++i; + } + } + if (errorExcelInfo.Count != 0) + return File(errorExcelInfo.ExportExcel(), "application/ms-excel"); + + //写入数据库 + var baseDB = baseService.Context; + baseDB.Ado.BeginTran(); + await db.Insertable(insertUserInfo).ExecuteCommandAsync(); + await db.Insertable(insertClassInfo).ExecuteCommandAsync(); + baseDB.Ado.CommitTran(); + + return Ok(); + } + + } +} diff --git a/Learn.Archives.API/Controllers/ExamController.cs b/Learn.Archives.API/Controllers/ExamController.cs new file mode 100644 index 0000000..a263464 --- /dev/null +++ b/Learn.Archives.API/Controllers/ExamController.cs @@ -0,0 +1,26 @@ +using Learn.Archives.API.Controllers.Dto; +using Learn.Archives.API.Expand; +using Learn.Archives.Core.Common; +using Learn.Archives.Core.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using System.Security.Claims; +using UserCenter.Model; + +namespace Learn.Archives.API.Controllers +{ + /// + /// 年级控制器 + /// + public class ExamController : BackController + { + readonly Repository baseService; + readonly LiveUserInfo userInfo; + public ExamController(Repository baseService, LiveUserInfo userInfo) : base(baseService) + { + this.baseService = baseService; + this.userInfo = userInfo; + } + } +} diff --git a/Learn.Archives.API/Controllers/ExamUserInfoController.cs b/Learn.Archives.API/Controllers/ExamUserInfoController.cs new file mode 100644 index 0000000..d113338 --- /dev/null +++ b/Learn.Archives.API/Controllers/ExamUserInfoController.cs @@ -0,0 +1,26 @@ +using Learn.Archives.API.Controllers.Dto; +using Learn.Archives.API.Expand; +using Learn.Archives.Core.Common; +using Learn.Archives.Core.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using System.Security.Claims; +using UserCenter.Model; + +namespace Learn.Archives.API.Controllers +{ + /// + /// 年级控制器 + /// + public class ExamUserInfoController : BackController + { + readonly Repository baseService; + readonly LiveUserInfo userInfo; + public ExamUserInfoController(Repository baseService, LiveUserInfo userInfo) : base(baseService) + { + this.baseService = baseService; + this.userInfo = userInfo; + } + } +} diff --git a/Learn.Archives.API/Learn.Archives.API.csproj b/Learn.Archives.API/Learn.Archives.API.csproj index 13893db..95d95f2 100644 --- a/Learn.Archives.API/Learn.Archives.API.csproj +++ b/Learn.Archives.API/Learn.Archives.API.csproj @@ -11,6 +11,7 @@ + diff --git a/Learn.Archives.API/appsettings.json b/Learn.Archives.API/appsettings.json index d3ddfa0..d148e6b 100644 --- a/Learn.Archives.API/appsettings.json +++ b/Learn.Archives.API/appsettings.json @@ -14,7 +14,7 @@ "DB": { "ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=learn.archives;CharSet=utf8mb4;pooling=true;SslMode=None", "SqlType": "MySql", - "UpdateTable": false + "UpdateTable": true }, "AuthKey": { "Secret": "9FAB7AC7-F1DB-4C56-B84F-044055A34AF2", diff --git a/Learn.Archives.Core/Common/AppCommon.cs b/Learn.Archives.Core/Common/AppCommon.cs index 1b5dcff..e572f79 100644 --- a/Learn.Archives.Core/Common/AppCommon.cs +++ b/Learn.Archives.Core/Common/AppCommon.cs @@ -17,6 +17,8 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using UserCenter.Model.Interface; +using MiniExcelLibs; +using MiniExcelLibs.OpenXml; namespace Learn.Archives.Core.Common { @@ -53,8 +55,9 @@ namespace Learn.Archives.Core.Common EnumType = Assemblies - .Where(s => s.FullName.Contains("Model")) + .Where(s => s.FullName.Contains("Model")|| s.FullName.Contains("Core")) .SelectMany(s => s.GetTypes().Where(x => x.IsEnum)) + .DistinctBy(s=>s.Name) .ToDictionary(s => s.Name, s => s); } @@ -79,9 +82,18 @@ namespace Learn.Archives.Core.Common /// public static class ExpandFunction { - - - + const string SheetName = "Sheet1"; + public static byte[] ExportExcel(this IEnumerable resultList) + { + var config = new OpenXmlConfiguration() + { + TableStyles = TableStyles.None + }; + using var memoryStream = new MemoryStream(); + memoryStream.SaveAs(resultList, true, SheetName, ExcelType.XLSX, config); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream.ToArray(); + } public static string GetMD5(this string input) { using (MD5 md5 = MD5.Create()) diff --git a/Learn.Archives.Core/Common/LiveUserInfo.cs b/Learn.Archives.Core/Common/LiveUserInfo.cs index d9c2ef1..67eefd9 100644 --- a/Learn.Archives.Core/Common/LiveUserInfo.cs +++ b/Learn.Archives.Core/Common/LiveUserInfo.cs @@ -44,5 +44,13 @@ namespace Learn.Archives.Core.Common { get => long.Parse(_httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Id)?.Value ?? "0"); } + + /// + /// 管理员id + /// + public string Name + { + get => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Name)?.Value??string.Empty; + } } } diff --git a/Learn.Archives.Core/Learn.Archives.Core.csproj b/Learn.Archives.Core/Learn.Archives.Core.csproj index 50292c9..6b80d3b 100644 --- a/Learn.Archives.Core/Learn.Archives.Core.csproj +++ b/Learn.Archives.Core/Learn.Archives.Core.csproj @@ -14,6 +14,7 @@ + diff --git a/Learn.Archives.Core/Model/Exam.cs b/Learn.Archives.Core/Model/Exam.cs index ff1e1f6..0ec852c 100644 --- a/Learn.Archives.Core/Model/Exam.cs +++ b/Learn.Archives.Core/Model/Exam.cs @@ -20,13 +20,7 @@ namespace Learn.Archives.Core.Model /// 考试名称 /// [SugarColumn(Length = 20)] - public required string Name { get; set; } - - /// - /// 考试名称 - /// - [SugarColumn(Length = 20)] - public required string AdminName { get; set; } + public string Name { get; set; } /// /// 年级 /// @@ -49,6 +43,7 @@ namespace Learn.Archives.Core.Model /// /// 参与班级数量 /// + [SugarColumn(IsNullable = true)] public int? ClassCount { get; set; } /// /// 创建时间 diff --git a/Learn.Archives.Core/Model/ExamClassInfo.cs b/Learn.Archives.Core/Model/ExamClassInfo.cs index 5655ff7..51625d9 100644 --- a/Learn.Archives.Core/Model/ExamClassInfo.cs +++ b/Learn.Archives.Core/Model/ExamClassInfo.cs @@ -20,27 +20,27 @@ namespace Learn.Archives.Core.Model /// 考试Id /// [SugarColumn(Length = 20)] - public required string ExamId { get; set; } + public long ExamId { get; set; } /// /// 考试名称 /// [SugarColumn(Length = 20)] - public required string ExamName { get; set; } - /// - /// 考试班级名称 - /// - [SugarColumn(Length = 20)] - public required string ClassName { get; set; } + public string ExamName { get; set; } /// /// 考试班级id /// public long ClassId { get; set; } + /// + /// 考试班级名称 + /// + [SugarColumn(Length = 20)] + public string ClassName { get; set; } /// /// 考试班级所属学校ID /// [SugarColumn(Length = 20)] - public required string SchoolName { get; set; } + public string SchoolName { get; set; } /// /// 考试班级所属学校名称 /// @@ -49,7 +49,7 @@ namespace Learn.Archives.Core.Model /// /// 年级 /// - public GradeLevelEnum GradeLevel { get; set; } + public string GradeLevel { get; set; } /// /// 毕业年份 毕业届 /// @@ -71,6 +71,42 @@ namespace Learn.Archives.Core.Model /// 参加人数 /// public int PeopleCount { get; set; } + /// + /// 最高分(赋分) + /// + public decimal MaxScore { get; set; } + /// + /// 最低分(赋分) + /// + public decimal MinScore { get; set; } + /// + /// 平均分(赋分) + /// + public decimal Average { get; set; } + /// + /// 平均分排名(赋分) + /// + public decimal AverageRank { get; set; } + + + /// + /// 资源校平均分 + /// + public decimal Average1 { get; set; } + /// + /// 资源校平均分 排名 + /// + public decimal AverageRank1 { get; set; } + + + /// + /// 录入人Id + /// + public long EntryPersonId { get; set; } + /// + /// 录入人名称 + /// + public string EntryPerson { get; set; } = string.Empty; /// /// 创建时间 diff --git a/Learn.Archives.Core/Model/ExamUserInfo.cs b/Learn.Archives.Core/Model/ExamUserInfo.cs index fbe220b..7eaf8d8 100644 --- a/Learn.Archives.Core/Model/ExamUserInfo.cs +++ b/Learn.Archives.Core/Model/ExamUserInfo.cs @@ -20,18 +20,22 @@ namespace Learn.Archives.Core.Model /// 考试Id /// [SugarColumn(Length = 20)] - public required string ExamId { get; set; } + public long ExamId { get; set; } /// /// 考试名称 /// [SugarColumn(Length = 20)] - public required string ExamName { get; set; } + public string ExamName { get; set; } + /// + /// 毕业届 + /// + [SugarColumn(Length = 4)] + public int GradeYear { get; set; } /// /// 年级 /// - [SugarColumn(Length = 12)] - public GradeEnum Level { get; set; } + public string GradeLevel { get; set; } /// /// 试卷类型 /// @@ -48,6 +52,17 @@ namespace Learn.Archives.Core.Model /// public long UserId { get; set; } + /// + /// 班级id + /// 来自 + /// + public long ClassId { get; set; } + /// + /// 学校id + /// 来自 + /// + public long SchoolId { get; set; } + /// /// 赋分后的总分 ///