Skip to content
bitzorcas
中EN

Guide

Search HTTP 契约、权限与 Feature

逐条解释 Search 九个端点、手写与生成式路由、请求超时、运行时权限派生、目录漂移、Feature Neutral 和输入验证边界。

Last updated

Search 同时使用五条手写 Minimal API 和四条 [GenerateEndpoint] 路由。授权最终由 request 的 ResourceDescriptor + AuthorizationAction 派生,注释里写“需要 manage/check”不会改变运行时权限。

1. 端点总表

方法路由实现请求动作实际权限
POST/api/search/unified手写转换Viewsearch.search.view
POST/api/search/conflict/check手写转换Searchsearch.conflict.search
POST/api/search/segment/words手写枚举校验Createsearch.index.create
DELETE/api/search/segment/words手写枚举校验Deletesearch.index.delete
GET/api/search/segment/words/{segmentType}手写枚举校验Viewsearch.index.view
GET/api/search/conflict/records/{recordId}generatedViewsearch.conflict.view
GET/api/search/index/cataloggeneratedViewsearch.index.view
GET/api/search/index/{indexKey}/statisticsgeneratedViewsearch.index.view
POST/api/search/index/{indexKey}/rebuildgeneratedManagesearch.index.manage

手写 group 统一 RequireAuthorization()、userPolicy rate limit;unified/conflict/segment 写使用 StandardCommand timeout,segment list 使用 ShortRead。Generated route 经过统一授权管道,但源码没有在 request 上声明 Search 专用 timeout policy。

2. 统一搜索请求

统一搜索请求
POST /api/search/unified HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9
Content-Type: application/json
{
"indexKey": "conflict",
"keyword": "海岚科技",
"skip": 0,
"take": 20
}

当前 API model 默认 Skip=0、Take=20,但没有规则拒绝:

  • 空白/未知 IndexKey;
  • Skip < 0;
  • Take≤0 或超大;
  • 过长 Keyword;
  • wildcard/query syntax 与控制字符;
  • 深分页导致的高成本窗口。

Handler 把同一个 keyword 写入 Name、Subject、Content,不 trim,也不选择 culture/analyzer。provider 如何处理空白和特殊字符取决于外部包,公开协议不能依赖未固定的隐式行为。

3. 利冲请求

ConflictCheckApiRequest
{
"tenantId": "client-supplied-and-ignored",
"parties": [
{
"name": "海岚科技有限公司",
"foreignName": "Hailan Technology",
"formerName": "海岚实业",
"creditCode": "91310000MA00000000"
}
],
"excludeCaseId": "case-2026-0042"
}

TenantId 保留在 DTO/API 中却被 Handler 覆盖为当前租户。这避免了跨租户读取,但会让 SDK 使用者误以为字段有效。应删除客户端 TenantId,或清晰标记 deprecated/ignored,避免审计和调用误解。

Parties 没有 request rule:null collection 会在枚举时异常;空列表直接返回 HasConflict=false;Name 虽声明 non-null,但 runtime JSON 可提交 null;重复 Name 会覆盖 category map 中前一个集合;Name 全空白但其他字段有值时仍以空白 Name 作字典 key。利冲高价值操作不能用这些隐式边界。

4. SegmentType 与 Word

端点在发送 Mediator 前用 Enum.IsDefined 校验 0/1/2,非法值直接返回匿名 400 对象:

{
"code": "Search.Segment.InvalidType",
"message": "无效的分词类型: 9"
}

这条错误没有走统一 Result/Error → Problem Details,响应 media type/traceId/扩展字段可能与其他端点不一致。Word 只在 Handler 拒绝 null/空白并 Trim;不限制长度、Unicode 规范化、大小写、控制字符或业务词类型。

目标输入规则
public static class SearchErrors
{
public static readonly Error IndexUnknown =
Error.NotFound("Search.Index.Unknown", "搜索场景不存在或未启用。");
public static readonly Error PageInvalid =
Error.Validation("Search.Page.Invalid", "搜索窗口超出范围。");
public static readonly Error KeywordTooLong =
Error.Validation("Search.Keyword.TooLong", "搜索关键字过长。");
}
// IndexKey 只能从已启用目录解析,不能让用户任意探测物理索引名。
var catalog = await catalogStore.GetAsync(request.IndexKey, cancellationToken);
if (catalog is null || catalog.Status != IndexStatus.Active)
return SearchErrors.IndexUnknown;
// 交互式搜索使用有界窗口;批量导出另建异步契约。
if (request.Skip < 0 || request.Take is < 1 or > 100)
return SearchErrors.PageInvalid;
// 先 Unicode normalize/trim,再按 UTF-8 byte 与 rune 数设置上限。
if (Normalize(request.Keyword).Length > 200)
return SearchErrors.KeywordTooLong;

5. 权限目录与运行时漂移

治理目录:

  • search.search.view;
  • search.conflict.search;
  • search.conflict.view;
  • search.index.manage;
  • search.index.view。

View 类端点、利冲 check 与重建都与目录一致。仍漂移的是弱词写:

运行时权限派生
// 利冲 check 实际派生与目录一致。
new ResourceDescriptor("search", "conflict");
AuthorizationAction.Search; // => search.conflict.search
// 重建声明 Manage,与目录一致;弱词写分别声明 Create/Delete。
new ResourceDescriptor("search", "index");
AuthorizationAction.Manage; // => search.index.manage
AuthorizationAction.Create; // => search.index.create
AuthorizationAction.Delete; // => search.index.delete

AuthorizationAction 没有 Check 动作,却有 Manage。修复路线:把弱词 create/delete 统一为 AuthorizationAction.Manage,或分权并迁移目录。需要一起修改 seed、角色 grant、缓存、升级映射和端点测试。

6. Feature 目录没有运行时门禁

SearchFeatures.Index = "search.index" 以 DefaultEnabled=false 进入治理 catalog。中央 FeaturePolicyEvaluator.ModuleFeatureMap 只有 tickets/chat/workflow,没有 search;Search request 因此得到 Neutral,不会查询租户 entitlement。

如果 index management 与 query 是不同套餐能力,一个 search.index 也可能太粗。推荐至少区分 query、conflict、index-management,或明确全模块只使用一个 entitlement。无论粒度如何,必须在统一 evaluator 或用例 gate 中真正执行。

7. 目录与统计的作用域

GetIndexStatistics 以当前 TenantId 调 engine,是租户级统计。GetIndexCatalog 读取全局 catalog,没有 TenantId,也没有只返回租户启用场景的过滤。拥有 search.index.view 的租户用户可能看到 DirectoryName、EntityType、全局 DocumentCount、LastRebuiltAt 与 Version。

目录详情偏运维/平台信息,应决定它属于:平台管理员;租户管理员的裁剪视图;或仅内部 health endpoint。不要用同一个 index.view 同时承担搜索使用权与物理目录可见权。

8. 索引场景级授权

UnifiedSearch 接受任意 IndexKey,但 ResourceDescriptor 固定为 search/search,没有把 IndexKey 带入 resource id/attributes。只要有统一搜索权限,就能尝试 conflict、keyword-search 或未来索引。

可选模型:

  • 每 IndexKey 权限:search.index.{key}.query;
  • catalog 保存 RequiredPermission/DataClassification;
  • authorization attribute 带 IndexKey、业务类型与敏感级别;
  • 对利冲等高敏场景只开放专用 endpoint,不允许 unified 绕过。

即使场景授权通过,命中仍需源资源 DataScope。

9. 稳定错误契约

当前稳定错误只有:

  • SegmentWord.EmptyWord(Failure);
  • ConflictRecord.PendingCasesModule(NotFound)。

缺少 IndexUnknown、IndexUnavailable、QueryInvalid、WindowTooLarge、ProviderTimeout、RebuildConflict 等分类。外部 provider exception 直接穿过 Handler,最终映射是否稳定取决于全局异常处理。

10. Endpoint 测试矩阵

  1. 每个 catalog grant 与实际 action 的 200/403;
  2. Feature enabled/disabled/unavailable;
  3. 认证、rate limit 与 timeout;
  4. IndexKey allowlist 和场景级权限;
  5. Skip/Take/Keyword 边界;
  6. Parties null/empty/重复/Unicode/超长;
  7. 请求 TenantId 不影响 effective tenant;
  8. SegmentType 400 也使用统一 Problem Details;
  9. generated/manual route OpenAPI 一致;
  10. provider unavailable 不被转成空结果。

11. 审查命令

Terminal window
# 对照端点动作与治理目录;conflict 与 rebuild 已与目录一致,弱词 create/delete 仍漂移。
rg -n "AuthorizationAction\.|search\.(search|conflict|index)\." \
src/Platform/Search src/Hosts/BitzOrcas.Api/Endpoints/SearchEndpoints.cs -g '*.cs'
# Search Feature 当前不在统一 module map。
rg -n "search\.index|ModuleFeatureMap|\[\"search\"\]" src tests -g '*.cs'
# 当前没有 Search request rule;补齐后应出现每个高风险请求的规则和测试。
rg -n "IRequestRule<.*(UnifiedSearch|CheckConflict|RebuildIndex|Segment)" \
src/Platform/Search tests -g '*.cs'

返回 Search 总览 · 统一搜索与利冲 · 测试与 GA

100%

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