Skip to content
bitzorcas
中EN

Guide

Reporting HTTP 契约、权限与数据范围

详细说明 Reporting 的 QUERY 与 POST 降级协议、请求校验、分页响应、精确权限、platform.reporting Feature、租户级 DataScope 和异步导出安全边界。

Last updated

Reporting 对外提供两条分页查询和一条异步导出提交端点。查询统一采用 RFC 10008 QUERY 主入口,并为不支持 QUERY 的客户端生成 POST .../_query 降级入口;不存在 GET 别名。三条入口都从认证上下文取得当前租户,不接受客户端 TenantId。

1. 端点总表

能力主入口兼容入口权限
工单汇总QUERY /api/reports/tickets/summaryPOST /api/reports/tickets/summary/_queryreporting.ticket-summary.read
审计活动日报QUERY /api/reports/activities/dailyPOST /api/reports/activities/daily/_queryreporting.activity.read
工单汇总导出POST /api/reports/tickets/summary/export无reporting.ticket-summary.export

QUERY 与 POST 降级入口接收同一 JSON 正文。OpenAPI 3.1 只发布 POST 降级操作,并用 x-http-query-method、x-http-query-path 等扩展声明首选 QUERY;生成 SDK 应读取这些扩展,而不是把操作降格为普通 POST 语义。

2. 工单汇总查询

QUERY 主入口
QUERY /api/reports/tickets/summary HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9
Content-Type: application/json
Accept: application/json
{
"status": "Closed",
"from": "2026-07-01T00:00:00+08:00",
"to": "2026-07-31T23:59:59+08:00",
"pageIndex": 1,
"pageSize": 20
}
字段类型/默认值当前处理
statusstring?null/空白不筛选;其他值 trim 后精确比较 StatusName;最长 64,禁止控制字符;不校验枚举
fromDateTimeOffset?OpenedAt >= from,闭区间下界
toDateTimeOffset?OpenedAt <= to,闭区间上界
pageIndexint = 1必须大于等于 1
pageSizeint = 20必须为 1–1000

From 晚于 To 或跨度超过 366 天时返回 Reporting.ReportingQuery.InvalidDateRange。时区偏移由 DateTimeOffset 保留;规则不会主动把边界改写成 UTC。若按自然月查询,应明确构造最后一个有效时刻或由业务层转换边界,不要误把次月零点当成排他上界。

PagedResult<TicketSummaryRow>
{
"items": [
{
"ticketId": "019c48cb7ba47000953d5af574ddc531",
"requesterId": "100",
"assigneeId": "200",
"subject": "无法登录生产租户",
"priorityName": "High",
"statusName": "Closed",
"openedAt": "2026-07-03T08:30:00Z",
"closedAt": "2026-07-03T10:15:00Z"
}
],
"totalCount": 1,
"pageIndex": 1,
"pageSize": 20
}

公开行不含 Mart 的 LastEventId 和 LastUpdatedAt,不能作为投影 watermark 使用。

3. 审计活动日报查询

查询一个 UTC 日期范围
POST /api/reports/activities/daily/_query HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9
Content-Type: application/json
{
"from": "2026-07-01T00:00:00Z",
"to": "2026-07-31T00:00:00Z",
"pageIndex": 1,
"pageSize": 50
}

from 和 to 是必填 DateTime,同样使用闭区间,要求顺序正确且跨度不超过 366 天。分页范围与工单查询一致。响应也是完整 PagedResult:

PagedResult<AuditActivityDailyRow>
{
"items": [
{
"activityDate": "2026-07-15T00:00:00",
"userId": "42",
"actionType": "ticket.view",
"activityCount": 17
}
],
"totalCount": 1,
"pageIndex": 1,
"pageSize": 50
}

协议仍未规定 DateTime Kind、租户时区或 date-only 归一方式;生产 writer 也尚不存在。详见审计活动日报与聚合任务。

4. 输入错误目录

条件稳定错误码
页码小于 1,或页大小不在 1–1000Reporting.ReportingQuery.InvalidPage
status 超过 64 字符或含控制字符Reporting.ReportingQuery.InvalidStatus
日期逆序或跨度超过 366 天Reporting.ReportingQuery.InvalidDateRange
非真实/委托用户、UserId 非正数或租户非法Reporting.User.Required
授权不是 Tenant DataScopeReporting.DataScope.TenantRequired
导出格式不是 csv/excelReporting.Export.InvalidFormat
幂等键不满足 8–128 个无控制字符Reporting.Export.InvalidIdempotencyKey

5. 精确权限已对齐 Read

两个 Query 都实现 IAuthorizedRequest 并声明 AuthorizationAction.Read:

查询权限派生
public ResourceDescriptor Resource { get; } =
new(ReportingPermissions.Module, ReportingPermissions.TicketResource);
public AuthorizationAction Action { get; } = AuthorizationAction.Read;
// => reporting.ticket-summary.read

治理目录和授权种子包含:

  • reporting.ticket-summary.read;
  • reporting.ticket-summary.export;
  • reporting.activity.read;
  • reporting.export-job.read。

旧版文档所述 .view/.read 漂移已经修复。不要为兼容旧文档重新授予 .view;应以当前生成权限和种子为准。

6. Feature 运行时事实

FeaturePolicyEvaluator.ModuleFeatureMap 包含:

["reporting"] = "platform.reporting"

platform.reporting 在中央 Feature seed 中默认 Disabled,并标记为商业能力;租户 override 可显式启用。禁用时 evaluator 返回 Deny,不是 Neutral。

Reporting 模块本身还通过 [FeatureDefinition] 贡献 reporting.mart,默认也是关闭,但授权管道不会根据资源模块 reporting 查询它。当前存在两个表达同一产品能力的代码:

代码当前用途
platform.reporting运行时授权 Feature gate、许可证示例和中央 seed
reporting.martReporting owner 治理 catalog 定义

在两者合并或建立显式别名前,运行配置必须启用 platform.reporting;只启用 reporting.mart 不会打开查询端点。

7. 当前用户与 Tenant DataScope

Endpoint 授权通过后,Handler 仍执行以下约束:

Reporting 的应用层数据范围门禁
// 资源 TenantId 必须由认证上下文覆盖,不能信任请求体。
var decision = await authorization.EvaluateAsync(
user,
resource with { TenantId = user.TenantId },
action,
cancellationToken);
// Reporting 只接受精确的租户级范围;User/Department/All 都不能替代。
if (!decision.IsAllowed || decision.DataScope != DataScope.Tenant)
return ReportingErrors.TenantDataScopeRequired;

当前用户必须满足:

  • IsAuthenticated=true;
  • CallerType 为 User 或 Delegated;
  • UserId 存在且大于 0;
  • TenantId 通过 TenancyDefaults.IsValid。

Store 的每个 Count、Page、Find、Upsert 和导出批读谓词也包含 TenantId,唯一索引同样包含 TenantId。调用者不能在正文中选择别的租户。

Own、Department、Office 等数据范围不会自动收窄 Mart 查询,而是直接被拒绝。原因是当前汇总表没有足够维度安全表达这些 scope。若未来支持更细范围,应先把 owner 规则转成数据库谓词或安全候选集,不能先取全租户数据再内存过滤。

8. 导出安全契约

导出请求:

{
"status": "Closed",
"from": "2026-07-01T00:00:00Z",
"to": "2026-07-31T23:59:59Z",
"format": "excel",
"idempotencyKey": "reporting-closed-202607-v1"
}

安全边界不是“复用查询后下载”:

  1. 提交要求精确 .export 权限和 Tenant DataScope;
  2. Builder Key、列和文件基础名由服务端固定;
  3. status/from/to/format 进入幂等指纹;
  4. 任务只允许 ExportScope.All,不接受 CheckedIds;
  5. 执行和重试恢复原请求租户与用户,并重新验证 .export;
  6. 每条 Mart 行还通过 Tickets owner 的 EnsureSubjectCanViewAsync 实时复核;AccessDenied/NotFound 行被跳过,基础设施错误会使任务失败;
  7. 输出固定八列:ticketId、subject、statusName、priorityName、requesterId、assigneeId、openedAt、closedAt。

这使导出比交互式查询多一层逐工单 owner 授权。查询本身仍要求租户级 scope,并直接返回全租户 Mart 行。

9. 字段敏感性

工单行的 Subject 是自由文本,RequesterId/AssigneeId 是人员标识;审计行的 UserId、ActionType 和计数可形成行为画像。当前没有字段安全 contributor 或响应 masker。前端隐藏列不是安全控制。

上线前至少应明确:

  • 哪些角色可读取逐工单 Subject 和人员 ID;
  • 哪些角色只能看聚合计数;
  • 导出是否需要 watermark、水印、下载过期和审计;
  • 审计日报何时聚合到组织层级或对 UserId 做假名化;
  • 删除、保留和合法保留如何同步到派生 Mart。

10. 契约测试清单

  1. QUERY 与 POST 降级使用相同正文和响应;GET 返回 405/未映射。
  2. OpenAPI 扩展正确声明 QUERY 主路径与降级路径。
  3. .read、.export 与缺失权限分别得到固定结果。
  4. platform.reporting 启用/禁用和 unavailable adapter 均失败方式明确。
  5. User、Delegated、System、匿名、无效 UserId/TenantId 的边界。
  6. Tenant、Own、Department、Office DataScope 的允许/拒绝矩阵。
  7. null/空白/64 字符/控制字符 status。
  8. 日期闭区间、偏移、Kind、逆序和 366/367 天边界。
  9. pageIndex 和 pageSize 的 1、20、1000、1001 边界。
  10. 导出格式、幂等键、指纹、重试复核和中途撤权。
  11. Subject/UserId 的字段策略与导出审计。
  12. 稳定 Problem Details 错误码和描述一致性。

11. 审查命令

Terminal window
# 路由、动作和请求规则。
rg -n "HttpRoute\.(Query|Post)|AuthorizationAction\.(Read|Export)|Validate(Page|DateRange|Idempotency)" \
src/Platform/Reporting -g '*.cs'
# 运行时 Feature 与 owner catalog 代码要同时审查。
rg -n "platform\.reporting|reporting\.mart|\[\"reporting\"\]" src tests -g '*.cs'
# Tenant DataScope 与逐工单导出复核。
rg -n "RequireTenantDataScopeAsync|DataScope\.Tenant|EnsureSubjectCanViewAsync" \
src/Platform/Reporting src/Platform/Tickets -g '*.cs'

返回 Reporting 总览 · Mart 与分页 · 测试与 GA

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%