using Evergreen.Health.Application.Health.Entities; using SqlSugar; namespace Evergreen.Health.Application.Health.Integrations.Ib43; // 仅由本地维护程序调用;不是 Controller,也不注册 HTTP 路由。 public sealed class Ib43LocalBinding(ISqlSugarClient db, TimeProvider clock) { public async Task Run(string phone, Ib43Options options, string actor, bool apply) { var device = options.Devices.SingleOrDefault(d => d.ProductKey == "evergreen-test" && d.SerialNumber == "IB43SIM001"); if (device?.Simulation != true) throw new InvalidOperationException("模拟设备白名单不匹配,已停止。"); var users = await db.Queryable().Where(u => u.Phone == phone && !u.IsDelete && u.Status == HealthUserStatus.Enabled).Select(u => new { u.Id }).ToListAsync(); if (users.Count != 1) throw new InvalidOperationException("未找到唯一且已启用的健康账号,已停止。"); var key = Ib43DataRepository.DeviceKey(device.ProductKey, device.SerialNumber); var slot = key + ".left"; var existing = await db.Queryable().Where(b => b.ActiveSlot == slot).FirstAsync(); if (existing != null) { if (existing.UserId != users[0].Id || !existing.Simulation || existing.EndedAtMs != null) throw new InvalidOperationException("左侧已被其他用户或非模拟绑定占用,未覆盖或解绑。"); return existing; } var binding = new HealthIb43Binding { DeviceKey = key, ProductKey = device.ProductKey, SerialNumber = device.SerialNumber, Side = "left", ActiveSlot = slot, UserId = users[0].Id, BedName = "IB43模拟床垫左侧", Simulation = true, StartedAtMs = clock.GetUtcNow().ToUnixTimeMilliseconds(), AssignedBy = 0 }; if (!apply) return binding; if (string.IsNullOrWhiteSpace(actor) || actor.Length > 200) throw new InvalidOperationException("维护操作者信息不合法。"); var stage = "binding"; await db.Ado.BeginTranAsync(); try { await db.Insertable(binding).ExecuteCommandAsync(); stage = "audit"; await db.Insertable(new HealthIb43MaintenanceAudit { Id = Guid.NewGuid().ToString("N"), BindingId = binding.Id, UserId = binding.UserId, Actor = actor, OccurredAtMs = binding.StartedAtMs, Operation = "local-bind-simulator-left" }).ExecuteCommandAsync(); stage = "commit"; await db.Ado.CommitTranAsync(); } catch (Exception error) { await db.Ado.RollbackTranAsync(); var code = error is MySqlConnector.MySqlException mysql ? $" MySQL {mysql.Number}" : ""; throw new InvalidOperationException($"绑定事务失败 ({stage}, {error.GetType().Name}{code}),请重新预检确认数据库状态。", error); } return binding; } }