如何为 billing 注册自定义 audit events
结论
如果完全不使用自定义持久化存储,就无法只靠进程内存可靠地登记可计费事件。应用重启、故障或扩缩容都会导致内存状态丢失,进程内存也无法满足计费所需的去重、追溯和审计要求。
业务事件需要写入持久化的审计目标。可以按以下顺序选择方案:
- 优先使用当前版本 Invantive Data Access Point 提供的 audit event 或 usage metering 接口。
- 如果没有可用的原生接口,可以将事件保存为 Exact Online 中合适的业务对象,例如日志、备注或文档。
- 如果以上方案都不适用,就必须引入外部事件存储,否则无法生成可靠的 billing 数据。
为什么技术指标不能代替业务事件
CPU、内存和 SQL 执行次数反映的是资源消耗,无法准确表示用户获得的业务价值。例如,一次上传可能包含 1 条银行交易,也可能包含 8 条,但两次操作执行的 SQL 数量未必有明显差别。
计费记录至少需要包含:
- 稳定的事件类型,例如
bank_transactions_uploaded - 业务数量,例如
8 - Exact Online division 或客户标识
- 事件发生时间
- 唯一事件 ID
- 相关批次或请求 ID
- 必要的业务上下文,但不能包含银行账号等敏感数据
建议采用以下事件结构:
{
"eventId": "01J6H8V2QJ8P3K6Y7M9N4R2T1X",
"eventType": "bank_transactions_uploaded",
"quantity": 8,
"unit": "bank_transaction",
"division": 123456,
"occurredAt": "2026-08-29T09:30:00Z",
"correlationId": "upload-20260829-00042"
}
注册位置
应在上传操作成功提交后登记事件,而不是在用户点击按钮时登记。否则,因验证失败、事务回滚或网络故障而未完成的上传也可能产生费用。
处理顺序可以是:
验证上传内容
↓
向 Exact Online 提交交易
↓
确认提交成功
↓
登记 bank_transactions_uploaded,quantity = 8
如果上传可能部分成功,quantity 应记录实际成功写入的交易数量,而不是请求中原有的数量。
使用 Invantive 原生接口
如果部署版本提供 audit event 或 usage metering API,应优先调用该接口。这类接口通常比普通日志更适合计费汇总和审计。
不同 Invantive 产品和版本暴露的 API 名称及参数可能不同。在没有对应版本文档时,不能假定某个 SQL package、表名或 HTTP endpoint 一定存在。下面的代码只用于说明调用方式,其中 RegisterBillableEvent 是占位名称,必须替换为当前环境实际提供的接口:
var billableEvent = new
{
EventId = eventId,
EventType = "bank_transactions_uploaded",
Quantity = successfulTransactionCount,
Unit = "bank_transaction",
Division = division,
OccurredAt = DateTimeOffset.UtcNow,
CorrelationId = uploadId
};
// 使用当前 Invantive Data Access Point 版本实际提供的
// audit event 或 usage metering API。
RegisterBillableEvent(billableEvent);
确认接口时,需要核实以下事项:
- 接口是否会持久化事件,而非只写入诊断日志。
- 是否允许自定义
eventType。 - 是否支持
quantity或类似的数量字段。 - 重复提交同一个
eventId时是否具有幂等性。 - 数据保留期限是否覆盖账单和争议处理周期。
- 是否可以按客户、division、时间段和事件类型导出数据。
普通 application log、trace 或 diagnostic log 不一定具备这些能力。即使日志中出现了事件文本,也不能据此认定它可以作为正式的计费依据。
使用 Exact Online 保存事件
如果没有原生计量接口,而且所有持久化状态都必须保留在 Exact Online 中,可以选择合适的标准对象承载事件。例如,每个上传批次创建一条文档或日志记录,并在描述或备注中写入结构化信息。
记录内容可以采用以下形式:
Event-ID: 01J6H8V2QJ8P3K6Y7M9N4R2T1X
Event-Type: bank_transactions_uploaded
Quantity: 8
Unit: bank_transaction
Correlation-ID: upload-20260829-00042
Occurred-At: 2026-08-29T09:30:00Z
这种做法不要求 Exact Online 支持 custom tables,但需要事先确认:
- 集成应用可以创建所选对象。
- 这种用途符合 Exact Online 的数据模型和 API 限制。
- 记录不会干扰用户日常使用的业务列表。
- 普通用户不会意外修改或删除这些对象。
- API 限流和存储容量可以承受预计的事件数量。
- 可以通过
Event-ID检查重复事件。
不要只保存自然语言记录:
Performed an upload of 8 bank transactions
自然语言便于阅读,却不适合稳定汇总。记录中至少还要包含结构化的事件类型和数量。
可靠性与幂等性
这里无法避开“双写”问题:Exact Online 中的交易可能已经成功提交,但审计事件写入失败。建议为每次业务操作生成稳定的 eventId,以便安全重试。
try
{
int uploadedCount = UploadBankTransactions(transactions);
RegisterUsageEvent(
eventId: eventId,
eventType: "bank_transactions_uploaded",
quantity: uploadedCount,
correlationId: uploadId
);
}
catch
{
// 不应重复上传已经成功提交的银行交易。
// 后续只重试登记具有相同 eventId 的 usage event。
throw;
}
如果事件目标无法按 eventId 去重,重试可能造成重复计费。这种情况下需要额外的持久化 outbox 或去重存储,单靠内存无法彻底解决。
注意事项
- 不要直接把诊断日志当作计费账本,除非已经确认其持久性、不可篡改性、保留期限和导出能力。
- 不要将客户名称、银行账号、交易说明等敏感内容写入计量事件。
- 需要明确数量口径,例如“成功创建的银行交易数”,并在所有版本中保持一致。
- 批量上传通常记录一条数量为
8的事件即可,不必写入八条文本日志,除非计费模型要求逐笔追踪。 - 如果计费数据必须在故障后保持完整,就不能只保存在内存中。至少要在 Invantive、Exact Online 或外部系统中设置一个持久化审计目标。
备注:内容仅供参考。
