- Add Fengling.Platform domain and infrastructure projects - Move Tenant aggregate from AuthService/Console to Platform.Domain - Add TenantRepository and SeedData to Platform - Remove duplicate Tenant/TenantInfo models from AuthService and Console - Update controllers and services to use Platform.Domain.Tenant - Add new migrations for PlatformDbContext BREAKING CHANGE: Tenant entity now uses strongly-typed ID (TenantId)
296 lines
8.9 KiB
C#
296 lines
8.9 KiB
C#
using Fengling.AuthService.Data;
|
|
using Fengling.AuthService.Models;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Security.Claims;
|
|
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
|
using Fengling.Platform.Infrastructure;
|
|
|
|
namespace Fengling.AuthService.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
public class UsersController(
|
|
ApplicationDbContext context,
|
|
UserManager<ApplicationUser> userManager,
|
|
RoleManager<ApplicationRole> roleManager,
|
|
ILogger<UsersController> logger,
|
|
PlatformDbContext platformDbContext)
|
|
: ControllerBase
|
|
{
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<object>> GetUsers(
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 10,
|
|
[FromQuery] string? userName = null,
|
|
[FromQuery] string? email = null,
|
|
[FromQuery] string? tenantCode = null)
|
|
{
|
|
var query = context.Users.AsQueryable();
|
|
|
|
if (!string.IsNullOrEmpty(userName))
|
|
{
|
|
query = query.Where(u => u.UserName!.Contains(userName));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(email))
|
|
{
|
|
query = query.Where(u => u.Email != null && u.Email.Contains(email));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(tenantCode))
|
|
{
|
|
query = query.Where(u => u.TenantInfo.TenantCode == tenantCode);
|
|
}
|
|
|
|
var totalCount = await query.CountAsync();
|
|
var users = await query
|
|
.OrderByDescending(u => u.CreatedTime)
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.ToListAsync();
|
|
|
|
var result = users.Select(async u => new
|
|
{
|
|
id = u.Id,
|
|
userName = u.UserName,
|
|
email = u.Email,
|
|
realName = u.RealName,
|
|
phone = u.Phone,
|
|
tenantId = u.TenantInfo.TenantId,
|
|
roles = (await userManager.GetRolesAsync(u)).ToList(),
|
|
emailConfirmed = u.EmailConfirmed,
|
|
isActive = !u.LockoutEnabled || u.LockoutEnd == null || u.LockoutEnd < DateTimeOffset.UtcNow,
|
|
createdAt = u.CreatedTime,
|
|
});
|
|
|
|
return Ok(new
|
|
{
|
|
items = await Task.WhenAll(result),
|
|
totalCount,
|
|
page,
|
|
pageSize
|
|
});
|
|
}
|
|
|
|
[HttpGet("{id}")]
|
|
public async Task<ActionResult<object>> GetUser(long id)
|
|
{
|
|
var user = await context.Users.FindAsync(id);
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var roles = await userManager.GetRolesAsync(user);
|
|
|
|
return Ok(new
|
|
{
|
|
id = user.Id,
|
|
userName = user.UserName,
|
|
email = user.Email,
|
|
realName = user.RealName,
|
|
phone = user.Phone,
|
|
tenantId = user.TenantInfo.TenantId,
|
|
roles,
|
|
emailConfirmed = user.EmailConfirmed,
|
|
isActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
|
|
createdAt = user.CreatedTime,
|
|
});
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<ApplicationUser>> CreateUser(CreateUserDto dto)
|
|
{
|
|
var tenantId = dto.TenantId ?? 0;
|
|
Tenant? tenant = null;
|
|
|
|
if (tenantId != 0)
|
|
{
|
|
tenant = await platformDbContext.Tenants.FindAsync(tenantId);
|
|
if (tenant == null)
|
|
{
|
|
return BadRequest("Invalid tenant ID");
|
|
}
|
|
}
|
|
|
|
var user = new ApplicationUser
|
|
{
|
|
UserName = dto.UserName,
|
|
Email = dto.Email,
|
|
RealName = dto.RealName,
|
|
Phone = dto.Phone,
|
|
TenantInfo = new TenantInfo(tenant!),
|
|
EmailConfirmed = dto.EmailConfirmed,
|
|
CreatedTime = DateTime.UtcNow,
|
|
};
|
|
|
|
var result = await userManager.CreateAsync(user, dto.Password);
|
|
if (!result.Succeeded)
|
|
{
|
|
return BadRequest(result.Errors);
|
|
}
|
|
|
|
if (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, SerializeToJson(dto));
|
|
|
|
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
|
|
}
|
|
|
|
[HttpPut("{id}")]
|
|
public async Task<IActionResult> UpdateUser(long id, UpdateUserDto dto)
|
|
{
|
|
var user = await context.Users.FindAsync(id);
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
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));
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPut("{id}/password")]
|
|
public async Task<IActionResult> ResetPassword(long id, ResetPasswordDto dto)
|
|
{
|
|
var user = await userManager.FindByIdAsync(id.ToString());
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var token = await userManager.GeneratePasswordResetTokenAsync(user);
|
|
var result = await userManager.ResetPasswordAsync(user, token, dto.NewPassword);
|
|
|
|
if (!result.Succeeded)
|
|
{
|
|
return BadRequest(result.Errors);
|
|
}
|
|
|
|
await CreateAuditLog("user", "reset_password", "User", user.Id, user.UserName);
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpDelete("{id}")]
|
|
public async Task<IActionResult> DeleteUser(long id)
|
|
{
|
|
var user = await context.Users.FindAsync(id);
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
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);
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null)
|
|
{
|
|
var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system";
|
|
var tenantId = 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,
|
|
};
|
|
|
|
context.AuditLogs.Add(log);
|
|
await context.SaveChangesAsync();
|
|
}
|
|
|
|
private string SerializeToJson(object obj)
|
|
{
|
|
return System.Text.Json.JsonSerializer.Serialize(obj, new System.Text.Json.JsonSerializerOptions
|
|
{
|
|
WriteIndented = false
|
|
});
|
|
}
|
|
}
|
|
|
|
public class CreateUserDto
|
|
{
|
|
public string UserName { get; set; } = string.Empty;
|
|
public string Email { get; set; } = string.Empty;
|
|
public string RealName { get; set; } = string.Empty;
|
|
public string? Phone { get; set; }
|
|
public long? TenantId { get; set; }
|
|
public List<long> RoleIds { get; set; } = new();
|
|
public string Password { get; set; } = string.Empty;
|
|
public bool EmailConfirmed { get; set; }
|
|
public bool IsActive { get; set; } = true;
|
|
}
|
|
|
|
public class UpdateUserDto
|
|
{
|
|
public string Email { get; set; } = string.Empty;
|
|
public string RealName { get; set; } = string.Empty;
|
|
public string? Phone { get; set; }
|
|
public bool EmailConfirmed { get; set; }
|
|
public bool IsActive { get; set; } = true;
|
|
}
|
|
|
|
public class ResetPasswordDto
|
|
{
|
|
public string NewPassword { get; set; } = string.Empty;
|
|
}
|