mirror of
https://github.com/AIDotNet/AntSK.git
synced 2026-02-17 22:10:14 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e032e3733 | ||
|
|
abed362ff2 | ||
|
|
1a3881e7a4 | ||
|
|
ea8dd21478 | ||
|
|
5ec918a2f9 | ||
|
|
08d3a0aa3d | ||
|
|
4044355ae7 | ||
|
|
f03649146f | ||
|
|
15fd59571f | ||
|
|
f8399887ce | ||
|
|
97548b0d2b | ||
|
|
d6b2c3a08b | ||
|
|
3342378feb |
@@ -386,7 +386,7 @@ version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.6.0
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.6.5
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
@@ -604,7 +604,3 @@ services.AddSingleton<IChatCompletion, CustomChatCompletion>();
|
||||
3. 满足以上要求
|
||||
|
||||
|
||||
## ☎️联系我
|
||||
如有任何问题或建议,请通过以下方式关注我的公众号《许泽宇的技术分享》,发消息与我联系,我们也有AIDotnet交流群,可以发送进群等消息,然后我会拉你进交流群
|
||||
|
||||
另外您也可以通过邮箱与我联系:antskpro@qq.com
|
||||
|
||||
140
docs/RBAC_README.md
Normal file
140
docs/RBAC_README.md
Normal 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: 会的。用户下次登录时会重新加载角色和权限,已删除的角色将不再生效。
|
||||
@@ -836,6 +836,126 @@
|
||||
部署名,azure需要使用
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
工号,用于登陆
|
||||
|
||||
@@ -81,10 +81,98 @@ namespace AntSK.Domain.Common.DependencyInjection
|
||||
llamafactoryStart.Value = "false";
|
||||
_dic_Repository.Insert(llamafactoryStart);
|
||||
}
|
||||
|
||||
// 初始化角色和权限
|
||||
InitRolesAndPermissions(scope.ServiceProvider);
|
||||
|
||||
_logger.LogInformation("初始化数据库初始数据完成");
|
||||
}
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IPermissions_Repositories : IRepository<Permissions>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IRoles_Repositories : IRepository<Roles>
|
||||
{
|
||||
}
|
||||
}
|
||||
45
src/AntSK.Domain/Repositories/Setting/Role/Roles.cs
Normal file
45
src/AntSK.Domain/Repositories/Setting/Role/Roles.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IRolePermissions_Repositories : IRepository<RolePermissions>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IUserRoles_Repositories : IRepository<UserRoles>
|
||||
{
|
||||
}
|
||||
}
|
||||
32
src/AntSK.Domain/Repositories/Setting/UserRole/UserRoles.cs
Normal file
32
src/AntSK.Domain/Repositories/Setting/UserRole/UserRoles.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,7 @@
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public string Role { get; set; }
|
||||
public List<string>? Roles { get; set; }
|
||||
public List<string>? Permissions { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<GridRow Gutter="(16, 16)">
|
||||
<GridCol Span="14">
|
||||
<Card Style="height:75vh;overflow: auto;">
|
||||
<Card Class="chat-card">
|
||||
<TitleTemplate>
|
||||
<Icon Type="setting" /> 选择应用
|
||||
<Select DataSource="@_list"
|
||||
@@ -23,19 +23,21 @@
|
||||
<a href="@( NavigationManager.BaseUri + "openchat/" + AppId)" target="_blank">分享使用</a>
|
||||
</TitleTemplate>
|
||||
<Body>
|
||||
@if (!string.IsNullOrEmpty(AppId))
|
||||
{
|
||||
<Watermark Content="AntSK">
|
||||
<ChatView AppId="@AppId" ShowTitle=false OnRelevantSources="OnRelevantSources"></ChatView>
|
||||
</Watermark>
|
||||
}
|
||||
<div class="chat-card__content">
|
||||
@if (!string.IsNullOrEmpty(AppId))
|
||||
{
|
||||
<Watermark Content="AntSK" Class="chat-card__watermark">
|
||||
<ChatView AppId="@AppId" ShowTitle=false OnRelevantSources="OnRelevantSources"></ChatView>
|
||||
</Watermark>
|
||||
}
|
||||
</div>
|
||||
</Body>
|
||||
</Card>
|
||||
</GridCol>
|
||||
<GridCol Span="10">
|
||||
<Card Style="height: 75vh;overflow: auto;">
|
||||
<TitleTemplate>
|
||||
<Icon Type="search" /> 调试结果
|
||||
<Icon Type="search" /> 知识溯源
|
||||
</TitleTemplate>
|
||||
<Extra>
|
||||
|
||||
@@ -62,13 +64,45 @@
|
||||
</GridRow>
|
||||
|
||||
<style>
|
||||
#chat {
|
||||
height: calc(75vh - 120px);
|
||||
.chat-card {
|
||||
height: 75vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-card .ant-card-body {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-card__content {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-card__watermark {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-card__watermark > * {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -11,46 +11,54 @@
|
||||
{
|
||||
<PageHeader Class="site-page-header" Title="@app.Name" Subtitle="@app.Describe" />
|
||||
}
|
||||
<div id="scrollDiv" style="flex:1; width:100%; overflow-y:auto; overflow-x:hidden; padding:16px;">
|
||||
<div id="scrollDiv" class="chat-scroll">
|
||||
<Virtualize Items="@(MessageList.OrderBy(o => o.CreateTime).ToList())" Context="item">
|
||||
@if (item.IsSend)
|
||||
{
|
||||
<GridRow>
|
||||
<GridRow Class="message-row" Gutter="(8,8)">
|
||||
<GridCol Span="23">
|
||||
<div class="chat-bubble sent">
|
||||
<Popover Title="@item.CreateTime.ToString()">
|
||||
<Unbound>
|
||||
<Flex Vertical RefBack="context">
|
||||
@if (item.FileName != null)
|
||||
{
|
||||
<p class="message-file">
|
||||
<Upload DefaultFileList="[new(){ FileName= item.FileName }]" />
|
||||
</p>
|
||||
}
|
||||
<p class="bubble-text">@(item.Context)</p>
|
||||
</Flex>
|
||||
</Unbound>
|
||||
</Popover>
|
||||
<div class="message-shell sent">
|
||||
<div class="chat-bubble sent">
|
||||
<Popover Title="@item.CreateTime.ToString()">
|
||||
<Unbound>
|
||||
<Flex Vertical RefBack="context" Class="bubble-body">
|
||||
@if (item.FileName != null)
|
||||
{
|
||||
<p class="message-file">
|
||||
<Upload DefaultFileList="[new(){ FileName= item.FileName }]" />
|
||||
</p>
|
||||
}
|
||||
<p class="bubble-text">@(item.Context)</p>
|
||||
</Flex>
|
||||
</Unbound>
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="message-meta meta-right">
|
||||
<span>@item.CreateTime.ToString("HH:mm")</span>
|
||||
<Icon Class="meta-action" Type="copy" Theme="outline" OnClick="async () =>await OnCopyAsync(item)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-meta meta-right">@item.CreateTime.ToString("HH:mm")</div>
|
||||
<Icon Style="float:right;margin-top:10px;" Type="copy" Theme="outline" OnClick="async () =>await OnCopyAsync(item)" />
|
||||
</GridCol>
|
||||
<GridCol Span="1">
|
||||
<Image Class="avatar" Width="28px" Height="28px" Src="./assets/KDpgvguMpGfqaHPjicRK.svg" />
|
||||
<GridCol Span="1" Class="avatar-col">
|
||||
<Image Class="avatar" Width="32px" Height="32px" Src="./assets/KDpgvguMpGfqaHPjicRK.svg" Preview="false" />
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
}
|
||||
else
|
||||
{
|
||||
<GridRow>
|
||||
<GridCol Span="1">
|
||||
<Image Class="avatar" Width="28px" Height="28px" Style="margin-top:10px;" Src="./assets/method-draw-image.svg" />
|
||||
<GridRow Class="message-row" Gutter="(8,8)">
|
||||
<GridCol Span="1" Class="avatar-col">
|
||||
<Image Class="avatar" Width="32px" Height="32px" Src="./assets/method-draw-image.svg" Preview="false" />
|
||||
</GridCol>
|
||||
<GridCol Span="23">
|
||||
<div class="chat-bubble received">
|
||||
@((MarkupString)(item.Context))
|
||||
<div class="message-shell received">
|
||||
<div class="chat-bubble received">
|
||||
@((MarkupString)(item.Context))
|
||||
</div>
|
||||
<div class="message-meta meta-left">
|
||||
<span>@item.CreateTime.ToString("HH:mm")</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-meta meta-left">@item.CreateTime.ToString("HH:mm")</div>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
}
|
||||
@@ -63,8 +71,10 @@
|
||||
<Upload DefaultFileList="fileList" OnRemove="HandleFileRemove" />
|
||||
</Flex>
|
||||
}
|
||||
<Flex Class="input-bar" Justify="end">
|
||||
<AntDesign.Input @bind-Value="@(_messageInput)" DebounceMilliseconds="@(-1)" Placeholder="输入消息回车发送" OnPressEnter="@(async () => await OnSendAsync())" Disabled="@Sendding"></AntDesign.Input>
|
||||
<Flex Class="input-bar" Justify="end" Align="center">
|
||||
<div class="input-bar__field">
|
||||
<AntDesign.Input @bind-Value="@(_messageInput)" DebounceMilliseconds="@(-1)" Placeholder="输入消息回车发送" OnPressEnter="@(async () => await OnSendAsync())" Disabled="@Sendding"></AntDesign.Input>
|
||||
</div>
|
||||
@if (app.EmbeddingModelID != null)
|
||||
{
|
||||
<Upload Action="@("api/File/UploadFile")"
|
||||
@@ -83,130 +93,371 @@
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg-start: #f7f9fc;
|
||||
--bg-end: #eef2f7;
|
||||
--surface-base: #ffffff;
|
||||
--surface-elevated: #f5f7fb;
|
||||
--border-subtle: rgba(15, 23, 42, 0.07);
|
||||
--shadow-soft: 0 16px 28px rgba(15, 23, 42, 0.08);
|
||||
--primary-color: var(--ant-primary-color, #1677ff);
|
||||
--primary-soft: rgba(22, 119, 255, 0.12);
|
||||
--bubble-recv: #ffffff;
|
||||
--bubble-sent: #daf8cb;
|
||||
--bubble-border: rgba(0,0,0,0.06);
|
||||
--text-secondary: #8a8f98;
|
||||
--bubble-border-sent: rgba(22, 119, 255, 0.25);
|
||||
--text-primary: #1f2329;
|
||||
--text-secondary: #7a808a;
|
||||
}
|
||||
|
||||
@@supports (color: color-mix(in srgb, white 50%, black 50%)) {
|
||||
:root {
|
||||
--bubble-sent: color-mix(in srgb, var(--primary-color) 28%, white);
|
||||
}
|
||||
}
|
||||
|
||||
#chat {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
min-height: 65vh;
|
||||
background: linear-gradient(180deg, var(--bg-start), var(--bg-end));
|
||||
border-radius: 8px;
|
||||
padding: 8px 8px 0 8px;
|
||||
width: 100%;
|
||||
min-height: min(65vh, 100%);
|
||||
max-height: 100%;
|
||||
background: var(--surface-base);
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
padding: 12px 12px 0 12px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
#scrollDiv {
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
#chat::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
right: 32px;
|
||||
bottom: -18px;
|
||||
height: 40px;
|
||||
background: rgba(15, 23, 42, 0.22);
|
||||
filter: blur(24px);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.chat-scroll {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 12px 16px 20px;
|
||||
background: var(--surface-base);
|
||||
border-radius: 16px;
|
||||
box-sizing: border-box;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
|
||||
.chat-scroll::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.chat-scroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(22, 119, 255, 0.24);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.chat-scroll::-webkit-scrollbar-track {
|
||||
background: rgba(15, 23, 42, 0.05);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.message-row {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.message-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.message-shell.sent {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-shell.received {
|
||||
align-items: flex-start;
|
||||
}
|
||||
/* custom scrollbar */
|
||||
#scrollDiv::-webkit-scrollbar { height: 8px; width: 8px; }
|
||||
#scrollDiv::-webkit-scrollbar-thumb { background: #c7cbd1; border-radius: 8px; }
|
||||
#scrollDiv::-webkit-scrollbar-track { background: transparent; }
|
||||
|
||||
.chat-bubble {
|
||||
padding: 12px 14px;
|
||||
margin: 6px 10px;
|
||||
border-radius: 14px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
max-width: 78%;
|
||||
position: relative;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
|
||||
border: 1px solid var(--bubble-border);
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
|
||||
border: none;
|
||||
word-break: break-word;
|
||||
line-height: 1.6;
|
||||
animation: pop .18s ease-out;
|
||||
line-height: 1.65;
|
||||
animation: pop .2s ease-out;
|
||||
transition: transform .2s ease, box-shadow .2s ease;
|
||||
}
|
||||
|
||||
.chat-bubble .bubble-text { margin: 0; white-space: pre-wrap; }
|
||||
|
||||
.received {
|
||||
background-color: var(--bubble-recv);
|
||||
align-self: flex-start;
|
||||
float: left;
|
||||
.chat-bubble:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.sent {
|
||||
background-color: var(--bubble-sent);
|
||||
align-self: flex-end;
|
||||
float: right;
|
||||
.chat-bubble .bubble-text {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chat-bubble .bubble-body {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-bubble.received {
|
||||
background: var(--bubble-recv);
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06), inset 0 0 0 1px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.chat-bubble.sent {
|
||||
background: var(--bubble-sent);
|
||||
color: #0c1a33;
|
||||
box-shadow: 0 12px 26px rgba(22, 119, 255, 0.18);
|
||||
}
|
||||
|
||||
.chat-bubble.sent .bubble-text {
|
||||
color: #102347;
|
||||
}
|
||||
|
||||
/* bubble tails */
|
||||
.chat-bubble.received::after {
|
||||
content: "";
|
||||
position: absolute; left: -6px; bottom: 10px;
|
||||
width: 10px; height: 10px; background: var(--bubble-recv);
|
||||
border-left: 1px solid var(--bubble-border);
|
||||
border-bottom: 1px solid var(--bubble-border);
|
||||
transform: rotate(45deg);
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
.chat-bubble.received::after,
|
||||
.chat-bubble.sent::after {
|
||||
content: "";
|
||||
position: absolute; right: -6px; bottom: 10px;
|
||||
width: 10px; height: 10px; background: var(--bubble-sent);
|
||||
border-right: 1px solid var(--bubble-border);
|
||||
border-bottom: 1px solid var(--bubble-border);
|
||||
transform: rotate(45deg);
|
||||
border-bottom-right-radius: 2px;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
bottom: auto;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
transform: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none !important;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
.chat-bubble.received::after {
|
||||
left: -14px;
|
||||
background: var(--bubble-recv);
|
||||
clip-path: polygon(100% 0, 0 0, 100% 100%);
|
||||
}
|
||||
|
||||
.chat-bubble.sent::after {
|
||||
right: -14px;
|
||||
background: var(--bubble-sent);
|
||||
clip-path: polygon(0 0, 100% 0, 0 100%);
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin: 2px 12px 4px;
|
||||
clear: both;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
}
|
||||
@@keyframes pop {
|
||||
from { transform: translateY(4px); opacity: .65; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
.meta-right { text-align: right; }
|
||||
.meta-left { text-align: left; }
|
||||
|
||||
.avatar { border-radius: 50%; box-shadow: 0 0 0 1px #e6e8eb inset; }
|
||||
.meta-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.meta-left {
|
||||
justify-content: flex-start;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.meta-action {
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color .2s ease;
|
||||
}
|
||||
|
||||
.meta-action:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.avatar-col {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.12) inset, 0 6px 16px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.input-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 8px 12px;
|
||||
border-top: 1px solid #e6e8eb;
|
||||
background: rgba(255,255,255,0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
padding: 12px;
|
||||
border-top: 1px solid rgba(15, 23, 42, 0.06);
|
||||
background: var(--surface-base);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -12px 24px rgba(15, 23, 42, 0.06);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.input-bar .ant-input {
|
||||
border-radius: 999px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.input-bar__field {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-elevated);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.input-bar__field .ant-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* markdown & code inside bubbles */
|
||||
.chat-bubble pre { background:#0b1021; color:#e6e6e6; padding:12px; border-radius:10px; overflow:auto; }
|
||||
.chat-bubble code { background: rgba(27,31,35,0.06); padding: .15rem .35rem; border-radius: 6px; }
|
||||
.chat-bubble pre code { background: transparent; padding: 0; }
|
||||
.chat-bubble table { width: 100%; border-collapse: collapse; }
|
||||
.chat-bubble table td, .chat-bubble table th { border: 1px solid #e6e8eb; padding: 6px 8px; }
|
||||
.chat-bubble pre {
|
||||
background: #0b1021;
|
||||
color: #e6e6e6;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
overflow: auto;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.ant-card-body { height: 90% !important; }
|
||||
.chat-bubble code {
|
||||
background: rgba(27, 31, 35, 0.08);
|
||||
padding: .15rem .35rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.chat-bubble pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chat-bubble table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.chat-bubble table td,
|
||||
.chat-bubble table th {
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.think {
|
||||
color: gray;
|
||||
color: rgba(22, 119, 255, 0.9);
|
||||
font-style: italic;
|
||||
text-align: left !important;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
margin-left: 8px;
|
||||
padding-left: 8px;
|
||||
border-left: 2px solid #7F7FFF;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid rgba(22, 119, 255, 0.4);
|
||||
}
|
||||
|
||||
.message-file { margin: 0 0 6px 0; }
|
||||
.message-file {
|
||||
margin: 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-file .ant-upload-list-item {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@media (max-width: 992px) {
|
||||
#chat {
|
||||
border-radius: 12px;
|
||||
padding: 10px 10px 0;
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
max-width: 88%;
|
||||
}
|
||||
|
||||
.input-bar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--surface-base: #111827;
|
||||
--surface-elevated: #1f2937;
|
||||
--border-subtle: rgba(148, 163, 184, 0.16);
|
||||
--shadow-soft: 0 18px 34px rgba(0, 0, 0, 0.45);
|
||||
--text-primary: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--bubble-recv: #1f2937;
|
||||
}
|
||||
|
||||
#chat {
|
||||
background: var(--surface-elevated);
|
||||
box-shadow: 0 28px 48px -24px rgba(0, 0, 0, 0.68);
|
||||
}
|
||||
|
||||
#chat::after {
|
||||
background: rgba(15, 23, 42, 0.55);
|
||||
}
|
||||
|
||||
.chat-scroll {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
}
|
||||
|
||||
.chat-bubble.received::after {
|
||||
background: var(--bubble-recv);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.message-file .ant-upload-list-item {
|
||||
background: rgba(22, 119, 255, 0.16);
|
||||
}
|
||||
|
||||
.input-bar {
|
||||
background: var(--surface-elevated);
|
||||
}
|
||||
}
|
||||
|
||||
@@keyframes pop {
|
||||
from { transform: translateY(6px); opacity: .55; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
|
||||
61
src/AntSK/Pages/Setting/Role/AddRole.razor
Normal file
61
src/AntSK/Pages/Setting/Role/AddRole.razor
Normal 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 {
|
||||
|
||||
}
|
||||
103
src/AntSK/Pages/Setting/Role/AddRole.razor.cs
Normal file
103
src/AntSK/Pages/Setting/Role/AddRole.razor.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/AntSK/Pages/Setting/Role/RoleList.razor
Normal file
72
src/AntSK/Pages/Setting/Role/RoleList.razor
Normal 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>;
|
||||
|
||||
}
|
||||
84
src/AntSK/Pages/Setting/Role/RoleList.razor.cs
Normal file
84
src/AntSK/Pages/Setting/Role/RoleList.razor.cs
Normal 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("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,22 @@
|
||||
</Select>
|
||||
</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">
|
||||
<Button Type="primary" OnClick="HandleSubmit">
|
||||
保存
|
||||
|
||||
@@ -12,21 +12,34 @@ namespace AntSK.Pages.Setting.User
|
||||
[Parameter]
|
||||
public string UserId { 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] public HttpClient HttpClient { get; set; }
|
||||
|
||||
private Users _userModel = new Users();
|
||||
private string _password = "";
|
||||
IEnumerable<string> _menuKeys;
|
||||
IEnumerable<string> _roleIds;
|
||||
|
||||
private List<MenuDataItem> menuList = new List<MenuDataItem>();
|
||||
private List<Roles> _allRoles = new List<Roles>();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// 加载所有角色
|
||||
_allRoles = _roles_Repositories.GetList(r => r.IsEnabled);
|
||||
|
||||
if (!string.IsNullOrEmpty(UserId))
|
||||
{
|
||||
_userModel = _users_Repositories.GetFirst(p => p.Id == UserId);
|
||||
_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();
|
||||
_menuKeys = _userModel.MenuRole?.Split(",");
|
||||
@@ -52,6 +65,21 @@ namespace AntSK.Pages.Setting.User
|
||||
}
|
||||
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
|
||||
_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
|
||||
{
|
||||
@@ -61,6 +89,28 @@ namespace AntSK.Pages.Setting.User
|
||||
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
|
||||
}
|
||||
_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();
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace AntSK.Services.Auth
|
||||
{
|
||||
public class AntSKAuthProvider(
|
||||
IUsers_Repositories _users_Repositories,
|
||||
IUserRoles_Repositories _userRoles_Repositories,
|
||||
IRoles_Repositories _roles_Repositories,
|
||||
IRolePermissions_Repositories _rolePermissions_Repositories,
|
||||
IPermissions_Repositories _permissions_Repositories,
|
||||
ProtectedSessionStorage _protectedSessionStore
|
||||
) : AuthenticationStateProvider
|
||||
{
|
||||
@@ -29,13 +33,18 @@ namespace AntSK.Services.Auth
|
||||
new Claim(ClaimTypes.Role, 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());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
string UserRole = "AntSKUser";
|
||||
if (user.IsNull())
|
||||
{
|
||||
return false;
|
||||
@@ -44,13 +53,42 @@ namespace AntSK.Services.Auth
|
||||
{
|
||||
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
|
||||
var claims = new[] {
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, UserRole)
|
||||
};
|
||||
identity = new ClaimsIdentity(claims, UserRole);
|
||||
await _protectedSessionStore.SetAsync("UserSession", new UserSession() { UserName = username, Role = UserRole });
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Name, username)
|
||||
};
|
||||
|
||||
// 添加所有角色到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());
|
||||
return true;
|
||||
}
|
||||
@@ -68,9 +106,25 @@ namespace AntSK.Services.Auth
|
||||
var userSession = userSessionStorageResult.Success ? userSessionStorageResult.Value : null;
|
||||
if (userSession.IsNotNull())
|
||||
{
|
||||
var claims = new[] {
|
||||
new Claim(ClaimTypes.Name, userSession.UserName),
|
||||
new Claim( ClaimTypes.Role, userSession.Role) };
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
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);
|
||||
}
|
||||
var user = new ClaimsPrincipal(identity);
|
||||
|
||||
@@ -59,6 +59,11 @@
|
||||
"name": "用户管理",
|
||||
"key": "setting.user"
|
||||
},
|
||||
{
|
||||
"path": "/setting/rolelist",
|
||||
"name": "角色管理",
|
||||
"key": "setting.role"
|
||||
},
|
||||
{
|
||||
"path": "/setting/chathistory",
|
||||
"name": "聊天记录",
|
||||
|
||||
Reference in New Issue
Block a user