主题
14 — 菜单同步(HMAC)
第三方将业务系统菜单 / 权限树节点写入门户 sys_permission,供角色授权与门户导航使用。
方向:你 → 门户(入向)。
代码锚点:OpenMenuController、OpenMenuHmacFilter、OpenMenuInboundService、PermissionMenuService.upsertFromOpenApi。
基路径:/api/open/menus
方法:全部为 POST(含删除语义的 /delete)。
管理端菜单 CRUD(
/api/system/menus)需门户登录 +system:menu:*,不是第三方入站面。
1. 鉴权头
| Header | 必填 | 说明 |
|---|---|---|
X-Client-Id | 是 | SSO client_id |
X-Timestamp | 是 | Unix 秒(epoch seconds) |
X-Signature | 是 | HMAC-SHA256 的 Hex(大小写不敏感比对) |
X-Idempotency-Key | 否 | 可选;记入 sync_menu_push_log,管理端「消息日志 · 菜单」可查;业务幂等键为 system_code + menuCode |
时间窗:±300 秒。超出返回 code=401「X-Timestamp 超出允许时间窗」。
Client 须:存在、启用、绑定 system_code、CONFIDENTIAL 且可解析 client_secret。否则 401/403。
system_code 始终取自 Client 绑定,请求体无法覆盖。
2. 签名算法
签名串(三个部分用 换行符 \n 拼接,含请求体原文):
text
{clientId}\n{timestamp}\n{rawBody}| 项 | 规则 |
|---|---|
| 算法 | HmacSHA256,密钥为 client_secret 的 UTF-8 字节 |
| 输出 | Hex 字符串 → 放入 X-Signature |
rawBody | HTTP 请求体原始字节按 UTF-8 解码后的字符串;须与实际上送 JSON 字节完全一致 |
与 02-事项推送-HMAC.md 算法一致,可复用同一签名工具。
3. 接口清单
| 方法 | 路径 | 语义 |
|---|---|---|
| POST | /api/open/menus/upsert | 按绑定 system_code + menuCode 创建或更新;可带 children(先父后子) |
| POST | /api/open/menus/delete | 按 menuCode 软删;仅 create_source=API |
成功响应 data:
| 字段 | 说明 |
|---|---|
permissionId | 门户 sys_permission.id(删除时可能为空) |
menuCode | 菜单编码 |
systemCode | Client 绑定系统编码 |
createSource | 恒为 API |
仅落库成功才 code=0。
4. 创建来源隔离(create_source)
| create_source | 含义 | 开放接口 |
|---|---|---|
API | 本接口写入 | 可 upsert / delete |
PAGE | 菜单管理页创建 | 禁止改/删 → code=409 |
PORTAL_APP | 门户应用连带生成 | 禁止改/删 → code=409 |
已存在同 menuCode 且来源非 API 时,upsert 返回 409,不会覆盖。
5. UPSERT — POST /api/open/menus/upsert
请求体字段
| 字段 | 必填 | 说明 |
|---|---|---|
menuCode | 是 | 节点唯一编码(字母数字 _ - :) |
type | 是 | directory / menu / button / link |
title | 是 | 显示标题 |
parentCode | 否 | 父节点 menuCode;根节点省略 |
permCode | 条件 | button 必填;其它可选 |
path | 条件 | directory / menu 必填 |
component | 条件 | menu 必填 |
link | 条件 | link 必填 |
icon / order / hideInMenu / keepAlive / affixTab / name / redirect / isAuth / extra | 否 | 同管理端菜单字段 |
children | 否 | 子节点数组;写入时先父后子;子节点未填 parentCode 时自动挂当前节点 |
示例
bash
BODY='{"menuCode":"hwy_home","type":"link","title":"高速首页","link":"https://highway.example.com/","permCode":"app:highway:view","order":1}'
TS=$(date +%s)
SIG=$(…HMAC Hex…)
curl -sS -X POST "https://portal.example.com/api/open/menus/upsert" \
-H "Content-Type: application/json" \
-H "X-Client-Id: demo-client" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-d "$BODY"树形示例(目录 + 子链接):
json
{
"menuCode": "hwy_root",
"type": "directory",
"title": "高速系统",
"path": "/highway",
"children": [
{
"menuCode": "hwy_toll",
"type": "link",
"title": "收费查询",
"link": "https://highway.example.com/toll",
"permCode": "HWY_TOLL_VIEW"
}
]
}6. DELETE — POST /api/open/menus/delete
json
{ "menuCode": "hwy_toll" }| 错误 | 说明 |
|---|---|
| 400 | 存在子菜单,无法删除 |
| 404 | 菜单不存在 |
| 409 | 非 API 来源,禁止删除 |
7. 边界
| 能力 | 方向 | 文档 |
|---|---|---|
| 菜单同步(本文) | 你 → 门户 | HMAC /api/open/menus |
| 组织/用户出向 | 门户 → 你 | 05-组织用户同步.md |
| 权限分配出向 | 门户 → 你 | 14-角色权限分配同步.md(权限分配同步) |
| 角色同步入向 | 你 → 门户 | 16-角色同步-HMAC.md |
| 管理端菜单 | 门户会话 | /api/system/menus(非开放面) |
门户应用连带菜单见内部稿 doc/43;开放写入统一打标 create_source=API。