mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-09 03:17:52 +00:00
feat: project ini and docs updates
This commit is contained in:
parent
dfa66bbfbe
commit
27b7a4dad1
20 changed files with 5580 additions and 97 deletions
53
.gitignore
vendored
Normal file
53
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editors / IDEs
|
||||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Environment / local config
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.*.example
|
||||
|
||||
# Java / Spring Boot
|
||||
target/
|
||||
build/
|
||||
.gradle/
|
||||
out/
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# Keep wrapper binaries/config in VCS
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!mvnw
|
||||
!mvnw.cmd
|
||||
|
||||
# React / frontend
|
||||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
.vite/
|
||||
.eslintcache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Temporary files
|
||||
.tmp/
|
||||
tmp/
|
||||
|
|
@ -14,6 +14,38 @@
|
|||
|
||||
同时,一期必须提供 ClawHub CLI 协议兼容层:服务端需要暴露一组与 ClawHub CLI 兼容的 registry API,使现有 ClawHub CLI 在不修改或仅最小配置修改的前提下可完成 registry 侧查询、解析、下载、发布、校验等核心操作。
|
||||
|
||||
### 1.1 技能坐标体系(已冻结)
|
||||
|
||||
skillhub 内部使用 namespace 坐标模型:`@{namespace_slug}/{skill_slug}`。
|
||||
|
||||
ClawHub CLI 使用单一 slug 模型,slug 校验规则为 `[a-z0-9]([a-z0-9-]*[a-z0-9])?`,不允许 `/` 出现。
|
||||
|
||||
为同时满足两套模型,定义以下双向映射规则:
|
||||
|
||||
**映射规则:**
|
||||
|
||||
| skillhub 坐标 | 兼容层 canonical slug | 说明 |
|
||||
|---|---|---|
|
||||
| `@global/my-skill` | `my-skill` | 全局空间省略前缀,直接使用 skill slug |
|
||||
| `@team-name/my-skill` | `team-name--my-skill` | 团队空间使用 `{namespace_slug}--{skill_slug}` 格式 |
|
||||
|
||||
**约束规则:**
|
||||
- 分隔符为双连字符 `--`
|
||||
- skill slug 和 namespace slug 均禁止包含 `--`(在校验规则中追加此限制)
|
||||
- slug 格式校验更新为:`[a-z0-9]([a-z0-9-]*[a-z0-9])?`,且不得包含连续两个以上的连字符 `--`
|
||||
- 兼容层解析 canonical slug 时:包含 `--` 则拆分为 `namespace_slug` + `skill_slug`,不包含则视为 `@global/{slug}`
|
||||
- 冲突规则:如果 `@global/team-name--my-skill` 与 `@team-name/my-skill` 产生冲突,以 `--` 拆分优先(即优先解析为团队空间技能)。全局空间的 skill slug 禁止包含 `--` 以避免歧义
|
||||
- 保留字规则:namespace slug 保留词列表同样适用于 canonical slug 的 namespace 部分
|
||||
|
||||
**显示规则:**
|
||||
- Web 端始终显示完整坐标:`@global/my-skill`、`@team-name/my-skill`
|
||||
- ClawHub CLI 兼容层返回 canonical slug:`my-skill`、`team-name--my-skill`
|
||||
- skillhub 自有 CLI 支持两种格式输入,内部统一转换为 namespace 坐标
|
||||
|
||||
**Well-known 发现:**
|
||||
- skillhub 服务端提供 `/.well-known/clawhub.json`,返回 `{ "apiBase": "/api/compat/v1" }`
|
||||
- ClawHub CLI 通过此机制自动发现兼容层 API 基地址
|
||||
|
||||
## 2. 参考项目取舍
|
||||
|
||||
### 2.1 继承 ClawHub 的部分
|
||||
|
|
@ -61,7 +93,7 @@
|
|||
- 技能浏览、详情、下载(公共技能匿名可访问)
|
||||
- 标签管理(`latest` 系统保留只读 + 自定义标签人工维护)
|
||||
- 技能包文件校验与 SKILL.md 元数据抽取
|
||||
- 基于 MySQL 全文索引的搜索
|
||||
- 基于 PostgreSQL 全文索引的搜索
|
||||
|
||||
命名空间与组织:
|
||||
- 单一全局命名空间(`@global/skill-name`),由平台管理员管理,不支持多个平台级 namespace
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
- JDK: 21
|
||||
- Framework: Spring Boot 3.x(最新稳定版)
|
||||
- Security: Spring Security + spring-boot-starter-oauth2-client
|
||||
- Database: MySQL 8.x
|
||||
- Database: PostgreSQL 16.x
|
||||
- Cache/Session: Redis 7.x(一期必须依赖,用于 Session 存储 + 分布式锁 + 幂等去重)
|
||||
- Object Storage: S3 协议兼容对象存储
|
||||
- Search: MySQL Full-Text Search(一期)
|
||||
- Search: PostgreSQL Full-Text Search(一期)
|
||||
- Future Search: Elasticsearch / OpenSearch / Vector Search
|
||||
|
||||
## 2. 总体架构
|
||||
|
|
@ -22,9 +22,9 @@ server/
|
|||
├── skillhub-app # 启动、配置装配、Controller 聚合
|
||||
├── skillhub-domain # 领域模型 + 领域服务 + 应用服务
|
||||
├── skillhub-auth # OAuth2 认证 + RBAC + 授权判定
|
||||
├── skillhub-search # 搜索 SPI + MySQL 全文实现
|
||||
├── skillhub-search # 搜索 SPI + PostgreSQL 全文实现
|
||||
├── skillhub-storage # 对象存储抽象 + S3 实现
|
||||
└── skillhub-infra # MyBatis、通用工具、配置基础
|
||||
└── skillhub-infra # JPA、通用工具、配置基础
|
||||
```
|
||||
|
||||
## 4. 模块依赖方向(依赖倒置,禁止领域层依赖基础设施)
|
||||
|
|
@ -39,9 +39,9 @@ storage → (独立抽象) # 纯 SPI,不依赖 domain
|
|||
|
||||
核心原则:
|
||||
- domain 是最内层,不依赖任何其他模块,只定义接口和实体
|
||||
- infra 实现 domain 中定义的 Repository 接口(MyBatis Mapper)
|
||||
- infra 实现 domain 中定义的 Repository 接口(Spring Data JPA)
|
||||
- app 负责装配所有模块,通过 Spring 依赖注入将 infra 实现注入 domain 接口
|
||||
- 禁止 domain → infra 方向的依赖,避免领域层与 MyBatis、事件实现绑死
|
||||
- 禁止 domain → infra 方向的依赖,避免领域层与 JPA、事件实现绑死
|
||||
|
||||
## 5. 各模块职责
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ storage → (独立抽象) # 纯 SPI,不依赖 domain
|
|||
|
||||
### skillhub-search
|
||||
- SPI 接口:`SearchIndexService`, `SearchQueryService`, `SearchRebuildService`
|
||||
- 一期实现:`MysqlFullTextIndexService`, `MysqlFullTextQueryService`
|
||||
- 一期实现:`PostgresFullTextIndexService`, `PostgresFullTextQueryService`
|
||||
- 独立搜索文档表 `skill_search_document`
|
||||
- 未来扩展点:ES / 向量检索实现
|
||||
|
||||
|
|
@ -79,10 +79,9 @@ storage → (独立抽象) # 纯 SPI,不依赖 domain
|
|||
- 对象 key 规则(使用不可变 ID,避免命名空间变更导致 key 失效):
|
||||
- 正式路径:`skills/{skillId}/{versionId}/{filePath}`
|
||||
- 打包路径:`packages/{skillId}/{versionId}/bundle.zip`
|
||||
- 临时上传:`tmp/{uploadId}/{filePath}`(24h GC 清理)
|
||||
|
||||
### skillhub-infra
|
||||
- MyBatis-Plus Mapper 实现
|
||||
- Spring Data JPA Repository 实现
|
||||
- Repository 实现
|
||||
- 通用工具(ID 生成、时间、JSON 等)
|
||||
- Spring Events 异步事件基础设施
|
||||
|
|
@ -109,8 +108,13 @@ web/
|
|||
```
|
||||
skillhub/
|
||||
├── server/ # Maven 多模块 Java 后端
|
||||
│ └── Dockerfile # 后端多阶段构建
|
||||
├── web/ # React 前端
|
||||
├── Makefile # 顶层构建编排(dev / build / docker)
|
||||
│ ├── Dockerfile # 前端多阶段构建
|
||||
│ └── nginx.conf # Nginx 配置(SPA 路由 + API 反向代理)
|
||||
├── docker-compose.yml # 本地开发(仅依赖服务:PostgreSQL/Redis/MinIO)
|
||||
├── docker-compose.prod.yml # 完整部署(前后端 + 依赖服务)
|
||||
├── Makefile # 顶层构建编排(dev / build / docker / deploy)
|
||||
├── docs/ # 设计文档
|
||||
└── README.md
|
||||
```
|
||||
|
|
@ -130,14 +134,14 @@ skillhub/
|
|||
|
||||
| 组件 | 一期要求 | 职责 |
|
||||
|------|---------|------|
|
||||
| MySQL 8.x | 主从 | 主存储 |
|
||||
| PostgreSQL 16.x | 主从 | 主存储 |
|
||||
| Redis 7.x | Sentinel 或 Cluster | Session 存储 + 分布式锁 + 幂等去重 |
|
||||
| S3 兼容存储 | MinIO 或云厂商 S3 | 技能包文件 + 预打包 zip |
|
||||
| Ingress | Nginx Ingress Controller | 路由分发 + TLS 终止 |
|
||||
|
||||
## 10. 推荐的一期技术决策
|
||||
|
||||
- ORM:MyBatis-Plus
|
||||
- ORM:Spring Data JPA (Hibernate)
|
||||
- API 文档:Springdoc OpenAPI
|
||||
- 对象存储:MinIO / AWS S3 兼容接口
|
||||
- 异步任务:Spring Events + 异步线程池,后续视复杂度引入 MQ
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
- `TEAM` 类型对应部门/团队,可创建多个
|
||||
- 技能完整寻址:`@{namespace_slug}/{skill_slug}`
|
||||
- slug 唯一约束:`slug`
|
||||
- slug 格式校验:`[a-z0-9]([a-z0-9-]*[a-z0-9])?`,长度 2-64
|
||||
- slug 格式校验:`[a-z0-9]([a-z0-9-]*[a-z0-9])?`,长度 2-64,且不得包含连续两个以上的连字符 `--`(为兼容层坐标映射保留)
|
||||
- slug 保留词列表(用户创建 namespace 时不可使用):`admin`, `api`, `dashboard`, `search`, `auth`, `me`, `global`, `system`, `static`, `assets`, `health`
|
||||
- 系统内置 namespace(`@global`)在数据库初始化时由 Flyway 脚本预置,绕过 slug 校验规则。保留词校验仅作用于用户创建 namespace 的接口
|
||||
- 状态语义:
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
- owner 作为 MEMBER 时可管理自己创建的 skill(提交审核、编辑草稿)
|
||||
- owner 离职/换组后,namespace ADMIN 仍能完整管理所有技能
|
||||
- `rating_avg` / `rating_count` 冗余字段,避免每次查询聚合
|
||||
- `slug`:面向用户的 URL 标识,来自 SKILL.md 的 `name` 字段,首次发布后不可变更。slug 格式校验规则与 namespace slug 相同:`[a-z0-9]([a-z0-9-]*[a-z0-9])?`,同样适用保留词限制
|
||||
- `slug`:面向用户的 URL 标识,来自 SKILL.md 的 `name` 字段,首次发布后不可变更。slug 格式校验规则与 namespace slug 相同:`[a-z0-9]([a-z0-9-]*[a-z0-9])?`,同样适用保留词限制,且不得包含连续两个以上的连字符 `--`(为兼容层坐标映射保留)。全局空间(`@global`)下的 skill slug 额外禁止包含 `--`,以避免与兼容层 canonical slug 产生歧义
|
||||
- `source_skill_id`:仅在"团队技能提升到全局"场景下填充,记录原始团队空间的 skill ID,用于追溯来源
|
||||
- 提升关系的唯一事实来源是 `promotion_request` 表,UI 查询"是否已提升"通过 `SELECT ... FROM promotion_request WHERE source_skill_id=? AND status='APPROVED'` 判定
|
||||
|
||||
|
|
@ -90,7 +90,6 @@
|
|||
| manifest_json | json | 文件清单 |
|
||||
| parsed_metadata_json | json | SKILL.md frontmatter 解析结果 |
|
||||
| status | enum | `DRAFT` / `PENDING_REVIEW` / `PUBLISHED` / `REJECTED` / `YANKED` |
|
||||
| file_transfer_status | enum | `PENDING` / `COMPLETED` / `FAILED`,异步文件转正状态 |
|
||||
| reject_reason | varchar(512) | 拒绝原因 |
|
||||
| published_by | bigint | |
|
||||
| published_at | datetime | |
|
||||
|
|
@ -98,7 +97,6 @@
|
|||
|
||||
- `status` 覆盖完整审核生命周期
|
||||
- 状态机:`DRAFT → PENDING_REVIEW → PUBLISHED / REJECTED`,`PUBLISHED → YANKED`
|
||||
- `DRAFT → PENDING_REVIEW` 前置条件:`file_transfer_status = COMPLETED`
|
||||
- 唯一约束:`(skill_id, version)` 防止重复发布
|
||||
- `YANKED` 状态:已发布后撤回
|
||||
|
||||
|
|
@ -142,6 +140,7 @@
|
|||
- `latest` 是系统保留标签,只读,自动跟随 `skill.latest_version_id`,不允许 API 手动移动
|
||||
- 自定义标签(如 `beta`、`stable-2026q1`)允许人工创建和移动
|
||||
- 唯一约束:`(skill_id, tag_name)`
|
||||
- `target_version_id` 必须指向 `status = PUBLISHED` 的版本,应用层校验
|
||||
|
||||
### review_task
|
||||
|
||||
|
|
@ -161,6 +160,7 @@
|
|||
- 仅用于普通发布审核,"提升到全局"使用独立的 `promotion_request` 表
|
||||
- `version` 字段用于乐观锁,防止多 Pod 并发审核
|
||||
- 业务约束:同一 `skill_version_id` 在 `status=PENDING` 时只能存在一条记录,重复提交返回 409 Conflict。撤回(PENDING → 删除 review_task + skill_version 回退到 DRAFT)后才能再次提交
|
||||
- PostgreSQL 并发约束落地:通过唯一索引 `(skill_version_id)` + 软删除标记实现。`review_task` 表增加 `deleted` 字段(bigint, 默认 0),唯一索引改为 `(skill_version_id, deleted)`。撤回时将 `deleted` 设为 `id`(非零值),新提交时 `deleted=0`,利用唯一索引防止并发重复提交。或者采用更简单的方案:撤回时物理删除 review_task 记录,依赖 `INSERT` 的唯一约束 `(skill_version_id)` 防并发。PostgreSQL 还支持 partial unique index 方案:`CREATE UNIQUE INDEX ON review_task (skill_version_id) WHERE status = 'PENDING'`,更优雅地实现"PENDING 状态唯一"约束
|
||||
|
||||
### promotion_request
|
||||
|
||||
|
|
@ -183,6 +183,7 @@
|
|||
- 审批通过后填充 `target_skill_id`,指向全局空间新创建的 skill
|
||||
- `promotion_request` 是提升关系的唯一事实来源,skill 表不再冗余 `promoted_to_skill_id`
|
||||
- 业务约束:同一 `source_version_id` 在 `status=PENDING` 时只能存在一条记录,重复提交返回 409 Conflict
|
||||
- PostgreSQL 并发约束落地:与 `review_task` 类似,通过唯一索引防止并发重复提交。推荐使用 partial unique index:`CREATE UNIQUE INDEX ON promotion_request (source_version_id) WHERE status = 'PENDING'`,或增加 `deleted` 字段 + `(source_version_id, deleted)` 唯一约束,或采用物理删除 + `(source_version_id)` 唯一约束方案
|
||||
|
||||
### skill_star
|
||||
|
||||
|
|
@ -349,7 +350,7 @@
|
|||
| status | enum | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
MySQL Full-Text Index 建在 `(title, summary, keywords, search_text)` 上。
|
||||
PostgreSQL Full-Text Index:在 `skill_search_document` 表增加 `search_vector tsvector` 列,通过触发器或 `GENERATED ALWAYS AS` 自动维护,建立 GIN 索引。
|
||||
|
||||
## 3.4 幂等记录表
|
||||
|
||||
|
|
@ -366,7 +367,7 @@ MySQL Full-Text Index 建在 `(title, summary, keywords, search_text)` 上。
|
|||
| expires_at | datetime | 过期时间(默认 24h) |
|
||||
|
||||
- 流程:收到请求 → 插入 record(PROCESSING)→ 业务处理 → 更新为 COMPLETED + resource_id → 重复请求时查 record 返回已有结果
|
||||
- Redis 做快速去重缓存(SETNX),MySQL 做持久化兜底
|
||||
- Redis 做快速去重缓存(SETNX),PostgreSQL 做持久化兜底
|
||||
- 定时任务清理过期记录
|
||||
|
||||
## 3.5 关键索引设计
|
||||
|
|
|
|||
|
|
@ -277,6 +277,8 @@ public class OAuthClaimsExtractor {
|
|||
- 作用域:`skill:read`, `skill:publish`, `skill:delete`, `token:manage`
|
||||
- 天然无状态,多 Pod 安全
|
||||
|
||||
> **一期作用域说明(非最小权限)**:一期 Token 作用域为粗粒度动作级别,不与 namespace 绑定。Token 继承用户的全部权限——如果用户是某个 namespace 的 MEMBER,则该用户的任何 Token(只要包含 `skill:publish` scope)都可以向该 namespace 发布技能。这是有意的一期简化,不满足最小权限原则。后续版本计划引入 namespace 级别的 Token 作用域限定(如 `namespace:ai-team:skill:publish`),或通过 `api_token_scope` 子表实现 Token 与 namespace 的绑定。
|
||||
|
||||
## 6. RBAC 授权判定
|
||||
|
||||
```
|
||||
|
|
@ -477,3 +479,72 @@ window.location.href = '/oauth2/authorization/github'
|
|||
- 前端权限控制是 UX 优化,不是安全边界
|
||||
- 后端每个写操作接口独立校验权限,不信任前端判定
|
||||
- 前端隐藏按钮 ≠ 安全,用户可以直接调 API,后端必须拦截
|
||||
|
||||
## 10. 权限矩阵(完整)
|
||||
|
||||
以下矩阵列出每个 API 接口的权限判定来源,作为后端实现的唯一参考。
|
||||
|
||||
### 10.1 Public API(匿名可访问)
|
||||
|
||||
| 接口 | 匿名 | 已登录 | 判定逻辑 |
|
||||
|------|------|--------|---------|
|
||||
| `GET /api/v1/skills`(搜索) | PUBLIC 技能 | PUBLIC + NAMESPACE_ONLY(成员空间)+ PRIVATE(owner/admin) | `SearchVisibilityScope` 投影 |
|
||||
| `GET /api/v1/skills/{ns}/{slug}` | PUBLIC 技能 | 同上 | visibility + namespace 成员关系 |
|
||||
| `GET /api/v1/skills/{ns}/{slug}/versions` | PUBLIC 技能 | 同上 | 同上 |
|
||||
| `GET /api/v1/skills/{ns}/{slug}/download` | PUBLIC 技能 | 同上 | 同上 |
|
||||
| `GET /api/v1/skills/{ns}/{slug}/resolve` | PUBLIC 技能 | 同上 | 同上 |
|
||||
| `GET /api/v1/namespaces` | 全部 | 全部 | 无限制 |
|
||||
|
||||
### 10.2 Authenticated API
|
||||
|
||||
| 接口 | 所需权限 | 判定来源 |
|
||||
|------|---------|---------|
|
||||
| `POST /api/v1/skills/{ns}/{slug}/star` | 已登录 | Session/Token |
|
||||
| `POST /api/v1/skills/{ns}/{slug}/rating` | 已登录 | Session/Token |
|
||||
| `POST .../versions/{ver}/submit-review` | namespace MEMBER 以上 | `namespace_member.role` |
|
||||
| `POST .../versions/{ver}/withdraw-review` | 提交人本人 或 namespace ADMIN | `review_task.submitted_by` 或 `namespace_member.role` |
|
||||
| `PUT /api/v1/skills/{ns}/{slug}/tags/{tag}` | namespace ADMIN 以上 或 owner | `namespace_member.role` 或 `skill.owner_id` |
|
||||
| `POST /api/v1/skills/{ns}/{slug}/archive` | namespace ADMIN 以上 或 owner | `namespace_member.role` 或 `skill.owner_id` |
|
||||
| `DELETE .../versions/{ver}` | namespace ADMIN 以上 或 owner(仅 DRAFT/REJECTED) | `namespace_member.role` 或 `skill.owner_id` + `skill_version.status` |
|
||||
|
||||
### 10.3 CLI API
|
||||
|
||||
| 接口 | 所需 Token Scope | 额外判定 |
|
||||
|------|-----------------|---------|
|
||||
| `GET /api/v1/cli/whoami` | 任意有效 Token | 无 |
|
||||
| `POST /api/v1/cli/publish` | `skill:publish` | 用户是目标 namespace 的 MEMBER 以上 |
|
||||
|
||||
### 10.4 Admin API
|
||||
|
||||
| 接口 | 所需平台角色 | 判定来源 |
|
||||
|------|------------|---------|
|
||||
| `POST /api/v1/admin/reviews/{id}/approve` | SKILL_ADMIN / SUPER_ADMIN | `user_role_binding` → `role_permission` |
|
||||
| `POST /api/v1/admin/reviews/{id}/reject` | SKILL_ADMIN / SUPER_ADMIN | 同上 |
|
||||
| `POST /api/v1/admin/promotions/{id}/approve` | SKILL_ADMIN / SUPER_ADMIN | 同上 |
|
||||
| `POST /api/v1/admin/promotions/{id}/reject` | SKILL_ADMIN / SUPER_ADMIN | 同上 |
|
||||
| `PUT /api/v1/admin/users/{id}/roles` | USER_ADMIN / SUPER_ADMIN | 同上,且 USER_ADMIN 不可分配 SUPER_ADMIN |
|
||||
| `POST /api/v1/admin/users/{id}/approve` | USER_ADMIN / SUPER_ADMIN | 同上 |
|
||||
| `POST /api/v1/admin/users/{id}/ban` | USER_ADMIN / SUPER_ADMIN | 同上 |
|
||||
| `GET /api/v1/admin/audit-logs` | AUDITOR / SUPER_ADMIN | 同上 |
|
||||
|
||||
### 10.5 Namespace API
|
||||
|
||||
| 接口 | 所需 namespace 角色 | 判定来源 |
|
||||
|------|-------------------|---------|
|
||||
| `POST /api/v1/namespaces/{slug}/reviews/{id}/approve` | 该空间 ADMIN 以上 | `namespace_member.role` |
|
||||
| `POST /api/v1/namespaces/{slug}/reviews/{id}/reject` | 该空间 ADMIN 以上 | `namespace_member.role` |
|
||||
| `POST /api/v1/namespaces/{slug}/members` | 该空间 ADMIN 以上 | `namespace_member.role` |
|
||||
| `DELETE /api/v1/namespaces/{slug}/members/{userId}` | 该空间 ADMIN 以上 | `namespace_member.role` |
|
||||
| `POST .../skills/{skillId}/promote` | 该空间 ADMIN 以上 或 owner | `namespace_member.role` 或 `skill.owner_id` |
|
||||
|
||||
### 10.6 Compatibility API(Token 认证)
|
||||
|
||||
| 接口 | 所需 Token Scope | 额外判定 |
|
||||
|------|-----------------|---------|
|
||||
| `GET /api/compat/v1/whoami` | 任意有效 Token | 无 |
|
||||
| `GET /api/compat/v1/search` | 可选(匿名限 PUBLIC) | `SearchVisibilityScope` |
|
||||
| `GET /api/compat/v1/resolve` | 可选(匿名限 PUBLIC) | visibility |
|
||||
| `GET /api/compat/v1/download` | 可选(匿名限 PUBLIC) | visibility |
|
||||
| `POST /api/compat/v1/skills` | `skill:publish` | 用户是目标 namespace 的 MEMBER 以上(namespace 由 canonical slug 解析) |
|
||||
| `DELETE /api/compat/v1/skills/{slug}` | `skill:delete` | namespace ADMIN 以上 或 owner |
|
||||
| `POST /api/compat/v1/stars/{slug}` | 任意有效 Token | 无 |
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ ACL 投影计算规则:
|
|||
- 匿名用户:`includeAllPublic=true`,其余为空集,`userId=null`
|
||||
- 已登录用户:`includeAllPublic=true`,`memberNamespaceIds` = 用户所属空间,`adminNamespaceIds` = 用户是 ADMIN 以上的空间,`userId` = 当前用户 ID
|
||||
|
||||
一期 MySQL 实现中,`SearchVisibilityScope` 转换为 WHERE 条件:
|
||||
一期 PostgreSQL 实现中,`SearchVisibilityScope` 转换为 WHERE 条件:
|
||||
```sql
|
||||
WHERE (visibility = 'PUBLIC')
|
||||
OR (visibility = 'NAMESPACE_ONLY' AND namespace_id IN (:memberNamespaceIds))
|
||||
|
|
@ -75,7 +75,7 @@ WHERE (visibility = 'PUBLIC')
|
|||
|
||||
唯一约束:`(skill_id)`
|
||||
|
||||
MySQL Full-Text Index 建在 `(title, summary, keywords, search_text)` 上。
|
||||
PostgreSQL 全文搜索索引:表增加 `search_vector tsvector` 生成列,基于 `title`、`summary`、`keywords`、`search_text` 自动维护,建立 GIN 索引。详见第 7 节。
|
||||
|
||||
## 4 索引写入时机
|
||||
|
||||
|
|
@ -95,11 +95,18 @@ MySQL Full-Text Index 建在 `(title, summary, keywords, search_text)` 上。
|
|||
|
||||
这些场景不是简单换 provider 能解决的,需要改表结构和索引写入逻辑。
|
||||
|
||||
**一期搜索能力边界(产品限制):**
|
||||
- 搜索只基于 `latest_version_id` 对应版本的内容
|
||||
- 不支持按 version 或 tag 搜索内容
|
||||
- 搜索结果不区分 channel(`beta`、`stable` 等标签通道)
|
||||
- 用户通过 tag 安装的技能内容可能与搜索结果展示的内容不一致(搜索展示 latest,安装的是 tag 指向的版本)
|
||||
- 若要支持 channel-aware 搜索,必须升级到 version 级索引(二期 ES 实现)
|
||||
|
||||
### 5.2 演进阶段
|
||||
|
||||
| 阶段 | 实现 | 索引粒度 | 切换方式 |
|
||||
|------|------|---------|---------|
|
||||
| 一期 | MySQL Full-Text | 每 skill 一条(latest_version_id) | 默认 |
|
||||
| 一期 | PostgreSQL Full-Text (tsvector + GIN) | 每 skill 一条(latest_version_id) | 默认 |
|
||||
| 二期 | ES / OpenSearch | 每 skill_version 一条 + skill 聚合文档 | 配置 `search.provider=elasticsearch` |
|
||||
| 三期 | 向量检索 | 每 skill_version 多条(chunk 级) | 配置 `search.provider=vector` |
|
||||
| 四期 | 混合排序 | 关键词 + 向量混合 | 配置 `search.provider=hybrid` |
|
||||
|
|
@ -121,16 +128,28 @@ MySQL Full-Text Index 建在 `(title, summary, keywords, search_text)` 上。
|
|||
|
||||
`rebuildAll()` / `rebuildByNamespace()` 执行前获取 Redis 分布式锁(key: `search:rebuild:{scope}`,TTL: 10min),获取失败则跳过。
|
||||
|
||||
## 7 MySQL 全文搜索中文支持
|
||||
## 7 PostgreSQL 全文搜索中文支持
|
||||
|
||||
MySQL Full-Text Index 必须使用 ngram parser:
|
||||
PostgreSQL 全文搜索使用 `tsvector` + `tsquery` + GIN 索引:
|
||||
|
||||
```sql
|
||||
-- 增加 tsvector 生成列
|
||||
ALTER TABLE skill_search_document
|
||||
ADD FULLTEXT INDEX ft_search (title, summary, keywords, search_text)
|
||||
WITH PARSER ngram;
|
||||
ADD COLUMN search_vector tsvector
|
||||
GENERATED ALWAYS AS (
|
||||
setweight(to_tsvector('simple', coalesce(title, '')), 'A') ||
|
||||
setweight(to_tsvector('simple', coalesce(summary, '')), 'B') ||
|
||||
setweight(to_tsvector('simple', coalesce(keywords, '')), 'B') ||
|
||||
setweight(to_tsvector('simple', coalesce(search_text, '')), 'C')
|
||||
) STORED;
|
||||
|
||||
-- 建立 GIN 索引
|
||||
CREATE INDEX idx_search_vector ON skill_search_document USING GIN (search_vector);
|
||||
```
|
||||
|
||||
配置 `ngram_token_size=2`(my.cnf)。
|
||||
中文支持方案:
|
||||
- 一期使用 `simple` 分词配置(按空格和标点分词),对中文支持有限但零依赖
|
||||
- 如需更好的中文分词,可安装 `zhparser` 或 `pg_jieba` 扩展,替换为对应的 text search configuration
|
||||
- PostgreSQL 的 `tsvector` 支持权重(A/B/C/D),可对 title 赋予更高权重,提升搜索相关性
|
||||
|
||||
已知局限:ngram 分词精度不如专业搜索引擎,中文搜索体验有限。建议 Phase 2 完成后评估搜索效果,如不满足需求则在 Phase 3 提前引入 ES。
|
||||
已知局限:`simple` 分词对中文的精度不如专业搜索引擎。建议 Phase 2 完成后评估搜索效果,如不满足需求则在 Phase 3 提前引入 ES。
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## 1 发布流程
|
||||
|
||||
一期采用同步发布模型:上传、校验、存储、持久化在一次请求中同步完成。前端通过异步上传(带进度条)提升用户体验,但后端处理是同步的。
|
||||
|
||||
> **设计决策**:一期暂不考虑异步发布(uploadId、publishId、状态轮询、异步转正等)。一期技能包为文本资源包,体积有限(上限 10MB),同步处理足以满足需求。如后续引入大文件或复杂校验流程,再考虑异步模型。
|
||||
|
||||
```
|
||||
用户提交发布
|
||||
│
|
||||
|
|
@ -16,20 +20,20 @@
|
|||
- [扩展点] PrePublishValidator 链(一期空实现)
|
||||
│
|
||||
▼
|
||||
③ 写入对象存储临时区(文件逐个上传到 `tmp/{uploadId}/{filePath}`,记录 SHA-256)
|
||||
③ 同步写入对象存储
|
||||
- 文件逐个上传到正式路径 `skills/{skillId}/{versionId}/{filePath}`,记录 SHA-256
|
||||
- 生成预打包 zip 到 `packages/{skillId}/{versionId}/bundle.zip`
|
||||
│
|
||||
▼
|
||||
④ 持久化数据
|
||||
- 创建 skill_version (status=DRAFT, file_transfer_status=PENDING)
|
||||
④ 持久化数据(与 ③ 在同一事务/请求中)
|
||||
- 创建或关联 skill 记录(首次发布时创建 skill)
|
||||
- 创建 skill_version (status=DRAFT)
|
||||
- 创建 skill_file 记录
|
||||
- 解析 SKILL.md frontmatter → parsed_metadata_json
|
||||
- 生成 manifest_json
|
||||
- 异步将文件从 `tmp/` 转正到 `skills/{skillId}/{versionId}/{filePath}`
|
||||
- 转正成功 → file_transfer_status=COMPLETED
|
||||
- 转正失败 → file_transfer_status=FAILED,记录失败原因
|
||||
│
|
||||
▼
|
||||
⑤ 提交审核(前置检查:file_transfer_status 必须为 COMPLETED,否则拒绝提审)
|
||||
⑤ 提交审核
|
||||
- skill_version.status → PENDING_REVIEW
|
||||
- 创建 review_task (status=PENDING)
|
||||
│
|
||||
|
|
@ -49,26 +53,13 @@
|
|||
|
||||
④ 和 ⑤ 分开,给用户一个检查草稿的机会。CLI 发布走同样的流程。
|
||||
|
||||
### 对象存储临时区与 GC
|
||||
### 对象存储写入策略
|
||||
|
||||
- 上传阶段文件写入 `tmp/{uploadId}/{filePath}`
|
||||
- 数据库事务提交成功后,异步将文件从 `tmp/` copy 到正式路径 `skills/{skillId}/{versionId}/{filePath}`,完成后删除 `tmp/` 副本
|
||||
- 定时 GC 任务:清理超过 24h 的 `tmp/` 前缀对象(覆盖事务失败、用户取消、流程中断等场景)
|
||||
- 如果数据库事务失败,`tmp/` 中的文件由 GC 自动清理,不产生孤儿对象
|
||||
|
||||
### 文件转正补偿机制
|
||||
|
||||
`skill_version` 增加 `file_transfer_status` 字段(`PENDING` / `COMPLETED` / `FAILED`),用于追踪异步转正状态。
|
||||
|
||||
安全门控:
|
||||
- 提交审核(DRAFT → PENDING_REVIEW):前置检查 `file_transfer_status = COMPLETED`,否则返回 400
|
||||
- 下载接口:前置检查 `file_transfer_status = COMPLETED`,否则返回 404
|
||||
|
||||
失败重试:
|
||||
- 转正失败时标记 `file_transfer_status = FAILED`
|
||||
- 定时任务每 5 分钟扫描 `file_transfer_status = PENDING` 且 `created_at > 5min ago` 或 `file_transfer_status = FAILED` 的记录,重试转正
|
||||
- 最多重试 3 次,超过后标记为 FAILED 并保留,用户可在草稿页看到"文件处理失败"提示,可手动触发重试或删除该版本重新上传
|
||||
- `tmp/` 中的源文件在转正成功前不删除,确保重试有源可用
|
||||
一期同步写入正式路径,不使用临时区:
|
||||
- 文件直接写入 `skills/{skillId}/{versionId}/{filePath}`
|
||||
- 如果数据库事务失败,对象存储中的文件成为孤儿对象
|
||||
- 定时 GC 任务:每天扫描对象存储中存在但数据库中无对应 `skill_file` 记录的文件,清理孤儿对象
|
||||
- 删除 DRAFT/REJECTED 版本时,同步清理对应的对象存储文件
|
||||
|
||||
### CLI publish 请求规范
|
||||
|
||||
|
|
@ -81,42 +72,28 @@ Parts:
|
|||
- auto_submit: boolean(可选,默认 false,为 true 时自动提交审核)
|
||||
```
|
||||
|
||||
CLI 默认行为:上传 → 创建 DRAFT → 自动提交审核(`auto_submit=true`)。
|
||||
一期同步响应:服务端同步完成上传、校验、存储、持久化,返回 `200 OK` + skill_version 信息。
|
||||
|
||||
CLI 默认行为:上传 → 创建 DRAFT → 自动提交审核(`auto_submit=true`),一次请求完成。
|
||||
Web 端默认行为:上传 → 创建 DRAFT → 用户预览确认 → 手动提交审核。
|
||||
|
||||
### CLI publish 异步协议
|
||||
|
||||
`auto_submit=true` 时,服务端在文件转正完成后自动提交审核,CLI 不需要额外调用 submit-review。
|
||||
|
||||
```
|
||||
CLI 调用 POST /api/v1/cli/publish (auto_submit=true)
|
||||
│
|
||||
▼
|
||||
服务端返回 202 Accepted + publishId + 初始状态
|
||||
│
|
||||
▼
|
||||
CLI 轮询 GET /api/v1/cli/publish/{publishId}/status
|
||||
│
|
||||
├── TRANSFERRING → 文件转正中,继续轮询(建议间隔 2s)
|
||||
├── SUBMITTED → 文件转正完成 + 已自动提交审核,CLI 结束
|
||||
├── DRAFT → auto_submit=false 时,文件转正完成但未提审,CLI 结束
|
||||
└── FAILED → 文件转正失败,返回错误原因,CLI 提示用户
|
||||
```
|
||||
|
||||
`/api/v1/cli/publish/{publishId}/status` 响应:
|
||||
`/api/v1/cli/publish` 响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"publishId": "uuid",
|
||||
"skillId": 456,
|
||||
"skillVersionId": 123,
|
||||
"fileTransferStatus": "COMPLETED",
|
||||
"versionStatus": "PENDING_REVIEW",
|
||||
"error": null
|
||||
"version": "1.2.0",
|
||||
"status": "PENDING_REVIEW",
|
||||
"namespace": "team-name",
|
||||
"slug": "my-skill"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`auto_submit=false` 时,`status` 为 `DRAFT`,用户需后续手动提交审核。
|
||||
|
||||
## 2 团队技能提升到全局空间(派生发布)
|
||||
|
||||
不直接修改原 skill 的 `namespace_id`,而是在全局空间创建新的 skill,保留来源追溯。原团队 skill 继续存在,安装坐标 `@team/skill` 不受影响。
|
||||
|
|
@ -177,8 +154,8 @@ CLI 轮询 GET /api/v1/cli/publish/{publishId}/status
|
|||
|
||||
一期使用原子 SQL 直接更新,可接受。如出现热点行瓶颈,切换为:
|
||||
1. Redis `INCR` 做实时计数(key: `skill:downloads:{skillId}`)
|
||||
2. 定时任务每 5 分钟批量回写 MySQL
|
||||
3. 查询时合并 MySQL 存量 + Redis 增量
|
||||
2. 定时任务每 5 分钟批量回写 PostgreSQL
|
||||
3. 查询时合并 PostgreSQL 存量 + Redis 增量
|
||||
|
||||
## 4 搜索流程
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
| 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}` | 命名空间详情 |
|
||||
|
||||
|
|
@ -56,7 +60,7 @@ Public API 的可见性规则:
|
|||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/api/v1/skills/{namespace}/{slug}/versions/{version}/submit-review` | 将 DRAFT 版本提交审核(前置:file_transfer_status=COMPLETED) |
|
||||
| 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 以上) |
|
||||
|
||||
|
|
@ -182,7 +186,182 @@ Admin API 按最小权限拆分,不再统一要求 SUPER_ADMIN:
|
|||
| CLI `install @team/skill` | `latest_version_id` | 等同于 `@latest` |
|
||||
| CLI `install @team/skill@beta` | `skill_tag` 查询 | 自定义标签指向的版本 |
|
||||
|
||||
## 7.9 Rate Limiting
|
||||
## 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
|
||||
|
||||
分两阶段实施:
|
||||
|
||||
|
|
@ -203,10 +382,24 @@ Admin API 按最小权限拆分,不再统一要求 SUPER_ADMIN:
|
|||
|
||||
触发限流时返回 `429 Too Many Requests` + `Retry-After` Header。
|
||||
|
||||
## 7.10 API 设计原则
|
||||
## 7.12 API 设计原则
|
||||
|
||||
- 统一响应格式:`{ code, message, data, timestamp }`
|
||||
### 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,用于兼容性测试和文档
|
||||
|
|
|
|||
|
|
@ -113,9 +113,33 @@ CLI 安装后在本地写入 `.astron/metadata.json`:
|
|||
|
||||
## 8.7 版本解析规则
|
||||
|
||||
skillhub 自有 CLI 支持完整 namespace 坐标:
|
||||
|
||||
```
|
||||
install @team/my-skill → 最新已发布版本(latest_version_id)
|
||||
install @team/my-skill@1.2.0 → 精确版本
|
||||
install @team/my-skill@latest → 等同于不带版本号(系统保留标签,只读)
|
||||
install @team/my-skill@beta → beta 标签(自定义标签)
|
||||
install my-skill → 等同于 @global/my-skill
|
||||
```
|
||||
|
||||
ClawHub CLI 通过兼容层使用 canonical slug:
|
||||
|
||||
```
|
||||
clawhub install my-skill → @global/my-skill 的最新版本
|
||||
clawhub install team-name--my-skill → @team-name/my-skill 的最新版本
|
||||
clawhub install my-skill@1.2.0 → @global/my-skill 的精确版本
|
||||
```
|
||||
|
||||
## 8.8 坐标映射与 ClawHub CLI 兼容
|
||||
|
||||
skillhub 内部使用 `@{namespace_slug}/{skill_slug}` 坐标,ClawHub CLI 使用单一 slug。映射规则详见 `00-product-direction.md` 1.1 节。
|
||||
|
||||
安装后的本地目录名始终使用 `skill.slug`(不含 namespace 前缀),确保与 OpenSkills/Claude 兼容客户端的互操作性。
|
||||
|
||||
| skillhub 坐标 | ClawHub canonical slug | 本地安装目录名 |
|
||||
|---|---|---|
|
||||
| `@global/my-skill` | `my-skill` | `my-skill/` |
|
||||
| `@team-name/my-skill` | `team-name--my-skill` | `my-skill/` |
|
||||
|
||||
注意:不同 namespace 下同名 skill 安装到本地时会产生目录冲突。skillhub CLI 应在安装时检测冲突并提示用户选择安装目录或使用别名。
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ window.location.href = "/oauth2/authorization/github"
|
|||
|
||||
## 7 关键交互
|
||||
|
||||
**技能详情页**:SKILL.md Markdown 渲染、右侧信息栏(版本/下载量/评分/收藏/标签/空间)、版本切换、安装命令一键复制。匿名用户可浏览和下载,收藏/评分按钮提示登录。
|
||||
**技能详情页**:SKILL.md Markdown 渲染、右侧信息栏(版本/下载量/评分/收藏/标签/空间)、版本切换、安装命令一键复制(同时展示 skillhub CLI 格式 `install @namespace/slug` 和 ClawHub CLI 格式 `install canonical-slug`)。匿名用户可浏览和下载,收藏/评分按钮提示登录。
|
||||
|
||||
**搜索页**:实时搜索(debounce 300ms)、技能卡片、排序(相关度/下载量/评分/最新)、命名空间过滤。匿名用户可搜索 PUBLIC 技能。
|
||||
**搜索页**:实时搜索(debounce 300ms)、技能卡片、排序(相关度/下载量/评分/最新)、命名空间过滤。匿名用户可搜索 PUBLIC 技能。注意:一期搜索仅基于 latest 版本内容,不支持按 tag/version 搜索(详见 `04-search-architecture.md` 5.1 节)。
|
||||
|
||||
**审核页面**:左侧列表 + 右侧内容预览(Markdown + 文件树)、通过/拒绝 + 意见输入。
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@
|
|||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌──────────────┐
|
||||
│ MySQL │ │ Redis │ │ S3 / MinIO │
|
||||
│ (主从) │ │ │ │ │
|
||||
│ PostgreSQL│ │ Redis │ │ S3 / MinIO │
|
||||
│ (主从) │ │ │ │ │
|
||||
└────────┘ └────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
## 2 服务配置
|
||||
|
||||
- 无状态设计,所有状态存储在 MySQL / Redis / S3
|
||||
- 无状态设计,所有状态存储在 PostgreSQL / Redis / S3
|
||||
- 健康检查:`/actuator/health`(liveness + readiness 分离)
|
||||
- 优雅停机:`spring.lifecycle.timeout-per-shutdown-phase=30s`
|
||||
- JVM:`-XX:MaxRAMPercentage=75.0`
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
| Profile | 用途 | 特点 |
|
||||
|---------|------|------|
|
||||
| `local` | 本地开发 | 本地 MySQL/MinIO,Mock OAuth(见下方说明) |
|
||||
| `local` | 本地开发 | Docker Compose 一键启动(PostgreSQL/Redis/MinIO),Mock OAuth(见下方说明) |
|
||||
| `dev` | 开发环境 | 共享基础设施,GitHub OAuth 测试应用 |
|
||||
| `staging` | 预发布 | 与生产同构 |
|
||||
| `prod` | 生产 | 多 Pod,完整基础设施 |
|
||||
|
|
@ -50,6 +50,354 @@
|
|||
|
||||
MockAuthFilter 仅在 `local` profile 激活,通过 `@Profile("local")` 注解保证不会泄漏到其他环境。
|
||||
|
||||
### Docker Compose 一键启动
|
||||
|
||||
项目提供两套 Docker Compose 配置,分别用于本地开发和完整部署。
|
||||
|
||||
#### docker-compose.yml — 本地开发(仅依赖服务)
|
||||
|
||||
本地开发时前后端在宿主机运行,Docker Compose 只拉起依赖服务:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml(项目根目录)
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_DB: skillhub
|
||||
POSTGRES_USER: skillhub
|
||||
POSTGRES_PASSWORD: skillhub_dev
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001" # MinIO Console
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
command: server /data --console-address ":9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
minio_data:
|
||||
```
|
||||
|
||||
#### docker-compose.prod.yml — 完整部署(前后端 + 依赖服务)
|
||||
|
||||
开发完成后,通过 `docker compose -f docker-compose.prod.yml up -d` 一键打包并启动整个系统:
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml(项目根目录)
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_DB: skillhub
|
||||
POSTGRES_USER: skillhub
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-skillhub_prod}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U skillhub"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
command: server /data --console-address ":9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
server:
|
||||
build:
|
||||
context: ./server
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: prod
|
||||
DATABASE_URL: jdbc:postgresql://postgres:5432/skillhub
|
||||
DATABASE_USERNAME: skillhub
|
||||
DATABASE_PASSWORD: ${DB_PASSWORD:-skillhub_prod}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_ACCESS_KEY: ${MINIO_ROOT_USER:-minioadmin}
|
||||
S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
S3_BUCKET: skillhub
|
||||
OAUTH2_GITHUB_CLIENT_ID: ${OAUTH2_GITHUB_CLIENT_ID}
|
||||
OAUTH2_GITHUB_CLIENT_SECRET: ${OAUTH2_GITHUB_CLIENT_SECRET}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ./web
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
server:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
minio_data:
|
||||
```
|
||||
|
||||
#### 前后端 Dockerfile
|
||||
|
||||
后端 Dockerfile(`server/Dockerfile`):
|
||||
```dockerfile
|
||||
FROM maven:3.9-eclipse-temurin-21 AS build
|
||||
WORKDIR /app
|
||||
COPY pom.xml .
|
||||
COPY skillhub-app/pom.xml skillhub-app/
|
||||
COPY skillhub-domain/pom.xml skillhub-domain/
|
||||
COPY skillhub-auth/pom.xml skillhub-auth/
|
||||
COPY skillhub-search/pom.xml skillhub-search/
|
||||
COPY skillhub-storage/pom.xml skillhub-storage/
|
||||
COPY skillhub-infra/pom.xml skillhub-infra/
|
||||
RUN mvn dependency:go-offline -B
|
||||
COPY . .
|
||||
RUN mvn package -DskipTests -B
|
||||
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/skillhub-app/target/*.jar app.jar
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
|
||||
```
|
||||
|
||||
前端 Dockerfile(`web/Dockerfile`):
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
```
|
||||
|
||||
前端 Nginx 配置(`web/nginx.conf`):
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# SPA 路由回退
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# API 反向代理到后端
|
||||
location /api/ {
|
||||
proxy_pass http://server:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# OAuth2 回调反向代理
|
||||
location /oauth2/ {
|
||||
proxy_pass http://server:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location /login/oauth2/ {
|
||||
proxy_pass http://server:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Well-known 发现端点
|
||||
location /.well-known/ {
|
||||
proxy_pass http://server:8080;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Spring Boot 配置文件分层
|
||||
|
||||
```
|
||||
server/skillhub-app/src/main/resources/
|
||||
├── application.yml # 公共配置(所有 profile 共享)
|
||||
├── application-local.yml # 本地开发(Docker Compose 服务地址)
|
||||
├── application-dev.yml # 开发环境
|
||||
├── application-staging.yml # 预发布
|
||||
└── application-prod.yml # 生产
|
||||
```
|
||||
|
||||
`application.yml`(公共配置):
|
||||
```yaml
|
||||
spring:
|
||||
application:
|
||||
name: skillhub
|
||||
jpa:
|
||||
open-in-view: false
|
||||
hibernate:
|
||||
ddl-auto: validate # 由 Flyway 管理 schema,Hibernate 仅校验
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
|
||||
server:
|
||||
shutdown: graceful
|
||||
|
||||
spring.lifecycle.timeout-per-shutdown-phase: 30s
|
||||
```
|
||||
|
||||
`application-local.yml`(本地开发,对应 Docker Compose):
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/skillhub
|
||||
username: skillhub
|
||||
password: skillhub_dev
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
jpa:
|
||||
show-sql: true
|
||||
|
||||
skillhub:
|
||||
storage:
|
||||
type: s3
|
||||
endpoint: http://localhost:9000
|
||||
access-key: minioadmin
|
||||
secret-key: minioadmin
|
||||
bucket: skillhub
|
||||
region: us-east-1
|
||||
access-policy:
|
||||
mode: OPEN # 本地开发默认开放准入
|
||||
```
|
||||
|
||||
`application-prod.yml`(生产环境,凭证从环境变量/K8s Secret 注入):
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: ${DATABASE_URL}
|
||||
username: ${DATABASE_USERNAME}
|
||||
password: ${DATABASE_PASSWORD}
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST}
|
||||
port: ${REDIS_PORT:6379}
|
||||
jpa:
|
||||
show-sql: false
|
||||
|
||||
skillhub:
|
||||
storage:
|
||||
type: s3
|
||||
endpoint: ${S3_ENDPOINT}
|
||||
access-key: ${S3_ACCESS_KEY}
|
||||
secret-key: ${S3_SECRET_KEY}
|
||||
bucket: ${S3_BUCKET:skillhub}
|
||||
region: ${S3_REGION:us-east-1}
|
||||
```
|
||||
|
||||
### 本地开发启动流程
|
||||
|
||||
```bash
|
||||
# 1. 启动依赖服务
|
||||
docker compose up -d
|
||||
|
||||
# 2. 启动后端(自动执行 Flyway 迁移)
|
||||
cd server && ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
|
||||
# 3. 启动前端
|
||||
cd web && pnpm dev
|
||||
```
|
||||
|
||||
### 完整部署(一键打包 + 启动)
|
||||
|
||||
```bash
|
||||
# 构建并启动所有服务(前后端 + 依赖)
|
||||
docker compose -f docker-compose.prod.yml up -d --build
|
||||
|
||||
# 仅重新构建并重启应用服务(依赖服务不重启)
|
||||
docker compose -f docker-compose.prod.yml up -d --build server web
|
||||
|
||||
# 停止所有服务
|
||||
docker compose -f docker-compose.prod.yml down
|
||||
|
||||
# 停止并清除数据卷(慎用)
|
||||
docker compose -f docker-compose.prod.yml down -v
|
||||
```
|
||||
|
||||
### Makefile 命令
|
||||
|
||||
```bash
|
||||
make dev # docker compose up -d + 后端 + 前端(本地开发)
|
||||
make dev-down # docker compose down
|
||||
make build # 构建后端 JAR + 前端 dist
|
||||
make docker # 构建前后端 Docker 镜像
|
||||
make deploy # docker compose -f docker-compose.prod.yml up -d --build
|
||||
make deploy-down # docker compose -f docker-compose.prod.yml down
|
||||
make generate-api # 生成 OpenAPI 类型
|
||||
```
|
||||
|
||||
## 4 配置管理
|
||||
|
||||
- 敏感配置:K8s Secret(数据库/Redis/S3 凭证、OAuth2 Client ID/Secret)
|
||||
|
|
@ -68,6 +416,8 @@ requestId 透传:Ingress 注入 → Spring Filter 读取放入 MDC → 日志
|
|||
|
||||
## 6 构建与发布
|
||||
|
||||
### CI Pipeline 构建
|
||||
|
||||
```
|
||||
代码提交 → CI Pipeline
|
||||
├── server: mvn package → JAR
|
||||
|
|
@ -75,14 +425,30 @@ requestId 透传:Ingress 注入 → Spring Filter 读取放入 MDC → 日志
|
|||
│
|
||||
▼
|
||||
Docker 多阶段构建
|
||||
├── server → openjdk:21-jre-slim
|
||||
├── server → eclipse-temurin:21-jre-alpine
|
||||
└── web → nginx:alpine
|
||||
│
|
||||
▼
|
||||
推送镜像 → K8s 滚动更新
|
||||
```
|
||||
|
||||
Makefile 顶层命令:`make dev-server`, `make dev-web`, `make build`, `make docker`, `make generate-api`
|
||||
### Docker Compose 完整部署
|
||||
|
||||
```
|
||||
make deploy
|
||||
│
|
||||
▼
|
||||
docker compose -f docker-compose.prod.yml up -d --build
|
||||
│
|
||||
├── 构建 server 镜像(Maven 多阶段构建 → JRE 运行)
|
||||
├── 构建 web 镜像(pnpm build → Nginx 静态服务 + 反向代理)
|
||||
├── 拉起 PostgreSQL / Redis / MinIO
|
||||
├── 等待依赖服务健康检查通过
|
||||
├── 启动 server(自动执行 Flyway 迁移)
|
||||
└── 启动 web(Nginx 代理 API 到 server)
|
||||
```
|
||||
|
||||
Makefile 顶层命令:`make dev`, `make dev-down`, `make build`, `make docker`, `make deploy`, `make deploy-down`, `make generate-api`
|
||||
|
||||
## 7 数据库迁移
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@
|
|||
|
||||
产出:架构设计文档、数据库 DDL、API OpenAPI spec 草案、前端线框图
|
||||
|
||||
已冻结决策:
|
||||
- 技能坐标体系:`@{namespace_slug}/{skill_slug}`,兼容层使用 `--` 双连字符映射(详见 `00-product-direction.md` 1.1 节)
|
||||
- 一期同步发布模型,暂不考虑异步发布
|
||||
- API Token 一期继承用户全部权限(非最小权限),后续版本细化
|
||||
- ClawHub CLI 兼容层基地址 `/api/compat/v1`,通过 `/.well-known/clawhub.json` 发现
|
||||
|
||||
## Phase 1:工程骨架 + 认证打通
|
||||
|
||||
### 后端
|
||||
|
|
@ -42,9 +48,9 @@
|
|||
|
||||
- 命名空间 CRUD + 成员管理
|
||||
- 对象存储集成
|
||||
- 技能发布(上传 → 校验 → 存储 → draft)
|
||||
- 技能发布(上传 → 校验 → 存储 → draft,一期同步处理)
|
||||
- 技能查询(详情、版本、文件)、下载(打包 + 可见性检查,PUBLIC 匿名可下载)
|
||||
- 标签管理、搜索(MySQL Full-Text,匿名搜索限 PUBLIC)
|
||||
- 标签管理、搜索(PostgreSQL Full-Text,匿名搜索限 PUBLIC)
|
||||
- 异步事件基础设施
|
||||
- Rate Limiting 升级(应用层精细限流:按用户/端点分类,基于 Redis 滑动窗口)
|
||||
|
||||
|
|
@ -67,7 +73,9 @@
|
|||
- 团队技能提升到全局(promotion_request 流程)
|
||||
- 评分 + 收藏 + 计数器(原子更新)
|
||||
- CLI API(whoami、publish、resolve、check)
|
||||
- ClawHub CLI 协议兼容层(registry metadata、resolve、download、publish、check 等核心接口)
|
||||
- ClawHub CLI 协议兼容层(`/api/compat/v1` 端点:search、resolve、download、publish、skills CRUD、stars)
|
||||
- 兼容层 canonical slug 映射(`--` 双连字符规则)
|
||||
- `/.well-known/clawhub.json` 发现端点
|
||||
- 协议适配器与兼容性测试(针对 ClawHub CLI 的真实请求/响应样例)
|
||||
- 审计日志(同步落库)、幂等去重(idempotency_record + Redis)
|
||||
|
||||
|
|
@ -109,4 +117,4 @@
|
|||
| 搜索效果不佳 | SPI 架构允许随时切换实现 |
|
||||
| 前后端接口频繁变更 | OpenAPI spec 先行,类型自动生成 |
|
||||
| 新增 OAuth Provider | Spring Security OAuth2 原生多 Provider 支持,只需配置 + 属性映射 |
|
||||
| ClawHub CLI 协议细节与现有模型不完全一致 | 增加兼容适配层与协议回归测试,避免把 skillhub 内部模型直接暴露给兼容客户端 |
|
||||
| ClawHub CLI 协议细节与现有模型不完全一致 | 兼容层使用 `--` 双连字符 canonical slug 映射,独立 Controller 层适配,协议回归测试覆盖 |
|
||||
|
|
|
|||
4507
docs/superpowers/plans/2026-03-11-phase1-foundation-auth.md
Normal file
4507
docs/superpowers/plans/2026-03-11-phase1-foundation-auth.md
Normal file
File diff suppressed because it is too large
Load diff
65
server/pom.xml
Normal file
65
server/pom.xml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>skillhub-app</module>
|
||||
<module>skillhub-domain</module>
|
||||
<module>skillhub-auth</module>
|
||||
<module>skillhub-search</module>
|
||||
<module>skillhub-storage</module>
|
||||
<module>skillhub-infra</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-domain</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-auth</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-search</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-storage</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-infra</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
57
server/skillhub-app/pom.xml
Normal file
57
server/skillhub-app/pom.xml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>skillhub-app</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-infra</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
36
server/skillhub-auth/pom.xml
Normal file
36
server/skillhub-auth/pom.xml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skillhub-auth</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
19
server/skillhub-domain/pom.xml
Normal file
19
server/skillhub-domain/pom.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skillhub-domain</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
19
server/skillhub-infra/pom.xml
Normal file
19
server/skillhub-infra/pom.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skillhub-infra</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-domain</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
19
server/skillhub-search/pom.xml
Normal file
19
server/skillhub-search/pom.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skillhub-search</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-domain</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
13
server/skillhub-storage/pom.xml
Normal file
13
server/skillhub-storage/pom.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.skillhub</groupId>
|
||||
<artifactId>skillhub-parent</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skillhub-storage</artifactId>
|
||||
</project>
|
||||
Loading…
Add table
Reference in a new issue