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