Merge pull request #136 from AIDotNet/feature_role

Feature role
This commit is contained in:
xuzeyu
2025-11-06 10:32:08 +08:00
committed by GitHub
24 changed files with 1033 additions and 11 deletions

140
docs/RBAC_README.md Normal file
View File

@@ -0,0 +1,140 @@
# 角色基础授权系统 (Role-Based Access Control)
## 概述
本系统实现了完整的角色基础授权功能,支持将权限绑定到角色,角色再绑定给用户,提供灵活的权限管理能力。
## 数据库结构
### 核心表
1. **Roles (角色表)**
- Id: 角色ID (主键)
- Name: 角色名称
- Code: 角色编码 (用于授权验证)
- Description: 角色描述
- IsEnabled: 是否启用
- CreateTime: 创建时间
2. **Permissions (权限表)**
- Id: 权限ID (主键)
- Name: 权限名称
- Code: 权限编码 (用于授权验证)
- Type: 权限类型 (Menu-菜单权限, Operation-操作权限)
- Description: 权限描述
- CreateTime: 创建时间
3. **RolePermissions (角色权限关联表)**
- Id: 关联ID (主键)
- RoleId: 角色ID
- PermissionId: 权限ID
- CreateTime: 创建时间
4. **UserRoles (用户角色关联表)**
- Id: 关联ID (主键)
- UserId: 用户ID
- RoleId: 角色ID
- CreateTime: 创建时间
## 默认角色和权限
系统首次运行时会自动初始化以下角色和权限:
### 默认角色
1. **AntSKAdmin (管理员)**
- 拥有系统所有权限
- 可以管理用户、角色、权限等
2. **AntSKUser (普通用户)**
- 拥有基本功能权限
- 包括:聊天、应用、知识库
### 默认权限
系统包含以下菜单权限:
- chat: 聊天
- app: 应用
- kms: 知识库
- plugins.apilist: API管理
- plugins.funlist: 函数管理
- modelmanager.modellist: 模型管理
- setting.user: 用户管理
- setting.role: 角色管理
- setting.chathistory: 聊天记录
- setting.delkms: 删除向量表
## 使用说明
### 1. 角色管理
访问 `/setting/rolelist` 可以进行角色管理:
- 查看所有角色
- 创建新角色
- 编辑角色信息
- 为角色分配权限
- 删除角色
### 2. 用户管理
访问 `/setting/userlist` 可以进行用户管理:
- 创建用户时可以分配角色
- 编辑用户时可以修改角色分配
- 用户可以拥有多个角色
### 3. 授权验证
系统支持两种授权方式:
**方式一:基于角色的授权**
```csharp
@attribute [Authorize(Roles = "AntSKAdmin")]
```
**方式二:基于多角色的授权**
```csharp
@attribute [Authorize(Roles = "AntSKAdmin,AntSKUser")]
```
## 技术实现
### 认证流程
1. 用户登录时,系统从数据库加载用户的角色和权限
2. 将所有角色添加到用户的Claims中
3. 将权限列表存储到UserSession中供后续使用
4. 支持多个角色同时验证
### 向后兼容
- 保留了原有的MenuRole字段支持逐步迁移
- 硬编码的管理员账号继续使用AntSKAdmin角色
- 新用户如果没有分配角色默认使用AntSKUser角色
## 安全性
1. **角色验证**: 只有AntSKAdmin角色可以访问角色和用户管理页面
2. **级联删除**: 删除角色时会自动删除相关的角色权限和用户角色关联
3. **启用状态**: 角色支持启用/禁用状态,禁用的角色不会加载权限
4. **多层级保护**: 数据库层、业务层、UI层都有权限验证
## 扩展建议
1. **操作权限**: 可以在Permissions表中添加Type为"Operation"的权限,用于控制具体操作
2. **权限缓存**: 可以考虑添加权限缓存机制,提高性能
3. **审计日志**: 可以添加角色和权限变更的审计日志
4. **批量操作**: 可以添加批量分配角色、批量分配权限的功能
## 常见问题
**Q: 如何添加新的权限?**
A: 可以通过代码在InitRolesAndPermissions方法中添加或者后续可以开发权限管理UI。
**Q: 用户可以有多个角色吗?**
A: 可以。系统支持一个用户拥有多个角色,最终拥有所有角色的权限并集。
**Q: 如何处理权限冲突?**
A: 系统采用"允许优先"策略,只要用户的任一角色拥有某项权限,用户就拥有该权限。
**Q: 删除角色会影响已登录的用户吗?**
A: 会的。用户下次登录时会重新加载角色和权限,已删除的角色将不再生效。

View File

@@ -836,6 +836,126 @@
部署名azure需要使用 部署名azure需要使用
</summary> </summary>
</member> </member>
<member name="T:AntSK.Domain.Repositories.Permissions">
<summary>
权限表
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Permissions.Id">
<summary>
权限ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Permissions.Name">
<summary>
权限名称
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Permissions.Code">
<summary>
权限编码
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Permissions.Type">
<summary>
权限类型Menu-菜单权限, Operation-操作权限)
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Permissions.Description">
<summary>
权限描述
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Permissions.CreateTime">
<summary>
创建时间
</summary>
</member>
<member name="T:AntSK.Domain.Repositories.RolePermissions">
<summary>
角色权限关联表
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.RolePermissions.Id">
<summary>
关联ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.RolePermissions.RoleId">
<summary>
角色ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.RolePermissions.PermissionId">
<summary>
权限ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.RolePermissions.CreateTime">
<summary>
创建时间
</summary>
</member>
<member name="T:AntSK.Domain.Repositories.Roles">
<summary>
角色表
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Roles.Id">
<summary>
角色ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Roles.Name">
<summary>
角色名称
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Roles.Code">
<summary>
角色编码
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Roles.Description">
<summary>
角色描述
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Roles.IsEnabled">
<summary>
是否启用
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Roles.CreateTime">
<summary>
创建时间
</summary>
</member>
<member name="T:AntSK.Domain.Repositories.UserRoles">
<summary>
用户角色关联表
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.UserRoles.Id">
<summary>
关联ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.UserRoles.UserId">
<summary>
用户ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.UserRoles.RoleId">
<summary>
角色ID
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.UserRoles.CreateTime">
<summary>
创建时间
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Users.No"> <member name="P:AntSK.Domain.Repositories.Users.No">
<summary> <summary>
工号,用于登陆 工号,用于登陆

View File

@@ -81,10 +81,98 @@ namespace AntSK.Domain.Common.DependencyInjection
llamafactoryStart.Value = "false"; llamafactoryStart.Value = "false";
_dic_Repository.Insert(llamafactoryStart); _dic_Repository.Insert(llamafactoryStart);
} }
// 初始化角色和权限
InitRolesAndPermissions(scope.ServiceProvider);
_logger.LogInformation("初始化数据库初始数据完成"); _logger.LogInformation("初始化数据库初始数据完成");
} }
return app; return app;
} }
private static void InitRolesAndPermissions(IServiceProvider serviceProvider)
{
var _roles_Repository = serviceProvider.GetRequiredService<IRoles_Repositories>();
var _permissions_Repository = serviceProvider.GetRequiredService<IPermissions_Repositories>();
var _rolePermissions_Repository = serviceProvider.GetRequiredService<IRolePermissions_Repositories>();
// 检查是否已经初始化
if (_roles_Repository.IsAny(r => r.Code == "AntSKAdmin"))
{
return;
}
// 创建管理员角色
var adminRole = new Roles
{
Id = Guid.NewGuid().ToString(),
Name = "管理员",
Code = "AntSKAdmin",
Description = "系统管理员,拥有所有权限",
IsEnabled = true,
CreateTime = DateTime.Now
};
_roles_Repository.Insert(adminRole);
// 创建普通用户角色
var userRole = new Roles
{
Id = Guid.NewGuid().ToString(),
Name = "普通用户",
Code = "AntSKUser",
Description = "普通用户,拥有基本功能权限",
IsEnabled = true,
CreateTime = DateTime.Now
};
_roles_Repository.Insert(userRole);
// 创建菜单权限
var menuPermissions = new List<Permissions>
{
new Permissions { Id = Guid.NewGuid().ToString(), Name = "聊天", Code = "chat", Type = "Menu", Description = "聊天功能权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "应用", Code = "app", Type = "Menu", Description = "应用管理权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "知识库", Code = "kms", Type = "Menu", Description = "知识库管理权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "API管理", Code = "plugins.apilist", Type = "Menu", Description = "API管理权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "函数管理", Code = "plugins.funlist", Type = "Menu", Description = "函数管理权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "模型管理", Code = "modelmanager.modellist", Type = "Menu", Description = "模型管理权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "用户管理", Code = "setting.user", Type = "Menu", Description = "用户管理权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "角色管理", Code = "setting.role", Type = "Menu", Description = "角色管理权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "聊天记录", Code = "setting.chathistory", Type = "Menu", Description = "聊天记录权限" },
new Permissions { Id = Guid.NewGuid().ToString(), Name = "删除向量表", Code = "setting.delkms", Type = "Menu", Description = "删除向量表权限" }
};
foreach (var permission in menuPermissions)
{
_permissions_Repository.Insert(permission);
}
// 为管理员角色分配所有权限
foreach (var permission in menuPermissions)
{
_rolePermissions_Repository.Insert(new RolePermissions
{
Id = Guid.NewGuid().ToString(),
RoleId = adminRole.Id,
PermissionId = permission.Id,
CreateTime = DateTime.Now
});
}
// 为普通用户角色分配基本权限(聊天、应用、知识库)
var basicPermissions = menuPermissions.Where(p => p.Code == "chat" || p.Code == "app" || p.Code == "kms").ToList();
foreach (var permission in basicPermissions)
{
_rolePermissions_Repository.Insert(new RolePermissions
{
Id = Guid.NewGuid().ToString(),
RoleId = userRole.Id,
PermissionId = permission.Id,
CreateTime = DateTime.Now
});
}
_logger.LogInformation("初始化角色和权限完成");
}
/// <summary> /// <summary>
/// 加载数据库的插件 /// 加载数据库的插件
/// </summary> /// </summary>

View File

@@ -0,0 +1,8 @@
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
public interface IPermissions_Repositories : IRepository<Permissions>
{
}
}

View File

@@ -0,0 +1,46 @@
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace AntSK.Domain.Repositories
{
/// <summary>
/// 权限表
/// </summary>
[SugarTable("Permissions")]
public partial class Permissions
{
/// <summary>
/// 权限ID
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
public string Id { get; set; }
/// <summary>
/// 权限名称
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 权限编码
/// </summary>
[Required]
public string Code { get; set; }
/// <summary>
/// 权限类型Menu-菜单权限, Operation-操作权限)
/// </summary>
[Required]
public string Type { get; set; }
/// <summary>
/// 权限描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}

View File

@@ -0,0 +1,10 @@
using AntSK.Domain.Common.DependencyInjection;
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
[ServiceDescription(typeof(IPermissions_Repositories), ServiceLifetime.Scoped)]
public class Permissions_Repositories : Repository<Permissions>, IPermissions_Repositories
{
}
}

View File

@@ -0,0 +1,8 @@
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
public interface IRoles_Repositories : IRepository<Roles>
{
}
}

View File

@@ -0,0 +1,45 @@
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace AntSK.Domain.Repositories
{
/// <summary>
/// 角色表
/// </summary>
[SugarTable("Roles")]
public partial class Roles
{
/// <summary>
/// 角色ID
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
public string Id { get; set; }
/// <summary>
/// 角色名称
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 角色编码
/// </summary>
[Required]
public string Code { get; set; }
/// <summary>
/// 角色描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}

View File

@@ -0,0 +1,10 @@
using AntSK.Domain.Common.DependencyInjection;
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
[ServiceDescription(typeof(IRoles_Repositories), ServiceLifetime.Scoped)]
public class Roles_Repositories : Repository<Roles>, IRoles_Repositories
{
}
}

View File

@@ -0,0 +1,8 @@
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
public interface IRolePermissions_Repositories : IRepository<RolePermissions>
{
}
}

View File

@@ -0,0 +1,32 @@
using SqlSugar;
namespace AntSK.Domain.Repositories
{
/// <summary>
/// 角色权限关联表
/// </summary>
[SugarTable("RolePermissions")]
public partial class RolePermissions
{
/// <summary>
/// 关联ID
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
public string Id { get; set; }
/// <summary>
/// 角色ID
/// </summary>
public string RoleId { get; set; }
/// <summary>
/// 权限ID
/// </summary>
public string PermissionId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}

View File

@@ -0,0 +1,10 @@
using AntSK.Domain.Common.DependencyInjection;
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
[ServiceDescription(typeof(IRolePermissions_Repositories), ServiceLifetime.Scoped)]
public class RolePermissions_Repositories : Repository<RolePermissions>, IRolePermissions_Repositories
{
}
}

View File

@@ -0,0 +1,8 @@
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
public interface IUserRoles_Repositories : IRepository<UserRoles>
{
}
}

View File

@@ -0,0 +1,32 @@
using SqlSugar;
namespace AntSK.Domain.Repositories
{
/// <summary>
/// 用户角色关联表
/// </summary>
[SugarTable("UserRoles")]
public partial class UserRoles
{
/// <summary>
/// 关联ID
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
public string Id { get; set; }
/// <summary>
/// 用户ID
/// </summary>
public string UserId { get; set; }
/// <summary>
/// 角色ID
/// </summary>
public string RoleId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}

View File

@@ -0,0 +1,10 @@
using AntSK.Domain.Common.DependencyInjection;
using AntSK.Domain.Repositories.Base;
namespace AntSK.Domain.Repositories
{
[ServiceDescription(typeof(IUserRoles_Repositories), ServiceLifetime.Scoped)]
public class UserRoles_Repositories : Repository<UserRoles>, IUserRoles_Repositories
{
}
}

View File

@@ -4,5 +4,7 @@
{ {
public string UserName { get; set; } public string UserName { get; set; }
public string Role { get; set; } public string Role { get; set; }
public List<string>? Roles { get; set; }
public List<string>? Permissions { get; set; }
} }
} }

View File

@@ -0,0 +1,61 @@
@namespace AntSK.Pages.Setting.Role
@using AntSK.Domain.Repositories
@using AntSK.Models
@page "/setting/role/add"
@page "/setting/role/add/{RoleId}"
@using AntSK.Services.Auth
@inherits AuthComponentBase
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "AntSKAdmin")]
<PageContainer Title="新增角色">
<ChildContent>
<Card>
<Form Model="@_roleModel"
Style="margin-top: 8px;"
OnFinish="HandleSubmit">
<FormItem Label="角色名称" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入角色名称" @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="角色编码" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入角色编码" @bind-Value="@context.Code" />
</FormItem>
<FormItem Label="角色描述" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入角色描述" @bind-Value="@context.Description" />
</FormItem>
<FormItem Label="是否启用" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Switch @bind-Checked="@context.IsEnabled" />
</FormItem>
<FormItem Label="权限分配" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Select Mode="multiple"
@bind-Values="_permissionIds"
Placeholder="选择权限"
TItemValue="string"
TItem="string"
Size="@AntSizeLDSType.Default">
<SelectOptions>
@foreach (var permission in _allPermissions)
{
<SelectOption TItem="string" TItemValue="string" Value="@permission.Id" Label="@permission.Name" />
}
</SelectOptions>
</Select>
</FormItem>
<FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol">
<Button Type="primary" OnClick="HandleSubmit">
保存
</Button>
<Button OnClick="Back">
返回
</Button>
</FormItem>
</Form>
</Card>
</ChildContent>
</PageContainer>
@code {
}

View File

@@ -0,0 +1,103 @@
using AntDesign;
using AntSK.Domain.Repositories;
using Microsoft.AspNetCore.Components;
namespace AntSK.Pages.Setting.Role
{
public partial class AddRole
{
[Parameter]
public string RoleId { get; set; }
[Inject] protected IRoles_Repositories _roles_Repositories { get; set; }
[Inject] protected IPermissions_Repositories _permissions_Repositories { get; set; }
[Inject] protected IRolePermissions_Repositories _rolePermissions_Repositories { get; set; }
[Inject] protected MessageService? Message { get; set; }
private Roles _roleModel = new Roles();
private List<Permissions> _allPermissions = new List<Permissions>();
private IEnumerable<string> _permissionIds;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
// 加载所有权限
_allPermissions = _permissions_Repositories.GetList();
if (!string.IsNullOrEmpty(RoleId))
{
_roleModel = _roles_Repositories.GetFirst(p => p.Id == RoleId);
// 加载角色已有的权限
var rolePermissions = _rolePermissions_Repositories.GetList(p => p.RoleId == RoleId);
_permissionIds = rolePermissions.Select(rp => rp.PermissionId);
}
}
private void HandleSubmit()
{
if (string.IsNullOrEmpty(RoleId))
{
//新增
_roleModel.Id = Guid.NewGuid().ToString();
_roleModel.CreateTime = DateTime.Now;
if (_roles_Repositories.IsAny(p => p.Code == _roleModel.Code))
{
_ = Message.Error("角色编码已存在!", 2);
return;
}
_roles_Repositories.Insert(_roleModel);
// 添加角色权限关联
if (_permissionIds != null)
{
foreach (var permissionId in _permissionIds)
{
_rolePermissions_Repositories.Insert(new RolePermissions
{
Id = Guid.NewGuid().ToString(),
RoleId = _roleModel.Id,
PermissionId = permissionId,
CreateTime = DateTime.Now
});
}
}
}
else
{
//修改
_roles_Repositories.Update(_roleModel);
// 先删除旧的角色权限关联
var oldRolePermissions = _rolePermissions_Repositories.GetList(p => p.RoleId == RoleId);
foreach (var rp in oldRolePermissions)
{
_rolePermissions_Repositories.Delete(rp.Id);
}
// 添加新的角色权限关联
if (_permissionIds != null)
{
foreach (var permissionId in _permissionIds)
{
_rolePermissions_Repositories.Insert(new RolePermissions
{
Id = Guid.NewGuid().ToString(),
RoleId = _roleModel.Id,
PermissionId = permissionId,
CreateTime = DateTime.Now
});
}
}
}
Back();
}
private void Back()
{
NavigationManager.NavigateTo("/setting/rolelist");
}
}
}

View File

@@ -0,0 +1,72 @@
@namespace AntSK.Pages.Setting.Role
@using AntSK.Domain.Repositories
@page "/setting/rolelist"
@inject NavigationManager NavigationManager
@using AntSK.Services.Auth
@inherits AuthComponentBase
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "AntSKAdmin")]
<div>
<PageContainer Title="角色管理">
<ChildContent>
<div class="standardList">
<Card Class="listCard"
Title="角色列表"
Style="margin-top: 24px;"
BodyStyle="padding: 0 32px 40px 32px">
<Extra>
<div class="extraContent">
<Search Class="extraContentSearch" Placeholder="查询" @bind-Value="_searchKeyword" OnSearch="OnSearch" />
</div>
</Extra>
<ChildContent>
<Button Type="dashed"
Style="width: 100%; margin-bottom: 8px;"
OnClick="AddRole">
<Icon Type="plus" Theme="outline" />
新增角色
</Button>
<AntList TItem="Roles"
DataSource="_data"
ItemLayout="ListItemLayout.Horizontal">
<ListItem Actions="new[] {
edit(()=> Edit(context.Id)),
delete(async ()=>await Delete(context.Id))
}" Style="width:100%">
<div class="listContent" style="width:100%">
<div class="listContentItem" style="width:20%">
<b>角色名称</b>
<p>@context.Name</p>
</div>
<div class="listContentItem" style="width:20%">
<b>角色编码</b>
<p>@context.Code</p>
</div>
<div class="listContentItem" style="width:30%">
<b>描述</b>
<p>@context.Description</p>
</div>
<div class="listContentItem" style="width:15%">
<b>状态</b>
<p>@(context.IsEnabled ? "启用" : "禁用")</p>
</div>
</div>
</ListItem>
</AntList>
</ChildContent>
</Card>
</div>
</ChildContent>
</PageContainer>
</div>
@code
{
RenderFragment edit(Action clickAction) =>@<a key="edit" @onclick="@clickAction">修改</a>;
RenderFragment delete(Action clickAction) =>@<a key="delete" @onclick="@clickAction">删除</a>;
}

View File

@@ -0,0 +1,84 @@
using AntDesign;
using AntSK.Domain.Repositories;
using AntSK.Models;
using Microsoft.AspNetCore.Components;
namespace AntSK.Pages.Setting.Role
{
public partial class RoleList
{
private readonly BasicListFormModel _model = new BasicListFormModel();
private List<Roles> _data;
private string _searchKeyword;
[Inject]
protected IRoles_Repositories _roles_Repositories { get; set; }
[Inject]
protected IRolePermissions_Repositories _rolePermissions_Repositories { get; set; }
[Inject]
protected IUserRoles_Repositories _userRoles_Repositories { get; set; }
[Inject]
IConfirmService _confirmService { get; set; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await InitData();
}
private async Task InitData(string searchKey = null)
{
if (string.IsNullOrEmpty(searchKey))
{
_data = _roles_Repositories.GetList();
}
else
{
_data = _roles_Repositories.GetList(p => p.Name.Contains(searchKey) || p.Code.Contains(searchKey) || (p.Description != null && p.Description.Contains(searchKey)));
}
await InvokeAsync(StateHasChanged);
}
public async Task OnSearch()
{
await InitData(_searchKeyword);
}
public async Task AddRole()
{
NavigationManager.NavigateTo("/setting/role/add");
}
public void Edit(string roleid)
{
NavigationManager.NavigateTo("/setting/role/add/" + roleid);
}
public async Task Delete(string roleid)
{
var content = "是否确认删除此角色";
var title = "删除";
var result = await _confirmService.Show(content, title, ConfirmButtons.YesNo);
if (result == ConfirmResult.Yes)
{
// 删除角色权限关联
var rolePerms = _rolePermissions_Repositories.GetList(p => p.RoleId == roleid);
foreach (var rp in rolePerms)
{
await _rolePermissions_Repositories.DeleteAsync(rp.Id);
}
// 删除用户角色关联
var userRoles = _userRoles_Repositories.GetList(p => p.RoleId == roleid);
foreach (var ur in userRoles)
{
await _userRoles_Repositories.DeleteAsync(ur.Id);
}
// 删除角色
await _roles_Repositories.DeleteAsync(roleid);
await InitData("");
}
}
}
}

View File

@@ -43,6 +43,22 @@
</Select> </Select>
</FormItem> </FormItem>
<FormItem Label="角色分配" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Select Mode="multiple"
@bind-Values="_roleIds"
Placeholder="选择角色"
TItemValue="string"
TItem="string"
Size="@AntSizeLDSType.Default">
<SelectOptions>
@foreach (var role in _allRoles)
{
<SelectOption TItem="string" TItemValue="string" Value="@role.Id" Label="@role.Name" />
}
</SelectOptions>
</Select>
</FormItem>
<FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol"> <FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol">
<Button Type="primary" OnClick="HandleSubmit"> <Button Type="primary" OnClick="HandleSubmit">
保存 保存

View File

@@ -12,21 +12,34 @@ namespace AntSK.Pages.Setting.User
[Parameter] [Parameter]
public string UserId { get; set; } public string UserId { get; set; }
[Inject] protected IUsers_Repositories _users_Repositories { get; set; } [Inject] protected IUsers_Repositories _users_Repositories { get; set; }
[Inject] protected IRoles_Repositories _roles_Repositories { get; set; }
[Inject] protected IUserRoles_Repositories _userRoles_Repositories { get; set; }
[Inject] protected MessageService? Message { get; set; } [Inject] protected MessageService? Message { get; set; }
[Inject] public HttpClient HttpClient { get; set; } [Inject] public HttpClient HttpClient { get; set; }
private Users _userModel = new Users(); private Users _userModel = new Users();
private string _password = ""; private string _password = "";
IEnumerable<string> _menuKeys; IEnumerable<string> _menuKeys;
IEnumerable<string> _roleIds;
private List<MenuDataItem> menuList = new List<MenuDataItem>(); private List<MenuDataItem> menuList = new List<MenuDataItem>();
private List<Roles> _allRoles = new List<Roles>();
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
// 加载所有角色
_allRoles = _roles_Repositories.GetList(r => r.IsEnabled);
if (!string.IsNullOrEmpty(UserId)) if (!string.IsNullOrEmpty(UserId))
{ {
_userModel = _users_Repositories.GetFirst(p => p.Id == UserId); _userModel = _users_Repositories.GetFirst(p => p.Id == UserId);
_password = _userModel.Password; _password = _userModel.Password;
// 加载用户已有的角色
var userRoles = _userRoles_Repositories.GetList(p => p.UserId == UserId);
_roleIds = userRoles.Select(ur => ur.RoleId);
} }
menuList = (await HttpClient.GetFromJsonAsync<MenuDataItem[]>("data/menu.json")).ToList().Where(p => p.Key != "setting").ToList(); menuList = (await HttpClient.GetFromJsonAsync<MenuDataItem[]>("data/menu.json")).ToList().Where(p => p.Key != "setting").ToList();
_menuKeys = _userModel.MenuRole?.Split(","); _menuKeys = _userModel.MenuRole?.Split(",");
@@ -52,6 +65,21 @@ namespace AntSK.Pages.Setting.User
} }
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password); _userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
_users_Repositories.Insert(_userModel); _users_Repositories.Insert(_userModel);
// 添加用户角色关联
if (_roleIds != null)
{
foreach (var roleId in _roleIds)
{
_userRoles_Repositories.Insert(new UserRoles
{
Id = Guid.NewGuid().ToString(),
UserId = _userModel.Id,
RoleId = roleId,
CreateTime = DateTime.Now
});
}
}
} }
else else
{ {
@@ -61,6 +89,28 @@ namespace AntSK.Pages.Setting.User
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password); _userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
} }
_users_Repositories.Update(_userModel); _users_Repositories.Update(_userModel);
// 先删除旧的用户角色关联
var oldUserRoles = _userRoles_Repositories.GetList(p => p.UserId == UserId);
foreach (var ur in oldUserRoles)
{
_userRoles_Repositories.Delete(ur.Id);
}
// 添加新的用户角色关联
if (_roleIds != null)
{
foreach (var roleId in _roleIds)
{
_userRoles_Repositories.Insert(new UserRoles
{
Id = Guid.NewGuid().ToString(),
UserId = _userModel.Id,
RoleId = roleId,
CreateTime = DateTime.Now
});
}
}
} }
Back(); Back();

View File

@@ -10,6 +10,10 @@ namespace AntSK.Services.Auth
{ {
public class AntSKAuthProvider( public class AntSKAuthProvider(
IUsers_Repositories _users_Repositories, IUsers_Repositories _users_Repositories,
IUserRoles_Repositories _userRoles_Repositories,
IRoles_Repositories _roles_Repositories,
IRolePermissions_Repositories _rolePermissions_Repositories,
IPermissions_Repositories _permissions_Repositories,
ProtectedSessionStorage _protectedSessionStore ProtectedSessionStorage _protectedSessionStore
) : AuthenticationStateProvider ) : AuthenticationStateProvider
{ {
@@ -29,13 +33,18 @@ namespace AntSK.Services.Auth
new Claim(ClaimTypes.Role, AdminRole) new Claim(ClaimTypes.Role, AdminRole)
}; };
identity = new ClaimsIdentity(claims, AdminRole); identity = new ClaimsIdentity(claims, AdminRole);
await _protectedSessionStore.SetAsync("UserSession", new UserSession() { UserName = username, Role = AdminRole }); await _protectedSessionStore.SetAsync("UserSession", new UserSession()
{
UserName = username,
Role = AdminRole,
Roles = new List<string> { AdminRole },
Permissions = new List<string>()
});
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
return true; return true;
} }
else else
{ {
string UserRole = "AntSKUser";
if (user.IsNull()) if (user.IsNull())
{ {
return false; return false;
@@ -44,13 +53,42 @@ namespace AntSK.Services.Auth
{ {
return false; return false;
} }
// 获取用户的角色和权限
var userRoles = _userRoles_Repositories.GetList(p => p.UserId == user.Id);
var roleIds = userRoles.Select(ur => ur.RoleId).ToList();
var roles = _roles_Repositories.GetList(r => roleIds.Contains(r.Id) && r.IsEnabled);
// 获取角色的权限
var rolePermissions = _rolePermissions_Repositories.GetList(rp => roleIds.Contains(rp.RoleId));
var permissionIds = rolePermissions.Select(rp => rp.PermissionId).Distinct().ToList();
var permissions = _permissions_Repositories.GetList(p => permissionIds.Contains(p.Id));
// 如果没有角色,使用默认角色
string defaultRole = "AntSKUser";
var roleList = roles.Any() ? roles.Select(r => r.Code).ToList() : new List<string> { defaultRole };
var permissionList = permissions.Select(p => p.Code).ToList();
// 用户认证成功创建用户的ClaimsIdentity // 用户认证成功创建用户的ClaimsIdentity
var claims = new[] { var claims = new List<Claim>
new Claim(ClaimTypes.Name, username), {
new Claim(ClaimTypes.Role, UserRole) new Claim(ClaimTypes.Name, username)
}; };
identity = new ClaimsIdentity(claims, UserRole);
await _protectedSessionStore.SetAsync("UserSession", new UserSession() { UserName = username, Role = UserRole }); // 添加所有角色到Claims
foreach (var role in roleList)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
identity = new ClaimsIdentity(claims, roleList.FirstOrDefault() ?? defaultRole);
await _protectedSessionStore.SetAsync("UserSession", new UserSession()
{
UserName = username,
Role = roleList.FirstOrDefault() ?? defaultRole,
Roles = roleList,
Permissions = permissionList
});
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
return true; return true;
} }
@@ -68,9 +106,25 @@ namespace AntSK.Services.Auth
var userSession = userSessionStorageResult.Success ? userSessionStorageResult.Value : null; var userSession = userSessionStorageResult.Success ? userSessionStorageResult.Value : null;
if (userSession.IsNotNull()) if (userSession.IsNotNull())
{ {
var claims = new[] { var claims = new List<Claim>
new Claim(ClaimTypes.Name, userSession.UserName), {
new Claim( ClaimTypes.Role, userSession.Role) }; new Claim(ClaimTypes.Name, userSession.UserName)
};
// 添加所有角色到Claims
if (userSession.Roles != null && userSession.Roles.Any())
{
foreach (var role in userSession.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
}
else
{
// 向后兼容使用单个Role
claims.Add(new Claim(ClaimTypes.Role, userSession.Role));
}
identity = new ClaimsIdentity(claims, userSession.Role); identity = new ClaimsIdentity(claims, userSession.Role);
} }
var user = new ClaimsPrincipal(identity); var user = new ClaimsPrincipal(identity);

View File

@@ -59,6 +59,11 @@
"name": "用户管理", "name": "用户管理",
"key": "setting.user" "key": "setting.user"
}, },
{
"path": "/setting/rolelist",
"name": "角色管理",
"key": "setting.role"
},
{ {
"path": "/setting/chathistory", "path": "/setting/chathistory",
"name": "聊天记录", "name": "聊天记录",