feat(auth): extract Tenant to Platform domain

- 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)
This commit is contained in:
movingsam 2026-02-18 23:00:09 +08:00
parent 8184f77c0f
commit 39cc9a8538
12 changed files with 192 additions and 592 deletions

View File

@ -1,6 +1,8 @@
using Fengling.AuthService.Data;
using Fengling.AuthService.Models;
using Fengling.AuthService.ViewModels;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Fengling.Platform.Infrastructure;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Identity;
@ -10,25 +12,14 @@ using Microsoft.EntityFrameworkCore;
namespace Fengling.AuthService.Controllers;
[Route("account")]
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ApplicationDbContext _dbContext;
private readonly ILogger<AccountController> _logger;
public AccountController(
public class AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ApplicationDbContext dbContext,
ILogger<AccountController> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_dbContext = dbContext;
_logger = logger;
}
ILogger<AccountController> logger,
PlatformDbContext platformDbContext)
: Controller
{
[HttpGet("login")]
public IActionResult Login(string returnUrl = "/")
{
@ -44,14 +35,14 @@ public class AccountController : Controller
return View(model);
}
var user = await _userManager.FindByNameAsync(model.Username);
var user = await userManager.FindByNameAsync(model.Username);
if (user == null || user.IsDeleted)
{
ModelState.AddModelError(string.Empty, "用户名或密码错误");
return View(model);
}
var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, true);
var result = await signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, true);
if (!result.Succeeded)
{
if (result.IsLockedOut)
@ -83,12 +74,12 @@ public class AccountController : Controller
return View(model);
}
var defaultTenant = await _dbContext.Tenants
.FirstOrDefaultAsync(t => t.TenantId == "default");
var tenant = await platformDbContext.Tenants
.FirstOrDefaultAsync(t => t.TenantCode == model.TenantCode);
if (defaultTenant == null)
if (tenant == null)
{
ModelState.AddModelError(string.Empty, "系统配置错误:未找到默认租户");
ModelState.AddModelError(string.Empty, $"系统配置错误:未找到租户{model.TenantCode}");
return View(model);
}
@ -98,10 +89,10 @@ public class AccountController : Controller
Email = model.Email,
NormalizedUserName = model.Username.ToUpper(),
NormalizedEmail = model.Email.ToUpper(),
TenantInfo = new TenantInfo(defaultTenant.Id, defaultTenant.TenantId, defaultTenant.Name)
TenantInfo = new TenantInfo(tenant)
};
var result = await _userManager.CreateAsync(user, model.Password);
var result = await userManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
foreach (var error in result.Errors)
@ -111,7 +102,7 @@ public class AccountController : Controller
return View(model);
}
await _signInManager.SignInAsync(user, isPersistent: false);
await signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(model.ReturnUrl);
}
@ -127,7 +118,7 @@ public class AccountController : Controller
[ValidateAntiForgeryToken]
public async Task<IActionResult> LogoutPost()
{
await _signInManager.SignOutAsync();
await signInManager.SignOutAsync();
return Redirect("/");
}
}

View File

@ -1,5 +1,6 @@
using Fengling.AuthService.Data;
using Fengling.AuthService.Models;
using Fengling.Platform.Infrastructure;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -10,21 +11,13 @@ namespace Fengling.AuthService.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class StatsController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly ILogger<StatsController> _logger;
public StatsController(
public class StatsController(
ApplicationDbContext context,
IOpenIddictApplicationManager applicationManager,
ILogger<StatsController> logger)
{
_context = context;
_applicationManager = applicationManager;
_logger = logger;
}
ILogger<StatsController> logger,
PlatformDbContext platformDbContext)
: ControllerBase
{
[HttpGet("dashboard")]
public async Task<ActionResult<object>> GetDashboardStats()
@ -32,10 +25,10 @@ public class StatsController : ControllerBase
var today = DateTime.UtcNow.Date;
var tomorrow = today.AddDays(1);
var userCount = await _context.Users.CountAsync(u => !u.IsDeleted);
var tenantCount = await _context.Tenants.CountAsync(t => !t.IsDeleted);
var userCount = await context.Users.CountAsync(u => !u.IsDeleted);
var tenantCount = await platformDbContext.Tenants.CountAsync(t => !t.Deleted);
var oauthClientCount = await CountOAuthClientsAsync();
var todayAccessCount = await _context.AccessLogs
var todayAccessCount = await context.AccessLogs
.CountAsync(l => l.CreatedAt >= today && l.CreatedAt < tomorrow);
return Ok(new
@ -50,7 +43,7 @@ public class StatsController : ControllerBase
private async Task<int> CountOAuthClientsAsync()
{
var count = 0;
var applications = _applicationManager.ListAsync();
var applications = applicationManager.ListAsync();
await foreach (var _ in applications)
{
count++;

View File

@ -6,49 +6,42 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using System.Text.Json;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Fengling.Platform.Infrastructure;
namespace Fengling.AuthService.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class TenantsController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<TenantsController> _logger;
public TenantsController(
public class TenantsController(
ApplicationDbContext context,
UserManager<ApplicationUser> userManager,
ILogger<TenantsController> logger)
{
_context = context;
_userManager = userManager;
_logger = logger;
}
ILogger<TenantsController> logger,
PlatformDbContext platformDbContext)
: ControllerBase
{
[HttpGet]
public async Task<ActionResult<object>> GetTenants(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10,
[FromQuery] string? name = null,
[FromQuery] string? tenantId = null,
[FromQuery] string? status = null)
[FromQuery] string? tenantCode = null,
[FromQuery] TenantStatus? status = null)
{
var query = _context.Tenants.AsQueryable();
var query = platformDbContext.Tenants.AsQueryable();
if (!string.IsNullOrEmpty(name))
{
query = query.Where(t => t.Name.Contains(name));
}
if (!string.IsNullOrEmpty(tenantId))
if (!string.IsNullOrEmpty(tenantCode))
{
query = query.Where(t => t.TenantId.Contains(tenantId));
query = query.Where(t => t.TenantCode.Contains(tenantCode));
}
if (!string.IsNullOrEmpty(status))
if (status.HasValue)
{
query = query.Where(t => t.Status == status);
}
@ -64,11 +57,11 @@ public class TenantsController : ControllerBase
foreach (var tenant in tenants)
{
var userCount = await _context.Users.CountAsync(u => u.TenantInfo.Id == tenant.Id && !u.IsDeleted);
var userCount = await context.Users.CountAsync(u => u.TenantInfo.TenantId == tenant.Id && !u.IsDeleted);
result.Add(new
{
id = tenant.Id,
tenantId = tenant.TenantId,
tenantId = tenant.Id,
name = tenant.Name,
contactName = tenant.ContactName,
contactEmail = tenant.ContactEmail,
@ -92,9 +85,9 @@ public class TenantsController : ControllerBase
}
[HttpGet("{id}")]
public async Task<ActionResult<Tenant>> GetTenant(long id)
public async Task<ActionResult<Tenant>> GetTenant(TenantId id)
{
var tenant = await _context.Tenants.FindAsync(id);
var tenant = await platformDbContext.Tenants.FindAsync(id);
if (tenant == null)
{
return NotFound();
@ -103,7 +96,7 @@ public class TenantsController : ControllerBase
return Ok(new
{
id = tenant.Id,
tenantId = tenant.TenantId,
tenantId = tenant.Id,
name = tenant.Name,
contactName = tenant.ContactName,
contactEmail = tenant.ContactEmail,
@ -118,16 +111,19 @@ public class TenantsController : ControllerBase
}
[HttpGet("{tenantId}/users")]
public async Task<ActionResult<List<object>>> GetTenantUsers(string tenantId)
public async Task<ActionResult<List<object>>> GetTenantUsers(TenantId tenantId)
{
var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId);
var tenant = await platformDbContext.Tenants
.FirstOrDefaultAsync(t => t.Id == tenantId);
if (tenant == null)
{
return NotFound();
}
var users = await _context.Users
.Where(u => u.TenantInfo.Id == tenant.Id && !u.IsDeleted)
var users = await context.Users
.Where(u =>
u.TenantInfo.TenantId == tenant.Id
&& !u.IsDeleted)
.ToListAsync();
var result = users.Select(async u => new
@ -136,8 +132,8 @@ public class TenantsController : ControllerBase
userName = u.UserName,
email = u.Email,
realName = u.RealName,
tenantId = u.TenantInfo.Id,
roles = await _userManager.GetRolesAsync(u),
tenantId = u.TenantInfo.TenantId,
roles = await userManager.GetRolesAsync(u),
isActive = !u.LockoutEnabled || u.LockoutEnd == null || u.LockoutEnd < DateTimeOffset.UtcNow,
createdAt = u.CreatedTime,
});
@ -146,15 +142,16 @@ public class TenantsController : ControllerBase
}
[HttpGet("{tenantId}/roles")]
public async Task<ActionResult<List<object>>> GetTenantRoles(string tenantId)
public async Task<ActionResult<List<object>>> GetTenantRoles(TenantId tenantId)
{
var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId);
var tenant = await platformDbContext.Tenants
.FirstOrDefaultAsync(t => t.Id == tenantId);
if (tenant == null)
{
return NotFound();
}
var roles = await _context.Roles
var roles = await context.Roles
.Where(r => r.TenantId == tenant.Id)
.ToListAsync();
@ -169,9 +166,10 @@ public class TenantsController : ControllerBase
}
[HttpGet("{tenantId}/settings")]
public async Task<ActionResult<TenantSettings>> GetTenantSettings(string tenantId)
public async Task<ActionResult<TenantSettings>> GetTenantSettings(TenantId tenantId)
{
var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId);
var tenant = await platformDbContext.Tenants
.FirstOrDefaultAsync(t => t.Id == tenantId);
if (tenant == null)
{
return NotFound();
@ -191,15 +189,16 @@ public class TenantsController : ControllerBase
}
[HttpPut("{tenantId}/settings")]
public async Task<IActionResult> UpdateTenantSettings(string tenantId, TenantSettings settings)
public async Task<IActionResult> UpdateTenantSettings(TenantId tenantId, TenantSettings settings)
{
var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId);
var tenant = await platformDbContext.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId);
if (tenant == null)
{
return NotFound();
}
await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.TenantId, null, JsonSerializer.Serialize(settings));
await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.Name, null,
JsonSerializer.Serialize(settings));
return NoContent();
}
@ -207,24 +206,14 @@ public class TenantsController : ControllerBase
[HttpPost]
public async Task<ActionResult<Tenant>> CreateTenant(CreateTenantDto dto)
{
var tenant = new Tenant
{
TenantId = dto.TenantId,
Name = dto.Name,
ContactName = dto.ContactName,
ContactEmail = dto.ContactEmail,
ContactPhone = dto.ContactPhone,
MaxUsers = dto.MaxUsers,
Description = dto.Description,
Status = dto.Status,
ExpiresAt = dto.ExpiresAt,
CreatedAt = DateTime.UtcNow,
};
var tenant = new Tenant(dto.TenantCode, dto.TenantName, dto.ContactName, dto.ContactEmail, dto.ContactPhone,
dto.MaxUsers, dto.Description, dto.ExpiresAt);
_context.Tenants.Add(tenant);
await _context.SaveChangesAsync();
platformDbContext.Tenants.Add(tenant);
await context.SaveChangesAsync();
await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantId, null, JsonSerializer.Serialize(dto));
await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantCode,
null, JsonSerializer.Serialize(dto));
return CreatedAtAction(nameof(GetTenant), new { id = tenant.Id }, tenant);
}
@ -232,27 +221,20 @@ public class TenantsController : ControllerBase
[HttpPut("{id}")]
public async Task<IActionResult> UpdateTenant(long id, UpdateTenantDto dto)
{
var tenant = await _context.Tenants.FindAsync(id);
var tenant = await platformDbContext.Tenants.FindAsync(id);
if (tenant == null)
{
return NotFound();
}
var oldValue = JsonSerializer.Serialize(tenant);
tenant.UpdateInfo(dto.Name, dto.ContactName, dto.ContactEmail, dto.ContactPhone);
tenant.Name = dto.Name;
tenant.ContactName = dto.ContactName;
tenant.ContactEmail = dto.ContactEmail;
tenant.ContactPhone = dto.ContactPhone;
tenant.MaxUsers = dto.MaxUsers;
tenant.Description = dto.Description;
tenant.Status = dto.Status;
tenant.ExpiresAt = dto.ExpiresAt;
tenant.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.TenantId, oldValue, JsonSerializer.Serialize(tenant));
await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.Name, oldValue,
JsonSerializer.Serialize(tenant));
return NoContent();
}
@ -260,7 +242,7 @@ public class TenantsController : ControllerBase
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTenant(long id)
{
var tenant = await _context.Tenants.FindAsync(id);
var tenant = await platformDbContext.Tenants.FindAsync(id);
if (tenant == null)
{
return NotFound();
@ -268,22 +250,23 @@ public class TenantsController : ControllerBase
var oldValue = JsonSerializer.Serialize(tenant);
var users = await _context.Users.Where(u => u.TenantInfo.Id == tenant.Id).ToListAsync();
var users = await context.Users.Where(u => u.TenantInfo.TenantId == tenant.Id).ToListAsync();
foreach (var user in users)
{
user.IsDeleted = true;
user.UpdatedTime = DateTime.UtcNow;
}
tenant.IsDeleted = true;
await _context.SaveChangesAsync();
tenant.Delete();
await context.SaveChangesAsync();
await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.TenantId, oldValue);
await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.Name, oldValue);
return NoContent();
}
private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null)
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");
@ -303,23 +286,21 @@ public class TenantsController : ControllerBase
NewValue = newValue,
};
_context.AuditLogs.Add(log);
await _context.SaveChangesAsync();
context.AuditLogs.Add(log);
await context.SaveChangesAsync();
}
}
public class CreateTenantDto
{
public string TenantId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string ContactName { get; set; } = string.Empty;
public string ContactEmail { get; set; } = string.Empty;
public string? ContactPhone { get; set; }
public int? MaxUsers { get; set; }
public string? Description { get; set; }
public string Status { get; set; } = "active";
public DateTime? ExpiresAt { get; set; }
}
public record CreateTenantDto(
string TenantCode,
string TenantName,
string ContactName,
string ContactEmail,
string ContactPhone,
int? MaxUsers,
string? Description,
string Status,
DateTime? ExpiresAt);
public class UpdateTenantDto
{

View File

@ -5,30 +5,22 @@ 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 : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager;
private readonly ILogger<UsersController> _logger;
public UsersController(
public class UsersController(
ApplicationDbContext context,
UserManager<ApplicationUser> userManager,
RoleManager<ApplicationRole> roleManager,
ILogger<UsersController> logger)
{
_context = context;
_userManager = userManager;
_roleManager = roleManager;
_logger = logger;
}
ILogger<UsersController> logger,
PlatformDbContext platformDbContext)
: ControllerBase
{
[HttpGet]
public async Task<ActionResult<object>> GetUsers(
@ -36,9 +28,9 @@ public class UsersController : ControllerBase
[FromQuery] int pageSize = 10,
[FromQuery] string? userName = null,
[FromQuery] string? email = null,
[FromQuery] string? tenantId = null)
[FromQuery] string? tenantCode = null)
{
var query = _context.Users.AsQueryable();
var query = context.Users.AsQueryable();
if (!string.IsNullOrEmpty(userName))
{
@ -50,9 +42,9 @@ public class UsersController : ControllerBase
query = query.Where(u => u.Email != null && u.Email.Contains(email));
}
if (!string.IsNullOrEmpty(tenantId))
if (!string.IsNullOrEmpty(tenantCode))
{
query = query.Where(u => u.TenantInfo.Id.ToString() == tenantId);
query = query.Where(u => u.TenantInfo.TenantCode == tenantCode);
}
var totalCount = await query.CountAsync();
@ -69,8 +61,8 @@ public class UsersController : ControllerBase
email = u.Email,
realName = u.RealName,
phone = u.Phone,
tenantId = u.TenantInfo.Id,
roles = (await _userManager.GetRolesAsync(u)).ToList(),
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,
@ -88,13 +80,13 @@ public class UsersController : ControllerBase
[HttpGet("{id}")]
public async Task<ActionResult<object>> GetUser(long id)
{
var user = await _context.Users.FindAsync(id);
var user = await context.Users.FindAsync(id);
if (user == null)
{
return NotFound();
}
var roles = await _userManager.GetRolesAsync(user);
var roles = await userManager.GetRolesAsync(user);
return Ok(new
{
@ -103,7 +95,7 @@ public class UsersController : ControllerBase
email = user.Email,
realName = user.RealName,
phone = user.Phone,
tenantId = user.TenantInfo.Id,
tenantId = user.TenantInfo.TenantId,
roles,
emailConfirmed = user.EmailConfirmed,
isActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
@ -115,11 +107,11 @@ public class UsersController : ControllerBase
public async Task<ActionResult<ApplicationUser>> CreateUser(CreateUserDto dto)
{
var tenantId = dto.TenantId ?? 0;
Tenant tenant = null;
Tenant? tenant = null;
if (tenantId != 0)
{
tenant = await _context.Tenants.FindAsync(tenantId);
tenant = await platformDbContext.Tenants.FindAsync(tenantId);
if (tenant == null)
{
return BadRequest("Invalid tenant ID");
@ -132,33 +124,33 @@ public class UsersController : ControllerBase
Email = dto.Email,
RealName = dto.RealName,
Phone = dto.Phone,
TenantInfo = new TenantInfo(tenantId, tenant?.TenantId ?? "default", tenant?.Name ?? "默认租户"),
TenantInfo = new TenantInfo(tenant!),
EmailConfirmed = dto.EmailConfirmed,
CreatedTime = DateTime.UtcNow,
};
var result = await _userManager.CreateAsync(user, dto.Password);
var result = await userManager.CreateAsync(user, dto.Password);
if (!result.Succeeded)
{
return BadRequest(result.Errors);
}
if (dto.RoleIds != null && dto.RoleIds.Any())
if (dto.RoleIds.Any())
{
foreach (var roleId in dto.RoleIds)
{
var role = await _roleManager.FindByIdAsync(roleId.ToString());
var role = await roleManager.FindByIdAsync(roleId.ToString());
if (role != null)
{
await _userManager.AddToRoleAsync(user, role.Name!);
await userManager.AddToRoleAsync(user, role.Name!);
}
}
}
if (!dto.IsActive)
{
await _userManager.SetLockoutEnabledAsync(user, true);
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
await userManager.SetLockoutEnabledAsync(user, true);
await userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
}
await CreateAuditLog("user", "create", "User", user.Id, user.UserName, null, SerializeToJson(dto));
@ -169,7 +161,7 @@ public class UsersController : ControllerBase
[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(long id, UpdateUserDto dto)
{
var user = await _context.Users.FindAsync(id);
var user = await context.Users.FindAsync(id);
if (user == null)
{
return NotFound();
@ -185,16 +177,16 @@ public class UsersController : ControllerBase
if (dto.IsActive)
{
await _userManager.SetLockoutEnabledAsync(user, false);
await _userManager.SetLockoutEndDateAsync(user, null);
await userManager.SetLockoutEnabledAsync(user, false);
await userManager.SetLockoutEndDateAsync(user, null);
}
else
{
await _userManager.SetLockoutEnabledAsync(user, true);
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
await userManager.SetLockoutEnabledAsync(user, true);
await userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
}
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user));
@ -204,14 +196,14 @@ public class UsersController : ControllerBase
[HttpPut("{id}/password")]
public async Task<IActionResult> ResetPassword(long id, ResetPasswordDto dto)
{
var user = await _userManager.FindByIdAsync(id.ToString());
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);
var token = await userManager.GeneratePasswordResetTokenAsync(user);
var result = await userManager.ResetPasswordAsync(user, token, dto.NewPassword);
if (!result.Succeeded)
{
@ -226,7 +218,7 @@ public class UsersController : ControllerBase
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteUser(long id)
{
var user = await _context.Users.FindAsync(id);
var user = await context.Users.FindAsync(id);
if (user == null)
{
return NotFound();
@ -235,7 +227,7 @@ public class UsersController : ControllerBase
var oldValue = System.Text.Json.JsonSerializer.Serialize(user);
user.IsDeleted = true;
user.UpdatedTime = DateTime.UtcNow;
await _context.SaveChangesAsync();
await context.SaveChangesAsync();
await CreateAuditLog("user", "delete", "User", user.Id, user.UserName, oldValue);
@ -262,8 +254,8 @@ public class UsersController : ControllerBase
NewValue = newValue,
};
_context.AuditLogs.Add(log);
await _context.SaveChangesAsync();
context.AuditLogs.Add(log);
await context.SaveChangesAsync();
}
private string SerializeToJson(object obj)

View File

@ -7,7 +7,6 @@ namespace Fengling.AuthService.Data;
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: IdentityDbContext<ApplicationUser, ApplicationRole, long>(options)
{
public DbSet<Tenant> Tenants { get; set; }
public DbSet<AccessLog> AccessLogs { get; set; }
public DbSet<AuditLog> AuditLogs { get; set; }
@ -23,27 +22,16 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
entity.OwnsOne(e => e.TenantInfo, navigationBuilder =>
{
navigationBuilder.Property(e => e.Id).HasColumnName("TenantId");
navigationBuilder.Property(e => e.TenantId).HasColumnName("TenantCode");
navigationBuilder.Property(e => e.Name).HasColumnName("TenantName");
navigationBuilder.Property(e => e.TenantCode).HasColumnName("TenantCode");
navigationBuilder.Property(e => e.TenantId).HasColumnName("TenantId");
navigationBuilder.Property(e => e.TenantName).HasColumnName("TenantName");
navigationBuilder.WithOwner();
});
});
builder.Entity<ApplicationRole>(entity => { entity.Property(e => e.Description).HasMaxLength(200); });
builder.Entity<Tenant>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.TenantId).IsUnique();
entity.Property(e => e.TenantId).HasMaxLength(50);
entity.Property(e => e.Name).HasMaxLength(100);
entity.Property(e => e.ContactName).HasMaxLength(50);
entity.Property(e => e.ContactEmail).HasMaxLength(100);
entity.Property(e => e.ContactPhone).HasMaxLength(20);
entity.Property(e => e.Status).HasMaxLength(20);
entity.Property(e => e.Description).HasMaxLength(500);
});
builder.Entity<AccessLog>(entity =>
{

View File

@ -1,4 +1,6 @@
using Fengling.AuthService.Models;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Fengling.Platform.Infrastructure;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Abstractions;
@ -16,28 +18,11 @@ public static class SeedData
var applicationManager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();
var scopeManager = scope.ServiceProvider.GetRequiredService<IOpenIddictScopeManager>();
var platformDbContext = scope.ServiceProvider.GetRequiredService<PlatformDbContext>();
var adminTenant = await platformDbContext.InitializeAsync();
await context.Database.EnsureCreatedAsync();
var defaultTenant = await context.Tenants
.AsNoTracking()
.FirstOrDefaultAsync(t => t.TenantId == "default");
if (defaultTenant == null)
{
defaultTenant = new Tenant
{
TenantId = "default",
Name = "默认租户",
ContactName = "系统管理员",
ContactEmail = "admin@fengling.local",
ContactPhone = "13800138000",
MaxUsers = 1000,
Description = "系统默认租户",
Status = "active",
CreatedAt = DateTime.UtcNow
};
context.Tenants.Add(defaultTenant);
await context.SaveChangesAsync();
}
var adminRole = await roleManager.FindByNameAsync("Admin");
if (adminRole == null)
@ -47,7 +32,7 @@ public static class SeedData
Name = "Admin",
DisplayName = "管理员",
Description = "System administrator",
TenantId = defaultTenant.Id,
TenantId = adminTenant.Id,
IsSystem = true,
Permissions = new List<string>
{
@ -70,7 +55,7 @@ public static class SeedData
Name = "User",
DisplayName = "普通用户",
Description = "Regular user",
TenantId = defaultTenant.Id,
TenantId = adminTenant.Id,
IsSystem = true,
Permissions = new List<string> { "user.view" },
CreatedTime = DateTime.UtcNow
@ -87,7 +72,7 @@ public static class SeedData
Email = "admin@fengling.local",
RealName = "系统管理员",
Phone = "13800138000",
TenantInfo = new TenantInfo(defaultTenant.Id, defaultTenant.TenantId, defaultTenant.Name),
TenantInfo = new TenantInfo(adminTenant),
EmailConfirmed = true,
IsDeleted = false,
CreatedTime = DateTime.UtcNow
@ -109,7 +94,7 @@ public static class SeedData
Email = "test@fengling.local",
RealName = "测试用户",
Phone = "13900139000",
TenantInfo = new TenantInfo(defaultTenant.Id, defaultTenant.TenantId, defaultTenant.Name),
TenantInfo = new TenantInfo(adminTenant.Id, adminTenant.TenantCode, adminTenant.Name),
EmailConfirmed = true,
IsDeleted = false,
CreatedTime = DateTime.UtcNow

View File

@ -33,4 +33,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fengling.Platform\Fengling.Platform.Domain\Fengling.Platform.Domain.csproj" />
<ProjectReference Include="..\Fengling.Platform\Fengling.Platform.Infrastructure\Fengling.Platform.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@ -13,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Fengling.AuthService.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260206142720_inital")]
partial class inital
[Migration("20260218145654_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -312,141 +312,6 @@ namespace Fengling.AuthService.Migrations
b.ToTable("AuditLogs");
});
modelBuilder.Entity("Fengling.AuthService.Models.OAuthApplication", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("ClientSecret")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("ClientType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("ConsentType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.PrimitiveCollection<string[]>("GrantTypes")
.IsRequired()
.HasColumnType("text[]");
b.PrimitiveCollection<string[]>("PostLogoutRedirectUris")
.IsRequired()
.HasColumnType("text[]");
b.PrimitiveCollection<string[]>("RedirectUris")
.IsRequired()
.HasColumnType("text[]");
b.PrimitiveCollection<string[]>("Scopes")
.IsRequired()
.HasColumnType("text[]");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ClientId")
.IsUnique();
b.ToTable("OAuthApplications");
});
modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ContactEmail")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("ContactName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("ContactPhone")
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTime?>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<int?>("MaxUsers")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("TenantId")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("TenantId")
.IsUnique();
b.ToTable("Tenants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
{
b.Property<int>("Id")
@ -760,25 +625,25 @@ namespace Fengling.AuthService.Migrations
modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b =>
{
b.OwnsOne("Fengling.AuthService.Models.TenantInfo", "TenantInfo", b1 =>
b.OwnsOne("Fengling.Platform.Domain.AggregatesModel.TenantAggregate.TenantInfo", "TenantInfo", b1 =>
{
b1.Property<long>("ApplicationUserId")
.HasColumnType("bigint");
b1.Property<long>("Id")
.HasColumnType("bigint")
.HasColumnName("TenantId");
b1.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TenantName");
b1.Property<string>("TenantId")
b1.Property<string>("TenantCode")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TenantCode");
b1.Property<long>("TenantId")
.HasColumnType("bigint")
.HasColumnName("TenantId");
b1.Property<string>("TenantName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TenantName");
b1.HasKey("ApplicationUserId");
b1.ToTable("AspNetUsers");

View File

@ -8,7 +8,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Fengling.AuthService.Migrations
{
/// <inheritdoc />
public partial class inital : Migration
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@ -119,31 +119,6 @@ namespace Fengling.AuthService.Migrations
table.PrimaryKey("PK_AuditLogs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OAuthApplications",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ClientId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
ClientSecret = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
DisplayName = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
RedirectUris = table.Column<string[]>(type: "text[]", nullable: false),
PostLogoutRedirectUris = table.Column<string[]>(type: "text[]", nullable: false),
Scopes = table.Column<string[]>(type: "text[]", nullable: false),
GrantTypes = table.Column<string[]>(type: "text[]", nullable: false),
ClientType = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
ConsentType = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Description = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthApplications", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OpenIddictApplications",
columns: table => new
@ -189,30 +164,6 @@ namespace Fengling.AuthService.Migrations
table.PrimaryKey("PK_OpenIddictScopes", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Tenants",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
TenantId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
ContactName = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
ContactEmail = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
ContactPhone = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),
MaxUsers = table.Column<int>(type: "integer", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Description = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
ExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tenants", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
@ -469,12 +420,6 @@ namespace Fengling.AuthService.Migrations
table: "AuditLogs",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_OAuthApplications_ClientId",
table: "OAuthApplications",
column: "ClientId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OpenIddictApplications_ClientId",
table: "OpenIddictApplications",
@ -507,12 +452,6 @@ namespace Fengling.AuthService.Migrations
table: "OpenIddictTokens",
column: "ReferenceId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Tenants_TenantId",
table: "Tenants",
column: "TenantId",
unique: true);
}
/// <inheritdoc />
@ -539,18 +478,12 @@ namespace Fengling.AuthService.Migrations
migrationBuilder.DropTable(
name: "AuditLogs");
migrationBuilder.DropTable(
name: "OAuthApplications");
migrationBuilder.DropTable(
name: "OpenIddictScopes");
migrationBuilder.DropTable(
name: "OpenIddictTokens");
migrationBuilder.DropTable(
name: "Tenants");
migrationBuilder.DropTable(
name: "AspNetRoles");

View File

@ -309,73 +309,6 @@ namespace Fengling.AuthService.Migrations
b.ToTable("AuditLogs");
});
modelBuilder.Entity("Fengling.AuthService.Models.Tenant", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ContactEmail")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("ContactName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("ContactPhone")
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTime?>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<int?>("MaxUsers")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Settings")
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("TenantId")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("TenantId")
.IsUnique();
b.ToTable("Tenants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
{
b.Property<int>("Id")
@ -689,25 +622,25 @@ namespace Fengling.AuthService.Migrations
modelBuilder.Entity("Fengling.AuthService.Models.ApplicationUser", b =>
{
b.OwnsOne("Fengling.AuthService.Models.TenantInfo", "TenantInfo", b1 =>
b.OwnsOne("Fengling.Platform.Domain.AggregatesModel.TenantAggregate.TenantInfo", "TenantInfo", b1 =>
{
b1.Property<long>("ApplicationUserId")
.HasColumnType("bigint");
b1.Property<long>("Id")
.HasColumnType("bigint")
.HasColumnName("TenantId");
b1.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TenantName");
b1.Property<string>("TenantId")
b1.Property<string>("TenantCode")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TenantCode");
b1.Property<long>("TenantId")
.HasColumnType("bigint")
.HasColumnName("TenantId");
b1.Property<string>("TenantName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TenantName");
b1.HasKey("ApplicationUserId");
b1.ToTable("AspNetUsers");

View File

@ -1,63 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Fengling.AuthService.Models;
public class Tenant
{
private long _id;
private string _tenantId;
private string _name;
[Key]
public long Id
{
get => _id;
set => _id = value;
}
[MaxLength(50)]
[Required]
public string TenantId
{
get => _tenantId;
set => _tenantId = value;
}
[MaxLength(100)]
[Required]
public string Name
{
get => _name;
set => _name = value;
}
[MaxLength(50)]
[Required]
public string ContactName { get; set; } = string.Empty;
[MaxLength(100)]
[Required]
[EmailAddress]
public string ContactEmail { get; set; } = string.Empty;
[MaxLength(20)]
public string? ContactPhone { get; set; }
public int? MaxUsers { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[MaxLength(500)]
public string? Description { get; set; }
[MaxLength(20)]
public string Status { get; set; } = "active";
public DateTime? ExpiresAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public bool IsDeleted { get; set; }
public TenantInfo Info => new(Id, TenantId, Name);
}

View File

@ -1,3 +0,0 @@
namespace Fengling.AuthService.Models;
public record TenantInfo(long Id, string TenantId, string Name);