diff --git a/AGSS/Areas/Identity/Pages/Account/Login.cshtml.cs b/AGSS/Areas/Identity/Pages/Account/Login.cshtml.cs index 9411276..25f1832 100644 --- a/AGSS/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/AGSS/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -127,7 +127,8 @@ namespace AGSS.Areas.Identity.Pages.Account _logger.LogInformation("User logged in."); var user = await _userManager.FindByEmailAsync(Input.Email); - var token = _jwt.GenerateJwtToken(user); + var roles = await _userManager.GetRolesAsync(user); + var token = _jwt.GenerateJwtToken(user,roles); var frontendCallback = $"{Request.Query["frontendCallback"]}?token={token}"; diff --git a/AGSS/Areas/Identity/Pages/Account/Register.cshtml.cs b/AGSS/Areas/Identity/Pages/Account/Register.cshtml.cs index 4a1d358..8a449c6 100644 --- a/AGSS/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/AGSS/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -128,8 +128,8 @@ namespace AGSS.Areas.Identity.Pages.Account if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); - var user1 = await _userManager.FindByEmailAsync(Input.Email); - var token = _jwt.GenerateJwtToken(user1); + var roles = await _userManager.GetRolesAsync(user); + var token = _jwt.GenerateJwtToken(user,roles); var frontendCallback = $"{Request.Query["frontendCallback"]}?token={token}"; diff --git a/AGSS/Controllers/Admin/AdminRoleControllers.cs b/AGSS/Controllers/Admin/AdminRoleControllers.cs new file mode 100644 index 0000000..60ec061 --- /dev/null +++ b/AGSS/Controllers/Admin/AdminRoleControllers.cs @@ -0,0 +1,117 @@ +using AGSS.Models.Entities; +using AGSS.Models.Template; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace AGSS.Controllers.Admin; + +[Authorize(Roles = "Admin")] +[Route("api/v1/Admin/[controller]")] +public class AdminRoleControllers:ControllerBase +{ + + private readonly RoleManager _roleManager; + private readonly UserManager _userManager; // Assuming UserModel is the type of user + + public AdminRoleControllers(RoleManager roleManager, UserManager userManager) + { + _roleManager = roleManager; + _userManager = userManager; + } + + [HttpPost] + public async Task AddRole([FromBody] RoleModel role) + { + if (role == null || string.IsNullOrWhiteSpace(role.Name)) + { + + return Ok(new ReturnTemplate(400,"创建失败,请提供名字","")); + } + + var result = await _roleManager.CreateAsync(role); + if (result.Succeeded) + { + return Ok(new ReturnTemplate(200,"创建成功",role)); + + } + else + { + return Ok(new ReturnTemplate(StatusCodes.Status500InternalServerError,"创建失败","Failed to create role: " + string.Join(", ", result.Errors.Select(e => e.Description)))); + } + } + [HttpPost] + public async Task EndowRole(string userId, string roleName) + { + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return Ok(new ReturnTemplate(400, "用户不存在", "")); + } + + var role = await _roleManager.FindByNameAsync(roleName); + if (role == null) + { + return Ok(new ReturnTemplate(400, "角色不存在", "")); + } + + var result = await _userManager.AddToRoleAsync(user, role.Name); + if (result.Succeeded) + { + return Ok(new ReturnTemplate(200, "角色分配成功", user)); + } + else + { + return Ok(new ReturnTemplate(StatusCodes.Status500InternalServerError, "角色分配失败", "Failed to endow role: " + string.Join(", ", result.Errors.Select(e => e.Description)))); + } + } + + +/// +/// 通过角色查询用户,支持分页 +/// +/// + [HttpPost] + public async Task SearchUserFromRole([FromBody] SearchUserFromRoleRequest request) + { + if (string.IsNullOrWhiteSpace(request.RoleName)) + { + return Ok(new ReturnTemplate(400, "角色名称不能为空", null)); + } + + var role = await _roleManager.FindByNameAsync(request.RoleName); + if (role == null) + { + return Ok(new ReturnTemplate(400, "角色不存在", null)); + } + + var usersInRole = await _userManager.GetUsersInRoleAsync(role.Name); + var totalUsers = usersInRole.Count; + + var pagedUsers = usersInRole + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + var response = new SearchUserFromRoleResponse + { + TotalCount = totalUsers, + Users = pagedUsers + }; + + return Ok(new ReturnTemplate(200, "查询成功", response)); + } + + public class SearchUserFromRoleRequest + { + public string RoleName { get; set; } + public int Page { get; set; } = 1; + public int PageSize { get; set; } = 10; + } + + public class SearchUserFromRoleResponse + { + public int TotalCount { get; set; } + public List Users { get; set; } + } +} \ No newline at end of file diff --git a/AGSS/DbSet/UserSet.cs b/AGSS/DbSet/UserSet.cs index cc5c531..6d1fb54 100644 --- a/AGSS/DbSet/UserSet.cs +++ b/AGSS/DbSet/UserSet.cs @@ -4,11 +4,26 @@ using Microsoft.EntityFrameworkCore; namespace AGSS.DbSet { - public class ApplicationDbContext : IdentityDbContext + public class ApplicationDbContext : IdentityDbContext { + + public override DbSet Users { get; set; } + public override DbSet Roles { get; set; } + public ApplicationDbContext(DbContextOptions options) : base(options) { } + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // 在这里添加额外的配置,如果需要的话 + // 例如: + // modelBuilder.Entity().ToTable("CustomUsers"); + // modelBuilder.Entity().ToTable("CustomRoles"); + } } } \ No newline at end of file diff --git a/AGSS/Migrations/20250709054553_userrole.Designer.cs b/AGSS/Migrations/20250709054553_userrole.Designer.cs new file mode 100644 index 0000000..47bab85 --- /dev/null +++ b/AGSS/Migrations/20250709054553_userrole.Designer.cs @@ -0,0 +1,300 @@ +// +using System; +using AGSS.DbSet; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AGSS.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250709054553_userrole")] + partial class userrole + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("AGSS.Models.Entities.UserModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Birthday") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Config") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("JobCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("JobName") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Sex") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("AGSS.Models.Entities.RoleModel", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("AGSS.Models.Entities.RoleModel", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/AGSS/Migrations/20250709054553_userrole.cs b/AGSS/Migrations/20250709054553_userrole.cs new file mode 100644 index 0000000..47a1543 --- /dev/null +++ b/AGSS/Migrations/20250709054553_userrole.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AGSS.Migrations +{ + /// + public partial class userrole : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs b/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs index 5ce1461..74624bb 100644 --- a/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,6 +22,32 @@ namespace AGSS.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + modelBuilder.Entity("AGSS.Models.Entities.UserModel", b => { b.Property("Id") @@ -109,32 +135,6 @@ namespace AGSS.Migrations b.ToTable("AspNetUsers", (string)null); }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.Property("Id") @@ -243,7 +243,7 @@ namespace AGSS.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + b.HasOne("AGSS.Models.Entities.RoleModel", null) .WithMany() .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade) @@ -270,7 +270,7 @@ namespace AGSS.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + b.HasOne("AGSS.Models.Entities.RoleModel", null) .WithMany() .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade) diff --git a/AGSS/Models/Entities/User.cs b/AGSS/Models/Entities/User.cs index 949134f..7949e56 100644 --- a/AGSS/Models/Entities/User.cs +++ b/AGSS/Models/Entities/User.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Identity; namespace AGSS.Models.Entities; -public class UserModel:IdentityUser +public class UserModel:IdentityUser { public string? Sex { get; set; } @@ -18,4 +18,9 @@ public class UserModel:IdentityUser [MaxLength(20)] public string? Birthday { get; set; } +} + +public class RoleModel : IdentityRole +{ + } \ No newline at end of file diff --git a/AGSS/Utilities/Jwt.cs b/AGSS/Utilities/Jwt.cs index 894b36e..b97fbdc 100644 --- a/AGSS/Utilities/Jwt.cs +++ b/AGSS/Utilities/Jwt.cs @@ -15,28 +15,30 @@ public class Jwt _configuration = configuration; } - - - public string GenerateJwtToken(UserModel user) + public string BuildToken(IEnumerable claims) { - var claims = new[] + DateTime expires = DateTime.Now.AddDays(int.Parse(_configuration["Jwt:ExpireMinutes"])); + byte[] keyBytes = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]); + var secKey = new SymmetricSecurityKey(keyBytes); + + var credentials = new SigningCredentials(secKey, + SecurityAlgorithms.HmacSha256Signature); + var tokenDescriptor = new JwtSecurityToken(expires: expires, + signingCredentials: credentials, claims: claims); + return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor); + } + + public async Task GenerateJwtToken(UserModel user,IList roles) + { + var claims = new List(); + claims.Add(new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString())); + claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); + // var roles = await user.GetRolesAsync(user); + foreach (string role in roles) { - new Claim(JwtRegisteredClaimNames.Sub, user.Email), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - new Claim(ClaimTypes.NameIdentifier, user.Id) - }; - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( - _configuration["Jwt:Key"])); - var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - var expires = DateTime.Now.AddMinutes( - Convert.ToDouble(_configuration["Jwt:ExpireMinutes"])); - var token = new JwtSecurityToken( - issuer: _configuration["Jwt:Issuer"], - audience: _configuration["Jwt:Audience"], - claims: claims, - expires: expires, - signingCredentials: creds - ); - return new JwtSecurityTokenHandler().WriteToken(token); + claims.Add(new Claim(ClaimTypes.Role, role)); + } + string jwtToken = BuildToken(claims); + return jwtToken; } } \ No newline at end of file diff --git a/AGSS/appsettings.json b/AGSS/appsettings.json index ea024a7..f595462 100644 --- a/AGSS/appsettings.json +++ b/AGSS/appsettings.json @@ -9,10 +9,10 @@ "Microsoft": { "ClientId": "1f0c6ff3-a458-466b-ac92-decaa1d8b132", "ClientSecret": "TdY8Q~Tsm1nPl6RZMbDGmVsXblJo1xdDKCoH9ayk" - + } - -}, + + }, "AllowedHosts": "*", "Jwt": { "Issuer": "https://api.zeronode.cn/api",