skillhub/docs/06-api-design.md
2026-03-11 22:47:05 +08:00

405 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# skillhub API 设计
## 7.1 Public API(匿名可访问)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/skills` | 搜索/列表(匿名仅返回 PUBLIC 技能) |
| GET | `/api/v1/skills/{namespace}/{slug}` | 技能详情(PUBLIC 匿名可访问) |
| GET | `/api/v1/skills/{namespace}/{slug}/versions` | 版本列表 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}` | 版本详情 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/files` | 文件清单 |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/file?path=...` | 读取单个文件(query param 避免路径中 / 的解析问题) |
| GET | `/api/v1/skills/{namespace}/{slug}/download` | 下载默认安装版本(latest_version_id 指向的版本) |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/download` | 下载指定版本包 |
| GET | `/api/v1/skills/{namespace}/{slug}/resolve` | 解析技能版本(支持 query param: `version`、`tag`、`hash`) |
| GET | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}/download` | 按标签下载(解析标签指向的版本后下载) |
| GET | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}/files` | 按标签查看文件清单 |
| GET | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}/file?path=...` | 按标签读取单个文件 |
| GET | `/api/v1/namespaces` | 公开命名空间列表 |
| GET | `/api/v1/namespaces/{slug}` | 命名空间详情 |
Public API 的可见性规则:
- `PUBLIC` 技能:匿名和已登录用户均可访问
- `NAMESPACE_ONLY` 技能:仅该命名空间成员可访问(需登录)
- `PRIVATE` 技能:owner 本人 + 该 namespace 的 ADMIN 以上可访问(需登录)
## 7.2 Auth API(OAuth2 登录相关)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/oauth2/authorization/github` | 发起 GitHub OAuth 登录(Spring Security 内置) |
| GET | `/login/oauth2/code/github` | GitHub OAuth 回调(Spring Security 内置) |
| GET | `/api/v1/auth/me` | 当前用户信息(未登录返回 401) |
| POST | `/api/v1/auth/logout` | 登出(清除 Session) |
| GET | `/api/v1/auth/providers` | 可用的 OAuth Provider 列表(前端渲染登录按钮用) |
`/api/v1/auth/providers` 响应示例:
```json
{
"data": [
{ "id": "github", "name": "GitHub", "authorizationUrl": "/oauth2/authorization/github" }
]
}
```
前端根据此接口动态渲染登录按钮,新增 Provider 无需改前端代码。
## 7.3 Authenticated API(需登录)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/skills/{namespace}/{slug}/star` | 收藏 |
| DELETE | `/api/v1/skills/{namespace}/{slug}/star` | 取消收藏 |
| POST | `/api/v1/skills/{namespace}/{slug}/rating` | 评分 |
| GET | `/api/v1/me/stars` | 我的收藏列表 |
| GET | `/api/v1/me/skills` | 我发布的技能列表 |
### 草稿与审核提交
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/submit-review` | 将 DRAFT 版本提交审核 |
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/withdraw-review` | 撤回提审(PENDING_REVIEW → DRAFT,同时删除关联的 PENDING review_task) |
| GET | `/api/v1/skills/{namespace}/{slug}/versions/{version}/draft` | 查看草稿详情(owner 或 namespace ADMIN 以上) |
### 标签管理
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/skills/{namespace}/{slug}/tags` | 列出标签 |
| PUT | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}` | 创建/移动自定义标签(`latest` 为系统保留标签,不可通过此接口操作) |
| DELETE | `/api/v1/skills/{namespace}/{slug}/tags/{tagName}` | 删除自定义标签(`latest` 不可删) |
### 技能生命周期管理
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/skills/{namespace}/{slug}/archive` | 归档技能(namespace ADMIN 或 owner) |
| POST | `/api/v1/skills/{namespace}/{slug}/unarchive` | 恢复归档(namespace ADMIN 或 owner) |
| DELETE | `/api/v1/skills/{namespace}/{slug}/versions/{version}` | 删除 DRAFT/REJECTED 版本 |
## 7.4 Token API(需登录)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/tokens` | 创建 API Token |
| GET | `/api/v1/tokens` | 列出我的 Token |
| DELETE | `/api/v1/tokens/{id}` | 吊销 Token |
## 7.5 CLI API(API Token 认证)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/cli/whoami` | Token 对应的用户信息 |
| POST | `/api/v1/cli/publish` | 发布技能包(返回 202 + publishId) |
| GET | `/api/v1/cli/publish/{publishId}/status` | 查询发布状态(文件转正 + 提审进度) |
| POST | `/api/v1/cli/publish/submit-review` | 手动提交审核(auto_submit=false 时使用) |
| GET | `/api/v1/cli/resolve/{namespace}/{slug}` | 解析版本 |
| GET | `/api/v1/cli/check/{namespace}/{slug}/{version}` | 本地哈希与远端比对 |
### ClawHub CLI 协议兼容层
一期不仅提供 skillhub 自有 CLI API,还必须暴露一组兼容 ClawHub CLI 的 registry API。
- 目标:让现有 ClawHub CLI 可通过配置 registry base URL 直接对接 skillhub
- 范围:覆盖 ClawHub CLI 所依赖的查询、版本解析、下载、发布、校验等核心接口
- 要求:兼容层优先保持 ClawHub CLI 既有请求/响应语义;若内部领域模型不同,通过 adapter 层完成协议转换,而不是要求客户端适配 skillhub 私有协议
- 要求:兼容层纳入 OpenAPI 或独立兼容协议文档,并作为正式对外契约维护
- 要求:兼容层与 skillhub 自有 `/api/v1/cli/**` 并存,二者共享同一套权限、审计、限流与领域服务
- 非目标:前端页面不直接依赖兼容层;兼容层用于服务已有 ClawHub CLI 和相关自动化脚本
兼容层最少需要覆盖的能力类别:
- Registry metadata:技能查询、技能详情、版本列表、标签/默认版本解析
- Artifact resolution:按技能坐标或版本解析下载地址/下载流
- Publish workflow:包上传、发布状态查询、提交审核
- Integrity check:版本存在性校验、摘要/哈希比对、whoami/token 上下文确认
如 ClawHub CLI 的现有协议与 skillhub 自有接口存在差异,文档以“兼容 ClawHub CLI 协议”为准,skillhub 内部 API 可继续保持当前风格。
## 7.6 Admin API(需对应平台角色)
Admin API 按最小权限拆分,不再统一要求 SUPER_ADMIN:
### 技能治理(需 SKILL_ADMIN / SUPER_ADMIN)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/admin/reviews` | 待审核列表 |
| GET | `/api/v1/admin/reviews/{id}` | 审核详情 |
| POST | `/api/v1/admin/reviews/{id}/approve` | 通过审核 |
| POST | `/api/v1/admin/reviews/{id}/reject` | 拒绝审核 |
| GET | `/api/v1/admin/promotions` | 待审核提升申请列表 |
| GET | `/api/v1/admin/promotions/{id}` | 提升申请详情 |
| POST | `/api/v1/admin/promotions/{id}/approve` | 通过提升申请 |
| POST | `/api/v1/admin/promotions/{id}/reject` | 拒绝提升申请 |
| POST | `/api/v1/admin/skills/{id}/hide` | 隐藏技能 |
| POST | `/api/v1/admin/skills/{id}/unhide` | 恢复技能 |
| POST | `/api/v1/admin/skills/{id}/yank/{versionId}` | 撤回已发布版本 |
### 用户治理(需 USER_ADMIN / SUPER_ADMIN)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/admin/users` | 用户列表 |
| GET | `/api/v1/admin/users/{id}` | 用户详情 |
| PUT | `/api/v1/admin/users/{id}/roles` | 修改用户角色(USER_ADMIN 不可分配 SUPER_ADMIN) |
| POST | `/api/v1/admin/users/{id}/approve` | 审批待准入用户 |
| POST | `/api/v1/admin/users/{id}/disable` | 封禁用户 |
| POST | `/api/v1/admin/users/{id}/enable` | 解封用户 |
### 审计(需 AUDITOR / SUPER_ADMIN)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/admin/audit-logs` | 审计日志查询 |
## 7.7 Namespace 管理 API(需命名空间 OWNER 或 ADMIN)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/namespaces` | 创建命名空间 |
| PUT | `/api/v1/namespaces/{slug}` | 更新命名空间信息 |
| GET | `/api/v1/namespaces/{slug}/members` | 成员列表 |
| POST | `/api/v1/namespaces/{slug}/members` | 添加成员 |
| PUT | `/api/v1/namespaces/{slug}/members/{userId}` | 修改成员角色 |
| DELETE | `/api/v1/namespaces/{slug}/members/{userId}` | 移除成员 |
| GET | `/api/v1/namespaces/{slug}/reviews` | 该空间待审核列表 |
| POST | `/api/v1/namespaces/{slug}/reviews/{id}/approve` | 空间管理员审核通过 |
| POST | `/api/v1/namespaces/{slug}/reviews/{id}/reject` | 空间管理员审核拒绝 |
| POST | `/api/v1/namespaces/{slug}/skills/{skillId}/promote` | 申请提升到全局 |
## 7.8 `latest` 语义说明
`latest` 自动跟随最新已发布版本,不可手动移动。
- `skill.latest_version_id`:每次审核通过自动更新,始终指向最新 PUBLISHED 版本
- `latest` 标签:系统保留,只读,自动与 `latest_version_id` 同步
- 自定义标签(如 `beta`、`stable-2026q1`):允许人工创建和移动,用于固定安装通道
| 场景 | 使用字段 | 说明 |
|------|---------|------|
| 搜索索引内容 | `latest_version_id` | 搜索文档取最新已发布版本内容 |
| `/download`(不带版本号) | `latest_version_id` | 下载最新已发布版本 |
| CLI `install @team/skill` | `latest_version_id` | 等同于 `@latest` |
| CLI `install @team/skill@beta` | `skill_tag` 查询 | 自定义标签指向的版本 |
## 7.9 Resolve 接口说明
`GET /api/v1/skills/{namespace}/{slug}/resolve` 用于解析技能版本,支持以下 query param:
| 参数 | 类型 | 说明 |
|------|------|------|
| `version` | string | 精确版本号(如 `1.2.0`) |
| `tag` | string | 标签名(如 `beta`、`latest`) |
| `hash` | string | fingerprint 哈希,用于判断本地版本是否与 registry 同步 |
解析优先级:
1. `version` 和 `tag` 不可同时传,同时传返回 `400 Bad Request`
2. 仅传 `version`:精确匹配版本号
3. 仅传 `tag`:查询 `skill_tag` 表获取 `target_version_id`
4. 仅传 `hash`:遍历已发布版本,比对 fingerprint
5. 均不传:返回 `latest_version_id` 指向的版本
响应:
```json
{
"data": {
"skillId": 456,
"namespace": "team-name",
"slug": "my-skill",
"version": "1.2.0",
"versionId": 123,
"fingerprint": "sha256:abc123...",
"downloadUrl": "/api/v1/skills/team-name/my-skill/versions/1.2.0/download"
}
}
```
`hash` 匹配时额外返回 `"matched": true`,不匹配时返回最新版本信息 + `"matched": false`。
## 7.10 ClawHub CLI 兼容层 API
兼容层 API 基地址为 `/api/compat/v1`,通过 `/.well-known/clawhub.json` 发现。兼容层使用 canonical slug(双连字符映射规则,详见 `00-product-direction.md` 1.1 节)。
认证方式:`Authorization: Bearer <token>`,复用 skillhub API Token 体系。
### Well-known 发现
```
GET /.well-known/clawhub.json
响应:
{
"apiBase": "/api/compat/v1"
}
```
### 兼容层端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/compat/v1/whoami` | 当前用户信息 |
| GET | `/api/compat/v1/search` | 搜索技能 |
| GET | `/api/compat/v1/resolve` | 通过 slug + fingerprint 解析版本 |
| GET | `/api/compat/v1/download` | 下载技能 zip 包 |
| GET | `/api/compat/v1/skills` | 列出技能(分页) |
| POST | `/api/compat/v1/skills` | 发布技能(multipart/form-data) |
| GET | `/api/compat/v1/skills/{slug}` | 获取技能详情 |
| DELETE | `/api/compat/v1/skills/{slug}` | 软删除技能 |
| GET | `/api/compat/v1/skills/{slug}/versions` | 列出版本 |
| GET | `/api/compat/v1/skills/{slug}/versions/{version}` | 版本详情 |
| GET | `/api/compat/v1/skills/{slug}/file` | 获取单个文件内容 |
| POST | `/api/compat/v1/stars/{slug}` | 收藏 |
| DELETE | `/api/compat/v1/stars/{slug}` | 取消收藏 |
### 兼容层请求/响应格式
**GET `/api/compat/v1/whoami`**
```json
{
"handle": "username",
"displayName": "User Name",
"role": "user"
}
```
**GET `/api/compat/v1/search?q={keyword}&page={page}&limit={limit}`**
```json
{
"results": [
{
"slug": "my-skill",
"name": "My Skill",
"description": "...",
"author": { "handle": "username", "displayName": "User Name" },
"version": "1.2.0",
"downloadCount": 100,
"starCount": 50,
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-03-01T00:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
```
注意:兼容层返回的 `slug` 为 canonical slug 格式(全局空间直接返回 skill slug,团队空间返回 `namespace--skill`)。
**GET `/api/compat/v1/resolve?slug={slug}&hash={fingerprint}`**
```json
{
"slug": "my-skill",
"version": "1.2.0",
"fingerprint": "sha256:abc123...",
"matched": true
}
```
`hash` 不匹配时返回最新版本 + `"matched": false`。
**GET `/api/compat/v1/download?slug={slug}&version={version}`**
返回 zip 文件流。`version` 可选,不传时下载最新已发布版本。
**POST `/api/compat/v1/skills`**
```
Content-Type: multipart/form-data
Parts:
- file: zip 包
```
一期同步响应,返回发布结果:
```json
{
"slug": "my-skill",
"version": "1.0.0",
"status": "pending_review"
}
```
注意:ClawHub 原始协议使用两步发布(先获取 upload URL,再 JSON publish),skillhub 兼容层简化为单步 multipart 上传。如果 ClawHub CLI 的发布流程无法适配单步模式,需要额外实现 `/api/compat/v1/upload-url` + `/api/compat/v1/publish` 两步兼容端点。
**GET `/api/compat/v1/skills/{slug}`**
```json
{
"slug": "my-skill",
"name": "My Skill",
"description": "...",
"author": { "handle": "username", "displayName": "User Name" },
"version": "1.2.0",
"versions": ["1.0.0", "1.1.0", "1.2.0"],
"license": "MIT-0",
"downloadCount": 100,
"starCount": 50,
"starred": false,
"files": [
{ "path": "SKILL.md", "size": 1024 }
],
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-03-01T00:00:00Z"
}
```
### 兼容层适配说明
- 兼容层是独立的 Controller 层,内部调用与 native API 相同的领域服务
- 请求进入时将 canonical slug 转换为 `(namespace_id, skill_slug)` 坐标
- 响应返回时将内部坐标转换为 canonical slug
- 兼容层不暴露 namespace 概念,对 ClawHub CLI 透明
- 发布时如果 canonical slug 包含 `--`,解析为团队空间发布;否则发布到全局空间
- 兼容层的认证复用 skillhub API Token,ClawHub CLI 通过 `clawhub login` 获取 token 后即可使用
## 7.11 Rate Limiting
分两阶段实施:
### Phase 1:Ingress 层基础限流
通过 Nginx Ingress `limit-req` 按 IP 全局限流,覆盖认证、搜索、下载等匿名可访问接口,防止基本的滥用和爬虫。
### Phase 2:应用层精细限流
基于 Redis 滑动窗口,按用户/端点分类的精细限流。
| 端点类别 | 限流策略 |
|---------|---------|
| 搜索 API | 已登录 60 次/分钟,匿名 20 次/分钟(按 IP) |
| 下载 API | 已登录 120 次/分钟,匿名 30 次/分钟(按 IP) |
| 发布 API | 10 次/小时(按用户) |
| 认证 API | 30 次/分钟(按 IP) |
触发限流时返回 `429 Too Many Requests` + `Retry-After` Header。
## 7.12 API 设计原则
### Native API(`/api/v1/*`)
- 统一响应包裹:`{ code, message, data, timestamp }`
- 分页格式:`{ items, total, page, size }`
- 错误码体系:业务错误码 + HTTP 状态码配合
- 版本策略:URL path 版本 `/api/v1/`
- 幂等性:写操作通过 `X-Request-Id` + Redis 去重(TTL 24h)
### Compatibility API(`/api/compat/v1/*`)
- 响应格式完全遵循 ClawHub 协议,不套统一响应包裹
- 错误响应遵循 ClawHub 格式:`{ error: string, message: string }`
- 分页格式遵循 ClawHub 格式:`{ results, total, page, limit }`
### OpenAPI 文档分离
生成两份独立的 OpenAPI spec:
- `openapi-native.json`:skillhub Native API,用于前端 SDK 生成
- `openapi-compat-clawhub.json`:ClawHub 兼容层 API,用于兼容性测试和文档