Open Platform
受众:开发者 摘要:开放平台:第三方应用 / AK/SK / 签名验签 / 防重放 / scope / 限流 / 调用日志 / 开放 API。
Open Platform 是开放能力聚合模块,用于把框架内部能力通过统一安全边界开放给第三方开发者、外部系统或合作伙伴。
它属于可选模块,不是框架必须依赖。默认应用为了展示完整能力会引入该模块;如果项目不需要对外开放 API,可以直接移除或注释 spring-open-application 中的 spring-open-module-open-platform 依赖,不应影响 core、starter、Web3、Payment、Notification 等其他模块正常运行。
它不是 Web3 专用模块。Web3 只是第一阶段能力域,后续聚合支付、通知、存储、AI 等能力也会通过同一开放平台边界接入。
模块
spring-open-modules/spring-open-module-open-platform包名:
com.springopen.module.openplatform当前模块已建立数据底座、签名工具、安全过滤器、后台管理 API 和 Web3 v1 开放 API,并已接入 spring-open-application。默认应用会暴露 /api/v1/open/**,但所有开放能力都需要有效应用、密钥、签名、防重放、scope 和限流校验。
路由
开放 API 统一使用版本化路由:
/api/v1/open/**后台管理 API 统一使用:
/api/v1/admin/open-platform/**开发者用户控制台 API 统一使用:
/api/v1/open/console/**开放平台是外部契约,所有对外能力必须带版本号。当前第一版使用 v1,后续如果出现破坏性语义变化,再新增 v2、v3;同一版本内只做向后兼容的字段新增或非破坏性增强。
设计边界
- Open Platform 是聚合出口,不是基础依赖;其他模块不能反向依赖 Open Platform。
- Open Platform 不直接引入链 SDK。
- Web3 能力通过
spring-open-starter-web3的 facade / manager 复用。 - 支付能力通过
spring-open-starter-payment复用。 - 通知、存储、AI 等能力通过各自 starter 或业务 service 复用。
- 第三方 SDK、私钥、签名材料和底层 provider 实现不能泄露到开放 API 层。
配置
spring:
open:
open-platform:
enabled: true
security:
filter-enabled: true
timestamp-tolerance: 5m
nonce-ttl: 10m
ip-whitelist-enabled: true
default-rate-limit-per-minute: 60
allowed-signature-methods:
- HMAC-SHA256
secret:
secret-key-prefix: key_
plain-secret-prefix: sk-
encryption-key: ${SPRING_OPEN_OPEN_PLATFORM_SECRET_ENCRYPTION_KEY:${spring.application.secret:}}说明:
| 配置 | 默认值 | 说明 |
|---|---|---|
enabled | true | 是否启用开放平台模块 |
security.filter-enabled | true | 是否启用开放平台访问过滤器;当前业务接口使用 /api/v1/open/** |
security.timestamp-tolerance | 5m | 请求时间戳允许偏差 |
security.nonce-ttl | 10m | nonce 防重放有效期 |
security.ip-whitelist-enabled | true | 是否启用应用 IP 白名单检查 |
security.default-rate-limit-per-minute | 60 | 应用未单独配置限流时的默认每分钟调用次数 |
security.default-free-trial-daily-quota | 10000 | 应用没有可用套餐且未配置应用级日配额时的默认免费试用日额度;设为 0 或负数可关闭 |
security.default-free-trial-monthly-quota | 100000 | 应用没有可用套餐且未配置应用级月配额时的默认免费试用月额度;设为 0 或负数可关闭 |
security.allowed-signature-methods | HMAC-SHA256 | 允许的签名算法 |
secret.secret-key-prefix | key_ | 内部密钥记录标识前缀;默认贴近主流平台 API Key 对象 ID 习惯,可配置为空字符串 |
secret.plain-secret-prefix | sk- | 明文 API Key 前缀;默认贴近主流开发者工具习惯,可配置为空字符串 |
secret.encryption-key | ${spring.application.secret:} | AI 服务商 数据库覆盖 API Key、应用 Webhook 签名密钥等需要可逆读取的敏感配置加密 key;生产建议使用 SPRING_OPEN_OPEN_PLATFORM_SECRET_ENCRYPTION_KEY 单独配置。未显式配置时启动期按 显式专用 → 数据库 spring.open.open-platform.secret.encryption-key → 数据库共享 spring.application.secret(首启自动播种,多实例共库收敛一致)→ 本地派生 顺序收敛;更换生效密钥会使已加密的存量数据不可解密,需重新录入 |
上述 Open Platform 模块启停、安全过滤、限流 / 免费额度和 Secret 前缀 / 加密 key 配置已由 OpenPlatformServiceProvider 声明到 lifecycle 配置 schema。后台配置中心可以发现这些配置;secret.encryption-key 会标记为敏感配置。
AI 服务商 上游 API Key 的数据库覆盖值和开放平台应用 Webhook 签名密钥会通过 spring-open-core-secret 的 SecretManager 写入 secret:v1: 可逆 Secret envelope;默认使用内部 local 服务商 和 spring.open.open-platform.secret.encryption-key,历史明文与旧 enc:v1: 密文仍可读取,下一次保存会写回新 envelope。通用 Secret 底座同时提供内部 byok 服务商,以及显式注册才启用的 KMS / Vault 服务商 SPI;框架不会默认启动第三方 KMS / Vault 服务商实现。需要 AWS KMS、Aliyun KMS 或 HashiCorp Vault Transit 时,显式引入 spring-open-starter-secret-sdk 并手动注册对应 SecretKmsClient / SecretVaultClient。开放平台应用 AppSecret 不走可逆加密,服务端只保存 secret_hash、secret_hint 和 masked_secret,创建 / 轮换响应中的明文只返回一次。
当前能力
开放平台基础:
- 开发者应用
- 应用套餐
- AK/SK 凭证
- 请求签名验签
- timestamp / nonce 防重放
- IP 白名单
- scope 权限范围
- 调用日志
- 计量事件
- 日 / 月配额
- 基础限流
- 应用 Webhook 回调端点配置
Web3 v1 开放能力:
- 链信息列表与链信息查询
- 原生币余额查询
- 批量原生币余额查询
- ERC-20 余额查询
- ERC-20 授权额度查询
- ERC-20 Token 信息查询
- Gas Price、Nonce 和 Gas 预估
- 交易和交易回执查询
- 批量交易确认数查询
- 交易确认数查询
- 交易 input 解码
- 事件日志查询
- Token 价格查询
- 批量转账计划预览,不接收私钥、不签名、不广播
- 批量转账真实签名,可选广播
- 资金归集计划预览,不接收私钥、不签名、不广播
- 资金归集真实签名,可选广播
Voucher 开放能力:
- 查询业务消费方和持有人当前可用卡券
- 业务结算前锁定一张卡券
- 业务成功后确认核销
- 业务取消、失败或超时时释放锁定
写交易开放接口默认不广播,只返回签名后的 rawTransaction;只有显式传入 broadcast=true 才广播到链上。生产使用必须经过 scope、签名、幂等、审计、风控和可选人工审批。
安全模型
开放平台第一阶段采用 AppKey + AppSecret + 签名 + 防重放 + scope 的轻量模型,适合第三方服务端调用。
AI 模型网关已由独立 spring-open-module-ai 承载,使用 AI 模块原生 Gateway Key、scope、限流、quota、用量事件和 服务商 健康链路,不再作为 Open Platform scope 的例外。Claude Code、CC Switch、OpenAI SDK 等开发者工具接入 AI 网关时,请使用后台 AI 平台创建的 sk-ai-... Gateway Key;Web3、支付、卡券等开放能力继续使用下面的 AppKey + AppSecret + HMAC 签名模式。
推荐请求头:
X-Open-App-Key: app_xxx
X-Open-Timestamp: 1779379200000
X-Open-Nonce: random-string
X-Open-Signature: hex-or-base64-signature
X-Open-Signature-Method: HMAC-SHA256
X-Open-Idempotency-Key: optional-business-idempotency-key限流响应头:
X-Open-RateLimit-Limit: 60
X-Open-RateLimit-Remaining: 59
X-Open-RateLimit-Reset: 1779408060
Retry-After: 12X-Open-RateLimit-Reset 使用 Unix 秒级时间戳。Retry-After 只在被限流时返回,表示建议多少秒后重试。应用不限流时不会返回这些限流响应头。
签名范围:
method
path
query string
timestamp
nonce
body sha256当前签名计算方式:
signingKey = SHA-256(AppSecret)
signature = HMAC-SHA256(canonicalString, signingKey)说明:
- 创建密钥接口只返回一次明文
AppSecret。 - 服务端数据库保存的是
SHA-256(AppSecret),当前第一版不会保存明文密钥。 - 客户端应先计算
SHA-256(AppSecret)作为签名 key,再对 canonical string 做 HMAC-SHA256。 - 后续如接入 KMS 或密文密钥列,可以在不改变外部安全模型的前提下升级服务端密钥存储方式。
设计原则:
AppSecret只用于服务端签名,不允许出现在前端页面、小程序或移动端源码中。- timestamp 默认只允许短时间窗口内有效。
- nonce 在有效窗口内只能使用一次,避免重放。
- scope 用于控制应用可访问的能力,统一使用
open:<capability>:<resource>:<action>风格,例如open:web3:balance:read、open:web3:transaction:read、open:payment:order:create。 - 调用日志必须记录 app、scope、path、traceId、耗时、结果 code 和脱敏后的请求来源。
- 幂等 key 只用于创建类、写入类或资金类接口;只读查询不强制要求。
后续如需面向浏览器、小程序或移动 App 暴露能力,应增加 OAuth2/OIDC、短期 token 或服务端代理模式,不应直接下发长期 AppSecret。
快速接入
典型接入流程:
- 开发者在用户控制台创建自己的开放平台应用,或由后台创建应用。
- 为应用生成密钥,保存本次响应中的
AppSecret。 - 为应用授权需要的 scope,例如
open:web3:balance:read。 - 客户端服务端按签名规则生成请求头。
- 调用
/api/v1/open/**开放 API。 - 通过调用日志查看结果、耗时、来源 IP、traceId 和失败原因。
开放平台适合“服务端调用服务端”。不要把 AppSecret 放到浏览器、小程序或移动 App 中;如果要给前端使用,应由自己的后端代理签名。
开发者控制台接入清单
开发者第一次接入时,建议按下面清单逐项确认,避免把 AI Gateway Key 和通用开放 API 签名模式混用:
| 步骤 | 要做什么 | 关键点 |
|---|---|---|
| 1 | 创建开放平台应用 | 用户控制台或后台都可以创建;应用归属用于隔离 Key、用量、账单和配额 |
| 2 | 生成应用密钥 | 明文只返回一次;通用开放 API 使用 AppKey + AppSecret 计算签名 |
| 3 | 授权 scope | Web3 按接口授权 open:web3:*;支付、卡券、广告等按对应开放能力授权 |
| 4 | 选择调用模式 | Web3 / 支付 / 卡券等开放 API 走 HMAC 签名请求头;AI 网关使用 AI 平台的 Gateway Key,见 AI 模型网关 |
| 5 | 复制 baseURL | OpenAI Compatible、Claude / CC Switch、Codex / Responses API 的 baseURL 不同,见 AI 模型网关 |
| 6 | 购买套餐并查账 | 用户控制台可查看可售套餐、购买预览、支付激活套餐订单、账务流水和月度账单快照 |
| 7 | 排障 | 响应中的 traceId 可到后台调用日志、AI 用量事件、服务商 健康和账务流水中串联排查 |
两种鉴权模式的区别
| 能力 | 适合场景 | 请求头 | 说明 |
|---|---|---|---|
| AI Gateway Key | Claude Code、CC Switch、OpenAI SDK、Codex-like 客户端、Cursor、Continue 等开发者工具 | Authorization: Bearer sk-ai-... 或 x-api-key: sk-ai-... | 由 AI 模块后台创建,只对 /api/v1/ai/gateway/** 生效,scope 使用 ai:gateway:* |
| Open Platform HMAC | Web3、支付、卡券、广告、内容访问、业务服务端调用开放 API | X-Open-App-Key、X-Open-Timestamp、X-Open-Nonce、X-Open-Signature-Method、X-Open-Signature | 适合服务端到服务端,前端页面不应持有长期 AppSecret |
如果调用 AI 网关接口却传了 HMAC 头,或者调用 Web3 接口却只传 sk-...,都会按接口 scope 和鉴权规则返回未认证或未授权错误。
聚合支付开放 API 使用同一套 HMAC 签名规则;固定下单样例、body hash、canonical string 和 Webhook 验签夹具见 聚合支付平台。
卡券开放 API 使用同一套 HMAC 签名规则,路由前缀为 /api/v1/open/vouchers,scope 为 open:voucher:available、open:voucher:lock 和 open:voucher:redeem。接口列表和请求体见 卡券。
广告开放 API 使用同一套 HMAC 签名规则,路由前缀为 /api/v1/open/advertising,scope 为 open:advertising:placement:read 和 open:advertising:event:write。接口列表和请求体见 自营广告。
内容访问检查开放 API 使用同一套 HMAC 签名规则,路由为 POST /api/v1/open/entitlement/content-access/check,scope 为 open:entitlement:content-access:read。该接口只复用 Entitlement 的中性内容访问策略返回可读性、权益凭证和会员折扣资格,不创建解锁凭证、不写购买订单、不替代 Blog / Forum / Reading / Video 自己的支付、积分扣减和退款状态机。接口字段见 权益与套餐。
应用 Webhook 回调端点
后台应用新增 Webhook 回调端点配置,用于把开放平台应用和 Runtime Webhook 投递底座连接起来。当前字段属于应用级运维配置,不改变开放 API 的 HMAC 鉴权规则:
| 应用字段 | Runtime Webhook header | 说明 |
|---|---|---|
webhookEnabled | webhook.enabled | 是否启用该应用的回调端点 |
webhookEndpointKey | webhook.endpoint-key | 命名 endpoint key,优先匹配 Runtime Webhook endpoint resolver |
webhookUrl | webhook.endpoint | 直接回调 URL,用于没有命名 endpoint 的轻量场景 |
webhookSigningEnabled | webhook.signing-enabled | 是否对投递请求生成 Webhook 签名 |
webhookSecret | webhook.secret | 写入时接收明文,服务端用 SecretManager 可逆保护后保存,详情 / 列表不返回明文 |
webhookSecret 是 write-only 字段。创建或更新时填写会替换现有密钥;留空表示不修改原密钥,不会清空已保存密钥。后台详情和列表只返回 hasWebhookSecret、maskedWebhookSecret、endpoint key / URL、启用状态和签名状态。Runtime 侧需要投递时,由 Open Platform Service 解密后生成标准 Webhook headers,业务 Controller 和 Admin 页面不会接触明文密钥。
创建套餐
POST /api/v1/admin/open-platform/packages
Content-Type: application/json
{
"code": "basic",
"name": "基础套餐",
"status": 1,
"dailyQuota": 10000,
"monthlyQuota": 300000,
"giftQuota": 1000,
"validityDays": 30,
"unitPrice": 0,
"currency": "CNY",
"priceTiers": [
{
"name": "标准阶梯",
"minUnitCount": 0,
"maxUnitCount": 1000000,
"unitPrice": 0.01,
"currency": "CNY",
"sortOrder": 1
}
]
}套餐用于声明默认日 / 月调用额度、赠送额度、有效期和后续计费单价。应用单独配置 dailyQuota / monthlyQuota 时优先使用应用配置;应用未配置时回退到绑定套餐额度;没有可用套餐时回退到 security.default-free-trial-daily-quota / security.default-free-trial-monthly-quota,让新开发者先用免费额度调通网关。阶梯价格用于运营配置和价格目录展示;用户控制台购买、续费和补款订单会绑定当前用户拥有的应用,支付动作会写入开放平台账务 recharge 流水,并把套餐与额度激活到目标应用。
创建应用
POST /api/v1/admin/open-platform/apps
Content-Type: application/json
{
"name": "Sample App",
"packageId": 1,
"status": "enabled",
"ipWhitelist": "127.0.0.1",
"rateLimitPerMinute": 60,
"dailyQuota": 10000,
"monthlyQuota": 300000
}packageId 为空时表示不绑定套餐。ipWhitelist 为空时表示不配置应用级白名单;如果全局启用白名单,生产环境建议填写固定出口 IP。rateLimitPerMinute 为 0 或负数表示该应用不限流,留空时使用全局默认限流。dailyQuota / monthlyQuota 为应用级配额覆盖值,空值、0 或负数表示不覆盖套餐额度;没有可用套餐时会继续使用默认免费试用额度。
创建密钥
POST /api/v1/admin/open-platform/apps/{appId}/secrets
Content-Type: application/json
{}响应中的明文 secret 只出现一次。后续列表和详情接口只返回 secretKey、maskedSecret、状态、状态原因、过期时间、禁用 / 轮换时间和最近使用时间,不返回明文。
用户控制台应用与密钥
开发者登录后可以通过用户侧控制台管理自己的应用和 Key。用户侧接口会按当前登录用户解析工作空间归属,默认个人开发者空间仍落到当前用户;后台管理接口走 /api/v1/admin/open-platform/** 和后台权限。
| 方法 | 路由 | 说明 |
|---|---|---|
GET | /api/v1/open/console/apps | 分页查询我的开放平台应用 |
GET | /api/v1/open/console/apps/{id} | 查询我的开放平台应用详情 |
POST | /api/v1/open/console/apps | 创建我的开放平台应用 |
PUT | /api/v1/open/console/apps/{id} | 更新我的开放平台应用,只能更新当前工作空间拥有的应用 |
DELETE | /api/v1/open/console/apps/{id} | 删除我的开放平台应用,只能删除当前工作空间拥有的应用 |
GET | /api/v1/open/console/apps/{appId}/usage | 查询我的应用套餐、日 / 月配额、已用量和剩余额度 |
GET | /api/v1/open/console/apps/{appId}/billing/ledgers | 查询我的应用账务流水 |
GET | /api/v1/open/console/apps/{appId}/billing/statements | 查询我的应用月度账单列表 |
GET | /api/v1/open/console/apps/{appId}/billing/statements/{billingMonth} | 查询我的应用指定月份账单详情 |
GET | /api/v1/open/console/packages | 查询可售套餐目录 |
GET | /api/v1/open/console/packages/{id} | 查询可售套餐详情 |
POST | /api/v1/open/console/packages/{id}/purchase-preview | 预览套餐购买金额和额度 |
POST | /api/v1/open/console/packages/{id}/purchase-orders | 创建套餐购买待支付订单 |
POST | /api/v1/open/console/packages/{id}/renew-orders | 创建套餐续费待支付订单 |
POST | /api/v1/open/console/packages/{id}/top-up-orders | 创建套餐补款待支付订单 |
GET | /api/v1/open/console/packages/purchase-orders | 分页查询我的套餐购买订单 |
GET | /api/v1/open/console/packages/purchase-orders/{orderNo} | 查询我的套餐购买订单详情 |
POST | /api/v1/open/console/packages/purchase-orders/{orderNo}/cancel | 取消我的待支付套餐订单 |
POST | /api/v1/open/console/packages/purchase-orders/{orderNo}/pay | 支付并激活我的待支付套餐订单 |
POST | /api/v1/open/console/packages/purchase-orders/{orderNo}/refund | 对已支付套餐订单发起退款状态动作 |
POST | /api/v1/open/console/packages/purchase-orders/{orderNo}/reverse | 对已支付或已退款套餐订单发起冲正状态动作 |
GET | /api/v1/open/console/apps/{appId}/secrets | 查询我的应用密钥列表 |
POST | /api/v1/open/console/apps/{appId}/secrets | 创建我的应用密钥,明文只返回一次 |
PUT | /api/v1/open/console/apps/{appId}/secrets/{secretId}/disable | 禁用我的应用密钥 |
POST | /api/v1/open/console/apps/{appId}/secrets/{secretId}/rotate | 轮换我的应用密钥,明文只返回一次 |
DELETE | /api/v1/open/console/apps/{appId}/secrets/{secretId} | 删除我的应用密钥 |
用户控制台返回的应用响应包含 ownerUserId、ownerTenantId、ownerOrganizationId、ownerDepartmentId 等归属字段,供后台调试和权限排查;普通页面不应展示内部归属字段给最终用户。应用更新、删除、用量、账务流水和账单接口都会先校验应用归属,只允许开发者操作或查看当前工作空间拥有的应用。请求体中的 id、appKey、归属字段不会改变服务端归属判定;后端始终以当前登录态解析出的 OpenPlatformWorkspaceContext 和数据库应用归属为准。
开放平台应用密钥治理分为两类:应用 AppSecret 继续 hash-only 保存,创建 / 轮换只返回一次明文;Webhook Secret、AI 服务商 上游 Key 等需要运行期读取的敏感值统一走 spring-open-core-secret 的 SecretManager envelope。内置实现支持 local / BYOK,KMS / Vault 通过显式 服务商 SPI 接入,不会默认启用第三方密钥服务。付费开放 API 的 scope、usage、quota 和 billing 以中性 Entitlement / Money contract 为准:Open Platform 负责开放 API 鉴权、scope、限流、计量事件和账务流水,Entitlement 负责 open-platform consumer 的套餐 / 配额与内容访问决策,Money / Cashier / Payment 负责资金事实与支付动作。
套餐目录接口只返回启用可售套餐和阶梯价格,用于购买前展示;购买预览接口只计算本次购买的阶梯单价、应付金额、额度合计和有效期,不创建购买订单、不扣费。购买、续费和补款订单接口会接收 unitCount 与 appId,先校验当前登录用户拥有该应用,再固化套餐、阶梯价、额度和应付金额快照,订单初始状态为 pending;pay 动作只允许待支付且未过期订单,会按订单金额写入应用账务账户 recharge 流水,随后把 packageId、日配额和“月配额 + 赠送额度”激活到该应用。订单分页和详情接口只返回当前登录用户自己的订单,前端可以继续展示待支付、已支付、已取消、已过期等状态。
授权 Scope
PUT /api/v1/admin/open-platform/apps/{appId}/scopes
Content-Type: application/json
{
"scopeCodes": [
"open:web3:network:read",
"open:web3:balance:read",
"open:web3:token:read",
"open:web3:transaction:read"
]
}PUT /scopes 是整体替换。传空数组会清空当前应用授权。
签名示例
签名使用原始请求信息生成 canonical string:
METHOD
PATH
QUERY_STRING
TIMESTAMP
NONCE
BODY_SHA256_HEX规则:
METHOD使用大写,例如GET、POST。PATH只包含路径,不包含协议、域名和端口。QUERY_STRING使用请求中的原始 query string;没有 query 时为空行。TIMESTAMP使用毫秒时间戳,默认允许 5 分钟偏差。NONCE在有效期内只能使用一次。BODY_SHA256_HEX是请求体字节的 SHA-256 hex;GET 空 body 使用空字符串的 SHA-256。signingKey = SHA-256(AppSecret)。signature = HMAC-SHA256(canonicalString, signingKey),输出 hex。
Node.js 示例:
import crypto from "node:crypto";
function sha256Hex(value) {
return crypto.createHash("sha256").update(value ?? "").digest("hex");
}
function signOpenRequest({ method, path, query = "", body = "", timestamp, nonce, appSecret }) {
const bodyHash = sha256Hex(body);
const canonical = [
method.toUpperCase(),
path.startsWith("/") ? path : `/${path}`,
query,
String(timestamp),
nonce,
bodyHash
].join("\n");
const signingKey = sha256Hex(appSecret);
return crypto.createHmac("sha256", signingKey).update(canonical).digest("hex");
}GET 请求示例:
const appKey = "app_xxx";
const appSecret = "secret_xxx";
const timestamp = Date.now();
const nonce = crypto.randomUUID();
const path = "/api/v1/open/web3/chains/bsc/balances/native";
const query = "address=0x0000000000000000000000000000000000000000";
const signature = signOpenRequest({
method: "GET",
path,
query,
timestamp,
nonce,
appSecret
});
const response = await fetch(`http://localhost:8000${path}?${query}`, {
headers: {
"X-Open-App-Key": appKey,
"X-Open-Timestamp": String(timestamp),
"X-Open-Nonce": nonce,
"X-Open-Signature": signature,
"X-Open-Signature-Method": "HMAC-SHA256"
}
});POST 请求示例:
const body = JSON.stringify({
addresses: [
"0x0000000000000000000000000000000000000000"
]
});
const path = "/api/v1/open/web3/chains/bsc/balances/native/batch";
const timestamp = Date.now();
const nonce = crypto.randomUUID();
const signature = signOpenRequest({
method: "POST",
path,
body,
timestamp,
nonce,
appSecret
});
const response = await fetch(`http://localhost:8000${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Open-App-Key": appKey,
"X-Open-Timestamp": String(timestamp),
"X-Open-Nonce": nonce,
"X-Open-Signature": signature,
"X-Open-Signature-Method": "HMAC-SHA256"
},
body
});固定 Web3 Token 价格签名夹具:
| 字段 | 值 |
|---|---|
appKey | app_web3_example |
appSecret | secret_web3_example |
method | POST |
path | /api/v1/open/web3/chains/bsc/token-prices |
query | 空字符串 |
timestamp | 1779379200000 |
nonce | web3-price-nonce-1 |
scope | open:web3:price:read |
请求体必须按实际发送字节计算 hash,下面 fixture 使用紧凑 JSON:
{"tokenAddress":"0x0000000000000000000000000000000000000000","tokenSymbol":"BNB","tokenDecimals":18,"quoteCurrency":"USD","providerName":"pancake-v2"}对应签名中间值:
bodyHash = b758ac948411195867487ab08bce9a7398f065d27095b507c1916e0a59604802
signingKey = 75e43b35a2d094be01bae26603e22ebd8afb8ef3fbaaa6607ee4a1c20cef7473
signature = 61e3f6ec7dcda8b9931aa7eb6e526e32c55d55ae805e55ad59af08212a486821canonical string:
POST
/api/v1/open/web3/chains/bsc/token-prices
1779379200000
web3-price-nonce-1
b758ac948411195867487ab08bce9a7398f065d27095b507c1916e0a59604802Java 17 HttpClient 精简示例:
下面代码只演示签名规则和请求头生成;第三方服务端应按自己的 HTTP 客户端、日志、重试和密钥管理规范封装。
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.HexFormat;
import java.util.UUID;
public final class OpenPlatformClient {
private static final String BASE_URL = "http://localhost:8000";
private static final String APP_KEY = "app_xxx";
private static final String APP_SECRET = "secret_xxx";
private final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
public String get(String path, String query) throws Exception {
long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString();
String signature = sign("GET", path, query, "", timestamp, nonce);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + path + (query == null || query.isBlank() ? "" : "?" + query)))
.header("X-Open-App-Key", APP_KEY)
.header("X-Open-Timestamp", String.valueOf(timestamp))
.header("X-Open-Nonce", nonce)
.header("X-Open-Signature", signature)
.header("X-Open-Signature-Method", "HMAC-SHA256")
.GET()
.build();
return http.send(request, HttpResponse.BodyHandlers.ofString()).body();
}
public String postJson(String path, String body) throws Exception {
long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString();
String payload = body == null ? "" : body;
String signature = sign("POST", path, "", payload, timestamp, nonce);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + path))
.header("Content-Type", "application/json")
.header("X-Open-App-Key", APP_KEY)
.header("X-Open-Timestamp", String.valueOf(timestamp))
.header("X-Open-Nonce", nonce)
.header("X-Open-Signature", signature)
.header("X-Open-Signature-Method", "HMAC-SHA256")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
return http.send(request, HttpResponse.BodyHandlers.ofString()).body();
}
private static String sign(String method, String path, String query, String body,
long timestamp, String nonce) throws Exception {
String canonical = String.join("\n",
method.toUpperCase(),
path.startsWith("/") ? path : "/" + path,
query == null ? "" : query,
String.valueOf(timestamp),
nonce,
sha256Hex(body == null ? "" : body));
return hmacSha256Hex(canonical, sha256Hex(APP_SECRET));
}
private static String sha256Hex(String value) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return HexFormat.of().formatHex(digest.digest(value.getBytes(StandardCharsets.UTF_8)));
}
private static String hmacSha256Hex(String value, String key) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return HexFormat.of().formatHex(mac.doFinal(value.getBytes(StandardCharsets.UTF_8)));
}
}响应统一使用 Result<T>:
{
"code": "200",
"message": "Success",
"data": {}
}开放平台鉴权失败、scope 未授权、限流失败等场景,HTTP 状态仍返回 200,业务失败通过 Result.code 表达。
常见业务码:
| Code | 场景 |
|---|---|
200 | 成功 |
400 | 请求参数或签名头缺失 |
401 | 未认证或应用凭证无效 |
403 | 应用禁用、IP 不允许、scope 未授权、路由未注册 scope |
422 | DTO validation 失败 |
429 | 应用限流 |
500 | 服务端异常 |
常见失败排查:
| Result.code | message key | 常见原因 | 处理建议 |
|---|---|---|---|
401 | messages.open-platform.signature.invalid | AppKey 不存在、签名不一致、body hash 不一致、query string 与实际请求不一致 | 重新核对 AppSecret、canonical string、原始 query string、请求体字节和签名输出格式 |
401 | messages.open-platform.signature.timestamp-invalid | X-Open-Timestamp 不是毫秒时间戳 | 使用 System.currentTimeMillis() 或等价毫秒时间戳 |
401 | messages.open-platform.signature.timestamp-expired | 客户端时间与服务端时间偏差超过 timestamp-tolerance | 校准服务器时间,避免复用过期请求 |
401 | messages.open-platform.signature.method-unsupported | X-Open-Signature-Method 不在允许列表中 | 当前默认使用 HMAC-SHA256 |
401 | messages.open-platform.secret.unavailable | 应用没有可用密钥,或密钥已禁用/过期 | 后台重新生成或启用密钥 |
401 | messages.open-platform.nonce.replayed | 同一应用在 nonce 有效期内重复使用相同 nonce | 每次请求生成新的随机 nonce |
403 | messages.open-platform.app.disabled | 应用被禁用 | 后台启用应用后再调用 |
403 | messages.open-platform.ip.forbidden | 请求来源 IP 不在应用白名单中 | 配置固定出口 IP,或调整应用白名单 |
403 | messages.open-platform.scope.forbidden | 应用没有当前路由所需 scope | 后台为应用授权对应 scope |
403 | messages.open-platform.route.scope-not-found | 新开放 API 没有注册 route -> scope 映射 | 补充 OpenPlatformRouteScopeRegistryCustomizer 和文档 |
429 | messages.exception.too-many-requests | 当前应用超过每分钟限流 | 根据 Retry-After 和 X-Open-RateLimit-Reset 退避重试 |
调用失败后,后台调用日志会记录 app、scope、path、traceId、结果 code、结果 message 和耗时。排障时优先用响应中的 traceId 或调用时间在后台调用日志中定位。
Web3 调用示例
查询链信息:
GET /api/v1/open/web3/chains/bsc/info批量查原生币余额:
POST /api/v1/open/web3/chains/bsc/balances/native/batch
Content-Type: application/json
{
"addresses": [
"0x0000000000000000000000000000000000000000",
"0x1111111111111111111111111111111111111111"
]
}查询 ERC-20 信息:
GET /api/v1/open/web3/chains/bsc/tokens/erc20?contractAddress=0x...查询 ERC-20 allowance:
GET /api/v1/open/web3/chains/bsc/tokens/erc20/allowance?contractAddress=0x...&ownerAddress=0x...&spenderAddress=0x...Gas 预估:
POST /api/v1/open/web3/chains/bsc/gas/estimate
Content-Type: application/json
{
"fromAddress": "0x...",
"toAddress": "0x...",
"value": "0",
"data": "0x"
}批量交易确认数:
POST /api/v1/open/web3/chains/bsc/transactions/confirmations/batch
Content-Type: application/json
{
"hashes": [
"0x..."
],
"requiredConfirmations": 12
}批量转账和资金归集属于资金类能力。计划预览接口不接收私钥、不签名、不广播,适合先做金额、Gas、nonce 和余额检查;真实写交易接口会接收私钥用于本次签名,默认 broadcast=false 只返回 rawTransaction;只有显式 broadcast=true 才广播。生产环境建议把这类接口放在可信服务端、专用应用、独立 scope、IP 白名单和更低限流之下。
当前模块已接入开放平台访问过滤器,当前对外业务接口使用 /api/v1/open/**:
- 根据路由 scope 注册表解析当前请求需要的 scope。
- 读取并缓存 request body,后续 Controller 仍可正常读取请求体。
- 构建签名校验入参。
- 执行应用、签名、timestamp、nonce、IP 白名单和 scope 校验。
- 执行应用级限流。
- 放行到业务 Controller。
- 无论成功或失败,都会尝试写入调用日志。
过滤器采用 fail-closed 策略:开放平台路由如果没有注册 scope,会直接返回统一 Result 失败响应,而不是默认放行。新增版本化开放能力时必须同步注册 route -> scope 映射。
spring-open-core-runtime 已提供 Open API 中立签名底层工具,Open Platform 主链路直接复用这些工具:
OpenApiHeadersOpenApiSignatureMethodsOpenApiSignatureRequestOpenApiSignaturesOpenPlatformAccessRequestFactory
这些工具负责 header 名称、canonical string、SHA-256 body hash、HMAC-SHA256 签名、常量时间比对,以及从 HttpServletRequest 构建安全校验入参。
OpenPlatformAccessRequestFactory 不直接读取请求流,调用方需要传入已缓存的 body 字节。当前访问过滤器会使用 request wrapper 缓存 body,避免业务 Controller 读取不到请求体。
当前模块还提供纯服务层安全校验:
OpenPlatformSecurityServiceOpenPlatformAccessRequestOpenPlatformAccessGrant
校验顺序:
- 查找并确认应用启用。
- 校验 timestamp 在允许窗口内。
- 校验签名算法。
- 使用应用可用密钥校验签名。
- 校验 IP 白名单。
- 校验应用授权 scope。
- 消费 nonce,防止重放。
- 更新密钥最后使用时间。
该服务已被开放平台访问过滤器调用,用于开放 API 安全校验;后台管理接口不经过该过滤器。
当前模块已提供调用日志记录服务:
OpenPlatformCallLogService.recordCall(...)OpenPlatformCallLogRecord
记录字段包括应用、scope、请求方法、路径、query string、请求 IP、User-Agent、traceId、成功状态、结果 code、结果消息、耗时和发生时间。服务会统一裁剪字段长度以匹配数据库列,避免异常请求把调用日志写入打爆。
该服务已被开放平台访问过滤器调用。过滤器会在成功、鉴权失败、限流失败等场景尝试写入调用日志;日志写入失败不会中断业务响应。
后台调用日志分页支持按以下条件筛选:
| 参数 | 说明 |
|---|---|
appKey | 应用 Key |
scopeCode | scope 编码 |
method | HTTP 方法,例如 GET、POST |
path | 请求路径,模糊匹配 |
requestIp | 请求来源 IP |
traceId | TraceId |
resultCode | 业务结果码,例如 401、403、429 |
success | 是否成功 |
occurredFrom | 开始时间,ISO 日期时间格式 |
occurredTo | 结束时间,ISO 日期时间格式 |
后台调用日志统计接口:
GET /api/v1/admin/open-platform/call-logs/statistics统计接口复用调用日志查看权限,支持和分页接口一致的筛选参数,返回总调用量、成功调用量、失败调用量、成功率、平均耗时、最大耗时、最小耗时、首次调用时间、最近调用时间,并按应用 Key 和 scope 聚合 Top 20 调用量。successRate 使用 0 到 1 的小数表示,例如 0.9500 表示 95%。该接口是 V0.5 第一阶段计量底座,只做只读统计,不自动扣费,也不修改订单、余额或账单。
后台调用日志导出接口:
GET /api/v1/admin/open-platform/call-logs/export导出接口复用调用日志查看权限和分页筛选条件,返回 text/csv;charset=UTF-8 附件。CSV 只包含排障和审计所需的元数据字段:日志 ID、应用 ID、应用 Key、scope、方法、路径、来源 IP、traceId、成功状态、结果 code、耗时、发生时间、创建时间和更新时间;不导出 query string、User-Agent 和结果消息原文,避免把请求参数或上游错误详情扩散到离线文件。
当前模块已提供基础限流服务:
OpenPlatformRateLimiterServicespring-open-starter-rate-limit
开放平台使用通用 Rate Limit starter 做固定窗口限流。应用单独配置 rate_limit_per_minute 时优先使用应用配置;未配置时使用 security.default-rate-limit-per-minute;配置为 0 或负数表示不限流。
该服务已被开放平台访问过滤器调用。生产多实例场景建议使用 spring.open.rate-limit.default-store=redis;只有单进程诊断或明确的 lite 兼容模式才使用 memory。
启用限流时,响应会带上 X-Open-RateLimit-Limit、X-Open-RateLimit-Remaining 和 X-Open-RateLimit-Reset。被限流时额外返回标准 Retry-After,方便调用方做退避重试。
当前模块已提供应用日 / 月配额:
open_platform_package.daily_quotaopen_platform_package.monthly_quotaopen_platform_app.daily_quotaopen_platform_app.monthly_quotaOpenPlatformAppUsageService
配额以计量事件的可计费用量单元为统计来源,按 UTC 自然日和自然月统计。普通开放 API 默认 1 次成功调用计 1 unit,AI 网关按 token 用量写入 unitCount。应用配置的配额优先于套餐配额;应用配额为空、0 或负数时,回退到绑定套餐配额;应用没有可用套餐时,回退到默认免费试用日 / 月额度;默认免费额度也被设置为 0 或负数时才表示不限制调用。开放平台访问过滤器会在签名、scope、nonce 和每分钟限流通过后检查应用配额;配额用完时返回业务码 429,并写入调用日志用于审计。
后台应用用量接口:
GET /api/v1/admin/open-platform/apps/{appId}/usage该接口复用应用查看权限,返回套餐信息、日配额、月配额、已用量、剩余量和当前统计窗口,便于后台展示套餐和用量进度。
用户控制台应用用量接口:
GET /api/v1/open/console/apps/{appId}/usage该接口先校验当前登录用户是否拥有该应用,再返回套餐信息、日配额、月配额、已用量、剩余量和当前统计窗口,便于开发者控制台展示“我的应用”用量和套餐余量。
用户控制台套餐目录接口:
GET /api/v1/open/console/packages
GET /api/v1/open/console/packages/{id}
POST /api/v1/open/console/packages/{id}/purchase-preview
POST /api/v1/open/console/packages/{id}/purchase-orders
POST /api/v1/open/console/packages/{id}/renew-orders
POST /api/v1/open/console/packages/{id}/top-up-orders
GET /api/v1/open/console/packages/purchase-orders
GET /api/v1/open/console/packages/purchase-orders/{orderNo}
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/cancel
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/pay
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/refund
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/reverse这些接口要求当前用户已登录。目录和详情返回可售套餐基础额度、赠送额度、有效期、默认单价和阶梯价格;购买预览接收 unitCount,返回匹配的阶梯价、本次应付金额、展示金额、日 / 月 / 赠送额度合计、有效期和预览过期时间。
购买预览是只读快照,不绑定应用、不创建购买订单、不扣费。购买、续费和补款订单接口接收 unitCount 与 appId,创建 pending 待支付订单并返回 orderNo、appId、套餐快照、阶梯价快照、额度合计、应付金额、展示金额、支付截止时间、状态展示值和可操作标记。订单分页和详情接口按当前登录用户隔离,只支持查询自己的购买订单,可按关键字、订单号、套餐编码和订单状态筛选。取消只允许待支付订单;支付只允许待支付且未过期订单,会写入开放平台账务 recharge 流水并激活应用套餐与额度;退款只允许已支付订单;冲正允许已支付或已退款订单。Payment 收银台支付、支付回调和正式月度账单落库已接入;自动续费继续走后续独立商业化闭环。
用户控制台应用账务流水接口:
GET /api/v1/open/console/apps/{appId}/billing/ledgers
GET /api/v1/open/console/apps/{appId}/billing/statements
GET /api/v1/open/console/apps/{appId}/billing/statements/{billingMonth}该接口先校验当前登录用户是否拥有该应用,再按应用 appKey 查询账务流水,支持方向、场景、币种、资产、关联业务、幂等键和发生时间筛选。前端展示金额、资产和时间时优先使用响应中的 displayAsset、displayAmount、displayBalanceBefore、displayBalanceAfter 和 displayOccurredAt,不要自行拼接币种和格式。
/billing/statements 是用户侧月度账单查询入口,支持 billingMonthFrom=yyyy-MM、billingMonthTo=yyyy-MM 和分页参数;未传账期时返回当前应用已有的正式账单快照。/billing/statements/{billingMonth} 返回单月账单详情,billingMonth 使用 yyyy-MM。账单详情优先读取 open_platform_billing_statement 和 open_platform_billing_statement_line_item,缺失时按当前应用归属与账期懒生成一次;已落库账单不会随用量事件变化重新计算。账单响应包含 statementNo、账期范围、生成时间、状态展示值、费用汇总和 lineItems 明细;snapshotOnly=true 表示账单本身是核算快照,不创建充值订单、不触发扣费。
后台计量事件聚合接口:
GET /api/v1/admin/open-platform/usage-events/statistics该接口复用计量事件查看权限,支持按 appKey、appSecretId、scopeCode、apiVersion、method、path、resultCode、billable、providerName、modelName、traceId、occurredFrom、occurredTo 过滤,返回总事件数、可计费事件数、免费事件数、总用量单元、可计费用量单元、免费用量单元、首次 / 最近发生时间,并按应用、scope、路径聚合 Top 20 用量,同时按币种汇总历史成本快照。
聚合结果只读,不直接扣费、不生成账单、不修改应用余额;它是后续账单、成本统计和余额扣费的统一数据来源。新增调用会在 usage event 中记录当时套餐的 unitPrice、currency 和 costAmount 快照,避免套餐改价后污染历史成本统计。
Open Platform 账务账户用于开放能力内部扣费和对账,资产编码仍按 fiat:CNY、fiat:USD 等法币资产写入,但账户与流水的 decimals 固定为 6 位内部微计费精度。AI Gateway、套餐阶梯价和 usage event 成本快照按 6 位小数向下截断后落账,便于 token 级费用逐笔对账;真实支付渠道的外部收款金额仍由 Cashier / Payment 按渠道规则处理。
后台账单费用预览接口:
GET /api/v1/admin/open-platform/billing/preview
GET /api/v1/admin/open-platform/billing/statements/preview
GET /api/v1/admin/open-platform/billing/statements
GET /api/v1/admin/open-platform/billing/statements/{billingMonth}?appKey=app_xxx
POST /api/v1/admin/open-platform/billing/statements/generate
GET /api/v1/admin/open-platform/billing/accounts
GET /api/v1/admin/open-platform/billing/accounts/{id}
GET /api/v1/admin/open-platform/billing/ledgers
GET /api/v1/admin/open-platform/billing/ledgers/{id}该接口复用计量事件查看权限,支持按 appKey、scopeCode、apiVersion、method、path、occurredFrom、occurredTo 过滤,返回当前筛选范围内的调用量、可计费用量、免费用量、应用 / scope / 路径聚合,以及按币种汇总的历史成本快照。
/billing/statements/preview 是账期账单草稿预览,支持 billingMonth=yyyy-MM,并可继续按 appKey、scopeCode、apiVersion、method、path 缩小范围。它会把账期归一为当月 [periodStart, periodEnd),返回 statementNo、账期范围、草稿标识、复用费用预览明细,以及按应用 / scope / 版本 / 方法 / 路径 / 币种聚合的 lineItems 明细行。该入口只读,不落账、不扣余额、不生成支付订单。
正式账单接口读取或生成已落库的月度账单快照:后台 GET /billing/statements 支持按 appKey、账期范围、状态和分页查询,GET /billing/statements/{billingMonth} 读取指定应用账期详情,POST /billing/statements/generate 按 appKey + billingMonth 幂等生成正式账单。首次生成会校验应用存在;已有账单直接返回历史快照,不随后续用量事件重算。
费用预览和账期草稿只做只读核算,不扣余额、不生成支付订单,也不会修改开放平台应用状态。账务账户和账务流水后台接口仍只提供查询;正式月度账单基于 usage event 生成不可变快照,便于开发者查看账期费用和明细。模块内部已具备幂等账务写入服务,用于付费调用阶段统一处理充值、扣费、冻结和解冻。套餐单价大于 0 的应用调用会先检查对应币种账务账户可用余额,余额不足、账户不存在或账户停用时直接拒绝进入 服务商。账务预占底座已提供 reserveBillingBalance(...),用 api-reserve 场景冻结预计费用并保持幂等;失败释放底座已提供 releaseBillingReservation(...),用 api-reserve-release 场景释放预占冻结金额;成功结算底座已提供 settleBillingReservation(...),先复用释放入口,再按实际费用写入扣费流水;AI 网关普通响应和流式响应调用链已接入自动预占、成功实扣和 服务商 异常 / 流式写出中断释放。AI 调用审计复用后台计量事件接口,支持按应用 appKey、应用密钥 appSecretId、模型、服务商 和 traceId 查询调用、token、成本快照与扣费链路。购买、续费、补款订单已有用户侧支付激活动作,退款和冲正仍是状态动作 contract;更完整的限流熔断仍属于后续付费调用增强。
计量与计费预留
开放平台后续会支持付费调用服务,但当前阶段不在过滤器里直接扣费,也不在能力 Controller 里写计费逻辑。
推荐边界:
- 调用日志是审计和排障底座;计量事件是配额、套餐、账单和成本统计的来源。
- 计量与业务能力解耦,由开放平台统一根据
appKey、scope、method、path、apiVersion和调用结果生成 usage event。 - 价格规则应绑定
apiVersion + scope + route + method + capability,避免接口升级后计费语义混乱。 - 默认只对通过鉴权、未被限流、进入业务 Controller 并得到业务结果的调用计量;签名失败、IP 白名单失败、scope 不足和限流拒绝默认不计费。
- 外部成本型能力可以通过价格规则声明“失败也计量”,例如第三方供应商已经产生实际成本的调用。
- 计量必须具备幂等键,当前使用
call-log:{callLogId},避免过滤器重试或异步消费导致重复计费。 - 账务账户和流水承载付费调用的运维与审计底座;账期草稿预览只基于 usage event 做只读核算;正式月度账单在 Open Platform 内落库为不可变快照,支付扣款也留在 Open Platform / Cashier 协作边界内,不回灌到 Web3、Payment、SMS、Storage 等具体能力模块。
当前已落地 open_platform_package、open_platform_usage_event、open_platform_billing_account、open_platform_billing_ledger、open_platform_billing_statement 和 open_platform_billing_statement_line_item:套餐提供默认日 / 月额度、单价和币种,应用可以绑定套餐并按需覆盖额度;成功且通过鉴权的开放 API 调用会在调用日志写入后生成一条可幂等去重的计量事件,并写入当时套餐的单价、币种和成本金额快照;应用日 / 月配额使用计量事件的可计费用量单元统计,避免失败鉴权、限流拒绝或异常日志影响套餐用量,也避免 AI token 调用只按请求次数消耗额度。套餐单价大于 0 时,请求进入 服务商 前会检查应用账务账户同币种可用余额是否至少覆盖本次套餐单价。用量聚合、账单预览和账期草稿预览基于计量事件提供只读统计;正式月度账单按 appKey + billingMonth 幂等生成落库快照,用户侧详情缺失时按应用归属懒生成一次。账务账户和账务流水已补齐资产字段,并在模块内部支持幂等创建账户、充值、扣费、冻结、解冻、余额快照、预计费用预占、预占失败释放和预占成功结算。AI 网关普通响应和流式响应会在进入 服务商 前冻结预计费用,成功后按实际 token 费用扣减,服务商 异常、流式写出异常、客户端断开或实际用量为 0 时释放冻结金额。账务账户 / 流水响应同时返回 displayAsset、displayAmount、displayBalanceAmount、displayOccurredAt 等展示字段,前端优先展示这些字段,不自行拼接金额和资产。后续如果需要定时快照或大数据量报表,可在 Open Platform 模块内增加 open_platform_usage_aggregate。支付补款、套餐续费、限流熔断和审计查询继续在 Open Platform 付费调用阶段实现,不回写 Web3、Payment、SMS、Storage 等具体能力模块。
当前模块已提供路由 scope 注册表:
OpenPlatformRouteScopeRegistryOpenPlatformRouteScopeRegistryCustomizerOpenPlatformRouteScope
开放 API 不应在 Filter 中硬编码 scope。每个能力模块应通过 customizer 注册自己的路由和 scope 映射,例如 Web3 v1 已经内置注册余额、交易、事件、价格、批量转账计划、批量转账、资金归集计划和资金归集路由。
注册表支持 Ant 风格路径匹配和 HTTP method 匹配,会优先选择更具体的路由规则。/api/v1/open/** 等版本化开放请求会先通过注册表解析当前请求需要的 scope,再执行验签、scope 授权、限流和调用日志记录。
后台提供只读路由 scope 元数据接口:
GET /api/v1/admin/open-platform/route-scopes该接口复用 scope 查看权限,返回 method、pattern、scopeCode。后台页面可以用它辅助应用授权,开发者也可以用它检查新增开放 API 是否已经注册 route -> scope 映射。
表结构
当前最小表:
open_platform_app
open_platform_package
open_platform_app_secret
open_platform_scope
open_platform_app_scope
open_platform_nonce
open_platform_call_log
open_platform_usage_event说明:
| 表 | 作用 |
|---|---|
open_platform_app | 开放平台应用主体,保存名称、状态、套餐、IP 白名单、限流策略、应用级日 / 月配额覆盖值和应用 Webhook endpoint / 签名密钥摘要等 |
open_platform_package | 开放平台应用套餐,保存默认日 / 月调用配额、单价、币种和启用状态 |
open_platform_app_secret | 应用密钥,支持密钥轮换、禁用和过期时间 |
open_platform_scope | 开放能力 scope 字典 |
open_platform_app_scope | 应用与 scope 授权关系 |
open_platform_nonce | 防重放记录,按过期时间清理 |
open_platform_call_log | 外部调用日志,用于审计、排障、计费或限流分析 |
open_platform_usage_event | 外部调用计量事件,保存用量单元、单价、币种和成本快照,用于应用配额、套餐、账单和后续成本统计 |
open_platform_billing_account | 开放平台账务账户,保存应用、币种、资产编码、余额、冻结金额和累计收支,作为后续扣费模型的账户主体 |
open_platform_billing_ledger | 开放平台账务流水,保存资产编码、账务方向、场景、金额、前后余额、幂等键和关联业务对象 |
表名前缀使用 open_platform_,避免与其他后台表混淆。
API
后台管理 API:
GET /api/v1/admin/open-platform/apps
POST /api/v1/admin/open-platform/apps
GET /api/v1/admin/open-platform/apps/{id}
GET /api/v1/admin/open-platform/apps/{appId}/usage
PUT /api/v1/admin/open-platform/apps/{id}
DELETE /api/v1/admin/open-platform/apps/{id}
DELETE /api/v1/admin/open-platform/apps
GET /api/v1/admin/open-platform/packages
POST /api/v1/admin/open-platform/packages
GET /api/v1/admin/open-platform/packages/{id}
PUT /api/v1/admin/open-platform/packages/{id}
DELETE /api/v1/admin/open-platform/packages/{id}
DELETE /api/v1/admin/open-platform/packages
GET /api/v1/admin/open-platform/scopes
POST /api/v1/admin/open-platform/scopes
GET /api/v1/admin/open-platform/scopes/{id}
PUT /api/v1/admin/open-platform/scopes/{id}
DELETE /api/v1/admin/open-platform/scopes/{id}
DELETE /api/v1/admin/open-platform/scopes
GET /api/v1/admin/open-platform/route-scopes
GET /api/v1/admin/open-platform/call-logs
GET /api/v1/admin/open-platform/call-logs/export
GET /api/v1/admin/open-platform/call-logs/{id}
DELETE /api/v1/admin/open-platform/call-logs/{id}
DELETE /api/v1/admin/open-platform/call-logs
GET /api/v1/admin/open-platform/usage-events
GET /api/v1/admin/open-platform/usage-events/statistics
GET /api/v1/admin/open-platform/usage-events/{id}
DELETE /api/v1/admin/open-platform/usage-events/{id}
DELETE /api/v1/admin/open-platform/usage-events
GET /api/v1/admin/open-platform/billing/preview
GET /api/v1/admin/open-platform/billing/statements/preview
GET /api/v1/admin/open-platform/billing/statements
GET /api/v1/admin/open-platform/billing/statements/{billingMonth}
POST /api/v1/admin/open-platform/billing/statements/generate
GET /api/v1/admin/open-platform/billing/accounts
GET /api/v1/admin/open-platform/billing/accounts/{id}
GET /api/v1/admin/open-platform/billing/ledgers
GET /api/v1/admin/open-platform/billing/ledgers/{id}
GET /api/v1/open/console/apps/{appId}/billing/ledgers
GET /api/v1/open/console/apps/{appId}/billing/statements
GET /api/v1/open/console/apps/{appId}/billing/statements/{billingMonth}
GET /api/v1/admin/ai/platform/overview
GET /api/v1/admin/ai/platform/providers
GET /api/v1/admin/ai/platform/models
PUT /api/v1/admin/ai/platform/models
GET /api/v1/admin/ai/platform/prices
GET /api/v1/admin/ai/platform/routing后台 AI 运营页使用 /api/v1/admin/ai/platform/** 代理接口,查询统一走后台权限 ai:platform:view,模型目录、价格、服务商 和路由保存动作走 ai:platform:update。这些接口复用 AI Platform 当前生效配置,但不走开放应用 sk-... Key、HMAC 或 Bearer Key 鉴权链路,避免 Admin 页面被开放平台调用方鉴权挡住。
Open Platform 基线迁移会把 developer 内置角色绑定到开放平台后台只读菜单和权限,包括应用、套餐、Scope、路由 Scope、调用日志、用量事件、账务和 AI 运营只读入口。该模板不授予删除、配置中心维护或跨模块全局维护能力;如后续开放开发者后台写操作,必须先补 workspace / 应用归属和数据范围强校验。
AI 模型价格目录默认采用主流供应商一致的 1M tokens 展示口径,并返回 billingUnitSize=1000000 供后台页面和计费逻辑换算。模型 metadata 使用 input-price-per-1m-tokens、output-price-per-1m-tokens、input-cost-per-1m-tokens、output-cost-per-1m-tokens 等 key;后续数据库价格覆盖仍归入 AI 运营配置阶段。
后台应用新增 Webhook 运维字段,复用应用新增 / 修改 / 详情接口返回:webhookEnabled、webhookEndpointKey、webhookUrl、webhookSigningEnabled、hasWebhookSecret 和 maskedWebhookSecret。webhookSecret 只允许在保存请求中写入,不会出现在响应体中;旧客户端不提交任何 Webhook 字段时,服务端会保留原有回调配置。
应用密钥与授权 API:
GET /api/v1/open/console/apps
GET /api/v1/open/console/apps/{id}
POST /api/v1/open/console/apps
GET /api/v1/open/console/apps/{appId}/usage
GET /api/v1/open/console/apps/{appId}/billing/ledgers
GET /api/v1/open/console/packages
GET /api/v1/open/console/packages/{id}
POST /api/v1/open/console/packages/{id}/purchase-preview
POST /api/v1/open/console/packages/{id}/purchase-orders
POST /api/v1/open/console/packages/{id}/renew-orders
POST /api/v1/open/console/packages/{id}/top-up-orders
GET /api/v1/open/console/packages/purchase-orders
GET /api/v1/open/console/packages/purchase-orders/{orderNo}
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/cancel
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/pay
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/refund
POST /api/v1/open/console/packages/purchase-orders/{orderNo}/reverse
GET /api/v1/open/console/apps/{appId}/secrets
POST /api/v1/open/console/apps/{appId}/secrets
PUT /api/v1/open/console/apps/{appId}/secrets/{secretId}/disable
POST /api/v1/open/console/apps/{appId}/secrets/{secretId}/rotate
DELETE /api/v1/open/console/apps/{appId}/secrets/{secretId}
GET /api/v1/admin/open-platform/apps/{appId}/secrets
POST /api/v1/admin/open-platform/apps/{appId}/secrets
PUT /api/v1/admin/open-platform/apps/{appId}/secrets/{secretId}/disable
POST /api/v1/admin/open-platform/apps/{appId}/secrets/{secretId}/rotate
DELETE /api/v1/admin/open-platform/apps/{appId}/secrets/{secretId}
GET /api/v1/admin/open-platform/apps/{appId}/scopes
PUT /api/v1/admin/open-platform/apps/{appId}/scopes密钥规则:
POST /secrets创建新密钥并只在本次响应中返回一次明文secret;如不需要设置过期时间,可直接提交空请求体。- 数据库存储
secret_hash、secret_hint和masked_secret,列表接口只返回secretKey、maskedSecret、状态、状态原因、过期时间和使用时间,不返回明文。 GET /secrets会同时返回该应用当前启用的scopeCodes、分钟限流、日配额和月配额,方便开发者控制台直接展示“这把 Key 继承了哪些权限和调用限制”。- 轮换密钥使用
POST /rotate,会在同一事务内禁用旧密钥并创建新密钥,新密钥明文同样只在本次响应中返回一次;旧密钥会记录statusReason=rotated、disabledAt、rotatedAt,新密钥会记录rotatedFromSecretId。 - 禁用密钥使用
PUT /disable,删除密钥使用DELETE;禁用和删除都会记录statusReason与禁用时间,删除额外记录deletedBy后再逻辑删除。 - 真实开放请求验签已由开放平台访问过滤器处理,不在后台管理 Controller 中处理。
Scope 授权规则:
PUT /scopes使用传入的scopeCodes整体替换应用授权。- 传空数组表示清空当前应用授权,系统会把已有授权置为禁用状态。
- 传入的 scope 必须存在且启用,否则返回业务错误。
第一阶段内置 Web3 scope:
| Scope | 说明 |
|---|---|
open:web3:balance:read | 查询原生币和 Token 余额 |
open:web3:token:read | 查询 ERC-20 Token 信息和授权额度 |
open:web3:transaction:read | 查询交易、回执、确认数和 input 解码 |
open:web3:event:read | 查询链上事件日志 |
open:web3:price:read | 查询 Token 价格和池子报价 |
open:web3:network:read | 查询链信息、可公开 RPC 节点、Gas Price 等网络信息 |
open:web3:wallet:read | 查询钱包地址信息和 nonce |
open:web3:wallet-signature:verify | 校验钱包 personal_sign 和 EIP-712 typed data 签名 |
open:web3:transfer:write | 批量转账计划预览和真实签名,按请求选择是否广播 |
open:web3:fund-collection:write | 资金归集计划预览和真实签名,按请求选择是否广播 |
其他已内置开放能力 scope:
| Scope | 说明 |
|---|---|
open:voucher:available | 查询业务主体可用卡券 |
open:voucher:lock | 业务结算前锁定或释放卡券 |
open:voucher:redeem | 确认或冲正卡券核销 |
open:advertising:placement:read | 查询广告位当前可展示素材 |
open:advertising:event:write | 上报广告曝光、点击和转化事件 |
开放 API:
GET /api/v1/open/web3/chains
GET /api/v1/open/web3/chains/{chain}/info
GET /api/v1/open/web3/chains/{chain}/balances/native
POST /api/v1/open/web3/chains/{chain}/balances/native/batch
GET /api/v1/open/web3/chains/{chain}/gas-price
GET /api/v1/open/web3/chains/{chain}/accounts/{address}
GET /api/v1/open/web3/chains/{chain}/accounts/{address}/nonce
POST /api/v1/open/web3/chains/{chain}/wallet-signatures/personal/verify
POST /api/v1/open/web3/chains/{chain}/wallet-signatures/typed-data/verify
POST /api/v1/open/web3/chains/{chain}/gas/estimate
GET /api/v1/open/web3/chains/{chain}/tokens/erc20
GET /api/v1/open/web3/chains/{chain}/tokens/erc20/allowance
GET /api/v1/open/web3/chains/{chain}/balances/erc20
GET /api/v1/open/web3/chains/{chain}/transactions/{hash}
GET /api/v1/open/web3/chains/{chain}/transactions/{hash}/receipt
GET /api/v1/open/web3/chains/{chain}/transactions/{hash}/confirmation
POST /api/v1/open/web3/chains/{chain}/transactions/confirmations/batch
POST /api/v1/open/web3/chains/{chain}/transaction-inputs/decode
POST /api/v1/open/web3/chains/{chain}/event-logs/search
POST /api/v1/open/web3/chains/{chain}/token-prices
POST /api/v1/open/web3/chains/{chain}/transfer-plans/batch
POST /api/v1/open/web3/chains/{chain}/transfers/batch
POST /api/v1/open/web3/chains/{chain}/fund-collection-plans
POST /api/v1/open/web3/chains/{chain}/fund-collections开放 API 的返回仍使用统一 Result<T>,HTTP 状态只表达传输层结果。
当前已实现的 Web3 v1 开放 API:
| API | Scope | 说明 |
|---|---|---|
GET /api/v1/open/web3/chains | open:web3:network:read | 查询已启用链摘要和可公开 rpc-urls,不返回 private-rpc-urls |
GET /api/v1/open/web3/chains/{chain}/info | open:web3:network:read | 查询指定链摘要和可公开 rpc-urls,不返回 private-rpc-urls |
GET /api/v1/open/web3/chains/{chain}/balances/native?address=... | open:web3:balance:read | 查询原生币余额,返回原始 wei、格式化余额和地址浏览器链接 |
POST /api/v1/open/web3/chains/{chain}/balances/native/batch | open:web3:balance:read | 批量查询原生币余额,地址列表放在请求体 |
GET /api/v1/open/web3/chains/{chain}/gas-price | open:web3:network:read | 查询当前 Gas Price |
GET /api/v1/open/web3/chains/{chain}/accounts/{address} | open:web3:wallet:read | 查询钱包地址余额、交易计数、是否合约地址和浏览器链接 |
GET /api/v1/open/web3/chains/{chain}/accounts/{address}/nonce?block=pending | open:web3:wallet:read | 查询地址 nonce,默认 pending |
POST /api/v1/open/web3/chains/{chain}/wallet-signatures/personal/verify | open:web3:wallet-signature:verify | 校验钱包 personal_sign 签名,只返回验签结果,不登录、不创建账号 |
POST /api/v1/open/web3/chains/{chain}/wallet-signatures/typed-data/verify | open:web3:wallet-signature:verify | 校验 EIP-712 typed data 签名,只返回验签结果,不登录、不创建账号 |
POST /api/v1/open/web3/chains/{chain}/gas/estimate | open:web3:transaction:read | 按 toAddress、data、value 预估 Gas |
GET /api/v1/open/web3/chains/{chain}/tokens/erc20?contractAddress=... | open:web3:token:read | 查询 ERC-20 Token 名称、符号、精度、总供应量和合约浏览器链接 |
GET /api/v1/open/web3/chains/{chain}/tokens/erc20/allowance?contractAddress=...&ownerAddress=...&spenderAddress=... | open:web3:token:read | 查询 ERC-20 allowance 授权额度 |
GET /api/v1/open/web3/chains/{chain}/balances/erc20?contractAddress=...&ownerAddress=... | open:web3:balance:read | 查询 ERC-20 余额,并返回 Token 名称、符号、精度和格式化余额 |
GET /api/v1/open/web3/chains/{chain}/transactions/{hash} | open:web3:transaction:read | 查询交易详情,并返回常见 input 解码摘要 |
GET /api/v1/open/web3/chains/{chain}/transactions/{hash}/receipt | open:web3:transaction:read | 查询交易回执、执行状态、Gas 消耗和事件日志 |
GET /api/v1/open/web3/chains/{chain}/transactions/{hash}/confirmation?requiredConfirmations=12 | open:web3:transaction:read | 查询交易确认数和是否达到要求确认数,未传确认数时默认 12 |
POST /api/v1/open/web3/chains/{chain}/transactions/confirmations/batch | open:web3:transaction:read | 批量查询交易确认数和成功失败状态 |
POST /api/v1/open/web3/chains/{chain}/transaction-inputs/decode | open:web3:transaction:read | 解码常见 EVM input,不访问链上节点 |
POST /api/v1/open/web3/chains/{chain}/event-logs/search | open:web3:event:read | 按区块范围、合约地址和 topics 搜索事件日志,默认只查 latest 区块 |
POST /api/v1/open/web3/chains/{chain}/token-prices | open:web3:price:read | 查询 Token 价格,可传入 providerName 选择价格 服务商 |
POST /api/v1/open/web3/chains/{chain}/transfer-plans/batch | open:web3:transfer:write | 预览批量转账计划,不接收私钥、不签名、不广播,只计算 nonce、gas、总额、余额是否足够和逐条计划 |
POST /api/v1/open/web3/chains/{chain}/transfers/batch | open:web3:transfer:write | 批量转账真实签名;默认 broadcast=false 只返回 rawTransaction,broadcast=true 时广播 |
POST /api/v1/open/web3/chains/{chain}/fund-collection-plans | open:web3:fund-collection:write | 预览资金归集计划,不接收私钥、不签名、不广播,只计算余额、nonce、gas、可归集金额和逐钱包计划 |
POST /api/v1/open/web3/chains/{chain}/fund-collections | open:web3:fund-collection:write | 资金归集真实签名;默认 broadcast=false 只返回 rawTransaction,broadcast=true 时广播 |
计划预览接口不会接收私钥,也不会生成 rawTransaction,适合在后台或第三方系统里先做金额、Gas、nonce 和余额检查。真实写交易接口会接收私钥用于本次签名。开放平台不会把私钥写入返回结果,当前调用日志也不记录请求体;生产环境仍建议只在可信服务端调用这类接口,前端浏览器不应直接提交私钥。
链信息接口会返回可公开 RPC URL,即 spring.open.web3.chains.<chain>.rpc-urls。私有 RPC URL,即 spring.open.web3.chains.<chain>.private-rpc-urls,永远不会通过开放平台返回。
Web3 配置类和运行时链配置快照中的私有 RPC 字段带有 JSON 忽略注解,防止配置对象被误序列化时泄露私有节点。开放平台仍采用 VO 白名单输出,不直接返回配置对象。
RPC URL 本身不是私钥,但私有或付费节点经常带有以下风险:
- 付费节点 URL 可能包含项目 key,泄露后会被刷额度。
- 公司自建节点、网关或内网地址会暴露基础设施拓扑。
- 生产 RPC 被外部复用后,容易带来限流、稳定性和链上查询审计压力。
- 某些节点服务商会把 project id 放在路径或 query 中,这类 URL 应按敏感配置处理。
Web3 接入样板
最小接入流程:
- 后台创建开放平台应用。
- 创建应用密钥,妥善保存
appKey和appSecret。 - 授权 Web3 scope,例如
open:web3:network:read、open:web3:balance:read、open:web3:token:read。 - 第三方服务端按签名规则生成请求头。
- 调用
/api/v1/open/web3/**。
查询链信息:
GET /api/v1/open/web3/chains/bsc/info
X-Open-App-Key: app_xxx
X-Open-Timestamp: 1779379200000
X-Open-Nonce: 4c68f8d1-1d8c-4b7d-9fc6-4c77369f28aa
X-Open-Signature: <signature>
X-Open-Signature-Method: HMAC-SHA256查询原生币余额:
GET /api/v1/open/web3/chains/bsc/balances/native?address=0x0000000000000000000000000000000000000000
X-Open-App-Key: app_xxx
X-Open-Timestamp: 1779379200000
X-Open-Nonce: 74c27e7d-a4c0-4a82-8701-138c0dd437ef
X-Open-Signature: <signature>
X-Open-Signature-Method: HMAC-SHA256查询 Token 信息:
GET /api/v1/open/web3/chains/bsc/tokens/erc20?contractAddress=0x55d398326f99059fF775485246999027B3197955
X-Open-App-Key: app_xxx
X-Open-Timestamp: 1779379200000
X-Open-Nonce: 01925f73-e9c8-40da-8ed4-f93d52dc79ec
X-Open-Signature: <signature>
X-Open-Signature-Method: HMAC-SHA256查询 Token 价格:
POST /api/v1/open/web3/chains/bsc/token-prices
Content-Type: application/json
X-Open-App-Key: app_web3_example
X-Open-Timestamp: 1779379200000
X-Open-Nonce: web3-price-nonce-1
X-Open-Signature: 61e3f6ec7dcda8b9931aa7eb6e526e32c55d55ae805e55ad59af08212a486821
X-Open-Signature-Method: HMAC-SHA256{
"tokenAddress": "0x0000000000000000000000000000000000000000",
"tokenSymbol": "BNB",
"tokenDecimals": 18,
"quoteCurrency": "USD",
"providerName": "pancake-v2"
}该示例对应 scope 为 open:web3:price:read,签名中间值见上文固定夹具。鉴权、scope、nonce 和每分钟限流任一环节失败时,HTTP 状态仍为 200,业务 Result.code 分别返回 401、403 或 429;被限流时读取 Retry-After 和 X-Open-RateLimit-Reset 做退避。只有通过鉴权、未被限流、进入业务 Controller 并成功写入可计费 usage event 的调用才消耗日 / 月配额;套餐存在单价时,请求进入 服务商 前还会做同币种账务余额预检。
批量转账计划预览:
POST /api/v1/open/web3/chains/bsc/transfer-plans/batch
Content-Type: application/json
X-Open-App-Key: app_xxx
X-Open-Timestamp: 1779379200000
X-Open-Nonce: 7b3c2b4e-0f80-4f66-8b19-450af51f376f
X-Open-Signature: <signature>
X-Open-Signature-Method: HMAC-SHA256{
"fromAddress": "0x0000000000000000000000000000000000000000",
"transfers": [
{
"toAddress": "0x0000000000000000000000000000000000000001",
"value": 1000000000000000000
}
]
}预览响应会返回 currentBalance、requiredBalance、balanceEnough、totalGasCost 和逐条 nonce / gasLimit / gasCost,不会返回签名交易。
批量转账只签名不广播:
POST /api/v1/open/web3/chains/bsc/transfers/batch
Content-Type: application/json
X-Open-App-Key: app_xxx
X-Open-Timestamp: 1779379200000
X-Open-Nonce: d707ac53-6037-4675-86a9-a0e5c600bba2
X-Open-Signature: <signature>
X-Open-Signature-Method: HMAC-SHA256
X-Open-Idempotency-Key: transfer-batch-20260522-0001{
"privateKey": "0x...",
"broadcast": false,
"transfers": [
{
"toAddress": "0x0000000000000000000000000000000000000001",
"value": 1000000000000000000
}
]
}broadcast=false 适合先对交易内容做二次审核;确认无误后再由可信后端使用 broadcast=true 发送链上交易。
资金归集计划预览:
POST /api/v1/open/web3/chains/bsc/fund-collection-plans
Content-Type: application/json
X-Open-App-Key: app_xxx
X-Open-Timestamp: 1779379200000
X-Open-Nonce: 0e74ef91-6c79-4f35-a912-6bcefb64164d
X-Open-Signature: <signature>
X-Open-Signature-Method: HMAC-SHA256{
"receiverAddress": "0x0000000000000000000000000000000000000009",
"reserveAmount": 10000000000000000,
"wallets": [
{
"fromAddress": "0x0000000000000000000000000000000000000001"
}
]
}归集预览会按钱包余额扣除 gasCost 和 reserveAmount 后计算 amount;余额不足的钱包会返回 skipped=true 和 skipReason=insufficient_balance。
Knowledge Ask 接入样板
Knowledge Ask 通过 Open Platform 暴露知识库问答能力,适合第三方服务端检索项目文档、产品说明或运营知识,也可以在管理员显式启用模型能力后请求受控生成。该能力复用开放平台签名、scope、限流、调用日志和计量事件,不使用 AI Gateway 的快捷 sk-... 鉴权。
调用方需要给应用授权 open:knowledge:ask scope。
POST /api/v1/open/knowledge/ask
Content-Type: application/json
X-Open-App-Key: app_knowledge_example
X-Open-Timestamp: 1779379200000
X-Open-Nonce: knowledge-ask-nonce-1
X-Open-Signature: 7ea36d16494b1ca5197b5230ca18aa8311db9f6b9a5ff530e8a5d2cbcbfd14c6
X-Open-Signature-Method: HMAC-SHA256{
"source": "project",
"question": "跨链桥如何校验出金钱包余额",
"topK": 3,
"returnContext": true
}默认 generate=false,接口只返回检索摘要和引用,不调用模型。需要生成式回答时可额外传:
{
"generate": true,
"provider": "simulated",
"model": "spring-open-simulated",
"temperature": 0.2,
"maxTokens": 512
}provider / model 是可选覆盖值;未传时由 Knowledge 与 AI Model 配置决定。真实模型未启用、模型不可用、护栏拒绝、prompt 过大或并发满时,接口仍返回检索摘要,并在 warnings 中标记降级原因。
该示例使用 appSecret=secret_knowledge_example,请求体紧凑 JSON 的 bodyHash 为:
3548fc37d7c3ed843657004612a0b4779c48e4de7ba76fd01d55e39fe5a0d18dcanonical string:
POST
/api/v1/open/knowledge/ask
1779379200000
knowledge-ask-nonce-1
3548fc37d7c3ed843657004612a0b4779c48e4de7ba76fd01d55e39fe5a0d18d成功响应的 data 包含 answer、answerMode、generated、provider、model、finishReason、usage、contexts、citations、warnings、hasContext 和 maxScore。answerMode=generated 表示生成式回答成功,extractive 表示使用检索摘要,no-context 表示没有可用上下文。returnContext=false 时仍会返回回答摘要和引用,但不会返回上下文全文;需要展示引用来源时保持 returnContext=true。
鉴权失败、scope 不足、nonce 重放、限流或配额用尽时仍按开放平台统一错误口径返回业务 Result.code;成功进入业务 Controller 的调用按普通开放 API 计量,默认 1 次成功调用计 1 unit。
钱包签名验签
钱包签名验签开放接口用于第三方应用确认“某个钱包地址确实签署了这段内容”。它不等同于 IAM 登录,不会创建账号,也不会消费业务 nonce。
personal_sign 验签请求:
POST /api/v1/open/web3/chains/bsc/wallet-signatures/personal/verify
Content-Type: application/json
{
"walletAddress": "0x0000000000000000000000000000000000000000",
"nonce": "login-20260522-0001",
"message": "Sign in to Example App\nNonce: login-20260522-0001",
"signature": "0x..."
}EIP-712 typed data 验签请求:
POST /api/v1/open/web3/chains/bsc/wallet-signatures/typed-data/verify
Content-Type: application/json
{
"walletAddress": "0x0000000000000000000000000000000000000000",
"typedDataJson": "{\"types\":{\"EIP712Domain\":[]},\"primaryType\":\"Login\",\"domain\":{},\"message\":{}}",
"signature": "0x..."
}返回示例:
{
"code": "200",
"message": "Success",
"data": {
"chainName": "bsc",
"chainType": "evm",
"signatureType": "personal",
"walletAddress": "0x0000000000000000000000000000000000000000",
"normalizedAddress": "0x0000000000000000000000000000000000000000",
"verified": true
}
}推荐客户端接入流程:
- 浏览器只负责连接钱包并拿到钱包签名。
- 前端把
walletAddress、message/typedDataJson、signature发送到第三方应用自己的后端。 - 第三方应用后端使用 Open Platform
appKey/appSecret给请求签名,再调用本框架开放接口。 - 第三方应用后端收到
verified=true后,再自行校验 nonce 是否存在、是否过期、是否已使用、业务域名和金额等字段是否可信。
不要把 Open Platform appSecret 放在浏览器、小程序或 App 客户端里。
浏览器侧钱包签名示例:
const [walletAddress] = await window.ethereum.request({ method: "eth_requestAccounts" });
const nonce = crypto.randomUUID();
const message = `Sign in to Example App\nNonce: ${nonce}`;
const signature = await window.ethereum.request({
method: "personal_sign",
params: [message, walletAddress]
});
await fetch("/your-server/api/wallet-signatures/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ walletAddress, nonce, message, signature })
});第三方服务端调用开放接口示例:
const body = JSON.stringify({ walletAddress, nonce, message, signature });
const path = "/api/v1/open/web3/chains/bsc/wallet-signatures/personal/verify";
const timestamp = Date.now();
const openNonce = crypto.randomUUID();
const openSignature = signOpenRequest({
method: "POST",
path,
body,
timestamp,
nonce: openNonce,
appSecret
});
const response = await fetch(`http://localhost:8000${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Open-App-Key": appKey,
"X-Open-Timestamp": String(timestamp),
"X-Open-Nonce": openNonce,
"X-Open-Signature": openSignature,
"X-Open-Signature-Method": "HMAC-SHA256"
},
body
});因此配置分层约定为:
rpc-urls:可公开节点,可用于开发者调试、开放平台链信息接口和文档示例。private-rpc-urls:私有节点,运行时优先用于链上查询、支付确认、事件扫描和交易广播,不对外返回。
如果 private-rpc-urls 为空,运行时会回退使用 rpc-urls。生产环境建议把内部结算、支付确认或事件扫描使用的节点放在 private-rpc-urls,开放平台只给开发者返回可公开或专门限流的 rpc-urls。
未注册 scope 的版本化开放请求会被过滤器拒绝;后续新增开放能力时,必须先注册路由 scope,再补充调用日志、限流、审计和文档。
实施顺序
- 后台应用、密钥、scope、调用日志模型已完成。
- 签名验签、防重放、IP 白名单、基础限流和开放平台访问过滤器已完成。
- Web3 v1 已接入链信息、余额、Token、授权、钱包、nonce、Gas、交易、事件、价格、钱包签名校验、批量转账计划、批量转账、资金归集计划和资金归集。
- 后续开放能力按能力域继续扩展,例如聚合支付、通知、存储、AI 或更多 Web3 写交易;资金类接口必须单独评估 scope、限流、审计、风控和计费。
当前状态
模块骨架、第一阶段数据底座、签名底层工具和后台管理 API 第一段已接入 Maven 聚合构建,当前包含:
open_platform_appopen_platform_packageopen_platform_app_secretopen_platform_scopeopen_platform_app_scopeopen_platform_nonceopen_platform_call_logopen_platform_usage_eventopen_platform_billing_accountopen_platform_billing_ledgeropen_platform_billing_statementopen_platform_billing_statement_line_item
当前模块已接入 application;这些表会通过 Liquibase includeAll 自动进入迁移范围。
当前已实现应用、应用套餐、套餐订单支付激活、应用密钥、应用 scope 授权、scope 字典、路由 scope 元数据、调用日志后台接口、计量事件、应用日 / 月配额、无套餐默认免费试用额度、用量聚合统计、成本快照统计、只读账单预览、正式月度账单落库、用户侧月度账单查询、账务账户和账务流水查询、内部幂等账务写入底座、AI 网关预占 / 实扣 / 失败释放、签名工具、HTTP 请求构建器、路由 scope 注册表、安全服务层、调用日志记录服务、内存限流服务、开放平台访问过滤器,以及 Web3 v1 链信息、余额、ERC-20 Token 信息、授权额度、钱包信息、nonce、Gas、交易、交易回执、确认数、事件日志、价格查询、钱包签名校验、批量转账计划、批量转账、资金归集计划和资金归集开放 API。下一阶段建议围绕更多 Web3 Token/NFT 写交易、聚合支付开放能力、支付补款 / 自动续费、更完整限流熔断和真实外部工具联调继续增强。