Browse Source

feat: 添加角色管理功能

refactor/repository-structure
hyh 8 months ago
parent
commit
e0bb742fbe
  1. 70
      src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommandHandler.cs
  2. 25
      src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleCommand.cs
  3. 10
      src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleResponse.cs
  4. 12
      src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleCommand.cs
  5. 10
      src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleResponse.cs
  6. 144
      src/CellularManagement.Application/Features/Roles/Commands/RoleCommandHandler.cs
  7. 12
      src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleQuery.cs
  8. 20
      src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleResponse.cs
  9. 5
      src/CellularManagement.Domain/Entities/AppRole.cs
  10. 14
      src/CellularManagement.Domain/Entities/UserRole.cs
  11. 4
      src/CellularManagement.Infrastructure/Configurations/AppRoleConfiguration.cs
  12. 12
      src/CellularManagement.Infrastructure/Context/AppDbContext.cs
  13. 25
      src/CellularManagement.Infrastructure/Context/UnitOfWork.cs
  14. 44
      src/CellularManagement.Infrastructure/Migrations/20250427103104_AddDefaultRoles.cs
  15. 39
      src/CellularManagement.Infrastructure/Migrations/20250429070845_InitialCreate.Designer.cs
  16. 33
      src/CellularManagement.Infrastructure/Migrations/20250429070845_InitialCreate.cs
  17. 208
      src/CellularManagement.Infrastructure/Migrations/20250429071554_AddRoleDescription.Designer.cs
  18. 30
      src/CellularManagement.Infrastructure/Migrations/20250429071554_AddRoleDescription.cs
  19. 42
      src/CellularManagement.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
  20. 1
      src/CellularManagement.Infrastructure/Repositories/UserRoleRepository.cs
  21. 13
      src/CellularManagement.Presentation/Controllers/AuthController.cs
  22. 201
      src/CellularManagement.Presentation/Controllers/RolesController.cs
  23. 4
      src/CellularManagement.WebAPI/appsettings.json

70
src/CellularManagement.Application/Features/Auth/Commands/RegisterUser/RegisterUserCommandHandler.cs

@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using Microsoft.Extensions.Options;
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser;
@ -12,25 +13,25 @@ namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser;
public sealed class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, OperationResult<RegisterUserResponse>>
{
private readonly UserManager<AppUser> _userManager;
private readonly RoleManager<AppRole> _roleManager;
private readonly ILogger<RegisterUserCommandHandler> _logger;
private readonly IUserRoleRepository _userRoleRepository;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 初始化处理器
/// </summary>
public RegisterUserCommandHandler(
UserManager<AppUser> userManager,
RoleManager<AppRole> roleManager,
ILogger<RegisterUserCommandHandler> logger,
IUserRoleRepository userRoleRepository)
IUserRoleRepository userRoleRepository,
IUnitOfWork unitOfWork)
{
_userManager = userManager;
_roleManager = roleManager;
_logger = logger;
_userRoleRepository = userRoleRepository;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 处理注册请求
/// </summary>
public async Task<OperationResult<RegisterUserResponse>> Handle(
RegisterUserCommand request,
CancellationToken cancellationToken)
@ -42,7 +43,7 @@ public sealed class RegisterUserCommandHandler : IRequestHandler<RegisterUserCom
if (existingUser != null)
{
_logger.LogWarning("用户名 {UserName} 已存在", request.UserName);
return OperationResult<RegisterUserResponse>.CreateFailure("用户名已存在");
return OperationResult<RegisterUserResponse>.CreateFailure("用户名已被使用");
}
// 检查邮箱是否已存在
@ -50,7 +51,7 @@ public sealed class RegisterUserCommandHandler : IRequestHandler<RegisterUserCom
if (existingUser != null)
{
_logger.LogWarning("邮箱 {Email} 已存在", request.Email);
return OperationResult<RegisterUserResponse>.CreateFailure("邮箱已存在");
return OperationResult<RegisterUserResponse>.CreateFailure("邮箱已被使用");
}
// 创建用户
@ -61,36 +62,51 @@ public sealed class RegisterUserCommandHandler : IRequestHandler<RegisterUserCom
PhoneNumber = request.PhoneNumber
};
// 创建用户
var result = await _userManager.CreateAsync(user, request.Password);
if (!result.Succeeded)
// 在事务中执行用户创建和角色分配
await _unitOfWork.ExecuteTransactionAsync(async () =>
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("创建用户失败: {Errors}", string.Join(", ", errors));
return OperationResult<RegisterUserResponse>.CreateFailure(errors);
}
// 创建用户
var result = await _userManager.CreateAsync(user, request.Password);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("创建用户失败: {Errors}", string.Join(", ", errors));
throw new InvalidOperationException(string.Join(", ", errors));
}
// 创建用户角色关系
var userRole = new UserRole
{
UserId = user.Id,
RoleId = "2", // User 角色的 ID
User = user
};
// 获取默认角色
var defaultRole = await _roleManager.FindByNameAsync("User");
if (defaultRole == null)
{
throw new InvalidOperationException("默认用户角色不存在");
}
// 添加用户角色关系
await _userRoleRepository.AddAsync(userRole, cancellationToken);
// 创建用户角色关系
var userRole = new UserRole
{
UserId = user.Id,
RoleId = defaultRole.Id,
User = user
};
// 添加用户角色关系
await _userRoleRepository.AddAsync(userRole, cancellationToken);
}, cancellationToken: cancellationToken);
_logger.LogInformation("用户 {UserName} 注册成功", request.UserName);
// 返回注册结果
return OperationResult<RegisterUserResponse>.CreateSuccess(
new RegisterUserResponse(user.Id));
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "用户 {UserName} 注册失败: {Message}", request.UserName, ex.Message);
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "用户 {UserName} 注册失败", request.UserName);
return OperationResult<RegisterUserResponse>.CreateFailure("注册失败,请稍后重试");
return OperationResult<RegisterUserResponse>.CreateFailure("系统错误,请稍后重试");
}
}
}

25
src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleCommand.cs

@ -0,0 +1,25 @@
using MediatR;
namespace CellularManagement.Application.Features.Roles.Commands.CreateRole;
/// <summary>
/// 创建角色命令
/// </summary>
/// <example>
/// {
/// "name": "Manager",
/// "description": "部门经理角色,拥有管理权限"
/// }
/// </example>
public sealed record CreateRoleCommand(
/// <summary>
/// 角色名称
/// </summary>
/// <example>Manager</example>
string Name,
/// <summary>
/// 角色描述
/// </summary>
/// <example>部门经理角色,拥有管理权限</example>
string? Description) : IRequest<OperationResult<CreateRoleResponse>>;

10
src/CellularManagement.Application/Features/Roles/Commands/CreateRole/CreateRoleResponse.cs

@ -0,0 +1,10 @@
namespace CellularManagement.Application.Features.Roles.Commands.CreateRole;
/// <summary>
/// 创建角色响应
/// </summary>
public sealed record CreateRoleResponse(
/// <summary>
/// 角色ID
/// </summary>
string RoleId);

12
src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleCommand.cs

@ -0,0 +1,12 @@
using MediatR;
namespace CellularManagement.Application.Features.Roles.Commands.DeleteRole;
/// <summary>
/// 删除角色命令
/// </summary>
public sealed record DeleteRoleCommand(
/// <summary>
/// 角色ID
/// </summary>
string RoleId) : IRequest<OperationResult<DeleteRoleResponse>>;

10
src/CellularManagement.Application/Features/Roles/Commands/DeleteRole/DeleteRoleResponse.cs

@ -0,0 +1,10 @@
namespace CellularManagement.Application.Features.Roles.Commands.DeleteRole;
/// <summary>
/// 删除角色响应
/// </summary>
public sealed record DeleteRoleResponse(
/// <summary>
/// 是否删除成功
/// </summary>
bool Success);

144
src/CellularManagement.Application/Features/Roles/Commands/RoleCommandHandler.cs

@ -0,0 +1,144 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using CellularManagement.Domain.Entities;
using CellularManagement.Domain.Repositories;
using CellularManagement.Application.Features.Roles.Commands.CreateRole;
using CellularManagement.Application.Features.Roles.Commands.DeleteRole;
using CellularManagement.Application.Features.Roles.Queries.GetRole;
namespace CellularManagement.Application.Features.Roles.Commands;
/// <summary>
/// 角色命令处理器
/// </summary>
public sealed class RoleCommandHandler :
IRequestHandler<CreateRoleCommand, OperationResult<CreateRoleResponse>>,
IRequestHandler<DeleteRoleCommand, OperationResult<DeleteRoleResponse>>,
IRequestHandler<GetRoleQuery, OperationResult<GetRoleResponse>>
{
private readonly RoleManager<AppRole> _roleManager;
private readonly ILogger<RoleCommandHandler> _logger;
/// <summary>
/// 初始化处理器
/// </summary>
public RoleCommandHandler(
RoleManager<AppRole> roleManager,
ILogger<RoleCommandHandler> logger)
{
_roleManager = roleManager;
_logger = logger;
}
/// <summary>
/// 处理创建角色请求
/// </summary>
public async Task<OperationResult<CreateRoleResponse>> Handle(
CreateRoleCommand request,
CancellationToken cancellationToken)
{
try
{
// 检查角色是否已存在
var existingRole = await _roleManager.FindByNameAsync(request.Name);
if (existingRole != null)
{
_logger.LogWarning("角色 {RoleName} 已存在", request.Name);
return OperationResult<CreateRoleResponse>.CreateFailure("角色已存在");
}
// 创建角色
var role = new AppRole
{
Name = request.Name,
Description = request.Description
};
var result = await _roleManager.CreateAsync(role);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("创建角色失败: {Errors}", string.Join(", ", errors));
return OperationResult<CreateRoleResponse>.CreateFailure(errors);
}
_logger.LogInformation("角色 {RoleName} 创建成功", request.Name);
return OperationResult<CreateRoleResponse>.CreateSuccess(
new CreateRoleResponse(role.Id));
}
catch (Exception ex)
{
_logger.LogError(ex, "创建角色 {RoleName} 失败", request.Name);
return OperationResult<CreateRoleResponse>.CreateFailure("创建角色失败,请稍后重试");
}
}
/// <summary>
/// 处理删除角色请求
/// </summary>
public async Task<OperationResult<DeleteRoleResponse>> Handle(
DeleteRoleCommand request,
CancellationToken cancellationToken)
{
try
{
// 查找角色
var role = await _roleManager.FindByIdAsync(request.RoleId);
if (role == null)
{
_logger.LogWarning("角色 {RoleId} 不存在", request.RoleId);
return OperationResult<DeleteRoleResponse>.CreateFailure("角色不存在");
}
// 删除角色
var result = await _roleManager.DeleteAsync(role);
if (!result.Succeeded)
{
var errors = result.Errors.Select(e => e.Description).ToList();
_logger.LogWarning("删除角色失败: {Errors}", string.Join(", ", errors));
return OperationResult<DeleteRoleResponse>.CreateFailure(errors);
}
_logger.LogInformation("角色 {RoleId} 删除成功", request.RoleId);
return OperationResult<DeleteRoleResponse>.CreateSuccess(
new DeleteRoleResponse(true));
}
catch (Exception ex)
{
_logger.LogError(ex, "删除角色 {RoleId} 失败", request.RoleId);
return OperationResult<DeleteRoleResponse>.CreateFailure("删除角色失败,请稍后重试");
}
}
/// <summary>
/// 处理获取角色请求
/// </summary>
public async Task<OperationResult<GetRoleResponse>> Handle(
GetRoleQuery request,
CancellationToken cancellationToken)
{
try
{
// 查找角色
var role = await _roleManager.FindByIdAsync(request.RoleId);
if (role == null)
{
_logger.LogWarning("角色 {RoleId} 不存在", request.RoleId);
return OperationResult<GetRoleResponse>.CreateFailure("角色不存在");
}
_logger.LogInformation("获取角色 {RoleId} 成功", request.RoleId);
return OperationResult<GetRoleResponse>.CreateSuccess(
new GetRoleResponse(role.Id, role.Name, role.Description));
}
catch (Exception ex)
{
_logger.LogError(ex, "获取角色 {RoleId} 失败", request.RoleId);
return OperationResult<GetRoleResponse>.CreateFailure("获取角色失败,请稍后重试");
}
}
}

12
src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleQuery.cs

@ -0,0 +1,12 @@
using MediatR;
namespace CellularManagement.Application.Features.Roles.Queries.GetRole;
/// <summary>
/// 获取角色查询
/// </summary>
public sealed record GetRoleQuery(
/// <summary>
/// 角色ID
/// </summary>
string RoleId) : IRequest<OperationResult<GetRoleResponse>>;

20
src/CellularManagement.Application/Features/Roles/Queries/GetRole/GetRoleResponse.cs

@ -0,0 +1,20 @@
namespace CellularManagement.Application.Features.Roles.Queries.GetRole;
/// <summary>
/// 获取角色响应
/// </summary>
public sealed record GetRoleResponse(
/// <summary>
/// 角色ID
/// </summary>
string Id,
/// <summary>
/// 角色名称
/// </summary>
string Name,
/// <summary>
/// 角色描述
/// </summary>
string? Description);

5
src/CellularManagement.Domain/Entities/AppRole.cs

@ -8,6 +8,11 @@ namespace CellularManagement.Domain.Entities;
/// </summary>
public sealed class AppRole : IdentityRole
{
/// <summary>
/// 角色描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 初始化角色
/// 使用 GUID 作为 ID

14
src/CellularManagement.Domain/Entities/UserRole.cs

@ -1,12 +1,20 @@
using Microsoft.AspNetCore.Identity;
namespace CellularManagement.Domain.Entities;
/// <summary>
/// 用户角色关系实体
/// </summary>
public sealed class UserRole : IdentityUserRole<string>
public sealed class UserRole
{
/// <summary>
/// 用户ID
/// </summary>
public string UserId { get; set; } = null!;
/// <summary>
/// 角色ID
/// </summary>
public string RoleId { get; set; } = null!;
/// <summary>
/// 用户
/// </summary>

4
src/CellularManagement.Infrastructure/Configurations/AppRoleConfiguration.cs

@ -45,6 +45,10 @@ public sealed class AppRoleConfiguration : IEntityTypeConfiguration<AppRole>
.IsConcurrencyToken()
.HasComment("并发控制戳");
builder.Property(r => r.Description)
.HasMaxLength(500)
.HasComment("角色描述");
// 配置关系
builder.HasMany(r => r.RoleClaims)
.WithOne()

12
src/CellularManagement.Infrastructure/Context/AppDbContext.cs

@ -14,6 +14,11 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
{
private readonly ILogger<AppDbContext> _logger;
/// <summary>
/// 用户角色关系
/// </summary>
public new DbSet<UserRole> UserRoles { get; set; } = null!;
/// <summary>
/// 初始化数据库上下文
/// </summary>
@ -25,11 +30,6 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
_logger = logger;
}
/// <summary>
/// 用户角色关系
/// </summary>
public DbSet<UserRole> UserRoles { get; set; } = null!;
/// <summary>
/// 配置实体模型
/// </summary>
@ -41,7 +41,7 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
// 应用所有的实体配置
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
// 忽略 Identity 相关的实体
// 忽略其他 Identity 相关的实体
modelBuilder.Ignore<IdentityUserLogin<string>>();
modelBuilder.Ignore<IdentityRoleClaim<string>>();
modelBuilder.Ignore<IdentityUserClaim<string>>();

25
src/CellularManagement.Infrastructure/Context/UnitOfWork.cs

@ -243,17 +243,22 @@ public class UnitOfWork : IUnitOfWork
throw new ArgumentNullException(nameof(action));
}
var transaction = await BeginTransactionAsync(isolationLevel, cancellationToken);
try
{
await action();
await CommitTransactionAsync(transaction, cancellationToken);
}
catch
// 使用执行策略
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await RollbackTransactionAsync(cancellationToken);
throw;
}
using var transaction = await _context.Database.BeginTransactionAsync(isolationLevel, cancellationToken);
try
{
await action();
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
});
}
/// <summary>

44
src/CellularManagement.Infrastructure/Migrations/20250427103104_AddDefaultRoles.cs

@ -1,44 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CellularManagement.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddDefaultRoles : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// 插入默认角色
migrationBuilder.InsertData(
table: "Roles",
columns: new[] { "Id", "Name", "NormalizedName", "ConcurrencyStamp" },
values: new object[,]
{
{
"1", // Admin 角色 ID
"Admin",
"ADMIN",
Guid.NewGuid().ToString()
},
{
"2", // User 角色 ID
"User",
"USER",
Guid.NewGuid().ToString()
}
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// 删除插入的角色
migrationBuilder.DeleteData(
table: "Roles",
keyColumn: "Id",
keyValues: new object[] { "1", "2" });
}
}
}

39
src/CellularManagement.Infrastructure/Migrations/20250427103103_InitialCreate.Designer.cs → src/CellularManagement.Infrastructure/Migrations/20250429070845_InitialCreate.Designer.cs

@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace CellularManagement.Infrastructure.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20250427103103_InitialCreate")]
[Migration("20250429070845_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
@ -160,6 +160,43 @@ namespace CellularManagement.Infrastructure.Migrations
t.HasComment("用户表");
});
});
modelBuilder.Entity("CellularManagement.Domain.Entities.UserRole", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", null, t =>
{
t.HasComment("用户角色关系表");
});
});
modelBuilder.Entity("CellularManagement.Domain.Entities.UserRole", b =>
{
b.HasOne("CellularManagement.Domain.Entities.AppRole", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("CellularManagement.Domain.Entities.AppUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}

33
src/CellularManagement.Infrastructure/Migrations/20250427103103_InitialCreate.cs → src/CellularManagement.Infrastructure/Migrations/20250429070845_InitialCreate.cs

@ -52,6 +52,31 @@ namespace CellularManagement.Infrastructure.Migrations
},
comment: "用户表");
migrationBuilder.CreateTable(
name: "UserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "text", nullable: false),
RoleId = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_UserRoles_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserRoles_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
},
comment: "用户角色关系表");
migrationBuilder.CreateIndex(
name: "IX_Roles_Name",
table: "Roles",
@ -64,6 +89,11 @@ namespace CellularManagement.Infrastructure.Migrations
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_UserRoles_RoleId",
table: "UserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "Users",
@ -97,6 +127,9 @@ namespace CellularManagement.Infrastructure.Migrations
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserRoles");
migrationBuilder.DropTable(
name: "Roles");

208
src/CellularManagement.Infrastructure/Migrations/20250429071554_AddRoleDescription.Designer.cs

@ -0,0 +1,208 @@
// <auto-generated />
using System;
using CellularManagement.Infrastructure.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CellularManagement.Infrastructure.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20250429071554_AddRoleDescription")]
partial class AddRoleDescription
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("CellularManagement.Domain.Entities.AppRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text")
.HasComment("角色ID,主键");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text")
.HasComment("并发控制戳");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasComment("角色描述");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasComment("角色名称");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasComment("标准化角色名称(大写)");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique()
.HasDatabaseName("IX_Roles_Name");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("Roles", null, t =>
{
t.HasComment("角色表");
});
});
modelBuilder.Entity("CellularManagement.Domain.Entities.AppUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text")
.HasComment("用户ID,主键");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer")
.HasComment("登录失败次数");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text")
.HasComment("并发控制戳");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasComment("电子邮箱");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean")
.HasComment("邮箱是否已验证");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean")
.HasComment("是否启用账户锁定");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone")
.HasComment("账户锁定结束时间");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasComment("标准化电子邮箱(大写)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasComment("标准化用户名(大写)");
b.Property<string>("PasswordHash")
.HasColumnType("text")
.HasComment("密码哈希值");
b.Property<string>("PhoneNumber")
.IsRequired()
.HasColumnType("text")
.HasComment("电话号码");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean")
.HasComment("电话号码是否已验证");
b.Property<string>("SecurityStamp")
.HasColumnType("text")
.HasComment("安全戳,用于并发控制");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean")
.HasComment("是否启用双因素认证");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasComment("用户名");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique()
.HasDatabaseName("IX_Users_Email");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.HasIndex("PhoneNumber")
.IsUnique()
.HasDatabaseName("IX_Users_PhoneNumber");
b.HasIndex("UserName")
.IsUnique()
.HasDatabaseName("IX_Users_UserName");
b.ToTable("Users", null, t =>
{
t.HasComment("用户表");
});
});
modelBuilder.Entity("CellularManagement.Domain.Entities.UserRole", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", null, t =>
{
t.HasComment("用户角色关系表");
});
});
modelBuilder.Entity("CellularManagement.Domain.Entities.UserRole", b =>
{
b.HasOne("CellularManagement.Domain.Entities.AppRole", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("CellularManagement.Domain.Entities.AppUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}
}

30
src/CellularManagement.Infrastructure/Migrations/20250429071554_AddRoleDescription.cs

@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CellularManagement.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddRoleDescription : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Description",
table: "Roles",
type: "character varying(500)",
maxLength: 500,
nullable: true,
comment: "角色描述");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Description",
table: "Roles");
}
}
}

42
src/CellularManagement.Infrastructure/Migrations/AppDbContextModelSnapshot.cs

@ -33,6 +33,11 @@ namespace CellularManagement.Infrastructure.Migrations
.HasColumnType("text")
.HasComment("并发控制戳");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasComment("角色描述");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
@ -157,6 +162,43 @@ namespace CellularManagement.Infrastructure.Migrations
t.HasComment("用户表");
});
});
modelBuilder.Entity("CellularManagement.Domain.Entities.UserRole", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", null, t =>
{
t.HasComment("用户角色关系表");
});
});
modelBuilder.Entity("CellularManagement.Domain.Entities.UserRole", b =>
{
b.HasOne("CellularManagement.Domain.Entities.AppRole", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("CellularManagement.Domain.Entities.AppUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}

1
src/CellularManagement.Infrastructure/Repositories/UserRoleRepository.cs

@ -54,6 +54,7 @@ public class UserRoleRepository : IUserRoleRepository
try
{
return await _context.UserRoles
.Include(ur => ur.Role)
.Where(ur => ur.UserId == userId)
.Select(ur => ur.Role.Name)
.ToListAsync(cancellationToken);

13
src/CellularManagement.Presentation/Controllers/AuthController.cs

@ -9,6 +9,7 @@ using Microsoft.Extensions.Caching.Memory;
using CellularManagement.Infrastructure.Configurations;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Http;
using CellularManagement.Presentation.Abstractions;
namespace CellularManagement.Presentation.Controllers;
@ -16,11 +17,8 @@ namespace CellularManagement.Presentation.Controllers;
/// 认证控制器
/// 提供用户认证相关的 API 接口,包括登录和注册功能
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
public class AuthController : ApiController
{
private readonly IMediator _mediator;
private readonly ILogger<AuthController> _logger;
private readonly ICacheService _cache;
private readonly AuthConfiguration _authConfig;
@ -36,9 +34,8 @@ public class AuthController : ControllerBase
IMediator mediator,
ILogger<AuthController> logger,
ICacheService cache,
IOptions<AuthConfiguration> authConfig)
IOptions<AuthConfiguration> authConfig) : base(mediator)
{
_mediator = mediator;
_logger = logger;
_cache = cache;
_authConfig = authConfig.Value;
@ -86,7 +83,7 @@ public class AuthController : ControllerBase
}
// 执行登录
var result = await _mediator.Send(command);
var result = await mediator.Send(command);
if (!result.IsSuccess)
{
@ -148,7 +145,7 @@ public class AuthController : ControllerBase
try
{
// 执行注册
var result = await _mediator.Send(command);
var result = await mediator.Send(command);
if (result.IsSuccess)
{

201
src/CellularManagement.Presentation/Controllers/RolesController.cs

@ -0,0 +1,201 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MediatR;
using CellularManagement.Application.Features.Roles.Commands.CreateRole;
using CellularManagement.Application.Features.Roles.Commands.DeleteRole;
using CellularManagement.Application.Features.Roles.Queries.GetRole;
using CellularManagement.Application.Common;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using CellularManagement.Presentation.Abstractions;
namespace CellularManagement.Presentation.Controllers;
/// <summary>
/// 角色管理控制器
/// 提供角色管理相关的 API 接口,包括创建、删除和查询角色功能
/// </summary>
//[Authorize(Roles = "Admin")] // 只有管理员可以访问
public class RolesController : ApiController
{
private readonly ILogger<RolesController> _logger;
/// <summary>
/// 初始化角色控制器
/// </summary>
/// <param name="mediator">MediatR 中介者,用于处理命令和查询</param>
/// <param name="logger">日志记录器</param>
public RolesController(
IMediator mediator,
ILogger<RolesController> logger) : base(mediator)
{
_logger = logger;
}
/// <summary>
/// 创建新角色
/// </summary>
/// <remarks>
/// 示例请求:
///
/// POST /api/roles/create
/// {
/// "name": "Manager",
/// "description": "部门经理角色,拥有管理权限"
/// }
///
/// 成功响应:
///
/// {
/// "isSuccess": true,
/// "data": {
/// "roleId": "550e8400-e29b-41d4-a716-446655440000"
/// },
/// "errorMessages": null
/// }
///
/// 失败响应:
///
/// {
/// "isSuccess": false,
/// "data": null,
/// "errorMessages": ["角色已存在"]
/// }
///
/// </remarks>
/// <param name="command">创建角色命令,包含角色名称和描述</param>
/// <returns>
/// 创建结果,包含:
/// - 成功:返回角色ID
/// - 失败:返回错误信息
/// </returns>
/// <response code="200">创建成功,返回角色ID</response>
/// <response code="400">创建失败,返回错误信息</response>
[HttpPost]
[ProducesResponseType(typeof(OperationResult<CreateRoleResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(OperationResult<CreateRoleResponse>), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<OperationResult<CreateRoleResponse>>> CreateRole([FromBody] CreateRoleCommand command)
{
try
{
var result = await mediator.Send(command);
if (result.IsSuccess)
{
_logger.LogInformation("角色 {RoleName} 创建成功", command.Name);
}
else
{
_logger.LogWarning("角色 {RoleName} 创建失败: {Error}",
command.Name,
result.ErrorMessages?.FirstOrDefault());
}
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建角色 {RoleName} 时发生异常", command.Name);
return StatusCode(StatusCodes.Status500InternalServerError,
OperationResult<CreateRoleResponse>.CreateFailure("系统错误,请稍后重试"));
}
}
/// <summary>
/// 删除角色
/// </summary>
/// <remarks>
/// 示例请求:
///
/// DELETE /api/roles/delete/{roleId}
///
/// </remarks>
/// <param name="roleId">角色ID</param>
/// <returns>
/// 删除结果,包含:
/// - 成功:返回删除成功标志
/// - 失败:返回错误信息
/// </returns>
/// <response code="200">删除成功</response>
/// <response code="400">删除失败,返回错误信息</response>
/// <response code="404">角色不存在</response>
[HttpDelete("{roleId}")]
[ProducesResponseType(typeof(OperationResult<DeleteRoleResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(OperationResult<DeleteRoleResponse>), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(OperationResult<DeleteRoleResponse>), StatusCodes.Status404NotFound)]
public async Task<ActionResult<OperationResult<DeleteRoleResponse>>> DeleteRole(string roleId)
{
try
{
var command = new DeleteRoleCommand(roleId);
var result = await mediator.Send(command);
if (result.IsSuccess)
{
_logger.LogInformation("角色 {RoleId} 删除成功", roleId);
}
else
{
_logger.LogWarning("角色 {RoleId} 删除失败: {Error}",
roleId,
result.ErrorMessages?.FirstOrDefault());
}
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除角色 {RoleId} 时发生异常", roleId);
return StatusCode(StatusCodes.Status500InternalServerError,
OperationResult<DeleteRoleResponse>.CreateFailure("系统错误,请稍后重试"));
}
}
/// <summary>
/// 获取角色信息
/// </summary>
/// <remarks>
/// 示例请求:
///
/// GET /api/roles/get/{roleId}
///
/// </remarks>
/// <param name="roleId">角色ID</param>
/// <returns>
/// 角色信息,包含:
/// - 成功:返回角色详细信息
/// - 失败:返回错误信息
/// </returns>
/// <response code="200">获取成功,返回角色信息</response>
/// <response code="404">角色不存在</response>
[HttpGet("{roleId}")]
[ProducesResponseType(typeof(OperationResult<GetRoleResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(OperationResult<GetRoleResponse>), StatusCodes.Status404NotFound)]
public async Task<ActionResult<OperationResult<GetRoleResponse>>> GetRole(string roleId)
{
try
{
var query = new GetRoleQuery(roleId);
var result = await mediator.Send(query);
if (result.IsSuccess)
{
_logger.LogInformation("获取角色 {RoleId} 信息成功", roleId);
}
else
{
_logger.LogWarning("获取角色 {RoleId} 信息失败: {Error}",
roleId,
result.ErrorMessages?.FirstOrDefault());
}
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取角色 {RoleId} 信息时发生异常", roleId);
return StatusCode(StatusCodes.Status500InternalServerError,
OperationResult<GetRoleResponse>.CreateFailure("系统错误,请稍后重试"));
}
}
}

4
src/CellularManagement.WebAPI/appsettings.json

@ -12,7 +12,7 @@
"EnableSensitiveDataLogging": true
},
"JwtOptions": {
"SecretKey": "your-256-bit-secret-key-here-for-jwt-token-signing",
"SecretKey": "your-512-bit-secret-key-here-for-jwt-token-signing-this-is-a-very-long-key-that-is-at-least-512-bits-long-and-contains-enough-entropy",
"Issuer": "CellularManagement",
"Audience": "CellularManagement.WebAPI",
"ExpiryMinutes": 15,
@ -24,7 +24,7 @@
"ValidateAudience": true,
"ValidateLifetime": true,
"KeyRotationDays": 30,
"MinKeyLength": 32
"MinKeyLength": 64
},
"AllowedHosts": "*",
"Auth": {

Loading…
Cancel
Save