2026-03-27 10:53:02 +08:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2026-06-30 16:01:06 +08:00
|
|
|
|
using VOL.Commons;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
using VOL.Entity.DomainModels.XinWei;
|
|
|
|
|
|
using YD_YiLu.Api.Context;
|
|
|
|
|
|
using YD_YiLu.Api.Services.Interface;
|
|
|
|
|
|
using YD_YiLu.Api.SmartSportsEntitys;
|
2026-04-13 09:59:12 +08:00
|
|
|
|
using YD_YiLu.Api.Utilities;
|
2026-04-08 16:09:51 +08:00
|
|
|
|
using YD_YiLu.Commons;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
using YD_YiLu.Commons.Dto.DeviceSync;
|
2026-06-30 16:01:06 +08:00
|
|
|
|
using YD_YiLu.Commons.Dto.Open;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
using YD_YiLu.Commons.Utils;
|
|
|
|
|
|
|
|
|
|
|
|
namespace YD_YiLu.Api.Services.Impl
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 壹动站/壹体屏/活力课间屏 设备同步服务实现
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class DeviceSyncService : IDeviceSyncService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly SmartSportsContext _context;
|
|
|
|
|
|
|
|
|
|
|
|
public DeviceSyncService(SmartSportsContext context)
|
|
|
|
|
|
{
|
|
|
|
|
|
_context = context;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<int?> GetOrgIdByDeviceAsync(string deviceSerial)
|
|
|
|
|
|
{
|
2026-04-02 13:53:26 +08:00
|
|
|
|
var device = await _context.YL_Device.FirstOrDefaultAsync(x => x.SerialNumber == deviceSerial);
|
|
|
|
|
|
if (device?.OrgId != null)
|
|
|
|
|
|
return device.OrgId;
|
|
|
|
|
|
else return 0;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<DeviceSyncUserPageResponseDto> UserPageAsync(DeviceSyncUserPageRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial);
|
|
|
|
|
|
if (!orgId.HasValue) return new DeviceSyncUserPageResponseDto();
|
|
|
|
|
|
|
|
|
|
|
|
string schoolCode = await _context.School.Where(x => x.Id == orgId).Select(x => x.SchoolCode).FirstAsync();
|
|
|
|
|
|
var query = _context.Student.Where(x => x.SchoolCode == schoolCode);
|
|
|
|
|
|
|
2026-04-02 13:53:26 +08:00
|
|
|
|
//if (dto.UserType == 1) query = query.Where(x => false);
|
|
|
|
|
|
//else if (dto.UserType == 2) { }
|
|
|
|
|
|
//else if (dto.UserType == 3) query = query.Where(x => false);
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
|
|
|
|
|
var total = await query.CountAsync();
|
|
|
|
|
|
var pageIndex = dto.PageIndex < 1 ? 1 : dto.PageIndex;
|
|
|
|
|
|
var fullSync = dto.FullSync || dto.PageSize <= 0;
|
|
|
|
|
|
var pageSize = fullSync ? total : (dto.PageSize <= 0 ? 10 : dto.PageSize);
|
|
|
|
|
|
var listQuery = query.OrderBy(x => x.Id);
|
|
|
|
|
|
if (!fullSync)
|
|
|
|
|
|
listQuery = (IOrderedQueryable<S_Student>)listQuery.Skip((pageIndex - 1) * pageSize).Take(pageSize);
|
|
|
|
|
|
var list = await listQuery
|
|
|
|
|
|
.Select(s => new DeviceSyncUserDto
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = s.Id,
|
|
|
|
|
|
UserType = 2,
|
|
|
|
|
|
Username = s.StudentNo,
|
|
|
|
|
|
Nickname = s.StudentName,
|
|
|
|
|
|
GradeName = "",
|
2026-06-30 14:53:24 +08:00
|
|
|
|
OrganizationId = (int)orgId,
|
2026-04-02 13:53:26 +08:00
|
|
|
|
OrganizationName = s.ClassName ?? "",
|
2026-03-27 10:53:02 +08:00
|
|
|
|
FacePictureUrl = s.Photo ?? "",
|
|
|
|
|
|
Identity = "学生",
|
|
|
|
|
|
Gender = s.Sex == 1 ? "男" : "女",
|
|
|
|
|
|
Birthday = s.Birthday.HasValue ? s.Birthday.Value.ToString("yyyy-MM-dd") : "",
|
2026-04-02 13:53:26 +08:00
|
|
|
|
UserSerial = s.StudentNo,
|
2026-03-27 10:53:02 +08:00
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var u in list)
|
|
|
|
|
|
{
|
|
|
|
|
|
var c = await _context.Class.FindAsync(u.OrganizationId);
|
|
|
|
|
|
if (c != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
u.OrganizationName = c.ClassName ?? "";
|
|
|
|
|
|
u.GradeName = c.GradeName ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new DeviceSyncUserPageResponseDto
|
|
|
|
|
|
{
|
|
|
|
|
|
SyncId = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
|
|
|
|
|
Current = fullSync ? 1 : pageIndex,
|
|
|
|
|
|
Pages = fullSync ? 1 : (total == 0 ? 0 : (int)Math.Ceiling(total / (double)Math.Max(pageSize, 1))),
|
|
|
|
|
|
Total = total,
|
|
|
|
|
|
UserList = list
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<DeviceSyncUserPageResponseDto> UserSyncAsync(string deviceSerial, int deviceType, int userType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await UserPageAsync(new DeviceSyncUserPageRequestDto
|
|
|
|
|
|
{
|
|
|
|
|
|
DeviceSerial = deviceSerial,
|
|
|
|
|
|
DeviceType = deviceType,
|
|
|
|
|
|
UserType = userType,
|
|
|
|
|
|
PageIndex = 1,
|
|
|
|
|
|
FullSync = true,
|
|
|
|
|
|
PageSize = 0
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<DeviceSyncOrganizationDto>> OrganizationAsync(DeviceSyncOrganizationRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial);
|
|
|
|
|
|
if (!orgId.HasValue) return new List<DeviceSyncOrganizationDto>();
|
|
|
|
|
|
|
|
|
|
|
|
string schoolCode = await _context.School.Where(x => x.Id == orgId).Select(x => x.SchoolCode).FirstAsync();
|
2026-06-30 14:53:24 +08:00
|
|
|
|
|
|
|
|
|
|
var grades = await (
|
|
|
|
|
|
from g in _context.Grade
|
|
|
|
|
|
join sg in _context.SchoolAssocGrade
|
|
|
|
|
|
on g.Id equals sg.GradeId
|
|
|
|
|
|
where sg.SchoolCode == schoolCode
|
|
|
|
|
|
orderby g.Id
|
|
|
|
|
|
select new
|
|
|
|
|
|
{
|
|
|
|
|
|
g.Id,
|
|
|
|
|
|
g.GradeName
|
|
|
|
|
|
}
|
|
|
|
|
|
).ToListAsync();
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
var classes = await _context.Class.Where(x => x.SchoolCode == schoolCode).ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var result = new List<DeviceSyncOrganizationDto>();
|
|
|
|
|
|
foreach (var g in grades)
|
|
|
|
|
|
{
|
|
|
|
|
|
var gradeClasses = classes.Where(x => x.GradeId == g.Id).ToList();
|
|
|
|
|
|
result.Add(new DeviceSyncOrganizationDto
|
|
|
|
|
|
{
|
|
|
|
|
|
OrganizationId = g.Id,
|
|
|
|
|
|
ParentId = 0,
|
|
|
|
|
|
ParentName = null,
|
|
|
|
|
|
OrganizationName = g.GradeName,
|
|
|
|
|
|
OrganizationType = "年级",
|
|
|
|
|
|
Children = gradeClasses.Select(c => new DeviceSyncOrganizationDto
|
|
|
|
|
|
{
|
|
|
|
|
|
OrganizationId = c.Id,
|
|
|
|
|
|
ParentId = g.Id,
|
|
|
|
|
|
ParentName = g.GradeName,
|
|
|
|
|
|
OrganizationName = c.ClassName ?? "",
|
|
|
|
|
|
OrganizationType = "班级",
|
|
|
|
|
|
ChildrenIds = new List<int>()
|
|
|
|
|
|
}).ToList(),
|
|
|
|
|
|
ChildrenIds = gradeClasses.Select(x => x.Id).ToList()
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<DeviceSyncTrainPushResponseItem>> TrainPushAsync(List<DeviceSyncTrainPushRequestDto> list)
|
|
|
|
|
|
{
|
2026-04-08 16:09:51 +08:00
|
|
|
|
if (list == null || !list.Any())
|
|
|
|
|
|
return new List<DeviceSyncTrainPushResponseItem>();
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
|
|
|
|
|
var result = new List<DeviceSyncTrainPushResponseItem>();
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 👉 1. 设备校验(可选,看你业务)
|
|
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(list.First().DeviceSerial);
|
|
|
|
|
|
if (!orgId.HasValue)
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 2. 批量查已存在的 TrainSerial(防重复)
|
2026-06-30 15:26:51 +08:00
|
|
|
|
var normalizedItems = list
|
|
|
|
|
|
.Where(x => x != null
|
|
|
|
|
|
&& !string.IsNullOrWhiteSpace(x.DeviceSerial)
|
|
|
|
|
|
&& !string.IsNullOrWhiteSpace(x.UserSerial))
|
|
|
|
|
|
.Select(x =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var trainSerial = NormalizeTrainSerial(x.TrainSerial, x);
|
|
|
|
|
|
var videoSerial = NormalizeSerial(x.VideoSerial, trainSerial);
|
|
|
|
|
|
return new
|
|
|
|
|
|
{
|
|
|
|
|
|
Item = x,
|
|
|
|
|
|
TrainSerial = trainSerial,
|
|
|
|
|
|
VideoSerial = videoSerial
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (!normalizedItems.Any())
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
|
|
var trainSerials = normalizedItems.Select(x => x.TrainSerial).Distinct().ToList();
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
var existSerials = await _context.YL_Train
|
|
|
|
|
|
.Where(x => trainSerials.Contains(x.TrainSerial))
|
|
|
|
|
|
.Select(x => x.TrainSerial)
|
|
|
|
|
|
.ToListAsync();
|
2026-06-30 15:26:51 +08:00
|
|
|
|
var seenSerials = new HashSet<string>(existSerials, StringComparer.OrdinalIgnoreCase);
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
var newEntities = new List<YL_Train>();
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-06-30 15:26:51 +08:00
|
|
|
|
foreach (var normalized in normalizedItems)
|
2026-03-27 10:53:02 +08:00
|
|
|
|
{
|
2026-06-30 15:26:51 +08:00
|
|
|
|
var item = normalized.Item;
|
2026-06-30 16:01:06 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 👉 3. 唯一校验(跳过已存在)
|
2026-07-01 11:03:31 +08:00
|
|
|
|
if (!seenSerials.Add(normalized.TrainSerial))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
var score = CalculateScore(item);
|
|
|
|
|
|
if (!score.HasValue || score.Value <= 0)
|
|
|
|
|
|
continue;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
var entity = new YL_Train
|
2026-03-27 10:53:02 +08:00
|
|
|
|
{
|
2026-04-08 16:09:51 +08:00
|
|
|
|
DeviceSerial = item.DeviceSerial,
|
|
|
|
|
|
DeviceType = item.DeviceType,
|
|
|
|
|
|
UserSerial = item.UserSerial,
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-06-30 15:26:51 +08:00
|
|
|
|
TrainSerial = normalized.TrainSerial,
|
|
|
|
|
|
VideoSerial = normalized.VideoSerial,
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
TrainExam = item.TrainExam,
|
|
|
|
|
|
QuizId = item.QuizId,
|
|
|
|
|
|
|
|
|
|
|
|
StandardCounts = item.StandardCounts,
|
|
|
|
|
|
UnStandardCounts = item.UnStandardCounts,
|
|
|
|
|
|
|
|
|
|
|
|
TimeKeeping = item.TimeKeeping,
|
|
|
|
|
|
Duration = item.Duration,
|
|
|
|
|
|
Distance = item.Distance,
|
|
|
|
|
|
|
|
|
|
|
|
CourseId = item.CourseId,
|
|
|
|
|
|
|
|
|
|
|
|
StartTime = Util.ParseDateTime(item.StartTime),
|
|
|
|
|
|
EndTime = Util.ParseDateTime(item.EndTime),
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 成绩计算(建议统一规则)
|
2026-07-01 11:03:31 +08:00
|
|
|
|
Score = score,
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
IsValid = 1, // 默认有效
|
|
|
|
|
|
UploadTime = DateTime.Now,
|
|
|
|
|
|
CreateTime = DateTime.Now
|
2026-03-27 10:53:02 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
newEntities.Add(entity);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 4. 批量入库(关键优化点)
|
|
|
|
|
|
if (newEntities.Any())
|
|
|
|
|
|
{
|
2026-06-30 16:01:06 +08:00
|
|
|
|
var sportsTestValueEntities = await AddSportsTestValueEntity(orgId.Value, newEntities);
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
_context.YL_Train.AddRange(newEntities);
|
2026-06-30 16:01:06 +08:00
|
|
|
|
if (sportsTestValueEntities.Any())
|
|
|
|
|
|
_context.SportsTestValue.AddRange(sportsTestValueEntities);
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
await _context.SaveChangesAsync();
|
2026-03-27 10:53:02 +08:00
|
|
|
|
}
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 👉 5. 返回结果
|
|
|
|
|
|
result = newEntities.Select(x => new DeviceSyncTrainPushResponseItem
|
|
|
|
|
|
{
|
|
|
|
|
|
TrainId = x.Id,
|
|
|
|
|
|
UserSerial = x.UserSerial
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
private double? CalculateScore(DeviceSyncTrainPushRequestDto item)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 👉 根据课目类型决定成绩字段
|
|
|
|
|
|
// 这里给你一个通用策略(你可以按课目细化)
|
|
|
|
|
|
|
|
|
|
|
|
// 计数类(跳绳/仰卧起坐)
|
|
|
|
|
|
if (item.StandardCounts > 0)
|
|
|
|
|
|
return item.StandardCounts;
|
|
|
|
|
|
|
|
|
|
|
|
// 计时类(悬垂)
|
|
|
|
|
|
if (item.TimeKeeping.HasValue)
|
|
|
|
|
|
return item.TimeKeeping;
|
|
|
|
|
|
|
|
|
|
|
|
// 距离类(跳远)
|
|
|
|
|
|
if (item.Distance.HasValue)
|
|
|
|
|
|
return item.Distance;
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-30 16:01:06 +08:00
|
|
|
|
private async Task<List<N_SportsTestValue>> AddSportsTestValueEntity(int orgId, List<YL_Train> trains)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sportsTestValueEntities = new List<N_SportsTestValue>();
|
|
|
|
|
|
if (trains == null || !trains.Any())
|
|
|
|
|
|
return sportsTestValueEntities;
|
|
|
|
|
|
|
|
|
|
|
|
var userSerials = trains
|
|
|
|
|
|
.Select(x => x.UserSerial)
|
|
|
|
|
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var students = await _context.Student
|
|
|
|
|
|
.Where(x => userSerials.Contains(x.StudentNo))
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var classIds = students
|
|
|
|
|
|
.Where(x => x.ClassId.HasValue)
|
|
|
|
|
|
.Select(x => x.ClassId.Value)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var classes = await _context.Class
|
|
|
|
|
|
.Where(x => classIds.Contains(x.Id))
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var healthStandards = await _context.HealthStandards.ToListAsync();
|
|
|
|
|
|
var semesterDto = new SemesterDto();
|
|
|
|
|
|
var nowTime = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
var studentMap = students
|
|
|
|
|
|
.GroupBy(x => x.StudentNo)
|
|
|
|
|
|
.ToDictionary(x => x.Key, x => x.First());
|
|
|
|
|
|
|
|
|
|
|
|
var classMap = classes.ToDictionary(x => x.Id, x => x);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var train in trains)
|
|
|
|
|
|
{
|
|
|
|
|
|
var value = ResolveSportsTestValue(train);
|
|
|
|
|
|
if (value == 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (!studentMap.TryGetValue(train.UserSerial, out var student))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
S_Class? classInfo = null;
|
|
|
|
|
|
if (student.ClassId.HasValue)
|
|
|
|
|
|
classMap.TryGetValue(student.ClassId.Value, out classInfo);
|
|
|
|
|
|
|
|
|
|
|
|
var category = ResolveSportsTestCategory(train.CourseId);
|
|
|
|
|
|
|
|
|
|
|
|
var standard = healthStandards.FirstOrDefault(x =>
|
|
|
|
|
|
x.CategoryEnum == category.CategoryEnum &&
|
|
|
|
|
|
x.GradeId == (classInfo?.GradeId ?? 0) &&
|
|
|
|
|
|
x.Sex == student.Sex &&
|
|
|
|
|
|
value >= x.MinValue &&
|
|
|
|
|
|
value < x.MaxValue);
|
|
|
|
|
|
|
|
|
|
|
|
var sportsTestValueEntity = new N_SportsTestValue
|
|
|
|
|
|
{
|
|
|
|
|
|
SchoolCode = student.SchoolCode,
|
|
|
|
|
|
Sex = student.Sex,
|
|
|
|
|
|
DataSource = DataSource.YL,
|
|
|
|
|
|
CategoryValue = category.CategoryValue,
|
|
|
|
|
|
CategoryEnum = category.CategoryEnum,
|
|
|
|
|
|
GradeId = classInfo?.GradeId ?? 0,
|
|
|
|
|
|
GradeName = classInfo?.GradeName ?? "",
|
|
|
|
|
|
ClassId = student.ClassId ?? 0,
|
|
|
|
|
|
ClassName = classInfo?.ClassName ?? student.ClassName ?? "",
|
|
|
|
|
|
TeacherId = 0,
|
|
|
|
|
|
TeacherName = "",
|
|
|
|
|
|
StudentNo = student.StudentNo,
|
|
|
|
|
|
StudentName = student.StudentName,
|
|
|
|
|
|
Value = value,
|
|
|
|
|
|
MotionDuration = train.Duration > 0 ? (int)Math.Ceiling(train.Duration / 60.0) : 0,
|
|
|
|
|
|
Consumption = 0,
|
|
|
|
|
|
Strength = 0,
|
|
|
|
|
|
Score = standard?.Score ?? 0,
|
|
|
|
|
|
Rank = standard?.Rank,
|
|
|
|
|
|
RankEnum = standard?.Score.GetRank() ?? 0,
|
|
|
|
|
|
AdditionalScore = 0,
|
|
|
|
|
|
WrongResults = train.UnStandardCounts,
|
|
|
|
|
|
Creator = orgId,
|
|
|
|
|
|
CreateDate = nowTime,
|
|
|
|
|
|
ScoreTime = train.EndTime,
|
|
|
|
|
|
Year = semesterDto.Year,
|
|
|
|
|
|
Semester = semesterDto.Semester,
|
|
|
|
|
|
IsDisplay = true
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
sportsTestValueEntities.Add(sportsTestValueEntity);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return sportsTestValueEntities;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static (int CategoryValue, string CategoryEnum) ResolveSportsTestCategory(int courseId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var categoryEnum = courseId switch
|
|
|
|
|
|
{
|
|
|
|
|
|
3 => nameof(SportsTestItemType.Pull_Up),
|
|
|
|
|
|
7 => nameof(SportsTestItemType.StandingLongJump),
|
|
|
|
|
|
8 => nameof(SportsTestItemType.OneMinuteJumpRope),
|
|
|
|
|
|
23 => nameof(SportsTestItemType.Sit_And_Reach),
|
|
|
|
|
|
1001 => nameof(SportsTestItemType.BMI),
|
|
|
|
|
|
1002 => nameof(SportsTestItemType.VitalCapacity),
|
|
|
|
|
|
_ => Enum.GetName(typeof(CourseType), courseId) ?? courseId.ToString()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (courseId, categoryEnum);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static float ResolveSportsTestValue(YL_Train train)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (train.CourseId == (int)CourseType.StandingLongJump && train.Distance.HasValue)
|
|
|
|
|
|
return (float)train.Distance.Value;
|
|
|
|
|
|
|
|
|
|
|
|
if (train.TimeKeeping.HasValue && train.TimeKeeping.Value > 0)
|
|
|
|
|
|
return (float)train.TimeKeeping.Value;
|
|
|
|
|
|
|
|
|
|
|
|
if (train.StandardCounts > 0)
|
|
|
|
|
|
return (float)train.StandardCounts;
|
|
|
|
|
|
|
|
|
|
|
|
if (train.Distance.HasValue)
|
|
|
|
|
|
return (float)train.Distance.Value;
|
|
|
|
|
|
|
|
|
|
|
|
return train.Score.HasValue ? (float)train.Score.Value : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
public async Task<DeviceSyncTrainDetailsDto> TrainDetailsAsync(int trainId)
|
|
|
|
|
|
{
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 1. 获取训练记录
|
|
|
|
|
|
var train = await _context.YL_Train.FirstOrDefaultAsync(t => t.Id == trainId);
|
2026-03-27 10:53:02 +08:00
|
|
|
|
if (train == null) return null;
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 2. 获取学生信息
|
|
|
|
|
|
var student = await _context.Student.FirstOrDefaultAsync(s => s.StudentNo == train.UserSerial);
|
|
|
|
|
|
if (student == null) return null;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 3. 最近10次训练记录(CountList)
|
|
|
|
|
|
var recentTrains = await _context.YL_Train
|
|
|
|
|
|
.Where(t => t.UserSerial == train.UserSerial && t.CourseId == train.CourseId)
|
|
|
|
|
|
.OrderByDescending(t => t.StartTime)
|
2026-03-27 10:53:02 +08:00
|
|
|
|
.Take(10)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
.Select(t => new CountListItem
|
|
|
|
|
|
{
|
|
|
|
|
|
TrainId = t.Id,
|
|
|
|
|
|
Score = (t.Score ?? 0).ToString("0"), // 先拿数值
|
|
|
|
|
|
Time = t.StartTime.ToString("yyyy-MM-dd HH:mm:ss")
|
|
|
|
|
|
})
|
|
|
|
|
|
.AsNoTracking() // 可选,提高性能
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 在内存中格式化
|
|
|
|
|
|
var recentTrainsDto = recentTrains
|
|
|
|
|
|
.Select(t => new CountListItem
|
|
|
|
|
|
{
|
|
|
|
|
|
TrainId = t.TrainId,
|
|
|
|
|
|
Score = t.Score, // 格式化整数
|
|
|
|
|
|
Time = t.Time
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 近10日最佳成绩(DailyList)
|
|
|
|
|
|
var tenDaysAgo = DateTime.Now.Date.AddDays(-10);
|
|
|
|
|
|
|
|
|
|
|
|
var dailyScores = await _context.YL_Train
|
|
|
|
|
|
.Where(t => t.UserSerial == train.UserSerial
|
|
|
|
|
|
&& t.CourseId == train.CourseId
|
|
|
|
|
|
&& t.StartTime >= tenDaysAgo)
|
|
|
|
|
|
.GroupBy(t => t.StartTime.Date)
|
|
|
|
|
|
.Select(g => new
|
|
|
|
|
|
{
|
|
|
|
|
|
Date = g.Key,
|
|
|
|
|
|
MaxScore = g.Max(t => t.Score) ?? 0
|
|
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(g => g.Date)
|
2026-03-27 10:53:02 +08:00
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 在内存中格式化
|
|
|
|
|
|
var dailyListDto = dailyScores
|
|
|
|
|
|
.Select(d => new DailyListItem
|
|
|
|
|
|
{
|
|
|
|
|
|
Score = d.MaxScore.ToString("0"),
|
|
|
|
|
|
Time = d.Date.ToString("yyyy-MM-dd 00:00:00")
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 课程名称(枚举描述)
|
|
|
|
|
|
string courseName = Util.GetEnumDescription<CourseType>(train.CourseId);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 返回 DTO
|
2026-03-27 10:53:02 +08:00
|
|
|
|
return new DeviceSyncTrainDetailsDto
|
|
|
|
|
|
{
|
2026-04-08 16:09:51 +08:00
|
|
|
|
TrainId = train.Id,
|
|
|
|
|
|
UserSerial = train.UserSerial,
|
|
|
|
|
|
UserType = 2, // 学生
|
|
|
|
|
|
Nickname = student.StudentName,
|
|
|
|
|
|
FacePictureUrl = student.Photo ?? "",
|
2026-03-27 10:53:02 +08:00
|
|
|
|
Identity = "学生",
|
2026-04-08 16:09:51 +08:00
|
|
|
|
OrganizationId = (int)student.ClassId,
|
|
|
|
|
|
OrganizationName = student.ClassName,
|
|
|
|
|
|
Gender = student.Sex == 1 ? "男" : "女",
|
|
|
|
|
|
Score = train.Score?.ToString("0") ?? "0",
|
|
|
|
|
|
UnStandardCounts = train.UnStandardCounts,
|
|
|
|
|
|
CourseId = train.CourseId,
|
|
|
|
|
|
CourseName = courseName,
|
2026-03-27 10:53:02 +08:00
|
|
|
|
TrainType = "自由练习",
|
2026-04-08 16:09:51 +08:00
|
|
|
|
VideoUrl = train.VideoSerial,
|
|
|
|
|
|
StartTime = train.StartTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
|
EndTime = train.EndTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
|
CountList = recentTrains,
|
|
|
|
|
|
DailyList = dailyListDto,
|
|
|
|
|
|
KeyframeList = new List<KeyframeListItem>() // 暂时为空
|
2026-03-27 10:53:02 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<DeviceSyncGradeOrderItemDto>> GradeOrderAsync(DeviceSyncGradeOrderRequestDto dto)
|
|
|
|
|
|
{
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 1️⃣ 获取组织ID
|
2026-03-27 10:53:02 +08:00
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial);
|
|
|
|
|
|
if (!orgId.HasValue) return new List<DeviceSyncGradeOrderItemDto>();
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 2️⃣ 获取学校编码
|
|
|
|
|
|
string schoolCode = await _context.School
|
|
|
|
|
|
.Where(x => x.Id == orgId)
|
|
|
|
|
|
.Select(x => x.SchoolCode)
|
|
|
|
|
|
.FirstAsync();
|
|
|
|
|
|
|
2026-04-09 20:30:17 +08:00
|
|
|
|
|
|
|
|
|
|
var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime);
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 4️⃣ 主查询(YL_Train + Student)
|
|
|
|
|
|
var query = from t in _context.YL_Train
|
|
|
|
|
|
join s in _context.Student on t.UserSerial equals s.StudentNo
|
|
|
|
|
|
where s.SchoolCode == schoolCode
|
|
|
|
|
|
select new { t, s };
|
|
|
|
|
|
|
|
|
|
|
|
// 5️⃣ 条件过滤
|
|
|
|
|
|
if (dto.CourseId.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.CourseId == dto.CourseId.Value);
|
|
|
|
|
|
|
2026-04-09 19:59:50 +08:00
|
|
|
|
if (dto.Gender > 0)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
query = query.Where(x => x.s.Sex == dto.Gender.Value);
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-09 19:59:50 +08:00
|
|
|
|
if (dto.OrganizeId != null && dto.OrganizeId.Count > 0 && dto.OrganizeId[0] > 0)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
query = query.Where(x => dto.OrganizeId.Contains(x.s.ClassId ?? 0));
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-09 20:30:17 +08:00
|
|
|
|
if (start.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.StartTime >= start.Value);
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
2026-04-09 20:30:17 +08:00
|
|
|
|
if (end.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.StartTime < end.Value.AddDays(1));
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 6️⃣ 分组求总成绩并排序
|
2026-03-27 10:53:02 +08:00
|
|
|
|
var grouped = await query
|
2026-04-09 20:30:17 +08:00
|
|
|
|
.GroupBy(x => new { x.s.Id, x.s.StudentName, x.s.ClassId, x.s.ClassName, x.s.Sex, x.t.CourseId })
|
2026-04-08 16:09:51 +08:00
|
|
|
|
.Select(g => new
|
|
|
|
|
|
{
|
|
|
|
|
|
Key = g.Key,
|
|
|
|
|
|
Score = g.Sum(x => x.t.Score ?? 0)
|
|
|
|
|
|
})
|
2026-03-27 10:53:02 +08:00
|
|
|
|
.OrderByDescending(x => x.Score)
|
|
|
|
|
|
.Skip((dto.Page - 1) * dto.Size)
|
|
|
|
|
|
.Take(dto.Size)
|
2026-04-09 20:30:17 +08:00
|
|
|
|
.ToListAsync(); // ✅ 确保在此处完成
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 7️⃣ 返回结果
|
|
|
|
|
|
return grouped.Select(x => new DeviceSyncGradeOrderItemDto
|
2026-03-27 10:53:02 +08:00
|
|
|
|
{
|
|
|
|
|
|
UserId = x.Key.Id,
|
|
|
|
|
|
Nickname = x.Key.StudentName,
|
2026-04-08 16:09:51 +08:00
|
|
|
|
OrganizationId = x.Key.ClassId ?? 0,
|
2026-03-27 10:53:02 +08:00
|
|
|
|
OrganizationName = x.Key.ClassName ?? "",
|
|
|
|
|
|
Gender = x.Key.Sex,
|
2026-04-09 20:30:17 +08:00
|
|
|
|
Score = Math.Round(x.Score).ToString("0"),
|
2026-04-08 16:09:51 +08:00
|
|
|
|
CourseId = x.Key.CourseId,
|
|
|
|
|
|
CourseName = Util.GetEnumDescription<CourseType>(x.Key.CourseId)
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-01 15:13:49 +08:00
|
|
|
|
public async Task<List<DeviceSyncOrganizationGradeOrderItemDto>> OrganizationGradeOrderAsync(DeviceSyncGradeOrderRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial);
|
|
|
|
|
|
if (!orgId.HasValue) return new List<DeviceSyncOrganizationGradeOrderItemDto>();
|
|
|
|
|
|
|
|
|
|
|
|
string schoolCode = await _context.School
|
|
|
|
|
|
.Where(x => x.Id == orgId)
|
|
|
|
|
|
.Select(x => x.SchoolCode)
|
|
|
|
|
|
.FirstAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime);
|
|
|
|
|
|
|
|
|
|
|
|
var query = from t in _context.YL_Train
|
|
|
|
|
|
join s in _context.Student on t.UserSerial equals s.StudentNo
|
|
|
|
|
|
where s.SchoolCode == schoolCode
|
|
|
|
|
|
&& t.Score.HasValue
|
|
|
|
|
|
&& t.Score.Value > 0
|
|
|
|
|
|
select new { t, s };
|
|
|
|
|
|
|
|
|
|
|
|
if (dto.CourseId.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.CourseId == dto.CourseId.Value);
|
|
|
|
|
|
|
|
|
|
|
|
if (dto.Gender > 0)
|
|
|
|
|
|
query = query.Where(x => x.s.Sex == dto.Gender.Value);
|
|
|
|
|
|
|
|
|
|
|
|
if (dto.OrganizeId != null && dto.OrganizeId.Count > 0 && dto.OrganizeId[0] > 0)
|
|
|
|
|
|
query = query.Where(x => dto.OrganizeId.Contains(x.s.ClassId ?? 0));
|
|
|
|
|
|
|
|
|
|
|
|
if (start.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.StartTime >= start.Value);
|
|
|
|
|
|
|
|
|
|
|
|
if (end.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.StartTime < end.Value.AddDays(1));
|
|
|
|
|
|
|
|
|
|
|
|
var page = dto.Page < 1 ? 1 : dto.Page;
|
|
|
|
|
|
var size = dto.Size < 1 ? 10 : dto.Size;
|
|
|
|
|
|
|
|
|
|
|
|
var grouped = await query
|
|
|
|
|
|
.GroupBy(x => new { x.s.ClassId, x.s.ClassName, x.t.CourseId })
|
|
|
|
|
|
.Select(g => new
|
|
|
|
|
|
{
|
|
|
|
|
|
g.Key.ClassName,
|
|
|
|
|
|
g.Key.CourseId,
|
|
|
|
|
|
SumScore = g.Sum(x => x.t.Score ?? 0),
|
|
|
|
|
|
AvgScore = g.Average(x => x.t.Score ?? 0),
|
|
|
|
|
|
SumCount = g.Count()
|
|
|
|
|
|
})
|
|
|
|
|
|
.OrderByDescending(x => x.SumScore)
|
|
|
|
|
|
.Skip((page - 1) * size)
|
|
|
|
|
|
.Take(size)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return grouped.Select(x => new DeviceSyncOrganizationGradeOrderItemDto
|
|
|
|
|
|
{
|
|
|
|
|
|
OrganizationName = x.ClassName ?? "",
|
|
|
|
|
|
SumScore = Math.Round(x.SumScore).ToString("0"),
|
|
|
|
|
|
AvgScore = x.AvgScore.ToString("0.##"),
|
|
|
|
|
|
SumCount = x.SumCount,
|
|
|
|
|
|
CourseId = x.CourseId,
|
|
|
|
|
|
CourseName = Util.GetEnumDescription<CourseType>(x.CourseId)
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<DeviceSyncBestScoreDto> BestScoreAsync(DeviceSyncBestScoreRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial);
|
|
|
|
|
|
if (!orgId.HasValue) return null;
|
|
|
|
|
|
|
|
|
|
|
|
string schoolCode = await _context.School
|
|
|
|
|
|
.Where(x => x.Id == orgId)
|
|
|
|
|
|
.Select(x => x.SchoolCode)
|
|
|
|
|
|
.FirstAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var currentStudent = await _context.Student
|
|
|
|
|
|
.FirstOrDefaultAsync(x => x.SchoolCode == schoolCode && x.StudentNo == dto.UserSerial);
|
|
|
|
|
|
|
|
|
|
|
|
if (currentStudent == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
var currentClass = currentStudent.ClassId.HasValue
|
|
|
|
|
|
? await _context.Class.FirstOrDefaultAsync(x => x.Id == currentStudent.ClassId.Value)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
var query = from t in _context.YL_Train
|
|
|
|
|
|
join s in _context.Student on t.UserSerial equals s.StudentNo
|
|
|
|
|
|
where s.SchoolCode == schoolCode
|
|
|
|
|
|
&& t.CourseId == dto.CourseId
|
|
|
|
|
|
&& s.Sex == dto.Gender
|
|
|
|
|
|
&& t.Score.HasValue
|
|
|
|
|
|
&& t.Score.Value > 0
|
|
|
|
|
|
select new { t, s };
|
|
|
|
|
|
|
|
|
|
|
|
if (dto.Range == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
query = query.Where(x => x.s.ClassId == currentStudent.ClassId);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (dto.Range == 2 && currentClass != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var classIds = await _context.Class
|
|
|
|
|
|
.Where(x => x.SchoolCode == schoolCode && x.GradeId == currentClass.GradeId)
|
|
|
|
|
|
.Select(x => x.Id)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
query = query.Where(x => classIds.Contains(x.s.ClassId ?? 0));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var best = await query
|
|
|
|
|
|
.OrderByDescending(x => x.t.Score)
|
|
|
|
|
|
.ThenBy(x => x.t.StartTime)
|
|
|
|
|
|
.FirstOrDefaultAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (best == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
var bestClass = best.s.ClassId.HasValue
|
|
|
|
|
|
? await _context.Class.FirstOrDefaultAsync(x => x.Id == best.s.ClassId.Value)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
return new DeviceSyncBestScoreDto
|
|
|
|
|
|
{
|
|
|
|
|
|
UserSerial = best.s.StudentNo,
|
|
|
|
|
|
Nickname = best.s.StudentName,
|
|
|
|
|
|
OrganizationId = best.s.ClassId ?? 0,
|
|
|
|
|
|
OrganizationName = bestClass?.ClassName ?? best.s.ClassName ?? "",
|
|
|
|
|
|
Gender = best.s.Sex,
|
|
|
|
|
|
Score = best.t.Score?.ToString("0.##") ?? "0",
|
|
|
|
|
|
CourseId = best.t.CourseId,
|
|
|
|
|
|
CourseName = Util.GetEnumDescription<CourseType>(best.t.CourseId)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 09:59:12 +08:00
|
|
|
|
public async Task<List<DeviceSyncUserDto>> LastGradeAsync(DeviceSyncLastGradeRequestDto dto)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
{
|
2026-04-13 09:59:12 +08:00
|
|
|
|
if (dto?.UserSerials == null || dto.UserSerials.Count == 0)
|
|
|
|
|
|
return new List<DeviceSyncUserDto>();
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
2026-04-13 09:59:12 +08:00
|
|
|
|
var serials = dto.UserSerials
|
2026-04-08 16:09:51 +08:00
|
|
|
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
|
|
|
|
|
.Select(x => x.Trim())
|
|
|
|
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var students = await _context.Student
|
2026-04-13 09:59:12 +08:00
|
|
|
|
.Where(s => serials.Contains(s.StudentNo))
|
2026-04-08 16:09:51 +08:00
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var studentSerials = students.Select(s => s.StudentNo).ToList();
|
2026-04-13 09:59:12 +08:00
|
|
|
|
|
|
|
|
|
|
var trains = await _context.YL_Train
|
2026-04-08 16:09:51 +08:00
|
|
|
|
.Where(t => studentSerials.Contains(t.UserSerial))
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
2026-04-13 09:59:12 +08:00
|
|
|
|
// 👉 人 + 课程 聚合
|
|
|
|
|
|
var trainGroup = trains
|
|
|
|
|
|
.GroupBy(t => new { t.UserSerial, t.CourseId })
|
|
|
|
|
|
.ToDictionary(
|
|
|
|
|
|
g => (g.Key.UserSerial, g.Key.CourseId),
|
|
|
|
|
|
g => new
|
|
|
|
|
|
{
|
|
|
|
|
|
Best = g.Max(x => x.Score ?? 0),
|
|
|
|
|
|
Last = g.OrderByDescending(x => x.EndTime).FirstOrDefault()?.Score ?? 0
|
|
|
|
|
|
});
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
2026-04-13 09:59:12 +08:00
|
|
|
|
// 👉 课程枚举(必须全返回)
|
|
|
|
|
|
var courseIds = new List<int> { 2, 7, 8, 9, 10, 11 };
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
2026-04-13 09:59:12 +08:00
|
|
|
|
var result = new List<DeviceSyncUserDto>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var serial in serials)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
{
|
2026-04-13 09:59:12 +08:00
|
|
|
|
var st = students.FirstOrDefault(s => s.StudentNo == serial);
|
|
|
|
|
|
|
|
|
|
|
|
var user = new DeviceSyncUserDto
|
2026-04-08 16:09:51 +08:00
|
|
|
|
{
|
2026-04-13 09:59:12 +08:00
|
|
|
|
UserId = st?.Id ?? 0,
|
|
|
|
|
|
UserType = 0, // 你自己定义
|
|
|
|
|
|
Username = st?.StudentNo ?? serial,
|
|
|
|
|
|
Nickname = st?.StudentName ?? "",
|
|
|
|
|
|
OrganizationId = st?.ClassId ?? 0,
|
|
|
|
|
|
OrganizationName = st?.ClassName ?? "",
|
|
|
|
|
|
GradeName = "", // 没有就空
|
|
|
|
|
|
FacePictureUrl = st?.Photo ?? "",
|
|
|
|
|
|
Identity = "", // 可选
|
|
|
|
|
|
Gender = st == null ? "" : (st.Sex == 1 ? "男" : "女"),
|
|
|
|
|
|
Birthday = st?.Birthday?.ToString("yyyy-MM-dd") ?? "",
|
|
|
|
|
|
UserSerial = serial
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 构造 trainLimitValues
|
|
|
|
|
|
foreach (var cid in courseIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (trainGroup.TryGetValue((serial, cid), out var val))
|
2026-04-08 16:09:51 +08:00
|
|
|
|
{
|
2026-04-13 09:59:12 +08:00
|
|
|
|
user.TrainLimitValues.Add(new TrainLimitValueDto
|
|
|
|
|
|
{
|
|
|
|
|
|
CourseId = cid,
|
|
|
|
|
|
BestResult = Math.Round(val.Best).ToString("0"),
|
|
|
|
|
|
LastResult = Math.Round(val.Last).ToString("0")
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
user.TrainLimitValues.Add(new TrainLimitValueDto
|
|
|
|
|
|
{
|
|
|
|
|
|
CourseId = cid,
|
|
|
|
|
|
BestResult = "0",
|
|
|
|
|
|
LastResult = "0"
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-04-08 16:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 09:59:12 +08:00
|
|
|
|
result.Add(user);
|
2026-04-08 16:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
public async Task<List<DeviceSyncRecordAppItemDto>> RecordAppAsync(DeviceSyncRecordAppRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial);
|
2026-04-08 16:09:51 +08:00
|
|
|
|
if (!orgId.HasValue)
|
|
|
|
|
|
return new List<DeviceSyncRecordAppItemDto>();
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 1. 学校编码
|
|
|
|
|
|
string schoolCode = await _context.School
|
|
|
|
|
|
.Where(x => x.Id == orgId)
|
|
|
|
|
|
.Select(x => x.SchoolCode)
|
|
|
|
|
|
.FirstAsync();
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 👉 2. 时间范围
|
2026-03-27 10:53:02 +08:00
|
|
|
|
var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime);
|
|
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 👉 3. 主查询(YL_Train + Student)
|
|
|
|
|
|
var query = from t in _context.YL_Train
|
|
|
|
|
|
join s in _context.Student on t.UserSerial equals s.StudentNo
|
2026-03-27 10:53:02 +08:00
|
|
|
|
where s.SchoolCode == schoolCode
|
2026-04-08 16:09:51 +08:00
|
|
|
|
select new { t, s };
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 4. 条件过滤
|
|
|
|
|
|
if (dto.CourseId.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.CourseId == dto.CourseId.Value);
|
|
|
|
|
|
|
2026-04-09 19:59:50 +08:00
|
|
|
|
if (dto.Gender > 0)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
query = query.Where(x => x.s.Sex == dto.Gender.Value);
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(dto.UserSerial))
|
|
|
|
|
|
query = query.Where(x => x.t.UserSerial == dto.UserSerial);
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(dto.UserName))
|
|
|
|
|
|
query = query.Where(x => x.s.StudentName.Contains(dto.UserName));
|
|
|
|
|
|
|
|
|
|
|
|
if (dto.OrganizeId != null && dto.OrganizeId.Count > 0)
|
|
|
|
|
|
query = query.Where(x => dto.OrganizeId.Contains(x.s.ClassId ?? 0));
|
|
|
|
|
|
|
|
|
|
|
|
if (start.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.StartTime >= start.Value);
|
|
|
|
|
|
|
|
|
|
|
|
if (end.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.t.StartTime < end.Value.AddDays(1));
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 5. 分页 + 排序
|
|
|
|
|
|
var list = await query
|
|
|
|
|
|
.OrderByDescending(x => x.t.StartTime)
|
2026-03-27 10:53:02 +08:00
|
|
|
|
.Skip((dto.Page - 1) * dto.Size)
|
|
|
|
|
|
.Take(dto.Size)
|
|
|
|
|
|
.Select(x => new DeviceSyncRecordAppItemDto
|
|
|
|
|
|
{
|
2026-04-08 16:09:51 +08:00
|
|
|
|
TrainId = x.t.Id,
|
|
|
|
|
|
|
|
|
|
|
|
Nickname = x.s.StudentName,
|
|
|
|
|
|
OrganizationId = x.s.ClassId ?? 0,
|
|
|
|
|
|
OrganizationName = x.s.ClassName,
|
|
|
|
|
|
|
|
|
|
|
|
Gender = x.s.Sex,
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
FacePictureUrl = x.s.Photo,
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
Score = x.t.Score.HasValue ? x.t.Score.Value.ToString("0.##") : "0",
|
|
|
|
|
|
|
|
|
|
|
|
StartTime = x.t.StartTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
|
EndTime = x.t.EndTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
|
CourseId = x.t.CourseId,
|
2026-04-09 19:59:50 +08:00
|
|
|
|
CourseName = Util.GetEnumDescription<CourseType>(x.t.CourseId), // 👉 建议后面接字典表
|
2026-03-27 10:53:02 +08:00
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
return list;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<DeviceSyncMyRecordResponseDto> MyRecordAsync(DeviceSyncMyRecordRequestDto dto)
|
|
|
|
|
|
{
|
2026-04-08 16:09:51 +08:00
|
|
|
|
var result = new DeviceSyncMyRecordResponseDto();
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(dto.DeviceSerial);
|
2026-04-08 16:09:51 +08:00
|
|
|
|
if (!orgId.HasValue) return result;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
// 👉 1. 查学生
|
|
|
|
|
|
string schoolCode = await _context.School
|
|
|
|
|
|
.Where(x => x.Id == orgId)
|
|
|
|
|
|
.Select(x => x.SchoolCode)
|
|
|
|
|
|
.FirstAsync();
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
var student = await _context.Student
|
|
|
|
|
|
.FirstOrDefaultAsync(x => x.SchoolCode == schoolCode && x.StudentNo == dto.UserSerial);
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
2026-04-08 16:09:51 +08:00
|
|
|
|
if (student == null) return result;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
|
|
|
|
|
var c = await _context.Class.FindAsync(student.ClassId);
|
2026-07-01 15:21:39 +08:00
|
|
|
|
result.UserSync = new UserSyncDto
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = student.Id,
|
|
|
|
|
|
UserType = 2,
|
|
|
|
|
|
Nickname = student.StudentName,
|
|
|
|
|
|
OrganizationId = student.ClassId ?? 0,
|
|
|
|
|
|
OrganizationName = c?.ClassName ?? "",
|
|
|
|
|
|
FacePictureUrl = student.Photo,
|
|
|
|
|
|
Identity = "学生",
|
|
|
|
|
|
Gender = student.Sex == 1 ? "男" : "女",
|
|
|
|
|
|
Birthday = student.Birthday?.ToString("yyyy-MM-dd") ?? "",
|
|
|
|
|
|
UserSerial = student.StudentNo,
|
|
|
|
|
|
Height = "0",
|
|
|
|
|
|
Weight = "0"
|
|
|
|
|
|
};
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 👉 2. 时间范围
|
|
|
|
|
|
var (start, end) = GetDateRange(dto.DateType, dto.QueryStartTime, dto.QueryEndTime);
|
|
|
|
|
|
|
|
|
|
|
|
var query = _context.YL_Train
|
2026-07-01 15:21:39 +08:00
|
|
|
|
.Where(x => x.UserSerial == dto.UserSerial
|
|
|
|
|
|
&& x.DeviceSerial == dto.DeviceSerial
|
|
|
|
|
|
&& x.DeviceType == dto.DeviceType
|
|
|
|
|
|
&& x.Score.HasValue
|
|
|
|
|
|
&& x.Score.Value > 0);
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
if (start.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.StartTime >= start.Value);
|
|
|
|
|
|
|
|
|
|
|
|
if (end.HasValue)
|
|
|
|
|
|
query = query.Where(x => x.StartTime < end.Value.AddDays(1));
|
|
|
|
|
|
|
2026-07-01 15:21:39 +08:00
|
|
|
|
if (dto.CourseIdList != null && dto.CourseIdList.Count > 0)
|
|
|
|
|
|
query = query.Where(x => dto.CourseIdList.Contains(x.CourseId));
|
|
|
|
|
|
else if (dto.CourseType > 0)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
query = query.Where(x => x.CourseId == dto.CourseType);
|
|
|
|
|
|
|
|
|
|
|
|
var trains = await query.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (!trains.Any())
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 3. 统计
|
|
|
|
|
|
var trainDays = trains.Select(x => x.StartTime.Date).Distinct().Count();
|
|
|
|
|
|
var totalSeconds = trains.Sum(x => x.Duration);
|
|
|
|
|
|
var trainHours = (totalSeconds / 3600.0).ToString("0.00");
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 4. gradeList(按天统计)
|
|
|
|
|
|
var gradeList = trains
|
2026-07-01 15:21:39 +08:00
|
|
|
|
.GroupBy(x => x.CourseId)
|
2026-04-08 16:09:51 +08:00
|
|
|
|
.Select(g => new GradeListItem
|
|
|
|
|
|
{
|
2026-07-01 15:21:39 +08:00
|
|
|
|
CourseId = g.Key,
|
|
|
|
|
|
CourseName = Util.GetEnumDescription<CourseType>(g.Key),
|
|
|
|
|
|
BestGrade = (g.Max(x => x.Score) ?? 0).ToString("0.##"),
|
|
|
|
|
|
AvgGrade = (g.Average(x => x.Score) ?? 0).ToString("0.##")
|
2026-04-08 16:09:51 +08:00
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 5. graphTrainList(课目统计)
|
|
|
|
|
|
var graphList = trains
|
|
|
|
|
|
.GroupBy(x => x.CourseId)
|
|
|
|
|
|
.Select(g => new GraphTrainListItem
|
|
|
|
|
|
{
|
|
|
|
|
|
CourseId = g.Key,
|
2026-04-09 19:59:50 +08:00
|
|
|
|
CourseName = Util.GetEnumDescription<CourseType>(g.Key),
|
2026-07-01 15:21:39 +08:00
|
|
|
|
Time = g.Sum(x => x.Duration),
|
2026-04-08 16:09:51 +08:00
|
|
|
|
Count = g.Count()
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 6. courseBestList(最好/平均/最差)
|
|
|
|
|
|
var courseBestList = trains
|
|
|
|
|
|
.Where(x => x.Score.HasValue)
|
|
|
|
|
|
.GroupBy(x => x.CourseId)
|
|
|
|
|
|
.Select(g => new CourseBestListItem
|
|
|
|
|
|
{
|
|
|
|
|
|
CourseId = g.Key,
|
2026-04-09 19:59:50 +08:00
|
|
|
|
CourseName = Util.GetEnumDescription<CourseType>(g.Key),
|
2026-04-08 16:09:51 +08:00
|
|
|
|
BestGrade = g.Max(x => x.Score).ToString(),
|
|
|
|
|
|
AvgGrade = g.Average(x => x.Score).ToString(),
|
|
|
|
|
|
WorstGrade = g.Min(x => x.Score).ToString()
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 👉 7. 返回
|
|
|
|
|
|
result = new DeviceSyncMyRecordResponseDto
|
2026-03-27 10:53:02 +08:00
|
|
|
|
{
|
|
|
|
|
|
UserSync = new UserSyncDto
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = student.Id,
|
|
|
|
|
|
UserType = 2,
|
|
|
|
|
|
Nickname = student.StudentName,
|
|
|
|
|
|
OrganizationId = student.ClassId ?? 0,
|
|
|
|
|
|
OrganizationName = c?.ClassName ?? "",
|
|
|
|
|
|
FacePictureUrl = student.Photo,
|
|
|
|
|
|
Identity = "学生",
|
|
|
|
|
|
Gender = student.Sex == 1 ? "男" : "女",
|
|
|
|
|
|
Birthday = student.Birthday?.ToString("yyyy-MM-dd") ?? "",
|
2026-04-08 16:09:51 +08:00
|
|
|
|
UserSerial = student.StudentNo,
|
|
|
|
|
|
Height = "",
|
|
|
|
|
|
Weight = ""
|
2026-03-27 10:53:02 +08:00
|
|
|
|
},
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
TrainCourseSum = graphList.Count,
|
2026-04-08 16:09:51 +08:00
|
|
|
|
TrainDays = trainDays,
|
|
|
|
|
|
TrainHours = trainHours,
|
2026-03-27 10:53:02 +08:00
|
|
|
|
TrainCounts = trains.Count,
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
GradeList = gradeList,
|
|
|
|
|
|
GraphTrainList = graphList,
|
2026-04-09 19:59:50 +08:00
|
|
|
|
//CourseBestList = courseBestList
|
2026-03-27 10:53:02 +08:00
|
|
|
|
};
|
2026-07-01 15:21:39 +08:00
|
|
|
|
result.UserSync.Identity = "学生";
|
|
|
|
|
|
result.UserSync.Gender = student.Sex == 1 ? "男" : "女";
|
|
|
|
|
|
result.UserSync.Height = "0";
|
|
|
|
|
|
result.UserSync.Weight = "0";
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
return result;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<TeacherLoginResponseDto> TeacherLoginAsync(TeacherLoginRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
var pwdHash = MD5Helper.MD5Encrypt32(dto.Password);
|
|
|
|
|
|
var user = await _context.Sys_User.FirstOrDefaultAsync(x => x.UserName == dto.Username && x.UserPwd == pwdHash);
|
|
|
|
|
|
if (user == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var school = await _context.School.FirstOrDefaultAsync(x => x.SchoolCode == user.SchoolCode);
|
|
|
|
|
|
return new TeacherLoginResponseDto
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = user.User_Id,
|
|
|
|
|
|
Nickname = user.UserTrueName ?? user.UserName,
|
|
|
|
|
|
Username = user.UserName,
|
|
|
|
|
|
OrganizationId = school?.Id ?? 0,
|
|
|
|
|
|
OrganizationName = school?.SchoolName ?? user.SchoolName ?? "",
|
|
|
|
|
|
SerialNumber = user.UserName,
|
|
|
|
|
|
Classification = 1,
|
|
|
|
|
|
Gender = user.Gender
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-13 10:11:41 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 新增app版本
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dto"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<long> AppVersionAddAsync(AppVersionAddRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1️⃣ 如果设为最新版本,取消旧版本
|
|
|
|
|
|
if (dto.IsLatest == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldList = await _context.Set<YL_AppVersion>()
|
|
|
|
|
|
.Where(x => x.ApplyName == dto.ApplyName
|
|
|
|
|
|
&& x.DeviceType == dto.DeviceType
|
|
|
|
|
|
&& x.HardwareType == dto.HardwareType
|
|
|
|
|
|
&& x.OperatingSystem == dto.OperatingSystem
|
|
|
|
|
|
&& x.Status == 1)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in oldList)
|
|
|
|
|
|
{
|
|
|
|
|
|
item.IsLatest = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2️⃣ 新版本
|
|
|
|
|
|
var entity = new YL_AppVersion
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyName = dto.ApplyName,
|
|
|
|
|
|
DeviceType = dto.DeviceType,
|
|
|
|
|
|
HardwareType = dto.HardwareType,
|
|
|
|
|
|
OperatingSystem = dto.OperatingSystem,
|
|
|
|
|
|
VersionNumber = dto.VersionNumber,
|
|
|
|
|
|
VersionCode = dto.VersionCode,
|
|
|
|
|
|
UpdateUrl = dto.UpdateUrl,
|
|
|
|
|
|
NeedForcedUpdating = dto.NeedForcedUpdating,
|
|
|
|
|
|
IsLatest = dto.IsLatest,
|
|
|
|
|
|
Status = 1,
|
|
|
|
|
|
CreateTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_context.Set<YL_AppVersion>().Add(entity);
|
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return entity.Id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
|
|
|
|
|
|
public async Task<AppVerifyResponseDto> AppVerifyAsync(AppVerifyRequestDto dto)
|
|
|
|
|
|
{
|
2026-04-13 09:59:12 +08:00
|
|
|
|
// 1️⃣ 查询最新版本
|
|
|
|
|
|
var latest = await _context.Set<YL_AppVersion>()
|
|
|
|
|
|
.Where(x => x.ApplyName == dto.ApplyName
|
|
|
|
|
|
&& x.DeviceType == dto.DeviceType
|
|
|
|
|
|
&& x.HardwareType == dto.HardwareType
|
|
|
|
|
|
&& x.OperatingSystem == dto.OperatingSystem
|
|
|
|
|
|
&& x.Status == 1
|
|
|
|
|
|
&& x.PublishStatus == 1)
|
|
|
|
|
|
.OrderByDescending(x => x.IsLatest)
|
|
|
|
|
|
.ThenByDescending(x => x.VersionCode)
|
|
|
|
|
|
.FirstOrDefaultAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 2️⃣ 没有配置版本 → 不更新
|
|
|
|
|
|
if (latest == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new AppVerifyResponseDto
|
|
|
|
|
|
{
|
|
|
|
|
|
VersionNumber = dto.VersionNumber,
|
|
|
|
|
|
NeedForcedUpdating = 0,
|
|
|
|
|
|
UpdateUrl = null,
|
|
|
|
|
|
HardwareType = dto.HardwareType,
|
|
|
|
|
|
ApplyName = dto.ApplyName
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3️⃣ 版本比较(必须用 VersionCode)
|
|
|
|
|
|
int currentVersionCode = ParseVersionCode(dto.VersionNumber);
|
|
|
|
|
|
|
|
|
|
|
|
bool needUpdate = latest.VersionCode > currentVersionCode;
|
|
|
|
|
|
|
|
|
|
|
|
if (!needUpdate)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new AppVerifyResponseDto
|
|
|
|
|
|
{
|
|
|
|
|
|
VersionNumber = dto.VersionNumber,
|
|
|
|
|
|
NeedForcedUpdating = 0,
|
|
|
|
|
|
UpdateUrl = null,
|
|
|
|
|
|
HardwareType = dto.HardwareType,
|
|
|
|
|
|
ApplyName = dto.ApplyName
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4️⃣ 是否强制升级
|
|
|
|
|
|
bool needForce = latest.NeedForcedUpdating == 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 最低版本限制(强制升级)
|
|
|
|
|
|
if (!string.IsNullOrEmpty(latest.MinSupportVersion))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CompareVersion(dto.VersionNumber, latest.MinSupportVersion) < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
needForce = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5️⃣ 返回结果
|
2026-03-27 10:53:02 +08:00
|
|
|
|
return new AppVerifyResponseDto
|
|
|
|
|
|
{
|
2026-04-13 09:59:12 +08:00
|
|
|
|
VersionNumber = latest.VersionNumber,
|
|
|
|
|
|
NeedForcedUpdating = needForce ? 1 : 0,
|
|
|
|
|
|
UpdateUrl = latest.UpdateUrl,
|
|
|
|
|
|
HardwareType = latest.HardwareType,
|
|
|
|
|
|
ApplyName = latest.ApplyName
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 视频回放上传
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dto"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<bool> DeviceVideoUploadAsync(DeviceVideoUploadRequestDto dto)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (dto.File == null || dto.File.Length == 0)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 1️⃣ 生成OSS路径(按我之前给你的规范)
|
|
|
|
|
|
var path = $"Upload/device/{dto.DeviceType}/{dto.DeviceSerial}/video/{dto.VideoSerial}/";
|
|
|
|
|
|
|
|
|
|
|
|
// 2️⃣ 上传到OSS
|
|
|
|
|
|
var url = ALiYunOss.Upload(dto.File, path, dto.VideoSerial);
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(url))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 3️⃣ 入库
|
|
|
|
|
|
var entity = new YL_DeviceVideo
|
|
|
|
|
|
{
|
|
|
|
|
|
DeviceSerial = dto.DeviceSerial,
|
|
|
|
|
|
DeviceType = dto.DeviceType,
|
|
|
|
|
|
VideoSerial = dto.VideoSerial,
|
|
|
|
|
|
VideoUrl = url,
|
|
|
|
|
|
Timestamp = dto.Timestamp,
|
|
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
|
|
Status = 1
|
2026-03-27 10:53:02 +08:00
|
|
|
|
};
|
2026-04-13 09:59:12 +08:00
|
|
|
|
|
|
|
|
|
|
_context.Set<YL_DeviceVideo>().Add(entity);
|
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
2026-03-27 10:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 10:11:41 +08:00
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
public async Task<DeviceSyncUserDto> GetUserDetailAsync(string deviceSerial, int userId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var orgId = await GetOrgIdByDeviceAsync(deviceSerial);
|
|
|
|
|
|
if (!orgId.HasValue) return null;
|
|
|
|
|
|
|
|
|
|
|
|
string schoolCode = await _context.School.Where(x => x.Id == orgId).Select(x => x.SchoolCode).FirstAsync();
|
|
|
|
|
|
var s = await _context.Student.FirstOrDefaultAsync(x => x.Id == userId && x.SchoolCode == schoolCode);
|
|
|
|
|
|
if (s == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var dto = new DeviceSyncUserDto
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = s.Id,
|
|
|
|
|
|
UserType = 2,
|
|
|
|
|
|
Username = s.StudentNo,
|
|
|
|
|
|
Nickname = s.StudentName,
|
|
|
|
|
|
OrganizationId = s.ClassId ?? 0,
|
|
|
|
|
|
OrganizationName = s.ClassName ?? "",
|
|
|
|
|
|
GradeName = "",
|
|
|
|
|
|
FacePictureUrl = s.Photo ?? "",
|
|
|
|
|
|
Identity = "学生",
|
|
|
|
|
|
Gender = s.Sex == 1 ? "男" : "女",
|
|
|
|
|
|
Birthday = s.Birthday.HasValue ? s.Birthday.Value.ToString("yyyy-MM-dd") : "",
|
|
|
|
|
|
UserSerial = s.StudentNo
|
|
|
|
|
|
};
|
|
|
|
|
|
var c = await _context.Class.FindAsync(dto.OrganizationId);
|
|
|
|
|
|
if (c != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
dto.OrganizationName = c.ClassName ?? "";
|
|
|
|
|
|
dto.GradeName = c.GradeName ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
return dto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-30 15:26:51 +08:00
|
|
|
|
private static string NormalizeTrainSerial(string serial, DeviceSyncTrainPushRequestDto item)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(serial))
|
|
|
|
|
|
return NormalizeSerial(serial, "AUTO-" + MD5Helper.MD5Encrypt32(serial));
|
|
|
|
|
|
|
|
|
|
|
|
var seed = string.Join("|", new[]
|
|
|
|
|
|
{
|
|
|
|
|
|
item.DeviceSerial,
|
|
|
|
|
|
item.UserSerial,
|
|
|
|
|
|
item.CourseId.ToString(),
|
|
|
|
|
|
item.TrainExam.ToString(),
|
|
|
|
|
|
item.StartTime,
|
|
|
|
|
|
item.EndTime,
|
|
|
|
|
|
item.Duration.ToString(),
|
|
|
|
|
|
item.StandardCounts.ToString(),
|
|
|
|
|
|
item.UnStandardCounts.ToString(),
|
|
|
|
|
|
item.Distance?.ToString() ?? "",
|
|
|
|
|
|
item.TimeKeeping?.ToString() ?? ""
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return "AUTO-" + MD5Helper.MD5Encrypt32(seed);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string NormalizeSerial(string serial, string fallback)
|
|
|
|
|
|
{
|
|
|
|
|
|
var value = string.IsNullOrWhiteSpace(serial) ? fallback : serial.Trim();
|
|
|
|
|
|
if (value.Length <= 64)
|
|
|
|
|
|
return value;
|
|
|
|
|
|
|
|
|
|
|
|
return "AUTO-" + MD5Helper.MD5Encrypt32(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 10:53:02 +08:00
|
|
|
|
private static (DateTime? start, DateTime? end) GetDateRange(int dateType, string queryStart, string queryEnd)
|
|
|
|
|
|
{
|
|
|
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
return dateType switch
|
|
|
|
|
|
{
|
|
|
|
|
|
1 => (now.Date, now.Date),
|
|
|
|
|
|
2 => (now.AddDays(-(int)now.DayOfWeek + 1).Date, now.Date),
|
|
|
|
|
|
3 => (new DateTime(now.Year, now.Month, 1), now.Date),
|
|
|
|
|
|
4 => (now.Month >= 9 ? new DateTime(now.Year, 9, 1) : new DateTime(now.Year - 1, 9, 1), now.Date),
|
|
|
|
|
|
5 => (DateTime.TryParse(queryStart, out var s) ? s : (DateTime?)null, DateTime.TryParse(queryEnd, out var e) ? e : (DateTime?)null),
|
|
|
|
|
|
_ => (null, null)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-04-08 16:09:51 +08:00
|
|
|
|
|
2026-04-13 09:59:12 +08:00
|
|
|
|
private int CompareVersion(string v1, string v2)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(v1) || string.IsNullOrEmpty(v2))
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
|
|
var arr1 = v1.Split('.');
|
|
|
|
|
|
var arr2 = v2.Split('.');
|
|
|
|
|
|
|
|
|
|
|
|
int len = Math.Max(arr1.Length, arr2.Length);
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int num1 = i < arr1.Length ? int.Parse(arr1[i]) : 0;
|
|
|
|
|
|
int num2 = i < arr2.Length ? int.Parse(arr2[i]) : 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (num1 > num2) return 1;
|
|
|
|
|
|
if (num1 < num2) return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
private int ParseVersionCode(string version)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(version))
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
|
|
var parts = version.Split('.');
|
|
|
|
|
|
int code = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < parts.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (int.TryParse(parts[i], out int num))
|
|
|
|
|
|
{
|
|
|
|
|
|
code = code * 100 + num;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return code;
|
|
|
|
|
|
}
|
2026-03-27 10:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|