主题
12 — 平台表 GraphQL 查询(HMAC)
第三方通过 HMAC 调用门户,对管理端已纳入 catalog 且审核授予的平台物理表执行只读 GraphQL 单表查询。
须先申请、后调用:未经审核通过的 Client 无 grant,GraphQL Schema 中看不到对应表。
设计方案:doc/51-通用单表GraphQL查询.md。
与 JDBC 视图区别:09-数据服务-JDBC视图-HMAC.md 查第三方库映射 JSON;本文查平台库白名单表。
查询基路径:/api/open/tables
查询方法:POST /graphql
1. 接入流程
text
1. 打开公开申请页 https://{portal}/open/data-query-apply
2. POST /api/portal/data-query/applications(选表、填用途与联系方式)
3. 等待管理端审核 → APPROVED(绑定 CONFIDENTIAL Client,写入 grant)
4. 使用 bound_client_id + client_secret 调 POST /api/open/tables/graphql| 阶段 | 路径 | 鉴权 |
|---|---|---|
| 可选表清单 | GET /api/portal/data-query/catalog/open | 无(仅 apply_open=1) |
| 提交申请 | POST /api/portal/data-query/applications | 无 |
| 进度查询 | GET /api/portal/data-query/applications?applyCode= | 无(持 applyCode) |
| GraphQL 查询 | POST /api/open/tables/graphql | HMAC |
公开申请页路由:/open/data-query-apply(前端独立页,非 /api)。
2. 申请 API(门户公开)
2.1 可选表 — GET /api/portal/data-query/catalog/open
响应 data 为数组:tableName、tableLabel(仅 catalog apply_open=1 且 enabled=1)。
2.2 提交 — POST /api/portal/data-query/applications
| 字段 | 必填 | 说明 |
|---|---|---|
orgName | 是 | 单位名称 |
contactName | 是 | 联系人 |
contactPhone | 是 | 电话 |
contactEmail | 否 | 邮箱 |
purpose | 是 | 用途说明 |
requestClientId | 否 | 期望复用的 client_id(须已存在且 CONFIDENTIAL) |
tableNames | 是 | 字符串数组;须在 open catalog 内 |
成功 data:applyCode、status=PENDING。
2.3 进度 — GET /api/portal/data-query/applications?applyCode=
| 字段 | 说明 |
|---|---|
applyCode | 申请单号 |
status | PENDING / APPROVED / REJECTED / REVOKED |
reviewComment | 审核意见(驳回/通过) |
boundClientId | 通过后绑定的 Client(方可调 GraphQL) |
tableNames | 申请明细 |
3. GraphQL 查询 — POST /api/open/tables/graphql
3.1 鉴权头
与 02-事项推送-HMAC.md、09-数据服务-JDBC视图-HMAC.md 完全一致:
| Header | 必填 | 说明 |
|---|---|---|
X-Client-Id | 是 | 审核通过绑定的 client_id |
X-Timestamp | 是 | Unix 秒 |
X-Signature | 是 | HMAC-SHA256 Hex |
签名串:
text
{clientId}\n{timestamp}\n{rawBody}时间窗:±300 秒。
Client 要求:
client_type=CONFIDENTIAL且启用- 已绑定
system_code - 审核通过后存在
sys_data_query_grant:principal_type=HMAC_CLIENT、principal_code={clientId}、enabled=1
未开通 grant_type=client_credentials。
3.2 请求 Body
| 字段 | 必填 | 说明 |
|---|---|---|
query | 是 | GraphQL 查询字符串 |
variables | 否 | 变量 JSON |
operationName | 否 | 多操作时指定 |
3.3 响应(ApiResult 例外)
本接口 不 使用门户统一 ApiResult 包装,直接返回 GraphQL 规范 JSON:
json
{
"data": {
"sysUser": {
"total": 100,
"pageNum": 1,
"pageSize": 20,
"records": [
{ "username": "zhangsan", "realName": "张三", "deptCode": "D001", "deptName": "研发部" }
]
}
},
"errors": null
}| 场景 | HTTP | body |
|---|---|---|
| 成功 | 200 | { "data": {...}, "errors": null } |
| 部分字段失败 | 200 | data + errors[](含 path、message) |
| 鉴权失败 | 200 或 401 | errors[].extensions.code=UNAUTHENTICATED |
| 无 grant / 表不可见 | 200 | errors 提示未知字段或 FORBIDDEN |
| 签名错误 | 200 | code 非 0 的 ApiResult(Filter 层拒绝,未进 GraphQL) |
注意:仅 GraphQL 执行成功进入引擎 的响应为
{data,errors};HMAC Filter 拒绝(Client 无效、签名错、非 CONFIDENTIAL)仍为ApiResult包装,与 02/09 一致。
3.4 示例
bash
BODY='{"query":"query { sysUser(pageNum:1, pageSize:10) { total records { username realName } } }"}'
TS=$(date +%s)
SIG=$(printf '%s\n%s\n%s' "$CLIENT_ID" "$TS" "$BODY" | openssl dgst -sha256 -hmac "$CLIENT_SECRET" | awk '{print $2}')
curl -sS -X POST "https://portal.example.com/api/open/tables/graphql" \
-H "Content-Type: application/json" \
-H "X-Client-Id: $CLIENT_ID" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-d "$BODY"4. Schema 与授权
- GraphQL Schema 动态生成:仅包含该 Client 有 grant 且 catalog
enabled=1的表。 - 未授权表:不出现在 Schema(查询未知字段报错,而非返回空列表)。
- 列:受 doc/42 字段元数据约束(开放面使用 Client 绑定
system_code下配置的默认字段权限或专用开放角色)。 - 行:受 doc/45/doc/46 与 catalog 锚点列约束。
5. 错误码摘要
| 场景 | 典型 |
|---|---|
| Client 非 CONFIDENTIAL | Filter:code=403,message 说明 Client 类型 |
| 无 grant | GraphQL FORBIDDEN |
| 表未在 catalog | 未知字段 |
| 分页超限 | pageSize > 200 → BAD_USER_INPUT |
| 申请未通过调 GraphQL | 无 grant,Schema 无表 |
6. 与 MCP 的边界
| 能力 | 路径 | 鉴权 |
|---|---|---|
| Agent 按登录用户查平台表 | POST /api/open/mcp/graphql | 工具令牌 + mcp:data:query(11-MCP.md) |
| 第三方系统级批查 | POST /api/open/tables/graphql | 本文 HMAC |
| 第三方库视图 JSON | POST /api/open/data/query | HMAC(09) |
7. 非目标
- 申请前调用 GraphQL
- PUBLIC Client、client_credentials
- Mutation / 任意 SQL
- 未审核表「先试再申请」