RuoVea.OmiApi.Reports 10.0.2.7

dotnet add package RuoVea.OmiApi.Reports --version 10.0.2.7
                    
NuGet\Install-Package RuoVea.OmiApi.Reports -Version 10.0.2.7
                    
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.Reports" Version="10.0.2.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RuoVea.OmiApi.Reports" Version="10.0.2.7" />
                    
Directory.Packages.props
<PackageReference Include="RuoVea.OmiApi.Reports" />
                    
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.Reports --version 10.0.2.7
                    
#r "nuget: RuoVea.OmiApi.Reports, 10.0.2.7"
                    
#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.Reports@10.0.2.7
                    
#: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.Reports&version=10.0.2.7
                    
Install as a Cake Addin
#tool nuget:?package=RuoVea.OmiApi.Reports&version=10.0.2.7
                    
Install as a Cake Tool

RuoVea.OmiApi.Reports

报表管理模块 -- 支持表格查询、图表组件、树形表格、主子表。基于 .NET 构建,支持 MySql、SqlServer、PostgreSQL、SQLite、Oracle 等多种数据库。通过动态 API 提供数据源管理、报表定义、字段配置、图表组件配置和仪表盘管理的完整后端服务。


目录


概览

功能特性

功能模块 描述 关键能力
数据源管理 多数据库连接配置 MySQL / SqlServer / PostgreSQL / SQLite / Oracle
报表定义 SQL 模板 + 字段元数据 SQL 解析、变量替换 #{varName}、分页排序过滤
字段配置 核心 + UI 扩展拆分 显示/搜索/选项三张 1:1 扩展表,按需 JOIN
图表组件 7 种图表类型 bar / line / pie / gauge / statcard / radar / funnel
数据缓存 图表查询缓存 可配置 TTL,基于 ExCache,支持参数维度缓存键
树形表格 ElementPlus tree table 行键/父键/子键/懒加载/默认展开
主子表 master-detail expandable rows 主表关联字段/子表关联字段/展开模式
仪表盘 网格布局容器 Dashboard + DashboardItem,ColSpan/RowSpan/PosX/PosY
选项数据源 4 种模式 static / sql / api / dict
图表数据源 3 种模式 sql (后端执行) / api (前端请求) / static (前端渲染)
国际化 i18n 多语言 zh-CN / en-US / ja-JP / vi-VN / zh-HK / zh-TW / fr-FR

系统架构

                                                    NuGet Package
                                             RuoVea.OmiApi.Reports
                                          ┌───────────────────────────┐
                                          │   ServiceCollection       │
                                          │   .AddOmiReportSetup()    │
                                          │   .AddReportInitSetup()   │
                                          └───────────┬───────────────┘
                                                      │ 注册 5 个 Service
              ┌───────────────────────────────────────┼───────────────────────────────────────┐
              ▼                       ▼               ▼               ▼                       ▼
  ┌──────────────────┐   ┌──────────────────┐  ┌──────────────┐  ┌──────────────┐   ┌──────────────┐
  │ RptDataSource    │   │ RptDefinition    │  │ RptChart     │  │ RptDashboard │   │ RptColumn    │
  │ Service          │   │ Service          │  │ ConfigService│  │ Service      │   │ Service      │
  └────────┬─────────┘   └────────┬─────────┘  └──────┬───────┘  └──────┬───────┘   └──────────────┘
           │                      │                    │                 │
           ▼                      ▼                    ▼                 ▼
  ┌─────────────────────────────────────────────────────────────────────────────────┐
  │                           SqlSugar ORM (SugarRepository)                        │
  ├─────────────────┬───────────────────┬──────────────────┬────────────────────────┤
  │  RptDataSource  │  RptDefinition    │  RptChartConfig   │  RptDashboard          │
  │                 │  RptDefinitionUi  │  RptChartConfigUi │  RptDashboardItem      │
  │                 │  RptColumn        │                   │                        │
  │                 │  RptColumnDisplay │                   │                        │
  │                 │  RptColumnSearch  │                   │                        │
  │                 │  RptColumnOption  │                   │                        │
  └─────────────────┴───────────────────┴──────────────────┴────────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
               ┌────────┐    ┌──────────┐    ┌──────────┐
               │ MySQL  │    │ SqlServer│    │  Oracle  │    ...
               └────────┘    └──────────┘    └──────────┘

表关系全景 (11 张表):

RptDataSource          -- 数据源 (10 字段)
      │
      ▼ DataSourceId
RptDefinition          -- 报表定义 (8 字段)
  ├── 1:1 RptDefinitionUi   -- 表格/树/主子表 UI (28 字段)
  └── 1:N RptColumn         -- 字段映射核心 (10 字段)
        ├── 1:1 RptColumnDisplay  -- 显示配置 (25 字段)
        ├── 1:1 RptColumnSearch   -- 搜索配置 (20 字段)
        └── 1:1 RptColumnOption   -- 选项配置 (9 字段, 仅 select)

RptChartConfig         -- 图表组件 (34 字段, 完全独立)
  └── 1:1 RptChartConfigUi  -- 图表 UI (19 字段)

RptDashboard           -- 仪表盘 (8 字段)
  └── 1:N RptDashboardItem -- 仪表盘项 (9 字段, ChartCode 关联图表)

核心设计要点:

  • 核心/UI 拆分: 后端使用的字段留在核心表(RptColumn 仅 10 字段),纯 UI 透传字段移入 1:1 扩展表。API 通过 DTO 合并,前后端契约不变。
  • 按需加载: 渲染列时 JOIN RptColumnDisplay,构建搜索时 JOIN RptColumnSearch,select 控件时 JOIN RptColumnOption。
  • 图表独立: RptChartConfig 自含完整数据源(sql/api/static),完全独立于 RptDefinition。
  • 反范式优化: RptColumn.DefinitionCode 刻意冗余 RptDefinition.Code,避免跨表 JOIN。

安装

NuGet 安装

TFM 命令
net8.0 Install-Package RuoVea.OmiApi.Reports -Version 8.0.2.5
net10.0 Install-Package RuoVea.OmiApi.Reports -Version 10.0.2.5

或使用 .NET CLI:

# .NET 8.0
dotnet add package RuoVea.OmiApi.Reports --version 8.0.2.5

# .NET 10.0
dotnet add package RuoVea.OmiApi.Reports --version 10.0.2.5

依赖说明

版本 (net8.0) 版本 (net10.0) 用途
Handlebars.Net 2.1.4 2.1.4 模板引擎(SQL 变量替换)
RuoVea.DynamicWebApi 8.0.* 10.0.* 动态 API 映射(命名约定自动注册 HTTP 方法)
RuoVea.ExSugar 8.0.* 10.0.* 基础框架(SqlSugar 仓储/实体基类/DTO/工具类)

30 秒快速开始

1. 安装包

dotnet add package RuoVea.OmiApi.Reports

2. 配置 appsettings.json

{
  "ConnectionConfigs": [
    {
      "DbType": "Sqlite",
      "ConnectionString": "DataSource=./ruovea.db"
    }
  ],
  "DbInitConfig": {
    "InitTable": true
  },
  "Swagger": {
    "ApiVersions": [
      { "Title": "报表管理", "Version": "Reports" }
    ]
  }
}

3. 注册服务

// Program.cs
var builder = WebApplication.CreateBuilder(args);

/// <summary>
/// 注册 SqlSugar ORM(依赖 RuoVea.ExSugar)
/// </summary>
builder.Services.AddSqlSugarSetup();

/// <summary>
/// 注册报表管理模块全部服务(Scoped 生命周期)
/// 自动从 IConfiguration["DbInitConfig"] 读取数据库初始化配置
/// </summary>
builder.Services.AddOmiReportSetup();

/// <summary>
/// 初始化数据库表结构(CodeFirst 自动建 11 张表)
/// 仅在 DbInitConfig.InitTable = true 时生效
/// </summary>
builder.Services.AddReportInitSetup();

var app = builder.Build();

app.UseRouting();
app.MapControllers();
app.UseSwagger();

app.Run();

4. 启动并访问 Swagger

启动应用后访问 https://localhost:{port}/swagger,在下拉框中选择 "报表管理" 分组,即可看到所有 API 端点。


核心场景

场景一:动态数据源管理与连接测试

业务流程:

用户                      RptDataSourceService                 数据库
 │                              │                                 │
 │  POST /AddAsync              │                                 │
 │  (Name + ConnectionString)   │                                 │
 ├─────────────────────────────>│                                 │
 │                              ├── IsAnyAsync (名称去重)          │
 │                              ├── InsertAsync (写入)             │
 │                              │                                 │
 │  POST /GetTestDbConnection   │                                 │
 │  (connStr + dbType)          │                                 │
 ├─────────────────────────────>│                                 │
 │                              ├── DbProvider.GetSugarDbContext   │
 │                              ├─────────────────────────────────>│
 │                              │<──────── db.Open() ──────────────│
 │                              │                                 │
 │<──── true / ParamiterException ───                             │
 │                              │                                 │
 │  GET /GetConnectionOptions   │                                 │
 ├─────────────────────────────>│                                 │
 │                              ├── Where(IsEnabled=Y).Select()   │
 │<──── [{value, label}, ...] ───                                 │

代码示例 -- 异步模式:

// 注入服务
private readonly RptDataSourceService _dataSourceService;

/// <summary>
/// 新增数据源并测试连接
/// </summary>
/// <param name="name">数据源名称</param>
/// <param name="connStr">连接字符串</param>
/// <param name="dbType">数据库类型枚举值</param>
/// <returns>操作结果描述</returns>
public async Task<string> AddAndTestDataSourceAsync(string name, string connStr, int dbType)
{
    // 1. 新增数据源
    var entity = new RptDataSource
    {
        Name = name,
        ConnectionString = connStr,
        DataType = dbType,
        IsEnabled = YesOrNot.Y
    };
    var addResult = await _dataSourceService.AddAsync(entity);

    if (addResult.Code != CodeStatus.OK)
        return $"数据源添加失败: {addResult.Message}";

    // 2. 测试连接
    try
    {
        var ok = _dataSourceService.GetTestDbConnection(connStr, dbType);
        return ok ? "数据源添加成功,连接测试通过" : "连接测试失败";
    }
    catch (ParamiterException ex)
    {
        return $"连接测试异常: {ex.Message}";
    }
}

代码示例 -- 同步模式:

/// <summary>
/// 同步获取已启用的数据源选项列表
/// </summary>
/// <returns>数据源选项列表(value/label)</returns>
public string GetEnabledDataSourcesSync()
{
    var result = _dataSourceService.GetConnectionOptions();
    return JsonConvert.SerializeObject(result);
}

场景二:报表定义 + SQL 解析 + 数据查询

业务流程:

用户                  RptDefinitionService                    外部数据库
 │                         │                                      │
 │  POST /PostParseSQLText  │                                      │
 │  (DataSourceId + SQL)    │                                      │
 ├────────────────────────>│                                      │
 │                         ├── 获取 RptDataSource (连接字符串)       │
 │                         ├── DbProvider.GetSugarDbContext        │
 │                         ├─────────────────────────────────────>│
 │                         │<──────── 执行 EXPLAIN / 元数据查询 ────│
 │                         ├── RptColumnService.GetReportMetaData  │
 │<── [{SqlField,DataType}, ──                                     │
 │     ...]                                                       │
 │                         │                                      │
 │  POST /AddAsync         │                                      │
 │  (DefinitionDto)        │                                      │
 ├────────────────────────>│                                      │
 │                         ├── ValidateDefinition                 │
 │                         ├── 事务写入:                            │
 │                         │    RptDefinition + RptDefinitionUi    │
 │                         │    + RptColumn*4                     │
 │                         │                                      │
 │  POST /GetListByCodeAsync│                                      │
 │  (code + parm)           │                                      │
 ├────────────────────────>│                                      │
 │                         ├── 获取报表定义+字段                      │
 │                         ├── 替换 #{varName} 变量                  │
 │                         ├── 构建 WHERE/ORDER BY                 │
 │                         ├── 连接外部数据库执行查询                  │
 │                         ├─────────────────────────────────────>│
 │                         │<──────── 查询结果 ─────────────────────│
 │                         ├── DBNull/DateTime 格式化               │
 │<── {PageNo, TotalRows,  ──                                      │
 │     Rows: [...]}                                               │

代码示例 -- 异步模式:

// 注入服务
private readonly RptDefinitionService _definitionService;
private readonly RptDataSourceService _dataSourceService;

/// <summary>
/// 创建报表并执行首次数据查询
/// </summary>
/// <param name="name">报表名称</param>
/// <param name="code">报表编码</param>
/// <param name="dataSourceId">数据源 ID</param>
/// <param name="sql">查询 SQL(可含 #{varName} 变量)</param>
/// <returns>分页查询结果</returns>
public async Task<RestfulResult<PageResultDto>> CreateAndQueryAsync(
    string name, string code, long dataSourceId, string sql)
{
    // 1. 解析 SQL 元数据
    var parseDto = new RptDefinition
    {
        DataSourceId = dataSourceId,
        QuerySql = sql
    };
    var metaResult = _definitionService.PostParseSQLText(parseDto);

    // 2. 构建字段配置(使用解析结果将 SQL 列映射为 RptColumnDto)
    var columns = new List<RptColumnDto>();
    // ... 根据 metaResult.Data 构建 columns

    // 3. 创建报表定义
    var dto = new DefinitionDto
    {
        ReportDefinition = new RptDefinitionDto
        {
            Name = name,
            Code = code,
            DataSourceId = dataSourceId,
            QuerySql = sql,
            IsEnabled = YesOrNot.Y
        },
        ReportColumnField = columns
    };
    var addResult = await _definitionService.AddAsync(dto);
    if (addResult.Code != CodeStatus.OK)
        throw new Exception($"报表创建失败: {addResult.Message}");

    // 4. 执行查询
    var parm = new Dictionary<string, string>
    {
        ["pageNo"] = "1",
        ["pageSize"] = "20"
    };
    return await _definitionService.GetListByCodeAsync(code, parm);
}

代码示例 -- 同步模式:

/// <summary>
/// 同步获取报表详情(含字段和 UI 扩展)
/// </summary>
/// <param name="id">报表定义 ID</param>
/// <returns>报表详情 DTO</returns>
public RptDefinitionDto GetDefinitionDetailSync(long id)
{
    var task = _definitionService.GetDetailAsync(id);
    var result = task.GetAwaiter().GetResult();
    if (result.Code != CodeStatus.OK)
        throw new Exception($"获取报表详情失败: {result.Message}");
    return result.Data;
}

场景三:图表组件配置与数据缓存

业务流程:

用户                 RptChartConfigService          ExCache          外部数据库
 │                         │                          │                  │
 │  POST /AddAsync         │                          │                  │
 │  (Name/Code/ConfigType  │                          │                  │
 │   /DataSourceId/QuerySql│                          │                  │
 │   /CacheEnabled/TTL)    │                          │                  │
 ├────────────────────────>│                          │                  │
 │                         ├── 校验 Name/Code/Type     │                  │
 │                         ├── 事务写入核心 + UI 扩展    │                  │
 │                         │                          │                  │
 │  POST /GetChartDataAsync│                          │                  │
 │  (id + parm)            │                          │                  │
 ├────────────────────────>│                          │                  │
 │                         ├── CacheEnabled == 1 ?    │                  │
 │                         │                          │                  │
 │                         ├── BuildCacheKey(id,parm) │                  │
 │                         ├── cache.ReadAsync(key) ──>│                  │
 │                         │<─────── hit? ────────────│                  │
 │                         │                          │                  │
 │                         ├── 未命中:                  │                  │
 │                         │   ReplaceSqlVariables    │                  │
 │                         │   DbProvider.Ado.SqlQuery│                  │
 │                         ├─────────────────────────────────────────>│
 │                         │<──────── 查询结果 ────────────────────────│
 │                         │                          │                  │
 │                         ├── cache.WriteAsync() ───>│                  │
 │                         │   (TTL 秒级过期)          │                  │
 │                         │                          │                  │
 │<── Restful.Success(data)│                          │                  │

代码示例 -- 异步模式:

// 注入服务
private readonly RptChartConfigService _chartService;

/// <summary>
/// 创建图表并执行带缓存的数据查询
/// </summary>
/// <param name="code">图表编码</param>
/// <param name="parm">查询参数(用于替换 SQL 中的 #{varName})</param>
/// <returns>图表数据</returns>
public async Task<RestfulResult> GetChartDataWithCacheAsync(string code, Dictionary<string, string> parm)
{
    // 1. 根据编码获取图表配置(自动合并 UI 扩展)
    var config = _chartService.GetDataByCode(code);
    if (config == null)
        return Restful.Bad(CodeStatus.NotFound, "图表配置不存在");

    // 2. 执行图表查询(自动处理缓存逻辑)
    //    如 CacheEnabled=1,先从 ExCache 读取
    //    缓存命中直接返回,未命中则查询数据库后写入缓存
    return await _chartService.GetChartDataByCodeAsync(code, parm);
}

/// <summary>
/// 批量删除图表配置(排除系统内置配置)
/// </summary>
/// <param name="ids">待删除的配置 ID 列表</param>
/// <returns>操作结果</returns>
public async Task<RestfulResult> BatchDeleteChartsAsync(List<long> ids)
{
    // 批量删除自动跳过 IsSystem=1 的系统内置配置
    return await _chartService.BatchDeleteAsync(ids);
}

代码示例 -- 同步模式:

/// <summary>
/// 同步获取所有已启用的图表配置列表
/// </summary>
/// <returns>图表配置列表(含 UI 扩展数据)</returns>
public List<RptChartConfig> GetEnabledChartsSync()
{
    var task = _chartService.GetEnabledListAsync();
    var result = task.GetAwaiter().GetResult();
    return result.Code == CodeStatus.OK ? result.Data : new List<RptChartConfig>();
}

场景四:仪表盘网格布局管理

业务流程:

用户                RptDashboardService              数据库
 │                         │                           │
 │  POST /AddAsync         │                           │
 │  (Name/Code/Columns     │                           │
 │   /Items[{ChartCode,    │                           │
 │     ColSpan,RowSpan,    │                           │
 │     PosX,PosY,Sort}])   │                           │
 ├────────────────────────>│                           │
 │                         ├── 校验 Code 唯一性          │
 │                         ├── IdGenerator 生成ID       │
 │                         ├── 事务插入:                 │
 │                         │    RptDashboard            │
 │                         │    + RptDashboardItem[]    │
 │                         ├──────────────────────────>│
 │                         │                           │
 │  GET /GetDetailAsync    │                           │
 │  (id)                   │                           │
 ├────────────────────────>│                           │
 │                         ├── .Includes(x=>x.Items)   │
 │                         ├── Mapster → RptDashboardDto│
 │<── {Name,Code,Columns,  │                           │
 │     Items:[...]}        │                           │

代码示例 -- 异步模式:

// 注入服务
private readonly RptDashboardService _dashboardService;

/// <summary>
/// 创建 3 列网格仪表盘(含 4 个图表项)
/// </summary>
/// <returns>操作结果</returns>
public async Task<RestfulResult> CreateDashboardAsync()
{
    var dto = new RptDashboardDto
    {
        Name = "运营概览",
        Code = "ops_overview",
        Columns = 3,
        IsEnabled = 1,
        Sort = 1,
        Items = new List<RptDashboardItemDto>
        {
            new() { ChartCode = "chart_stat_total", Title = "总收入",
                     ColSpan = 1, RowSpan = 1, PosX = 0, PosY = 0, Sort = 1 },
            new() { ChartCode = "chart_bar_trend",  Title = "月度趋势",
                     ColSpan = 2, RowSpan = 1, PosX = 1, PosY = 0, Sort = 2 },
            new() { ChartCode = "chart_line_growth",Title = "增长率",
                     ColSpan = 1, RowSpan = 1, PosX = 0, PosY = 1, Sort = 3 },
            new() { ChartCode = "chart_pie_ratio",  Title = "品类占比",
                     ColSpan = 2, RowSpan = 1, PosX = 1, PosY = 1, Sort = 4 }
        }
    };
    return await _dashboardService.AddAsync(dto);
}

代码示例 -- 同步模式:

/// <summary>
/// 同步分页查询仪表盘列表
/// </summary>
/// <param name="pageNo">页码</param>
/// <param name="pageSize">每页条数</param>
/// <returns>分页结果</returns>
public PageResult<RptDashboard> GetDashboardPageSync(int pageNo, int pageSize)
{
    var task = _dashboardService.GetPagesAsync(new PageParam { PageNo = pageNo, PageSize = pageSize });
    var result = task.GetAwaiter().GetResult();
    return result.Code == CodeStatus.OK ? result.Data : new PageResult<RptDashboard>(pageNo, pageSize);
}

场景五:树形表格与主子表

业务流程:

用户                RptDefinitionService               数据库
 │                         │                             │
 │  创建树形表格:            │                             │
 │  POST /AddAsync         │                             │
 │  (IsTree=Y,             │                             │
 │   TreeRowKey="id",      │                             │
 │   TreeParentKey="pid")  │                             │
 ├────────────────────────>│                             │
 │                         ├── ValidateDefinition        │
 │                         │   检查 TreeRowKey/ParentKey  │
 │                         │   自动清理主子表字段          │
 │                         ├────────────────────────────>│
 │                         │                             │
 │  POST /GetTreeListByCode│                             │
 │  Async (code + parm)    │                             │
 ├────────────────────────>│                             │
 │                         ├── pageSize = ushort.MaxValue│
 │                         ├── 不分页返回全量数据          │
 │<── {TotalRows: N,       │                             │
 │     Rows: [全量树数据]}  │                             │
 │                         │                             │
 │  创建主子表:              │                             │
 │  POST /AddAsync         │                             │
 │  (主表: IsMaster=Y       │                             │
 │   LinkMasterField="id"  │                             │
 │   LinkDetailField="mid")│                             │
 ├────────────────────────>│                             │
 │                         │                             │
 │  创建子表:                │                             │
 │  POST /AddAsync         │                             │
 │  (MasterSqlId=主表ID     │                             │
 │   LinkDetailField="mid")│                             │
 ├────────────────────────>│                             │
 │                         │                             │
 │  GET /GetChildrenAsync  │                             │
 │  (主表ID)                │                             │
 ├────────────────────────>│                             │
 │                         ├── LeftJoin RptDefinitionUi  │
 │                         │   Where MasterSqlId == id   │
 │<── [子表 DTO 列表]       │                             │

代码示例 -- 异步模式:

/// <summary>
/// 查询树形表格数据(按编码,不分页返回全量)
/// </summary>
/// <param name="code">报表定义编码</param>
/// <returns>全量树形数据</returns>
public async Task<RestfulResult<PageResultDto>> GetTreeDataAsync(string code)
{
    var parm = new Dictionary<string, string>();
    // 树形查询自动设置 pageSize = ushort.MaxValue,不分页
    return await _definitionService.GetTreeListByCodeAsync(code, parm);
}

/// <summary>
/// 获取主子表的子表列表
/// </summary>
/// <param name="masterId">主表定义 ID</param>
/// <returns>子表定义列表(含字段和 UI 扩展)</returns>
public async Task<RestfulResult<List<RptDefinitionDto>>> GetChildDefinitionsAsync(long masterId)
{
    return await _definitionService.GetChildrenAsync(masterId);
}

代码示例 -- 同步模式:

/// <summary>
/// 同步获取树形表格全量数据
/// </summary>
/// <param name="code">报表定义编码</param>
/// <returns>全量树形数据</returns>
public PageResultDto GetTreeDataSync(string code)
{
    var task = _definitionService.GetTreeListByCodeAsync(code, new Dictionary<string, string>());
    var result = task.GetAwaiter().GetResult();
    if (result.Code != CodeStatus.OK)
        throw new Exception($"获取树形数据失败: {result.Message}");
    return result.Data;
}

配置选项详解

DbInitConfig -- 数据库初始化配置

配置项 类型 默认值 说明
InitTable bool false 启动时是否通过 SqlSugar CodeFirst 自动创建 11 张表

ConnectionConfigs -- SqlSugar 连接配置

配置项 类型 必填 说明
DbType string 数据库类型:Sqlite / MySql / SqlServer / PostgreSQL / Oracle
ConnectionString string 数据库连接字符串
ConfigId string 多库标识(多库场景)

Swagger 分组配置

{
  "Swagger": {
    "ApiVersions": [
      { "Title": "报表管理", "Version": "Reports" }
    ]
  }
}

所有报表 API 均使用 [ApiExplorerSettings(GroupName = "Report")] 分组,在 Swagger UI 中以独立分组展示。

DI 注册方法对比

重载 参数 说明
AddOmiReportSetup() 自动从 IConfiguration["DbInitConfig"] 读取配置
AddOmiReportSetup(IConfiguration) 自定义 IConfiguration 从指定配置实例读取
AddOmiReportSetup(Action<DbInitConfig>) 配置委托 通过代码直接配置
AddReportInitSetup() CodeFirst 建表,仅当 InitTable=true 时执行
// 方式一:自动读取 appsettings.json
builder.Services.AddOmiReportSetup();

// 方式二:指定配置
builder.Services.AddOmiReportSetup(config, ServiceLifetime.Singleton);

// 方式三:代码配置
builder.Services.AddOmiReportSetup(cfg =>
{
    cfg.InitTable = false; // 生产环境手动管理表结构
}, ServiceLifetime.Scoped);

API 接口速览

RptDataSourceService -- 数据源管理

HTTP 方法 描述
GET GetPagesAsync 分页查询数据源(按名称/类型过滤)
GET GetDataById 根据 ID 获取数据源
POST AddAsync 增加数据源
PUT UpdateAsync 更新数据源
DELETE DeleteAsync 删除数据源
GET GetDataType 获取数据库类型枚举列表(MySQL / SqlServer / Sqlite / Oracle / PostgreSQL)
POST GetTestDbConnection 测试数据库连接
GET GetConnectionOptions 查询已启用的数据源选项列表(value/label)

RptDefinitionService -- 报表定义管理

HTTP 方法 描述
GET GetPagesAsync 分页查询报表定义(按名称/数据源/数据库名/表类型过滤)
GET GetDataById 根据 ID 获取报表定义
GET GetDataAdnSubById 根据 ID 获取报表定义(含字段列表和 UI 扩展)
GET GetDataAdnSubByCode 根据编码获取报表定义(含字段列表和 UI 扩展)
POST PostParseSQLText 解析 SQL 文本,返回字段元数据(列名/类型)
GET GetDefinitionListAsync 获取报表定义列表(按启用状态过滤)
POST AddAsync 增加报表定义(含字段 + UI 扩展,事务写入主表 + 5 张扩展表)
PUT UpdateAsync 更新报表定义(含字段 + UI 扩展,先删后增策略)
DELETE DeleteAsync 删除报表定义(级联删除 UI 扩展和字段数据)
GET GetDetailAsync 根据 ID 获取报表详情(合并所有 UI 扩展,支持 safe 脱敏)
GET GetDetailByCodeAsync 根据编码获取报表详情(合并所有 UI 扩展,支持 safe 脱敏)
POST GetListAsync 执行报表查询,返回分页数据(按 ID)
POST GetListByCodeAsync 执行报表查询,返回分页数据(按编码)
GET GetChildrenAsync 获取主表的所有子表定义列表
POST GetTreeListByCodeAsync 树形查询(不分页返回全量数据,pageSize = ushort.MaxValue)

RptChartConfigService -- 图表组件管理

HTTP 方法 描述
GET GetDataById 根据 ID 获取图表配置(含 UI 扩展数据)
GET GetDataByCode 根据编码获取图表配置(含 UI 扩展数据)
GET GetPagesAsync 分页查询图表配置(按名称/编码/类型/启用状态过滤)
GET GetEnabledListAsync 获取所有启用的图表配置列表
POST AddAsync 增加图表配置(事务写入核心 + UI 扩展,校验类型/编码唯一性)
PUT UpdateAsync 更新图表配置(验证系统配置保护,先删后增 UI 扩展)
DELETE DeleteAsync 删除图表配置(系统内置配置拒绝删除)
POST BatchDeleteAsync 批量删除图表配置(跳过 IsSystem=1 的系统配置)
POST GetChartDataAsync 执行图表查询(按 ID,自动处理缓存/变量替换)
POST GetChartDataByCodeAsync 执行图表查询(按编码)

RptDashboardService -- 仪表盘管理

HTTP 方法 描述
GET GetPagesAsync 分页查询仪表盘
GET GetDetailAsync 获取仪表盘详情(含 Items)
GET GetDetailByCodeAsync 根据编码获取仪表盘详情
POST AddAsync 新增仪表盘(含 Items,IdGenerator 生成分布式 ID)
PUT UpdateAsync 更新仪表盘(含 Items,先删后增策略)
DELETE DeleteAsync 删除仪表盘(软删除主表,硬删除关联 Items)

错误处理与日志

错误码表

错误码 含义 常见触发场景
CodeStatus.OK 操作成功 --
CodeStatus.BadRequest 请求参数错误 数据重复、编码已存在、参数缺失、系统配置保护、类型无效
CodeStatus.NotFound 资源不存在 记录不存在、仪表盘不存在、报表定义不存在
ParamiterException SQL 执行/参数校验异常 SQL 执行失败、数据库引擎未指定、查询列不能为空

i18n 错误信息 (zh-CN):

中文文本
data_duplicate 数据重复
not_exist / record_not_exist 不存在 / 记录不存在
upload_data_required 未提供上传数据
no_input_value 没有传入值
QueryColumnCannotBeEmpty 查询列不能为空
DataSourceCannotBeEmpty 所属数据源不能为空
SqlCannotBeEmpty 待解析SQL语句不能为空
SpecifyDbEngine 请指定数据库引擎类型:MySql, SqlServer, Oracle 或 PostgreSQL
QueryException 查询异常
code_cannot_be_empty 编码不能为空
code_already_exists 编码已存在
config_name_required 配置名称不能为空
config_code_required 配置编码不能为空
config_type_required 配置类型不能为空
config_id_required 配置ID不能为空
config_not_exist 配置不存在
config_code_exists 配置编码已存在
config_name_exists 配置名称已存在
config_type_invalid 配置类型无效,有效类型:bar,line,pie,gauge,statcard,radar,funnel
please_select_config_to_delete 请选择要删除的配置
please_select_data_source 请先选择数据源
data_cannot_be_empty 数据不能为空
data_invalid 数据无效
id_invalid 无效ID
system_config_cannot_delete 以下系统内置配置不能删除
system_config_cannot_modify_code 系统内置配置不能修改编码
system_config_cannot_modify_type 系统内置配置不能修改配置类型
dashboard_not_exist 仪表盘不存在
definition_not_exist 报表定义不存在
tree_requires_row_key 树形表格必须填写行标识列(treeRowKey)
tree_requires_parent_key 树形表格必须填写父节点列(treeParentKey)
master_requires_link_master_field 主表必须填写主表关联字段(linkMasterField)
master_requires_link_detail_field 主表必须填写子表关联字段(linkDetailField)
NoDataToExport 没有数据可导出
unknown_error / server_error 出现未知错误 / 服务器内部发生错误

错误处理示例

异步模式:

/// <summary>
/// 安全执行报表查询,统一错误处理
/// </summary>
/// <param name="code">报表编码</param>
/// <param name="parm">查询参数</param>
/// <returns>标准化 RESTful 结果</returns>
public async Task<RestfulResult<PageResultDto>> SafeQueryAsync(string code, Dictionary<string, string> parm)
{
    try
    {
        return await _definitionService.GetListByCodeAsync(code, parm);
    }
    catch (ParamiterException ex)
    {
        // SQL 执行失败 / 参数校验失败
        return new RestfulResult<PageResultDto>
        {
            Code = CodeStatus.BadRequest,
            Message = $"查询失败: {ex.Message}"
        };
    }
    catch (Exception ex)
    {
        // 未预期异常
        return new RestfulResult<PageResultDto>
        {
            Code = CodeStatus.BadRequest,
            Message = $"服务器错误: {ex.Message}"
        };
    }
}

同步模式:

/// <summary>
/// 同步安全查询(含统一错误处理)
/// </summary>
/// <param name="code">报表编码</param>
/// <param name="parm">查询参数</param>
/// <returns>标准化 RESTful 结果</returns>
public RestfulResult<PageResultDto> SafeQuerySync(string code, Dictionary<string, string> parm)
{
    try
    {
        var task = _definitionService.GetListByCodeAsync(code, parm);
        return task.GetAwaiter().GetResult();
    }
    catch (ParamiterException ex)
    {
        return new RestfulResult<PageResultDto>
        {
            Code = CodeStatus.BadRequest,
            Message = $"查询失败: {ex.Message}"
        };
    }
    catch (Exception ex)
    {
        return new RestfulResult<PageResultDto>
        {
            Code = CodeStatus.BadRequest,
            Message = $"服务器错误: {ex.Message}"
        };
    }
}

版本迁移指南

从旧版本迁移

本包遵循语义化版本 (SemVer)。当前最新版本:

TFM 版本
net8.0 8.0.2.5
net10.0 10.0.2.5

迁移步骤:

  1. 更新 NuGet 包版本:
# .NET 8.0
dotnet add package RuoVea.OmiApi.Reports --version 8.0.2.5

# .NET 10.0
dotnet add package RuoVea.OmiApi.Reports --version 10.0.2.5
  1. 检查依赖包版本是否匹配:
依赖包 net8.0 net10.0
Handlebars.Net 2.1.4 2.1.4
RuoVea.DynamicWebApi 8.0.* 10.0.*
RuoVea.ExSugar 8.0.* 10.0.*
  1. ⚠️ 如果从 8.0.x 升级到 10.0.x,需要同步升级运行时到 .NET 10.0。

  2. 重新执行 AddReportInitSetup() 或手动执行数据库迁移脚本(表结构有变更时)。


常见问题

Q1: 启动后 Swagger 中看不到"报表管理"分组?

检查 appsettings.json 中是否配置了 Swagger.ApiVersions,确保包含 { "Title": "报表管理", "Version": "Reports" }

Q2: 图表数据查询返回空?

  1. 检查 RptChartConfig.DataSourceId 是否正确指向有效的 RptDataSource
  2. 检查 QuerySql 中的 #{varName} 变量是否在请求参数 parm 中传入
  3. ⚠️ 如果开启了缓存 (CacheEnabled=1),首次写入缓存的数据将被后续请求复用;如需刷新,等待 TTL 过期或重启缓存服务

Q3: 如何保护敏感字段(不向前端暴露 SQL)?

使用 GetDetailAsync(id, safe: true)GetDetailByCodeAsync(code, safe: true),安全模式会自动清空 OptionSqlOptionApiUrlOptionApiParams 字段。

Q4: 树形表格和主子表的字段互斥规则?

  • IsTree = Y 时,自动清理主子表字段 (IsMaster=N, MasterSqlId=null, LinkMasterField=null, ...)
  • IsMaster = Y 时,自动清理树形字段 (IsTree=N, TreeRowKey=null, TreeParentKey=null, ...)
  • 两者不可同时启用

Q5: 系统内置配置 (IsSystem=1) 的保护规则?

  • 不可删除(DeleteAsync / BatchDeleteAsync 会返回错误)
  • 不可修改 CodeConfigType
  • 可修改其他字段(如 NameIsEnabledSort、缓存配置等)

Q6: ❗ 大量图表组件并发查询时的缓存策略?

图表数据缓存键由 ChartData:{chartConfigId}:{sortedParams} 组成,参数按键排序后拼接为稳定字符串。相同图表、相同参数组合共享同一缓存键。TTL 默认 300 秒,可通过 RptChartConfig.CacheTtl 自定义。

Q7: ⚠️ 多线程环境下的 DI 生命周期注意事项?

  • 所有 Service 默认注册为 Scoped(推荐)
  • 不要将 Scoped 服务注入到 Singleton 服务中(会导致生命周期提升)
  • 如需调整,可通过 AddOmiReportSetup(serviceLifetime: ServiceLifetime.Singleton) 显式指定
  • AddReportInitSetup() 使用 Task.Run 在后台线程执行建表,启动后立即返回,不阻塞应用启动

Q8: 支持哪些数据库作为外部数据源?

RptDataSource 支持 SqlSugar 的全部数据库类型。GetDataType() 接口返回前 5 种最常用类型:MySQL (0)SqlServer (1)Sqlite (2)Oracle (3)PostgreSQL (4)

Q9: 报表定义更新的"先删后增"策略是什么?

更新报表定义时,系统在事务中执行:

  1. 更新 RptDefinition 主记录
  2. 删除旧的 RptDefinitionUi,插入新的
  3. 删除旧的 RptColumn + RptColumnDisplay + RptColumnSearch + RptColumnOption
  4. 使用 IdGenerator.Id 重新生成所有 ID 并批量插入新数据

这确保了字段顺序和配置的原子一致性。

Q10: ❗ 查询性能优化建议?

  • RptColumn.DefinitionCode 已冗余存储,查询字段时无需 JOIN RptDefinition 表
  • 按需加载:仅在需要时加载扩展表(显示/搜索/选项),不使用 Includes 一次性加载所有关联
  • 图表缓存:为高频访问的图表配置开启 CacheEnabled=1,减少数据库查询压力
  • 批量合并:分页查询时使用 ids.Contains() 批量加载 UI 扩展,避免 N+1 查询

详细数据库模型文档见 DB_MODEL.md

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.Reports:

Package Downloads
RuoVea.OmiReports

简单报表配置

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.2.7 82 9/2/2026
10.0.2.6 109 8/20/2026
10.0.2.5 104 8/16/2026
10.0.2.4 94 8/15/2026
10.0.2.3 97 8/15/2026
10.0.2.2 92 8/15/2026
10.0.2.1 106 8/15/2026
10.0.2 99 8/14/2026
10.0.1.6 119 7/25/2026
10.0.1.5 110 7/17/2026
8.0.2.7 86 9/2/2026
8.0.2.6 106 8/20/2026
8.0.2.5 100 8/16/2026
8.0.2.4 87 8/15/2026
8.0.2.3 87 8/15/2026
8.0.2.2 84 8/15/2026
8.0.2.1 95 8/15/2026
8.0.2 95 8/14/2026
8.0.1.6 149 7/25/2026
8.0.1.5 107 7/17/2026
Loading failed