风控列表游标绑定用户与查询条件(docs/25 遗留 P3)

docs/05 §3.8 要求游标绑定用户、查询条件、排序字段和方向,此前实现只把 offset
用 base64 包了一层:任何登录用户拿到别人的游标都能继续翻,换个筛选条件也能继续翻
(偏移量对不上就静默返回错页)。现在游标里携带 SHA-256 指纹:

- 指纹口径 = user_id + data_scope/customer_ids + 查询条件(排除 limit/cursor)
- 刻意排除 limit:它是分页参数、不是查询条件,算进去只会让翻页时改页大小失效
- /evidence/{source} 的 source 是路径参数,单独并入指纹,否则 customers 的
  游标能直接拿去翻 products
- 指纹不符一律 InvalidCursorError -> 400 INVALID_CURSOR(docs/05 §3.6)

新增 2 个单测:换用户/换筛选/换 data_scope 失效、改 limit 仍有效、
不同证据类型游标不互通。
This commit is contained in:
2026-09-11 14:05:37 +08:00
parent e8b075bc7f
commit a572c09a5c
4 changed files with 142 additions and 23 deletions
+3 -2
View File
@@ -31,6 +31,7 @@ class RiskNotificationService:
query: RiskNotificationPageQuery,
) -> dict[str, Any]:
await AuthorizationService.require(context, "risk:alert:read")
binding = RiskQueryService._binding(context, query, "/notifications")
page = await RiskRepository(
self.session,
scope=scope_from_context(context),
@@ -41,10 +42,10 @@ class RiskNotificationService:
end_time=query.end_time,
page=PageRequest(
limit=query.limit,
offset=decode_offset_cursor(query.cursor),
offset=decode_offset_cursor(query.cursor, binding=binding),
),
)
return RiskQueryService._page(page)
return RiskQueryService._page(page, binding=binding)
def create_in_app(
self,