feat(console): add UserService with audit logging
This commit is contained in:
parent
c87278fb18
commit
993152aa56
271
Services/UserService.cs
Normal file
271
Services/UserService.cs
Normal file
@ -0,0 +1,271 @@
|
||||
using Fengling.AuthService.Data;
|
||||
using Fengling.AuthService.Models;
|
||||
using Fengling.Console.Models.Dtos;
|
||||
using Fengling.Console.Repositories;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Fengling.Console.Services;
|
||||
|
||||
public interface IUserService
|
||||
{
|
||||
Task<(IEnumerable<UserDto> Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null);
|
||||
Task<UserDto?> GetUserAsync(long id);
|
||||
Task<UserDto> CreateUserAsync(CreateUserDto dto);
|
||||
Task<UserDto> UpdateUserAsync(long id, UpdateUserDto dto);
|
||||
Task ResetPasswordAsync(long id, ResetPasswordDto dto);
|
||||
Task DeleteUserAsync(long id);
|
||||
}
|
||||
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private readonly IUserRepository _repository;
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly RoleManager<ApplicationRole> _roleManager;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public UserService(
|
||||
IUserRepository repository,
|
||||
ITenantRepository tenantRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<ApplicationRole> roleManager,
|
||||
ApplicationDbContext context,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_repository = repository;
|
||||
_tenantRepository = tenantRepository;
|
||||
_userManager = userManager;
|
||||
_roleManager = roleManager;
|
||||
_context = context;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<UserDto> Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null)
|
||||
{
|
||||
var users = await _repository.GetPagedAsync(page, pageSize, userName, email, tenantId);
|
||||
var totalCount = await _repository.CountAsync(userName, email, tenantId);
|
||||
|
||||
var userDtos = new List<UserDto>();
|
||||
foreach (var user in users)
|
||||
{
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
userDtos.Add(new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName,
|
||||
Email = user.Email,
|
||||
RealName = user.RealName,
|
||||
Phone = user.Phone,
|
||||
TenantId = user.TenantInfo.Id,
|
||||
TenantName = user.TenantInfo.Name,
|
||||
Roles = roles.ToList(),
|
||||
EmailConfirmed = user.EmailConfirmed,
|
||||
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
|
||||
CreatedAt = user.CreatedTime
|
||||
});
|
||||
}
|
||||
|
||||
return (userDtos, totalCount);
|
||||
}
|
||||
|
||||
public async Task<UserDto?> GetUserAsync(long id)
|
||||
{
|
||||
var user = await _repository.GetByIdAsync(id);
|
||||
if (user == null) return null;
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName,
|
||||
Email = user.Email,
|
||||
RealName = user.RealName,
|
||||
Phone = user.Phone,
|
||||
TenantId = user.TenantInfo.Id,
|
||||
TenantName = user.TenantInfo.Name,
|
||||
Roles = roles.ToList(),
|
||||
EmailConfirmed = user.EmailConfirmed,
|
||||
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
|
||||
CreatedAt = user.CreatedTime
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<UserDto> CreateUserAsync(CreateUserDto dto)
|
||||
{
|
||||
var tenantId = dto.TenantId ?? 0;
|
||||
Tenant? tenant = null;
|
||||
|
||||
if (tenantId != 0)
|
||||
{
|
||||
tenant = await _tenantRepository.GetByIdAsync(tenantId);
|
||||
if (tenant == null)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid tenant ID");
|
||||
}
|
||||
}
|
||||
|
||||
var user = new ApplicationUser
|
||||
{
|
||||
UserName = dto.UserName,
|
||||
Email = dto.Email,
|
||||
RealName = dto.RealName,
|
||||
Phone = dto.Phone,
|
||||
TenantInfo = new TenantInfo(tenantId, tenant?.TenantId ?? "default", tenant?.Name ?? "默认租户"),
|
||||
EmailConfirmed = dto.EmailConfirmed,
|
||||
CreatedTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var result = await _userManager.CreateAsync(user, dto.Password);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
|
||||
}
|
||||
|
||||
if (dto.RoleIds != null && dto.RoleIds.Any())
|
||||
{
|
||||
foreach (var roleId in dto.RoleIds)
|
||||
{
|
||||
var role = await _roleManager.FindByIdAsync(roleId.ToString());
|
||||
if (role != null)
|
||||
{
|
||||
await _userManager.AddToRoleAsync(user, role.Name!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dto.IsActive)
|
||||
{
|
||||
await _userManager.SetLockoutEnabledAsync(user, true);
|
||||
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
|
||||
}
|
||||
|
||||
await CreateAuditLog("user", "create", "User", user.Id, user.UserName, null, System.Text.Json.JsonSerializer.Serialize(dto));
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName,
|
||||
Email = user.Email,
|
||||
RealName = user.RealName,
|
||||
Phone = user.Phone,
|
||||
TenantId = user.TenantInfo.Id,
|
||||
TenantName = user.TenantInfo.Name,
|
||||
Roles = roles.ToList(),
|
||||
EmailConfirmed = user.EmailConfirmed,
|
||||
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
|
||||
CreatedAt = user.CreatedTime
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<UserDto> UpdateUserAsync(long id, UpdateUserDto dto)
|
||||
{
|
||||
var user = await _repository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"User with ID {id} not found");
|
||||
}
|
||||
|
||||
var oldValue = System.Text.Json.JsonSerializer.Serialize(user);
|
||||
|
||||
user.Email = dto.Email;
|
||||
user.RealName = dto.RealName;
|
||||
user.Phone = dto.Phone;
|
||||
user.EmailConfirmed = dto.EmailConfirmed;
|
||||
user.UpdatedTime = DateTime.UtcNow;
|
||||
|
||||
if (dto.IsActive)
|
||||
{
|
||||
await _userManager.SetLockoutEnabledAsync(user, false);
|
||||
await _userManager.SetLockoutEndDateAsync(user, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _userManager.SetLockoutEnabledAsync(user, true);
|
||||
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user));
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName,
|
||||
Email = user.Email,
|
||||
RealName = user.RealName,
|
||||
Phone = user.Phone,
|
||||
TenantId = user.TenantInfo.Id,
|
||||
TenantName = user.TenantInfo.Name,
|
||||
Roles = roles.ToList(),
|
||||
EmailConfirmed = user.EmailConfirmed,
|
||||
IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
|
||||
CreatedAt = user.CreatedTime
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ResetPasswordAsync(long id, ResetPasswordDto dto)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(id.ToString());
|
||||
if (user == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"User with ID {id} not found");
|
||||
}
|
||||
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var result = await _userManager.ResetPasswordAsync(user, token, dto.NewPassword);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
|
||||
}
|
||||
|
||||
await CreateAuditLog("user", "reset_password", "User", user.Id, user.UserName);
|
||||
}
|
||||
|
||||
public async Task DeleteUserAsync(long id)
|
||||
{
|
||||
var user = await _repository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"User with ID {id} not found");
|
||||
}
|
||||
|
||||
var oldValue = System.Text.Json.JsonSerializer.Serialize(user);
|
||||
user.IsDeleted = true;
|
||||
user.UpdatedTime = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await CreateAuditLog("user", "delete", "User", user.Id, user.UserName, oldValue);
|
||||
}
|
||||
|
||||
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system";
|
||||
var tenantId = httpContext?.User?.FindFirstValue("TenantId");
|
||||
|
||||
var log = new AuditLog
|
||||
{
|
||||
Operator = userName,
|
||||
TenantId = tenantId,
|
||||
Operation = operation,
|
||||
Action = action,
|
||||
TargetType = targetType,
|
||||
TargetId = targetId,
|
||||
TargetName = targetName,
|
||||
IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown",
|
||||
Status = "success",
|
||||
OldValue = oldValue,
|
||||
NewValue = newValue,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.AuditLogs.Add(log);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user