admin/src/Oms.Application/Services/Rbac/OmsUserService.cs
zzh 6852135774
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
feat: 仓库地址排序备注与用户注册错误提示
仓库地址增加排序/备注字段与迁移;用户创建时对邮箱冲突等给出明确中文提示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-09 14:12:36 +08:00

395 lines
14 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 Fake.Application;
using Fake.Domain.Exceptions;
using Fake.Rbac.Application.Dtos.Common;
using Fake.Rbac.Application.Dtos.Role;
using Fake.Rbac.Application.Dtos.User;
using Fake.Rbac.Application.Services;
using Fake.Rbac.Domain.Managers;
using Fake.Rbac.Infrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Fake.ObjectStorage;
using Oms.Application.Dtos.Rbac;
using Oms.Application.Rbac;
using Oms.Application.Shared;
using Oms.Application.Storage;
using Oms.Domain.SystemAggregate.Entities;
using Oms.Domain.SystemAggregate.Enums;
using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.Rbac;
/// <summary>
/// 接管 RBAC 用户接口,扩展电话、工号、微信号、微信二维码字段。
/// </summary>
[Authorize]
[ApiExplorerSettings(GroupName = "RBAC-User")]
public class OmsUserService(
UserService userService,
AccountManager accountManager,
UserProfileRepository profileRepository,
IEmployeeRepository employeeRepository,
FakeRbacDbContext rbacDbContext,
IObjectStorage fileStorage) : ApplicationService
{
/// <summary>
/// 获取用户详情(含扩展资料)。
/// </summary>
[HttpGet("/rbac/user/get")]
public async Task<OmsUserDetailDto> GetAsync([FromQuery] Guid id, CancellationToken cancellationToken = default)
{
var user = await userService.GetAsync(id, cancellationToken);
var profile = await profileRepository.GetAsync(id, cancellationToken);
return MapUserDetail(user, profile);
}
/// <summary>
/// 分页查询用户列表(含扩展资料)。
/// </summary>
[HttpGet("/rbac/user/get-list")]
public async Task<PagedResultDto<OmsUserDetailDto>> GetListAsync(
[FromQuery] UserPagedRequestDto input,
CancellationToken cancellationToken = default)
{
var result = await userService.GetListAsync(input, cancellationToken);
if (result.Items.Count == 0)
{
return new PagedResultDto<OmsUserDetailDto>
{
Total = result.Total,
Items = [],
};
}
var userIds = result.Items
.Select(x => x.Id)
.Where(x => x != Guid.Empty)
.Distinct()
.ToList();
var profileMap = await profileRepository.GetManyAsync(userIds, cancellationToken);
return new PagedResultDto<OmsUserDetailDto>
{
Total = result.Total,
Items = result.Items
.Select(user =>
{
profileMap.TryGetValue(user.Id, out var profile);
return MapUserDetail(user, profile);
})
.ToList(),
};
}
/// <summary>
/// 按角色查询用户简要列表。
/// </summary>
[HttpGet("/rbac/user/get-users-by-role")]
public Task<List<UserSimpleDto>> GetUsersByRoleAsync(
[FromQuery] Guid roleId,
CancellationToken cancellationToken = default) =>
userService.GetUsersByRoleAsync(roleId, cancellationToken);
/// <summary>
/// 创建用户并保存扩展资料。
/// </summary>
[HttpPost("/rbac/user/create")]
public async Task<OmsUserDetailDto> CreateAsync(
[FromBody] OmsUserCreateInput input,
CancellationToken cancellationToken = default)
{
var account = input.Account?.Trim() ?? string.Empty;
OmsRbacRegistrationGuard.ValidateAdminUserFields(
input.Name,
account,
input.Password,
input.Email,
input.Phone,
input.WechatId,
requirePassword: true);
var employeeNo = await ResolveEmployeeNoForCreateAsync(input.EmployeeNo, cancellationToken);
await OmsRbacRegistrationGuard.EnsureLoginIdentityAvailableAsync(
rbacDbContext,
account,
input.Email,
forAdminUserCreate: true,
excludeUserId: null,
cancellationToken);
await OmsRbacRegistrationGuard.EnsurePhoneAvailableAsync(
rbacDbContext,
input.Phone,
excludeUserId: null,
cancellationToken);
await OmsRbacRegistrationGuard.EnsureWechatIdAvailableAsync(
rbacDbContext,
input.WechatId,
excludeUserId: null,
cancellationToken);
await OmsRbacRegistrationGuard.EnsureOrganizationExistsAsync(
rbacDbContext,
input.OrganizationId,
cancellationToken);
await OmsRbacRegistrationGuard.EnsureRolesExistAsync(
rbacDbContext,
input.RoleIds,
cancellationToken);
try
{
var user = await userService.CreateAsync(new UserCreateDto
{
Name = input.Name!.Trim(),
Account = account,
Password = input.Password,
// 禁止 Email='':uk_user_email 会把空串当成占用
Email = string.IsNullOrWhiteSpace(input.Email) ? null : input.Email.Trim(),
Avatar = OmsObjectStorageExtensions.ToStorageKey(input.Avatar),
OrganizationId = input.OrganizationId,
RoleIds = input.RoleIds,
}, cancellationToken);
await profileRepository.SaveAsync(
user.Id,
new UserProfileData
{
Phone = input.Phone,
EmployeeNo = employeeNo,
WechatId = input.WechatId,
WechatQrCode = input.WechatQrCode,
WecomKfUrl = input.WecomKfUrl,
},
cancellationToken);
await EnsureEmployeeProfileAsync(user.Id, input, cancellationToken);
var profile = await profileRepository.GetAsync(user.Id, cancellationToken);
return MapUserDetail(user, profile);
}
catch (Exception ex) when (ex is not BusinessException)
{
throw OmsRbacRegistrationGuard.ToSaveBusinessException(ex, forAdminUserCreate: true);
}
}
/// <summary>
/// 更新用户及扩展资料。
/// </summary>
[HttpPut("/rbac/user/update")]
public async Task<OmsUserDetailDto> UpdateAsync(
[FromQuery] Guid id,
[FromBody] OmsUserUpdateInput input,
CancellationToken cancellationToken = default)
{
if (id == Guid.Empty)
{
throw new BusinessException("用户 Id 无效");
}
// 更新不改登录账号/密码
OmsRbacRegistrationGuard.ValidateAdminUserFields(
input.Name,
account: "placeholder",
password: null,
input.Email,
input.Phone,
input.WechatId,
requirePassword: false);
var email = string.IsNullOrWhiteSpace(input.Email) ? null : input.Email.Trim();
await OmsRbacRegistrationGuard.EnsureEmailAvailableAsync(
rbacDbContext,
email,
excludeUserId: id,
forAdminUserCreate: true,
cancellationToken);
await OmsRbacRegistrationGuard.EnsurePhoneAvailableAsync(
rbacDbContext,
input.Phone,
excludeUserId: id,
cancellationToken);
await OmsRbacRegistrationGuard.EnsureWechatIdAvailableAsync(
rbacDbContext,
input.WechatId,
excludeUserId: id,
cancellationToken);
await OmsRbacRegistrationGuard.EnsureOrganizationExistsAsync(
rbacDbContext,
input.OrganizationId,
cancellationToken);
try
{
var user = await userService.UpdateAsync(
id,
new UserUpdateDto
{
Name = input.Name!.Trim(),
Email = email,
Avatar = OmsObjectStorageExtensions.ToStorageKey(input.Avatar),
OrganizationId = input.OrganizationId,
},
cancellationToken);
await profileRepository.UpdateContactAsync(
id,
new UserProfileData
{
Phone = input.Phone,
WechatId = input.WechatId,
WechatQrCode = input.WechatQrCode,
WecomKfUrl = input.WecomKfUrl,
},
cancellationToken);
var profile = await profileRepository.GetAsync(id, cancellationToken);
return MapUserDetail(user, profile);
}
catch (Exception ex) when (ex is not BusinessException)
{
throw OmsRbacRegistrationGuard.ToSaveBusinessException(ex, forAdminUserCreate: true);
}
}
/// <summary>
/// 修改用户密码。
/// </summary>
[HttpPut("/rbac/user/update-password")]
public Task UpdatePasswordAsync(
[FromQuery] Guid id,
[FromBody] UpdatePasswordDto input,
CancellationToken cancellationToken = default) =>
userService.UpdatePasswordAsync(id, input, cancellationToken);
/// <summary>
/// 管理员重置用户密码(无需旧密码)。
/// </summary>
[HttpPost("/rbac/user/reset-password")]
public async Task ResetPasswordAsync(
[FromQuery] Guid id,
[FromBody] ResetUserPasswordInput input,
CancellationToken cancellationToken = default)
{
if (id == Guid.Empty)
{
throw new BusinessException("用户 Id 无效");
}
var newPassword = input?.NewPassword?.Trim();
if (string.IsNullOrWhiteSpace(newPassword))
{
throw new BusinessException("新密码不能为空");
}
if (newPassword.Length < 6)
{
throw new BusinessException("密码长度不能少于6位");
}
await accountManager.ResetPasswordAsync(id, newPassword, cancellationToken);
}
/// <summary>
/// 更新用户头像。
/// </summary>
[HttpPut("/rbac/user/update-avatar")]
public Task UpdateAvatarAsync(
[FromQuery] Guid id,
[FromQuery] string avatarUrl,
CancellationToken cancellationToken = default) =>
userService.UpdateAvatarAsync(id, OmsObjectStorageExtensions.ToStorageKey(avatarUrl) ?? avatarUrl, cancellationToken);
/// <summary>
/// 删除用户。
/// </summary>
[HttpDelete("/rbac/user/delete")]
public Task DeleteAsync([FromQuery] Guid id, CancellationToken cancellationToken = default) =>
userService.DeleteAsync(id, cancellationToken);
/// <summary>
/// 批量删除用户。
/// </summary>
[HttpDelete("/rbac/user/delete-batch")]
public Task DeleteBatchAsync([FromQuery] List<Guid> ids, CancellationToken cancellationToken = default) =>
userService.DeleteBatchAsync(ids, cancellationToken);
/// <summary>
/// 为用户分配角色。
/// </summary>
[HttpPost("/rbac/user/assign-roles")]
public Task AssignRolesAsync(
[FromQuery] Guid userId,
[FromBody] List<Guid> roleIds,
CancellationToken cancellationToken = default) =>
userService.AssignRolesAsync(userId, roleIds, cancellationToken);
/// <summary>
/// 获取用户已分配角色。
/// </summary>
[HttpGet("/rbac/user/get-user-roles")]
public Task<List<RoleDto>> GetUserRolesAsync(
[FromQuery] Guid userId,
CancellationToken cancellationToken = default) =>
userService.GetUserRolesAsync(userId, cancellationToken);
/// <summary>
/// 获取用户权限码列表。
/// </summary>
[HttpGet("/rbac/user/get-user-permissions")]
public Task<List<string>> GetUserPermissionsAsync(
[FromQuery] Guid userId,
CancellationToken cancellationToken = default) =>
userService.GetUserPermissionsAsync(userId, cancellationToken);
private async Task<string> ResolveEmployeeNoForCreateAsync(
string? requestedEmployeeNo,
CancellationToken cancellationToken)
{
var employeeNo = string.IsNullOrWhiteSpace(requestedEmployeeNo)
? await profileRepository.GenerateNextEmployeeNoAsync(cancellationToken)
: requestedEmployeeNo.Trim().ToUpperInvariant();
if (!UserProfileRepository.IsValidEmployeeNo(employeeNo))
{
throw new BusinessException($"工号「{employeeNo}」格式无效,应为 IDE + 至少 3 位数字,如 IDE001");
}
if (await profileRepository.IsEmployeeNoTakenAsync(employeeNo, null, cancellationToken))
{
throw new BusinessException($"工号「{employeeNo}」已存在,请更换工号后再创建");
}
return employeeNo;
}
private async Task EnsureEmployeeProfileAsync(
Guid userId,
OmsUserCreateInput input,
CancellationToken cancellationToken)
{
var userIdText = userId.ToString();
if (await employeeRepository.ExistsByUserIdAsync(userIdText, null, cancellationToken))
{
return;
}
await employeeRepository.InsertAsync(
new Employee
{
UserId = userIdText,
RealName = string.IsNullOrWhiteSpace(input.Name) ? string.Empty : input.Name.Trim(),
HireDate = DateTime.Today,
EmploymentStatus = EmploymentStatus.Probation,
EmploymentType = EmploymentType.FullTime,
},
cancellationToken);
}
private OmsUserDetailDto MapUserDetail(UserDto user, UserProfileData? profile)
{
var detail = OmsUserProfileMapper.ToDetailDto(user, profile);
detail.Avatar = fileStorage.GetPublicUrl(detail.Avatar);
return detail;
}
}