Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

History
670 lines (534 loc) · 25.8 KB

File metadata and controls

670 lines (534 loc) · 25.8 KB

FRKB API 速览(指纹 + 精选艺人,对接版)

本页面向客户端对接,所有字段与返回均与实现同步(以 src/routes/src/controllers/ 为准)。

全局信息

  • 指纹前缀:/frkbapi/v1/fingerprint-sync
  • 精选艺人前缀:/frkbapi/v1/curated-artist-sync
  • 鉴权:所有业务接口默认需要请求头 Authorization: Bearer <API_SECRET_KEY>,并携带 userKey
  • 请求头:Content-Type: application/json
  • 请求体大小:JSON 解析按环境变量 REQUEST_SIZE_LIMIT(默认 10MB),但额外校验当前限制为 10MB,超过将返回错误
  • 速率限制:全局基础限流(100次/分钟),敏感操作额外严格限流(10次/5分钟);响应包含标准限流头(RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  • 会话 TTL:差异会话有效期 5 分钟(SYNC_CONFIG.DIFF_SESSION_TTL
  • 指纹规范:64 位十六进制(SHA256),小写;数组必须去重,否则请求会因重复项被拒绝
  • 批量大小:BATCH_SIZE 源自服务端配置(环境变量 BATCH_SIZE,默认 1000)
  • 指纹总量上限:默认每个 userKey 最多 200,000 条,可由管理员为单个 userKey 调整
  • 精选艺人快照规范:
    • artists 为轻量全量快照,元素结构 { name, count, fingerprints? }
    • name 以“去首尾空格 + 压缩中间空白 + 小写归一化”作为合并键
    • fingerprints 可选,但强烈建议携带;服务端会按指纹并集去重后反推 count 下限,避免跨设备计数越合越歪

1) 同步预检查(指纹)

  • 方法与路径:POST /check
  • 认证:需要 API 密钥 + userKey(body)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
countbodyinteger>= 0客户端指纹集合总数
hashbodystring64 位十六进制(SHA256)客户端集合哈希
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean是否需要继续同步
reasonstring判定原因:already_synced/count_mismatch/hash_mismatch/server_empty/client_empty/sync_in_progress
messagestring友好提示
serverCountnumber服务端指纹数量
serverHashstring服务端集合哈希(SHA256)
clientCountnumber回显客户端数量
clientHashstring回显客户端哈希
lastSyncAtstring上次同步时间
limitnumberuserKey 的指纹上限(条)
performanceobject性能指标(毫秒)
timestampstring服务端时间戳

1a) 校验 userKey(只读)

  • 方法与路径:POST /validate-user-key
  • 认证:需要 API 密钥(无需 userKey 权限检查)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4需验证的用户标识
  • 成功返回字段:
字段类型说明
successboolean是否成功
data.userKeystring标准化后的 userKey
data.isActiveboolean是否激活
data.permissionsobject已移除(仅保留启用/禁用状态)
data.descriptionstring描述信息
data.lastUsedAtstring/null最近使用时间
limitnumber当前 userKey 的指纹总量上限(只读)
performanceobject{ validateDuration }
timestampstring时间戳

说明:

  • 该端点只做“格式 + 白名单可用性”只读校验,不写入统计。

2) 双向差异检测(分批,指纹)

  • 方法与路径:POST /bidirectional-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重当前批次指纹列表
batchIndexbodyinteger>= 0批次索引(从 0 开始)
batchSizebodyinteger1..BATCH_SIZE每批大小
  • 成功返回字段(命名以实现为准):
字段类型说明
successboolean是否成功
batchIndexnumber批次索引
batchSizenumber批大小
serverMissingFingerprintsstring[]服务端缺失(客户端需“推送到服务端”)
serverExistingFingerprintsstring[]服务端已存在
countsobject统计信息:{ clientBatch, serverMissing, serverExisting }
sessionInfoobject/null仅在 batchIndex=0 且预估客户端缺失时返回,包含 sessionId 等信息,供后续分页拉取
bloomFilterStatsobject/null布隆过滤器统计(启用时)
performanceobject性能指标
timestampstring时间戳

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“服务器现有 + 客户端需新增”估算后超过上限,直接拒绝;details 包括 { phase: 'analyze_diff', limit, serverTotal, clientTotal, pendingAdd, finalTotal }

说明:此前文档中的 missingOnClient/missingOnServer 字段已更正为 serverMissingFingerprintsserverExistingFingerprints,请以此为准。

错误(与本端点相关的新增):

  • 400 FINGERPRINT_LIMIT_EXCEEDED:若“当前服务端数量 + 本批待新增数”将超过上限,则直接拒绝;details 包括 { phase: 'bidirectional_diff', limit, currentServerCount, requestedAddCount, allowedAddCount }

3) 批量新增(指纹)

  • 方法与路径:POST /add
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
addFingerprintsbodystring[]1..BATCH_SIZE;每项 64 位十六进制;去重待新增指纹列表
  • 成功返回字段:
字段类型说明
successboolean是否成功
addedCountnumber新增条数
duplicateCountnumber已存在条数
totalRequestednumber请求条数
batchResultobject简要统计
performanceobject性能指标
timestampstring时间戳

说明:

  • 口径:duplicateCount 仅统计“服务器已存在”的重复;若请求体内存在重复指纹,服务端将以 400 校验错误直接拒绝(客户端需在提交前去重)。
  • 上限:若“当前服务端数量 + 本次唯一新增数”将超过上限,返回 400 FINGERPRINT_LIMIT_EXCEEDEDdetails 包括 { phase: 'batch_add', limit, currentCount, uniqueNewCount, allowedAddCount }

4) 一次性差异分析(会话,指纹)

  • 方法与路径:POST /analyze-diff
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(较重操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
clientFingerprintsbodystring[]0..100000;64 位十六进制客户端完整指纹集合(允许为空用于全量拉取)
  • 成功返回字段:
字段类型说明
successboolean是否成功
diffSessionIdstring差异会话 ID(用于分页拉取)
diffStatsobject{ clientMissingCount, serverMissingCount, totalPages, pageSize }
serverStatsobject{ totalFingerprintCount, clientCurrentCount }
recommendationsobject同步建议
performanceobject性能指标
timestampstring时间戳

5) 分页拉取差异(指纹)

  • 方法与路径:POST /pull-diff-page
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:全局基础限流(100次/分钟)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
diffSessionIdbodystring/^diff_[a-z0-9_]+$/i会话 ID(来自 /analyze-diff
pageIndexbodyinteger>= 0从 0 开始
  • 成功返回字段:
字段类型说明
successboolean是否成功
sessionIdstring会话 ID
missingFingerprintsstring[]本页需要“拉取到客户端”的指纹
pageInfoobject{ currentPage, pageSize, totalPages, hasMore, totalCount }
performanceobject性能指标
timestampstring时间戳

默认分页大小:SYNC_CONFIG.DEFAULT_PAGE_SIZE = 1000

说明:

  • 分页集合基于 /analyze-diffmissingInClient 结果;同一 diffSessionId 内分页顺序稳定;页内按 fingerprint 升序。
  • 会话过期/不存在:返回 404,错误码 DIFF_SESSION_NOT_FOUND(响应体可包含 retryAfter 秒数提示需重新执行 /analyze-diff)。

6) 同步状态

  • 方法与路径:GET /status?userKey=...
  • 认证:需要 API 密钥 + userKey(query)
  • 成功返回字段:
字段类型说明
successboolean是否成功
userKeystring标准化后的 userKey
syncStatusobject/null当前同步锁信息(若有)
userMetaobject/null缓存的用户集合元数据
bloomFilterStatsobject/null布隆过滤器统计
timestampstring时间戳

7) 服务统计

  • 方法与路径:GET /service-stats
  • 认证:仅需要 API 密钥(无需 userKey
  • 成功返回(示意):
{
"success": true,
"stats": {
"activeSessions": 0,
"syncLocks": 0,
"cacheStats": { "enabled": true, "size": 0, "hitRate": "0%" },
"bloomFilterStats": { "enabled": true, "totalFilters": 0 }
},
"timestamp": "2024-01-01T00:00:00.000Z"
}

8) 清除用户缓存

  • 方法与路径:DELETE /cache/:userKey
  • 认证:需要 API 密钥 + 同步权限;调用方需携带“自身 userKey”(body 或 query 中)以通过认证,路径参数为“目标用户”
  • 成功返回字段:{ success, message, clearedItems: { cache, bloomFilter }, timestamp }

9) 强制释放同步锁(管理员)

  • 方法与路径:DELETE /lock/:userKey
  • 认证:仅需 adminToken(query),匹配环境变量 ADMIN_SECRET_TOKEN;不需要 API 密钥
  • 成功返回字段:{ success, message, previousLock, timestamp }

10) 重置用户数据(不重置使用统计)

  • 方法与路径:POST /reset
  • 认证:需要 API 密钥 + userKey(body)
  • 速率限制:严格限流(敏感操作)
  • 请求体字段:
字段位置类型必填约束说明
userKeybodystringUUID v4目标用户标识
notesbodystring≤500 字重置备注(将写入 AuthorizedUserKey.notes
  • 成功返回字段:
字段类型说明
successboolean是否成功
messagestring固定为“userKey数据已重置”
userKeystring标准化后的 userKey
before.fingerprintCountnumber重置前指纹条数
before.metaCountnumber重置前元数据记录条数
before.usageStats.totalRequestsnumber使用统计(仅回显,不会被清零)
before.usageStats.totalSyncsnumber使用统计(仅回显,不会被清零)
result.clearedFingerprintsnumber实际删除的指纹条数
result.clearedMetasnumber实际删除的元数据条数
result.deletedSessionsnumber删除的差异会话数
result.clearedCachenumber清理的缓存项数
timestampstring时间戳
  • 成功请求示例:
curl -X POST "$BASE_URL/frkbapi/v1/fingerprint-sync/reset" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "notes": "客户端发起重置" }'
  • 说明:

  • 该操作将删除该 userKey 的全部指纹数据与元数据,清理相关缓存与持久化差异会话;但不会重置 AuthorizedUserKey.usageStats

  • 若存在精选艺人快照,也会一并删除。

  • 调用方需携带有效 API_SECRET_KEY,并在 body 中提供有效 userKey

  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_USER_KEYuserKey 缺失或格式不合法
    • 404 USER_KEY_NOT_FOUND:白名单不存在该 userKey
    • 403 USER_KEY_INACTIVE:该 userKey 已被禁用
    • 429 STRICT_RATE_LIMIT_EXCEEDED:敏感操作触发严格限流(含 retryAfter 秒)
    • 400 REQUEST_TOO_LARGE:请求体超过 10MB
    • 500 INTERNAL_ERROR / AUTH_ERROR:服务端内部错误或认证异常
  • 错误响应(示例):

{
"success": false,
"error": "STRICT_RATE_LIMIT_EXCEEDED",
"message": "敏感操作请求过于频繁,请稍后再试",
"details": { "windowMs": 300000, "maxRequests": 10, "retryAfter": 300 },
"timestamp": "2025-01-01T00:00:00.000Z"
}

错误响应(统一)

{
"success": false,
"error": "ERROR_CODE",
"message": "错误描述",
"details": { "...": "..." },
"timestamp": "ISO8601"
}

常见错误码:

  • INVALID_API_KEYINVALID_USER_KEY
  • RATE_LIMIT_EXCEEDEDSTRICT_RATE_LIMIT_EXCEEDED
  • VALIDATION_ERRORINVALID_FINGERPRINT_FORMATREQUEST_TOO_LARGE
  • DIFF_SESSION_NOT_FOUND(会话过期/不存在)、INTERNAL_ERROR
  • FINGERPRINT_LIMIT_EXCEEDED(指纹总量超过上限)
  • INVALID_CURATED_ARTIST_SNAPSHOT(精选艺人快照声明与归一化结果不一致)

HTTP 状态:200/400/401/403/404/409/429/500(与实现中的错误处理中间件一致)。


对接建议

  • 先调用 /check 决定是否继续
  • 批量大小建议 1000;失败使用指数退避重试;支持断点续传
  • 保证指纹数组去重与格式合法(64 位十六进制 SHA256),避免被后端拒绝
  • 关注响应限流头与 performance 字段,适当调节并发与批大小
  • 精选艺人同步建议始终携带 fingerprints,否则跨设备只能按 count 取较大值,无法严格还原每次来源

11) 精选艺人快照同步(轻量)

  • 方法与路径:POST /frkbapi/v1/curated-artist-sync/sync

  • 认证:需要 API 密钥 + userKey(body)

  • 适用场景:同步 FRKB 客户端“用户喜欢的艺人”轻量数据;数据量远小于全量指纹,服务端直接按全量快照做归一化与合并

  • 请求体字段:

字段位置类型必填约束说明
userKeybodystringUUID v4同步用户标识
artistsbodyobject[]0..5000客户端精选艺人快照
artists[].namebodystring去空白后非空,≤ 200 字符艺人名
artists[].countbodynumber> 0当前艺人累计次数
artists[].fingerprintsbodystring[]每项 64 位十六进制 SHA256贡献该艺人计数的原始指纹集合
countbodyinteger>= 0客户端声明的归一化艺人数;若提供,必须与服务端归一化结果一致
hashbodystring64 位十六进制 SHA256客户端声明的快照哈希;若提供,必须与服务端归一化结果一致
  • 成功返回字段:
字段类型说明
successboolean是否成功
needSyncboolean客户端提交前是否与服务端存在差异
changedboolean本次请求是否写入并更新了服务端快照
reasonstringalready_synced / server_empty / client_empty / merged / client_outdated
messagestring友好提示
clientSnapshotobject客户端归一化后的快照统计与数据
serverSnapshotBeforeobject合并前服务端快照统计与数据
mergedSnapshotobject合并后的权威快照;客户端应使用它覆盖本地
performanceobject性能指标
timestampstring服务端时间戳
  • clientSnapshot / serverSnapshotBefore / mergedSnapshot 字段:
字段类型说明
artistCountnumber艺人数
totalCountnumber所有艺人 count 之和
fingerprintCountnumber所有关联原始指纹数量之和
hashstring归一化快照哈希
lastSyncAtstring/null上次服务端同步时间(仅服务端快照返回)
itemsobject[]快照明细:{ name, count, fingerprints }
  • 合并规则(实现口径):

    • 先按归一化艺人名合并。
    • fingerprints 做并集去重。
    • countmax(已有count, 新count, fingerprints.length)
    • 若客户端只是旧子集,则服务端不会改写,但会把最新快照回给客户端。
  • 成功请求示例:

curl -X POST "$BASE_URL/frkbapi/v1/curated-artist-sync/sync" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "userKey": "550e8400-e29b-41d4-a716-446655440000", "artists": [ { "name": "Daft Punk", "count": 3, "fingerprints": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ] } ] }'

附:健康接口速览(无业务鉴权)

  • 基础健康:GET /health(无需鉴权)。返回进程与数据库连通状态;非 200 视为不健康。
  • 详细健康:GET /frkbapi/v1/health/detailed。返回组件健康、内存/CPU、耗时等。
  • 系统统计:GET /frkbapi/v1/health/stats。返回数据库、运行时与服务统计。
  • 诊断接口:GET /frkbapi/v1/health/diagnose(严格限流,需 adminToken)。返回诊断与建议。

错误日志上报

  • 方法与路径:POST /frkbapi/v1/error-report/upload

  • 认证:需要 API 密钥(无需 userKey

  • 速率限制:严格限流(5 次/5 分钟,按 IP 计数)

  • 请求头:Content-Type: text/plain,且 User-Agent 必须为 node(或以 node/ 开头)

  • 请求体:纯文本错误日志内容(文本文件原样内容),不需要 JSON;默认 ≤ 50MB(可通过 ERROR_REPORT_MAX_SIZE 调整)。

  • 成功返回字段:

字段类型说明
successboolean是否成功
idstring服务器生成的错误记录 ID
messagestring固定为“错误日志已保存”
timestampstring时间戳
  • 可能的错误:

    • 401 INVALID_API_KEY:缺少/格式错误/无效的 Authorization 头
    • 400 INVALID_REPORT_PAYLOAD:缺少 message/stack
    • 429 CUSTOM_RATE_LIMIT_EXCEEDED:触发严格限流
    • 500 WRITE_REPORT_FAILED:服务器保存失败
    • 403 INVALID_CLIENTUser-Agent 非 Node(视为非法来源)
  • 服务器行为:

    • 上报将被保存为独立 .log 文件,目录 logs/error-reports/(可通过 ERROR_REPORT_DIR 配置)。
    • 文件名:YYYY-MM-DDTHH-mm-SS-sss_UUID.log
    • 不回显敏感字段,仅返回记录 id

快速开始(客户端最小示例)

以下以 BASE_URL 表示服务器地址(如 http://localhost:3001),统一前缀 PREFIX=/frkbapi/v1/fingerprint-sync

  • 必备请求头:
Authorization: Bearer <API_SECRET_KEY>Content-Type: application/json
  • fetch 示例:
constBASE_URL='http://localhost:3001';constPREFIX='/frkbapi/v1/fingerprint-sync';constAPI_SECRET_KEY='<your-api-secret-key>';asyncfunctionpost(path,body){constres=awaitfetch(`${BASE_URL}${PREFIX}${path}`,{method: 'POST',headers: {Authorization: `Bearer ${API_SECRET_KEY}`,'Content-Type': 'application/json'},body: JSON.stringify(body)});returnres.json();}// 1) 预检查constcheck=awaitpost('/check',{ userKey, count, hash });if(!check.success)thrownewError(check.message);// 2) 若需要同步,按 1000 批次进行双向差异(示意)constbatchSize=1000;for(leti=0;i<clientFingerprints.length;i+=batchSize){constbatch=clientFingerprints.slice(i,i+batchSize);constdiff=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: batch,batchIndex: Math.floor(i/batchSize),
batchSize
});// 将 diff.serverMissingFingerprints 聚合,稍后统一 /add 推送到服务端}// 3) 可选择一次性差异+分页拉取客户端缺失constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letpage=0;page<analysis.diffStats.totalPages;page++){constpageRes=awaitpost('/pull-diff-page',{
userKey,diffSessionId: analysis.diffSessionId,pageIndex: page});// 将 pageRes.missingFingerprints 合入本地集合}// 4) 推送服务端缺失consttoAdd=aggregateAllServerMissing();for(leti=0;i<toAdd.length;i+=batchSize){constaddRes=awaitpost('/add',{ userKey,addFingerprints: toAdd.slice(i,i+batchSize)});}
  • axios 示例:
importaxiosfrom'axios';constapi=axios.create({baseURL: 'http://localhost:3001/frkbapi/v1/fingerprint-sync',headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});const{data: check}=awaitapi.post('/check',{ userKey, count, hash });
  • curl 示例:
curl -X POST \
"$BASE_URL/frkbapi/v1/fingerprint-sync/check" \
-H "Authorization: Bearer $API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"userKey":"...","count":12345,"hash":"<64hex>"}'

各端点最小调用示例

以下仅列出关键示例,参数定义仍以上方端点小节为准。

  • POST /check(fetch)
awaitpost('/check',{ userKey, count, hash });
  • POST /bidirectional-diff(fetch)
awaitpost('/bidirectional-diff',{ userKey,clientFingerprints: batch, batchIndex, batchSize });
  • POST /add(fetch)
awaitpost('/add',{ userKey, addFingerprints });
  • POST /analyze-diff + /pull-diff-page(fetch)
consta=awaitpost('/analyze-diff',{ userKey, clientFingerprints });constp0=awaitpost('/pull-diff-page',{ userKey,diffSessionId: a.diffSessionId,pageIndex: 0});
  • GET /status(fetch)
constres=awaitfetch(`${BASE_URL}${PREFIX}/status?userKey=${encodeURIComponent(userKey)}`,{headers: {Authorization: `Bearer ${API_SECRET_KEY}`}});constdata=awaitres.json();

重试与幂等策略(客户端建议)

  • 幂等:
    • /add 对已存在的指纹不会重复创建(返回 duplicateCount 统计),可按批次安全重试。
    • 差异计算与分页拉取为读操作,重试安全。
  • 重试建议:
    • 网络/5xx/429:指数退避(如 1s、2s、4s,上限 30s),最多 3-5 次。
    • 400/401/403:修正参数或鉴权后再发起,不要盲目重试。
  • 批处理:
    • 建议 batchSize=1000,失败仅重试失败批次。

速率限制与响应头

  • 服务端启用标准 RateLimit 响应头(如 RateLimit-LimitRateLimit-RemainingRateLimit-Reset)。
  • 触发限流时,响应 JSON 会包含 retryAfter 秒数提示;也可能返回 Retry-After 头。
  • 限流策略:
    • 常规接口(同步、查询、健康等):全局基础限流(100次/分钟)
    • 敏感操作(/analyze-diff、缓存清理、锁管理、系统诊断):额外严格限流(10次/5分钟)

常见错误码与处理建议

错误码HTTP说明客户端处理
INVALID_API_KEY401API 密钥缺失/错误校验并重新配置密钥
INVALID_USER_KEY400/404userKey 格式无效或不存在修正 userKey 或联系管理员发放
RATE_LIMIT_EXCEEDED / STRICT_RATE_LIMIT_EXCEEDED429触发限流retryAfter 或指数退避重试,降低并发/批量
INVALID_FINGERPRINT_FORMAT / VALIDATION_ERROR400参数校验失败修正参数;确保指纹去重且为 64 位十六进制(SHA256)
DIFF_SESSION_NOT_FOUND400/404差异会话过期/不存在重新执行 analyze-diff 并继续分页
INTERNAL_ERROR500服务器内部错误记录请求,指数退避重试;若持续失败联系服务端

注:实际 HTTP 状态以响应为准;生产环境可能隐藏 debug 字段。


典型同步流程(伪代码)

asyncfunctionsyncAll(userKey,clientFingerprints){consthash=sha256OfSet(clientFingerprints);constcheck=awaitpost('/check',{ userKey,count: clientFingerprints.length, hash });if(!check.success||!check.needSync)return;// A. 服务端缺什么 → /bidirectional-diff 分批找出 → /add 推给服务端constbatchSize=1000;constserverMissing=[];for(leti=0;i<clientFingerprints.length;i+=batchSize){const{ serverMissingFingerprints }=awaitpost('/bidirectional-diff',{
userKey,clientFingerprints: clientFingerprints.slice(i,i+batchSize),batchIndex: Math.floor(i/batchSize),
batchSize
});serverMissing.push(...serverMissingFingerprints);}for(leti=0;i<serverMissing.length;i+=batchSize){awaitpost('/add',{ userKey,addFingerprints: serverMissing.slice(i,i+batchSize)});}// B. 客户端缺什么 → /analyze-diff → /pull-diff-page 拉齐constanalysis=awaitpost('/analyze-diff',{ userKey, clientFingerprints });for(letp=0;p<analysis.diffStats.totalPages;p++){constpage=awaitpost('/pull-diff-page',{ userKey,diffSessionId: analysis.diffSessionId,pageIndex: p});clientFingerprints=union(clientFingerprints,page.missingFingerprints);}returnclientFingerprints;}