using System; using System.Linq; using System.Reflection; using System.Threading.Tasks; using NCC.Common.Enum; using NCC.Common.Core.Manager; using NCC.Common.Filter; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.lq_user_profile_version; using NCC.Extend.Interfaces.UserProfileVersion; using NCC.Extend.Models; using NCC.FriendlyException; using NCC.System.Entitys.Permission; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using SqlSugar; namespace NCC.Extend { /// /// 用户主档时点全量版本查询与还原(R-004)。 /// [ApiDescriptionSettings(Tag = "绿纤用户主档版本", Name = "LqUserProfileVersion", Order = 201)] [Route("api/Extend/LqUserProfileVersion")] public class LqUserProfileVersionQueryService : IDynamicApiController, ITransient { private readonly ISqlSugarClient _db; private readonly IUserManager _userManager; private readonly IUserProfileVersionAppender _userProfileVersionAppender; public LqUserProfileVersionQueryService( ISqlSugarClient db, IUserManager userManager, IUserProfileVersionAppender userProfileVersionAppender) { _db = db; _userManager = userManager; _userProfileVersionAppender = userProfileVersionAppender; } /// /// 按用户分页查询全量主档版本(新到旧);权限与字段审计列表一致。 /// [HttpGet("logs")] public async Task GetVersionLogs([FromQuery] string targetUserId, [FromQuery] PageInputBase page) { if (string.IsNullOrWhiteSpace(targetUserId)) { throw NCCException.Oh("targetUserId 不能为空"); } page ??= new PageInputBase(); var userInfo = await _userManager.GetUserInfo(); var target = await _db.Queryable() .Where(x => x.Id == targetUserId && x.DeleteMark == null) .FirstAsync(); if (target == null) { throw NCCException.Oh("目标用户不存在"); } if (!userInfo.isAdministrator && !userInfo.dataScope.Any(it => it.organizeId == target.OrganizeId && it.Edit == true)) { throw NCCException.Oh(ErrorCode.D1013); } var q = _db.Queryable() .Where(x => x.TargetUserId == targetUserId) .OrderBy(x => x.VersionNo, OrderByType.Desc); var data = await q.ToPagedListAsync(page.currentPage, page.pageSize); return PageResult.SqlSugarPageResult(data); } /// /// 将 BASE_USER 还原为指定版本快照(密码/密钥保留当前库内值);成功后追加新版本记录。 /// [HttpPost("restore")] public async Task RestoreToVersion([FromBody] LqUserProfileVersionRestoreInput input) { if (string.IsNullOrWhiteSpace(input?.VersionId)) { throw NCCException.Oh("versionId 不能为空"); } var userInfo = await _userManager.GetUserInfo(); var verRows = await _db.Queryable() .Where(x => x.Id == input.VersionId) .Take(1) .ToListAsync(); var ver = verRows.FirstOrDefault(); if (ver == null) { throw NCCException.Oh("版本不存在"); } LqUserProfileFullSnapshotDto dto; try { dto = JsonConvert.DeserializeObject(ver.FullSnapshotJson); } catch { throw NCCException.Oh("快照 JSON 解析失败"); } if (dto == null || dto.SchemaVersion < LqUserProfileSnapshotConstants.FullUserSchemaVersion || dto.User == null) { throw NCCException.Oh("该版本快照格式过旧,无法一键还原,请人工对照 JSON 修改主档"); } var currentRows = await _db.Queryable() .Where(x => x.Id == ver.TargetUserId) .Take(1) .ToListAsync(); var current = currentRows.FirstOrDefault(); if (current == null) { throw NCCException.Oh("用户不存在"); } if (!userInfo.isAdministrator && !userInfo.dataScope.Any(it => it.organizeId == current.OrganizeId && it.Edit == true)) { throw NCCException.Oh(ErrorCode.D1013); } CopyUserSnapshotOntoRow(current, dto.User); try { _db.Ado.BeginTran(); var ok = await _db.Updateable(current).ExecuteCommandAsync(); if (ok <= 0) { throw NCCException.Oh("还原写入失败"); } await _userProfileVersionAppender.AfterUserProfilePersistedAsync(ver.TargetUserId, "RESTORE_FROM_VERSION", true); _db.Ado.CommitTran(); } catch { _db.Ado.RollbackTran(); throw; } } /// /// 将快照中的业务字段覆盖到当前行,保留 Id、Password、Secretkey。 /// internal static void CopyUserSnapshotOntoRow(UserEntity target, UserEntity source) { if (target == null || source == null) { return; } foreach (var prop in typeof(UserEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!prop.CanRead || !prop.CanWrite) { continue; } var n = prop.Name; if (string.Equals(n, nameof(UserEntity.Id), StringComparison.OrdinalIgnoreCase) || string.Equals(n, nameof(UserEntity.Password), StringComparison.OrdinalIgnoreCase) || string.Equals(n, nameof(UserEntity.Secretkey), StringComparison.OrdinalIgnoreCase)) { continue; } prop.SetValue(target, prop.GetValue(source)); } } } }