using System.Text.RegularExpressions; using Fake.DependencyInjection; using Fake.Rbac.Infrastructure; using Fake.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Oms.Application.Dtos.Rbac; namespace Oms.Application.Rbac; public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDependency { /// /// 工号规则:IDE + 至少 3 位数字,如 IDE001。 /// private static readonly Regex EmployeeNoPattern = new(@"^IDE(\d{3,})$", RegexOptions.IgnoreCase | RegexOptions.Compiled); public async Task GetAsync(Guid userId, CancellationToken cancellationToken = default) { var sql = $@" SELECT u.`Phone` AS `Phone`, u.`EmployeeNo` AS `EmployeeNo`, u.`WechatId` AS `WechatId`, u.`WechatQrCode` AS `WechatQrCode`, u.`WecomKfUrl` AS `WecomKfUrl` FROM `{FakeTables.User}` u WHERE u.`Id` = {{0}} AND u.`IsDeleted` = 0 LIMIT 1 "; var row = await dbContext.Database .SqlQueryRaw(sql, userId.ToString()) .FirstOrDefaultAsync(cancellationToken); if (row == null) { return null; } return new UserProfileData { Phone = row.Phone, EmployeeNo = row.EmployeeNo, WechatId = row.WechatId, WechatQrCode = row.WechatQrCode, WecomKfUrl = row.WecomKfUrl, }; } public async Task> GetManyAsync( IReadOnlyCollection userIds, CancellationToken cancellationToken = default) { if (userIds.Count == 0) { return []; } var inClause = string.Join(",", userIds.Select(x => $"'{x}'")); var sql = $@" SELECT u.`Id` AS `UserId`, u.`Phone` AS `Phone`, u.`EmployeeNo` AS `EmployeeNo`, u.`WechatId` AS `WechatId`, u.`WechatQrCode` AS `WechatQrCode`, u.`WecomKfUrl` AS `WecomKfUrl` FROM `{FakeTables.User}` u WHERE u.`Id` IN ({inClause}) AND u.`IsDeleted` = 0 "; var rows = await dbContext.Database.SqlQueryRaw(sql).ToListAsync(cancellationToken); return rows.ToDictionary( x => x.UserId, x => new UserProfileData { Phone = x.Phone, EmployeeNo = x.EmployeeNo, WechatId = x.WechatId, WechatQrCode = x.WechatQrCode, WecomKfUrl = x.WecomKfUrl, }); } public async Task SaveAsync(Guid userId, UserProfileData profile, CancellationToken cancellationToken = default) { var sql = $@" UPDATE `{FakeTables.User}` SET `Phone` = {{1}}, `EmployeeNo` = {{2}}, `WechatId` = {{3}}, `WechatQrCode` = {{4}}, `WecomKfUrl` = {{5}} WHERE `Id` = {{0}} AND `IsDeleted` = 0 "; await dbContext.Database.ExecuteSqlRawAsync( sql, [ userId.ToString(), // EF Core 参数不接受 DBNull,空值直接传 null 即可映射为 SQL NULL (object?)NormalizeOptional(profile.Phone), (object?)NormalizeOptional(profile.EmployeeNo), (object?)NormalizeOptional(profile.WechatId), (object?)NormalizeOptional(profile.WechatQrCode), (object?)NormalizeOptional(profile.WecomKfUrl), ], cancellationToken); } public async Task UpdateContactAsync( Guid userId, UserProfileData profile, CancellationToken cancellationToken = default) { var sql = $@" UPDATE `{FakeTables.User}` SET `Phone` = {{1}}, `WechatId` = {{2}}, `WechatQrCode` = {{3}}, `WecomKfUrl` = {{4}} WHERE `Id` = {{0}} AND `IsDeleted` = 0 "; await dbContext.Database.ExecuteSqlRawAsync( sql, [ userId.ToString(), (object?)NormalizeOptional(profile.Phone), (object?)NormalizeOptional(profile.WechatId), (object?)NormalizeOptional(profile.WechatQrCode), (object?)NormalizeOptional(profile.WecomKfUrl), ], cancellationToken); } public async Task IsEmployeeNoTakenAsync( string employeeNo, Guid? excludeUserId, CancellationToken cancellationToken = default) { var normalized = employeeNo.Trim().ToUpperInvariant(); var sql = excludeUserId.HasValue ? $@" SELECT COUNT(*) AS `Value` FROM `{FakeTables.User}` u WHERE u.`IsDeleted` = 0 AND u.`EmployeeNo` = {{0}} AND u.`Id` <> {{1}} " : $@" SELECT COUNT(*) AS `Value` FROM `{FakeTables.User}` u WHERE u.`IsDeleted` = 0 AND u.`EmployeeNo` = {{0}} "; var count = excludeUserId.HasValue ? await dbContext.Database.SqlQueryRaw(sql, normalized, excludeUserId.Value.ToString()) .FirstAsync(cancellationToken) : await dbContext.Database.SqlQueryRaw(sql, normalized).FirstAsync(cancellationToken); return count > 0; } public async Task GenerateNextEmployeeNoAsync(CancellationToken cancellationToken = default) { var sql = $@" SELECT u.`EmployeeNo` AS `EmployeeNo` FROM `{FakeTables.User}` u WHERE u.`IsDeleted` = 0 AND u.`EmployeeNo` IS NOT NULL AND u.`EmployeeNo` <> '' "; var rows = await dbContext.Database.SqlQueryRaw(sql).ToListAsync(cancellationToken); var max = 0; foreach (var row in rows) { var value = row.EmployeeNo?.Trim() ?? string.Empty; var match = EmployeeNoPattern.Match(value); if (!match.Success) { continue; } if (int.TryParse(match.Groups[1].Value, out var number)) { max = Math.Max(max, number); } } var next = max + 1; return $"IDE{next:D3}"; } public static bool IsValidEmployeeNo(string employeeNo) => EmployeeNoPattern.IsMatch(employeeNo.Trim()); private static string? NormalizeOptional(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } return value.Trim(); } private sealed class UserProfileRow { public string? Phone { get; set; } public string? EmployeeNo { get; set; } public string? WechatId { get; set; } public string? WechatQrCode { get; set; } public string? WecomKfUrl { get; set; } } private sealed class UserProfileListRow { public Guid UserId { get; set; } public string? Phone { get; set; } public string? EmployeeNo { get; set; } public string? WechatId { get; set; } public string? WechatQrCode { get; set; } public string? WecomKfUrl { get; set; } } private sealed class EmployeeNoRow { public string? EmployeeNo { get; set; } } }