RuoVea.OmiApi.UserRoleMenu
8.0.3.14
See the version list below for details.
dotnet add package RuoVea.OmiApi.UserRoleMenu --version 8.0.3.14
NuGet\Install-Package RuoVea.OmiApi.UserRoleMenu -Version 8.0.3.14
<PackageReference Include="RuoVea.OmiApi.UserRoleMenu" Version="8.0.3.14" />
<PackageVersion Include="RuoVea.OmiApi.UserRoleMenu" Version="8.0.3.14" />
<PackageReference Include="RuoVea.OmiApi.UserRoleMenu" />
paket add RuoVea.OmiApi.UserRoleMenu --version 8.0.3.14
#r "nuget: RuoVea.OmiApi.UserRoleMenu, 8.0.3.14"
#:package RuoVea.OmiApi.UserRoleMenu@8.0.3.14
#addin nuget:?package=RuoVea.OmiApi.UserRoleMenu&version=8.0.3.14
#tool nuget:?package=RuoVea.OmiApi.UserRoleMenu&version=8.0.3.14
RuoVea.OmiApi.UserRoleMenu
用户角色菜单管理 API 组件 —— 基于 .NET 构建的轻量级、跨平台 RBAC 权限管理系统后端。
RuoVea.OmiApi.UserRoleMenu 是一个开箱即用的用户-角色-菜单权限管理 NuGet 包,提供用户 CRUD、角色 CRUD、菜单树管理、角色菜单授权、用户角色授权、按钮权限控制、租户与租户套餐管理、JWT 认证集成、缓存管理的完整能力。基于 SqlSugar ORM 和 DynamicWebApi,注册即自动生成 RESTful API 端点,支持 MySql / SqlServer / PostgreSQL / SQLite / Oracle 等多种数据库。
⚠️ 互斥「基座」组件:本组件与
RuoVea.OmiApi.User(仅用户)、RuoVea.OmiApi.UserRole(用户+角色)为互斥的基座组件,功能逐级增强,只能按需选择其一安装,不可同时引用。
目录
概览
功能特性
| 模块 | 功能 |
|---|---|
| 👤 用户管理 | 分页查询、增删改查、状态启用/停用、密码修改/重置、登录锁定解除、用户角色授权、基本信息管理 |
| 🔐 角色管理 | 分页查询、增删改查、状态控制、角色菜单授权、角色用户关联查询 |
| 📋 菜单管理 | 树形菜单 CRUD、按钮权限管理、菜单类型(目录/菜单/按钮)校验、登录菜单树动态构建 |
| 🔒 权限控制 | 基于按钮权限标识(xxx:xxx 格式)的细粒度权限控制,按钮权限缓存自动刷新 |
| 🌱 种子数据 | 自动建表、预置菜单/角色/用户种子数据(Web 菜单与 API 菜单双模式) |
| 🗄️ 缓存管理 | 按钮权限缓存、黑名单缓存、密码错误次数缓存,支持按前缀批量删除与查询 |
| 🛡️ 安全防护 | 超级管理员禁止删除/修改状态、禁止操作本人账号状态、禁止删除含用户的角色 |
| 🏢 多租户 | SysUser / SysRole / SysMenu 实现 ITenantEntity,支持租户隔离 |
| 🏬 租户与套餐管理 | 租户 CRUD、租户状态/套餐分配、套餐 CRUD 与角色菜单联动裁剪(按 ConnectionConfigs[0].IsTenantIdFilter 开关注册) |
| 🔑 JWT 集成 | 内置 JwtBearer 认证配置,与 RuoVea.ExJwtBearer 无缝对接 |
| 🗄️ 多库支持 | MySql、SqlServer、PostgreSQL、SQLite、Oracle、Dm 等 |
架构一览
┌─────────────────────────────────────────────────────────────┐
│ NuGet Package │
│ RuoVea.OmiApi.UserRoleMenu │
├─────────────────────────────────────────────────────────────┤
│ Service Layer (9 Services) │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ SysUser │ │ SysRole │ │ SysMenu │ │
│ │ Service │ │ Service │ │ Service │ │
│ │ 17 endpoints │ │ 8 endpoints │ │ 6 endpoints │ │
│ ├───────────────┤ ├───────────────┤ ├───────────────┤ │
│ │ SysUserRole │ │ SysRoleMenu │ │ SysCache │ │
│ │ Service │ │ Service │ │ Service │ │
│ │ (internal) │ │ (internal) │ │ 5 endpoints │ │
│ ├───────────────┴─┴───────────────┴─┴───────────────┤ │
│ │ SysTenantService (12) · SysTenantPackageService (7)│ │
│ │ — 仅当 ConnectionConfigs[0].IsTenantIdFilter=true │ │
│ │ 时注册(Swagger tenant 分组) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ DI Extensions │
│ ├─ AddOmiSystemSetup() (3 overloads) │
│ └─ AddSystemInitSetup(isWeb) (种子数据 + 建表) │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer (8 Entities) │
│ SysUser ──< SysUserRole >── SysRole │
│ SysRole ──< SysRoleMenu >── SysMenu │
│ SysMenu ─── SysMenu (Self-ref tree, Pid) │
│ SysTenant ──< SysTenantPackageRel >── SysTenantPackage │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure │
│ SqlSugar ORM · DynamicWebApi · ExSugar Repo │
│ ExJwtBearer · ExFilter · ExPws · OmiApi.Config │
└─────────────────────────────────────────────────────────────┘
支持的 .NET 版本
| TFM | NuGet 版本 |
|---|---|
net8.0 |
8.0.3.13 |
net10.0 |
10.0.1.11 |
安装
NuGet 包管理器
# .NET 8 项目
Install-Package RuoVea.OmiApi.UserRoleMenu -Version 8.0.3.13
# .NET 10 项目
Install-Package RuoVea.OmiApi.UserRoleMenu -Version 10.0.1.11
.NET CLI
dotnet add package RuoVea.OmiApi.UserRoleMenu --version 8.0.3.13
依赖项
本包依赖以下组件(安装时会自动引入):
| 包名 | 用途 |
|---|---|
RuoVea.DynamicWebApi |
动态 API 控制器生成 |
RuoVea.ExSugar |
SqlSugar 仓储模式封装 |
RuoVea.ExJwtBearer |
JWT 认证集成 |
RuoVea.ExFilter |
请求过滤与拦截 |
RuoVea.ExPws |
密码加密服务 |
RuoVea.OmiApi.Config |
系统配置管理 |
30 秒快速开始
1. 配置数据库连接 (appsettings.json)
{
"ConnectionConfigs": [
{
"DbType": "Sqlite",
"ConnectionString": "DataSource=./ruovea.db",
"IsTenantIdFilter": false
}
],
/* Jwt 配置 */
"Jwt": {
"ValidateIssuerSigningKey": true,
"IssuerSigningKey": "3c1cbc3f546eda35168c3aa3cb91780fbe703f0996c6d123ea96dc85c70bbc0a",
"ValidateIssuer": true,
"ValidIssuer": "SecurityDemo.Authentication.JWT",
"ValidateAudience": true,
"ValidAudience": "jwtAudience",
"ValidateLifetime": true,
"ExpiredTime": 1440,
"ClockSkew": 5
},
"PasswordConfig": {
"DefaultPassword": "123456",
"StrongPassword": true
},
"DbInitConfig": {
"InitTable": true,
"InitSeedData": true
},
"Swagger": {
"ApiVersions": [
{
"Title": "系统应用",
"Version": "system"
},
{
"Title": "租户管理",
"Version": "tenant"
}
]
}
}
支持的 DbType 值:
MySql、SqlServer、Sqlite、Oracle、PostgreSQL、Dm、Kdbndp、OpenGauss、ClickHouse等。
2. 注册服务 (Program.cs)
// <summary>
// 在 Program.cs 中注册 OmiApi.UserRoleMenu 组件服务
// </summary>
var builder = WebApplication.CreateBuilder(args);
// 注册动态 Web API(自动将 Service 映射为 REST 控制器)
builder.Services.AddDynamicWebApi(options =>
{
options.RemoveControllerPostfixes = new List<string> { "AppService", "Service" };
options.RemovePrefix = new List<string> { "get", "post" };
});
// 注册用户角色菜单模块服务(默认 Scoped 生命周期)
builder.Services.AddOmiSystemSetup();
// 注册 SqlSugar ORM
builder.Services.AddSqlSugarSetup();
// 注册 HttpContext 用户上下文
builder.Services.AddHttpContextSetup<AspNetUser>();
// 注册 JWT 认证
builder.Services.AddAuthenticationSetup(IdentifyEnum.Jwt, true);
// 初始化数据库表结构和种子数据(isWeb: true=Web菜单, false=API菜单)
builder.Services.AddSystemInitSetup(isWeb: true);
// 注册请求拦截、验证、异常处理
builder.Services
.RequestActionSetup()
.ResultSetup()
.ExceptionSetup();
// 注册 Swagger
builder.Services.AddSwaggerSetup();
// 跨域配置
builder.Services.AddCors(option =>
{
option.AddDefaultPolicy(builder =>
{
builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
});
});
var app = builder.Build();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.Run();
3. 启动并访问 Swagger
启动项目后,访问 https://localhost:xxxx/swagger,即可看到 "系统应用" 分组下的全部 RESTful API 端点。
核心场景
场景一:创建用户并授权角色
// <summary>
// 创建用户并授权角色 —— 异步写法。先创建用户,再为其分配角色集合。
// </summary>
public async Task<bool> CreateUserWithRolesAsync(
SysUserService userService, AddUserInput input, List<long> roleIds)
{
// 1. 创建用户(账号去重检查自动执行,返回新用户 Id)
var userId = await userService.AddUser(input);
// 2. 授权用户角色
await userService.GrantRole(new UserRolesInput
{
UserId = userId,
RoleIdList = roleIds
});
return true;
}
// <summary>
// 创建用户并授权角色 —— 同步写法(通过 .GetAwaiter().GetResult() 调用)
// ⚠️ 注意:在 ASP.NET 上下文中可能导致死锁,仅推荐在 Console/测试环境使用。
// </summary>
public bool CreateUserWithRoles(SysUserService userService, AddUserInput input, List<long> roleIds)
{
var userId = userService.AddUser(input).GetAwaiter().GetResult();
userService.GrantRole(new UserRolesInput
{
UserId = userId,
RoleIdList = roleIds
}).GetAwaiter().GetResult();
return true;
}
创建用户并授权流程:
开始
│
├─ 1. 校验账号是否已存在 → 存在则抛出 i18n.account_exists
│
├─ 2. 密码加密(IPasswordServer)
│
├─ 3. INSERT SysUser(用户主表)
│
└─ 4. DELETE + INSERT SysUserRole[](先清空旧角色,再批量写入新角色)
│
完成
场景二:角色菜单授权(完整权限配置)
// <summary>
// 为角色授予菜单权限 —— 异步写法。传入角色ID和菜单ID集合,先清空旧权限再批量写入。
// </summary>
public async Task GrantMenuToRoleAsync(SysRoleService roleService, long roleId, List<long> menuIds)
{
await roleService.GrantMenu(new RoleMenuInput
{
Id = roleId,
MenuIdList = menuIds
});
}
// <summary>
// 查询角色已有的菜单权限 —— 用于授权页面的回显
// </summary>
public async Task<List<long>> GetRoleMenuIdsAsync(SysRoleService roleService, long roleId)
{
return await roleService.GetOwnMenuList(new RoleInput { Id = roleId });
}
角色菜单授权流程:
开始事务
│
├─ 1. DELETE FROM SysRoleMenu WHERE RoleId = @roleId(清空旧权限)
│
├─ 2. INSERT SysRoleMenu[](批量写入新权限)
│
└─ 3. 清除按钮权限缓存(按前缀批量删除)
│
提交事务
场景三:获取登录用户菜单树(动态导航)
// <summary>
// 获取当前登录用户的菜单树 —— 根据用户角色动态构建,自动过滤禁用菜单和按钮类型节点。
// 异步写法:适用于 Controller 或 Service 中直接调用。
// </summary>
public async Task<List<MenuOutput>> GetUserMenuTreeAsync(SysMenuService menuService)
{
return await menuService.GetLoginMenuTree();
}
// <summary>
// 获取系统菜单树(用于角色授权时选择) —— 包含所有节点供管理员勾选。
// </summary>
public async Task<List<MenuTreeOutput>> GetGrantMenuTreeAsync(
SysMenuService menuService, long roleId)
{
// TreeForGrant 为 [NonAction] 内部方法:仅可注入调用,不对外暴露 HTTP 端点
return await menuService.TreeForGrant(
new TreeForGrantInput { RoleId = roleId },
isSuperAdmin: true, // 管理员查看全部菜单树
userId: 0
);
}
// <summary>
// 获取登录用户菜单树 —— 同步写法
// ⚠️ 注意:在 ASP.NET 上下文中可能导致死锁,仅推荐在 Console/测试环境使用。
// </summary>
public List<MenuOutput> GetUserMenuTree(SysMenuService menuService)
{
return menuService.GetLoginMenuTree().GetAwaiter().GetResult();
}
登录菜单树构建流程:
1. 获取当前用户信息(ICurrentUser)
│
2. 查询用户角色集合(SysUserRole)
│
3. 查询角色菜单集合(SysRoleMenu)
│
4. 查询所有菜单(SysMenu)
│
5. 过滤:
├─ 仅保留用户角色拥有的菜单
├─ 排除 IsDisable = Y(已禁用)
└─ 排除 Type = 按钮类型(目录/菜单保留)
│
6. 递归构建树形结构(Pid 自引用)
│
返回树形菜单
场景四:按钮权限校验(细粒度权限控制)
// <summary>
// 获取当前用户的按钮权限标识集合 —— 支持缓存,用于前端按钮显隐控制。
// 异步写法:返回 List<string>,如 ["user:add", "user:delete", "role:grant"]。
// </summary>
public async Task<List<string>> GetUserButtonPermissionsAsync(SysMenuService menuService)
{
return await menuService.GetOwnBtnPermList();
}
// <summary>
// 检查用户是否拥有特定按钮权限 —— 用于后端 API 鉴权。
// </summary>
public async Task<bool> HasPermissionAsync(SysMenuService menuService, string permission)
{
var perms = await menuService.GetOwnBtnPermList();
return perms.Contains(permission);
}
// <summary>
// 检查用户是否拥有特定按钮权限 —— 同步写法
// </summary>
public bool HasPermission(SysMenuService menuService, string permission)
{
var perms = menuService.GetOwnBtnPermList().GetAwaiter().GetResult();
return perms.Contains(permission);
}
按钮权限缓存流程:
首次请求 GetOwnBtnPermList()
│
├─ 缓存命中 → 直接返回
│
└─ 缓存未命中:
│
├─ 1. 获取用户角色(SysUserRole)
├─ 2. 获取角色菜单(SysRoleMenu)
├─ 3. 查询按钮类型菜单(Type = 按钮)
├─ 4. 提取 Permission 字段
├─ 5. 过滤空值和非法格式
├─ 6. 写入缓存,设置过期时间
│
返回权限标识集合
角色权限变更时:
→ GrantMenu() 自动调用 RemoveByPrefixKey() 清除按钮权限缓存
场景五:密码修改与重置(安全闭环)
// <summary>
// 用户自行修改密码 —— 需验证旧密码,且新密码不能与旧密码相同。
// 异步写法。
// </summary>
public async Task<bool> ChangePasswordAsync(
SysUserService userService, ChangePwdInput input)
{
// input 包含: PasswordOld (旧密码), PasswordNew (新密码), isEncPass (是否已加密)
await userService.ChangePwd(input);
return true;
}
// <summary>
// 管理员重置用户密码 —— 无需旧密码,重置为系统默认密码(PasswordConfig.DefaultPassword)。
// 异步写法。
// </summary>
public async Task<bool> ResetUserPasswordAsync(
SysUserService userService, ResetPwdUserInput input)
{
// input 仅包含: Id (用户ID);新密码为系统默认密码
await userService.ResetPwd(input);
return true;
}
// <summary>
// 解除用户登录锁定 —— 清除密码错误次数缓存。
// 异步写法。
// </summary>
public async Task UnlockUserAsync(SysUserService userService, long userId)
{
await userService.UnlockLogin(new UnlockLoginInput { Id = userId });
}
// <summary>
// 修改密码 —— 同步写法
// </summary>
public bool ChangePassword(SysUserService userService, ChangePwdInput input)
{
userService.ChangePwd(input).GetAwaiter().GetResult();
return true;
}
密码修改流程:
1. 获取当前用户信息(ICurrentUser)
│
2. 校验旧密码(isEncPass=false 时 Encrypt(旧密码) 与库中密码比对)
├─ 不匹配 → 抛出 i18n.password_error
│
3. 校验新旧密码不能相同
├─ 相同 → 抛出 i18n.new_password_same_as_old
│
4. StrongPassword=true 时 IPasswordServer.Validate(新密码) 校验强度
│
5. UPDATE SysUser SET Password = IPasswordServer.Encrypt(新密码)
│
完成
配置选项详解
数据库连接配置 (ConnectionConfigs)
{
"ConnectionConfigs": [
{
"DbType": "Sqlite",
"ConnectionString": "DataSource=./ruovea.db",
"EnableUnderLine": false,
"EnableDiffLog": false,
"IsEncrypt": false,
"DbSecurity": "",
"IsDeleteFilter": true,
"IsUserIdFilter": false,
"IsTenantIdFilter": false,
"CommandTimeOut": 30
}
]
}
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DbType |
string | 必填 | 数据库类型 |
ConnectionString |
string | 必填 | 连接字符串 |
EnableUnderLine |
bool | false |
驼峰转下划线 |
EnableDiffLog |
bool | false |
启用库表差异日志 |
IsEncrypt |
bool | false |
连接字符串是否加密 |
DbSecurity |
string | "" |
解密密钥(IsEncrypt=true 时使用) |
IsDeleteFilter |
bool | true |
⚠️ 全局软删除过滤(实体需继承 IDeletedEntity) |
IsUserIdFilter |
bool | false |
按创建者过滤(实体需继承 ICreatorFilter 或 EntityBase) |
IsTenantIdFilter |
bool | false |
按租户过滤(SysUser/SysRole/SysMenu 实现 ITenantEntity)。⚠️ 此开关同时决定租户功能:true 时注册 SysTenantService / SysTenantPackageService(Swagger tenant 分组)、初始化租户相关表并写入含租户管理/套餐管理的菜单种子数据 |
CommandTimeOut |
int | 30 |
SQL 命令超时时间(秒) |
表初始化配置 (DbInitConfig)
{
"DbInitConfig": {
"InitTable": true,
"InitSeedData": true
}
}
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
InitTable |
bool | true |
启动时自动检查并创建缺失的数据表(CodeFirst,含租户相关表,前提是 IsTenantIdFilter=true) |
InitSeedData |
bool | true |
是否写入种子数据(菜单/角色/角色菜单/用户/用户角色,以及默认租户) |
❗ 性能提醒:
AddSystemInitSetup在后台执行表结构检查与种子数据写入(Storageable按主键幂等,不覆盖已有数据)。生产环境首次启动后建议将InitTable和InitSeedData设为false。
JWT 认证配置 (Jwt)
{
"Jwt": {
"ValidateIssuerSigningKey": true,
"IssuerSigningKey": "3c1cbc3f546eda35168c3aa3cb91780fbe703f0996c6d123ea96dc85c70bbc0a",
"ValidateIssuer": true,
"ValidIssuer": "SecurityDemo.Authentication.JWT",
"ValidateAudience": true,
"ValidAudience": "jwtAudience",
"ValidateLifetime": true,
"ExpiredTime": 1440,
"ClockSkew": 5
}
}
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ValidateIssuerSigningKey |
bool | true |
是否验证密钥 |
IssuerSigningKey |
string | 必填 | ⚠️ 密钥,长度必须大于 16 且足够复杂 |
ValidateIssuer |
bool | true |
是否验证签发方 |
ValidIssuer |
string | 必填 | 签发方标识 |
ValidateAudience |
bool | true |
是否验证签收方 |
ValidAudience |
string | 必填 | 签收方标识 |
ValidateLifetime |
bool | true |
是否验证过期时间 |
ExpiredTime |
int | 1440 |
过期时间(分钟),默认 24 小时 |
ClockSkew |
int | 5 |
❗ 过期时间容错值(秒),默认 5 秒 |
密码配置 (PasswordConfig)
{
"PasswordConfig": {
"DefaultPassword": "123456",
"StrongPassword": true
}
}
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DefaultPassword |
string | "123456" |
新增/重置用户时的默认密码(种子用户与租户管理员密码均使用该值加密) |
StrongPassword |
bool | true |
是否启用强密码校验(修改密码时通过 IPasswordServer.Validate() 校验强度) |
Swagger API 版本配置
{
"Swagger": {
"ApiVersions": [
{
"Title": "系统应用",
"Version": "system"
}
]
}
}
| 配置项 | 类型 | 说明 |
|---|---|---|
Title |
string | API 版本显示标题,用于 Swagger UI 中展示 |
Version |
string | API 版本标识,用于路由和文档区分 |
本组件中
system分组(用户/角色/菜单/缓存)与tenant分组(租户/租户套餐,仅在开启多租户时出现)需要分别配置。
DI 注册配置
// <summary>
// AddOmiSystemSetup —— 三种重载,适应不同配置来源。
// </summary>
// 重载 1:自动从全局 AppSettings 读取配置
builder.Services.AddOmiSystemSetup();
// 重载 2:传入自定义 IConfiguration
builder.Services.AddOmiSystemSetup(configuration.GetSection("MySystem"));
// 重载 3:通过 Action 委托配置 DbInitConfig
builder.Services.AddOmiSystemSetup(options =>
{
options.InitTable = true;
});
// 自定义服务生命周期
builder.Services.AddOmiSystemSetup(ServiceLifetime.Singleton);
| 方法 | 参数 | 默认值 | 说明 |
|---|---|---|---|
AddOmiSystemSetup() |
无 | — | 从 AppSettings 自动绑定配置,Scoped 生命周期 |
AddOmiSystemSetup(IConfiguration config) |
config |
— | 通过 IConfiguration 传入配置节 |
AddOmiSystemSetup(Action<DbInitConfig> config) |
config 委托 |
— | 代码内配置 DbInitConfig |
serviceLifetime |
ServiceLifetime |
Scoped |
注册所有服务的生命周期 |
// <summary>
// AddSystemInitSetup —— 初始化数据库表和种子数据。
// </summary>
// isWeb = true:初始化 Web 菜单种子数据
builder.Services.AddSystemInitSetup(isWeb: true);
// isWeb = false:初始化 API 菜单种子数据
builder.Services.AddSystemInitSetup(isWeb: false);
| 方法 | 参数 | 默认值 | 说明 |
|---|---|---|---|
AddSystemInitSetup(bool isWeb) |
isWeb |
false |
异步初始化数据库表和种子数据,控制菜单种子类型 |
⚠️ 线程安全: 切换为
Singleton生命周期时,确保注入的SugarRepository<T>和ISqlSugarClient本身支持并发访问。SqlSugar 的SqlSugarClient是线程安全的,但仓储的某些操作依赖请求上下文(如ICurrentUser自动填充),单例模式下可能导致用户信息串扰。
❗ 性能提醒:
AddSystemInitSetup使用后台任务执行表结构检查和种子数据写入。生产环境首次启动后,已完成初始化的数据库无需重复执行,可通过AddOmiSystemSetup(options => { options.InitTable = false; })关闭。
API 接口速览
接口自动归入 Swagger 分组:system(用户/角色/菜单/缓存)与 tenant(租户/租户套餐,仅在 ConnectionConfigs[0].IsTenantIdFilter = true 时注册)。默认路由前缀由 DynamicWebApi 配置决定(默认 /api)。
SysUserService —— 用户管理
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
GET |
/api/SysUser/GetUserById |
GetUserById(long userId) |
根据用户 Id 获取用户实体 |
GET |
/api/SysUser/GetPages |
GetPagesAsync([FromQuery] PageUserInput) |
获取用户分页列表(含角色名称,排除超管) |
GET |
/api/SysUser/GetRoleUserListByRoleId |
GetRoleUserListByRoleId(long roleId) |
获取指定角色下的用户列表(非超管、启用中) |
GET |
/api/SysUser/UserList |
UserList() |
获取用户列表(不含超管) |
GET |
/api/SysUser/GetAll |
GetAllAsync() |
获取全部用户简要列表(仅 ID、账号、姓名) |
GET |
/api/SysUser/GetBaseInfo |
GetBaseInfo() |
查看用户基本信息 |
PUT |
/api/SysUser/UpdateBaseInfo |
UpdateBaseInfo(SysUser) |
更新用户基本信息 |
POST |
/api/SysUser/AddUser |
AddUser(AddUserInput) |
增加用户(自动去重 Account、加密密码、同步授权角色) |
PUT |
/api/SysUser/UpdateUser |
UpdateUser(UpdateUserInput) |
更新用户(忽略密码和状态字段,同步更新角色) |
DELETE |
/api/SysUser/DeleteUser |
DeleteUser(DeleteUserInput) |
删除用户(禁止删除超管/自身,级联删除用户角色) |
POST |
/api/SysUser/SetStatus |
SetStatus(UserInput) |
设置用户状态(启用/停用,联动黑名单缓存) |
POST |
/api/SysUser/GrantRole |
GrantRole(UserRolesInput) |
授权用户角色(先删后插,清除按钮权限缓存) |
POST |
/api/SysUser/ChangePwd |
ChangePwd(ChangePwdInput) |
修改用户密码(需验证旧密码,支持强度校验) |
POST |
/api/SysUser/ResetPwd |
ResetPwd(ResetPwdUserInput) |
重置用户密码(管理员操作,重置为默认密码) |
POST |
/api/SysUser/UnlockLogin |
UnlockLogin(UnlockLoginInput) |
解除登录锁定(清空密码错误次数缓存) |
GET |
/api/SysUser/GetOwnRoleList |
GetOwnRoleList(long userId) |
获取用户拥有角色 Id 集合 |
SysRoleService —— 角色管理
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
GET |
/api/SysRole/GetPages |
GetPagesAsync([FromQuery] PageRoleInput) |
获取角色分页列表(含权限数据隔离) |
GET |
/api/SysRole/GetList |
GetList() |
获取角色列表(仅启用、按权限范围过滤) |
POST |
/api/SysRole/AddRole |
AddRole(AddRoleInput) |
增加角色(Name/Code 唯一性校验,同步菜单授权) |
PUT |
/api/SysRole/UpdateRole |
UpdateRole(AddRoleInput) |
更新角色(同步菜单授权) |
DELETE |
/api/SysRole/DeleteRole |
DeleteRole(DeleteRoleInput) |
删除角色(禁止删除系统管理员角色、检查关联用户,级联删除用户角色与角色菜单) |
POST |
/api/SysRole/GrantMenu |
GrantMenu(RoleMenuInput) |
授权角色菜单(先删后插,清除按钮权限缓存) |
GET |
/api/SysRole/GetOwnMenuList |
GetOwnMenuList([FromQuery] RoleInput) |
根据角色 Id 获取菜单 Id 集合 |
POST |
/api/SysRole/SetStatus |
SetStatus(RoleInput) |
设置角色状态(启用/停用) |
SysMenuService —— 菜单管理
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
GET |
/api/SysMenu/GetLoginMenuTree |
GetLoginMenuTree() |
获取登录用户菜单树(超管全量 / 普通用户按权限过滤并剔除按钮) |
GET |
/api/SysMenu/GetList |
GetList([FromQuery] MenuInput) |
获取菜单列表(可按 Title/Type 过滤;有筛选时返回扁平列表) |
POST |
/api/SysMenu/AddMenu |
AddMenu(AddMenuInput) |
增加菜单(标题/路由名/父节点校验) |
PUT |
/api/SysMenu/UpdateMenu |
UpdateMenu(UpdateMenuInput) |
更新菜单 |
DELETE |
/api/SysMenu/DeleteMenu |
DeleteMenu(DeleteMenuInput) |
删除菜单(级联子菜单与角色菜单关联) |
GET |
/api/SysMenu/GetOwnBtnPermList |
GetOwnBtnPermList() |
获取用户按钮权限集合(缓存,TTL 7 天) |
SysMenuService中的TreeForGrant、GetMenuTree、GetNorMalUserMenuListQueryable、GetAllBtnPermList均为[NonAction]内部方法,不暴露为 HTTP 端点。
SysTenantService —— 租户管理(tenant 分组,仅在多租户开关开启时可用)
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
GET |
/api/SysTenant/GetPages |
GetPagesAsync([FromQuery] SysTenantParam) |
分页查询租户(支持按套餐 Id 筛选) |
GET |
/api/SysTenant/GetData |
GetDataAsync([FromQuery] EntityBaseIdDto) |
根据 ID 获取租户详情 |
GET |
/api/SysTenant/GetList |
GetListAsync() |
获取租户列表(仅启用) |
GET |
/api/SysTenant/GetTenantByName |
GetTenantByName(string name) |
根据名称获取租户 |
GET |
/api/SysTenant/GetTenantByWebsite |
GetTenantByWebsite(string website) |
根据绑定域名获取租户 |
POST |
/api/SysTenant/AddData |
AddDataAsync(SysTenantDto) |
新增租户(自动创建租户管理员用户 + 默认角色 + 套餐授权) |
PUT |
/api/SysTenant/UpdateData |
UpdateDataAsync(SysTenantDto) |
更新租户(同步重新分配套餐) |
DELETE |
/api/SysTenant/DeleteData |
DeleteDataAsync([FromQuery] EntityBaseIdDto) |
删除租户(软删,默认租户禁止删除,级联删除套餐关联) |
PUT |
/api/SysTenant/ChangeStatus |
ChangeStatus(ChangeStateTenantInput) |
更新租户状态(默认租户禁止停用) |
GET |
/api/SysTenant/GetTenantPackageIds |
GetTenantPackageIds(long tenantId) |
获取租户的套餐 Id 集合 |
GET |
/api/SysTenant/GetTenantPackages |
GetTenantPackages(long tenantId) |
获取租户的套餐列表 |
POST |
/api/SysTenant/GrantPackages |
GrantPackages(GrantTenantPackageInput) |
分配租户套餐(全量覆盖,同步租户角色菜单) |
SysTenantPackageService —— 租户套餐管理(tenant 分组,仅在多租户开关开启时可用)
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
GET |
/api/SysTenantPackage/GetPages |
GetPagesAsync([FromQuery] SysTenantPackageParam) |
分页查询租户套餐 |
GET |
/api/SysTenantPackage/GetData |
GetDataAsync([FromQuery] EntityBaseIdDto) |
根据 ID 获取套餐详情 |
GET |
/api/SysTenantPackage/GetList |
GetListAsync() |
获取套餐列表(仅启用) |
POST |
/api/SysTenantPackage/AddData |
AddDataAsync(SysTenantPackageDto) |
新增套餐(Name/Code 唯一性校验) |
PUT |
/api/SysTenantPackage/UpdateData |
UpdateDataAsync(SysTenantPackageDto) |
更新套餐(菜单变化时同步租户角色菜单) |
DELETE |
/api/SysTenantPackage/DeleteData |
DeleteDataAsync([FromQuery] EntityBaseIdDto) |
删除套餐(软删,同步裁剪关联租户的角色菜单) |
PUT |
/api/SysTenantPackage/ChangeStatus |
ChangeStatus(ChangeStateTenantPackageInput) |
更新套餐状态 |
SysTenantPackageService中的SyncTenantRoleMenus、SyncTenantRoleMenusByTenantId均为[NonAction]内部方法,不暴露为 HTTP 端点。
SysCacheService —— 缓存管理
| HTTP | 路由 | 方法 | 说明 |
|---|---|---|---|
GET |
/api/SysCache/GetValue |
GetValue(string key) |
根据键名获取缓存值 |
GET |
/api/SysCache/GetKeysByPrefixKey |
GetKeysByPrefixKey(string prefixKey) |
根据键名前缀获取键名集合 |
DELETE |
/api/SysCache/Remove |
Remove(string key) |
删除指定缓存 |
DELETE |
/api/SysCache/RemoveByPrefixKey |
RemoveByPrefixKey(string prefixKey) |
根据键名前缀批量删除缓存 |
DELETE |
/api/SysCache/Clear |
Clear(string profix = "RuoVea*") |
清空所有缓存 |
SysCacheService中的Set/Get<T>/ExistKey/AdGetAsync<T>等均为[NonAction]内部方法,仅供服务内部调用,不暴露为 HTTP 端点。
内部服务(不公开 API)
| 服务 | 依赖 | 说明 |
|---|---|---|
SysUserRoleService |
SugarRepository<SysUserRole>, SysCacheService |
用户角色关联 CRUD(授权/删除/查询,供 SysUserService 等内部调用) |
SysRoleMenuService |
SugarRepository<SysRoleMenu>, SysCacheService |
角色菜单关联 CRUD(授权/删除/查询,供 SysRoleService 和 SysMenuService 内部调用) |
错误处理与日志
错误码速查
组件内部使用 i18n 国际化资源管理错误信息,支持多语言切换。以下为本组件服务实际抛出的核心错误码:
| 错误码 | 含义 | 触发场景 |
|---|---|---|
i18n.account_exists |
账号已存在 | 创建用户时账号重复 |
i18n.account_not_exists |
账号不存在 | 删除/修改/查询的用户不存在 |
i18n.data_exists |
数据已存在 | 创建角色时 Name/Code 重复 |
i18n.dict_status_error |
字典状态错误 | 设置用户/角色状态时 IsDisable 不是合法枚举值 |
i18n.illegal_operation_self |
非法操作,禁止删除自己 | 用户尝试删除自身账号 |
i18n.new_password_same_as_old |
新密码不能与旧密码相同 | 修改密码时新旧密码一致 |
i18n.parent_node_cannot_be_button |
父节点不能为按钮类型 | 创建/更新菜单时选择了按钮类型作为父节点 |
i18n.password_error |
旧密码输入错误 | 修改密码时旧密码校验失败 |
i18n.permission_id_format_empty |
权限标识格式为空 | 按钮权限标识为空字符串 |
i18n.permission_id_format_error |
权限标识格式错误 | 按钮权限标识不符合 xxx:xxx 格式 |
i18n.prohibit_delete_admin |
禁止删除系统管理员角色 | 尝试删除 sys_manager_role 角色 |
i18n.prohibit_delete_super_admin |
禁止删除超级管理员 | 尝试删除超级管理员账号 |
i18n.prohibit_modify_self_status |
禁止修改本人账号状态 | 尝试启用/停用自己的账号 |
i18n.prohibit_modify_super_admin_status |
禁止修改超级管理员状态 | 尝试修改超级管理员的启用状态 |
i18n.prohibit_same_node_as_parent |
禁止本节点与父节点相同 | 菜单编辑时将自身设为父节点 |
i18n.record_not_exists |
记录不存在 | 删除角色时角色记录缺失 |
i18n.role_has_accounts |
此角色下面存在账号禁止删除 | 删除角色时仍有用户关联 |
i18n.route_name_duplicate |
路由名称重复 | 创建/更新菜单时路由名称已存在 |
ErrorEnum.D4000 |
通用业务错误 | 创建/更新菜单时同层级标题或权限标识重复 |
租户/套餐模块错误码(tenant 分组服务):
| 错误码 | 含义 | 触发场景 |
|---|---|---|
i18n.error_message_tenant_name_exists |
租户名称已存在 | 新增/更新租户时名称重复 |
i18n.error_message_tenant_not_found |
租户不存在 | 更新/删除/改状态时租户记录缺失 |
i18n.error_message_tenant_package_exists |
套餐已存在 | 新增/更新套餐时 Name/Code 重复 |
i18n.error_message_tenant_package_not_found |
套餐不存在 | 更新/删除/改状态时套餐记录缺失 |
i18n.error_message_tenant_default_cannot_delete |
默认租户不可删除/停用 | 对系统默认租户执行删除或停用 |
i18n.error_message_tenant_admin_account_required |
租户管理员账号必填 | 新增租户时未填写 AdminAccount |
i18n.error_message_tenant_admin_account_exists |
租户管理员账号已存在 | 管理员账号全局唯一校验失败 |
i18n.error_message_tenant_expire_time_required |
过期时间必填 | 新增/更新租户时未填写 ExpireTime |
i18n.error_message_tenant_account_count_required |
账号配额必填 | 新增/更新租户时未填写 AccountCount |
i18n.error_message_invalid_status |
状态值非法 | 租户/套餐状态值不是合法枚举 |
异常处理示例
// <summary>
// 安全创建用户 —— 捕获参数、业务和数据库异常。
// 异步写法。
// </summary>
public async Task<(bool Success, string Message)> SafeCreateUserAsync(
SysUserService userService, AddUserInput dto)
{
try
{
await userService.AddUser(dto);
return (true, "用户创建成功");
}
catch (Exception ex) when (ex.Message.Contains("account_exists"))
{
// 账号已存在
return (false, $"账号 '{dto.Account}' 已存在,请更换账号名");
}
catch (ArgumentException ex)
{
// 参数校验失败(必填字段缺失、格式错误)
return (false, $"参数校验失败: {ex.Message}");
}
catch (Exception ex) when (ex.Message.Contains("transaction", StringComparison.OrdinalIgnoreCase))
{
// 事务执行失败(数据库层面错误)
return (false, $"数据操作失败,已自动回滚: {ex.Message}");
}
}
// <summary>
// 安全删除角色 —— 含关联数据防护检查。
// 异步写法。
// </summary>
public async Task<(bool Success, string Message)> SafeDeleteRoleAsync(
SysRoleService roleService, long roleId)
{
try
{
await roleService.DeleteRole(new DeleteRoleInput { Id = roleId });
return (true, "角色删除成功");
}
catch (Exception ex) when (ex.Message.Contains("prohibit_delete_admin"))
{
return (false, "系统管理员角色禁止删除");
}
catch (Exception ex) when (ex.Message.Contains("role_has_accounts"))
{
return (false, "该角色下仍有用户关联,请先解除用户角色关系");
}
catch (Exception ex) when (ex.Message.Contains("record_not_exists"))
{
return (false, "角色不存在或已被删除");
}
}
// <summary>
// 安全删除角色 —— 同步写法(仅在非 ASP.NET 上下文使用)
// </summary>
public (bool Success, string Message) SafeDeleteRole(SysRoleService roleService, long roleId)
{
try
{
roleService.DeleteRole(new DeleteRoleInput { Id = roleId }).GetAwaiter().GetResult();
return (true, "角色删除成功");
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
日志集成
组件不直接输出日志,依赖调用方集成的日志框架。推荐在 Program.cs 中配置 Serilog 或 NLog 来捕获:
// SqlSugar 的 SQL 日志可通过 AOP 事件捕获
builder.Services.AddSqlSugarSetup(); // 内部配置了 SQL 执行日志
版本迁移指南
从 8.0.x 升级到 10.0.x
| 变更项 | 说明 |
|---|---|
| TFM 升级 | net8.0 → net10.0,需同步升级所有依赖包到 10.0.* 版本 |
| 包版本对齐 | RuoVea.ExFilter、RuoVea.ExJwtBearer、RuoVea.ExPws、RuoVea.OmiApi.Config、RuoVea.DynamicWebApi、RuoVea.ExSugar 均需升至对应 10.0.* |
| API 兼容 | 所有公开 API 向后兼容,无需修改业务代码 |
| 数据库 | 表结构无变更,无需执行迁移脚本 |
API 变更历史
| 版本 | 变更 |
|---|---|
8.0.3.13 / 10.0.1.11 |
当前版本:新增租户/套餐管理(多租户开关 IsTenantIdFilter 控制注册与建表)、菜单支持可见端/移动路径/快捷入口等 |
8.0.2.19 |
修复多字段查询时 SqlSugar 因重复参数 @value 键而报错的问题 |
8.0.2.x |
组件版本升级,图表数据缓存、表结构初始化处理 |
| 更早版本 | 初始发布 |
常见问题
Q: 如何切换数据库?
修改 appsettings.json 中的 DbType 和 ConnectionString,然后重新运行。AddSystemInitSetup 会自动为新数据库创建表结构并写入种子数据。
// MySql 示例
{ "DbType": "MySql", "ConnectionString": "Server=localhost;Database=ruovea;Uid=root;Pwd=123456;" }
// SqlServer 示例
{ "DbType": "SqlServer", "ConnectionString": "Server=.;Database=ruovea;Trusted_Connection=True;" }
// PostgreSQL 示例
{ "DbType": "PostgreSQL", "ConnectionString": "Host=localhost;Database=ruovea;Username=postgres;Password=123456;" }
Q: 如何自定义 API 路由前缀?
在 AddDynamicWebApi 中配置 DefaultApiPrefix:
builder.Services.AddDynamicWebApi(options =>
{
options.DefaultApiPrefix = "/openapi/api";
});
Q: Web 菜单和 API 菜单有什么区别?
AddSystemInitSetup(isWeb: true) 初始化的是前端 Web 应用的菜单种子数据(含路由路径、组件、图标等),isWeb: false 初始化的是纯 API 接口的菜单种子数据。根据您的项目类型选择合适的模式。
Q: ⚠️ 超级管理员账号是什么?有哪些防护?
种子数据中预置的超级管理员账号在组件内部有严格防护:
- 禁止删除超级管理员(
i18n.prohibit_delete_super_admin) - 禁止修改超级管理员状态(
i18n.prohibit_modify_super_admin_status) - 禁止修改本人账号状态(
i18n.prohibit_modify_self_status) - 禁止删除系统管理员角色(
i18n.prohibit_delete_admin)
这些防护确保系统始终至少有一个可用管理员。
Q: ❗ 按钮权限缓存何时失效?
按钮权限缓存在以下场景自动失效:
- 角色菜单授权变更(
GrantMenu)时,自动调用RemoveByPrefixKey清除相关缓存 - 缓存过期时间到达后自动失效
- 手动调用
SysCacheService.RemoveByPrefixKey(prefixKey)强制清除
如果需要立即刷新缓存,可通过 SysCacheService 的 RemoveByPrefixKey 方法清除按钮权限缓存前缀。
Q: 为什么删除角色时提示"此角色下面存在账号"?
该角色仍有用户关联时,为防止权限失控,组件会抛出 i18n.role_has_accounts 错误。解决方案:通过 SysUserService.GrantRole() 先解除所有用户与该角色的关联,再执行删除。
Q: 菜单的三种类型(目录/菜单/按钮)各自用途是什么?
| 类型 | 说明 | 典型场景 |
|---|---|---|
| 目录 | 分组节点,无路由组件,仅用于组织菜单树 | 系统管理、内容管理等顶级目录 |
| 菜单 | 有路由和组件的页面节点,显示在左侧导航 | 用户管理、角色管理等具体页面 |
| 按钮 | 权限控制节点,不显示在菜单树中,仅用于权限标识 | user:add、role:delete 等操作按钮 |
创建菜单时,父节点不能为按钮类型(
i18n.parent_node_cannot_be_button),且路由名称不允许重复(i18n.route_name_duplicate)。
Q: ⚠️ 修改密码后需要重新登录吗?
修改密码操作不会使当前 JWT Token 失效。如果业务需要修改密码后强制重新登录,请在调用方额外实现 Token 失效逻辑(如将 Token 加入黑名单缓存)。
Q: ❗ 多租户场景下数据如何隔离?
SysUser、SysRole、SysMenu 均实现 ITenantEntity 接口。启用租户过滤后,查询和操作会自动按 TenantId 过滤。配置方式:
{
"ConnectionConfigs": [
{
"IsTenantIdFilter": true
}
]
}
⚠️
ConnectionConfigs[0].IsTenantIdFilter是本组件的多租户总开关:
true:注册SysTenantService/SysTenantPackageService(Swaggertenant分组)、初始化SysTenant/SysTenantPackage/SysTenantPackageRel表、写入含"租户管理/套餐管理"的菜单种子数据与默认租户;false:不注册租户服务、不建租户表、剔除租户相关菜单种子数据。
许可证
本项目基于 Apache 2.0 License 开源发布。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- RuoVea.DynamicWebApi (>= 8.0.0)
- RuoVea.ExConfig (>= 8.0.0.1)
- RuoVea.ExFilter (>= 8.0.1.13)
- RuoVea.ExJwtBearer (>= 8.0.0.11)
- RuoVea.ExPws (>= 8.0.0.5)
- RuoVea.ExSugar (>= 8.0.1.2)
- RuoVea.OmiApi.Config (>= 8.0.1.8)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on RuoVea.OmiApi.UserRoleMenu:
| Package | Downloads |
|---|---|
|
RuoVea.OmiUserRoleMenu
字典管理 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.1.12 | 0 | 8/18/2026 |
| 10.0.1.11 | 35 | 8/17/2026 |
| 10.0.1.10 | 33 | 8/17/2026 |
| 10.0.1.9 | 48 | 8/16/2026 |
| 10.0.1.8 | 48 | 8/16/2026 |
| 10.0.1.7 | 46 | 8/16/2026 |
| 10.0.1.6 | 49 | 8/16/2026 |
| 10.0.1.5 | 39 | 8/16/2026 |
| 10.0.1.4 | 40 | 8/15/2026 |
| 10.0.1 | 59 | 8/16/2026 |
| 8.0.3.14 | 0 | 8/18/2026 |
| 8.0.3.13 | 30 | 8/17/2026 |
| 8.0.3.12 | 34 | 8/17/2026 |
| 8.0.3.9 | 40 | 8/16/2026 |
| 8.0.3.8 | 41 | 8/16/2026 |
| 8.0.3.7 | 42 | 8/16/2026 |
| 8.0.3.6 | 43 | 8/16/2026 |
| 8.0.3.5 | 37 | 8/16/2026 |
| 8.0.3.4 | 35 | 8/15/2026 |
| 8.0.3 | 42 | 8/16/2026 |