admin/src/Oms.Application/Rbac/UserProfileRepository.cs
zzh e85dfb80b5
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
feat: 仓配开放 API 与后台用户启停
新增 warehouseOpenApi(Token/入库/件重尺/红外图),并在 fk_user 支持 IsEnabled 禁用登录。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 17:53:00 +08:00

295 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.RegularExpressions;
using Fake.Application;
using Fake.DependencyInjection;
using Fake.EntityFrameworkCore;
using Fake.Rbac.Infrastructure;
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`,
COALESCE(u.`IsEnabled`, 1) AS `IsEnabled`
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,
IsEnabled = row.IsEnabled != 0,
};
}
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`,
COALESCE(u.`IsEnabled`, 1) AS `IsEnabled`
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,
IsEnabled = x.IsEnabled != 0,
});
}
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}},
`IsEnabled` = {{6}}
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),
profile.IsEnabled ? 1 : 0,
],
cancellationToken);
}
public async Task SetEnabledAsync(Guid userId, bool isEnabled, CancellationToken cancellationToken = default)
{
var sql = $@"
UPDATE `{FakeTables.User}`
SET `IsEnabled` = {{1}}
WHERE `Id` = {{0}} AND `IsDeleted` = 0
";
var affected = await dbContext.Database.ExecuteSqlRawAsync(
sql,
[userId.ToString(), isEnabled ? 1 : 0],
cancellationToken);
if (affected <= 0)
{
throw new BusinessException("用户不存在");
}
}
public async Task EnsureEnabledOrThrowAsync(Guid userId, CancellationToken cancellationToken = default)
{
var sql = $@"
SELECT COALESCE(u.`IsEnabled`, 1) AS `Value`
FROM `{FakeTables.User}` u
WHERE u.`Id` = {{0}} AND u.`IsDeleted` = 0
LIMIT 1
";
var rows = await dbContext.Database
.SqlQueryRaw<long>(sql, userId.ToString())
.ToListAsync(cancellationToken);
if (rows.Count == 0)
{
throw new BusinessException("用户不存在");
}
if (rows[0] == 0)
{
throw new BusinessException("账号已禁用");
}
}
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; }
public int IsEnabled { get; set; } = 1;
}
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; }
public int IsEnabled { get; set; } = 1;
}
private sealed class EmployeeNoRow
{
public string? EmployeeNo { get; set; }
}
}