From 57e98333cfadf027f68f977119cb2a7925f457c6 Mon Sep 17 00:00:00 2001 From: tiananlin Date: Tue, 26 Aug 2025 15:14:35 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Learn.Archives.API/D?= =?UTF-8?q?ockerfiles/Dockerfile-production?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dockerfiles/Dockerfile-production | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Learn.Archives.API/Dockerfiles/Dockerfile-production diff --git a/Learn.Archives.API/Dockerfiles/Dockerfile-production b/Learn.Archives.API/Dockerfiles/Dockerfile-production new file mode 100644 index 0000000..d7599a1 --- /dev/null +++ b/Learn.Archives.API/Dockerfiles/Dockerfile-production @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS publish +WORKDIR /src +COPY . . +WORKDIR "/src/Learn.Archives.API" +ENV NUGET_XMLDOC_MODE none +RUN dotnet nuget add source --name marking https://gitea.23544.com/api/packages/marking/nuget/index.json +RUN dotnet publish "Learn.Archives.API.csproj" -c Release -o /app + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final +WORKDIR /app +COPY --from=publish /app . +ENV ASPNETCORE_ENVIRONMENT=Production +ENV TZ=Asia/Shanghai +EXPOSE 8080 +ENTRYPOINT ["dotnet", "Learn.Archives.API.dll"] \ No newline at end of file From 9dbd3e49006acf0901c08480657224b501807a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Wed, 3 Sep 2025 11:51:32 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E8=BF=81=E7=A7=BB=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E4=B8=BA=20=5Fv1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Learn.Archives.API/appsettings.Staging.json | 2 +- Learn.Archives.API/appsettings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Learn.Archives.API/appsettings.Staging.json b/Learn.Archives.API/appsettings.Staging.json index 10408ea..6da4172 100644 --- a/Learn.Archives.API/appsettings.Staging.json +++ b/Learn.Archives.API/appsettings.Staging.json @@ -19,7 +19,7 @@ "OtherDBArr": [ { "ConfigId": 1001, //用户中心 - "ConnectionString": "AllowLoadLocalInfile=true;Server=58.17.132.2;User ID=marking;Password=qwe123!@#;Port=3306;Database=usercenter;CharSet=utf8mb4;Port=43306;pooling=true;SslMode=None;", + "ConnectionString": "AllowLoadLocalInfile=true;Server=58.17.132.2;User ID=marking;Password=qwe123!@#;Port=3306;Database=usercenter_v1;CharSet=utf8mb4;Port=43306;pooling=true;SslMode=None;", "SqlType": "MySql" } ] diff --git a/Learn.Archives.API/appsettings.json b/Learn.Archives.API/appsettings.json index ab25147..d3d0c1e 100644 --- a/Learn.Archives.API/appsettings.json +++ b/Learn.Archives.API/appsettings.json @@ -29,7 +29,7 @@ { "ConfigId": 1001, //用户中心 //"ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=usercenter;CharSet=utf8mb4;pooling=true;SslMode=None;", - "ConnectionString": "AllowLoadLocalInfile=true;Server=58.17.132.2;User ID=marking;Password=qwe123!@#;Port=3306;Database=usercenter;CharSet=utf8mb4;Port=43306;pooling=true;SslMode=None;", + "ConnectionString": "AllowLoadLocalInfile=true;Server=58.17.132.2;User ID=marking;Password=qwe123!@#;Port=3306;Database=usercenter_v1;CharSet=utf8mb4;Port=43306;pooling=true;SslMode=None;", "SqlType": "MySql" } ] From d0638bcfd4058e2cd3db5d33b41812ba9f72bd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Fri, 5 Sep 2025 18:12:55 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=B5=B4=E6=A0=A1?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E7=9A=84=E6=8B=93=E5=B1=95=20=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E6=89=A7=E8=A1=8C=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ExamUserInfoController.cs | 16 ++++++++++++++-- Learn.Archives.API/appsettings.json | 2 +- .../Model/Dto/SchoolBusinessDto.cs | 6 +++++- Learn.Archives.Core/Model/SchoolBusiness.cs | 6 ++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Learn.Archives.API/Controllers/ExamUserInfoController.cs b/Learn.Archives.API/Controllers/ExamUserInfoController.cs index 8b0754e..c7a5903 100644 --- a/Learn.Archives.API/Controllers/ExamUserInfoController.cs +++ b/Learn.Archives.API/Controllers/ExamUserInfoController.cs @@ -6,9 +6,11 @@ using Learn.Archives.Core.Model.Dto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; +using System.Linq; using System.Security.Claims; using UserCenter.Model; using UserCenter.Model.Common; +using UserCenter.Model.Enum; namespace Learn.Archives.API.Controllers { @@ -24,7 +26,7 @@ namespace Learn.Archives.API.Controllers this.baseService = baseService; this.userInfo = userInfo; } - public override Task PageList([FromBody] QueryRequestBase model) + public override async Task PageList([FromBody] QueryRequestBase model) { var c = model.Conditions.FirstOrDefault(s => s.FieldName == "Grade"); if (c != null) @@ -46,7 +48,17 @@ namespace Learn.Archives.API.Controllers }); } } - return base.PageList(model); + var oldSer = model.OrderBy.ToEnum(); + if (oldSer != null) + { + model.OrderBy = "id"; + var res = (PageResult)await base.PageList(model); + if (model.OrderByType == SqlSugar.OrderByType.Asc) + res.Data = res.Data.OrderBy(s => s.SubjectDic?[oldSer.Value]).ToList(); + else + res.Data = res.Data.OrderByDescending(s => s.SubjectDic?[oldSer.Value]).ToList(); + } + return await base.PageList(model); } } } diff --git a/Learn.Archives.API/appsettings.json b/Learn.Archives.API/appsettings.json index d3d0c1e..76c592f 100644 --- a/Learn.Archives.API/appsettings.json +++ b/Learn.Archives.API/appsettings.json @@ -14,7 +14,7 @@ "DB": { "ConnectionString": "AllowLoadLocalInfile=true;Server=58.17.132.2;User ID=marking;Password=qwe123!@#;Port=3306;Database=learn.archives;CharSet=utf8mb4;Port=43306;pooling=true;SslMode=None;", "SqlType": "MySql", - "UpdateTable": false + "UpdateTable": true }, "AuthKey": { "Secret": "9FAB7AC7-F1DB-4C56-B84F-044055A34AF2", diff --git a/Learn.Archives.Core/Model/Dto/SchoolBusinessDto.cs b/Learn.Archives.Core/Model/Dto/SchoolBusinessDto.cs index 374f7fc..62306d0 100644 --- a/Learn.Archives.Core/Model/Dto/SchoolBusinessDto.cs +++ b/Learn.Archives.Core/Model/Dto/SchoolBusinessDto.cs @@ -51,7 +51,7 @@ namespace Learn.Archives.Core.Model.Dto /// public DateTime? ExecutionTime { get; set; } /// - /// 操作人 [后台自动赋值提交人名称] + /// 操作人 /// public string? Operator { get; set; } public string? ExecutionTimeStr => ExecutionTime?.ToString("yyyy-MM-dd"); @@ -86,6 +86,10 @@ namespace Learn.Archives.Core.Model.Dto /// 解决时间 /// public DateTime? EndTime { get; set; } + /// + /// 反馈问题的执行记录 + /// + public RecordDto[]? RecordArr { get; set; } public string? EndTimeStr=> EndTime?.ToString("yyyy-MM-dd"); diff --git a/Learn.Archives.Core/Model/SchoolBusiness.cs b/Learn.Archives.Core/Model/SchoolBusiness.cs index a181514..2542666 100644 --- a/Learn.Archives.Core/Model/SchoolBusiness.cs +++ b/Learn.Archives.Core/Model/SchoolBusiness.cs @@ -89,6 +89,12 @@ namespace Learn.Archives.Core.Model [SugarColumn(IsNullable = true, Length = 1000)] public string? ClassMeeting { get; set; } + /// + /// 预计解决时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? EndTime { get; set; } + /// /// 反馈问题数量 /// From abcdad03b08b80d2f74b765fb09631d6bd003fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Fri, 12 Sep 2025 18:33:51 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E8=81=8C=E4=BD=8Did?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/StudentController.cs | 44 ++++++++++++++++++- Learn.Archives.API/appsettings.json | 2 +- .../Learn.Archives.Core.csproj | 2 +- .../Model/Dto/UserCenterServiceDto.cs | 32 ++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/Learn.Archives.API/Controllers/StudentController.cs b/Learn.Archives.API/Controllers/StudentController.cs index c6a5b8c..d74102e 100644 --- a/Learn.Archives.API/Controllers/StudentController.cs +++ b/Learn.Archives.API/Controllers/StudentController.cs @@ -10,7 +10,9 @@ using Mapster; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; +using SqlSugar; using System.Diagnostics; +using System.Linq.Expressions; using System.Security.Claims; using System.Text.RegularExpressions; using UserCenter.Model; @@ -20,20 +22,22 @@ using UserCenter.Model.Enum; namespace Learn.Archives.API.Controllers { /// - /// 年级控制器 + /// 学生接口 /// public class StudentController : BackController { private readonly IHttpContextAccessor _httpContextAccessor; readonly Repository baseService; + readonly Repository positionService; readonly UserCenterService _userCenterService; readonly LiveUserInfo userInfo; - public StudentController(Repository baseService, LiveUserInfo userInfo, IHttpContextAccessor httpContextAccessor, UserCenterService userCenterService) : base(baseService) + public StudentController(Repository baseService, LiveUserInfo userInfo, IHttpContextAccessor httpContextAccessor, UserCenterService userCenterService, Repository positionService) : base(baseService) { this.baseService = baseService; this.userInfo = userInfo; _httpContextAccessor = httpContextAccessor; _userCenterService = userCenterService; + this.positionService = positionService; } [NonAction] @@ -54,6 +58,42 @@ namespace Learn.Archives.API.Controllers Oh.ModelError("无效数据"); return await baseService.AsQueryable().FirstAsync(s => s.UserCenterId == uid); } + /// + /// 获取职位id + /// 调用流程 用户中心->档案系统 + /// + /// + [HttpPost] + public async Task PosititonIds(PositionIdsReq[] data) + { + if (data == null || data.Count()==0) + Oh.ModelError("无效数据"); + var query = new Expressionable(); + foreach (var pos in data) + { + query = query.Or(q => + q.SchoolId == pos.SchoolId && + q.PositionType == pos.PositionType && + q.GradeLevel == pos.GradeLevel && + q.GraduationYear == pos.GraduationYear && + q.PositionLevel == pos.PositionLevel + && + ( + // PositionLevel == 3:只匹配前三个字段 + (pos.PositionLevel == 3) || + // PositionLevel == 4:再加上 ClassId + (pos.PositionLevel == 4 && q.ClassId == pos.ClassId) || + + // PositionLevel == 5:再加上 ClassId + SubjectId + (pos.PositionLevel == 5 && q.ClassId == pos.ClassId && q.SubjectId == pos.SubjectId) + ) + ); + } + return await positionService.AsQueryable() + .Where(query.ToExpression()) + .Select(p => p.Id).ToArrayAsync(); + } + /// /// 修改用户信息 diff --git a/Learn.Archives.API/appsettings.json b/Learn.Archives.API/appsettings.json index 76c592f..d3d0c1e 100644 --- a/Learn.Archives.API/appsettings.json +++ b/Learn.Archives.API/appsettings.json @@ -14,7 +14,7 @@ "DB": { "ConnectionString": "AllowLoadLocalInfile=true;Server=58.17.132.2;User ID=marking;Password=qwe123!@#;Port=3306;Database=learn.archives;CharSet=utf8mb4;Port=43306;pooling=true;SslMode=None;", "SqlType": "MySql", - "UpdateTable": true + "UpdateTable": false }, "AuthKey": { "Secret": "9FAB7AC7-F1DB-4C56-B84F-044055A34AF2", diff --git a/Learn.Archives.Core/Learn.Archives.Core.csproj b/Learn.Archives.Core/Learn.Archives.Core.csproj index 7f3019b..d41fa79 100644 --- a/Learn.Archives.Core/Learn.Archives.Core.csproj +++ b/Learn.Archives.Core/Learn.Archives.Core.csproj @@ -28,6 +28,6 @@ - + diff --git a/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs b/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs index e1f0b17..1b4b135 100644 --- a/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs +++ b/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs @@ -10,6 +10,38 @@ using UserCenter.Model; namespace Learn.Archives.Core.Model.Dto { + + + public class PositionIdsReq + { + /// + /// 学校编号 + /// + public long SchoolId { get; set; } + /// + /// 年级 + /// + public string GradeLevel { get; set; } + /// + /// 毕业届 + /// + public int GraduationYear { get; set; } + + /// + /// 班级编号 + /// + public long? ClassId { get; set; } + + /// + /// 职级 + /// + public int PositionLevel { get; set; } + public int? PositionType { get; set; } = 2; + /// + /// 科目 + /// + public int? SubjectId { get; set; } + } public class TeacherInfoImportError : TeacherInfoImport { /// From 43a16815ca62038413551779f3628bbea7535901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Tue, 23 Sep 2025 18:20:31 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=80=83=E8=AF=95?= =?UTF-8?q?=20=E5=A4=9A=E5=AD=A6=E7=A7=91=E6=88=90=E7=BB=A9=E5=88=86?= =?UTF-8?q?=E6=AE=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ExamClassInfoController.cs | 93 +++++++++++++++---- .../Controllers/ExamController.cs | 10 +- .../Controllers/ExamTagsController.cs | 28 ++++++ Learn.Archives.API/appsettings.json | 2 +- Learn.Archives.Core/Model/Dto/ExamClassDto.cs | 13 +++ Learn.Archives.Core/Model/Exam.cs | 3 +- Learn.Archives.Core/Model/ExamClassInfo.cs | 28 +++--- Learn.Archives.Core/Model/ExamClassTag.cs | 77 +++++++++++++++ Learn.Archives.Core/Model/ExamTags.cs | 73 +++++++++++++++ 9 files changed, 291 insertions(+), 36 deletions(-) create mode 100644 Learn.Archives.API/Controllers/ExamTagsController.cs create mode 100644 Learn.Archives.Core/Model/Dto/ExamClassDto.cs create mode 100644 Learn.Archives.Core/Model/ExamClassTag.cs create mode 100644 Learn.Archives.Core/Model/ExamTags.cs diff --git a/Learn.Archives.API/Controllers/ExamClassInfoController.cs b/Learn.Archives.API/Controllers/ExamClassInfoController.cs index b74a5d7..b474e78 100644 --- a/Learn.Archives.API/Controllers/ExamClassInfoController.cs +++ b/Learn.Archives.API/Controllers/ExamClassInfoController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; using System.Buffers.Text; +using System.Collections.Generic; using System.Diagnostics; using System.Security.Claims; using UserCenter.Model; @@ -25,10 +26,14 @@ namespace Learn.Archives.API.Controllers readonly Repository baseService; readonly Repository examService; readonly Repository schoolService; + readonly Repository tagService; 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) + public ExamClassInfoController(Repository baseService, LiveUserInfo userInfo, + IHttpContextAccessor httpContext, Repository examUserInfoService, + Repository examService, Repository schoolService, + Repository tagService) : base(baseService) { this.baseService = baseService; this.userInfo = userInfo; @@ -36,8 +41,9 @@ namespace Learn.Archives.API.Controllers this.examUserInfoService = examUserInfoService; this.examService = examService; this.schoolService = schoolService; + this.tagService = tagService; } - + [NonAction] private Dictionary? ImportExamInfoSubjectDic(ImportExamInfo info) { @@ -54,7 +60,19 @@ namespace Learn.Archives.API.Controllers { SubjectEnum.地理, info.地理}, }; } - + [NonAction] + private static decimal? SubjectScore(ExamUserInfo info,SubjectEnum? s) + { + switch (s) + { + case null: + return info.AssignScore; + default: + if (info?.SubjectDic is null) + return -999; + return info.SubjectDic.ContainsKey(s.Value)? info.SubjectDic?[s.Value] : -999 ; + } + } /// /// 导入考试信息 /// @@ -175,7 +193,7 @@ namespace Learn.Archives.API.Controllers //写入数据库 await examUserInfoService.AsInsertable(insertUserInfo).ExecuteCommandAsync(); - await CalculatingTestResults(exam,examUserInfoService,schoolService); + await CalculatingTestResults(exam,examUserInfoService,schoolService, tagService); return Ok(); } @@ -188,18 +206,23 @@ namespace Learn.Archives.API.Controllers /// /// [NonAction] - public static async Task CalculatingTestResults(Exam exam, Repository eUService, Repository sService) + public static async Task CalculatingTestResults(Exam exam, Repository eUService, + Repository sService, Repository tagService) { var userInfoArr = await eUService.AsQueryable() .Where(s => s.ExamId == exam.Id) .ToArrayAsync(); var insertTotalClassInfo = new List(); + var insertTotalClassTag = new List(); + var eTagArr = await tagService.AsQueryable().Where(s => s.ExamId == exam.Id) + .ToArrayAsync(); var db = sService.Context; foreach (var schoolArr in userInfoArr.GroupBy(s => s.SchoolId)) { var insertClassInfo = new List(); + var insertClassTag = new List(); var school = await sService.GetFirstAsync(s => s.Id == schoolArr.Key); var classArr = await db.Queryable().Where(c => c.SchoolId == school.Id && c.GradeLevel == exam.GradeLevel && @@ -228,12 +251,43 @@ namespace Learn.Archives.API.Controllers }; insertClassInfo.Add(eCInfo); var avgTotal = 0m; + + //todo 班级分段分析 + var classTagDic = new Dictionary(); foreach (var eUserInfo in classUserArr) { - var v = eUserInfo.AssignScore; + if (!classTagDic.TryGetValue(eUserInfo.ClassId, out ExamClassTag[]? classTagArr)) + { + classTagArr = new ExamClassTag[10]; + classTagDic.Add(eUserInfo.ClassId, classTagArr); + } //上线人数 - if (v >= exam.ScoreLine) - eCInfo.OnLineCount++; + foreach (var item in eTagArr) + { + var subV = SubjectScore(eUserInfo, item.SubjectId); + //总分分段 + if (subV >= item.MinScore && subV <= item.MaxScore) + { + var tag = classTagArr[(int)(item.SubjectId ?? 0)]; + if (tag is null) + { + tag = new ExamClassTag() + { + MaxScore = item.MaxScore, + MinScore = item.MinScore, + ExamId =exam.Id, + ExamTagId = item.Id, + Name = item.TagName, + SubjectId = item.SubjectId + }; + insertClassTag.Add(tag); + classTagArr[(int)(item.SubjectId ?? 0)]= tag; + } + classTagArr[(int)(item.SubjectId ?? 0)].OnLineCount++; + } + + } + var v = eUserInfo.AssignScore; //最大小分 if (v < eCInfo.MinScore) eCInfo.MinScore = v; @@ -244,23 +298,28 @@ namespace Learn.Archives.API.Controllers //总分平均分 eCInfo.Average = avgTotal / eCInfo.PeopleCount; //计算上线率 - eCInfo.OnLineRate = (decimal)eCInfo.OnLineCount / (decimal)eCInfo.PeopleCount; + if(classTagDic.ContainsKey(eCInfo.ClassId)) + foreach (var item in classTagDic[eCInfo.ClassId]) + item.OnLineRate = (decimal)item.OnLineCount / (decimal)eCInfo.PeopleCount; } - insertTotalClassInfo.AddRange(insertClassInfo); + insertTotalClassTag.AddRange(insertClassTag); } - { //计算年级上线率排名 - var i = 0; - foreach (var item in insertTotalClassInfo.OrderByDescending(s => s.OnLineRate) - .GroupBy(s => s.OnLineRate)) + { //计算班级上线率排名 + foreach (var examTagArr in insertTotalClassTag.GroupBy(s => s.ExamTagId)) { - foreach (var u in item) - u.OnLineRanking = ++i; + var i = 0; + foreach (var item in examTagArr.OrderByDescending(s => s.OnLineRate) + .GroupBy(s => s.OnLineRate)) + { + foreach (var u in item) + u.OnLineRanking = ++i; + } } //计算年级平均分排名 - i = 0; + var i = 0; foreach (var item in insertTotalClassInfo.OrderByDescending(s => s.Average) .GroupBy(s => s.Average)) { diff --git a/Learn.Archives.API/Controllers/ExamController.cs b/Learn.Archives.API/Controllers/ExamController.cs index 1b8d7d8..e28a7d7 100644 --- a/Learn.Archives.API/Controllers/ExamController.cs +++ b/Learn.Archives.API/Controllers/ExamController.cs @@ -20,18 +20,20 @@ namespace Learn.Archives.API.Controllers { readonly Repository baseService; readonly Repository schoolService; + readonly Repository tagService; readonly Repository examClassInfoService; readonly Repository examUserInfoService; readonly LiveUserInfo userInfo; - public ExamController(Repository baseService, LiveUserInfo userInfo, - Repository examClassInfoService, Repository examUserInfoService, - Repository schoolService) : base(baseService) + public ExamController(Repository baseService, LiveUserInfo userInfo, + Repository examClassInfoService, Repository examUserInfoService, + Repository schoolService, Repository tagService) : base(baseService) { this.baseService = baseService; this.userInfo = userInfo; this.examClassInfoService = examClassInfoService; this.examUserInfoService = examUserInfoService; this.schoolService = schoolService; + this.tagService = tagService; } public override Task PageList([FromBody] QueryRequestBase model) { @@ -69,7 +71,7 @@ namespace Learn.Archives.API.Controllers } var res =await base.Edit(model); if (res) - await ExamClassInfoController.CalculatingTestResults(model, examUserInfoService, schoolService); + await ExamClassInfoController.CalculatingTestResults(model, examUserInfoService, schoolService, tagService); return res ; } public override async Task Del([FromBody] params long[] ids) diff --git a/Learn.Archives.API/Controllers/ExamTagsController.cs b/Learn.Archives.API/Controllers/ExamTagsController.cs new file mode 100644 index 0000000..2d61097 --- /dev/null +++ b/Learn.Archives.API/Controllers/ExamTagsController.cs @@ -0,0 +1,28 @@ +using Learn.Archives.API.Controllers.Dto; +using Learn.Archives.API.Expand; +using Learn.Archives.Core.Common; +using Learn.Archives.Core.Model; +using Learn.Archives.Core.Model.Dto; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using System.Linq; +using System.Security.Claims; +using UserCenter.Model; +using UserCenter.Model.Common; +using UserCenter.Model.Enum; + +namespace Learn.Archives.API.Controllers +{ + /// + /// 年级控制器 + /// + public class ExamTagsController : BackController + { + readonly Repository baseService; + public ExamTagsController(Repository baseService) : base(baseService) + { + this.baseService = baseService; + } + } +} diff --git a/Learn.Archives.API/appsettings.json b/Learn.Archives.API/appsettings.json index d3d0c1e..76c592f 100644 --- a/Learn.Archives.API/appsettings.json +++ b/Learn.Archives.API/appsettings.json @@ -14,7 +14,7 @@ "DB": { "ConnectionString": "AllowLoadLocalInfile=true;Server=58.17.132.2;User ID=marking;Password=qwe123!@#;Port=3306;Database=learn.archives;CharSet=utf8mb4;Port=43306;pooling=true;SslMode=None;", "SqlType": "MySql", - "UpdateTable": false + "UpdateTable": true }, "AuthKey": { "Secret": "9FAB7AC7-F1DB-4C56-B84F-044055A34AF2", diff --git a/Learn.Archives.Core/Model/Dto/ExamClassDto.cs b/Learn.Archives.Core/Model/Dto/ExamClassDto.cs new file mode 100644 index 0000000..ebe47a1 --- /dev/null +++ b/Learn.Archives.Core/Model/Dto/ExamClassDto.cs @@ -0,0 +1,13 @@ +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Learn.Archives.Core.Model.Dto +{ + + + +} diff --git a/Learn.Archives.Core/Model/Exam.cs b/Learn.Archives.Core/Model/Exam.cs index 58f9fbb..9e521a2 100644 --- a/Learn.Archives.Core/Model/Exam.cs +++ b/Learn.Archives.Core/Model/Exam.cs @@ -38,7 +38,8 @@ namespace Learn.Archives.Core.Model /// public string? _grade; /// - /// 年级 + /// 年级 + /// AOP自动转换年级 /// [SugarColumn(IsIgnore = true)] public string Grade diff --git a/Learn.Archives.Core/Model/ExamClassInfo.cs b/Learn.Archives.Core/Model/ExamClassInfo.cs index 4223d4c..8bf0b2d 100644 --- a/Learn.Archives.Core/Model/ExamClassInfo.cs +++ b/Learn.Archives.Core/Model/ExamClassInfo.cs @@ -1,4 +1,5 @@ -using Learn.Archives.Core.Model.Enum; +using Learn.Archives.Core.Model.Dto; +using Learn.Archives.Core.Model.Enum; using Learn.Archives.Core.Model.Interface; using SqlSugar; using System.ComponentModel.DataAnnotations; @@ -71,18 +72,19 @@ namespace Learn.Archives.Core.Model /// public int GradeYear { get; set; } - /// - /// 上线率 考试排名 - /// - public decimal OnLineRanking { get; set; } - /// - /// 上线率 - /// - public decimal OnLineRate { get; set; } - /// - /// 上线人数 - /// - public int OnLineCount { get; set; } + ///// + ///// 上线率 考试排名 + ///// + //public decimal OnLineRanking { get; set; } + ///// + ///// 上线率 + ///// + //public decimal OnLineRate { get; set; } + ///// + ///// 上线人数 + ///// + //public int OnLineCount { get; set; } + /// /// 参加人数 /// diff --git a/Learn.Archives.Core/Model/ExamClassTag.cs b/Learn.Archives.Core/Model/ExamClassTag.cs new file mode 100644 index 0000000..9cefd5c --- /dev/null +++ b/Learn.Archives.Core/Model/ExamClassTag.cs @@ -0,0 +1,77 @@ +using Learn.Archives.Core.Model.Dto; +using Learn.Archives.Core.Model.Enum; +using Learn.Archives.Core.Model.Interface; +using SqlSugar; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Text.Json; +using UserCenter.Model; +using UserCenter.Model.Common; +using UserCenter.Model.Enum; +using UserCenter.Model.Interface; + +namespace Learn.Archives.Core.Model +{ + /// + /// 班级考试详情 + /// + [SugarTable("examclasstag")] + public class ExamClassTag : EntityBaseId, IDB + { + /// + /// 考试Id + /// + [SugarColumn(Length = 20)] + public long ExamId { get; set; } + + /// + /// 考试标签id + /// + public long ExamTagId { get; set; } + /// + /// 标签名称 + /// + public string Name { get; set; } + + + /// + /// 分段所属学科 + /// 可空 空即为总分分段 + /// + [SugarColumn(IsNullable = true)] + public SubjectEnum? SubjectId { get; set; } + + /// + /// 最小分值 + /// + [SugarColumn(DecimalDigits = 2)] + public decimal MinScore { get; set; } + + /// + /// 最大分值 + /// + [SugarColumn(DecimalDigits = 2)] + public decimal MaxScore { get; set; } + + /// + /// 上线率 考试排名 + /// + public int OnLineRanking { get; set; } + /// + /// 上线率 + /// + [SugarColumn(DecimalDigits = 2)] + public decimal OnLineRate { get; set; } + /// + /// 上线人数 + /// + public int OnLineCount { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } = DateTime.Now; + + + } +} diff --git a/Learn.Archives.Core/Model/ExamTags.cs b/Learn.Archives.Core/Model/ExamTags.cs new file mode 100644 index 0000000..edb3cde --- /dev/null +++ b/Learn.Archives.Core/Model/ExamTags.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Text; + +using UserCenter.Model; +using UserCenter.Model.Interface; +using SqlSugar; +using UserCenter.Model.Enum; +using Learn.Archives.Core.Model.Interface; + +namespace Learn.Archives.Core.Model +{ + /// + /// 考试成绩分段表 + /// + [SugarTable("examtags")] + public partial class ExamTags : EntityBaseId, IDB + { + public ExamTags() + { + + this.IsDefault = Convert.ToInt32("0"); + this.CreateTime = DateTime.Now; + + } + /// + /// + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public override long Id { get; set; } + + /// + /// 考试编号 + /// + public long ExamId { get; set; } + + + /// + /// 分段所属学科 + /// 可空 空即为总分分段 + /// + [SugarColumn(IsNullable = true)] + public SubjectEnum? SubjectId { get; set; } + + /// + /// 最小分值 + /// + [SugarColumn(DecimalDigits = 2)] + public decimal MinScore { get; set; } + + /// + /// 最大分值 + /// + [SugarColumn(DecimalDigits = 2)] + public decimal MaxScore { get; set; } + + /// + /// 标签名称 + /// + public string TagName { get; set; } + + /// + /// 是否为默认标签 0:否 1:是 + /// + public int IsDefault { get; set; } + + /// + /// 添加时间 + /// + public DateTime CreateTime { get; set; } + + } +} From fb6147ecbf234f6754f274da138964307ba0f260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Wed, 24 Sep 2025 19:17:49 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E7=8F=AD=E7=BA=A7?= =?UTF-8?q?=E8=80=83=E8=AF=95=E6=88=90=E7=BB=A9=E5=88=86=E6=AE=B5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20=E5=88=A0=E9=99=A4=20=E5=A4=9A=E4=BD=99=E7=9A=84?= =?UTF-8?q?=E7=8F=AD=E7=BA=A7=E7=B1=BB=E5=9E=8B=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Dto/ExamClassInfoDto.cs | 15 +- .../Controllers/ExamClassInfoController.cs | 414 ++++++++++-------- .../Controllers/StudentController.cs | 4 - .../Model/Dto/UserCenterServiceDto.cs | 15 - Learn.Archives.Core/Model/ExamClassTag.cs | 5 + 5 files changed, 247 insertions(+), 206 deletions(-) diff --git a/Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs b/Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs index a6b065e..a18a342 100644 --- a/Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs +++ b/Learn.Archives.API/Controllers/Dto/ExamClassInfoDto.cs @@ -1,7 +1,15 @@ -using MiniExcelLibs.Attributes; +using Learn.Archives.Core.Model; +using MiniExcelLibs.Attributes; namespace Learn.Archives.API.Controllers.Dto { + public class ExamClassTagReq : ExamClassTag + { + /// + /// 学科类型 + /// + public string SubjectStr => SubjectId?.ToString() ?? "总分"; + } public class ImportExamDto { /// @@ -46,11 +54,6 @@ namespace Learn.Archives.API.Controllers.Dto [ExcelColumnName("云校班级号")] public string Class { get; set; } /// - /// - /// - [ExcelColumnName("班级类型")] - public string ClassType { get; set; } - /// /// 学生姓名 /// [ExcelColumnName("学生姓名")] diff --git a/Learn.Archives.API/Controllers/ExamClassInfoController.cs b/Learn.Archives.API/Controllers/ExamClassInfoController.cs index b474e78..4abca20 100644 --- a/Learn.Archives.API/Controllers/ExamClassInfoController.cs +++ b/Learn.Archives.API/Controllers/ExamClassInfoController.cs @@ -27,13 +27,14 @@ namespace Learn.Archives.API.Controllers readonly Repository examService; readonly Repository schoolService; readonly Repository tagService; + readonly Repository classTagService; readonly Repository examUserInfoService; readonly LiveUserInfo userInfo; readonly IHttpContextAccessor accessor; public ExamClassInfoController(Repository baseService, LiveUserInfo userInfo, - IHttpContextAccessor httpContext, Repository examUserInfoService, - Repository examService, Repository schoolService, - Repository tagService) : base(baseService) + IHttpContextAccessor httpContext, Repository examUserInfoService, + Repository examService, Repository schoolService, + Repository tagService, Repository classTagService) : base(baseService) { this.baseService = baseService; this.userInfo = userInfo; @@ -42,37 +43,9 @@ namespace Learn.Archives.API.Controllers this.examService = examService; this.schoolService = schoolService; this.tagService = tagService; + this.classTagService = classTagService; } - [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.地理}, - }; - } - [NonAction] - private static decimal? SubjectScore(ExamUserInfo info,SubjectEnum? s) - { - switch (s) - { - case null: - return info.AssignScore; - default: - if (info?.SubjectDic is null) - return -999; - return info.SubjectDic.ContainsKey(s.Value)? info.SubjectDic?[s.Value] : -999 ; - } - } /// /// 导入考试信息 /// @@ -136,14 +109,14 @@ namespace Learn.Archives.API.Controllers !p.DeleteState && !c.DeleteState && !u.DeleteState && !s.DeleteState) .Select((s, c, p, pr, u) => new { - Name = c.Name + c.Type.ToString() + u.RealName, + Name = c.Name + u.RealName, u.Id, }).ToDictionaryAsync(s => s.Name, s => s.Id); //处理学生成绩数据 var userList = dataList.Select(s => { var classInfo = classArr - .FirstOrDefault(x => x.Name == s.Class && x.Type == s.ClassType.ToEnum()); + .FirstOrDefault(x => x.Name == s.Class ); if (classInfo == null) { s.Error = "未能匹配班级"; @@ -152,7 +125,7 @@ namespace Learn.Archives.API.Controllers } var grade = GradeHelper.GetStudentGradeBaseByGrade(s.Grade); var sub = ImportExamInfoSubjectDic(s); - var name = s.Class + s.ClassType.ToEnum().GetHashCode() + s.Student; + var name = s.Class + s.Student; if (!userDic.ContainsKey(name)) { s.Error = "未能匹配到年级班级下对应的学生"; @@ -198,151 +171,6 @@ namespace Learn.Archives.API.Controllers return Ok(); } - /// - /// 计算考试成绩 - /// - /// - /// - /// - /// - [NonAction] - public static async Task CalculatingTestResults(Exam exam, Repository eUService, - Repository sService, Repository tagService) - { - var userInfoArr = await eUService.AsQueryable() - .Where(s => s.ExamId == exam.Id) - .ToArrayAsync(); - - var insertTotalClassInfo = new List(); - var insertTotalClassTag = new List(); - var eTagArr = await tagService.AsQueryable().Where(s => s.ExamId == exam.Id) - .ToArrayAsync(); - var db = sService.Context; - foreach (var schoolArr in userInfoArr.GroupBy(s => s.SchoolId)) - { - - var insertClassInfo = new List(); - var insertClassTag = new List(); - var school = await sService.GetFirstAsync(s => s.Id == schoolArr.Key); - var classArr = await db.Queryable().Where(c => c.SchoolId == school.Id && - c.GradeLevel == exam.GradeLevel && - c.GraduationYear == exam.GradeYear && !c.DeleteState).ToArrayAsync(); - - foreach (var classUserArr in userInfoArr.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, - MaxScore = -99, - BaseSchoolScore = exam.BaseSchoolScore, - TestPaperType = exam.TestPaperType, - Type = exam.Type, - Average1 = exam.BaseSchoolScore, - }; - insertClassInfo.Add(eCInfo); - var avgTotal = 0m; - - //todo 班级分段分析 - var classTagDic = new Dictionary(); - foreach (var eUserInfo in classUserArr) - { - if (!classTagDic.TryGetValue(eUserInfo.ClassId, out ExamClassTag[]? classTagArr)) - { - classTagArr = new ExamClassTag[10]; - classTagDic.Add(eUserInfo.ClassId, classTagArr); - } - //上线人数 - foreach (var item in eTagArr) - { - var subV = SubjectScore(eUserInfo, item.SubjectId); - //总分分段 - if (subV >= item.MinScore && subV <= item.MaxScore) - { - var tag = classTagArr[(int)(item.SubjectId ?? 0)]; - if (tag is null) - { - tag = new ExamClassTag() - { - MaxScore = item.MaxScore, - MinScore = item.MinScore, - ExamId =exam.Id, - ExamTagId = item.Id, - Name = item.TagName, - SubjectId = item.SubjectId - }; - insertClassTag.Add(tag); - classTagArr[(int)(item.SubjectId ?? 0)]= tag; - } - classTagArr[(int)(item.SubjectId ?? 0)].OnLineCount++; - } - - } - var v = eUserInfo.AssignScore; - //最大小分 - if (v < eCInfo.MinScore) - eCInfo.MinScore = v; - if (v > eCInfo.MaxScore) - eCInfo.MaxScore = v; - avgTotal += v;//追加得分 - } - //总分平均分 - eCInfo.Average = avgTotal / eCInfo.PeopleCount; - //计算上线率 - if(classTagDic.ContainsKey(eCInfo.ClassId)) - foreach (var item in classTagDic[eCInfo.ClassId]) - item.OnLineRate = (decimal)item.OnLineCount / (decimal)eCInfo.PeopleCount; - } - insertTotalClassInfo.AddRange(insertClassInfo); - insertTotalClassTag.AddRange(insertClassTag); - } - - { //计算班级上线率排名 - foreach (var examTagArr in insertTotalClassTag.GroupBy(s => s.ExamTagId)) - { - var i = 0; - foreach (var item in examTagArr.OrderByDescending(s => s.OnLineRate) - .GroupBy(s => s.OnLineRate)) - { - foreach (var u in item) - u.OnLineRanking = ++i; - } - } - - //计算年级平均分排名 - var i = 0; - foreach (var item in insertTotalClassInfo.OrderByDescending(s => s.Average) - .GroupBy(s => s.Average)) - { - foreach (var u in item) - u.AverageRank = ++i; - } - } - //写入数据库 - var baseDB = eUService.Context; - baseDB.Ado.BeginTran(); - try - { - await baseDB.Deleteable().Where(s=>s.ExamId==exam.Id).ExecuteCommandAsync(); - await baseDB.Insertable(insertTotalClassInfo).ExecuteCommandAsync(); - baseDB.Ado.CommitTran(); - } - catch (Exception ex) - { - baseDB.Ado.RollbackTran(); - Oh.ModelError($"导入失败:写入数据时候出现了异常 [{ex.Message}] !"); - } - - } /// /// 下载导入成绩模板 @@ -355,7 +183,6 @@ namespace Learn.Archives.API.Controllers { School="例子学校[导入时候请删除]", Class="测试班级", - ClassType="普通班级", Grade="高2028", Student="学生姓名", 语文=80.5m, @@ -418,5 +245,230 @@ namespace Learn.Archives.API.Controllers } return base.PageList(model); } + + + + /// + /// 重新计算考试成绩排名 + /// + /// 考试id + /// + [HttpGet] + public async Task RecalculateExamRankings(long examId) + { + if (examId == 0) + Oh.ModelError("传入了异常参数"); + var exam = examService.GetById(examId); + if (exam is null) + Oh.ModelError("无效的考试"); + await CalculatingTestResults(exam, examUserInfoService, schoolService, tagService); + return Ok(); + + } + + + /// + /// 获取班级考试分段排名 + /// + /// 考试id + /// + [HttpGet] + public async Task> ClassRanking(long examId,long classId) + { + if (examId == 0) + Oh.ModelError("传入异常参数"); + if (! await examService.IsAnyAsync(s => s.Id == examId)) + Oh.ModelError("无效的考试"); + var arr = await classTagService.AsQueryable().Where(s => s.ExamId == examId && s.ClassId == classId) + .Select() + .ToArrayAsync(); + return arr.OrderBy(s => s.SubjectId); + } + + + + + + [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.地理}, + }; + } + [NonAction] + private static decimal? SubjectScore(ExamUserInfo info, SubjectEnum? s) + { + switch (s) + { + case null: + return info.AssignScore; + default: + if (info?.SubjectDic is null) + return -999; + return info.SubjectDic.ContainsKey(s.Value) ? info.SubjectDic?[s.Value] : -999; + } + } + + /// + /// 计算考试成绩 + /// + /// + /// + /// + /// + [NonAction] + public static async Task CalculatingTestResults(Exam exam, Repository eUService, + Repository sService, Repository tagService) + { + var userInfoArr = await eUService.AsQueryable() + .Where(s => s.ExamId == exam.Id) + .ToArrayAsync(); + + var insertTotalClassInfo = new List(); + var insertTotalClassTag = new List(); + var eTagArr = await tagService.AsQueryable().Where(s => s.ExamId == exam.Id) + .ToArrayAsync(); + var db = sService.Context; + foreach (var schoolArr in userInfoArr.GroupBy(s => s.SchoolId)) + { + + var insertClassInfo = new List(); + var insertClassTag = new List(); + var school = await sService.GetFirstAsync(s => s.Id == schoolArr.Key); + var classArr = await db.Queryable().Where(c => c.SchoolId == school.Id && + c.GradeLevel == exam.GradeLevel && + c.GraduationYear == exam.GradeYear && !c.DeleteState).ToArrayAsync(); + + foreach (var classUserArr in userInfoArr.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, + MaxScore = -99, + BaseSchoolScore = exam.BaseSchoolScore, + TestPaperType = exam.TestPaperType, + Type = exam.Type, + Average1 = exam.BaseSchoolScore, + }; + insertClassInfo.Add(eCInfo); + var avgTotal = 0m; + + //todo 班级分段分析 + var classTagDic = new Dictionary(); + foreach (var eUserInfo in classUserArr) + { + if (!classTagDic.TryGetValue(eUserInfo.ClassId, out ExamClassTag[]? classTagArr)) + { + classTagArr = new ExamClassTag[10]; + classTagDic.Add(eUserInfo.ClassId, classTagArr); + } + //上线人数 + foreach (var item in eTagArr) + { + var subV = SubjectScore(eUserInfo, item.SubjectId); + //总分分段 + if (subV >= item.MinScore && subV <= item.MaxScore) + { + var tag = classTagArr[(int)(item.SubjectId ?? 0)]; + if (tag is null) + { + tag = new ExamClassTag() + { + MaxScore = item.MaxScore, + MinScore = item.MinScore, + ClassId = classInfo.Id, + ExamId = exam.Id, + ExamTagId = item.Id, + Name = item.TagName, + SubjectId = item.SubjectId + }; + insertClassTag.Add(tag); + classTagArr[(int)(item.SubjectId ?? 0)] = tag; + } + classTagArr[(int)(item.SubjectId ?? 0)].OnLineCount++; + } + + } + var v = eUserInfo.AssignScore; + //最大小分 + if (v < eCInfo.MinScore) + eCInfo.MinScore = v; + if (v > eCInfo.MaxScore) + eCInfo.MaxScore = v; + avgTotal += v;//追加得分 + } + //总分平均分 + eCInfo.Average = avgTotal / eCInfo.PeopleCount; + //计算上线率 + if (classTagDic.ContainsKey(eCInfo.ClassId)) + foreach (var item in classTagDic[eCInfo.ClassId]) + if(item !=null) item.OnLineRate = (decimal)item.OnLineCount / (decimal)eCInfo.PeopleCount; + } + insertTotalClassInfo.AddRange(insertClassInfo); + insertTotalClassTag.AddRange(insertClassTag); + } + + { + + var i = 0; + //计算班级上线率排名 + foreach (var examTagArr in insertTotalClassTag.GroupBy(s => s.ExamTagId)) + { + i = 0; + foreach (var item in examTagArr.OrderByDescending(s => s.OnLineRate) + .GroupBy(s => s.OnLineRate)) + { + foreach (var u in item) + u.OnLineRanking = ++i; + } + } + + //计算年级平均分排名 + i = 0; + foreach (var item in insertTotalClassInfo.OrderByDescending(s => s.Average) + .GroupBy(s => s.Average)) + { + foreach (var u in item) + u.AverageRank = ++i; + } + } + //写入数据库 + var baseDB = eUService.Context; + baseDB.Ado.BeginTran(); + try + { + await baseDB.Deleteable().Where(s => s.ExamId == exam.Id).ExecuteCommandAsync(); + await baseDB.Deleteable().Where(s => s.ExamId == exam.Id).ExecuteCommandAsync(); + await baseDB.Insertable(insertTotalClassInfo).ExecuteCommandAsync(); + await baseDB.Insertable(insertTotalClassTag).ExecuteCommandAsync(); + baseDB.Ado.CommitTran(); + } + catch (Exception ex) + { + baseDB.Ado.RollbackTran(); + Oh.ModelError($"导入失败:写入数据时候出现了异常 [{ex.Message}] !"); + } + + } } } diff --git a/Learn.Archives.API/Controllers/StudentController.cs b/Learn.Archives.API/Controllers/StudentController.cs index d74102e..6f65129 100644 --- a/Learn.Archives.API/Controllers/StudentController.cs +++ b/Learn.Archives.API/Controllers/StudentController.cs @@ -221,7 +221,6 @@ namespace Learn.Archives.API.Controllers School = s.School, Grade = gStr, Class = s.Class, - ClassType = s.ClassType??ClassTypeEnum.云校班.ToString(), Phone = s.Phone, RealName = s.RealName, Stages = s.Grade.Contains("初") ? StudentStagesEnum.初中.ToString() : StudentStagesEnum.高中.ToString() @@ -302,7 +301,6 @@ namespace Learn.Archives.API.Controllers School = s.School, Grade = gStr, Class = s.Class, - ClassType = s.ClassType??ClassTypeEnum.云校班.ToString(), ExamNo = s.Id.ToString(), Phone = s.Phone, RealName = s.RealName, @@ -353,7 +351,6 @@ namespace Learn.Archives.API.Controllers School = "必填:与系统匹配", Grade = "必填:可选值\r\n[初一初二初三,高一高二高山]", Class = "必填:与系统匹配\r\n格式:10班[数字+班]", - ClassType = "必填:可选值\r\n[云校班 海豚智学班 蓝鲸智库班 中职班 其他 雅思班 点阵笔班级 移动校园班级 智学班 ...]", Status = "选填 可选值\r\n[就读,退出]", AmountRelief ="选填: 为0则视为 '未申请减免'", ReliefType ="选填: 建卡贫困户\r\n低保户\r\n教师子女 \r\n孤儿\r\n艺体生\r\n残疾学生\r\n领导特殊承诺减免\r\n领导同意的特殊贫困减免", @@ -385,7 +382,6 @@ namespace Learn.Archives.API.Controllers School = "必填:与系统匹配", Grade = "必填:可选值\r\n[初一初二初三,高一高二高山]", Class = "必填:与系统匹配\r\n格式:10班[数字+班]\r\n任教类型是年级主任时不填", - ClassType = "必填:可选值\r\n[云校班 海豚智学班 蓝鲸智库班 中职班 其他 雅思班 点阵笔班级 移动校园班级 智学班 ...]\r\n任教类型是年级主任时不填", Subject = "选填学科", ExamNo ="选填: 填写老师任职信息[不在授课架构中的职务]", }}; diff --git a/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs b/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs index 1b4b135..19ca055 100644 --- a/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs +++ b/Learn.Archives.Core/Model/Dto/UserCenterServiceDto.cs @@ -83,11 +83,6 @@ namespace Learn.Archives.Core.Model.Dto /// [ExcelColumn(Name = "班级", Width = 15)] public string Class { get; set; } - /// - /// 班级类型 - /// - [ExcelColumn(Name = "班级类型", Width = 20)] - public string ClassType { get; set; } /// /// 科目 @@ -161,11 +156,6 @@ namespace Learn.Archives.Core.Model.Dto /// [ExcelColumn(Name="班级", Width = 15)] public string Class { get; set; } - /// - /// 班级 - /// - [ExcelColumn(Name="班级类型", Width = 20)] - public string ClassType { get; set; } /// /// 减免金额 @@ -295,11 +285,6 @@ namespace Learn.Archives.Core.Model.Dto [ExcelColumn(Name="班级")] public string Class { get; set; } /// - /// 班级 - /// - [ExcelColumn(Name="班级类型")] - public string ClassType { get; set; } - /// /// 科目 /// [ExcelColumn(Name="科目")] diff --git a/Learn.Archives.Core/Model/ExamClassTag.cs b/Learn.Archives.Core/Model/ExamClassTag.cs index 9cefd5c..b14b668 100644 --- a/Learn.Archives.Core/Model/ExamClassTag.cs +++ b/Learn.Archives.Core/Model/ExamClassTag.cs @@ -24,6 +24,11 @@ namespace Learn.Archives.Core.Model [SugarColumn(Length = 20)] public long ExamId { get; set; } + /// + /// 班级id + /// + public long ClassId { get; set; } + /// /// 考试标签id /// From d31c898d35c0c2a869307bc3a063a84d43236b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Thu, 25 Sep 2025 14:03:49 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E7=8F=AD=E7=BA=A7?= =?UTF-8?q?=E5=AD=A6=E7=94=9F=E6=88=90=E7=BB=A9=E6=8E=92=E5=BA=8F=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Learn.Archives.API/Controllers/ExamUserInfoController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Learn.Archives.API/Controllers/ExamUserInfoController.cs b/Learn.Archives.API/Controllers/ExamUserInfoController.cs index c7a5903..ca1d860 100644 --- a/Learn.Archives.API/Controllers/ExamUserInfoController.cs +++ b/Learn.Archives.API/Controllers/ExamUserInfoController.cs @@ -57,6 +57,7 @@ namespace Learn.Archives.API.Controllers res.Data = res.Data.OrderBy(s => s.SubjectDic?[oldSer.Value]).ToList(); else res.Data = res.Data.OrderByDescending(s => s.SubjectDic?[oldSer.Value]).ToList(); + return res; } return await base.PageList(model); } From 90a6815c7ae31e2eed950c959c3ff6390d8d8a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Thu, 25 Sep 2025 15:42:53 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E7=8F=AD=E7=BA=A7?= =?UTF-8?q?=E5=88=86=E6=AE=B5=E5=88=86=E6=9E=90=E9=94=99=E8=AF=AF=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ExamClassInfoController.cs | 51 +++++++------------ Learn.Archives.Core/Model/ExamClassTag.cs | 5 +- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/Learn.Archives.API/Controllers/ExamClassInfoController.cs b/Learn.Archives.API/Controllers/ExamClassInfoController.cs index 4abca20..d9d6c60 100644 --- a/Learn.Archives.API/Controllers/ExamClassInfoController.cs +++ b/Learn.Archives.API/Controllers/ExamClassInfoController.cs @@ -342,8 +342,6 @@ namespace Learn.Archives.API.Controllers foreach (var schoolArr in userInfoArr.GroupBy(s => s.SchoolId)) { - var insertClassInfo = new List(); - var insertClassTag = new List(); var school = await sService.GetFirstAsync(s => s.Id == schoolArr.Key); var classArr = await db.Queryable().Where(c => c.SchoolId == school.Id && c.GradeLevel == exam.GradeLevel && @@ -370,42 +368,37 @@ namespace Learn.Archives.API.Controllers Type = exam.Type, Average1 = exam.BaseSchoolScore, }; - insertClassInfo.Add(eCInfo); + insertTotalClassInfo.Add(eCInfo); var avgTotal = 0m; //todo 班级分段分析 - var classTagDic = new Dictionary(); + var classTagDic = new Dictionary(); foreach (var eUserInfo in classUserArr) { - if (!classTagDic.TryGetValue(eUserInfo.ClassId, out ExamClassTag[]? classTagArr)) - { - classTagArr = new ExamClassTag[10]; - classTagDic.Add(eUserInfo.ClassId, classTagArr); - } //上线人数 foreach (var item in eTagArr) { var subV = SubjectScore(eUserInfo, item.SubjectId); + if (!classTagDic.TryGetValue(item.Id, out ExamClassTag? tag)) + { + tag = new ExamClassTag() + { + MaxScore = item.MaxScore, + MinScore = item.MinScore, + ClassId = classInfo.Id, + PeopleCount = eCInfo.PeopleCount, + ExamId = exam.Id, + ExamTagId = item.Id, + Name = item.TagName, + SubjectId = item.SubjectId + }; + insertTotalClassTag.Add(tag); + classTagDic.Add(item.Id, tag); + } //总分分段 if (subV >= item.MinScore && subV <= item.MaxScore) { - var tag = classTagArr[(int)(item.SubjectId ?? 0)]; - if (tag is null) - { - tag = new ExamClassTag() - { - MaxScore = item.MaxScore, - MinScore = item.MinScore, - ClassId = classInfo.Id, - ExamId = exam.Id, - ExamTagId = item.Id, - Name = item.TagName, - SubjectId = item.SubjectId - }; - insertClassTag.Add(tag); - classTagArr[(int)(item.SubjectId ?? 0)] = tag; - } - classTagArr[(int)(item.SubjectId ?? 0)].OnLineCount++; + tag.OnLineCount++; } } @@ -419,13 +412,7 @@ namespace Learn.Archives.API.Controllers } //总分平均分 eCInfo.Average = avgTotal / eCInfo.PeopleCount; - //计算上线率 - if (classTagDic.ContainsKey(eCInfo.ClassId)) - foreach (var item in classTagDic[eCInfo.ClassId]) - if(item !=null) item.OnLineRate = (decimal)item.OnLineCount / (decimal)eCInfo.PeopleCount; } - insertTotalClassInfo.AddRange(insertClassInfo); - insertTotalClassTag.AddRange(insertClassTag); } { diff --git a/Learn.Archives.Core/Model/ExamClassTag.cs b/Learn.Archives.Core/Model/ExamClassTag.cs index b14b668..f8a3167 100644 --- a/Learn.Archives.Core/Model/ExamClassTag.cs +++ b/Learn.Archives.Core/Model/ExamClassTag.cs @@ -65,8 +65,9 @@ namespace Learn.Archives.Core.Model /// /// 上线率 /// - [SugarColumn(DecimalDigits = 2)] - public decimal OnLineRate { get; set; } + [SugarColumn(IsIgnore = true)] + public decimal OnLineRate => OnLineCount / PeopleCount; + public int PeopleCount { get; set; } /// /// 上线人数 /// From 7c46a2f49e51bb3452bf027bea7cf4b9459a7ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Thu, 25 Sep 2025 16:09:58 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E4=B8=8A=E7=BA=BF?= =?UTF-8?q?=E7=8E=87=E4=B8=BA=E5=88=86=E6=9E=90=E5=8E=9F=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Learn.Archives.Core/Model/ExamClassTag.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Learn.Archives.Core/Model/ExamClassTag.cs b/Learn.Archives.Core/Model/ExamClassTag.cs index f8a3167..15e490e 100644 --- a/Learn.Archives.Core/Model/ExamClassTag.cs +++ b/Learn.Archives.Core/Model/ExamClassTag.cs @@ -66,7 +66,10 @@ namespace Learn.Archives.Core.Model /// 上线率 /// [SugarColumn(IsIgnore = true)] - public decimal OnLineRate => OnLineCount / PeopleCount; + public decimal OnLineRate => (decimal)OnLineCount / (decimal)PeopleCount; + /// + /// 总人数 + /// public int PeopleCount { get; set; } /// /// 上线人数 From 12c022e05cbf9b7872ea3c55eda880788ed3ae30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Tue, 14 Oct 2025 10:41:07 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=20=E7=AE=A1=E7=90=86=E5=91=98=E5=AF=BC?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/AdminController.cs | 115 +++++++++++++++++- .../Controllers/Dto/AdminDto.cs | 10 ++ .../Controllers/Dto/ClassDto.cs | 33 ++++- .../Controllers/StudentController.cs | 4 +- Learn.Archives.API/Expand/HttpFilter.cs | 67 ++++++---- Learn.Archives.API/Program.cs | 22 ++-- .../Common/BasicAuthMiddleware.cs | 4 +- 7 files changed, 211 insertions(+), 44 deletions(-) create mode 100644 Learn.Archives.API/Controllers/Dto/AdminDto.cs diff --git a/Learn.Archives.API/Controllers/AdminController.cs b/Learn.Archives.API/Controllers/AdminController.cs index 08a15a1..ae41ea9 100644 --- a/Learn.Archives.API/Controllers/AdminController.cs +++ b/Learn.Archives.API/Controllers/AdminController.cs @@ -3,10 +3,16 @@ using Learn.Archives.API.Controllers.Dto; using Learn.Archives.API.Expand; using Learn.Archives.Core.Common; using Learn.Archives.Core.Model; +using Learn.Archives.Core.Model.Dto; +using Learn.Archives.Core.Model.Enum; +using Mapster; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using MiniExcelLibs; using System.Diagnostics; using System.Security.Claims; +using System.Text.RegularExpressions; +using UserCenter.Model.Common; namespace Learn.Archives.API.Controllers { @@ -15,11 +21,17 @@ namespace Learn.Archives.API.Controllers readonly Repository baseService; readonly Repository menuRelationDB; readonly Repository menuDB; - public AdminController(Repository baseService, Repository menuRelationDB, Repository menuDB) : base(baseService) + readonly Repository roleDB; + readonly LiveUserInfo userInfo; + readonly IHttpContextAccessor accessor; + public AdminController(Repository baseService, Repository menuRelationDB, Repository menuDB, IHttpContextAccessor accessor, Repository roleDB, LiveUserInfo userInfo = null) : base(baseService) { this.baseService = baseService; this.menuRelationDB = menuRelationDB; this.menuDB = menuDB; + this.accessor = accessor; + this.roleDB = roleDB; + this.userInfo = userInfo; } /// /// 管理员登录 @@ -44,7 +56,7 @@ namespace Learn.Archives.API.Controllers if (admin.Password != model.Password.GetMD5()) Oh.Error("登录失败,密码错误"); // 获取租户信息 - var buttonRole = admin.RoleId==1 + var buttonRole = admin.RoleId == 1 ? ["*:*:*"] : await menuRelationDB.AsQueryable() .LeftJoin((mr, m) => mr.MenuId == m.Id) @@ -73,12 +85,107 @@ namespace Learn.Archives.API.Controllers } - public override Task Edit([FromBody] Admin model) + public override async Task Edit([FromBody] Admin model) { //创建用户时 密码加密 if (model.Id == 0) model.Password = model.Password.GetMD5(); - return base.Edit(model); + if (string.IsNullOrEmpty(model.Account) || model.Account.Length < 2 || + string.IsNullOrEmpty(model.Phone) || model.Phone.Length < 11 || + string.IsNullOrEmpty(model.Name) || model.Phone.Length < 2) + { + Oh.ModelError("账号/手机号/名称 不合法"); + } + if (await baseService.IsAnyAsync(s => s.Account == model.Account && s.Id != model.Id)) + Oh.ModelError($"账号 {model.Account} 已被使用!"); + return await base.Edit(model); + } + + + /// + /// 下载导入模板 + /// + /// + [HttpGet, ResultIgnore, AllowAnonymous] + public IActionResult DwImportTemplate() + { + var resultList = new List() { new AdminImport() + { + Account = "登录账号[建议使用手机号]", + Name = "必填:用户名称", + Phone = "联系方式", + Role = "必填:与系统的角色名称匹配\r\n普通成员 管理员", + Password = "必填: 登录密码", + } }; + return File(resultList.ExportExcel(), "application/ms-excel", + $"导入管理员模板{DateTime.Now.ToString("MMddHHmm")}.xlsx"); + } + + /// + /// 导入考试信息 + /// + /// + [HttpPost, ResultIgnore] + [HttpLogEnable] + public async Task Import(IFormFile? file) + { + if(!userInfo.IsSa) + Oh.ModelError("只允许管理员使用本功能!"); + var fl = file != null ? file : accessor.HttpContext?.Request.Form.Files[0]; + if (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.Account)); + } + if (dataList == null || dataList.Count() == 0) + Oh.ModelError("导入失败:无有效数据"); + + //处理数据 + var accountArr = await baseService.AsQueryable() + .Select(s => s.Account).Distinct() + .ToArrayAsync(); + var accountH = accountArr.ToHashSet(); + var roleDic = await roleDB.AsQueryable() + .ToDictionaryAsync(s => s.Name, s => s.Id); + var errorExcelInfo = new List(); + var insertInfo = new List(); + foreach (var imp in dataList) + { + imp.Account = imp.Account.Trim(); + imp.Phone = imp.Phone.Trim(); + imp.Name = imp.Name.Trim(); + imp.Role = imp.Role.Trim(); + if (accountH.Contains(imp.Account)) + { + imp.Error = $"导入失败:账号已被使用!"; + errorExcelInfo.Add(imp); + continue; + } + else if (!roleDic.ContainsKey(imp.Role)) + { + imp.Error = $"导入失败:无效的 角色名称!"; + errorExcelInfo.Add(imp); + continue; + } + var admin = imp.Adapt(); + admin.Enable = true; + admin.RoleId = (long)roleDic[imp.Role]; + admin.Password = imp.Password.Trim().GetMD5(); + insertInfo.Add(admin); + } + + if (errorExcelInfo.Count != 0) + return File(errorExcelInfo.ExportExcel(), "application/ms-excel" + , $"错误管理员信息{DateTime.Now.ToString("MMddHHmm")}.xlsx"); + //写入数据库 + await baseService.InsertRangeAsync(insertInfo); + return Ok(); } } } diff --git a/Learn.Archives.API/Controllers/Dto/AdminDto.cs b/Learn.Archives.API/Controllers/Dto/AdminDto.cs new file mode 100644 index 0000000..d7b3f72 --- /dev/null +++ b/Learn.Archives.API/Controllers/Dto/AdminDto.cs @@ -0,0 +1,10 @@ +using UserCenter.Model; +using UserCenter.Model.Common; + +namespace Learn.Archives.API.Controllers.Dto +{ + public class ClassDto:Classes + { + public string Grade => GradeHelper.GetGrade(this.GradeLevel, GraduationYear); + } +} diff --git a/Learn.Archives.API/Controllers/Dto/ClassDto.cs b/Learn.Archives.API/Controllers/Dto/ClassDto.cs index d7b3f72..2ed5b38 100644 --- a/Learn.Archives.API/Controllers/Dto/ClassDto.cs +++ b/Learn.Archives.API/Controllers/Dto/ClassDto.cs @@ -1,10 +1,37 @@ -using UserCenter.Model; +using MiniExcelLibs.Attributes; +using UserCenter.Model; using UserCenter.Model.Common; namespace Learn.Archives.API.Controllers.Dto { - public class ClassDto:Classes + + + public class AdminImportError : AdminImport { - public string Grade => GradeHelper.GetGrade(this.GradeLevel, GraduationYear); + + [ExcelColumn(Name = "错误原因", Width = 25)] + public string Error { get; set; } + } + public class AdminImport + { + + [ExcelColumn(Name = "账号", Width = 30)] + public string Account { get; set; } + + [ExcelColumn(Name = "名称", Width = 15)] + public string Name { get; set; } + + [ExcelColumn(Name = "电话号码", Width = 30)] + public string Phone { get; set; } + + [ExcelColumn(Name = "角色", Width = 30)] + public string Role { get; set; } + /// + /// 密码 + /// + [ExcelColumn(Name = "密码", Width = 20)] + public string Password { get; set; } + + } } diff --git a/Learn.Archives.API/Controllers/StudentController.cs b/Learn.Archives.API/Controllers/StudentController.cs index 6f65129..cf10306 100644 --- a/Learn.Archives.API/Controllers/StudentController.cs +++ b/Learn.Archives.API/Controllers/StudentController.cs @@ -347,7 +347,7 @@ namespace Learn.Archives.API.Controllers { var resultList = new List() { new StudentInfoImport() { - RealName = "导入规范[导入时请删除本列]", + RealName = "导入规范[导入时请删除本行]", School = "必填:与系统匹配", Grade = "必填:可选值\r\n[初一初二初三,高一高二高山]", Class = "必填:与系统匹配\r\n格式:10班[数字+班]", @@ -377,7 +377,7 @@ namespace Learn.Archives.API.Controllers new TeacherInfoImport() { Phone="必填", - RealName = "导入规范[导入时请删除本列]", + RealName = "导入规范[导入时请删除本行]", UserType = "必填 可选值\r\n[年级主任,班主任,教师]", School = "必填:与系统匹配", Grade = "必填:可选值\r\n[初一初二初三,高一高二高山]", diff --git a/Learn.Archives.API/Expand/HttpFilter.cs b/Learn.Archives.API/Expand/HttpFilter.cs index 6353a10..415aede 100644 --- a/Learn.Archives.API/Expand/HttpFilter.cs +++ b/Learn.Archives.API/Expand/HttpFilter.cs @@ -21,6 +21,7 @@ using Learn.Archives.Core.Common; using Learn.Archives.Core.Model.Dto; using Learn.Archives.Core.Model; using SqlSugar.IOC; +using static System.Net.Mime.MediaTypeNames; namespace Learn.Archives.API.Expand { @@ -50,8 +51,32 @@ namespace Learn.Archives.API.Expand this.userInfo = userInfo; } + /// + /// 执行接口前文件做缓存处理 + /// + /// + /// + public void ExecutingFileCached(ActionExecutingContext context) + { + //特殊处理:ResultIgnore,不进行返回结果包装,原样输出 + var endpoint = context.HttpContext.GetEndpoint(); + // 直接返回原始结果,不封装 + if (endpoint?.Metadata.GetMetadata() == null) return; + if (context.HttpContext.Request.HasFormContentType && + context.HttpContext.Request.Form.Files != null && + context.HttpContext.Request.Form.Files.Count() > 0) + { + context.HttpContext.Items["FileCached"]= + context.HttpContext.Request.Form.Files.Select(s => + { + var stream = new MemoryStream(); + s.CopyTo(stream); + stream.Position = 0; + return (s, stream); + }).ToArray(); + } - + } /// /// 执行接口前400 处理 /// @@ -132,43 +157,43 @@ namespace Learn.Archives.API.Expand string request = null; var logId = Yitter.IdGenerator.YitIdHelper.NextId(); - if (!context.Request.Method - .Equals("GET", StringComparison.InvariantCultureIgnoreCase)) + if (!context.Request.Method.Equals("GET", StringComparison.InvariantCultureIgnoreCase)) { - context.Request.EnableBuffering(); //记录请求参数 if (context.Request.Body.CanSeek) { try { - if (context.Request.HasFormContentType && context.Request?.Form?.Files?.Count() > 0) + var fileArr = context.Items.ContainsKey("FileCached") ? context.Items["FileCached"] as (IFormFile file, MemoryStream stream)[] : null; + if (context.Request.HasFormContentType && fileArr != null) { // 设置保存目录(例如:项目根目录下的Uploads文件夹) string uploadsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UploadLogs", logId.ToString()); // 创建目录(如果不存在) if (!Directory.Exists(uploadsFolder)) Directory.CreateDirectory(uploadsFolder); - foreach (var file in context.Request.Form.Files) + foreach (var fileInfo in fileArr) { // 生成安全文件名(防止路径遍历攻击) - string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(file.FileName); - string filePath = Path.Combine(uploadsFolder, uniqueFileName); + string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(fileInfo.file.FileName); // 保存文件 - using var stream = new FileStream(filePath, FileMode.Create); - await file.CopyToAsync(stream); + using var stream = new FileStream(Path.Combine(uploadsFolder, uniqueFileName), FileMode.Create, FileAccess.Write); + fileInfo.stream.Position = 0; + await fileInfo.stream.CopyToAsync(stream); + fileInfo.stream.Dispose(); } request = $"请求体包含{context.Request.Form.Files.Count()}个文件 目录 {uploadsFolder}"; } else { context.Request.Body.Position = 0; - using var sr = new StreamReader(context.Request.Body); + using var sr = new System.IO.StreamReader(context.Request.Body); request = await sr.ReadToEndAsync(); } } catch (Exception ex) { - request = "处理请求日志时发生了错误 \r\n" + ex.ToString(); + request = "处理请求入参时发生了错误 \r\n" + ex.ToString() + "\r\n 原有请求数据 " + request; } } } @@ -181,7 +206,7 @@ namespace Learn.Archives.API.Expand Request = request, IP = context.Connection?.RemoteIpAddress?.ToString(), ResponseCode = result?.Code ?? -1, - Response = result != null ? JsonSerializer.Serialize(result) : null, + Response = (result != null ? JsonSerializer.Serialize(result) : null) , Authorization = context.Request.Headers.ContainsKey("Authorization") ? context.Request.Headers["Authorization"].ToString() : string.Empty, @@ -193,18 +218,12 @@ namespace Learn.Archives.API.Expand } - - - - - - /// - /// 在Controller的Action执行前执行 - /// - /// - public override void OnActionExecuting(ActionExecutingContext context) + public override async void OnActionExecuting(ActionExecutingContext context) { + + Executing400(context); + ExecutingFileCached(context); base.OnActionExecuting(context); } @@ -214,6 +233,8 @@ namespace Learn.Archives.API.Expand /// public override async void OnActionExecuted(ActionExecutedContext context) { + + try { BaseReturn? res = ApiResultFormatting(context); diff --git a/Learn.Archives.API/Program.cs b/Learn.Archives.API/Program.cs index 0918320..0a3d6b4 100644 --- a/Learn.Archives.API/Program.cs +++ b/Learn.Archives.API/Program.cs @@ -17,17 +17,6 @@ builder.Services.AddLogging(loggingBuilder => loggingBuilder.SetMinimumLevel(LogLevel.Warning); // С־Ϊ Warning }); -builder.Services.AddControllers(options => -{ - // ȫģ͸ֵĬֵ ͳһظʽ - options.Filters.Add(); -}) -.AddJsonOptions(options => -{ - - options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//תʱʹUnicode - options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// ĬСշ null շ -}); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerExpand("ѧУϵͳ"); builder.Configuration.AddAppConfig(args); @@ -43,6 +32,17 @@ builder.Services.AddHttpContextAccessor(); +builder.Services.AddControllers(options => +{ + // ȫģ͸ֵĬֵ ͳһظʽ + options.Filters.Add(); +}) +.AddJsonOptions(options => +{ + + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//תʱʹUnicode + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// ĬСշ null շ +}); var app = builder.Build(); AppCommon.Services = app.Services; diff --git a/Learn.Archives.Core/Common/BasicAuthMiddleware.cs b/Learn.Archives.Core/Common/BasicAuthMiddleware.cs index 9414eec..9705a5f 100644 --- a/Learn.Archives.Core/Common/BasicAuthMiddleware.cs +++ b/Learn.Archives.Core/Common/BasicAuthMiddleware.cs @@ -24,6 +24,9 @@ namespace Learn.Archives.Core.Common public async Task InvokeAsync(HttpContext context) { + if (!context.Request.Body.CanSeek) + context.Request.EnableBuffering(); // 允许重新读取请求体 + if (context.Request.Path.StartsWithSegments("/swagger") && (context.Request.Path.Value?.Contains("swagger.json") ?? true)) { @@ -36,7 +39,6 @@ namespace Learn.Archives.Core.Common if (await IsAuthorized(usernamePassword[0], usernamePassword[1])) { - await _next(context); return; }