using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Device.Entities; using Dlhis.Entity.Device.Enums; using System.Linq.Expressions; using Dlhis.ScheduledTask.TaskAssignment.Request; using Dlhis.ScheduledTask.TaskAssignment.Response; using Kurisu.AspNetCore.Scope; using Kurisu.AspNetCore.Utils.Extensions; using Kurisu.SqlSugar.Services; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Options; using SqlSugar; using Kurisu.SqlSugar.Extensions; using Newtonsoft.Json.Linq; using Kurisu.Core.CustomClass; using Kurisu.AspNetCore; using Kurisu.AspNetCore.Utils; using Org.BouncyCastle.Asn1.Ocsp; namespace Dlhis.ScheduledTask.TaskAssignment.Services.Implements; public class AssignmentService : IAssignmentService { private readonly IDbContext _dbContext; private readonly ILogger _logger; private readonly RedisCache _redisCache; public AssignmentService(IDbContext dbContext, ILogger logger, RedisCache redisCache) { _dbContext = dbContext; _logger = logger; _redisCache = redisCache; } public async Task> DeviceGetTasksAsync(DeviceTaskGetRequest request) { ////获取心跳设备 todo //var key = "heardbeat-devices"; //var heartbeatDeviceNos = (await _redisCache.ListRangeAsync(key))?.Select(x => x.ToString()).ToList(); var data = await ContinueUpdateAsync(async () => { var page = await _dbContext.Queryable() .LeftJoin((deviceTask, deivce) => deviceTask.DeviceUniqueNo == deivce.UniqueNo) .LeftJoin((deviceTask, deivce, deivceTamplate) => deivceTamplate.Code == deivce.TemplateCode) .LeftJoin((deviceTask, deivce, deivceTamplate, ga) => deviceTask.UserNo == ga.No) .LeftJoin((deviceTask, deivce, deivceTamplate, ga, galeave) => deviceTask.UserNo == galeave.No) .LeftJoin((deviceTask, deivce, deivceTamplate, ga, galeave, department) => department.No == ga.DepartmentNo) .WhereIF(request.Brand.IsPresent(), (deviceTask, deivce, deivceTamplate) => deivceTamplate.Brand == request.Brand) .WhereIF(request.DeviceUniqueNo.IsPresent(), (deviceTask, deivce) => deviceTask.DeviceUniqueNo == request.DeviceUniqueNo) .WhereIF(request.Types.IsPresent(), deviceTask => request.Types.Contains(deviceTask.Type)) .WhereIF(!request.IsProcess, deviceTask => deviceTask.Status == DeviceTaskStatusEnumType.Wait) .WhereIF(request.IsProcess, deviceTask => deviceTask.Status == DeviceTaskStatusEnumType.Process) //.Where(deviceTask => heartbeatDeviceNos.Contains(deviceTask.DeviceUniqueNo)) .OrderBy(deviceTask => deviceTask.CreateTime) .Select((deviceTask, deivce, deivceTamplate, ga, galeave, department) => new { UserId = SqlFunc.IsNull(SqlFunc.IsNull(deviceTask.UserId, ga.Id), galeave.IdentityUserId), TaskId = deviceTask.Id, deviceTask.Type, deviceTask.DeviceUniqueNo, DeviceConnectionInfo = deivce.ConnectionInfo, deivceTamplate.Brand, FuncStr = deivce.Funcs.ToString(), deviceTask.UserNo, UserCode = SqlFunc.IsNull(ga.Code, galeave.Code), UserName = SqlFunc.IsNull(ga.Name, galeave.Name), ga.DepartmentNo, DepartmentName = department.Name, ga.CardNo, deviceTask.Version }) .ToPageListAsync(request); if (!page.Data.Any()) { return page.Data; } var taskIdVs = page.Data.Select(x => new { x.TaskId, x.Version }).ToList(); Expression> where = LinqExpressionExtensions.False(); taskIdVs.ForEach(p => { where = where.Or(x => x.Id == p.TaskId && x.Version == p.Version && x.Status != DeviceTaskStatusEnumType.Success); }); await _dbContext.Client.Ado.BeginTranAsync(); try { var effecet = await _dbContext.Updateable() .SetColumns(x => x.Version == x.Version + 1) .SetColumns(x => x.Status == DeviceTaskStatusEnumType.Process) .SetColumns(x => x.ModifiedTime == DateTime.Now) .Where(where) .ExecuteCommandAsync(); if (taskIdVs.Count != effecet) { throw new VersionExceptions("更新错误"); } await _dbContext.Client.Ado.CommitTranAsync(); } catch { await _dbContext.Client.Ado.RollbackTranAsync(); throw; } return page.Data; }, 5); if (!data.Any()) { return new List(); } Dictionary deviceUploadInfos = null; if (data.Any(x => x.Type == DeviceTaskEnumType.UploadRecord)) { var deviceNos = data.Select(x => x.DeviceUniqueNo).Distinct(); deviceUploadInfos = new Dictionary(deviceNos.Count()); var logInfos = await _dbContext.Queryable().Select(it => new { Index2 = SqlFunc.RowNumber(SqlFunc.Desc(it.Id), it.DeviceUniqueNo), it.DeviceUniqueNo, it.RaiseDateTime }) .MergeTable() .Where(it => it.Index2 == 1) .ToListAsync(); foreach (var item in deviceNos) { var logInfo = logInfos.FirstOrDefault(x => x.DeviceUniqueNo == item); deviceUploadInfos.Add(item, logInfo?.RaiseDateTime); } } //申请新的请求域 return Scoped.Request.Value.Create((sp) => { var faceUrlOptions = sp.GetService>().Value; var dataProtectionProvider = sp.GetService(); var timeLimitedDataProtector = dataProtectionProvider .CreateProtector("down:face") .ToTimeLimitedDataProtector(); return data.GroupBy(x => new { x.Brand, x.DeviceUniqueNo, x.DeviceConnectionInfo, x.FuncStr }) .Select(x => { var b = x.Key.Brand; var d = x.Key.DeviceUniqueNo; var dc = x.Key.DeviceConnectionInfo ?? "{}"; var func = x.Key.FuncStr ?? "[]"; var jobj = new JObject { { "connectionInfo", JToken.Parse(dc) }, { "function", JArray.Parse(func) } }; return new DeviceTaskToDeviceByDeviceResponse { Brand = b, DeviceUniqueNo = d, DeviceInfo = jobj, Tasks = x.AsEnumerable().Select(item => { var key = $"face:{item.UserCode}"; var token = item.Type == DeviceTaskEnumType.Delete ? string.Empty : timeLimitedDataProtector.Protect(key, TimeSpan.FromSeconds(faceUrlOptions.Limit)); var dto = new TaskCombineDto { TaskId = item.TaskId, Type = item.Type, }; switch (dto.Type) { case DeviceTaskEnumType.Add: case DeviceTaskEnumType.Delete: case DeviceTaskEnumType.Update: { dto.Info = new UserToDeviceResponse { UserId = item.UserId.Value, UserNo = item.UserNo, UserCode = item.UserCode, UserName = item.UserName, DepartmentNo = item.DepartmentNo, DepartmentName = item.DepartmentName, CardNo = item.Type == DeviceTaskEnumType.Delete ? string.Empty : item.CardNo, Face = item.Type == DeviceTaskEnumType.Delete ? string.Empty : faceUrlOptions.Url + token,//下发才附带照片信息 ValidDatePeriod = new DatePeriod { Start = DateOnly.FromDateTime(DateTime.Now.AddDays(-1)), End = DateOnly.FromDateTime(DateTime.Now.AddYears(20)), }, TimePeriods = new List { 1 }, Doors = new List { 1 }, Schedule = 1 }; } break; case DeviceTaskEnumType.TimePeriodAdd: case DeviceTaskEnumType.TimePeriodDelete: { dto.Info = new TimePeriodToDeviceResponse { Id = 1, Name = "小白领", Enable = true, Sections = new List() { new() { DayOfWeek= DayOfWeek.Monday, TimePeriods = new List() { new() { Start = TimeOnly.Parse("08:00:00"), End = TimeOnly.Parse("17:30:00"), }, } } } }; } break; case DeviceTaskEnumType.ScheduleAdd: case DeviceTaskEnumType.ScheduleDelete: { dto.Info = new ScheduleToDeviceResponse { Id = 1, Enable = true, Name = "计划的一部分", Schedules = new List { new() { Name = "国庆节", DateTimePeriod = new DateTimePeriod { Start = new DateTime(2024,10,1), End = new DateTime(2024,10,7) } } } }; } break; case DeviceTaskEnumType.UploadRecord: { dto.Info = new UploadRecordToDeviceResponse { DateTimePeriod = new DateTimePeriod { Start = deviceUploadInfos[d] ?? DateTime.Now, End = DateTime.Now.AddDays(1) } }; } break; default: break; } return dto; }) .ToList() }; }).ToList(); }); } /// /// 获取新token /// /// /// /// public async Task RenewFaceUrlAsync(long taskId) { var task = await _dbContext.Queryable().Where(x => x.Id == taskId).FirstAsync(); task.ThrowIfNull("该任务不存在"); (task.Status != DeviceTaskStatusEnumType.Process).ThrowIfTrue($"设备任务下发状态需为{DeviceTaskStatusEnumType.Process.GetDescription()}"); var faceUrlOptions = App.GetServiceProvider(true).GetService>().Value; var dataProtectionProvider = App.GetServiceProvider(true).GetService(); var timeLimitedDataProtector = dataProtectionProvider .CreateProtector("down:face") .ToTimeLimitedDataProtector(); var userCode = await _dbContext.Queryable().Where(x => x.No == task.UserNo).Select(x => x.Code).SingleAsync(); var key = $"face:{userCode}"; var token = timeLimitedDataProtector.Protect(key, TimeSpan.FromSeconds(faceUrlOptions.Limit)); return faceUrlOptions.Url + token; } public async Task UpdateDeviceTaskStatusAsync(List requests) { var taskIds = requests.Select(x => x.TaskId).ToList(); var deviceTasks = await _dbContext.Queryable() .Where(x => taskIds.Contains(x.Id)) .Where(x => x.Status == DeviceTaskStatusEnumType.Process) .ToListAsync(); var now = DateTime.Now; deviceTasks.ForEach(task => { var callbackMessage = requests.First(x => x.TaskId == task.Id); task.Status = callbackMessage.IsSuccess ? DeviceTaskStatusEnumType.Success : DeviceTaskStatusEnumType.Error; task.ResultMessage = callbackMessage.ResultMessage; task.ModifiedTime = now; }); await _dbContext.UpdateAsync(deviceTasks); } //*********************************************************** help methods ********************************************************************* /// /// 并发处理 /// /// /// /// /// /// private async Task ContinueUpdateAsync(Func> func, int retry) { for (int i = 0; i < retry; i++) { try { return await func(); } catch (Exception ex) when (ex is VersionExceptions) { _logger.LogWarning(ex, "并发更新失败:{message},", ex.Message); } catch (Exception) { throw; } } throw new UserFriendlyException("操作失败,请重试"); } }