RuoVea.OmiApi.SystemApp 8.0.3.12

There is a newer version of this package available.
See the version list below for details.
dotnet add package RuoVea.OmiApi.SystemApp --version 8.0.3.12
                    
NuGet\Install-Package RuoVea.OmiApi.SystemApp -Version 8.0.3.12
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RuoVea.OmiApi.SystemApp" Version="8.0.3.12" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RuoVea.OmiApi.SystemApp" Version="8.0.3.12" />
                    
Directory.Packages.props
<PackageReference Include="RuoVea.OmiApi.SystemApp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RuoVea.OmiApi.SystemApp --version 8.0.3.12
                    
#r "nuget: RuoVea.OmiApi.SystemApp, 8.0.3.12"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RuoVea.OmiApi.SystemApp@8.0.3.12
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RuoVea.OmiApi.SystemApp&version=8.0.3.12
                    
Install as a Cake Addin
#tool nuget:?package=RuoVea.OmiApi.SystemApp&version=8.0.3.12
                    
Install as a Cake Tool

RuoVea.OmiApi.SystemApp

系统应用管理 — 用户、角色、菜单、机构、职位、权限管理 API


目录


概览

RuoVea.OmiApi.SystemApp 是基于 .NET 8.0 / .NET 10.0 构建的企业级系统应用管理组件,提供开箱即用的用户、角色、菜单、机构、职位、权限管理能力。基于 SqlSugar ORM,支持 MySql、SqlServer、PostgreSQL、SQLite、Oracle 等主流数据库,内置多租户、数据权限过滤、Redis 缓存、国际化 i18n 与种子数据初始化。

功能矩阵

模块 功能 说明
用户管理 CRUD / 密码修改与重置 / 状态管理 / 角色授权 / 登录解锁 支持 AdminType 分级、多机构兼职
角色管理 CRUD / 菜单授权 / 数据范围授权 / 分页查询 支持 DataScope 全部/自定义/本部门/本部门及以下/仅本人/个人 六种数据范围
菜单管理 树形 CRUD / 按钮权限 / 登录菜单树 / 一键生成 支持菜单类型(目录/菜单/按钮)、外链、iframe、缓存
机构管理 树形 CRUD / 数据权限过滤 / 级联删除保护 部门树天然支持多级嵌套
职位管理 CRUD / 在职人员查询 职位与用户通过 SysUserExtOrg 多对多关联
权限管理 角色-菜单、用户-角色、角色-机构 多对多关联 完整 RBAC 模型
缓存管理 Redis 缓存 / 按前缀删除/查询 / 用户权限与数据范围缓存 自动缓存失效
数据权限 SqlSugarFilter(全局 AOP)/ DataScopeService(按需调用)/ EntityBase 机构级 + 个人级隔离,支持全局自动过滤或 Manual 精确控制
多租户 ITenantEntity / TenantAttribute 租户数据天然隔离
种子数据 用户/角色/菜单/机构/职位 预设数据 一键初始化数据库表结构与业务数据
国际化 i18n 多语言资源文件(zh-CN / en-US / ja-JP / fr-FR / vi-VN / zh-HK / zh-TW) 中文错误提示开箱即用

架构总览

┌──────────────────────────────────────────────────────────────────────────┐
│                          Client (Web / Mobile / Third-Party)              │
└────────────────────────────────┬─────────────────────────────────────────┘
                                 │  HTTP (REST)
                                 ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                     RuoVea.DynamicWebApi (Auto API Mapping)               │
│           /openapi/api/SysUser/GetPagesAsync  ...                        │
└────────────────────────────────┬─────────────────────────────────────────┘
                                 │
         ┌───────────────────────┼───────────────────────┐
         ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  SysUserService │    │  SysRoleService │    │  SysMenuService │
│  依赖:          │    │  依赖:          │    │  依赖:          │
│  IPasswordServer│    │  SysRoleOrgSvc  │    │  SysRoleMenuSvc │
│  ICurrentUser   │    │  SysRoleMenuSvc │    │  SysUserRoleSvc │
│  SysOrgService  │    │  SysOrgService  │    │  SysCacheService│
└────────┬────────┘    └────────┬────────┘    └────────┬────────┘
         │                      │                      │
         ▼                      ▼                      ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                         SqlSugar ORM (SugarRepository<T>)                 │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                   │
│   │  SqlSugar    │  │  SqlSugar    │  │  SqlSugar    │                   │
│   │  Filter      │  │  Filter      │  │  Filter      │                   │
│   │ (数据权限)    │  │ (数据权限)    │  │ (数据权限)    │                   │
│   └──────────────┘  └──────────────┘  └──────────────┘                   │
└────────────────────────────────┬─────────────────────────────────────────┘
                                 │
                                 ▼
┌──────────────────────────────────────────────────────────────────────────┐
│    MySql  │  SqlServer  │  PostgreSQL  │  SQLite  │  Oracle  │  ...      │
└──────────────────────────────────────────────────────────────────────────┘

安装

NuGet 包管理器

# .NET 8.0
dotnet add package RuoVea.OmiApi.SystemApp --version 8.0.2.21

# .NET 10.0
dotnet add package RuoVea.OmiApi.SystemApp --version 10.0.0.4

或通过 Visual Studio 包管理器控制台:

# .NET 8.0
Install-Package RuoVea.OmiApi.SystemApp -Version 8.0.2.21

# .NET 10.0
Install-Package RuoVea.OmiApi.SystemApp -Version 10.0.0.4

依赖项

包名 用途
RuoVea.ExFilter 异常过滤器与请求拦截
RuoVea.ExJwtBearer JWT Bearer 认证配置
RuoVea.ExPws 密码加密服务 (IPasswordServer)
RuoVea.OmiApi.Config 数据库初始化配置 (DbInitConfig)
RuoVea.DynamicWebApi 类库自动映射为 REST API 控制器
RuoVea.ExSugar SqlSugar 数据库连接配置

30 秒快速开始

1. 配置文件 (appsettings.json)

{
  // <summary>JWT 认证配置</summary>
  "Jwt": {
    "ValidateIssuerSigningKey": true,   // 是否验证密钥,默认 true
    "IssuerSigningKey": "3c1cbc3f546eda35168c3aa3cb91780fbe703f0996c6d123ea96dc85c70bbc0a",
    "ValidateIssuer": true,
    "ValidIssuer": "RuoVea.OmiApi",
    "ValidateAudience": true,
    "ValidAudience": "omiApiAudience",
    "ValidateLifetime": true,
    "ExpiredTime": 1440,                // 过期时间(分钟),默认 1440 (24h)
    "ClockSkew": 5                      // 过期时间容错值(秒)
  },

  // <summary>数据库连接配置(第一个为默认库)</summary>
  "ConnectionConfigs": [
    {
      "DbType": "Sqlite",
      "ConnectionString": "DataSource=./ruovea.db"
    }
  ],

  // <summary>Swagger API 版本配置</summary>
  "Swagger": {
    "ApiVersions": [
      {
        "Title": "系统应用",
        "Version": "system"
      }
    ]
  },

  // <summary>数据库初始化与种子数据配置</summary>
  "DbInitConfig": {
    "InitTable": true,      // 是否自动建表
    "InitSeedData": true    // 是否初始化种子数据
  },

  // <summary>密码加密配置</summary>
  "PasswordConfig": {
    "DefaultPassword": "123qwe",
    "EncryptTimes": 3
  }
}

2. Program.cs

// <summary>构建 Web 应用并注入 SystemApp 服务</summary>
var builder = WebApplication.CreateBuilder(args);

// <summary>1. DynamicWebApi:自动将 Service 类映射为 API 控制器</summary>
builder.Services.AddDynamicWebApi(options =>
{
    options.DefaultApiPrefix = "/openapi/api";
    options.RemoveControllerPostfixes = new List<string> { "AppService", "Service" };
    options.RemovePrefix = new List<string> { "get", "post" };
});

// <summary>2. 注入用户/角色/菜单/机构/职位/权限全套服务</summary>
builder.Services.AddOmiSystemSetup();

// <summary>3. HttpContext 用户上下文</summary>
builder.Services.AddHttpContextSetup<AspNetUser>();

// <summary>4. SqlSugar 数据库连接 + 数据权限过滤器</summary>
builder.Services.AddSqlSugarSetup(moreFilter: (provider) =>
{
    SqlSugarFilter.SetOrgEntityFilter(builder.Services, provider);       // 数据范围过滤(机构/用户维度)
    SqlSugarFilter.SetCustomEntityFilter(builder.Services, provider);    // 自定义业务过滤(IEntityFilter)
});

// <summary>5. 初始化数据库表结构和种子数据(isWeb: true=Web菜单, false=API菜单)</summary>
builder.Services.AddSystemInitSetup(isWeb: false);

// <summary>6. 请求拦截器(操作日志、参数验证、统一结果、异常处理)</summary>
builder.Services
    .RequestActionSetup()
    .ResultSetup()
    .ExceptionSetup();

// <summary>7. Swagger 文档</summary>
builder.Services.AddSwaggerSetup();

// <summary>8. 跨域</summary>
builder.Services.AddCors(option =>
{
    option.AddDefaultPolicy(b =>
        b.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
});

var app = builder.Build();

// <summary>中间件管道</summary>
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseSwaggerSetup();
app.MapControllers();

app.Run();

3. 访问 Swagger

启动项目后,访问 Swagger UI,选择 "系统应用 (system)" 分组即可查看全部 API:

https://localhost:5001/swagger/index.html

核心场景

场景一:用户 CRUD 与生命周期管理

 Client                                SysUserService                    Database
  │                                         │                               │
  │  POST /openapi/api/SysUser/AddUser      │                               │
  │────────────────────────────────────────>│                               │
  │                                         │  IPasswordServer.Encrypt(pwd) │
  │                                         │  SugarRepository<SysUser>     │
  │                                         │──────────────────────────────>│
  │                                         │  INSERT INTO SysUser          │
  │                                         │<──────────────────────────────│
  │  { userId, account, ... }               │                               │
  │<────────────────────────────────────────│                               │
  │                                         │                               │
  │  PUT /openapi/api/SysUser/ResetPwd      │                               │
  │────────────────────────────────────────>│                               │
  │                                         │  验证权限 (SupAdmin 绕过)      │
  │                                         │  IPasswordServer.Encrypt(new)  │
  │                                         │  UPDATE SysUser SET Password   │
  │                                         │──────────────────────────────>│
  │  200 OK                                 │                               │
  │<────────────────────────────────────────│                               │
创建用户
// <summary>异步创建用户示例</summary>
public async Task<long> CreateUserAsync(SysUserService userService)
{
    var input = new AddUserInput
    {
        Account = "zhangsan",
        Password = "Abc@123456",
        NickName = "张三",
        RealName = "张三",
        Email = "zhangsan@example.com",
        Phone = "13800138000",
        OrgId = 1,
        PosId = 1
    };
    return await userService.AddUser(input);
}

// <summary>同步调用示例(❗仅限控制台/非 Web 上下文使用)</summary>
public long CreateUserSync(SysUserService userService)
{
    var input = new AddUserInput
    {
        Account = "zhangsan",
        Password = "Abc@123456",
        NickName = "张三",
        RealName = "张三",
        Email = "zhangsan@example.com",
        Phone = "13800138000",
        OrgId = 1,
        PosId = 1
    };
    // ❗ 同步调用会阻塞线程,生产环境请使用 async/await
    return userService.AddUser(input).GetAwaiter().GetResult();
}
修改密码
// <summary>异步修改当前用户密码</summary>
public async Task<bool> ChangePasswordAsync(SysUserService userService)
{
    var input = new ChangePwdInput
    {
        OldPassword = "Abc@123456",
        NewPassword = "Xyz@654321"
    };
    // <summary>内部验证旧密码 + 新密码加密 + 更新数据库</summary>
    return await userService.ChangePwd(input);
}

// <summary>同步修改密码(⚠️ 同步方法不支持异步上下文传播)</summary>
public bool ChangePasswordSync(SysUserService userService)
{
    var input = new ChangePwdInput
    {
        OldPassword = "Abc@123456",
        NewPassword = "Xyz@654321"
    };
    return userService.ChangePwd(input).GetAwaiter().GetResult();
}
解除登录锁定
// <summary>管理员异步解除指定用户的登录锁定</summary>
public async Task<bool> UnlockUserAsync(SysUserService userService, long userId)
{
    var input = new UnlockLoginInput { UserId = userId };
    return await userService.UnlockLogin(input);
}

场景二:角色授权与数据范围隔离

 Client                            SysRoleService                    Database
  │                                     │                               │
  │  POST .../SysRole/GrantMenu         │                               │
  │  { roleId, menuIdList }             │                               │
  │────────────────────────────────────>│                               │
  │                                     │  DELETE + INSERT              │
  │                                     │  SysRoleMenu (批量替换)       │
  │                                     │──────────────────────────────>│
  │                                     │  SysCacheService.Remove       │
  │                                     │  (清除角色权限缓存)            │
  │  200 OK                             │                               │
  │<────────────────────────────────────│                               │
  │                                     │                               │
  │  POST .../SysRole/GrantDataScope    │                               │
  │  { roleId, scope, orgIdList }       │                               │
  │────────────────────────────────────>│                               │
  │                                     │  UPDATE SysRole.DataScope     │
  │                                     │  DELETE + INSERT SysRoleOrg   │
  │                                     │──────────────────────────────>│
  │  200 OK                             │                               │
  │<────────────────────────────────────│                               │
创建角色并授权菜单
// <summary>异步创建角色并授权菜单</summary>
public async Task<long> CreateRoleWithMenusAsync(
    SysRoleService roleService, SysMenuService menuService)
{
    // <summary>创建角色</summary>
    var addRoleInput = new AddRoleInput
    {
        Name = "业务主管",
        Code = "biz_manager",
        Sort = 100,
        DataScope = DataScopeType.Custom,
        Remark = "业务部门主管角色"
    };
    long roleId = await roleService.AddRole(addRoleInput);

    // <summary>授权菜单</summary>
    var grantInput = new RoleMenuInput
    {
        RoleId = roleId,
        MenuIdList = new List<long> { 1, 2, 3, 4, 5 }
    };
    await roleService.GrantMenu(grantInput);

    return roleId;
}
授权数据范围
// <summary>异步授权角色数据范围(自定义机构列表)</summary>
public async Task<bool> GrantDataScopeAsync(SysRoleService roleService, long roleId)
{
    var input = new RoleOrgInput
    {
        RoleId = roleId,
        DataScope = DataScopeType.Custom,
        OrgIdList = new List<long> { 101, 102, 103 }
    };
    // <summary>Custom 模式下写入 SysRoleOrg 表,用于查询时过滤</summary>
    await roleService.GrantDataScope(input);
    return true;
}

// <summary>同步授权数据范围</summary>
public bool GrantDataScopeSync(SysRoleService roleService, long roleId)
{
    var input = new RoleOrgInput
    {
        RoleId = roleId,
        DataScope = DataScopeType.Custom,
        OrgIdList = new List<long> { 101, 102, 103 }
    };
    roleService.GrantDataScope(input).GetAwaiter().GetResult();
    return true;
}

场景三:菜单树与按钮权限

┌─────────────────────────────────────────────────────────┐
│  SysMenu 树形结构                                        │
│                                                         │
│  系统管理 (目录) Type=Dir                                │
│  ├─ 用户管理 (菜单) Type=Menu                            │
│  │  ├─ 新增用户 (按钮) Type=Btn  Permission=user:add     │
│  │  ├─ 编辑用户 (按钮) Type=Btn  Permission=user:edit    │
│  │  └─ 删除用户 (按钮) Type=Btn  Permission=user:delete  │
│  ├─ 角色管理 (菜单) Type=Menu                            │
│  │  ├─ 新增角色 (按钮) Type=Btn                          │
│  │  └─ 授权菜单 (按钮) Type=Btn                          │
│  └─ 菜单管理 (菜单) Type=Menu                            │
│                                                         │
└─────────────────────────────────────────────────────────┘
获取登录菜单树
// <summary>异步获取当前登录用户的菜单树(含按钮权限)</summary>
public async Task<List<MenuTreeOutput>> GetLoginMenuAsync(SysMenuService menuService)
{
    // <summary>根据用户角色过滤,返回树形菜单,按钮权限挂载在对应菜单下</summary>
    var menuTree = await menuService.GetLoginMenuTree();
    return menuTree;
}

// <summary>同步获取登录菜单树</summary>
public List<MenuTreeOutput> GetLoginMenuSync(SysMenuService menuService)
{
    return menuService.GetLoginMenuTree().GetAwaiter().GetResult();
}
一键创建含增删改查按钮的菜单
// <summary>异步一键生成菜单及标准 CRUD 按钮权限</summary>
public async Task<bool> CreateMenuWithPermissionAsync(SysMenuService menuService)
{
    var input = new AddMenuInput
    {
        Pid = 1,
        Type = MenuType.Menu,
        Name = "UserManagement",
        Path = "/system/user",
        Component = "system/user/index",
        Title = "用户管理",
        Icon = "user",
        Sort = 1,
        // <summary>勾选将自动生成增/删/改/查四个按钮子菜单</summary>
        GenerateCRUD = true
    };
    await menuService.AddMenu(input);
    return true;
}

// <summary>同步一键创建菜单</summary>
public bool CreateMenuWithPermissionSync(SysMenuService menuService)
{
    var input = new AddMenuInput
    {
        Pid = 1,
        Type = MenuType.Menu,
        Name = "UserManagement",
        Path = "/system/user",
        Component = "system/user/index",
        Title = "用户管理",
        Icon = "user",
        Sort = 1,
        GenerateCRUD = true
    };
    menuService.AddMenu(input).GetAwaiter().GetResult();
    return true;
}
获取按钮权限集合
// <summary>异步获取当前用户的按钮权限标识集合</summary>
/// <remarks>返回如 ["user:add", "user:edit", "user:delete", 前端据此控制按钮显隐</remarks>
public async Task<List<string>> GetButtonPermissionsAsync(SysMenuService menuService)
{
    return await menuService.GetOwnBtnPermList();
}

场景四:机构树与职位管理

 机构树                          职位
 ┌─────────────────┐           ┌──────────┐
 │ 集团总公司        │           │ 总经理    │  SysPosition
 │ ├─ 技术部        │           │ 技术总监  │
 │ │  ├─ 前端组     │           │ 前端组长  │
 │ │  └─ 后端组     │           │ 后端组长  │
 │ ├─ 市场部        │           │ 市场经理  │
 │ └─ 财务部        │           │ 财务经理  │
 └─────────────────┘           └──────────┘
         │                          │
         └──────────┬───────────────┘
                    ▼
          SysUserExtOrg (用户-机构-职位 关联表)
          ┌────────────────────────────────┐
          │ UserId │ OrgId │ PosId │Default│
          │    1   │  101  │   1   │  Y    │
          │    1   │  102  │   5   │  N    │  ← 用户可兼职多机构多职位
          │    2   │  101  │   3   │  Y    │
          └────────────────────────────────┘
创建机构
// <summary>异步创建组织机构</summary>
public async Task<long> CreateOrgAsync(SysOrgService orgService)
{
    var input = new AddOrgInput
    {
        Pid = 1,
        Name = "研发中心",
        Code = "RD-CENTER",
        Contacts = "李四",
        Tel = "010-88888888",
        Sort = 10
    };
    return await orgService.AddOrg(input);
}
删除机构(级联保护)
// <summary>异步删除机构 — 系统自动校验子机构与关联用户</summary>
/// <remarks>
/// 删除前校验链:
/// 1. i18n.organization_has_users — 当前机构下有用户时禁止删除
/// 2. i18n.subordinate_org_has_users — 下级机构有关联用户时禁止删除
/// 3. 仅当机构及所有下级均无用户时方可删除
/// </remarks>
public async Task<bool> DeleteOrgSafeAsync(SysOrgService orgService, long orgId)
{
    try
    {
        var input = new DeleteOrgInput { Id = orgId };
        await orgService.DeleteOrg(input);
        return true;
    }
    catch (Exception ex)
    {
        // <summary>根据 i18n 错误码提示具体失败原因</summary>
        Console.WriteLine($"删除失败: {ex.Message}");
        return false;
    }
}

场景五:缓存管理与数据权限过滤

 请求到达
     │
     ▼
 ┌──────────────────────┐
 │  SqlSugarFilter      │  数据权限过滤器(AOP 切面自动注入)
 │  .SetCustomEntityFilter()  │
 └──────────┬───────────┘
            │
            ▼
 ┌──────────────────────┐     ┌──────────────────────┐
 │  用户数据范围缓存      │     │  数据范围类型          │
 │  Redis Key:           │     │  All      → 全部     │
 │  user:datascope:{id}  │     │  Custom   → 自定义机构 │
 │                       │     │  Dept     → 本部门    │
 │  SysCacheService      │     │  DeptWithChildren     │
 │  ├─ Remove(key)       │     │           → 本部门及以下│
 │  ├─ RemoveByPrefix    │     │  Personal → 仅本人    │
 │  ├─ GetKeysByPrefix   │     │  Self     → 个人     │
 │  └─ GetValue          │     └──────────────────────┘
 └──────────────────────┘
缓存操作
// <summary>异步根据键名前缀删除缓存</summary>
/// <remarks>
/// ❗ RemoveByPrefixKey 依赖 Redis 的 SCAN 命令遍历键,键量极大时可能耗时。
///    建议在生产环境中控制前缀粒度。
/// </remarks>
public async Task RemoveCacheByPrefixAsync(SysCacheService cacheService, string prefix)
{
    await cacheService.RemoveByPrefixKey(new BaseIdInput { Value = prefix });
}

// <summary>异步获取缓存值</summary>
public async Task<string> GetCacheValueAsync(SysCacheService cacheService, string key)
{
    var input = new BaseIdInput { Value = key };
    return await cacheService.GetValue(input);
}

// <summary>同步清空所有缓存(⚠️ 生产环境慎用)</summary>
public void ClearAllCache(SysCacheService cacheService)
{
    // ⚠️ 清空全部缓存可能引发瞬时性能尖峰,建议仅在维护窗口执行
    cacheService.Clear().GetAwaiter().GetResult();
}
数据权限过滤原理
// <summary>数据权限过滤在 SqlSugar 查询中自动生效</summary>
/// <remarks>
/// SqlSugarFilter.SetCustomEntityFilter 在每次查询前动态注入 WHERE 条件:
/// - SuperAdmin 角色: 不添加任何过滤(查看全部数据)
/// - All 数据范围: 不添加过滤
/// - Dept 数据范围: 添加 WHERE OrgId IN (用户有权限的机构ID集合)
/// - DeptWithChildren: 添加 WHERE OrgId IN (本部门及所有子机构ID)
/// - Define(自定义): 添加 WHERE OrgId IN (角色配置的自定义机构ID集合)
/// - Self(仅本人): 添加 WHERE CreateUserId = @CurrentUserId
/// 
/// 无需在业务代码中手动拼接过滤条件。
/// </remarks>
public async Task<List<SysUser>> QueryWithAutoFilterAsync(SugarRepository<SysUser> repo)
{
    // <summary>查询自动带上了当前用户的数据权限过滤条件</summary>
    return await repo.GetListAsync();
}
数据权限双维度过滤(v8.0.2.23+)

组件支持两种数据权限过滤维度,在 SqlSugarFilter 中自动识别:

业务表基类 过滤条件 适用场景
EntityBaseData(含 CreateOrgId) orgIds.Contains(CreateOrgId ?? 0) 业务表存储了创建者所属机构
EntityBase(仅含 Creator,无 CreateOrgId) userIds.Contains(Creator) 业务表只记录了创建者用户Id,通过机构反查用户集合

场景一:业务表仅存储 UserId(无 OrgId 字段)

实体继承 EntityBase 即可,过滤器会通过以下链路自动计算可访问的用户范围:

当前用户 → 获取可访问的机构Id集合
        → SysUserExtOrg: WHERE OrgId IN (orgIds) → userIds
        → 自动注入: userIds.Contains(Creator)
// <summary>场景一示例:仅记录创建者的业务实体</summary>
public class MyBusinessLog : EntityBase
{
    public string Content { get; set; }
    // 注意:不继承 EntityBaseData,没有 CreateOrgId
    // 数据权限过滤器会自动按 Creator 进行用户维度过滤
}

// <summary>查询时自动生效,无需手动拼接 WHERE</summary>
public async Task<List<MyBusinessLog>> QueryLogsAsync(SugarRepository<MyBusinessLog> repo)
{
    return await repo.GetListAsync();
}

场景二:业务表存储了 OrgId

实体继承 EntityBaseData 即可,过滤器会按机构Id直接过滤(原有行为不变):

// <summary>场景二示例:记录了创建者所属机构的业务实体</summary>
public class MyBusinessOrder : EntityBaseData
{
    public string OrderNo { get; set; }
    // 继承自 EntityBaseData 的 CreateOrgId 字段
    // 数据权限过滤器会自动按 CreateOrgId 进行机构维度过滤
}

// <summary>查询时自动生效</summary>
public async Task<List<MyBusinessOrder>> QueryOrdersAsync(SugarRepository<MyBusinessOrder> repo)
{
    return await repo.GetListAsync();
}

手动调用数据范围 API

如果需要在前端或业务逻辑中主动获取数据范围:

// <summary>获取机构维度数据范围(返回机构Id集合)</summary>
public async Task<List<long>> GetOrgScopeAsync(SysUserService userService, long userId)
{
    return await userService.GetUserDataScopeIdList(userId);
}

// <summary>批量获取机构维度数据范围</summary>
public async Task<Dictionary<long, List<long>>> GetOrgsScopeAsync(SysUserService userService, List<long> userIds)
{
    return await userService.GetUsersDataScope(userIds);
}

// <summary>获取用户维度数据范围(返回用户Id集合)</summary>
public async Task<List<long>> GetUserScopeAsync(SysUserService userService, long userId)
{
    return await userService.GetUserDataScopeUserIdList(userId);
}

// <summary>批量获取用户维度数据范围</summary>
public async Task<Dictionary<long, List<long>>> GetUsersScopeAsync(SysUserService userService, List<long> userIds)
{
    return await userService.GetUsersDataScopeUserIdList(userIds);
}
	#### DataScopeService — 按需数据权限过滤(v8.0.2.26+)

DataScopeService 是比 SqlSugarFilter.SetOrgEntityFilter 更灵活的数据权限过滤方式。前者通过 moreFilter 全局 AOP 注入,对所有实体生效且可能影响未登录接口;后者由业务代码按需调用,开发人员显式决定哪些查询需要数据范围过滤。

与 SetOrgEntityFilter 的区别:

维度 SetOrgEntityFilter(全局) DataScopeService(按需)
生效方式 moreFilter 回调 → 全局 AOP 注入 Service 注入后链式调用 BuildFilterAsync<T>()
过滤粒度 所有 EntityBase / EntityBaseData 子类 由开发人员在每个查询上显式调用
未登录请求 可能因类型不匹配报错 返回 u => true,不受影响
控制力 无差别全局生效 逐查询精确控制

使用方式:

  1. DI 自动注册(无需手动 AddScoped,已在 AddOmiSystemSetup 中完成)
  2. 在 Service 构造函数中注入 DataScopeService
  3. 在查询链中加一行 .Where(await _dataScopeService.BuildFilterAsync<T>())
// <summary>示例:注入 DataScopeService 并按需添加数据范围过滤</summary>
public partial class MyService : IApplicationService
{
    private readonly SugarRepository<MyEntity> _repo;
    private readonly DataScopeService _dataScopeService;

    public MyService(SugarRepository<MyEntity> repo, DataScopeService dataScopeService)
    { _repo = repo; _dataScopeService = dataScopeService; }

    public async Task<PageResult<MyEntityDto>> GetPagesAsync(MyEntityQuery parm)
    {
        return await _repo.AsQueryable()
            .Where(await _dataScopeService.BuildFilterAsync<MyEntity>())  // ← 数据范围过滤
            .Where(predicate.ToExpression())
            .ToPageAsync<MyEntity, MyEntityDto>(parm);
    }
}

BuildFilterAsync<T>() 返回 Expression<Func<T, bool>>(永远非 null):

| 场景 | 返回值 | |------|--------| | 超管 / 未登录 | u => true(无过滤) | | ALL 数据范围 | u => true(无过滤) | | SELF(仅本人) | u => u.Creator == userId | | 机构 / 自定义 | u => u.Creator.HasValue && userIds.Contains(u.Creator.Value) | #### 自定义业务过滤器(IEntityFilter)

`SetCustomEntityFilter` 是一个扩展点,允许下游业务模块为**特定实体**添加自定义的全局过滤条件,无需修改框架源码。

**和 `SetOrgEntityFilter` 的区别**:

| 方法 | 过滤维度 | 适用范围 | 适用场景 |
|------|----------|----------|----------|
| `SetOrgEntityFilter` | 数据权限范围 | 所有 `EntityBase` / `EntityBaseData` 子类 | 按用户/机构隔离数据 |
| `SetCustomEntityFilter` | 自定义业务规则 | 指定实体类型 | 按业务状态、类型等维度过滤 |

**实现步骤**:

1. 创建一个类,实现 `IEntityFilter` 接口
2. 在 `AddEntityFilter()` 方法中返回自定义的 `TableFilterItem` 过滤表达式
3. 在 `moreFilter` 回调中注册 `SetCustomEntityFilter`

**示例**:

```csharp
// <summary>1. 实现 IEntityFilter 接口 — 例如:只查已发布的文章</summary>
public class ArticleFilter : IEntityFilter
{
    public IEnumerable<TableFilterItem<object>> AddEntityFilter()
    {
        Expression<Func<Article, bool>> dynamicExpression = u => u.Status == "published";
        var tableFilterItem = new TableFilterItem<object>(typeof(Article), dynamicExpression);

        return new[] { tableFilterItem };
    }
}

// <summary>2. 在 Program.cs 中注册(moreFilter 回调中)</summary>
// <remarks>SetCustomEntityFilter 会自动扫描并加载所有 IEntityFilter 实现</remarks>
builder.Services.AddSqlSugarSetup(moreFilter: (provider) =>
{
    SqlSugarFilter.SetOrgEntityFilter(builder.Services, provider);       // 数据范围
    SqlSugarFilter.SetCustomEntityFilter(builder.Services, provider);    // 自定义业务过滤
});

// <summary>3. 查询时自动生效,无需手动拼接 WHERE</summary>
// 以下查询会自动带上 WHERE Status = 'published'
public async Task<List<Article>> GetPublishedArticlesAsync(SugarRepository<Article> repo)
{
    return await repo.GetListAsync();
}
```

**多个过滤条件**:一个类可以返回多个 `TableFilterItem`,一个项目可以有多个 `IEntityFilter` 实现类,它们都会被加载和应用。

**缓存**:过滤表达式会缓存到 Redis,键为 `db:{configId}:custom:{userId}`,避免每次请求都反射扫描。

---

## 配置选项详解

JWT 配置 (Jwt)

配置项 类型 默认值 说明
ValidateIssuerSigningKey bool true 是否验证签名密钥
IssuerSigningKey string — HMAC-SHA256 签名密钥,建议长度 >= 32
ValidateIssuer bool true 是否验证签发方 (iss)
ValidIssuer string — 合法的签发方标识
ValidateAudience bool true 是否验证接收方 (aud)
ValidAudience string — 合法的接收方标识
ValidateLifetime bool true 是否验证 Token 过期时间
ExpiredTime long 1440 Token 有效期(分钟),默认 24 小时
ClockSkew long 5 时钟偏移容错(秒),用于服务端时间不一致场景

数据库连接配置 (ConnectionConfigs)

配置项 类型 说明
DbType string 数据库类型:MySql、SqlServer、Sqlite、Oracle、PostgreSQL 等
ConnectionString string 数据库连接字符串
ConfigId string 多库标识(不填即默认库)
EnableUnderLine bool 启用驼峰转下划线(表/列名)
IsDeleteFilter bool 软删除过滤(实体需继承 IDeletedEntity)
IsTenantIdFilter bool 多租户过滤(实体需继承 ITenantIdFilter)
CommandTimeOut int SQL 命令超时(秒),默认 30

数据库初始化配置 (DbInitConfig)

配置项 类型 默认值 说明
InitTable bool true 启动时自动检测并创建不存在的数据库表(CodeFirst)
InitSeedData bool true 初始化种子数据(管理员/基础角色/菜单等)

密码加密配置 (PasswordConfig)

配置项 类型 默认值 说明
DefaultPassword string 123qwe 种子数据用户及重置密码时的默认密码
EncryptTimes int 3 密码加密迭代次数

Swagger 版本配置 (Swagger.ApiVersions)

配置项 类型 说明
Title string Swagger UI 中显示的 API 分组标题
Version string API 版本标识,GroupName 固定为 system

API 接口速览

默认路由前缀: /openapi/api | Swagger GroupName: system

SysUserService — 用户管理

HTTP 路由 方法 说明
GET /SysUser/GetUserById GetUserById 根据用户 Id 获取用户
GET /SysUser/GetDataAsync GetDataAsync 根据主键查询
GET /SysUser/GetAllAsync GetAllAsync 获取全部用户简要列表(仅 ID、账号、姓名)
GET /SysUser/GetPagesAsync GetPagesAsync 获取用户分页列表
POST /SysUser/AddUser AddUser 增加用户
GET /SysUser/UserList UserList 获取用户列表
PUT /SysUser/UpdateUser UpdateUser 更新用户
DELETE /SysUser/DeleteUser DeleteUser 删除用户
GET /SysUser/GetBaseInfo GetBaseInfo 查看用户基本信息
PUT /SysUser/UpdateBaseInfo UpdateBaseInfo 更新用户基本信息
PUT /SysUser/SetStatus SetStatus 设置用户状态 (启用/禁用)
POST /SysUser/GrantRole GrantRole 授权用户角色
PUT /SysUser/ChangePwd ChangePwd 修改用户密码
PUT /SysUser/ResetPwd ResetPwd 重置用户密码
PUT /SysUser/UnlockLogin UnlockLogin 解除登录锁定
GET /SysUser/GetOwnRoleList GetOwnRoleList 获取用户拥有角色集合
GET /SysUser/GetOwnExtOrgList GetOwnExtOrgList 获取用户扩展机构集合
GET /SysUser/GetRoleUserListByRoleId GetRoleUserListByRoleId 获取角色用户相关信息
GET /SysUser/GetUserDataScopeIdList GetUserDataScopeIdList 获取用户数据范围(机构Id集合)
GET /SysUser/GetUsersDataScope GetUsersDataScope 批量获取用户数据权限范围(机构维度)
GET /SysUser/GetUserDataScopeUserIdList GetUserDataScopeUserIdList 获取用户数据范围(用户Id维度)
GET /SysUser/GetUsersDataScopeUserIdList GetUsersDataScopeUserIdList 批量获取用户数据权限范围(用户Id维度)

SysRoleService — 角色管理

HTTP 路由 方法 说明
GET /SysRole/GetPagesAsync GetPagesAsync 获取角色分页列表
GET /SysRole/GetDataAsync GetDataAsync 根据主键查询
GET /SysRole/GetList GetList 获取角色列表
POST /SysRole/AddRole AddRole 增加角色
PUT /SysRole/UpdateRole UpdateRole 更新角色
DELETE /SysRole/DeleteRole DeleteRole 删除角色
POST /SysRole/GrantMenu GrantMenu 授权角色菜单
POST /SysRole/GrantDataScope GrantDataScope 授权角色数据范围
GET /SysRole/GetOwnMenuList GetOwnMenuList 根据角色 Id 获取菜单 Id 集合
GET /SysRole/GetOwnOrgList GetOwnOrgList 根据角色 Id 获取机构 Id 集合
PUT /SysRole/SetStatus SetStatus 设置角色状态 (启用/禁用)

SysMenuService — 菜单管理

HTTP 路由 方法 说明
GET /SysMenu/GetLoginMenuTree GetLoginMenuTree 获取登录菜单树
GET /SysMenu/GetDataAsync GetDataAsync 根据主键查询
GET /SysMenu/GetList GetList 获取菜单列表
POST /SysMenu/AddMenu AddMenu 增加菜单
POST /SysMenu/AddMenus AddMenus 增加菜单和权限(一键生成 CRUD)
PUT /SysMenu/UpdateMenu UpdateMenu 更新菜单
DELETE /SysMenu/DeleteMenu DeleteMenu 删除菜单
GET /SysMenu/GetOwnBtnPermList GetOwnBtnPermList 获取按钮权限集合

SysOrgService — 机构管理

HTTP 路由 方法 说明
GET /SysOrg/GetList GetList 获取机构列表
GET /SysOrg/GetDataAsync GetDataAsync 根据主键查询
POST /SysOrg/AddOrg AddOrg 增加机构
PUT /SysOrg/UpdateOrg UpdateOrg 更新机构
DELETE /SysOrg/DeleteOrg DeleteOrg 删除机构

SysPosService — 职位管理

HTTP 路由 方法 说明
GET /SysPos/GetList GetList 获取职位列表
GET /SysPos/GetDataAsync GetDataAsync 根据主键查询
POST /SysPos/AddPos AddPos 增加职位
PUT /SysPos/UpdatePos UpdatePos 更新职位
DELETE /SysPos/DeletePos DeletePos 删除职位

SysCacheService — 缓存管理

HTTP 路由 方法 说明
DELETE /SysCache/Remove Remove 删除缓存
DELETE /SysCache/Clear Clear 清空所有缓存
DELETE /SysCache/RemoveByPrefixKey RemoveByPrefixKey 根据键名前缀删除缓存
GET /SysCache/GetKeysByPrefixKey GetKeysByPrefixKey 根据键名前缀获取键名集合
GET /SysCache/GetValue GetValue 获取缓存值

错误处理与日志

错误码表

错误码 说明 触发场景
ErrorEnum.D4000 请求参数验证失败 入参不合法(必填项为空、格式错误等)
i18n.account_exists 账号已存在 创建用户时账号重复
i18n.account_not_exists 账号不存在 登录/查询不存在的账号
i18n.prohibit_delete_super_admin 禁止删除超级管理员 尝试删除 SupAdmin 角色用户
i18n.illegal_operation_self 非法操作自身 用户对自己执行禁止操作
i18n.prohibit_modify_self_status 禁止修改自身状态 用户尝试启用/禁用自己
i18n.prohibit_modify_super_admin_status 禁止修改超级管理员状态 操作用户尝试修改管理员状态
i18n.dict_status_error 字典状态异常 依赖字典项已禁用或不存在
i18n.new_password_same_as_old 新密码与旧密码相同 修改密码时新旧密码一致
i18n.password_error 密码错误 修改密码时旧密码验证失败
i18n.data_exists 数据已存在 唯一约束冲突(名称/编码重复)
i18n.record_not_exists 记录不存在 更新/删除不存在的记录
i18n.route_name_duplicate 路由名称重复 创建菜单时路由名冲突
i18n.parent_node_cannot_be_button 父节点不能是按钮 将菜单/目录挂载到按钮类型节点下
i18n.permission_id_format_empty 权限标识不能为空 按钮类型菜单未填写权限标识
i18n.permission_id_format_error 权限标识格式错误 按钮权限标识不符合规范
i18n.prohibit_same_node_as_parent 禁止选择自己作为父节点 移动节点到自身或子节点下
i18n.prohibit_add_root_organization 禁止添加根机构 创建一级机构(根据业务策略)
i18n.organization_exists 机构已存在 创建机构时名称/编码重复
i18n.no_permission_to_operate_organization 无权限操作机构 非管理员操作受保护机构
i18n.current_org_id_cannot_match_parent 当前机构 Id 不能与父级相同 更新机构时设置了循环引用
i18n.organization_has_users 机构下存在用户 删除有用户的机构
i18n.affiliated_org_has_users 附属机构下存在用户 删除机构时其扩展关联有用户
i18n.subordinate_org_has_users 下级机构存在用户 删除机构时其子机构有关联用户
i18n.position_exists 职位已存在 创建职位时编码重复
i18n.position_not_exists 职位不存在 更新/删除不存在的职位
i18n.no_permission_to_modify_position 无权限修改职位 非管理员操作受保护职位
i18n.position_has_users 职位下存在用户 删除有用户关联的职位
i18n.prohibit_delete_admin 禁止删除管理员 尝试删除管理员角色
i18n.role_has_accounts 角色下存在账号 删除有用户关联的角色
i18n.no_permission 无权限 用户缺少操作所需权限
i18n.org_select 请选择机构 创建用户/配置时未选择机构
i18n.position_select 请选择职位 创建用户时未选择职位

异常处理示例

// <summary>异步方式 — 带完整错误码识别与日志记录的异常处理</summary>
public async Task<object> CreateUserWithErrorHandlingAsync(SysUserService userService, ILogger logger)
{
    try
    {
        var input = new AddUserInput
        {
            Account = "testuser",
            Password = "Test@123",
            NickName = "测试用户",
            OrgId = 1,
            PosId = 1
        };
        long userId = await userService.AddUser(input);
        logger.LogInformation("用户创建成功: {UserId}, {Account}", userId, input.Account);
        return new { Success = true, UserId = userId };
    }
    catch (Exception ex) when (ex.Message.Contains("i18n.account_exists"))
    {
        // <summary>账号已存在的业务异常</summary>
        logger.LogWarning("创建用户失败 — 账号已存在: {Message}", ex.Message);
        return new { Success = false, Error = "ACCOUNT_EXISTS", Message = "账号已存在,请更换账号名" };
    }
    catch (Exception ex) when (ex.Message.Contains("i18n.org_select"))
    {
        // <summary>未选择机构的业务异常</summary>
        logger.LogWarning("创建用户失败 — 未选择机构: {Message}", ex.Message);
        return new { Success = false, Error = "ORG_REQUIRED", Message = "请选择所属机构" };
    }
    catch (Exception ex)
    {
        // <summary>未预期的系统异常</summary>
        logger.LogError(ex, "创建用户时发生系统异常");
        return new { Success = false, Error = "SYSTEM_ERROR", Message = "系统异常,请稍后重试" };
    }
}

// <summary>同步方式 — 带错误处理的异常捕获(⚠️ 仅在非 Web 场景使用)</summary>
public object CreateUserWithErrorHandlingSync(SysUserService userService)
{
    try
    {
        var input = new AddUserInput
        {
            Account = "testuser",
            Password = "Test@123",
            NickName = "测试用户",
            OrgId = 1,
            PosId = 1
        };
        long userId = userService.AddUser(input).GetAwaiter().GetResult();
        return new { Success = true, UserId = userId };
    }
    ⚠️ // 同步等待会丢失异步上下文中的 ICurrentUser 等 Scoped 依赖信息
    catch (Exception ex)
    {
        return new { Success = false, Error = ex.Message };
    }
}

版本迁移指南

从早期版本迁移至 8.0.x / 10.0.x

重大变更:包结构从单 TFM 变更为双 TFM 支持(net8.0 / net10.0)。

  1. 升级 NuGet 引用

    
    <PackageReference Include="RuoVea.OmiApi.SystemApp" Version="7.x.x" />
    
    
    <PackageReference Include="RuoVea.OmiApi.SystemApp" Version="8.0.2.21" />    
    <PackageReference Include="RuoVea.OmiApi.SystemApp" Version="10.0.0.4" />   
    
  2. AddSystemInitSetup 参数变更

    // <summary>旧版本 — 无参数</summary>
    builder.Services.AddSystemInitSetup();
    
    // <summary>新版本 — 新增 isWeb 参数控制种子菜单类型</summary>
    // isWeb = true  → 使用 Web 菜单种子数据(适合前后端分离项目)
    // isWeb = false → 使用 API 菜单种子数据(默认,适合纯 API 项目)
    builder.Services.AddSystemInitSetup(isWeb: false);
    
  3. AddSqlSugarSetup 数据权限过滤器

    // <summary>新版本中必须显式注册数据权限过滤器</summary>
    // 否则多租户和数据范围过滤不会生效
    builder.Services.AddSqlSugarSetup(moreFilter: (provider) =>
    {
        SqlSugarFilter.SetCustomEntityFilter(builder.Services, provider);
    });
    
  4. Swagger 版本配置必须显式声明

    // <summary>appsettings.json 中必须添加 Swagger 节点</summary>
    {
      "Swagger": {
        "ApiVersions": [
          { "Title": "系统应用", "Version": "system" }
        ]
      }
    }
    

常见问题

Q: 启动后 Swagger 中看不到 system 分组?

原因:未配置 Swagger.ApiVersions 或 AddSwaggerSetup() 未调用。

// <summary>appsettings.json 中添加 Swagger 版本配置</summary>
{
  "Swagger": {
    "ApiVersions": [
      { "Title": "系统应用", "Version": "system" }
    ]
  }
}

Q: 数据库表未自动创建?

原因:DbInitConfig.InitTable 未设为 true,或 AddSystemInitSetup() 未调用。

// <summary>确保 DbInitConfig 配置正确</summary>
{
  "DbInitConfig": {
    "InitTable": true,       // 自动建表
    "InitSeedData": true     // 初始化种子数据
  }
}

⚠️ 注意:AddOmiSystemSetup() 必须在 AddSystemInitSetup() 之前调用,否则 DbInitConfig 无法正确传递。

Q: 如何切换数据库类型?

修改 ConnectionConfigs[0].DbType 和对应的 ConnectionString:

// <summary>MySQL 示例</summary>
{
  "ConnectionConfigs": [
    {
      "DbType": "MySql",
      "ConnectionString": "Server=localhost;Database=ruovea;Uid=root;Pwd=123456;"
    }
  ]
}

// <summary>PostgreSQL 示例</summary>
{
  "ConnectionConfigs": [
    {
      "DbType": "PostgreSQL",
      "ConnectionString": "Host=localhost;Database=ruovea;Username=postgres;Password=123456"
    }
  ]
}

Q: 超级管理员密码是什么?如何修改?

种子数据中超级管理员账号为 superAdmin,默认密码由 PasswordConfig.DefaultPassword 控制(默认为 123qwe)。修改方式:

// <summary>在 appsettings.json 中修改默认密码</summary>
{
  "PasswordConfig": {
    "DefaultPassword": "YourSecurePassword@2024",
    "EncryptTimes": 5
  }
}

修改后需重新初始化种子数据(删除已有数据库文件或执行清表操作后重启)。

Q: 数据权限过滤不生效?

❗ 请检查以下三项:

// <summary>1. 确保 AddSqlSugarSetup 中注册了 moreFilter</summary>
builder.Services.AddSqlSugarSetup(moreFilter: (provider) =>
{
    SqlSugarFilter.SetCustomEntityFilter(builder.Services, provider); // ← 必须
});

// <summary>2. 实体须继承 EntityBase(内含 CreateUserId/OrgId 等过滤字段)</summary>
// <summary>3. 当前登录用户的角色 DataScope 配置正确(非 All)</summary>

Q: 缓存清除后权限未立即生效?

角色/菜单授权变更后,组件内部会自动调用 SysCacheService.Remove 清除相关缓存。如果使用外部 Redis 管理工具手动清除,请删除 user:permission:* 和 user:datascope:* 前缀的键。

Q: AddOmiSystemSetup 的三个重载如何选择?

重载 适用场景
AddOmiSystemSetup() 使用 appsettings.json 中配置(默认推荐)
AddOmiSystemSetup(IConfiguration config) 动态/内存配置、多环境配置
AddOmiSystemSetup(Action<DbInitConfig> config) 代码内构建配置、复杂初始化逻辑

Q: ⚠️ 同步调用 .GetAwaiter().GetResult() 有什么风险?

在 ASP.NET Core 的同步上下文中调用异步方法并使用 .GetAwaiter().GetResult() 可能导致死锁(deadlock),因为 SynchronizationContext 会尝试将后续代码封送回原线程,而该线程正在被阻塞。始终推荐在 Web 应用中使用 async/await。


Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on RuoVea.OmiApi.SystemApp:

Package Downloads
RuoVea.OmiSystem

角色用户菜单机构职位管理

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.1.16 106 8/20/2026
10.0.1.15 100 8/18/2026
10.0.1.14 105 8/17/2026
10.0.1.13 95 8/17/2026
10.0.1.12 112 8/16/2026
10.0.1.11 97 8/16/2026
10.0.1.10 108 8/16/2026
10.0.1.9 106 8/16/2026
10.0.1.8 104 8/16/2026
10.0.1 100 8/16/2026
8.0.3.18 94 8/20/2026
8.0.3.17 102 8/18/2026
8.0.3.16 94 8/17/2026
8.0.3.15 99 8/17/2026
8.0.3.12 100 8/16/2026
8.0.3.11 92 8/16/2026
8.0.3.10 96 8/16/2026
8.0.3.9 99 8/16/2026
8.0.3.8 94 8/16/2026
8.0.3 99 8/16/2026
Loading failed