新增字典功能,修复一堆bug

This commit is contained in:
罗澜大帅哥 2025-07-15 22:21:06 +08:00
parent 3370ce073e
commit 7d3cae2d8d
15 changed files with 620 additions and 826 deletions

View File

@ -136,7 +136,7 @@ namespace AGSS.Areas.Identity.Pages.Account
var roles = await _userManager.GetRolesAsync(user);
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
claims.Add(new Claim("role", role));
}
string jwtToken = _jwt.BuildToken(claims, jwtOptions.Value);

View File

@ -148,7 +148,7 @@ namespace AGSS.Areas.Identity.Pages.Account
var roles = await _userManager.GetRolesAsync(user);
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
claims.Add(new Claim("role", role));
}
string jwtToken = _jwt.BuildToken(claims, jwtOptions.Value);
var frontendCallback = $"{Request.Query["frontendCallback"]}?token={jwtToken}";

View File

@ -1,203 +0,0 @@
using System;
using AGSS.Models.Entities;
using AGSS.Models.Template;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;
using AGSS.DbSet;
namespace AGSS.Controllers.Admin
{
[Route("api/v1/[controller]/[action]")]
public class AdminDictionaryController : ControllerBase
{
private readonly ApplicationDbContext _dbContext;
private readonly UserManager<UserModel> _userManager;
public AdminDictionaryController(ApplicationDbContext dbContext, UserManager<UserModel> userManager)
{
_dbContext = dbContext;
_userManager = userManager;
}
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> GetParentDictionaries([FromBody] string label)
{
// 确保 label 不是 null
label ??= string.Empty;
var parentDictionaries = _dbContext.Dictionaries.Where(d => d.ParentId == null && (string.IsNullOrEmpty(label) || d.Label.Contains(label))).ToList();
return Ok(new ReturnTemplate(200, "查询成功", parentDictionaries));
}
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> AddParentDictionary([FromBody] DictionaryModel dictionary)
{
if (dictionary == null || string.IsNullOrWhiteSpace(dictionary.Label) || string.IsNullOrWhiteSpace(dictionary.Value))
{
return Ok(new ReturnTemplate(400, "请求参数无效请提供Label和Value", null));
}
dictionary.Uuid = Guid.NewGuid().ToString();
dictionary.CreateTime = DateTime.UtcNow;
dictionary.CreateUserId = _userManager.GetUserId(User);
_dbContext.Dictionaries.Add(dictionary);
await _dbContext.SaveChangesAsync();
return Ok(new ReturnTemplate(200, "添加父级字典成功", dictionary));
}
[HttpPut]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> UpdateParentDictionary([FromBody] DictionaryModel dictionary)
{
if (dictionary == null || string.IsNullOrWhiteSpace(dictionary.Uuid) || string.IsNullOrWhiteSpace(dictionary.Label))
{
return Ok(new ReturnTemplate(400, "请求参数无效请提供Uuid和Label", null));
}
var existingDictionary = _dbContext.Dictionaries.FirstOrDefault(d => d.Uuid == dictionary.Uuid && d.ParentId == null);
if (existingDictionary == null)
{
return Ok(new ReturnTemplate(404, "未找到指定的父级字典", null));
}
existingDictionary.Label = dictionary.Label;
await _dbContext.SaveChangesAsync();
return Ok(new ReturnTemplate(200, "更新父级字典成功", existingDictionary));
}
[HttpDelete]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteParentDictionary([FromBody] string uuid)
{
if (string.IsNullOrWhiteSpace(uuid))
{
return Ok(new ReturnTemplate(400, "请求参数无效请提供Uuid", null));
}
var parentDictionary = _dbContext.Dictionaries.FirstOrDefault(d => d.Uuid == uuid && d.ParentId == null);
if (parentDictionary == null)
{
return Ok(new ReturnTemplate(404, "未找到指定的父级字典", null));
}
_dbContext.Dictionaries.RemoveRange(_dbContext.Dictionaries.Where(d => d.ParentId == uuid));
_dbContext.Dictionaries.Remove(parentDictionary);
await _dbContext.SaveChangesAsync();
return Ok(new ReturnTemplate(200, "删除父级字典成功", null));
}
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> CreateChildDictionary([FromBody] DictionaryModel dictionary)
{
if (dictionary == null || string.IsNullOrWhiteSpace(dictionary.ParentId) || string.IsNullOrWhiteSpace(dictionary.ParentValue) || string.IsNullOrWhiteSpace(dictionary.Label) || string.IsNullOrWhiteSpace(dictionary.Value))
{
return Ok(new ReturnTemplate(400, "请求参数无效请提供ParentId、ParentValue、Label和Value", null));
}
dictionary.Uuid = Guid.NewGuid().ToString();
dictionary.CreateTime = DateTime.UtcNow;
dictionary.CreateUserId = _userManager.GetUserId(User);
_dbContext.Dictionaries.Add(dictionary);
await _dbContext.SaveChangesAsync();
return Ok(new ReturnTemplate(200, "创建子级字典成功", dictionary));
}
[HttpPut]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> UpdateChildDictionary([FromBody] DictionaryModel dictionary)
{
if (dictionary == null || string.IsNullOrWhiteSpace(dictionary.Uuid) || string.IsNullOrWhiteSpace(dictionary.Label) || string.IsNullOrWhiteSpace(dictionary.Value))
{
return Ok(new ReturnTemplate(400, "请求参数无效请提供Uuid、Label和Value", null));
}
var existingDictionary = _dbContext.Dictionaries.FirstOrDefault(d => d.Uuid == dictionary.Uuid && d.ParentId != null);
if (existingDictionary == null)
{
return Ok(new ReturnTemplate(404, "未找到指定的子级字典", null));
}
existingDictionary.Label = dictionary.Label;
existingDictionary.LabelEn = dictionary.LabelEn;
existingDictionary.Remark = dictionary.Remark;
existingDictionary.Value = dictionary.Value;
existingDictionary.Tag = dictionary.Tag;
await _dbContext.SaveChangesAsync();
return Ok(new ReturnTemplate(200, "更新子级字典成功", existingDictionary));
}
[HttpDelete]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteChildDictionary([FromBody] string uuid)
{
if (string.IsNullOrWhiteSpace(uuid))
{
return Ok(new ReturnTemplate(400, "请求参数无效请提供Uuid", null));
}
var childDictionary = _dbContext.Dictionaries.FirstOrDefault(d => d.Uuid == uuid && d.ParentId != null);
if (childDictionary == null)
{
return Ok(new ReturnTemplate(404, "未找到指定的子级字典", null));
}
_dbContext.Dictionaries.Remove(childDictionary);
await _dbContext.SaveChangesAsync();
return Ok(new ReturnTemplate(200, "删除子级字典成功", null));
}
[HttpPost]
public IActionResult GetChildDictionaries([FromBody] ChildDictionaryRequest request)
{
if (string.IsNullOrWhiteSpace(request.Value))
{
return Ok(new ReturnTemplate(400, "请求参数无效请提供Value", null));
}
var childDictionaries = _dbContext.Dictionaries
.Where(d => d.ParentValue == request.Value &&
(request.Tag == null || (!string.IsNullOrEmpty(d.Tag) && d.Tag.Contains("," + request.Tag + ","))))
.ToList();
if (request.Tag != null)
{
childDictionaries = childDictionaries.Where(d => !string.IsNullOrEmpty(d.Tag)).ToList();
}
return Ok(new ReturnTemplate(200, "查询成功", childDictionaries));
}
public class ChildDictionaryRequest
{
public string Value { get; set; }
public string Tag { get; set; }
}
}
}

View File

@ -18,8 +18,8 @@ namespace AGSS.Controllers.Admin;
/// 控制器类,用于管理角色相关的操作,包括添加角色、分配角色给用户以及通过角色查询用户。
/// 该控制器仅限具有"Admin"角色的用户访问。
/// </summary>
[Authorize]
[Route("api/v1/[controller]/[action]")]
[Authorize(Roles = "Admin")]
[Route("api/v1/[controller]")]
public class AdminRoleControllers:ControllerBase
{
/// <summary>
@ -60,7 +60,7 @@ public class AdminRoleControllers:ControllerBase
/// </summary>
/// <param name="role">要添加的角色信息</param>
/// <returns>返回操作结果,包含状态码、消息和数据</returns>
[HttpPost]
[HttpPost("role")]
public async Task<IActionResult> AddRole(string rolename,string normalizedname)
{
@ -84,7 +84,8 @@ public class AdminRoleControllers:ControllerBase
/// <param name="userId">用户的唯一标识符</param>
/// <param name="roleName">要分配的角色名称</param>
/// <returns>返回一个包含操作结果的ReturnTemplate对象其中Code表示状态码Msg表示消息Data表示附加数据如果有的话</returns>
[HttpPost]
[HttpPost("role/endow")]
public async Task<IActionResult> EndowRole(string userId, string roleName)
{
var user = await _userManager.FindByIdAsync(userId);
@ -116,7 +117,7 @@ public class AdminRoleControllers:ControllerBase
/// </summary>
/// <param name="userId">要删除的用户的唯一标识符。</param>
/// <returns>返回操作结果包含状态码、消息和数据。如果删除成功则返回200状态码如果用户ID为空或未找到指定用户则分别返回400或404状态码若删除过程中出现错误则返回500状态码并附带错误信息。</returns>
[HttpPost]
[HttpDelete("user")]
public async Task<IActionResult> DelUser(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
@ -143,7 +144,7 @@ public class AdminRoleControllers:ControllerBase
}
[HttpPost]
[HttpPost("menu")]
public async Task<IActionResult> SetMenu([FromBody]UserMenuRequest request)
{
if (string.IsNullOrWhiteSpace(request.Id) || string.IsNullOrWhiteSpace(request.MenuName))
@ -169,7 +170,7 @@ public class AdminRoleControllers:ControllerBase
}
[HttpGet]
[HttpGet("role/all")]
public async Task<IActionResult> AllRole()
{
@ -184,7 +185,7 @@ public class AdminRoleControllers:ControllerBase
/// </summary>
/// <param name="request">包含角色名称、页码和每页大小的请求对象</param>
/// <returns>返回包含总用户数和当前页用户的响应对象</returns>
[HttpPost]
[HttpPost("user")]
public async Task<IActionResult> SearchUserFromRole([FromBody] SearchUserFromRoleRequest request)
{

View File

@ -0,0 +1,228 @@
using System.ComponentModel.DataAnnotations;
using AGSS.Models.Template;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AGSS.Controllers.Dict
{
[ApiController]
[Route("api/[controller]")]
// [Produces("application/json")]
public class DictController : ControllerBase
{
private readonly DictService _dictService;
private readonly ILogger<DictController> _logger;
public DictController(DictService dictService, ILogger<DictController> logger)
{
_dictService = dictService;
_logger = logger;
}
// 1. 获取父级列表
[HttpGet("parents")]
public async Task<ActionResult> GetParents([FromQuery] string? label)
{
try
{
var result = await _dictService.GetParentsAsync(label);
return Ok(new ReturnTemplate(200, "获取成功啦!", result));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(500, "失败,服务器类错误!", ex));
}
}
// 2. 创建父级项
// [Authorize(Roles = "Admin")]
[HttpPost("parents")]
public async Task<ActionResult> CreateParent([FromBody] CreateParentRequest request)
{
try
{
var result = await _dictService.CreateParentAsync(request.Label, request.Value);
return Ok(new ReturnTemplate(200, "创建成功啦!", result));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(500, "错误", ex.Message));
}
}
// 请求模型
public class CreateParentRequest
{
[Required(ErrorMessage = "字典标签不能为空")]
[StringLength(100, ErrorMessage = "标签长度不能超过100字符")]
public string Label { get; set; } = null!;
[Required(ErrorMessage = "字典值不能为空")]
[StringLength(50, ErrorMessage = "值长度不能超过50字符")]
public string Value { get; set; } = null!;
}
// 2.1 更新父级项
[Authorize(Roles = "Admin")]
[HttpPut("parents/{uuid}")]
public async Task<ActionResult> UpdateParent(
[FromRoute] Guid uuid,
[FromBody] UpdateParentRequest request)
{
try
{
await _dictService.UpdateParentAsync(uuid, request.Label);
return Ok(new ReturnTemplate(200, "创建成功啦!", ""));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(200, "失败", ex));
}
}
public class UpdateParentRequest
{
[Required(ErrorMessage = "字典标签不能为空")]
[StringLength(100, ErrorMessage = "标签长度不能超过100字符")]
public string Label { get; set; } = null!;
}
// 3. 删除父级项
[Authorize(Roles = "Admin")]
[HttpDelete("parents/{uuid}")]
public async Task<ActionResult> DeleteParent([FromRoute] Guid uuid)
{
try
{
await _dictService.DeleteParentAsync(uuid);
return Ok(new ReturnTemplate(200, "删除成功啦!", ""));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(200, "失败", ex));
}
}
// 4. 创建子项
[HttpPost("children")]
public async Task<ActionResult> CreateChild([FromBody] CreateChildRequest request)
{
try
{
var result = await _dictService.CreateChildAsync(
request.ParentId,
request.ParentValue,
request.Label,
request.Value,
request.LabelEn,
request.Remark,
request.Tag);
return Ok(new ReturnTemplate(200, "创建成功啦!", result));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(200, "我们遇到了一些问题,无法删除!", ""));
}
}
public class CreateChildRequest
{
[Required(ErrorMessage = "父级ID不能为空")] public Guid ParentId { get; set; }
[Required(ErrorMessage = "父级值不能为空")] public string ParentValue { get; set; } = null!;
[Required(ErrorMessage = "字典标签不能为空")] public string Label { get; set; } = null!;
[Required(ErrorMessage = "字典值不能为空")] public string Value { get; set; } = null!;
public string? LabelEn { get; set; }
public string? Remark { get; set; }
public string? Tag { get; set; }
}
// 4.1 更新子项
[HttpPut("children/{uuid}")]
public async Task<ActionResult> UpdateChild(
[FromRoute] Guid uuid,
[FromBody] UpdateChildRequest request)
{
try
{
await _dictService.UpdateChildAsync(
uuid,
request.Label,
request.Value,
request.LabelEn,
request.Remark,
request.Tag);
return Ok(new ReturnTemplate(200, "更新成功啦!", ""));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(200, "失败!", ""));
}
}
public class UpdateChildRequest
{
[Required(ErrorMessage = "字典标签不能为空")] public string Label { get; set; } = null!;
[Required(ErrorMessage = "字典值不能为空")] public string Value { get; set; } = null!;
public string? LabelEn { get; set; }
public string? Remark { get; set; }
public string? Tag { get; set; }
}
// 5. 删除子项
[HttpDelete("children/{uuid}")]
public async Task<ActionResult> DeleteChild([FromRoute] Guid uuid)
{
try
{
await _dictService.DeleteChildAsync(uuid);
return Ok(new ReturnTemplate(200, "删除成功啦!", ""));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(200, "删除失败!", ""));
}
}
// 6. 获取子项
[HttpGet("children")]
public async Task<ActionResult> GetChildren(
[FromQuery, Required] string value,
[FromQuery] string? tag = null)
{
try
{
var result = await _dictService.GetChildrenAsync(value, tag);
return Ok(new ReturnTemplate(200, "查询成功啦!", result));
}
catch (Exception ex)
{
return Ok(new ReturnTemplate(200, "查询失败!", ""));
}
}
}
// 统一响应模型
public class ApiResponse
{
public int Code { get; set; }
public string Message { get; set; } = null!;
}
public class ApiResponse<T> : ApiResponse
{
public T? Data { get; set; }
}
}

View File

@ -4,27 +4,58 @@ using Microsoft.EntityFrameworkCore;
namespace AGSS.DbSet
{
public class ApplicationDbContext : IdentityDbContext<UserModel,RoleModel,string>
public class ApplicationDbContext : IdentityDbContext<UserModel, RoleModel, string>
{
public override DbSet<UserModel> Users { get; set; }
public override DbSet<RoleModel> Roles { get; set; }
public DbSet<DictionaryModel> Dictionaries { get; set; }
public DbSet<DictItem> DictItems { get; set; }
public DbSet<MenuModel> Menus { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<DictItem>(entity =>
{
entity.HasKey(e => e.Uuid);
entity.Property(e => e.ParentId).HasColumnName("parentid");
entity.Property(e => e.Tag).HasColumnName("tag");
entity.Property(e => e.CreateTime).HasColumnName("createtime");
entity.Property(e => e.ParentValue).HasColumnName("parentvalue");
entity.Property(e => e.Value).HasColumnName("value");
entity.Property(e => e.Label).HasColumnName("label");
entity.Property(e => e.Tag).HasColumnName("tag");
});
// 配置自引用关系
modelBuilder.Entity<DictItem>()
.HasOne(d => d.Parent)
.WithMany(d => d.Children)
.HasForeignKey(d => d.ParentId)
.OnDelete(DeleteBehavior.Cascade); // 级联删除
// 添加索引
modelBuilder.Entity<DictItem>()
.HasIndex(d => d.ParentId);
modelBuilder.Entity<DictItem>()
.HasIndex(d => d.ParentValue);
modelBuilder.Entity<DictItem>()
.HasIndex(d => d.Value);
modelBuilder.Entity<DictItem>()
.HasIndex(d => d.Tag);
// 确保顶级项的约束
modelBuilder.Entity<DictItem>()
.HasCheckConstraint("CK_TopLevelItems",
"ParentId IS NULL OR (Tag IS NOT NULL AND ParentValue IS NOT NULL)");
modelBuilder.Entity<DictionaryModel>()
.HasKey(d => d.Uuid); // 假设 Id 是 DictionaryModel 的主键字段
modelBuilder.Entity<MenuModel>()
.HasKey(m => m.Uuid);

View File

@ -1,425 +0,0 @@
// <auto-generated />
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("20250714035418_AddMenuTableV2")]
partial class AddMenuTableV2
{
/// <inheritdoc />
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.DictionaryModel", b =>
{
b.Property<string>("Uuid")
.HasColumnType("text");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreateUserId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Label")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LabelEn")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Remark")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tag")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Uuid");
b.ToTable("Dictionaries");
});
modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b =>
{
b.Property<string>("Uuid")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Adaptability")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Component")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Icon")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Label")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("MenuCode")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("ParentId")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Query")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<int>("Sort")
.HasColumnType("integer");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(1)
.HasColumnType("character varying(1)");
b.Property<DateTime?>("UpdateTime")
.HasColumnType("timestamp with time zone");
b.HasKey("Uuid");
b.HasIndex("ParentId");
b.ToTable("Menus");
});
modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("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<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("Birthday")
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Config")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Description")
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<string>("JobCode")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("JobName")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("MenuCode")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("MenuName")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<string>("Sex")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b =>
{
b.HasOne("AGSS.Models.Entities.MenuModel", null)
.WithMany()
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("AGSS.Models.Entities.RoleModel", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,22 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AGSS.Migrations
{
/// <inheritdoc />
public partial class AddMenuTableV2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -22,51 +22,6 @@ namespace AGSS.Migrations
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("AGSS.Models.Entities.DictionaryModel", b =>
{
b.Property<string>("Uuid")
.HasColumnType("text");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreateUserId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Label")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LabelEn")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Remark")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tag")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Uuid");
b.ToTable("Dictionaries");
});
modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b =>
{
b.Property<string>("Uuid")
@ -252,6 +207,68 @@ namespace AGSS.Migrations
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("DictItem", b =>
{
b.Property<Guid>("Uuid")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("createtime");
b.Property<Guid>("CreateUserId")
.HasColumnType("uuid");
b.Property<string>("Label")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("label");
b.Property<string>("LabelEn")
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<Guid?>("ParentId")
.HasColumnType("uuid")
.HasColumnName("parentid");
b.Property<string>("ParentValue")
.HasColumnType("text")
.HasColumnName("parentvalue");
b.Property<string>("Remark")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("Tag")
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasColumnName("tag");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("value");
b.HasKey("Uuid");
b.HasIndex("ParentId");
b.HasIndex("ParentValue");
b.HasIndex("Tag");
b.HasIndex("Value");
b.ToTable("DictItems", t =>
{
t.HasCheckConstraint("CK_TopLevelItems", "ParentId IS NULL OR (Tag IS NOT NULL AND ParentValue IS NOT NULL)");
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
@ -366,6 +383,16 @@ namespace AGSS.Migrations
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("DictItem", b =>
{
b.HasOne("DictItem", "Parent")
.WithMany("Children")
.HasForeignKey("ParentId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Parent");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("AGSS.Models.Entities.RoleModel", null)
@ -416,6 +443,11 @@ namespace AGSS.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("DictItem", b =>
{
b.Navigation("Children");
});
#pragma warning restore 612, 618
}
}

View File

@ -9,7 +9,6 @@ namespace AGSS.Models.DTOs
public string ParentId { get; set; }
public string ParentValue { get; set; }
public string Label { get; set; }
public string LabelEn { get; set; }
public string Remark { get; set; }
public string Value { get; set; }
public string Tag { get; set; }

View File

@ -1,18 +1,37 @@
using System;
using System.Collections.Generic;
namespace AGSS.Models.Entities
using System.ComponentModel.DataAnnotations;
public class DictItem
{
public class DictionaryModel
{
public string Uuid { get; set; }
public string ParentId { get; set; }
public string ParentValue { get; set; }
public string Label { get; set; }
public string LabelEn { get; set; }
public string Remark { get; set; }
public string Value { get; set; }
public string Tag { get; set; }
public DateTime CreateTime { get; set; }
public string CreateUserId { get; set; }
}
}
// 主键
public Guid Uuid { get; set; } = Guid.NewGuid();
// 层级关系
public Guid? ParentId { get; set; }
public string? ParentValue { get; set; }
// 基础信息
[Required]
[StringLength(100)]
public string Label { get; set; } = null!; // 中文标签
[StringLength(100)]
public string? LabelEn { get; set; } // 英文标签
[StringLength(500)]
public string? Remark { get; set; } // 备注
[Required]
[StringLength(50)]
public string Value { get; set; } = null!; // 字典值
[StringLength(500)]
public string? Tag { get; set; } // 逗号分隔的标签
// 审计字段
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
public Guid CreateUserId { get; set; }
// 导航属性
public DictItem? Parent { get; set; }
public ICollection<DictItem> Children { get; set; } = new List<DictItem>();
}

View File

@ -1,4 +1,5 @@
using System.Reflection;
using System.Security.Claims;
using System.Text;
using AGSS.DbSet;
using AGSS.Models;
@ -56,6 +57,8 @@ builder.Services.AddIdentityCore<UserModel>(options =>
builder.Services.AddScoped<Jwt>();
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<MenuService>();
builder.Services.AddScoped<DictService>();
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
@ -82,6 +85,10 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
builder.Services.AddAuthorization();
builder.Services.Configure<JwtBearerOptions>(options =>
{
options.TokenValidationParameters.RoleClaimType = "role";
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

View File

@ -0,0 +1,25 @@
// 用于获取当前用户信息
using System.Security.Claims;
public interface ICurrentUserService
{
Guid UserId { get; }
bool IsAdmin { get; }
}
public class CurrentUserService : ICurrentUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Guid UserId =>
Guid.Parse(_httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value);
public bool IsAdmin =>
_httpContextAccessor.HttpContext?.User?.IsInRole("luolan") ?? false;
}

View File

@ -0,0 +1,175 @@
using AGSS.DbSet;
using Microsoft.EntityFrameworkCore;
public class DictService
{
private readonly ApplicationDbContext _context;
public DictService(ApplicationDbContext context, ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
private readonly ICurrentUserService _currentUserService;
// 1. 获取父级列表
public async Task<List<DictItem>> GetParentsAsync(string? label)
{
var query = _context.DictItems
.Where(d => d.ParentId == null);
if (!string.IsNullOrWhiteSpace(label))
{
query = query.Where(d => d.Label.Contains(label));
}
return await query.ToListAsync();
}
// 2. 创建父级项
public async Task<DictItem> CreateParentAsync(string label, string value)
{
if (!_currentUserService.IsAdmin)
throw new UnauthorizedAccessException("您不是管理员,无权限");
var newItem = new DictItem
{
Label = label,
Value = value,
CreateUserId = _currentUserService.UserId
};
_context.DictItems.Add(newItem);
await _context.SaveChangesAsync();
return newItem;
}
// 2.1 更新父级项
public async Task UpdateParentAsync(Guid uuid, string label)
{
if (!_currentUserService.IsAdmin)
throw new UnauthorizedAccessException("您不是管理员,无权限");
var item = await _context.DictItems
.FirstOrDefaultAsync(d => d.Uuid == uuid && d.ParentId == null);
if (item == null) throw new KeyNotFoundException("字典项不存在");
item.Label = label;
await _context.SaveChangesAsync();
}
// 3. 删除父级项(级联删除)
public async Task DeleteParentAsync(Guid uuid)
{
if (!_currentUserService.IsAdmin)
throw new UnauthorizedAccessException("您不是管理员,无权限");
var item = await _context.DictItems
.Include(d => d.Children)
.FirstOrDefaultAsync(d => d.Uuid == uuid && d.ParentId == null);
if (item == null) throw new KeyNotFoundException("字典项不存在");
_context.DictItems.Remove(item);
await _context.SaveChangesAsync();
}
// 4. 创建子项
public async Task<DictItem> CreateChildAsync(
Guid parentId,
string parentValue,
string label,
string value,
string? labelEn = null,
string? remark = null,
string? tag = null)
{
if (!_currentUserService.IsAdmin)
throw new UnauthorizedAccessException("您不是管理员,无权限");
var parent = await _context.DictItems
.FirstOrDefaultAsync(d => d.Uuid == parentId && d.ParentId == null);
if (parent == null) throw new KeyNotFoundException("父级字典项不存在");
var newItem = new DictItem
{
ParentId = parentId,
ParentValue = parentValue,
Label = label,
Value = value,
LabelEn = labelEn,
Remark = remark,
Tag = tag,
CreateUserId = _currentUserService.UserId
};
_context.DictItems.Add(newItem);
await _context.SaveChangesAsync();
return newItem;
}
// 4.1 更新子项
public async Task UpdateChildAsync(
Guid uuid,
string label,
string value,
string? labelEn = null,
string? remark = null,
string? tag = null)
{
if (!_currentUserService.IsAdmin)
throw new UnauthorizedAccessException("您不是管理员,无权限");
var item = await _context.DictItems
.FirstOrDefaultAsync(d => d.Uuid == uuid && d.ParentId != null);
if (item == null) throw new KeyNotFoundException("字典项不存在");
item.Label = label;
item.Value = value;
item.LabelEn = labelEn;
item.Remark = remark;
item.Tag = tag;
await _context.SaveChangesAsync();
}
// 5. 删除子项
public async Task DeleteChildAsync(Guid uuid)
{
if (!_currentUserService.IsAdmin)
throw new UnauthorizedAccessException("您不是管理员,无权限");
var item = await _context.DictItems
.FirstOrDefaultAsync(d => d.Uuid == uuid && d.ParentId != null);
if (item == null) throw new KeyNotFoundException("字典项不存在");
_context.DictItems.Remove(item);
await _context.SaveChangesAsync();
}
// 6. 获取子项(带标签过滤)
public async Task<List<DictItem>> GetChildrenAsync(string value, string? tag = null)
{
var query = _context.DictItems
.Where(d => d.ParentValue == value);
if (!string.IsNullOrWhiteSpace(tag))
{
// 精确标签匹配逻辑
var tags = tag.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.Trim())
.ToList();
query = query.Where(d => d.Tag != null &&
tags.All(t => d.Tag.Contains(t)));
}
return await query.ToListAsync();
}
}

View File

@ -1,105 +1,32 @@
CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
"MigrationId" character varying(150) NOT NULL,
"ProductVersion" character varying(32) NOT NULL,
CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId")
START TRANSACTION;
DROP TABLE "Dictionaries";
CREATE TABLE "DictItems" (
"Uuid" uuid NOT NULL,
parentid uuid,
parentvalue text,
label character varying(100) NOT NULL,
"LabelEn" character varying(100),
"Remark" character varying(500),
value character varying(50) NOT NULL,
tag character varying(500),
createtime timestamp with time zone NOT NULL,
"CreateUserId" uuid NOT NULL,
CONSTRAINT "PK_DictItems" PRIMARY KEY ("Uuid"),
CONSTRAINT "CK_TopLevelItems" CHECK (ParentId IS NULL OR (Tag IS NOT NULL AND ParentValue IS NOT NULL)),
CONSTRAINT "FK_DictItems_DictItems_parentid" FOREIGN KEY (parentid) REFERENCES "DictItems" ("Uuid") ON DELETE CASCADE
);
START TRANSACTION;
CREATE TABLE "AspNetRoles" (
"Id" text NOT NULL,
"Name" character varying(256),
"NormalizedName" character varying(256),
"ConcurrencyStamp" text,
CONSTRAINT "PK_AspNetRoles" PRIMARY KEY ("Id")
);
CREATE INDEX "IX_DictItems_parentid" ON "DictItems" (parentid);
CREATE TABLE "AspNetUsers" (
"Id" text NOT NULL,
"Sex" text,
"Description" character varying(100),
"Config" character varying(200),
"JobCode" character varying(10),
"JobName" character varying(10),
"Birthday" character varying(20),
"MenuCode" character varying(500),
"MenuName" character varying(500),
"UserName" character varying(256),
"NormalizedUserName" character varying(256),
"Email" character varying(256),
"NormalizedEmail" character varying(256),
"EmailConfirmed" boolean NOT NULL,
"PasswordHash" text,
"SecurityStamp" text,
"ConcurrencyStamp" text,
"PhoneNumber" text,
"PhoneNumberConfirmed" boolean NOT NULL,
"TwoFactorEnabled" boolean NOT NULL,
"LockoutEnd" timestamp with time zone,
"LockoutEnabled" boolean NOT NULL,
"AccessFailedCount" integer NOT NULL,
CONSTRAINT "PK_AspNetUsers" PRIMARY KEY ("Id")
);
CREATE INDEX "IX_DictItems_parentvalue" ON "DictItems" (parentvalue);
CREATE TABLE "AspNetRoleClaims" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
"RoleId" text NOT NULL,
"ClaimType" text,
"ClaimValue" text,
CONSTRAINT "PK_AspNetRoleClaims" PRIMARY KEY ("Id"),
CONSTRAINT "FK_AspNetRoleClaims_AspNetRoles_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AspNetRoles" ("Id") ON DELETE CASCADE
);
CREATE INDEX "IX_DictItems_tag" ON "DictItems" (tag);
CREATE TABLE "AspNetUserClaims" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
"UserId" text NOT NULL,
"ClaimType" text,
"ClaimValue" text,
CONSTRAINT "PK_AspNetUserClaims" PRIMARY KEY ("Id"),
CONSTRAINT "FK_AspNetUserClaims_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
);
CREATE TABLE "AspNetUserLogins" (
"LoginProvider" text NOT NULL,
"ProviderKey" text NOT NULL,
"ProviderDisplayName" text,
"UserId" text NOT NULL,
CONSTRAINT "PK_AspNetUserLogins" PRIMARY KEY ("LoginProvider", "ProviderKey"),
CONSTRAINT "FK_AspNetUserLogins_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
);
CREATE TABLE "AspNetUserRoles" (
"UserId" text NOT NULL,
"RoleId" text NOT NULL,
CONSTRAINT "PK_AspNetUserRoles" PRIMARY KEY ("UserId", "RoleId"),
CONSTRAINT "FK_AspNetUserRoles_AspNetRoles_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AspNetRoles" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_AspNetUserRoles_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
);
CREATE TABLE "AspNetUserTokens" (
"UserId" text NOT NULL,
"LoginProvider" text NOT NULL,
"Name" text NOT NULL,
"Value" text,
CONSTRAINT "PK_AspNetUserTokens" PRIMARY KEY ("UserId", "LoginProvider", "Name"),
CONSTRAINT "FK_AspNetUserTokens_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
);
CREATE INDEX "IX_AspNetRoleClaims_RoleId" ON "AspNetRoleClaims" ("RoleId");
CREATE UNIQUE INDEX "RoleNameIndex" ON "AspNetRoles" ("NormalizedName");
CREATE INDEX "IX_AspNetUserClaims_UserId" ON "AspNetUserClaims" ("UserId");
CREATE INDEX "IX_AspNetUserLogins_UserId" ON "AspNetUserLogins" ("UserId");
CREATE INDEX "IX_AspNetUserRoles_RoleId" ON "AspNetUserRoles" ("RoleId");
CREATE INDEX "EmailIndex" ON "AspNetUsers" ("NormalizedEmail");
CREATE UNIQUE INDEX "UserNameIndex" ON "AspNetUsers" ("NormalizedUserName");
CREATE INDEX "IX_DictItems_value" ON "DictItems" (value);
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20250709144855_Initial', '9.0.6');
VALUES ('20250715135627_dic1', '9.0.6');
COMMIT;