2025-06-06 16:55:14 +08:00

1327 lines
57 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AutoMapper;
using Castle.DynamicProxy.Generators;
using Google.Protobuf.Collections;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using VOL.Business.IRepositories;
using VOL.Business.IServices.Norm;
using VOL.Business.IServices.School;
using VOL.Core.CacheManager;
using VOL.Core.Extensions;
using VOL.Core.Extensions.AutofacManager;
using VOL.Core.ManageUser;
using VOL.Core.Utilities;
using VOL.Entity.DomainModels;
using VOL.Entity.DomainModels.Business.People;
using VOL.Entity.Enum;
using VOL.Model;
using VOL.Model.Ai;
using VOL.Model.IOT.Request;
using VOL.Model.School.Request;
using VOL.Model.School.Response;
using VOL.System.IRepositories;
using VOL.System.Repositories;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
namespace VOL.Business.Services.School
{
public class S_StudentService : IS_StudentService, IDependency
{
#region
private readonly IMapper _mapper;
private readonly ICacheService _cacheService;
private readonly ICacheQueryService _cacheQueryService;
private readonly IotDataSyncService _iotDataSyncService;
private readonly IS_StudentRepository _studentRepository;
private readonly IS_GradeRepository _gradeRepository;
private readonly IS_ClassRepository _classRepository;
private readonly IN_SportsTestResultRepository _sportsTestResultRepository;
private readonly IN_SportsTestCategoryRepository _sportsTestCategoryRepository;
[ActivatorUtilitiesConstructor]
public S_StudentService(IMapper mapper,
ICacheService cacheService,
ICacheQueryService cacheQueryService,
IS_StudentRepository studentRepository,
IS_GradeRepository gradeRepository,
IS_ClassRepository classRepository,
IN_SportsTestResultRepository sportsTestResultRepository,
IN_SportsTestCategoryRepository sportsTestCategoryRepository,
IotDataSyncService iotDataSyncService)
{
_mapper = mapper;
_cacheService = cacheService;
_cacheQueryService = cacheQueryService;
_studentRepository = studentRepository;
_gradeRepository = gradeRepository;
_classRepository = classRepository;
_sportsTestResultRepository = sportsTestResultRepository;
_sportsTestCategoryRepository = sportsTestCategoryRepository;
_iotDataSyncService = iotDataSyncService;
}
#endregion
public async Task<PageDataDto<StudentPageListModel>> GetStudentPageList(StudentPageListParam paramDto)
{
var res = new PageDataDto<StudentPageListModel>();
var classIds = new List<int>();
var isTeacher = (UserContext.Current.RoleId == 3);
if (isTeacher)
{
var teacher = await _studentRepository.DbContext.Set<S_Teacher>().FirstOrDefaultAsync(x => x.SchoolCode == UserContext.Current.TenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo);
var teacherClassList = await (
from at in _studentRepository.DbContext.Set<S_ClassAssocTeacher>()
join c in _studentRepository.DbContext.Set<S_Class>() on at.ClassId equals c.Id
where at.TeacherId == teacher.Id
select c).ToListAsync();
classIds = teacherClassList.Select(c => c.Id).Distinct().ToList();
}
var query = from s in _studentRepository.DbContext.Set<S_Student>()
join c in _studentRepository.DbContext.Set<S_Class>() on s.ClassId equals c.Id
where s.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && (!isTeacher || classIds.Contains(c.Id))
select new StudentPageListModel()
{
Id = s.Id,
Age = s.Age,
ClassId = s.ClassId,
ClassName = c.ClassName,
GradeName = c.GradeName,
HeartRateFrontNo = s.HeartRateFrontNo,
HeartRateId = s.HeartRateId,
Photo = s.Photo,
RopeSkipQRCode = s.RopeSkipQRCode,
SchoolRollNo = s.SchoolRollNo,
Sex = s.Sex,
StudentName = s.StudentName,
StudentNo = s.StudentNo,
OrderNo = s.OrderNo,
StudentStatus = s.StudentStatus
};
if (!string.IsNullOrWhiteSpace(paramDto.StudentName))
{
query = query.Where(x => x.StudentName.Contains(paramDto.StudentName));
}
if (!string.IsNullOrWhiteSpace(paramDto.StudentNo))
{
query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo));
}
if (paramDto.Sex > 0)
{
query = query.Where(x => x.Sex == paramDto.Sex);
}
if (paramDto.ClassId > 0)
{
query = query.Where(x => x.ClassId == paramDto.ClassId);
}
if (paramDto.StudentStatus > 0)
{
query = query.Where(x => x.StudentStatus == paramDto.StudentStatus);
}
res.Total = await query.CountAsync();
var list = await query
.OrderBy(x => x.Id)
.Skip((paramDto.PageIndex - 1) * paramDto.PageSize)
.Take(paramDto.PageSize)
.ToListAsync();
res.Datas = list;
return res;
}
public async Task<List<StudentPageListModel>> GetStudentList(StudentExportParam paramDto)
{
var classIds = new List<int>();
var isTeacher = (UserContext.Current.RoleId == 3);
if (isTeacher)
{
var teacher = await _studentRepository.DbContext.Set<S_Teacher>().FirstOrDefaultAsync(x => x.SchoolCode == UserContext.Current.TenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo);
var teacherClassList = await (
from at in _studentRepository.DbContext.Set<S_ClassAssocTeacher>()
join c in _studentRepository.DbContext.Set<S_Class>() on at.ClassId equals c.Id
where at.TeacherId == teacher.Id
select c).ToListAsync();
classIds = teacherClassList.Select(c => c.Id).Distinct().ToList();
}
var query = from s in _studentRepository.DbContext.Set<S_Student>()
join c in _studentRepository.DbContext.Set<S_Class>() on s.ClassId equals c.Id
where c.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && s.StudentStatus == StudentStatus.Normal && (!isTeacher || classIds.Contains(c.Id))
select new StudentPageListModel()
{
Id = s.Id,
Age = s.Age,
ClassId = s.ClassId,
ClassName = c.ClassName,
GradeName = c.GradeName,
HeartRateFrontNo = s.HeartRateFrontNo,
HeartRateId = s.HeartRateId,
Photo = s.Photo,
RopeSkipQRCode = s.RopeSkipQRCode,
SchoolRollNo = s.SchoolRollNo,
Sex = s.Sex,
StudentName = s.StudentName,
StudentNo = s.StudentNo,
StudentStatus = s.StudentStatus
};
if (!string.IsNullOrWhiteSpace(paramDto.StudentName))
{
query = query.Where(x => x.StudentName.Contains(paramDto.StudentName));
}
if (!string.IsNullOrWhiteSpace(paramDto.StudentNo))
{
query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo));
}
if (paramDto.Sex > 0)
{
query = query.Where(x => x.Sex == paramDto.Sex);
}
if (paramDto.ClassId > 0)
{
query = query.Where(x => x.ClassId == paramDto.ClassId);
}
if (paramDto.StudentStatus > 0)
{
query = query.Where(x => x.StudentStatus == paramDto.StudentStatus);
}
var list = await query.OrderBy(x => x.Id).ToListAsync();
return list;
}
/// <summary>
/// 获取场馆学员
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<List<StudentPageListModel>> GetStudentListBySportsHall(StudentExportParam paramDto)
{
var query = from s in _studentRepository.DbContext.Set<S_Student>()
where s.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && s.StudentStatus == StudentStatus.Normal
select new StudentPageListModel()
{
Id = s.Id,
Age = s.Age,
ClassId = s.ClassId,
ClassName = s.ClassName,
GradeName = "运动馆",
HeartRateFrontNo = s.HeartRateFrontNo,
HeartRateId = s.HeartRateId,
Photo = s.Photo,
RopeSkipQRCode = s.RopeSkipQRCode,
SchoolRollNo = s.SchoolRollNo,
Sex = s.Sex,
StudentName = s.StudentName,
StudentNo = s.StudentNo,
StudentStatus = s.StudentStatus,
OrderNo = s.OrderNo,
};
if (!string.IsNullOrWhiteSpace(paramDto.StudentName))
{
query = query.Where(x => x.StudentName.Contains(paramDto.StudentName));
}
if (!string.IsNullOrWhiteSpace(paramDto.StudentNo))
{
query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo));
}
if (paramDto.Sex > 0)
{
query = query.Where(x => x.Sex == paramDto.Sex);
}
if (paramDto.ClassId > 0)
{
query = query.Where(x => x.ClassId == paramDto.ClassId);
}
if (paramDto.StudentStatus > 0)
{
query = query.Where(x => x.StudentStatus == paramDto.StudentStatus);
}
var list = await query.OrderBy(x => x.OrderNo).ToListAsync();
return list;
}
public async Task AddStudent(AddStudentParam paramDto)
{
var stuExists = await _studentRepository.ExistsAsync(x => x.StudentNo == paramDto.StudentNo);
if (stuExists)
throw new Exception($"学号{paramDto.StudentNo}的学生已存在!");
var studentEntity = _mapper.Map<S_Student>(paramDto);
studentEntity.StudentStatus = StudentStatus.Normal;
studentEntity.SchoolCode = UserContext.Current.TenantId;
studentEntity.Creator = UserContext.Current.UserId;
studentEntity.CreateDate = DateTime.Now;
await _studentRepository.AddAsync(studentEntity);
await _studentRepository.SaveChangesAsync();
//调用回调函数同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Add.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(new List<S_Student>() { studentEntity })
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Add.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(new List<S_Student>() { studentEntity })
});
}
public async Task ModifyStudent(AddStudentParam paramDto)
{
if (paramDto == null || string.IsNullOrEmpty(paramDto.StudentNo))
{
throw new ArgumentNullException("学生学号必填");
}
var student = await _studentRepository.FindAsyncFirst(x => x.Id == paramDto.Id);
if (student == null)
throw new ArgumentNullException("未找到要更新的数据");
var stuExists = await _studentRepository.ExistsAsync(x => x.StudentNo == paramDto.StudentNo && x.Id != student.Id);
if (stuExists)
throw new Exception($"学号{paramDto.StudentNo}的学生已存在!");
student.StudentName = paramDto.StudentName; // 学生名称
student.ClassId = paramDto.ClassId; // 班级
student.Sex = paramDto.Sex; // 性别
student.Age = paramDto.Age; // 年龄
student.Birthday = paramDto.Birthday; // 出生日期
student.IDCard = paramDto.IDCard; // 身份证
student.Photo = paramDto.Photo; // 学生照片
student.HeartRateFrontNo = paramDto.HeartRateFrontNo; // 心率设备正面编号
student.HeartRateId = paramDto.HeartRateId; // 心率设备ID
student.RunChipNo = paramDto.RunChipNo; // 中长跑芯片编号
student.RopeSkipNo = paramDto.RopeSkipQRCode; // 跳绳编号
student.OrderNo = 1;
student.Creator = UserContext.Current.UserId;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = DateTime.Now;
student.StudentStatus = StudentStatus.Normal;
_studentRepository.Update(student);
await _studentRepository.SaveChangesAsync();
//调用回调函数同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Update.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(student)
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Update.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(student)
});
}
public async Task UpdateStudentStatus(UpdateStudentStatusParam paramDto)
{
var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == paramDto.StudentNo);
if (student == null)
throw new ArgumentNullException("未找到要更新的数据");
student.StudentStatus = paramDto.Status;
_studentRepository.Update(student);
await _studentRepository.SaveChangesAsync();
//调用回调函数同步数据到IOT
_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Update.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(student)
}));
}
public async Task UpdateStudentPwd(UpdateStudentPwdParam paramDto)
{
var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == paramDto.StudentNo);
if (student == null)
throw new ArgumentNullException("未找到要更新的数据");
student.AppletPwd = paramDto.StudentNo;
_studentRepository.Update(student);
await _studentRepository.SaveChangesAsync();
//调用回调函数同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Update.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(student)
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Update.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(student)
});
}
public async Task ChangeClasses(string studentNo, int classId)
{
var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == studentNo);
if (student == null)
throw new ArgumentNullException("未找到更新的数据");
var classModel = await _classRepository.FindAsyncFirst(x => x.Id == student.ClassId);
var sportsTestResults = await _sportsTestResultRepository.FindAsIQueryable(x => x.ClassId == student.ClassId && x.StudentNo == studentNo).ToListAsync();
using (var transaction = _studentRepository.DbContext.Database.BeginTransaction())
{
try
{
student.ClassId = classId;
student.ClassName = classModel.ClassName;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = DateTime.Now;
_studentRepository.Update(student);
foreach (var item in sportsTestResults)
{
item.ClassId = classId;
item.ClassName = classModel.ClassName;
item.Modifier = UserContext.Current.UserId;
item.ModifyDate = DateTime.Now;
}
_sportsTestResultRepository.UpdateRange(sportsTestResults);
await _studentRepository.SaveChangesAsync();
// 提交事务
transaction.Commit();
}
catch (Exception ex)
{
// 发生错误,回滚事务
transaction.Rollback();
throw new Exception("操作失败");
}
}
}
public async Task ImportStudents(IFormFile file)
{
if (file == null || file.Length <= 0)
throw new Exception("操作失败");
var tenantId = UserContext.Current.TenantId;
var dataObjects = new List<ImportStudentParam>();
using (var fileStream = file.OpenReadStream())
{
dataObjects = Tool.ConvertExcelToList<ImportStudentParam>(fileStream);
}
var greadNames = dataObjects.Select(x => x.GradeName).Distinct().ToList();
var greadList = await _gradeRepository.FindAsync(x => greadNames.Contains(x.GradeName));
var greadIds = greadList.Select(x => x.Id).ToList();
var classList = await _classRepository.FindAsync(x => greadIds.Contains(x.GradeId) && x.SchoolCode == tenantId);
var groupedStudentNos = dataObjects.GroupBy(x => x.StudentNo);
foreach (var group in groupedStudentNos)
{
if (!string.IsNullOrWhiteSpace(group.Key) && group.Count() > 1)
{
throw new Exception($"存在重复的学号:{group.Key},请检查后重新操作");
}
}
var stuNos = groupedStudentNos.Select(g => g.Key).ToList();
var stuList = await _studentRepository.FindAsync(x => x.SchoolCode == tenantId && stuNos.Contains(x.StudentNo));
var entitys = new List<S_Student>();
foreach (var data in dataObjects)
{
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("学号不能为空");
}
var stuExists = stuList.Exists(x => x.StudentNo == data.StudentNo);
if (stuExists)
throw new Exception($"学号{data.StudentNo}的学生已存在!");
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("姓名不能为空");
}
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("性别不能为空");
}
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("年龄不能为空");
}
var greadModel = greadList.Where(x => x.GradeName.Equals(data.GradeName)).FirstOrDefault();
if (greadModel == null)
throw new Exception("未找到年级信息");
var classModel = classList.Where(x => x.GradeId == greadModel.Id && x.ClassName == data.ClassName).FirstOrDefault();
if (classModel == null)
throw new Exception("未找到班级信息");
var studentEntity = new S_Student()
{
SchoolCode = tenantId,
StudentName = data.StudentName,
StudentNo = data.StudentNo,
ClassId = classModel.Id,
Age = data.Age,
IDCard = data.IDCard,
Birthday = data.Birthday,
HeartRateFrontNo = data.HeartRateFrontNo,
HeartRateId = data.HeartRateId,
RopeSkipQRCode = data.RopeSkipQRCode,
Sex = data.Sex == "男" ? SexType.Male : SexType.Female,
RunChipNo = data.RunChipNo,
StudentStatus = StudentStatus.Normal,
Creator = UserContext.Current.UserId,
CreateDate = DateTime.Now
};
entitys.Add(studentEntity);
}
await _studentRepository.AddRangeAsync(entitys);
await _studentRepository.SaveChangesAsync();
//调用回调函数同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Add.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(entitys)
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Add.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(entitys)
});
}
/// <summary>
/// 上传学生头像
/// </summary>
public async Task<string> UploadPhoto(IFormFile file, int gradeId, int classId, string studentNo, string studentName)
{
//var student = _studentRepository.FindFirst(c => c.StudentNo == studentNo);
//if (student == null)
//{
// throw new Exception("学生未找到");
//}
// 设置照片最大允许大小 (5MB)
const long MaxFileSize = 2 * 1024 * 1024;
// 检查文件大小是否超过限制
if (file.Length > MaxFileSize)
{
throw new Exception("上传的照片文件大小不能超过2MB");
}
var url = ALiYunOss.Upload(file, $"Upload/{UserContext.Current.TenantId}/Photo/Student/{gradeId}/{classId}/", studentNo);
var faceEntityWithRequest = new FaceEntityWithDto()
{
EntityId = studentNo,
SchoolCode = UserContext.Current.TenantId,
IsStudent = true
};
await ALiYunFace.DeleteFaceEntityWith(faceEntityWithRequest);
var faceEntityWithReuslt = await ALiYunFace.FaceEntityWith(faceEntityWithRequest);
if (!faceEntityWithReuslt)
{
throw new Exception("上传失败");
}
var faceWithReuslt = await ALiYunFace.FaceWith(new FaceWithDto()
{
SchoolCode = UserContext.Current.TenantId,
Name = studentName,
IsStudent = true,
EntityId = studentNo,
ImageUrl = url
});
if (!faceWithReuslt)
{
throw new Exception("上传失败");
}
return url;
}
public async Task BatchUploadPhoto(IFormFile zipFile)
{
// 解压缩 zip 文件
var uploadPath = Path.Combine("Upload", "Student");
Directory.CreateDirectory(uploadPath);
var zipFilePath = Path.Combine(uploadPath, zipFile.FileName);
using (var fileStream = new FileStream(zipFilePath, FileMode.Create))
{
await zipFile.CopyToAsync(fileStream);
}
// 解压后的目录路径
var extractPath = Path.Combine(uploadPath, Path.GetFileNameWithoutExtension(zipFile.FileName));
// 检查并删除已存在的目录
if (Directory.Exists(extractPath))
{
Directory.Delete(extractPath, true);
}
ZipFile.ExtractToDirectory(zipFilePath, extractPath);
// 递归获取所有文件
var files = Directory.GetFiles(extractPath, "*.*", SearchOption.AllDirectories)
.Select(filePath => new FileInfo(filePath))
.ToList();
// 获取学号列表
var studentNos = files.Select(x => Path.GetFileNameWithoutExtension(x.Name)).ToList();
var students = await _studentRepository.FindAsync(x => studentNos.Contains(x.StudentNo) && x.SchoolCode.Equals(UserContext.Current.TenantId));
var classList = await _classRepository.FindAsync(x => x.SchoolCode.Equals(UserContext.Current.TenantId));
var studentEntitys = new List<S_Student>();
foreach (var fileInfo in files)
{
try
{
using (var stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
{
var formFile = new FormFile(stream, 0, stream.Length, fileInfo.FullName, fileInfo.Name);
var stuNo = Path.GetFileNameWithoutExtension(fileInfo.Name);
var student = students.FirstOrDefault(x => x.StudentNo.Equals(stuNo, StringComparison.OrdinalIgnoreCase));
if (student != null)
{
var cla = classList.FirstOrDefault(x => x.Id == student.ClassId);
var url = await UploadPhoto(formFile, cla == null ? 0 : cla.GradeId, student.ClassId, stuNo, student.StudentName);
student.Photo = url;
studentEntitys.Add(student);
}
else
{
//throw new Exception($"未找到学号:{stuNo}");
continue;
}
}
}
catch (Exception)
{
continue;
}
}
_studentRepository.UpdateRange(studentEntitys);
await _studentRepository.SaveChangesAsync();
// 删除临时文件和目录
Directory.Delete(extractPath, true);
File.Delete(zipFilePath);
}
/// <summary>
/// 学生数据统计
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<StudentDataStatsModel> StudentWholeDataStats(StudentDataStatsParam paramDto)
{
var res = new StudentDataStatsModel();
var tenantId = UserContext.Current.TenantId;
if (string.IsNullOrEmpty(paramDto.StudentNo))
{
paramDto.StudentNo = _studentRepository.DbContext.Set<S_Student>().First().StudentNo;
}
var student = await (from s in _studentRepository.DbContext.Set<S_Student>()
join c in _studentRepository.DbContext.Set<S_Class>() on s.ClassId equals c.Id
where s.StudentNo == paramDto.StudentNo && s.SchoolCode.Equals(UserContext.Current.TenantId)
select new StudentPageListModel()
{
StudentName = s.StudentName,
GradeId = c.GradeId,
ClassId = s.ClassId,
ClassName = c.ClassName,
GradeName = c.GradeName,
Photo = s.Photo,
Age = s.Age,
Sex = s.Sex
}).FirstOrDefaultAsync();
if (student == null)
throw new ArgumentNullException("未找到学生数据");
res.StudentName = student.StudentName;
res.ClassName = student.ClassName;
res.GradeName = student.GradeName;
res.Photo = student.Photo;
res.Sex = student.Sex.Description();
res.Age = student.Age;
var heartRateData = await (from t in _studentRepository.DbContext.Set<I_TrainingData>()
join s in _studentRepository.DbContext.Set<I_TrainRanking>() on t.Id equals s.TrainingDataId
where
t.SchoolCode.Equals(UserContext.Current.TenantId)
&& t.ItemType == (int)TrainingItemType.HeartRate
&& s.StudentNo == paramDto.StudentNo
select new
{
t.Id,
s.HighHeartRate
})
.OrderByDescending(x => x.Id)
.Take(5)
.ToListAsync();
var keys = new[] { "一", "二", "三", "四", "五" };
if (heartRateData.Count == 0)
{
res.HeartRateData = keys.ToDictionary(key => key, key => 0);
}
else
{
res.HeartRateData = heartRateData
.Select((x, index) => new { Key = keys[index], Value = x.HighHeartRate ?? 0 })
.ToDictionary(x => x.Key, x => x.Value);
}
// 从缓存中获取数据
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x => x.SchoolCode.Equals(tenantId) && x.StudentNo == paramDto.StudentNo, paramDto.Mode);
var categoryList = await (from s in _sportsTestResultRepository.DbContext.Set<S_GradeAssocCategory>()
join n in _sportsTestResultRepository.DbContext.Set<N_SportsTestCategory>()
on s.CategoryValue equals n.CategoryValue
where s.GradeId == student.GradeId
select new
{
n.CategoryValue,
n.CategoryName
}).ToListAsync();
foreach (var category in categoryList)
{
var categoryResults = sportsTestResults.Where(x => x.CategoryValue == category.CategoryValue).ToList();
var maxValue = categoryResults.Any() ? categoryResults.Max(x => x.Score + x.AdditionalScore) : 0f;
var lastValue = categoryResults.OrderByDescending(x => x.ScoreTime).Select(x => x.Score + x.AdditionalScore).FirstOrDefault();
var resultContrast = new ResultContrast
{
MaxValue = maxValue,
LastValue = lastValue
};
res.TestResultCompare.AxisX.Add(category.CategoryName);
res.TestResultCompare.AxisY.Add(resultContrast);
}
return res;
}
/// <summary>
/// 各项目平均成绩
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Dictionary<string, float>> TestResultAvg(StudentCategoryParam paramDto)
{
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x =>
x.StudentNo == paramDto.StudentNo &&
x.SchoolCode.Equals(UserContext.Current.TenantId) &&
x.CategoryValue == paramDto.CategoryValue
, paramDto.Mode);
var now = DateTime.Now;
var currentQuarter = (now.Month - 1) / 3 + 1;
var currentYear = now.Year;
// 处理跨年情况
var quarters = new[]
{
new { Label = "本季度", Quarter = currentQuarter, Year = currentYear },
new { Label = "上季度", Quarter = currentQuarter - 1 == 0 ? 4 : currentQuarter - 1, Year = currentQuarter == 1 ? currentYear - 1 : currentYear },
new { Label = "前季度", Quarter = currentQuarter - 2 == 0 ? 4 : currentQuarter - 2 == -1 ? 3 : currentQuarter - 2, Year = currentQuarter <= 2 ? currentYear - 1 : currentYear }
};
return quarters.ToDictionary(
q => q.Label,
q =>
{
var filteredResults = sportsTestResults
.Where(x => x.ScoreTime >= Tool.GetQuarterStartDate(q.Quarter, q.Year) && x.ScoreTime <= Tool.GetQuarterEndDate(q.Quarter, q.Year))
.Select(x => x.Score + x.AdditionalScore)
.ToList();
return filteredResults.Any() ? (float)Math.Round(filteredResults.Average(), 2) : 0;
}
);
}
/// <summary>
/// 各项目成绩对比
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Dictionary<string, float>> TestResultContrast(StudentCategoryParam paramDto)
{
// 获取指定班级和类别的测试结果
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x =>
x.ClassId == paramDto.ClassId &&
x.SchoolCode.Equals(UserContext.Current.TenantId) &&
x.CategoryValue == paramDto.CategoryValue
, paramDto.Mode);
// 提取班级所有成绩
var filteredResults = sportsTestResults
.Select(x => x.Score + x.AdditionalScore)
.ToList();
// 提取个人成绩
var personalScore = sportsTestResults
.Where(x => x.StudentNo == paramDto.StudentNo)
.Select(x => x.Score + x.AdditionalScore)
.FirstOrDefault();
var quarters = new[]
{
new { Label = "个人成绩", Value = personalScore },
new { Label = "班级平均分", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Average(),2) : 0f },
new { Label = "班级中位数", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Median(),2) : 0f },
new { Label = "班级最高分", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Max(),2) : 0f }
};
return quarters.ToDictionary(q => q.Label, q => q.Value);
}
/// <summary>
/// 成绩趋势
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<VariousSportsProportion> ResultTrends(StudentResultTrendsParam paramDto)
{
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x =>
x.StudentNo == paramDto.StudentNo &&
x.SchoolCode.Equals(UserContext.Current.TenantId) &&
x.CategoryValue == paramDto.CategoryValue
, paramDto.Mode);
var result = new VariousSportsProportion();
DateTime currentDate = DateTime.Now;
switch (paramDto.CycleTime)
{
case CycleTimeEnum.InThePastWeek:
// 近一周:按天展示
var pastWeekResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddDays(-7))
.GroupBy(x => x.ScoreTime.Date);
foreach (var group in pastWeekResults)
{
result.AxisX.Add(group.Key.ToString("yyyy-MM-dd"));
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
case CycleTimeEnum.InThePastTwoWeeks:
// 近两周:按天展示
var pastTwoWeeksResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddDays(-14))
.GroupBy(x => x.ScoreTime.Date);
foreach (var group in pastTwoWeeksResults)
{
result.AxisX.Add(group.Key.ToString("yyyy-MM-dd"));
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
case CycleTimeEnum.InThePastMonth:
// 近一月每5天一个阶段展示
var pastMonthResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddDays(-30))
.GroupBy(x => (currentDate - x.ScoreTime).Days / 5);
foreach (var group in pastMonthResults)
{
var minDate = group.Min(r => r.ScoreTime).ToString("yyyy-MM-dd");
var maxDate = group.Max(r => r.ScoreTime).ToString("yyyy-MM-dd");
result.AxisX.Add($"{minDate} - {maxDate}");
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
case CycleTimeEnum.InThePastYear:
// 近一年:按月展示
var pastYearResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddYears(-1))
.GroupBy(x => new { x.ScoreTime.Year, x.ScoreTime.Month });
foreach (var group in pastYearResults)
{
result.AxisX.Add($"{group.Key.Year}-{group.Key.Month:D2}");
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
default:
// 默认处理,如果没有匹配的情况
break;
}
return result;
}
/// <summary>
/// 训练记录
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<PageDataDto<StudentTrainingRecordsModel>> TrainingRecords(StudentTrainingRecordsParam paramDto)
{
var res = new PageDataDto<StudentTrainingRecordsModel>();
var tenantId = UserContext.Current.TenantId;
if (string.IsNullOrEmpty(paramDto.StudentNo))
{
paramDto.StudentNo = _studentRepository.DbContext.Set<S_Student>().First(s => s.SchoolCode == tenantId).StudentNo;
}
// 查询 I_TrainingData 表
var iotQuery = from student in _studentRepository.DbContext.Set<I_TrainRanking>()
join trainingData in _studentRepository.DbContext.Set<I_TrainingData>()
on student.TrainingDataId equals trainingData.Id
where trainingData.IsDisplay && trainingData.SchoolCode == tenantId
select new StudentTrainingRecordsModel
{
StudentNo = student.StudentNo,
// 训练模式
TrainType = trainingData.ModelType ?? 0,
//项目模式
ModelName = trainingData.ModelName,
CategoryValue = (TrainingItemType)trainingData.ItemType,
// 成绩
Result = (float?)trainingData.ClassNumAvg,
ResultLevel = trainingData.ClassScore,
// 训练时间
ScoreTime = trainingData.InsertTime,
};
// 查询 Ai_SportsTestData 表
var aiQuery = from trainingData in _studentRepository.DbContext.Set<Ai_SportsTestData>()
where trainingData.IsDisplay && trainingData.SchoolCode == tenantId
select new StudentTrainingRecordsModel
{
StudentNo = trainingData.StudentNo,
TrainType = (int)trainingData.ModeType,
ModelName = trainingData.ModelName,
CategoryValue = (TrainingItemType)trainingData.CategoryValue,
Result = (float?)trainingData.Value,
ResultLevel = (int?)trainingData.RankEnum,
ScoreTime = trainingData.ScoreTime
};
// 合并两个查询结果
var combinedQuery = iotQuery.Concat(aiQuery);
// 根据学号过滤
if (!string.IsNullOrEmpty(paramDto.StudentNo))
{
combinedQuery = combinedQuery.Where(x => x.StudentNo == paramDto.StudentNo);
}
// 根据开始时间过滤
if (paramDto.StartTime.HasValue)
{
combinedQuery = combinedQuery.Where(x => x.ScoreTime >= paramDto.StartTime);
}
// 根据结束时间过滤
if (paramDto.EndTime.HasValue)
{
combinedQuery = combinedQuery.Where(x => x.ScoreTime <= paramDto.EndTime);
}
// 根据项目类型过滤
if (paramDto.CategoryValue > 0)
{
combinedQuery = combinedQuery.Where(x => x.CategoryValue == (TrainingItemType)paramDto.CategoryValue);
}
// 根据成绩等级过滤
if (paramDto.Rank > 0)
{
combinedQuery = combinedQuery.Where(x => x.ResultLevel == (int)paramDto.Rank);
}
// 获取总数
res.Total = await combinedQuery.CountAsync();
// 分页和排序
var list = await combinedQuery
.OrderBy(c => c.ScoreTime) // 按训练时间排序
.Skip((paramDto.PageIndex - 1) * paramDto.PageSize)
.Take(paramDto.PageSize)
.ToListAsync();
foreach (var item in list)
{
// 项目
item.CategoryName = item.CategoryValue.GetDisplayName();
var grade = ((AchievementRank)(item.ResultLevel.HasValue && item.ResultLevel.Value > 0 ? item.ResultLevel.Value : 4));
var gradeName = grade.GetDescription();
// 等级
item.Grade = gradeName;
var trainName = ((Ai_ModeEnum)(item.TrainType)).GetDescription();
// 训练模式
item.TrainName = trainName;
}
res.Datas = list;
return res;
}
/// <summary>
/// 体测记录
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<PageDataDto<StudentTestRecordsModel>> TestRecords(StudentTrainingRecordsParam paramDto)
{
var res = new PageDataDto<StudentTestRecordsModel>();
var tenantId = UserContext.Current.TenantId;
if (string.IsNullOrEmpty(paramDto.StudentNo))
{
paramDto.StudentNo = this._studentRepository.DbContext.Set<S_Student>().First().StudentNo;
}
// 初始化基础查询条件
Expression<Func<SportsTestValueModel, bool>> query = (x => x.SchoolCode == tenantId);
if (!string.IsNullOrEmpty(paramDto.StudentNo))
{
query = query.And(x => x.StudentNo == paramDto.StudentNo);
}
if (paramDto.StartTime.HasValue)
{
query = query.And(x => x.ScoreTime >= paramDto.StartTime);
}
if (paramDto.EndTime.HasValue)
{
query = query.And(x => x.ScoreTime <= paramDto.StartTime);
}
if (paramDto.CategoryValue > 0)
{
query = query.And(x => x.CategoryValue == paramDto.CategoryValue);
}
if (paramDto.Rank > 0)
{
query = query.And(x => x.RankEnum == paramDto.Rank);
}
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(query.Compile(), paramDto.TrainType);
// 获取总数
res.Total = sportsTestResults.Count();
// 分页和排序
res.Datas = sportsTestResults
.OrderBy(c => c.ScoreTime) // 按训练时间排序
.Select(c => new StudentTestRecordsModel
{
TrainName = c.ModeType.GetDescription(),
CategoryValue = (SportsTestItemType)c.CategoryValue,
CategoryName = ((SportsTestItemType)c.CategoryValue).GetDisplayName(),
Result = c.Value,
Score = (c.Score + c.AdditionalScore),
Rank = c.Rank,
ScoreTime = c.ScoreTime
})
.Skip((paramDto.PageIndex - 1) * paramDto.PageSize)
.Take(paramDto.PageSize)
.ToList();
return res;
}
// 构建课堂模式查询
private IQueryable<StudentTrainingRecordsModel> BuildClassRoomModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from td in _studentRepository.DbContext.Set<I_TrainingData>()
join tr in _studentRepository.DbContext.Set<I_TrainRanking>() on td.Id equals tr.TrainingDataId into ranking
from r in ranking.DefaultIfEmpty()
where td.SchoolCode == tenantId && r.StudentNo == studentNo
select new StudentTrainingRecordsModel
{
TrainName = "课堂模式",
ModelName = td.ModelName,
CategoryValue = (TrainingItemType)td.ItemType,
CategoryName = ((TrainingItemType)td.ItemType).GetDescription(),
Result = r.JumpValue,
ResultLevel = r.ResultLevel,
ScoreTime = td.InsertTime
};
return ApplyFilters(query, paramDto);
}
// 构建自由模式查询
private IQueryable<StudentTrainingRecordsModel> BuildFreeModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from a in _studentRepository.DbContext.Set<Ai_TrainingData>()
join n in _studentRepository.DbContext.Set<N_SportsTestValue>() on a.StartTime equals n.ScoreTime
where a.SchoolCode == tenantId && a.StudentNo == studentNo
select new StudentTrainingRecordsModel
{
TrainName = "自由模式",
ScoreTime = a.StartTime,
CategoryValue = a.CategoryValue,
CategoryName = ((TrainingItemType)Convert.ToInt32(a.CategoryValue)).GetDescription(),
ModelName = "/",
Grade = n.Rank
};
return ApplyFilters(query, paramDto);
}
// 通用过滤逻辑
private IQueryable<StudentTrainingRecordsModel> ApplyFilters(IQueryable<StudentTrainingRecordsModel> query, StudentTrainingRecordsParam paramDto)
{
return query
.WhereIF(paramDto.StartTime.HasValue, x => x.ScoreTime >= paramDto.StartTime)
.WhereIF(paramDto.EndTime.HasValue, x => x.ScoreTime <= paramDto.EndTime)
.WhereIF(paramDto.CategoryValue > 0, x => x.CategoryValue == (TrainingItemType)paramDto.CategoryValue)
.WhereIF(paramDto.Rank > 0, x => x.ResultLevel == (int)paramDto.Rank);
}
// 构建体测课堂模式查询
private IQueryable<StudentTestRecordsModel> BuildTestRoomModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from a in _studentRepository.DbContext.Set<I_SportsTestData>()
join r in _studentRepository.DbContext.Set<N_SportsTestValue>() on a.ScoreTime equals r.ScoreTime
where a.SchoolCode == tenantId && r.StudentNo == studentNo
select new StudentTestRecordsModel
{
TrainName = "课堂模式",
CategoryValue = (SportsTestItemType)r.CategoryValue,
CategoryName = ((SportsTestItemType)r.CategoryValue).GetDescription(),
Result = r.Value,
Rank = r.Rank,
Score = r.Score,
ScoreTime = r.ScoreTime
};
return TestApplyFilters(query, paramDto);
}
private IQueryable<StudentTestRecordsModel> BuildTestFreeModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from a in _studentRepository.DbContext.Set<Ai_TrainingData>()
join n in _studentRepository.DbContext.Set<N_SportsTestValue>() on a.StartTime equals n.ScoreTime
where a.SchoolCode == tenantId && a.StudentNo == studentNo
select new StudentTestRecordsModel
{
TrainName = "自由模式",
ScoreTime = a.StartTime,
CategoryValue = (SportsTestItemType)a.CategoryValue,
CategoryName = ((TrainingItemType)Convert.ToInt32(a.CategoryValue)).GetDescription(),
Rank = n.Rank,
Score = n.Score,
Result = n.Value
};
return TestApplyFilters(query, paramDto);
}
// 通用过滤逻辑
private IQueryable<StudentTestRecordsModel> TestApplyFilters(IQueryable<StudentTestRecordsModel> query, StudentTrainingRecordsParam paramDto)
{
return query
.WhereIF(paramDto.StartTime.HasValue, x => x.ScoreTime >= paramDto.StartTime)
.WhereIF(paramDto.EndTime.HasValue, x => x.ScoreTime <= paramDto.EndTime)
.WhereIF(paramDto.CategoryValue > 0, x => x.CategoryValue == (SportsTestItemType)paramDto.CategoryValue)
.WhereIF(paramDto.Rank > 0, x => x.Rank == paramDto.RankStr);
}
/// <summary>
/// 学生名单列表
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<List<StudentNamePageListModel>> GetStudentNamePageList(StudentNamePageListParam paramDto)
{
// 查询学生信息
var students = await _studentRepository.FindAsIQueryable(x => x.ClassId == paramDto.ClassId)
.Select(x => new StudentNamePageListModel()
{
StudentNo = x.StudentNo,
StudentName = x.StudentName,
Photo = x.Photo
})
.ToListAsync();
// 查询两个数据源的成绩并合并
var combinedResults = await _sportsTestResultRepository.FindAsIQueryable(x =>
x.ClassId == paramDto.ClassId &&
x.SchoolCode.Equals(UserContext.Current.TenantId)
)
.Select(x => new { x.StudentNo, x.Score })
.Concat(
_sportsTestResultRepository.DbContext.Set<Ai_SportsTestData>().Where(x =>
x.ClassId == paramDto.ClassId &&
x.SchoolCode.Equals(UserContext.Current.TenantId)
)
.Select(x => new { x.StudentNo, x.Score })
)
.ToListAsync();
// 使用字典来存储平均成绩,便于后续查找
var averageScoresDict = combinedResults
.GroupBy(x => x.StudentNo)
.ToDictionary(g => g.Key, g => g.Average(x => x.Score));
// 遍历学生信息,为每个学生分配 Rank
students.ForEach(student =>
{
if (averageScoresDict.TryGetValue(student.StudentNo, out var averageScore))
{
student.Rank = averageScore.GetRank(); // 有平均成绩的情况
}
else
{
student.Rank = 0.GetRank(); // 没有成绩时默认处理
}
});
return students;
}
/// <summary>
/// 更换班级
/// </summary>
/// <param name="studentNoList"></param>
/// <param name="classId"></param>
/// <returns></returns>
public async Task ReplaceClasses(List<string> studentNoList, int classId)
{
var students = await _studentRepository.FindAsync(x => studentNoList.Contains(x.StudentNo));
if (students == null || students.Count == 0)
throw new ArgumentNullException("未找到更新的数据");
foreach (var student in students)
{
student.ClassId = classId;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = DateTime.Now;
}
_studentRepository.UpdateRange(students);
await _studentRepository.SaveChangesAsync();
}
}
}