244 lines
7.5 KiB
C#
244 lines
7.5 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 工号规则:IDE + 至少 3 位数字,如 IDE001。
|
||
/// </summary>
|
||
private static readonly Regex EmployeeNoPattern = new(@"^IDE(\d{3,})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||
|
||
public async Task<UserProfileData?> 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<UserProfileRow>(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<Dictionary<Guid, UserProfileData>> GetManyAsync(
|
||
IReadOnlyCollection<Guid> 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<UserProfileListRow>(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<bool> 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<long>(sql, normalized, excludeUserId.Value.ToString())
|
||
.FirstAsync(cancellationToken)
|
||
: await dbContext.Database.SqlQueryRaw<long>(sql, normalized).FirstAsync(cancellationToken);
|
||
|
||
return count > 0;
|
||
}
|
||
|
||
public async Task<string> 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<EmployeeNoRow>(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; }
|
||
}
|
||
}
|