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
<PackageReference Include="RuoVea.OmiApi.Reports" Version="10.0.2.7" />
<PackageVersion Include="RuoVea.OmiApi.Reports" Version="10.0.2.7" />
<PackageReference Include="RuoVea.OmiApi.Reports" />
paket add RuoVea.OmiApi.Reports --version 10.0.2.7
#r "nuget: RuoVea.OmiApi.Reports, 10.0.2.7"
#:package RuoVea.OmiApi.Reports@10.0.2.7
#addin nuget:?package=RuoVea.OmiApi.Reports&version=10.0.2.7
#tool nuget:?package=RuoVea.OmiApi.Reports&version=10.0.2.7
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 |
迁移步骤:
- 更新 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
- 检查依赖包版本是否匹配:
| 依赖包 | net8.0 | net10.0 |
|---|---|---|
| Handlebars.Net | 2.1.4 | 2.1.4 |
| RuoVea.DynamicWebApi | 8.0.* | 10.0.* |
| RuoVea.ExSugar | 8.0.* | 10.0.* |
⚠️ 如果从 8.0.x 升级到 10.0.x,需要同步升级运行时到 .NET 10.0。
重新执行
AddReportInitSetup()或手动执行数据库迁移脚本(表结构有变更时)。
常见问题
Q1: 启动后 Swagger 中看不到"报表管理"分组?
检查 appsettings.json 中是否配置了 Swagger.ApiVersions,确保包含 { "Title": "报表管理", "Version": "Reports" }。
Q2: 图表数据查询返回空?
- 检查
RptChartConfig.DataSourceId是否正确指向有效的RptDataSource - 检查
QuerySql中的#{varName}变量是否在请求参数parm中传入 - ⚠️ 如果开启了缓存 (
CacheEnabled=1),首次写入缓存的数据将被后续请求复用;如需刷新,等待 TTL 过期或重启缓存服务
Q3: 如何保护敏感字段(不向前端暴露 SQL)?
使用 GetDetailAsync(id, safe: true) 或 GetDetailByCodeAsync(code, safe: true),安全模式会自动清空 OptionSql、OptionApiUrl、OptionApiParams 字段。
Q4: 树形表格和主子表的字段互斥规则?
- 当
IsTree = Y时,自动清理主子表字段 (IsMaster=N, MasterSqlId=null, LinkMasterField=null, ...) - 当
IsMaster = Y时,自动清理树形字段 (IsTree=N, TreeRowKey=null, TreeParentKey=null, ...) - 两者不可同时启用
Q5: 系统内置配置 (IsSystem=1) 的保护规则?
- 不可删除(
DeleteAsync/BatchDeleteAsync会返回错误) - 不可修改
Code和ConfigType - 可修改其他字段(如
Name、IsEnabled、Sort、缓存配置等)
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: 报表定义更新的"先删后增"策略是什么?
更新报表定义时,系统在事务中执行:
- 更新
RptDefinition主记录 - 删除旧的
RptDefinitionUi,插入新的 - 删除旧的
RptColumn+RptColumnDisplay+RptColumnSearch+RptColumnOption - 使用
IdGenerator.Id重新生成所有 ID 并批量插入新数据
这确保了字段顺序和配置的原子一致性。
Q10: ❗ 查询性能优化建议?
- RptColumn.DefinitionCode 已冗余存储,查询字段时无需 JOIN RptDefinition 表
- 按需加载:仅在需要时加载扩展表(显示/搜索/选项),不使用
Includes一次性加载所有关联 - 图表缓存:为高频访问的图表配置开启
CacheEnabled=1,减少数据库查询压力 - 批量合并:分页查询时使用
ids.Contains()批量加载 UI 扩展,避免 N+1 查询
详细数据库模型文档见 DB_MODEL.md
| Product | Versions 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. |
-
net10.0
- Handlebars.Net (>= 2.1.4)
- RuoVea.DynamicWebApi (>= 10.0.0)
- RuoVea.ExSugar (>= 10.0.1.3)
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 |