feat: resolve createBy/updateBy to RBAC user display names

Add OmsRbacUserDisplayNameResolver and use it in customer, order, warehouse, and channel APIs so audit fields return names instead of mapIds.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-06-29 19:07:17 +08:00
parent e875f0c012
commit 07fc7fffe7
10 changed files with 147 additions and 17 deletions

View File

@ -102,8 +102,18 @@ public class CustomerPaginatedOutput
/// </summary> /// </summary>
public DateTime CreateTime { get; set; } public DateTime CreateTime { get; set; }
/// <summary>
/// 创建人(展示名称)。
/// </summary>
public string? CreateBy { get; set; }
/// <summary> /// <summary>
/// 更新时间。 /// 更新时间。
/// </summary> /// </summary>
public DateTime? UpdateTime { get; set; } public DateTime? UpdateTime { get; set; }
/// <summary>
/// 更新人(展示名称)。
/// </summary>
public string? UpdateBy { get; set; }
} }

View File

@ -74,6 +74,9 @@ public partial class SalesProductService
.GroupBy(x => x.PriceId) .GroupBy(x => x.PriceId)
.ToDictionary(x => x.Key, x => x.OrderBy(item => item.Id).ToList()); .ToDictionary(x => x.Key, x => x.OrderBy(item => item.Id).ToList());
var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
prices.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
return prices.Select(price => new SalesProductPriceListOutput return prices.Select(price => new SalesProductPriceListOutput
{ {
Id = price.Id, Id = price.Id,
@ -88,9 +91,9 @@ public partial class SalesProductService
Factors = factorMap.TryGetValue(price.Id, out var factors) ? factors.Select(MapPriceFactorOutput).ToList() : [], Factors = factorMap.TryGetValue(price.Id, out var factors) ? factors.Select(MapPriceFactorOutput).ToList() : [],
Details = detailMap.TryGetValue(price.Id, out var details) ? details.Select(MapPriceDetailOutput).ToList() : [], Details = detailMap.TryGetValue(price.Id, out var details) ? details.Select(MapPriceDetailOutput).ToList() : [],
CreateTime = price.CreateTime, CreateTime = price.CreateTime,
CreateBy = price.CreateUserId <= 0 ? null : price.CreateUserId.ToString(), CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(price.CreateUserId, auditUserNameMap),
UpdateTime = OmsDocHelper.NullIfDefault(price.UpdateTime), UpdateTime = OmsDocHelper.NullIfDefault(price.UpdateTime),
UpdateBy = price.UpdateUserId <= 0 ? null : price.UpdateUserId.ToString() UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(price.UpdateUserId, auditUserNameMap)
}).ToList(); }).ToList();
} }
@ -342,7 +345,9 @@ public partial class SalesProductService
}; };
} }
private static SalesProductDetailOutput MapDetailOutput(SalesProduct entity) private static SalesProductDetailOutput MapDetailOutput(
SalesProduct entity,
IReadOnlyDictionary<int, string> auditUserNameMap)
{ {
return new SalesProductDetailOutput return new SalesProductDetailOutput
{ {
@ -363,9 +368,9 @@ public partial class SalesProductService
IsActive = (byte)entity.IsActive, IsActive = (byte)entity.IsActive,
Scheme = entity.Scheme, Scheme = entity.Scheme,
CreateTime = entity.CreateTime, CreateTime = entity.CreateTime,
CreateBy = entity.CreateUserId <= 0 ? null : entity.CreateUserId.ToString(), CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap),
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime), UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime),
UpdateBy = entity.UpdateUserId <= 0 ? null : entity.UpdateUserId.ToString() UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap)
}; };
} }

View File

@ -25,6 +25,8 @@ public partial class SalesProductService : ApplicationService
private IWeightRoundingRepository WeightRoundingRepository => LazyServiceProvider.GetRequiredService<IWeightRoundingRepository>(); private IWeightRoundingRepository WeightRoundingRepository => LazyServiceProvider.GetRequiredService<IWeightRoundingRepository>();
private AllocationProviderSnapshotEnricher AllocationProviderSnapshotEnricher => private AllocationProviderSnapshotEnricher AllocationProviderSnapshotEnricher =>
LazyServiceProvider.GetRequiredService<AllocationProviderSnapshotEnricher>(); LazyServiceProvider.GetRequiredService<AllocationProviderSnapshotEnricher>();
private OmsRbacUserDisplayNameResolver UserDisplayNameResolver =>
LazyServiceProvider.GetRequiredService<OmsRbacUserDisplayNameResolver>();
/// <summary> /// <summary>
/// 查询销售产品列表。 /// 查询销售产品列表。
@ -71,7 +73,9 @@ public partial class SalesProductService : ApplicationService
public async Task<SalesProductDetailOutput> GetDetailAsync([FromQuery] int id) public async Task<SalesProductDetailOutput> GetDetailAsync([FromQuery] int id)
{ {
var entity = await GetProductOrThrowAsync(id); var entity = await GetProductOrThrowAsync(id);
return MapDetailOutput(entity); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
[entity.CreateUserId, entity.UpdateUserId]);
return MapDetailOutput(entity, auditUserNameMap);
} }
/// <summary> /// <summary>

View File

@ -329,7 +329,10 @@ public partial class ServiceChannelService
}; };
} }
private static ServiceChannelDetailOutput MapDetailOutput(ServiceChannel entity, IReadOnlyDictionary<int, string> groupMap) private static ServiceChannelDetailOutput MapDetailOutput(
ServiceChannel entity,
IReadOnlyDictionary<int, string> groupMap,
IReadOnlyDictionary<int, string> auditUserNameMap)
{ {
return new ServiceChannelDetailOutput return new ServiceChannelDetailOutput
{ {
@ -346,9 +349,9 @@ public partial class ServiceChannelService
PreScript = entity.PreScript, PreScript = entity.PreScript,
IsActive = (byte)entity.IsActive, IsActive = (byte)entity.IsActive,
CreateTime = entity.CreateTime, CreateTime = entity.CreateTime,
CreateBy = entity.CreateUserId <= 0 ? null : entity.CreateUserId.ToString(), CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap),
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime), UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime),
UpdateBy = entity.UpdateUserId <= 0 ? null : entity.UpdateUserId.ToString() UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap)
}; };
} }

View File

@ -20,6 +20,8 @@ public partial class ServiceChannelService : ApplicationService
private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService<IServiceChannelRepository>(); private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService<IServiceChannelRepository>();
private IServiceChannelGroupRepository ServiceChannelGroupRepository => LazyServiceProvider.GetRequiredService<IServiceChannelGroupRepository>(); private IServiceChannelGroupRepository ServiceChannelGroupRepository => LazyServiceProvider.GetRequiredService<IServiceChannelGroupRepository>();
private OmsRbacUserDisplayNameResolver UserDisplayNameResolver =>
LazyServiceProvider.GetRequiredService<OmsRbacUserDisplayNameResolver>();
/// <summary> /// <summary>
/// 查询服务渠道列表。 /// 查询服务渠道列表。
@ -67,7 +69,9 @@ public partial class ServiceChannelService : ApplicationService
public async Task<ServiceChannelDetailOutput> GetDetailAsync([FromQuery] int id) public async Task<ServiceChannelDetailOutput> GetDetailAsync([FromQuery] int id)
{ {
var entity = await GetEntityOrThrowAsync(id); var entity = await GetEntityOrThrowAsync(id);
return MapDetailOutput(entity, await GetGroupMapAsync()); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
[entity.CreateUserId, entity.UpdateUserId]);
return MapDetailOutput(entity, await GetGroupMapAsync(), auditUserNameMap);
} }
/// <summary> /// <summary>

View File

@ -32,6 +32,9 @@ public class CustomerService : ApplicationService
private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService<FakeRbacDbContext>(); private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService<FakeRbacDbContext>();
private OmsRbacUserDisplayNameResolver UserDisplayNameResolver =>
LazyServiceProvider.GetRequiredService<OmsRbacUserDisplayNameResolver>();
/// <summary> /// <summary>
/// 查询客户列表。 /// 查询客户列表。
/// </summary> /// </summary>
@ -57,7 +60,16 @@ public class CustomerService : ApplicationService
new Dictionary<string, bool> { [nameof(Customer.UpdateTime)] = false, [nameof(Customer.Id)] = false }); new Dictionary<string, bool> { [nameof(Customer.UpdateTime)] = false, [nameof(Customer.Id)] = false });
var totalCount = await CustomerRepository.CountAsync(predicate); var totalCount = await CustomerRepository.CountAsync(predicate);
var items = entities.Select(entity => ObjectMapper.Map<Customer, CustomerPaginatedOutput>(entity)).ToList(); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
entities.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
var items = entities.Select(entity =>
{
var output = ObjectMapper.Map<Customer, CustomerPaginatedOutput>(entity);
output.CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap);
output.UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap);
output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime);
return output;
}).ToList();
return new OmsPageResult<CustomerPaginatedOutput> return new OmsPageResult<CustomerPaginatedOutput>
{ {
@ -82,8 +94,10 @@ public class CustomerService : ApplicationService
{ {
var entity = await GetCustomerOrThrowAsync(id); var entity = await GetCustomerOrThrowAsync(id);
var output = ObjectMapper.Map<Customer, CustomerOutput>(entity); var output = ObjectMapper.Map<Customer, CustomerOutput>(entity);
output.CreateBy = entity.CreateUserId <= 0 ? null : entity.CreateUserId.ToString(); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
output.UpdateBy = entity.UpdateUserId <= 0 ? null : entity.UpdateUserId.ToString(); [entity.CreateUserId, entity.UpdateUserId]);
output.CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap);
output.UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap);
output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime); output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime);
return output; return output;
} }

View File

@ -69,6 +69,9 @@ public partial class OrderService
: await CountryRepository.GetListAsync(x => countryCodes.Contains(x.CountryCode)); : await CountryRepository.GetListAsync(x => countryCodes.Contains(x.CountryCode));
var countryMap = countries.ToDictionary(x => x.CountryCode, StringComparer.OrdinalIgnoreCase); var countryMap = countries.ToDictionary(x => x.CountryCode, StringComparer.OrdinalIgnoreCase);
var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
orders.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
return orders.Select(order => return orders.Select(order =>
{ {
senderMap.TryGetValue(order.Id, out var sender); senderMap.TryGetValue(order.Id, out var sender);
@ -127,9 +130,9 @@ public partial class OrderService
IsIntercept = order.IsIntercept > 0, IsIntercept = order.IsIntercept > 0,
IsRemote = order.IsRemote > 0, IsRemote = order.IsRemote > 0,
CreateTime = order.CreateTime, CreateTime = order.CreateTime,
CreateBy = order.CreateUserId <= 0 ? null : order.CreateUserId.ToString(), CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.CreateUserId, auditUserNameMap),
UpdateTime = OmsDocHelper.NullIfDefault(order.UpdateTime), UpdateTime = OmsDocHelper.NullIfDefault(order.UpdateTime),
UpdateBy = order.UpdateUserId <= 0 ? null : order.UpdateUserId.ToString(), UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.UpdateUserId, auditUserNameMap),
Sender = MapSender(sender), Sender = MapSender(sender),
Receiver = MapReceiver(recipient), Receiver = MapReceiver(recipient),
Items = (items ?? []).Select(MapDeclaration).ToList(), Items = (items ?? []).Select(MapDeclaration).ToList(),

View File

@ -28,6 +28,8 @@ public partial class OrderService : ApplicationService
private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService<IWarehouseAddressRepository>(); private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService<IWarehouseAddressRepository>();
private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService<ISalesProductRepository>(); private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService<ISalesProductRepository>();
private IWebHostEnvironment WebHostEnvironment => LazyServiceProvider.GetRequiredService<IWebHostEnvironment>(); private IWebHostEnvironment WebHostEnvironment => LazyServiceProvider.GetRequiredService<IWebHostEnvironment>();
private OmsRbacUserDisplayNameResolver UserDisplayNameResolver =>
LazyServiceProvider.GetRequiredService<OmsRbacUserDisplayNameResolver>();
/// <summary> /// <summary>
/// 查询订单列表。 /// 查询订单列表。

View File

@ -18,6 +18,9 @@ public class WarehouseService : ApplicationService
{ {
private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService<IWarehouseAddressRepository>(); private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService<IWarehouseAddressRepository>();
private OmsRbacUserDisplayNameResolver UserDisplayNameResolver =>
LazyServiceProvider.GetRequiredService<OmsRbacUserDisplayNameResolver>();
/// <summary> /// <summary>
/// 查询仓库列表。 /// 查询仓库列表。
/// </summary> /// </summary>
@ -74,8 +77,10 @@ public class WarehouseService : ApplicationService
{ {
var entity = await GetWarehouseOrThrowAsync(id); var entity = await GetWarehouseOrThrowAsync(id);
var output = ObjectMapper.Map<WarehouseAddress, WarehouseOutput>(entity); var output = ObjectMapper.Map<WarehouseAddress, WarehouseOutput>(entity);
output.CreateBy = entity.CreateUserId <= 0 ? null : entity.CreateUserId.ToString(); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
output.UpdateBy = entity.UpdateUserId <= 0 ? null : entity.UpdateUserId.ToString(); [entity.CreateUserId, entity.UpdateUserId]);
output.CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap);
output.UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap);
output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime); output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime);
return output; return output;
} }

View File

@ -0,0 +1,80 @@
using Fake.DependencyInjection;
using Fake.Rbac.Infrastructure;
using Microsoft.EntityFrameworkCore;
namespace Oms.Application.Shared;
/// <summary>
/// 将 OMS 审计字段中的 RBAC mapId(user.userid)解析为展示名称。
/// </summary>
public class OmsRbacUserDisplayNameResolver : IScopedDependency
{
private readonly FakeRbacDbContext _rbacDbContext;
public OmsRbacUserDisplayNameResolver(FakeRbacDbContext rbacDbContext)
{
_rbacDbContext = rbacDbContext;
}
public async Task<string?> ResolveAsync(int userId, CancellationToken cancellationToken = default)
{
if (userId <= 0)
{
return null;
}
var map = await ResolveManyAsync([userId], cancellationToken);
return FormatUserId(userId, map);
}
public async Task<IReadOnlyDictionary<int, string>> ResolveManyAsync(
IEnumerable<int> userIds,
CancellationToken cancellationToken = default)
{
var ids = userIds.Where(x => x > 0).Distinct().ToList();
if (ids.Count == 0)
{
return new Dictionary<int, string>();
}
var sql =
$"SELECT `userid` AS `{nameof(RbacUserNameRow.UserId)}`, `Name` AS `{nameof(RbacUserNameRow.DisplayName)}` FROM `user` WHERE `IsDeleted` = 0 AND `userid` IN ({string.Join(",", ids)})";
var rows = await _rbacDbContext.Database
.SqlQueryRaw<RbacUserNameRow>(sql)
.ToListAsync(cancellationToken);
return rows.ToDictionary(
x => x.UserId,
x => NormalizeDisplayName(x.DisplayName, x.UserId));
}
public static string? FormatUserId(int userId, IReadOnlyDictionary<int, string>? nameMap)
{
if (userId <= 0)
{
return null;
}
if (nameMap != null &&
nameMap.TryGetValue(userId, out var name) &&
!string.IsNullOrWhiteSpace(name))
{
return name;
}
return userId.ToString();
}
private static string NormalizeDisplayName(string? name, int userId)
{
var trimmed = name?.Trim();
return string.IsNullOrWhiteSpace(trimmed) ? userId.ToString() : trimmed;
}
private sealed class RbacUserNameRow
{
public int UserId { get; set; }
public string? DisplayName { get; set; }
}
}