Reporting 是面向读取的投影模块,不是通用 BI 引擎。模块拥有两张去规范化 Mart 表:Rpt_TicketSummary 保存工单当前快照,Rpt_AuditActivityDaily 保存租户、日期、用户和动作四个维度的日计数。公开业务面包括两条 RFC 10008 QUERY 查询和一条工单汇总异步导出提交端点。
1. 已实现能力
| 能力 | 当前实现 |
|---|---|
| 工单汇总查询 | QUERY /api/reports/tickets/summary,POST 降级路径为 /api/reports/tickets/summary/_query |
| 审计活动日报查询 | QUERY /api/reports/activities/daily,POST 降级路径为 /api/reports/activities/daily/_query |
| 工单汇总导出 | POST /api/reports/tickets/summary/export,仅接受 csv 或 excel,始终异步 |
| 权限 | 查询使用 .read,导出使用 .export |
| Feature | 运行时把资源模块 reporting 映射到 platform.reporting |
| 数据范围 | Handler 再次执行授权决策,并且只接受 DataScope.Tenant |
| 工单投影 | 订阅 opened、assigned、started、resolved、closed、reopened 六个 topic |
| 存储 | 两张 owner-local Mart 表,通过 IEntitySet<T> 和 Query Shape 适配 ORM |
| 无持久化宿主 | IReportingMartStore 使用 unavailable adapter 失败关闭,不返回伪空数据 |
当前没有报表设计器、图表或仪表盘引擎、跨租户分析、审计日报聚合任务、投影 checkpoint、inbox、按版本重放、对账、影子表重建、新鲜度 API 和 Mart 生命周期清理。
2. 核心流程
事件发布不是由业务 Handler 手工构造 Ticket*IntegrationEvent。Ticket 聚合抛出的领域事件同时实现 IIntegrationEvent 并带 [IntegrationTopic];源生成器为事件类型生成 topic 与标量 payload 映射;命令事务在提交前把消息写入 CAP Outbox。Reporting 中同名的 Ticket*IntegrationEvent 是 consumer 绑定模型。
3. 查询协议
列表和分页读取不提供 GET 别名。客户端应优先发送带 JSON 正文的 QUERY;代理或工具链不支持 QUERY 时,发送同正文的 POST 降级请求。
| 报表 | 请求字段 | 响应 |
|---|---|---|
| 工单汇总 | status?、from?、to?、pageIndex=1、pageSize=20 | PagedResult<TicketSummaryRow> |
| 审计日报 | 必填 from、to,以及 pageIndex=1、pageSize=20 | PagedResult<AuditActivityDailyRow> |
QUERY /api/reports/tickets/summary HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiJ9Content-Type: application/jsonAccept: application/json
{ "status": "Closed", "from": "2026-07-01T00:00:00Z", "to": "2026-07-31T23:59:59Z", "pageIndex": 1, "pageSize": 50}兼容请求只改变方法和路径:
POST /api/reports/tickets/summary/_query HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiJ9Content-Type: application/json
{"status":"Closed","from":"2026-07-01T00:00:00Z","to":"2026-07-31T23:59:59Z","pageIndex":1,"pageSize":50}请求规则如下:
pageIndex必须大于等于 1;pageSize必须为 1–1000;- status 会 trim,最长 64 个字符,不能包含控制字符;当前不校验它是否属于
TicketStatus; - from 不得晚于 to,跨度不得超过 366 天;
- From/To 都是闭区间,工单按
OpenedAt过滤,审计日报按ActivityDate过滤; - 工单范围使用
DateTimeOffset,但不会强制转成 UTC;审计范围使用DateTime,协议没有进一步约束 Kind。
4. 权限、Feature 与数据范围
三条业务入口使用以下稳定权限:
| 入口 | 资源与动作 | 派生权限 |
|---|---|---|
| 工单汇总查询 | reporting/ticket-summary + Read | reporting.ticket-summary.read |
| 审计日报查询 | reporting/activity + Read | reporting.activity.read |
| 工单汇总导出 | reporting/ticket-summary + Export | reporting.ticket-summary.export |
统一 Feature evaluator 把 reporting 映射到中央 entitlement platform.reporting;禁用时返回 Deny。Reporting owner 另行贡献了 reporting.mart Feature 定义,但查询授权链并不读取这个代码。部署和套餐配置当前应以 platform.reporting 为运行时事实;两个 Feature 码的重复含义仍需在源码中收敛。
查询与导出 Handler 只接受已认证的真实用户或委托用户,要求有效的正数 UserId 和 TenantId。随后再次调用 IAuthorizationDecisionService,只有 IsAllowed=true 且 DataScope == Tenant 才继续。Own、Department、Office 等较窄范围会失败关闭,而不是转换成行级谓词。
这套规则避免了“先查全租户再在内存裁剪”,代价是只有租户级数据范围用户能使用报表。公开行仍含 Subject、RequesterId、AssigneeId 或逐用户活动计数;当前没有字段掩码。
5. 工单汇总异步导出
POST /api/reports/tickets/summary/export HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiJ9Content-Type: application/json
{ "status": "Closed", "from": "2026-07-01T00:00:00Z", "to": "2026-07-31T23:59:59Z", "format": "csv", "idempotencyKey": "reporting-202607-closed-v1"}提交成功返回 { "jobId": "..." }。实现约束包括:
- 幂等键 trim 后必须为 8–128 个无控制字符的文本;筛选与格式进入请求指纹;
- Builder Key 固定为
reporting.ticket-summary,列集合由服务端决定; - 只允许
ExportScope.All,不接受客户端 CheckedIds; ForceAsync=true,原始 Mart 批读默认 5000、最大 100000,授权扫描批次为 1000;- Builder 对每个 Ticket 调用 Tickets owner 的
ITicketResourceAuthorizationReader,撤销权限后后续批次不再输出该行; - 重试时重新检查
.export和租户级 DataScope; - 公开任务参数只显示规范化后的 status/from/to,不回显原始参数 JSON。
任务历史、状态、下载、取消和重试由平台 Export Center 的 /api/export/... 端点提供,不属于 Reporting 专用路由。
6. 工单投影现状
| Topic | 投影变化 |
|---|---|
ticket.opened | 创建 New,写 Requester、Subject、Priority、OpenedAt |
ticket.assigned | 写 AssigneeId,并改为 Assigned |
ticket.started | 改为 InProgress |
ticket.resolved | 改为 Resolved |
ticket.closed | 改为 Closed,并写 ClosedAt |
ticket.reopened | 改为 Reopened,并清空 ClosedAt |
consumer 对直接重复的最后一个 EventId 跳过,对 OccurredAt < LastUpdatedAt 的非 opened 事件跳过;缺少 opened 前置行、Mart 读写失败或其他处理异常会重新抛出,让 CAP 执行重试。现有集成测试直接构造六种 consumer DTO,验证完整状态序列、直接重复和陈旧 closed。
仍有三个关键边界:
Ticket.Open直接Raise(new TicketOpened(ticket.Id, ...)),此时 Id 为"0";仓储稍后才分配最终 ID。opened 与后续事件无法按同一 TicketId 汇合。- Mart 只保存最后一个 EventId,能处理 A,A,不能识别 A,B,A;事件也没有聚合版本或序号。
- Upsert 是先查后写,没有 compare-and-set;相同时间戳和并发消费仍可能由后提交者覆盖。
详见工单事件投影与乱序一致性。
7. 审计日报现状
Rpt_AuditActivityDaily 与 UpsertAuditActivityDailyAsync 已实现,但生产源码没有 consumer、JobHost executor、SQL 聚合器、checkpoint 或回补命令。现有 Handler 测试用 substitute 返回手工行,只验证映射。
ActivityDate 是完整 DateTime,Store 不规范到 UTC midnight;Upsert 覆盖完整 ActivityCount,不是原子递增。同一业务日可能因时分秒或 Kind 不同形成多个唯一键。详见审计活动日报与聚合任务。
8. Mart 查询与一致性
两条标准列表都通过 Query Shape 执行,并显式增加可信 TenantId 谓词:
- 工单按
(OpenedAt desc, TicketId desc)稳定排序; - 审计日报按
(ActivityDate desc, UserId desc, ActionType desc)稳定排序; - From/To 都是闭区间;
- 公开 DTO 不包含 Mart 的
LastEventId或LastUpdatedAt,调用方无法判断 watermark; - 两类 Upsert 都采用 check-then-insert/update,没有数据库原子 Upsert 或版本条件。
9. 测试证据与生产缺口
现有证据覆盖 Query Handler 规则、Read 权限、Tenant DataScope、Feature 映射、导出提交与逐行 owner 复核、六事件 consumer 状态序列、Infrastructure ORM 中立性、Mart 元数据,以及 API Shell unavailable adapter。
仍缺少:真实 Ticket 创建到 Outbox 再到 CAP 绑定的端到端测试、opened 最终 ID 守卫、ReportingMartStore 双 ORM 行为套件、版本/inbox/gap 语义、并发 Upsert、审计日报生产任务、真实 HTTP 授权矩阵、投影 watermark、对账、重建演练和生产规模查询计划。
生产验收清单见测试、可观测性、重建与 GA。
10. 源码导航
| 主题 | 源码入口 |
|---|---|
| 查询 | src/Platform/Reporting/...Application/Queries |
| 导出提交与 Builder | ...Application/Commands/ExportTicketSummary、TicketSummaryExportBuilder.cs |
| 请求与 DataScope 规则 | ...Application/ReportingApplicationRules.cs |
| 权限与 owner Feature | ReportingConstants.cs、ReportingFeatures.cs |
| 运行时 Feature 映射 | src/Framework/BitzOrcas.Application/Authorization/Feature/FeaturePolicyEvaluator.cs |
| 工单 consumer | ...Reporting.Infrastructure/TicketReportingEventConsumer.cs |
| Mart Store 与 Query Shape | ...Reporting.Infrastructure/ReportingMartStore.cs |
| 工单发布事件 | src/Platform/Tickets/...Contracts/Tickets/Events/Ticket*.cs |
| 审计日报行 | ...Reporting.Infrastructure/Persistence/RptAuditActivityDailyRecord.cs |
返回平台模块目录 · Tickets · Auditing · Authorization