mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-27 11:14:59 +00:00
docs: project's documents
This commit is contained in:
parent
b36b375ba0
commit
db64bef098
11 changed files with 2230 additions and 0 deletions
118
docs/00-product-direction.md
Normal file
118
docs/00-product-direction.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Astron Skills 产品定位与 MVP 范围
|
||||
|
||||
## 1. 定位
|
||||
|
||||
单实例共享技能注册中心(Skills Hub / Registry),不是多租户平台。
|
||||
|
||||
- 平台只有一个共享注册中心实例
|
||||
- 隔离边界是 namespace,不是租户
|
||||
- `@global` 是平台级公共空间,由平台管理员管理
|
||||
- `@team-*` 是协作与治理边界(部门/团队),不是租户边界
|
||||
- 公共技能(visibility=PUBLIC)匿名可浏览和下载
|
||||
|
||||
以 ClawHub 为产品蓝本(继承产品模型,不照搬技术实现),以 OpenSkills 借鉴 SKILL.md 格式和目录结构约定(不兼容其客户端运行时行为)。
|
||||
|
||||
同时,一期必须提供 ClawHub CLI 协议兼容层:服务端需要暴露一组与 ClawHub CLI 兼容的 registry API,使现有 ClawHub CLI 在不修改或仅最小配置修改的前提下可完成 registry 侧查询、解析、下载、发布、校验等核心操作。
|
||||
|
||||
## 2. 参考项目取舍
|
||||
|
||||
### 2.1 继承 ClawHub 的部分
|
||||
|
||||
- Skill Registry 的整体产品边界
|
||||
- 技能版本、标签、下载的业务模型
|
||||
- 发布后治理机制(报告、标记、隐藏、撤回)
|
||||
- Web 浏览、详情页、上传发布、管理后台的功能切分
|
||||
- 公共查询 API 与 CLI API 的双通道设计
|
||||
- ClawHub CLI 所依赖的 registry API 协议面
|
||||
- Skill 元数据提取与服务端校验思路
|
||||
- 审计、收藏、评分、统计、运营标签等扩展位
|
||||
|
||||
不直接继承:
|
||||
- Convex 数据模型与运行时
|
||||
- 向量检索的一期实现方式
|
||||
|
||||
### 2.2 借鉴 OpenSkills 的部分
|
||||
|
||||
- `SKILL.md` 格式兼容(frontmatter + markdown body)
|
||||
- 技能包目录结构约定(SKILL.md + references/ + scripts/ + assets/)
|
||||
- 四级目录优先级(`.agent/skills` → `~/.agent/skills` → `.claude/skills` → `~/.claude/skills`)
|
||||
- 目录名作为 lookup key(安装后目录名 = skill slug)
|
||||
- AGENTS.md `<skill>` 描述块格式兼容
|
||||
- 目标:Astron CLI 安装的技能可被 OpenSkills/Claude 兼容客户端发现和使用
|
||||
|
||||
不直接继承:
|
||||
- 以 CLI 为中心的产品定位
|
||||
- "无服务端"的前提
|
||||
|
||||
## 3. 产品原则
|
||||
|
||||
- Hub 优先:服务端是核心,CLI 和 Agent 集成是入口能力
|
||||
- 兼容优先:兼容 `SKILL.md` 及常见目录约定
|
||||
- CLI 兼容优先:除 Astron CLI 外,一期明确要求实现 ClawHub CLI 协议兼容层
|
||||
- 分层优先:搜索、对象存储都必须有可替换边界
|
||||
- 开放认证:基于标准 OAuth2 协议,一期 GitHub 登录,架构支持后续扩展多 Provider
|
||||
- 审计优先:企业内部分发平台必须保留发布、下载、删除、授权等审计链路
|
||||
|
||||
## 4. 一期 MVP 功能
|
||||
|
||||
核心能力:
|
||||
- 技能发布(提交 → 审核 → 上线,每版本审核)
|
||||
- 技能版本管理(semver + 标签)
|
||||
- 技能浏览、详情、下载(公共技能匿名可访问)
|
||||
- 标签管理(`latest` 系统保留只读 + 自定义标签人工维护)
|
||||
- 技能包文件校验与 SKILL.md 元数据抽取
|
||||
- 基于 MySQL 全文索引的搜索
|
||||
|
||||
命名空间与组织:
|
||||
- 单一全局命名空间(`@global/skill-name`),由平台管理员管理,不支持多个平台级 namespace
|
||||
- 团队/部门命名空间(`@team-slug/skill-name`)
|
||||
- 命名空间成员管理
|
||||
- 创建技能时选择归属空间
|
||||
|
||||
审核流程:
|
||||
- 每版本审核策略
|
||||
- 分级审核:团队空间由团队管理员审核,全局空间由平台管理员审核
|
||||
- 团队技能提升到全局需平台管理员二次审核
|
||||
- 平台管理员拥有全局审核权
|
||||
- 一期纯人工审核,架构预留自动预检扩展点(`PrePublishValidator`)
|
||||
|
||||
认证与权限:
|
||||
- OAuth2 标准登录(一期 GitHub OAuth)
|
||||
- API Token(CLI / agent 使用)
|
||||
- ClawHub CLI 协议兼容层(registry API 兼容查询、解析、下载、发布、校验等核心接口)
|
||||
- RBAC 角色权限体系(平台角色:SUPER_ADMIN / SKILL_ADMIN / USER_ADMIN / AUDITOR + 命名空间角色)
|
||||
- 管理后台:用户角色管理、发布审核
|
||||
|
||||
社交功能:
|
||||
- 收藏(star)
|
||||
- 评分(1-5 分)
|
||||
|
||||
审计:
|
||||
- 发布、审核、下载、删除等关键操作审计
|
||||
|
||||
## 5. 一期明确不做(含后续规划)
|
||||
|
||||
- 评论 → Phase 5 上线,含举报机制
|
||||
- 自动安全扫描 → Phase 5 上线,接入 `PrePublishValidator` 扩展点
|
||||
- 举报/标记机制 → Phase 5 上线,配合评论和治理闭环
|
||||
- 向量搜索 → Phase 3(搜索演进路线)
|
||||
- 在线编辑器 → 暂不规划
|
||||
- Webhook/事件通知 → Phase 5(预留扩展点)
|
||||
- 技能依赖/兼容性声明 → 暂不规划(预留 `parsed_metadata_json` 字段)
|
||||
|
||||
### latest 语义说明
|
||||
|
||||
这是有意的产品决策,不是继承 ClawHub 的回滚模型:
|
||||
|
||||
- `latest` 自动跟随最新已发布版本,只读,不可手动移动
|
||||
- 回滚/稳定通道管理通过自定义标签实现(如 `stable`、`beta`、`stable-2026q1`)
|
||||
- ClawHub 的"通过移动 latest 做回滚"能力被替换为"通过自定义标签做通道管理"
|
||||
|
||||
## 6. 一期核心约束
|
||||
|
||||
- Skill 包视为"文本资源包",不接受二进制大文件
|
||||
- 技能包主入口文件固定为 `SKILL.md`
|
||||
- 元数据以 `SKILL.md` frontmatter 为主,数据库持久化解析结果
|
||||
- 文件内容原文存对象存储,检索面向数据库中的派生字段与可索引文本
|
||||
- Web 认证与 API Token 认证分离,但统一汇聚到平台用户体系
|
||||
- 公共技能(visibility=PUBLIC)匿名可浏览和下载,无需登录
|
||||
146
docs/01-system-architecture.md
Normal file
146
docs/01-system-architecture.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Astron Skills 系统架构设计
|
||||
|
||||
## 1. 技术基线
|
||||
|
||||
- JDK: 21
|
||||
- Framework: Spring Boot 3.x(最新稳定版)
|
||||
- Security: Spring Security + spring-boot-starter-oauth2-client
|
||||
- Database: MySQL 8.x
|
||||
- Cache/Session: Redis 7.x(一期必须依赖,用于 Session 存储 + 分布式锁 + 幂等去重)
|
||||
- Object Storage: S3 协议兼容对象存储
|
||||
- Search: MySQL Full-Text Search(一期)
|
||||
- Future Search: Elasticsearch / OpenSearch / Vector Search
|
||||
|
||||
## 2. 总体架构
|
||||
|
||||
采用单体优先、模块化单体设计。业务域清晰,一期规模不需要拆分微服务。
|
||||
|
||||
## 3. 后端模块结构
|
||||
|
||||
```
|
||||
server/
|
||||
├── astron-skills-app # 启动、配置装配、Controller 聚合
|
||||
├── astron-skills-domain # 领域模型 + 领域服务 + 应用服务
|
||||
├── astron-skills-auth # OAuth2 认证 + RBAC + 授权判定
|
||||
├── astron-skills-search # 搜索 SPI + MySQL 全文实现
|
||||
├── astron-skills-storage # 对象存储抽象 + S3 实现
|
||||
└── astron-skills-infra # MyBatis、通用工具、配置基础
|
||||
```
|
||||
|
||||
## 4. 模块依赖方向(依赖倒置,禁止领域层依赖基础设施)
|
||||
|
||||
```
|
||||
app → domain, auth, search, storage, infra
|
||||
infra → domain # infra 实现 domain 定义的 Repository 接口
|
||||
auth → domain # auth 引用 UserAccount 等领域实体
|
||||
search → domain # search 引用 SkillSearchDocument 等领域模型
|
||||
storage → (独立抽象) # 纯 SPI,不依赖 domain
|
||||
```
|
||||
|
||||
核心原则:
|
||||
- domain 是最内层,不依赖任何其他模块,只定义接口和实体
|
||||
- infra 实现 domain 中定义的 Repository 接口(MyBatis Mapper)
|
||||
- app 负责装配所有模块,通过 Spring 依赖注入将 infra 实现注入 domain 接口
|
||||
- 禁止 domain → infra 方向的依赖,避免领域层与 MyBatis、事件实现绑死
|
||||
|
||||
## 5. 各模块职责
|
||||
|
||||
### astron-skills-app
|
||||
- Spring Boot 启动类
|
||||
- Controller 分包:`controller.portal`(公开查询)、`controller.cli`(CLI API)、`controller.admin`(管理后台)
|
||||
- 全局异常处理、请求日志、OpenAPI 配置
|
||||
- 配置文件与环境 profile
|
||||
|
||||
### astron-skills-domain
|
||||
- 核心实体:Skill, SkillVersion, SkillFile, SkillTag, Namespace, NamespaceMember, ReviewTask, AuditLog, SkillStar, SkillRating
|
||||
- 领域服务:发布流程编排、审核状态机、命名空间管理、标签管理
|
||||
- 应用服务:面向 Controller 的用例编排
|
||||
- Repository 接口定义(实现在 infra)
|
||||
|
||||
### astron-skills-auth
|
||||
- Spring Security OAuth2 Client 配置(一期 GitHub,可扩展多 Provider)
|
||||
- `CustomOAuth2UserService`:OAuth2 用户 → 平台用户映射
|
||||
- `IdentityBindingService`:外部身份 → 平台用户绑定
|
||||
- Spring Session (Redis) 管理
|
||||
- API Token 签发、校验、吊销
|
||||
- RBAC:角色定义、权限点、资源级授权判定
|
||||
- 用户实体:UserAccount, IdentityBinding, ApiToken, Role, Permission, UserRoleBinding
|
||||
|
||||
### astron-skills-search
|
||||
- SPI 接口:`SearchIndexService`, `SearchQueryService`, `SearchRebuildService`
|
||||
- 一期实现:`MysqlFullTextIndexService`, `MysqlFullTextQueryService`
|
||||
- 独立搜索文档表 `skill_search_document`
|
||||
- 未来扩展点:ES / 向量检索实现
|
||||
|
||||
### astron-skills-storage
|
||||
- SPI 接口:`ObjectStorageService`
|
||||
- 一期实现:S3 兼容实现(MinIO / AWS S3)
|
||||
- 文件哈希校验、打包下载
|
||||
- 对象 key 规则(使用不可变 ID,避免命名空间变更导致 key 失效):
|
||||
- 正式路径:`skills/{skillId}/{versionId}/{filePath}`
|
||||
- 打包路径:`packages/{skillId}/{versionId}/bundle.zip`
|
||||
- 临时上传:`tmp/{uploadId}/{filePath}`(24h GC 清理)
|
||||
|
||||
### astron-skills-infra
|
||||
- MyBatis-Plus Mapper 实现
|
||||
- Repository 实现
|
||||
- 通用工具(ID 生成、时间、JSON 等)
|
||||
- Spring Events 异步事件基础设施
|
||||
|
||||
## 6. 前端工程结构
|
||||
|
||||
```
|
||||
web/
|
||||
├── src/
|
||||
│ ├── app/ # 路由、全局 Provider、布局
|
||||
│ ├── pages/ # 页面入口
|
||||
│ ├── features/ # 搜索、上传、版本管理、审核等业务功能
|
||||
│ ├── entities/ # skill、user、namespace 等领域展示逻辑
|
||||
│ ├── shared/ # 通用组件、hooks、工具
|
||||
│ └── api/ # openapi-typescript 生成的类型 + openapi-fetch 客户端
|
||||
├── package.json
|
||||
└── vite.config.ts
|
||||
```
|
||||
|
||||
技术栈:React 19 + TypeScript + Vite + shadcn/ui + Tailwind CSS + TanStack Query + TanStack Router + openapi-fetch
|
||||
|
||||
## 7. Monorepo 顶层结构
|
||||
|
||||
```
|
||||
astron-skills/
|
||||
├── server/ # Maven 多模块 Java 后端
|
||||
├── web/ # React 前端
|
||||
├── Makefile # 顶层构建编排(dev / build / docker)
|
||||
├── docs/ # 设计文档
|
||||
└── README.md
|
||||
```
|
||||
|
||||
简单分目录,各自独立构建,Makefile 串联。
|
||||
|
||||
## 8. 部署架构
|
||||
|
||||
同域部署,统一入口:
|
||||
- `https://skills.example.com/` → 前端静态资源
|
||||
- `https://skills.example.com/api/*` → 反向代理到 Spring Boot
|
||||
- 生产环境通过 Nginx 或网关统一接入
|
||||
|
||||
## 9. 分布式环境要求
|
||||
|
||||
本服务在 K8s 中部署多个 Pod,所有组件必须无状态设计。
|
||||
|
||||
| 组件 | 一期要求 | 职责 |
|
||||
|------|---------|------|
|
||||
| MySQL 8.x | 主从 | 主存储 |
|
||||
| Redis 7.x | Sentinel 或 Cluster | Session 存储 + 分布式锁 + 幂等去重 |
|
||||
| S3 兼容存储 | MinIO 或云厂商 S3 | 技能包文件 + 预打包 zip |
|
||||
| Ingress | Nginx Ingress Controller | 路由分发 + TLS 终止 |
|
||||
|
||||
## 10. 推荐的一期技术决策
|
||||
|
||||
- ORM:MyBatis-Plus
|
||||
- API 文档:Springdoc OpenAPI
|
||||
- 对象存储:MinIO / AWS S3 兼容接口
|
||||
- 异步任务:Spring Events + 异步线程池,后续视复杂度引入 MQ
|
||||
- 缓存/Session:Spring Session + Redis
|
||||
- 数据库迁移:Flyway
|
||||
- 认证:Spring Security OAuth2 Client(一期 GitHub)
|
||||
395
docs/02-domain-model.md
Normal file
395
docs/02-domain-model.md
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
# Astron Skills 领域模型与数据模型
|
||||
|
||||
## 3.1 核心实体
|
||||
|
||||
### namespace
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | 主键 |
|
||||
| slug | varchar(64) | URL 友好标识 |
|
||||
| display_name | varchar(128) | 展示名 |
|
||||
| type | enum | `GLOBAL` / `TEAM` |
|
||||
| description | text | 描述 |
|
||||
| avatar_url | varchar(512) | 头像 |
|
||||
| status | enum | `ACTIVE` / `FROZEN` / `ARCHIVED` |
|
||||
| created_by | bigint | 创建人 |
|
||||
| created_at | datetime | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
- `GLOBAL` 类型全局唯一(只有一个 `@global`),由平台管理员管理
|
||||
- `TEAM` 类型对应部门/团队,可创建多个
|
||||
- 技能完整寻址:`@{namespace_slug}/{skill_slug}`
|
||||
- slug 唯一约束:`slug`
|
||||
- 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 的接口
|
||||
- 状态语义:
|
||||
- `ACTIVE`:正常使用
|
||||
- `FROZEN`:冻结,只读不可发布新版本,已有技能仍可浏览/下载
|
||||
- `ARCHIVED`:归档,对外不可见
|
||||
|
||||
### namespace_member
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| namespace_id | bigint | |
|
||||
| user_id | bigint | |
|
||||
| role | enum | `OWNER` / `ADMIN` / `MEMBER` |
|
||||
| created_at | datetime | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
- `OWNER`:命名空间创建者,可转让
|
||||
- `ADMIN`:可审核该空间内的技能发布、管理成员
|
||||
- `MEMBER`:可在该空间内发布技能(提交审核)
|
||||
- 唯一约束:`(namespace_id, user_id)`,一个用户在一个空间只有一个角色
|
||||
|
||||
### skill
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| namespace_id | bigint | 所属命名空间 |
|
||||
| slug | varchar(128) | URL 友好标识 |
|
||||
| display_name | varchar(256) | |
|
||||
| summary | varchar(512) | |
|
||||
| owner_id | bigint | 主要维护人(可转让) |
|
||||
| source_skill_id | bigint | 派生来源(团队技能提升到全局时记录原 skill ID),nullable |
|
||||
| visibility | enum | `PUBLIC` / `NAMESPACE_ONLY` / `PRIVATE` |
|
||||
| status | enum | `ACTIVE` / `HIDDEN` / `ARCHIVED` |
|
||||
| latest_version_id | bigint | 最新已发布版本(自动跟随,每次发布自动更新) |
|
||||
| download_count | bigint | |
|
||||
| star_count | int | |
|
||||
| rating_avg | decimal(3,2) | 平均评分 |
|
||||
| rating_count | int | 评分人数 |
|
||||
| created_by | bigint | |
|
||||
| created_at | datetime | |
|
||||
| updated_by | bigint | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
- 唯一约束:`(namespace_id, slug)`
|
||||
- `owner_id` 语义为"主要维护人",可转让。权限主轴是 namespace role,不是 owner:
|
||||
- namespace ADMIN 对空间内所有 skill 有完整管理权(归档、版本管理、提升到全局),不受 owner 限制
|
||||
- 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])?`,同样适用保留词限制
|
||||
- `source_skill_id`:仅在"团队技能提升到全局"场景下填充,记录原始团队空间的 skill ID,用于追溯来源
|
||||
- 提升关系的唯一事实来源是 `promotion_request` 表,UI 查询"是否已提升"通过 `SELECT ... FROM promotion_request WHERE source_skill_id=? AND status='APPROVED'` 判定
|
||||
|
||||
### skill_version
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_id | bigint | |
|
||||
| version | varchar(32) | semver |
|
||||
| version_sort | bigint | 排序用数值 |
|
||||
| changelog | text | |
|
||||
| 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 | |
|
||||
| created_at | datetime | |
|
||||
|
||||
- `status` 覆盖完整审核生命周期
|
||||
- 状态机:`DRAFT → PENDING_REVIEW → PUBLISHED / REJECTED`,`PUBLISHED → YANKED`
|
||||
- `DRAFT → PENDING_REVIEW` 前置条件:`file_transfer_status = COMPLETED`
|
||||
- 唯一约束:`(skill_id, version)` 防止重复发布
|
||||
- `YANKED` 状态:已发布后撤回
|
||||
|
||||
版本号不可变性规则:
|
||||
|
||||
| 版本状态 | 版本号处理 |
|
||||
|---------|-----------|
|
||||
| DRAFT | 可删除该版本记录,重新使用同版本号 |
|
||||
| PENDING_REVIEW | 可撤回到 DRAFT,然后删除 |
|
||||
| REJECTED | 可删除该版本记录,重新使用同版本号 |
|
||||
| PUBLISHED | 版本号永久占用,不可复用 |
|
||||
| YANKED | 版本号永久占用,不可复用,版本列表中显示但标记为不可下载 |
|
||||
|
||||
### skill_file
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_version_id | bigint | |
|
||||
| file_path | varchar(512) | |
|
||||
| content_type | varchar(128) | |
|
||||
| size_bytes | bigint | |
|
||||
| sha256 | varchar(64) | |
|
||||
| object_key | varchar(512) | |
|
||||
| is_entry_file | boolean | |
|
||||
| created_at | datetime | |
|
||||
|
||||
### skill_tag
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_id | bigint | |
|
||||
| tag_name | varchar(64) | |
|
||||
| target_version_id | bigint | |
|
||||
| created_by | bigint | |
|
||||
| created_at | datetime | |
|
||||
| updated_by | bigint | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
- `latest` 是系统保留标签,只读,自动跟随 `skill.latest_version_id`,不允许 API 手动移动
|
||||
- 自定义标签(如 `beta`、`stable-2026q1`)允许人工创建和移动
|
||||
- 唯一约束:`(skill_id, tag_name)`
|
||||
|
||||
### review_task
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_version_id | bigint | 关联的版本 |
|
||||
| namespace_id | bigint | 所属空间(决定谁能审核) |
|
||||
| status | enum | `PENDING` / `APPROVED` / `REJECTED` |
|
||||
| version | int | 乐观锁版本号,默认 1 |
|
||||
| submitted_by | bigint | 提交人 |
|
||||
| reviewed_by | bigint | 审核人 |
|
||||
| review_comment | text | 审核意见 |
|
||||
| submitted_at | datetime | |
|
||||
| reviewed_at | datetime | |
|
||||
|
||||
- 仅用于普通发布审核,"提升到全局"使用独立的 `promotion_request` 表
|
||||
- `version` 字段用于乐观锁,防止多 Pod 并发审核
|
||||
- 业务约束:同一 `skill_version_id` 在 `status=PENDING` 时只能存在一条记录,重复提交返回 409 Conflict。撤回(PENDING → 删除 review_task + skill_version 回退到 DRAFT)后才能再次提交
|
||||
|
||||
### promotion_request
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| source_skill_id | bigint | 来源团队 skill |
|
||||
| source_version_id | bigint | 申请提升的版本 |
|
||||
| target_namespace_id | bigint | 目标全局 namespace |
|
||||
| target_skill_id | bigint | 审批通过后生成的全局 skill ID,nullable |
|
||||
| status | enum | `PENDING` / `APPROVED` / `REJECTED` |
|
||||
| version | int | 乐观锁版本号,默认 1 |
|
||||
| submitted_by | bigint | 提交人 |
|
||||
| reviewed_by | bigint | 审核人 |
|
||||
| review_comment | text | 审核意见 |
|
||||
| submitted_at | datetime | |
|
||||
| reviewed_at | datetime | |
|
||||
|
||||
- 完整表达"哪个团队 skill 的哪一版被申请提升到哪个全局空间"
|
||||
- 审批通过后填充 `target_skill_id`,指向全局空间新创建的 skill
|
||||
- `promotion_request` 是提升关系的唯一事实来源,skill 表不再冗余 `promoted_to_skill_id`
|
||||
- 业务约束:同一 `source_version_id` 在 `status=PENDING` 时只能存在一条记录,重复提交返回 409 Conflict
|
||||
|
||||
### skill_star
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_id | bigint | |
|
||||
| user_id | bigint | |
|
||||
| created_at | datetime | |
|
||||
|
||||
唯一约束:`(skill_id, user_id)`
|
||||
|
||||
### skill_rating
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_id | bigint | |
|
||||
| user_id | bigint | |
|
||||
| score | tinyint | 1-5 |
|
||||
| created_at | datetime | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
唯一约束:`(skill_id, user_id)`,每人每技能一条,可修改
|
||||
|
||||
### user_account
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| display_name | varchar(128) | |
|
||||
| email | varchar(256) | |
|
||||
| avatar_url | varchar(512) | |
|
||||
| status | enum | `ACTIVE` / `PENDING` / `DISABLED` / `MERGED` |
|
||||
| merged_to_user_id | bigint | 合并目标用户 ID,仅 MERGED 状态有值 |
|
||||
| created_at | datetime | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
- 状态语义:
|
||||
- `ACTIVE`:正常使用
|
||||
- `PENDING`:等待管理员审批(AccessPolicy 返回 PENDING_APPROVAL 时创建)
|
||||
- `DISABLED`:管理员封禁,登录后拒绝所有操作,返回 403
|
||||
- `MERGED`:已合并到其他账号,保留记录不物理删除,登录时自动跳转到合并目标账号
|
||||
- 授权层在每次请求时检查用户状态,非 `ACTIVE` 用户拒绝所有写操作
|
||||
|
||||
### identity_binding
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| user_id | bigint | |
|
||||
| provider_code | varchar(64) | 如 `github` |
|
||||
| subject | varchar(256) | OAuth Provider 返回的唯一用户标识 |
|
||||
| login_name | varchar(128) | 如 GitHub login |
|
||||
| extra_json | json | 原始扩展字段 |
|
||||
| created_at | datetime | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
- 唯一约束:`(provider_code, subject)`
|
||||
- 一期只接入 GitHub OAuth,但表结构支持后续扩展多个 OAuth Provider
|
||||
|
||||
### api_token
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| subject_type | varchar(32) | `USER`(一期)/ `SERVICE_ACCOUNT`(预留) |
|
||||
| subject_id | bigint | 关联主体 ID(一期等同于 user_id) |
|
||||
| user_id | bigint | 兼容字段,一期与 subject_id 相同 |
|
||||
| name | varchar(128) | Token 名称(必填),如"CI/CD"、"本地开发" |
|
||||
| token_prefix | varchar(16) | |
|
||||
| token_hash | varchar(64) | |
|
||||
| scope_json | json | |
|
||||
| expires_at | datetime | |
|
||||
| last_used_at | datetime | |
|
||||
| revoked_at | datetime | |
|
||||
| created_at | datetime | |
|
||||
|
||||
### audit_log
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| actor_user_id | bigint | |
|
||||
| action | varchar(64) | |
|
||||
| target_type | varchar(64) | |
|
||||
| target_id | bigint | |
|
||||
| request_id | varchar(64) | |
|
||||
| client_ip | varchar(64) | |
|
||||
| user_agent | varchar(512) | |
|
||||
| detail_json | json | |
|
||||
| created_at | datetime | |
|
||||
|
||||
## 3.2 RBAC 实体
|
||||
|
||||
一期即上线完整 RBAC,平台角色按最小权限拆分,避免所有治理能力压在单一超管角色上。
|
||||
|
||||
平台角色(一期内置,Flyway 预置):
|
||||
|
||||
| 角色 code | 说明 | 典型权限 |
|
||||
|-----------|------|---------|
|
||||
| `SUPER_ADMIN` | 平台超管,拥有所有权限 | 全部 |
|
||||
| `SKILL_ADMIN` | 技能治理:全局空间审核、提升审核、隐藏/撤回 | `review:approve`, `skill:manage`, `promotion:approve` |
|
||||
| `USER_ADMIN` | 用户治理:准入审批、封禁/解封、角色分配(不可分配 SUPER_ADMIN) | `user:manage`, `user:approve` |
|
||||
| `AUDITOR` | 审计只读:查看审计日志 | `audit:read` |
|
||||
|
||||
- 命名空间权限仍由 `namespace_member.role`(OWNER / ADMIN / MEMBER)决定,不走 RBAC 表
|
||||
- 一个用户可持有多个平台角色(多条 `user_role_binding`)
|
||||
- `SUPER_ADMIN` 隐含所有权限,代码中硬判定短路
|
||||
|
||||
### role
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| code | varchar(64) | `SUPER_ADMIN` / `SKILL_ADMIN` / `USER_ADMIN` / `AUDITOR` |
|
||||
| name | varchar(128) | 展示名 |
|
||||
| description | varchar(512) | |
|
||||
| is_system | boolean | 系统内置角色不可删除 |
|
||||
| created_at | datetime | |
|
||||
|
||||
### permission
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| code | varchar(128) | 如 `skill:publish`, `review:approve`, `user:manage` |
|
||||
| name | varchar(128) | |
|
||||
| group_code | varchar(64) | 权限分组 |
|
||||
|
||||
### role_permission
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| role_id | bigint | |
|
||||
| permission_id | bigint | |
|
||||
|
||||
### user_role_binding
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| user_id | bigint | |
|
||||
| role_id | bigint | |
|
||||
| created_at | datetime | |
|
||||
|
||||
## 3.3 搜索文档表
|
||||
|
||||
### skill_search_document
|
||||
|
||||
一个 skill 对应一条搜索文档,内容取 `latest_version_id` 对应版本。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_id | bigint | 唯一,一 skill 一条 |
|
||||
| namespace_id | bigint | 用于空间过滤 |
|
||||
| owner_id | bigint | 用于 PRIVATE 可见性判定 |
|
||||
| title | varchar(256) | |
|
||||
| summary | varchar(512) | |
|
||||
| keywords | varchar(512) | |
|
||||
| search_text | text | SKILL.md 正文 + frontmatter 拼接 |
|
||||
| visibility | enum | 冗余,避免搜索时 join |
|
||||
| status | enum | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
MySQL Full-Text Index 建在 `(title, summary, keywords, search_text)` 上。
|
||||
|
||||
## 3.4 幂等记录表
|
||||
|
||||
### idempotency_record
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| request_id | varchar(64) | 主键,客户端传入的 UUID v4 |
|
||||
| resource_type | varchar(64) | 如 `skill_version`, `api_token` |
|
||||
| resource_id | bigint | 业务操作产生的资源 ID |
|
||||
| status | enum | `PROCESSING` / `COMPLETED` / `FAILED` |
|
||||
| response_status_code | int | 原始响应状态码 |
|
||||
| created_at | datetime | |
|
||||
| expires_at | datetime | 过期时间(默认 24h) |
|
||||
|
||||
- 流程:收到请求 → 插入 record(PROCESSING)→ 业务处理 → 更新为 COMPLETED + resource_id → 重复请求时查 record 返回已有结果
|
||||
- Redis 做快速去重缓存(SETNX),MySQL 做持久化兜底
|
||||
- 定时任务清理过期记录
|
||||
|
||||
## 3.5 关键索引设计
|
||||
|
||||
| 表 | 索引 | 用途 |
|
||||
|------|------|------|
|
||||
| namespace | `(slug)` UNIQUE | 唯一约束 |
|
||||
| skill | `(namespace_id, status)` | 命名空间内技能列表 |
|
||||
| skill | `(namespace_id, slug)` UNIQUE | 唯一约束 |
|
||||
| skill_version | `(skill_id, status)` | 版本列表 |
|
||||
| skill_version | `(skill_id, version)` UNIQUE | 唯一约束 |
|
||||
| skill_tag | `(skill_id, tag_name)` UNIQUE | 标签唯一约束 |
|
||||
| review_task | `(namespace_id, status)` | 审核列表 |
|
||||
| review_task | `(submitted_by, status)` | 我的提交 |
|
||||
| promotion_request | `(source_skill_id)` | 按来源 skill 查询 |
|
||||
| promotion_request | `(status)` | 待审核列表 |
|
||||
| idempotency_record | `(expires_at)` | 过期清理 |
|
||||
| audit_log | `(created_at)` | 审计查询 |
|
||||
| audit_log | `(actor_user_id, created_at)` | 用户操作历史 |
|
||||
| skill_star | `(user_id)` | 我的收藏 |
|
||||
| skill_star | `(skill_id)` | 技能收藏数 |
|
||||
| skill_rating | `(skill_id)` | 评分聚合 |
|
||||
| namespace_member | `(namespace_id, user_id)` UNIQUE | 成员唯一约束 |
|
||||
| namespace_member | `(user_id)` | 用户所属空间 |
|
||||
| identity_binding | `(provider_code, subject)` UNIQUE | 身份查找 |
|
||||
| api_token | `(token_hash)` | Token 校验 |
|
||||
479
docs/03-authentication-design.md
Normal file
479
docs/03-authentication-design.md
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
# Astron Skills 认证与授权设计
|
||||
|
||||
## 1. 认证架构
|
||||
|
||||
```
|
||||
请求进入
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Layer 1: OAuth2 Login │ Spring Security OAuth2 Client
|
||||
│ (一期 GitHub,可扩展) │ 授权码模式 (Authorization Code)
|
||||
└─────────────┬───────────────┘
|
||||
│ OAuth2User
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Layer 2: Access Policy │ 准入策略判定
|
||||
│ (认证成功 ≠ 有权使用平台) │ 白名单/邮箱域名/开放注册
|
||||
└─────────────┬───────────────┘
|
||||
│ 准入通过
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Layer 3: Identity Mapping │ OAuth2 用户 → 平台用户
|
||||
│ (查询/创建 identity_binding) │ 自动注册 + 信息同步
|
||||
└─────────────┬───────────────┘
|
||||
│ PlatformPrincipal
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Layer 4: Session / Token │ Web: Spring Session (Redis)
|
||||
│ │ CLI: API Token
|
||||
└─────────────┬───────────────┘
|
||||
│ SecurityContext
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Layer 5: Authorization │ RBAC + 资源级判定
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
## 2. 准入策略(Access Policy)
|
||||
|
||||
OAuth 认证成功仅代表身份可信,不代表有权使用平台。准入层在认证成功后、创建平台用户前执行。
|
||||
|
||||
```java
|
||||
// 基于 claims 的准入策略,与 Provider 无关
|
||||
public interface AccessPolicy {
|
||||
AccessDecision evaluate(OAuthClaims claims);
|
||||
}
|
||||
|
||||
public record OAuthClaims(
|
||||
String provider, // github, google, wechat
|
||||
String subject, // provider 唯一 ID
|
||||
String email, // nullable(微信等可能无邮箱)
|
||||
boolean emailVerified, // 是否已验证
|
||||
String providerLogin, // 如 GitHub login
|
||||
Map<String, Object> extra
|
||||
) {}
|
||||
|
||||
public enum AccessDecision {
|
||||
ALLOW, // 准入,继续创建/绑定平台用户
|
||||
DENY, // 拒绝,不建立 Session,重定向到拒绝页
|
||||
PENDING_APPROVAL // 等待管理员审批,不建立业务 Session
|
||||
}
|
||||
```
|
||||
|
||||
### 2.1 一期支持的策略(通过配置切换)
|
||||
|
||||
```yaml
|
||||
astron:
|
||||
access-policy:
|
||||
mode: EMAIL_DOMAIN # OPEN / PROVIDER_ALLOWLIST / EMAIL_DOMAIN / SUBJECT_WHITELIST
|
||||
allowed-providers:
|
||||
- github
|
||||
allowed-email-domains:
|
||||
- company.com
|
||||
- subsidiary.com
|
||||
```
|
||||
|
||||
| 策略 | 判定依据 | 说明 |
|
||||
|------|---------|------|
|
||||
| `OPEN` | 无限制 | 所有 OAuth 登录用户自动准入 |
|
||||
| `PROVIDER_ALLOWLIST` | `claims.provider` | 仅允许指定 Provider 登录 |
|
||||
| `EMAIL_DOMAIN` | `claims.email` + `claims.emailVerified` | 仅允许已验证邮箱且域名匹配(email 为空或未验证则 DENY) |
|
||||
| `SUBJECT_WHITELIST` | `claims.provider` + `claims.subject` | 按 `provider:subject` 白名单,管理员预添加 |
|
||||
|
||||
### 2.2 准入失败处理
|
||||
|
||||
- `DENY`:抛出 `OAuth2AccessDeniedException`,由 `failureHandler` 重定向到 `/access-denied` 页面。不创建用户,不建立 Session。
|
||||
- `PENDING_APPROVAL`:创建 `user_account`(status=`PENDING`),但不建立业务 Session。抛出 `AccountPendingException`,由 `failureHandler` 重定向到 `/pending-approval` 页面(纯静态提示页,无需登录态)。管理员在后台审批后状态变为 `ACTIVE`,用户下次 OAuth 登录才会正常建立 Session。
|
||||
|
||||
安全边界:PENDING / DISABLED 用户绝不会拥有有效的业务 Session,从根源上杜绝"待审批账号已认证"的风险。
|
||||
|
||||
### 2.3 扩展性
|
||||
|
||||
后续新增 OAuth Provider(Google、GitLab、微信)时,准入策略与 Provider 无关,统一在 AccessPolicy 层判定,不需要重做入驻逻辑。
|
||||
|
||||
## 3. Web 认证流程(OAuth2 Authorization Code)
|
||||
|
||||
```
|
||||
浏览器点击"登录"
|
||||
│
|
||||
▼
|
||||
前端跳转: /oauth2/authorization/github
|
||||
│
|
||||
▼
|
||||
Spring Security 重定向到 GitHub 授权页
|
||||
│
|
||||
▼
|
||||
用户在 GitHub 授权
|
||||
│
|
||||
▼
|
||||
GitHub 回调: /login/oauth2/code/github?code=xxx&state=xxx
|
||||
│
|
||||
▼
|
||||
Spring Security 自动完成:
|
||||
① 用 code 换取 access_token
|
||||
② 调用 GitHub API 获取用户信息
|
||||
③ 触发自定义 OAuth2UserService
|
||||
│
|
||||
▼
|
||||
CustomOAuth2UserService:
|
||||
① 从 OAuth2User 提取 provider + externalId → 构建 OAuthClaims
|
||||
② AccessPolicy.evaluate(claims) → 准入判定
|
||||
│
|
||||
├── DENY → 抛出 OAuth2AccessDeniedException → failureHandler 重定向 /access-denied(不建立 Session)
|
||||
├── PENDING_APPROVAL → 创建 PENDING 用户 → 抛出 AccountPendingException → failureHandler 重定向 /pending-approval(不建立 Session)
|
||||
└── ALLOW ↓
|
||||
│
|
||||
③ 查询 identity_binding 是否已绑定
|
||||
├── 已绑定 → 加载平台用户,检查用户状态(DISABLED → 抛异常),同步最新头像/昵称
|
||||
└── 未绑定 → 创建 user_account(ACTIVE) + identity_binding
|
||||
│
|
||||
▼
|
||||
AuthenticationSuccessHandler:
|
||||
① 创建 Spring Session (Redis)
|
||||
② 重定向到前端页面 (可配置的 redirect_uri)
|
||||
```
|
||||
|
||||
### 3.1 Spring Security 配置要点
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login(oauth2 -> oauth2
|
||||
.userInfoEndpoint(info -> info
|
||||
.userService(customOAuth2UserService))
|
||||
.successHandler(oAuth2SuccessHandler)
|
||||
.failureHandler(oAuth2FailureHandler)
|
||||
)
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
|
||||
.csrf(csrf -> csrf
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.ignoringRequestMatchers("/api/v1/cli/**"))
|
||||
// ...
|
||||
;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 OAuth2 Provider 扩展设计
|
||||
|
||||
一期只实现 GitHub,但架构支持后续扩展:
|
||||
|
||||
```yaml
|
||||
# application.yml
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
github:
|
||||
client-id: ${OAUTH2_GITHUB_CLIENT_ID}
|
||||
client-secret: ${OAUTH2_GITHUB_CLIENT_SECRET}
|
||||
scope: read:user,user:email
|
||||
# 二期扩展示例:
|
||||
# gitlab:
|
||||
# client-id: ...
|
||||
# authorization-grant-type: authorization_code
|
||||
# google:
|
||||
# client-id: ...
|
||||
```
|
||||
|
||||
Spring Security OAuth2 Client 原生支持多 Provider 并存,新增 Provider 只需:
|
||||
1. `application.yml` 添加 registration 配置
|
||||
2. `CustomOAuth2UserService` 中按 `registrationId` 分支处理用户属性映射
|
||||
3. 前端登录页增加对应按钮(通过 `/api/v1/auth/providers` 自动发现)
|
||||
|
||||
## 4. 核心接口设计
|
||||
|
||||
```java
|
||||
// 自定义 OAuth2 用户服务,处理准入 + 用户映射
|
||||
@Service
|
||||
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
|
||||
|
||||
@Override
|
||||
public OAuth2User loadUser(OAuth2UserRequest request) {
|
||||
OAuth2User oAuth2User = super.loadUser(request);
|
||||
String registrationId = request.getClientRegistration().getRegistrationId();
|
||||
|
||||
// 提取标准化 claims(传入 accessToken 用于调用 Provider API,如 GitHub /user/emails)
|
||||
OAuthClaims claims = OAuthClaimsExtractor.extract(registrationId, oAuth2User, request.getAccessToken());
|
||||
|
||||
// 准入策略判定(基于 claims,与 Provider 无关)
|
||||
AccessDecision decision = accessPolicy.evaluate(claims);
|
||||
if (decision == AccessDecision.DENY) {
|
||||
throw new OAuth2AccessDeniedException("Access denied by policy");
|
||||
}
|
||||
if (decision == AccessDecision.PENDING_APPROVAL) {
|
||||
// 创建 PENDING 用户但不返回有效 principal,不建立业务 Session
|
||||
identityBindingService.createPendingUser(registrationId, claims);
|
||||
throw new AccountPendingException("Account pending approval");
|
||||
}
|
||||
|
||||
// 绑定或创建平台用户(仅 ALLOW 才走到这里)
|
||||
UserAccount account = identityBindingService.bindOrCreate(registrationId, claims);
|
||||
if (account.getStatus() == UserStatus.DISABLED) {
|
||||
throw new AccountDisabledException("Account is disabled");
|
||||
}
|
||||
|
||||
return new PlatformOAuth2User(account, oAuth2User.getAuthorities());
|
||||
}
|
||||
}
|
||||
|
||||
// 按 Provider 提取标准化 claims(每个 Provider 有自己的可信字段契约)
|
||||
public class OAuthClaimsExtractor {
|
||||
public static OAuthClaims extract(String registrationId, OAuth2User user,
|
||||
OAuth2AccessToken accessToken) {
|
||||
return switch (registrationId) {
|
||||
case "github" -> extractGitHub(user, accessToken);
|
||||
// 后续扩展其他 Provider
|
||||
default -> throw new OAuth2AuthenticationException("Unsupported provider: " + registrationId);
|
||||
};
|
||||
}
|
||||
|
||||
// GitHub: 公开 email 可能为空,需调用 /user/emails API 获取已验证邮箱
|
||||
private static OAuthClaims extractGitHub(OAuth2User user, OAuth2AccessToken accessToken) {
|
||||
String verifiedEmail = GitHubEmailFetcher.fetchVerifiedEmail(accessToken);
|
||||
return new OAuthClaims(
|
||||
"github",
|
||||
String.valueOf(user.getAttribute("id")),
|
||||
verifiedEmail, // 从 /user/emails 获取的已验证邮箱,可能为 null
|
||||
verifiedEmail != null, // 只有确认 verified 才为 true
|
||||
user.getAttribute("login"),
|
||||
Map.of("avatar_url", user.getAttribute("avatar_url"))
|
||||
);
|
||||
}
|
||||
|
||||
// GitHubEmailFetcher: 调用 GitHub /user/emails API,
|
||||
// 返回 primary + verified 的邮箱,无则返回 null
|
||||
}
|
||||
```
|
||||
|
||||
### 4.1 多 Provider 账号合并策略
|
||||
|
||||
同一个员工通过不同 OAuth Provider 登录时,可能产生多个 `user_account`。
|
||||
|
||||
一期策略:默认关闭自动合并,仅支持管理员手动合并。
|
||||
|
||||
- 一期 GitHub-only:不需要自动合并,每个 Provider 登录独立创建用户
|
||||
- 多 Provider 上线时,再引入显式绑定/合并流程(用户主动发起 + 邮箱验证确认)
|
||||
- 管理员可在后台手动合并两个 user_account(合并 identity_binding、迁移 skill ownership、合并角色取并集)
|
||||
|
||||
合并操作规则:
|
||||
- 合并操作写入审计日志
|
||||
- 合并后原 user_account 标记为 `MERGED`,保留记录不物理删除
|
||||
- 预留扩展位:未来可配置 `astron.identity.auto-merge-on-verified-email=true` 开启基于已验证邮箱的自动合并
|
||||
|
||||
## 5. CLI 认证(API Token)
|
||||
|
||||
- Token 格式:`ask_` 前缀 + 随机字符串
|
||||
- 存储:只存 SHA-256 哈希,明文只展示一次
|
||||
- 校验:从 `Authorization: Bearer ask_xxx` 提取 → 哈希比对 → 加载关联用户 → 检查用户状态
|
||||
- 作用域:`skill:read`, `skill:publish`, `skill:delete`, `token:manage`
|
||||
- 天然无状态,多 Pod 安全
|
||||
|
||||
## 6. RBAC 授权判定
|
||||
|
||||
```
|
||||
权限判定 = 平台角色权限(role → permission 查询) ∪ 命名空间角色(namespace_member.role)
|
||||
```
|
||||
|
||||
一期即上线完整 RBAC,平台角色按最小权限拆分:
|
||||
|
||||
| 平台角色 | 职责 |
|
||||
|---------|------|
|
||||
| `SUPER_ADMIN` | 全部权限,硬判定短路 |
|
||||
| `SKILL_ADMIN` | 全局空间审核、提升审核、隐藏/撤回技能 |
|
||||
| `USER_ADMIN` | 准入审批、封禁/解封、角色分配(不可分配 SUPER_ADMIN) |
|
||||
| `AUDITOR` | 审计日志只读 |
|
||||
|
||||
- 命名空间权限仍由 `namespace_member.role`(OWNER / ADMIN / MEMBER)决定
|
||||
- 一个用户可持有多个平台角色
|
||||
- 普通用户无平台角色,仅通过 namespace 成员关系获得操作权限
|
||||
|
||||
判定逻辑:
|
||||
1. 从 SecurityContext 获取当前用户
|
||||
2. 检查用户状态(`DISABLED` → 拒绝所有操作)
|
||||
3. 查询用户的平台角色(`user_role_binding` → `role` → `role_permission`)
|
||||
4. `SUPER_ADMIN` 短路:直接通过所有权限检查
|
||||
5. 如果涉及命名空间资源,查询用户在该命名空间的角色(`namespace_member.role`)
|
||||
6. 检查命名空间状态(`FROZEN` → 拒绝写操作)
|
||||
7. 合并平台权限 + 命名空间角色,判定是否满足
|
||||
|
||||
| 操作 | 所需权限 | 判定逻辑 |
|
||||
|------|---------|---------|
|
||||
| 提交发布审核 | `skill:publish` | 用户是该 namespace 的 MEMBER 以上,且 namespace 非 FROZEN |
|
||||
| 管理技能(归档/版本管理) | `skill:manage` | namespace ADMIN 以上,或 owner 本人 |
|
||||
| 提升到全局 | `skill:promote` | namespace ADMIN 以上,或 owner 本人 |
|
||||
| 审核团队空间技能 | `review:approve` | 该 namespace 的 ADMIN,或持有 SKILL_ADMIN / SUPER_ADMIN |
|
||||
| 审核全局空间技能 | `review:approve` | 持有 SKILL_ADMIN / SUPER_ADMIN |
|
||||
| 审核提升申请 | `promotion:approve` | 持有 SKILL_ADMIN / SUPER_ADMIN |
|
||||
| 隐藏/撤回技能 | `skill:manage` | 持有 SKILL_ADMIN / SUPER_ADMIN |
|
||||
| 管理用户角色 | `user:manage` | 持有 USER_ADMIN / SUPER_ADMIN |
|
||||
| 审批用户准入 | `user:approve` | 持有 USER_ADMIN / SUPER_ADMIN |
|
||||
| 查看审计日志 | `audit:read` | 持有 AUDITOR / SUPER_ADMIN |
|
||||
|
||||
权限主轴说明:
|
||||
- namespace role 是权限主轴,namespace ADMIN 对空间内所有 skill 有完整管理权,不受 owner 限制
|
||||
- `owner_id` 语义为"主要维护人",owner 作为 MEMBER 时仅可管理自己创建的 skill
|
||||
- 企业场景人员流动频繁,owner 离职后 namespace ADMIN 仍能完整管理所有技能
|
||||
|
||||
### 6.1 审核与提升 API 路径适用范围
|
||||
|
||||
| API 路径 | 适用范围 | 权限要求 |
|
||||
|----------|---------|---------|
|
||||
| `POST /api/v1/admin/reviews/{id}/approve` | 全局空间审核 | SKILL_ADMIN / SUPER_ADMIN |
|
||||
| `POST /api/v1/admin/promotions/{id}/approve` | 提升到全局审核 | SKILL_ADMIN / SUPER_ADMIN |
|
||||
| `POST /api/v1/namespaces/{slug}/reviews/{id}/approve` | 团队空间内发布审核 | 该空间 ADMIN |
|
||||
| `GET /api/v1/admin/audit-logs` | 审计日志查询 | AUDITOR / SUPER_ADMIN |
|
||||
| `PUT /api/v1/admin/users/{id}/roles` | 用户角色管理 | USER_ADMIN / SUPER_ADMIN |
|
||||
| `POST /api/v1/admin/users/{id}/approve` | 用户准入审批 | USER_ADMIN / SUPER_ADMIN |
|
||||
|
||||
SUPER_ADMIN 和持有对应角色的用户均可通过 Admin API 操作,团队管理员只能通过 Namespace API 审核本空间。
|
||||
|
||||
## 7. Session 设计
|
||||
|
||||
- 存储:Spring Session + Redis(必须,多 Pod 环境刚需)
|
||||
- 序列化:JSON
|
||||
- 过期:默认 8 小时,Redis TTL 自动清理
|
||||
|
||||
### 7.1 Session 内容
|
||||
|
||||
Session 中存储以下字段:
|
||||
- `userId`:平台用户 ID
|
||||
- `displayName`:展示名
|
||||
- `oauthProvider`:登录使用的 OAuth Provider
|
||||
- `currentNamespaceId`:当前选中的命名空间(可选)
|
||||
- `platformRoles`:平台角色列表(如 `["SKILL_ADMIN", "AUDITOR"]`),登录时从 `user_role_binding` → `role` 查询写入
|
||||
- `roleVersion`:角色版本号,用于缓存一致性
|
||||
|
||||
### 7.2 角色缓存一致性机制
|
||||
|
||||
平台角色变更需要即时生效(如撤销审核权限),不能等 Session 过期:
|
||||
|
||||
1. 每次请求时从 Session 读取 `roleVersion`
|
||||
2. 与 Redis 中的 `user:{userId}:roleVersion` 比对
|
||||
3. 版本一致 → 直接使用 Session 中的 `platformRoles`
|
||||
4. 版本不一致 → 从数据库重新加载角色,更新 Session
|
||||
|
||||
管理员修改用户角色时,递增 Redis 中该用户的 `roleVersion`。
|
||||
|
||||
## 8. CSRF 防护
|
||||
|
||||
采用 Cookie-to-Header 模式:
|
||||
- 后端设置 `XSRF-TOKEN` Cookie(`HttpOnly=false`)
|
||||
- 前端从 Cookie 读取 Token,放入请求 Header `X-XSRF-TOKEN`
|
||||
- 后端校验 Header 与 Cookie 是否一致
|
||||
- CLI API(`/api/v1/cli/**`)豁免 CSRF(使用 API Token 认证,无 Cookie)
|
||||
|
||||
## 9. 前端权限控制
|
||||
|
||||
### 9.1 `/api/v1/auth/me` 响应结构
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"userId": 42,
|
||||
"displayName": "zhangsan",
|
||||
"email": "zhangsan@company.com",
|
||||
"avatarUrl": "https://...",
|
||||
"oauthProvider": "github",
|
||||
"platformRoles": ["SKILL_ADMIN", "AUDITOR"],
|
||||
"namespaces": [
|
||||
{ "slug": "ai-team", "role": "ADMIN" },
|
||||
{ "slug": "global", "role": "MEMBER" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端权限判定基于 `platformRoles` + `namespaces[].role`,后端通过 `role_permission` 表查询权限码。
|
||||
|
||||
### 9.2 usePermission() Hook
|
||||
|
||||
```typescript
|
||||
function usePermission() {
|
||||
const { data: me } = useQuery({ queryKey: ['auth', 'me'], queryFn: fetchMe })
|
||||
|
||||
const hasRole = (role: string) => me?.platformRoles.includes(role) ?? false
|
||||
const isSuperAdmin = () => hasRole('SUPER_ADMIN')
|
||||
const isSkillAdmin = () => hasRole('SKILL_ADMIN') || isSuperAdmin()
|
||||
const isUserAdmin = () => hasRole('USER_ADMIN') || isSuperAdmin()
|
||||
const isAuditor = () => hasRole('AUDITOR') || isSuperAdmin()
|
||||
|
||||
return {
|
||||
isLoggedIn: !!me,
|
||||
isSuperAdmin,
|
||||
isSkillAdmin,
|
||||
isUserAdmin,
|
||||
isAuditor,
|
||||
|
||||
// 命名空间角色判定
|
||||
getNamespaceRole: (slug: string) =>
|
||||
me?.namespaces.find(n => n.slug === slug)?.role,
|
||||
isNamespaceAdmin: (slug: string) =>
|
||||
['OWNER', 'ADMIN'].includes(me?.namespaces.find(n => n.slug === slug)?.role ?? ''),
|
||||
isNamespaceMember: (slug: string) =>
|
||||
['OWNER', 'ADMIN', 'MEMBER'].includes(me?.namespaces.find(n => n.slug === slug)?.role ?? ''),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 路由级守卫
|
||||
|
||||
在 TanStack Router `beforeLoad` 中判定:
|
||||
|
||||
| 路由 | 条件 |
|
||||
|------|------|
|
||||
| `/dashboard/*` | 已登录 |
|
||||
| `/dashboard/namespaces/{slug}/reviews` | 已登录 + 该 namespace 的 ADMIN 以上 |
|
||||
| `/admin/*` | 已登录 + 持有任一平台角色(SUPER_ADMIN / SKILL_ADMIN / USER_ADMIN / AUDITOR) |
|
||||
|
||||
不满足条件时:未登录 → 重定向登录;已登录但无权限 → 显示 403 页面。
|
||||
|
||||
### 9.4 操作级控制
|
||||
|
||||
| 场景 | 判定逻辑 | UI 行为 |
|
||||
|------|---------|---------|
|
||||
| 技能详情页"提交发布"按钮 | `isNamespaceMember(namespace)` | 非成员不显示 |
|
||||
| 审核列表"通过/拒绝"按钮 | `isNamespaceAdmin(namespace) \|\| isSkillAdmin()` | 无权限不显示 |
|
||||
| 用户管理页 | `isUserAdmin()` | 无权限不显示 |
|
||||
| 用户管理页"设为 SUPER_ADMIN" | `isSuperAdmin()` | 仅超管可见 |
|
||||
| 审计日志页 | `isAuditor()` | 无权限不显示 |
|
||||
| 技能详情页"归档"按钮 | `isNamespaceAdmin(namespace)` 或当前用户是 owner | 否则不显示 |
|
||||
| 命名空间"添加成员"按钮 | `isNamespaceAdmin(namespace)` | 非管理员不显示 |
|
||||
| 收藏/评分按钮 | `isLoggedIn` | 未登录时点击提示登录 |
|
||||
|
||||
### 9.5 登录交互
|
||||
|
||||
```
|
||||
前端登录按钮
|
||||
│
|
||||
▼
|
||||
window.location.href = '/oauth2/authorization/github'
|
||||
│
|
||||
▼
|
||||
(后端 OAuth2 流程,用户无感)
|
||||
│
|
||||
▼
|
||||
回调后重定向到前端 (如 /?login=success)
|
||||
│
|
||||
▼
|
||||
前端检测 URL 参数 → 调用 /api/v1/auth/me → 更新登录态
|
||||
```
|
||||
|
||||
前端无需引入额外 OAuth 库,登录流程完全由后端 Spring Security 处理。前端只需:
|
||||
- 调用 `/api/v1/auth/providers` 获取可用 Provider 列表,动态渲染登录按钮
|
||||
- 处理登录后的重定向
|
||||
- 通过 `/api/v1/auth/me` 检测登录状态
|
||||
|
||||
### 9.6 安全边界原则
|
||||
|
||||
- 前端权限控制是 UX 优化,不是安全边界
|
||||
- 后端每个写操作接口独立校验权限,不信任前端判定
|
||||
- 前端隐藏按钮 ≠ 安全,用户可以直接调 API,后端必须拦截
|
||||
136
docs/04-search-architecture.md
Normal file
136
docs/04-search-architecture.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Astron Skills 搜索架构
|
||||
|
||||
## 1 SPI 接口
|
||||
|
||||
```java
|
||||
public interface SearchIndexService {
|
||||
void index(SkillSearchDocument doc);
|
||||
void batchIndex(List<SkillSearchDocument> docs);
|
||||
void remove(Long skillId);
|
||||
}
|
||||
|
||||
public interface SearchQueryService {
|
||||
SearchResult search(SearchQuery query);
|
||||
}
|
||||
|
||||
public interface SearchRebuildService {
|
||||
void rebuildAll();
|
||||
void rebuildByNamespace(Long namespaceId);
|
||||
void rebuildBySkill(Long skillId);
|
||||
}
|
||||
```
|
||||
|
||||
## 2 SearchQuery 模型
|
||||
|
||||
```java
|
||||
public record SearchQuery(
|
||||
String keyword,
|
||||
Long namespaceId, // 可选,指定空间搜索
|
||||
String namespaceSlug, // 可选
|
||||
SearchVisibilityScope scope, // ACL 投影,由应用服务层计算注入
|
||||
SortField sortBy, // RELEVANCE / DOWNLOADS / RATING / NEWEST
|
||||
int page,
|
||||
int size
|
||||
) {}
|
||||
|
||||
// 搜索可见范围投影,由应用服务层根据当前用户计算
|
||||
public record SearchVisibilityScope(
|
||||
boolean includeAllPublic, // 是否包含所有 PUBLIC 技能
|
||||
Set<Long> memberNamespaceIds, // 用户是 MEMBER 的 namespace(可见 NAMESPACE_ONLY)
|
||||
Set<Long> adminNamespaceIds, // 用户是 ADMIN 的 namespace(可见 PRIVATE)
|
||||
Long userId // 当前用户 ID(可见自己的 PRIVATE skill),匿名为 null
|
||||
) {}
|
||||
```
|
||||
|
||||
ACL 投影计算规则:
|
||||
- 匿名用户:`includeAllPublic=true`,其余为空集,`userId=null`
|
||||
- 已登录用户:`includeAllPublic=true`,`memberNamespaceIds` = 用户所属空间,`adminNamespaceIds` = 用户是 ADMIN 以上的空间,`userId` = 当前用户 ID
|
||||
|
||||
一期 MySQL 实现中,`SearchVisibilityScope` 转换为 WHERE 条件:
|
||||
```sql
|
||||
WHERE (visibility = 'PUBLIC')
|
||||
OR (visibility = 'NAMESPACE_ONLY' AND namespace_id IN (:memberNamespaceIds))
|
||||
OR (visibility = 'PRIVATE' AND (namespace_id IN (:adminNamespaceIds) OR owner_id = :userId))
|
||||
```
|
||||
|
||||
迁移到 ES 时,`SearchVisibilityScope` 可直接映射为 bool query 的 should/filter 子句。
|
||||
|
||||
## 3 搜索文档表 skill_search_document
|
||||
|
||||
一个 skill 对应一条搜索文档,内容取 `latest_version_id` 对应版本。版本发布时自动更新该条文档。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | |
|
||||
| skill_id | bigint | 唯一,一 skill 一条 |
|
||||
| namespace_id | bigint | 用于空间过滤 |
|
||||
| owner_id | bigint | 用于 PRIVATE 可见性判定 |
|
||||
| title | varchar(256) | |
|
||||
| summary | varchar(512) | |
|
||||
| keywords | varchar(512) | |
|
||||
| search_text | text | SKILL.md 正文 + frontmatter 拼接 |
|
||||
| visibility | enum | 冗余,避免搜索时 join |
|
||||
| status | enum | |
|
||||
| updated_at | datetime | |
|
||||
|
||||
唯一约束:`(skill_id)`
|
||||
|
||||
MySQL Full-Text Index 建在 `(title, summary, keywords, search_text)` 上。
|
||||
|
||||
## 4 索引写入时机
|
||||
|
||||
以下场景触发搜索文档更新(upsert by skill_id):
|
||||
- 审核通过(`PENDING_REVIEW → PUBLISHED`):`latest_version_id` 自动更新,用新版本内容更新搜索文档
|
||||
- 技能状态变更(隐藏/归档/恢复):更新搜索文档的 status 字段
|
||||
|
||||
## 5 搜索演进路线
|
||||
|
||||
### 5.1 一期数据建模约束
|
||||
|
||||
一期"每个 skill 一条搜索文档、内容永远取 latest_version_id"是有意的简化。这个模型在以下场景下会不够用:
|
||||
|
||||
- 版本级检索(搜索某个旧版本的内容)
|
||||
- 自定义标签/通道检索(搜索 `@beta` 标签指向的版本内容)
|
||||
- 向量 chunk 索引(一个 skill 的 SKILL.md 拆成多个 embedding chunk)
|
||||
|
||||
这些场景不是简单换 provider 能解决的,需要改表结构和索引写入逻辑。
|
||||
|
||||
### 5.2 演进阶段
|
||||
|
||||
| 阶段 | 实现 | 索引粒度 | 切换方式 |
|
||||
|------|------|---------|---------|
|
||||
| 一期 | MySQL Full-Text | 每 skill 一条(latest_version_id) | 默认 |
|
||||
| 二期 | ES / OpenSearch | 每 skill_version 一条 + skill 聚合文档 | 配置 `search.provider=elasticsearch` |
|
||||
| 三期 | 向量检索 | 每 skill_version 多条(chunk 级) | 配置 `search.provider=vector` |
|
||||
| 四期 | 混合排序 | 关键词 + 向量混合 | 配置 `search.provider=hybrid` |
|
||||
|
||||
### 5.3 SPI 演进策略
|
||||
|
||||
一期 SPI 接口(`SearchIndexService` / `SearchQueryService`)的入参是 `SkillSearchDocument`(skill 粒度)。二期切换到 ES 时:
|
||||
|
||||
1. 新增 `SkillVersionSearchDocument` 模型(version 粒度)
|
||||
2. `SearchIndexService` 新增 `indexVersion()` 方法(向下兼容,一期实现空方法)
|
||||
3. ES 实现同时写入 skill 聚合文档 + version 文档
|
||||
4. `SearchQueryService.search()` 的返回结果不变(仍返回 skill 级摘要),内部实现切换为 ES 查询
|
||||
|
||||
这意味着二期切换不是零成本的——需要新增模型、扩展 SPI、重建索引。但一期不为此过度设计,SPI 抽象保证了切换时不需要改业务层代码。
|
||||
|
||||
通过 `@ConditionalOnProperty` 或自定义 SPI 加载机制切换。
|
||||
|
||||
## 6 分布式安全
|
||||
|
||||
`rebuildAll()` / `rebuildByNamespace()` 执行前获取 Redis 分布式锁(key: `search:rebuild:{scope}`,TTL: 10min),获取失败则跳过。
|
||||
|
||||
## 7 MySQL 全文搜索中文支持
|
||||
|
||||
MySQL Full-Text Index 必须使用 ngram parser:
|
||||
|
||||
```sql
|
||||
ALTER TABLE skill_search_document
|
||||
ADD FULLTEXT INDEX ft_search (title, summary, keywords, search_text)
|
||||
WITH PARSER ngram;
|
||||
```
|
||||
|
||||
配置 `ngram_token_size=2`(my.cnf)。
|
||||
|
||||
已知局限:ngram 分词精度不如专业搜索引擎,中文搜索体验有限。建议 Phase 2 完成后评估搜索效果,如不满足需求则在 Phase 3 提前引入 ES。
|
||||
273
docs/05-business-flows.md
Normal file
273
docs/05-business-flows.md
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
# Astron Skills 核心业务流
|
||||
|
||||
## 1 发布流程
|
||||
|
||||
```
|
||||
用户提交发布
|
||||
│
|
||||
▼
|
||||
① 身份与权限校验(用户是否为该 namespace 的 MEMBER 以上)
|
||||
│
|
||||
▼
|
||||
② 技能包校验
|
||||
- SKILL.md 存在性、frontmatter 格式
|
||||
- 文件类型白名单、单文件大小限制、总包大小限制
|
||||
- 版本号 semver 合法性、不与已有版本冲突
|
||||
- [扩展点] PrePublishValidator 链(一期空实现)
|
||||
│
|
||||
▼
|
||||
③ 写入对象存储临时区(文件逐个上传到 `tmp/{uploadId}/{filePath}`,记录 SHA-256)
|
||||
│
|
||||
▼
|
||||
④ 持久化数据
|
||||
- 创建 skill_version (status=DRAFT, file_transfer_status=PENDING)
|
||||
- 创建 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)
|
||||
│
|
||||
▼
|
||||
⑥ 审核(人工)
|
||||
├── 通过 → skill_version.status → PUBLISHED
|
||||
│ review_task.status → APPROVED(乐观锁)
|
||||
│ 更新 skill.latest_version_id(自动跟随最新已发布版本)
|
||||
│ 同步写入审计日志
|
||||
│ 异步触发: 搜索索引写入(取 latest_version_id 对应版本内容)
|
||||
│
|
||||
└── 拒绝 → skill_version.status → REJECTED
|
||||
review_task.status → REJECTED(乐观锁)
|
||||
记录 reject_reason
|
||||
同步写入审计日志
|
||||
```
|
||||
|
||||
④ 和 ⑤ 分开,给用户一个检查草稿的机会。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/` 中的源文件在转正成功前不删除,确保重试有源可用
|
||||
|
||||
### CLI publish 请求规范
|
||||
|
||||
```
|
||||
POST /api/v1/cli/publish
|
||||
Content-Type: multipart/form-data
|
||||
Parts:
|
||||
- file: zip 包(必需)
|
||||
- namespace: 目标命名空间 slug(必需)
|
||||
- auto_submit: boolean(可选,默认 false,为 true 时自动提交审核)
|
||||
```
|
||||
|
||||
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` 响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"publishId": "uuid",
|
||||
"skillVersionId": 123,
|
||||
"fileTransferStatus": "COMPLETED",
|
||||
"versionStatus": "PENDING_REVIEW",
|
||||
"error": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2 团队技能提升到全局空间(派生发布)
|
||||
|
||||
不直接修改原 skill 的 `namespace_id`,而是在全局空间创建新的 skill,保留来源追溯。原团队 skill 继续存在,安装坐标 `@team/skill` 不受影响。
|
||||
|
||||
```
|
||||
团队空间技能(已发布)
|
||||
│
|
||||
▼
|
||||
① 技能 owner 或 namespace admin 发起"提升到全局"申请
|
||||
│
|
||||
▼
|
||||
② 创建 promotion_request (source_skill_id, source_version_id, target_namespace_id, status=PENDING)
|
||||
│
|
||||
▼
|
||||
③ 平台管理员审核
|
||||
├── 通过 →
|
||||
│ ① 在全局空间创建新 skill(source_skill_id = 原 skill ID)
|
||||
│ ② 复制 source_version_id 对应版本的文件和元数据到新 skill(严格使用申请时指定的版本,不取最新)
|
||||
│ ③ 新 skill.visibility = PUBLIC
|
||||
│ ④ promotion_request.target_skill_id = 新 skill ID,status → APPROVED
|
||||
│ ⑤ 搜索索引写入新 skill,同步写入审计日志
|
||||
│ (提升关系唯一事实来源是 promotion_request,UI 查询"是否已提升"通过该表判定)
|
||||
│
|
||||
└── 拒绝 → 记录原因,原技能不受影响
|
||||
```
|
||||
|
||||
后续版本更新:
|
||||
- 全局空间的新 skill 由其 owner 独立管理版本
|
||||
- 原团队 skill 可继续独立迭代
|
||||
- 两者版本不自动同步,如需同步由 owner 手动操作
|
||||
|
||||
## 3 下载流程
|
||||
|
||||
```
|
||||
下载请求
|
||||
│
|
||||
▼
|
||||
① 校验技能状态(ACTIVE)、版本状态(PUBLISHED)
|
||||
│
|
||||
▼
|
||||
② 可见性检查
|
||||
- PUBLIC: 任何人(包括匿名用户)
|
||||
- NAMESPACE_ONLY: 该 namespace 的成员(需登录)
|
||||
- PRIVATE: owner 本人 + 该 namespace 的 ADMIN 以上(需登录)
|
||||
│
|
||||
▼
|
||||
③ 返回预生成包或按文件清单打包
|
||||
│
|
||||
▼
|
||||
④ 审计与统计
|
||||
- audit_log 同步写入(记录下载人/IP/版本)
|
||||
- download_count 异步更新(原子 SQL: download_count = download_count + 1)
|
||||
- 匿名下载:审计记录 IP + User-Agent,不关联用户
|
||||
- 已登录下载:审计记录用户 ID
|
||||
```
|
||||
|
||||
### download_count 热点行优化预案
|
||||
|
||||
一期使用原子 SQL 直接更新,可接受。如出现热点行瓶颈,切换为:
|
||||
1. Redis `INCR` 做实时计数(key: `skill:downloads:{skillId}`)
|
||||
2. 定时任务每 5 分钟批量回写 MySQL
|
||||
3. 查询时合并 MySQL 存量 + Redis 增量
|
||||
|
||||
## 4 搜索流程
|
||||
|
||||
```
|
||||
搜索请求 (keyword, namespaceSlug?, sortBy)
|
||||
│
|
||||
▼
|
||||
① 构建 SearchQuery
|
||||
- 匿名用户:visibility 限定为 PUBLIC
|
||||
- 已登录用户:根据命名空间成员关系计算可见范围
|
||||
│
|
||||
▼
|
||||
② SearchQueryService.search(query)
|
||||
│
|
||||
▼
|
||||
③ 返回分页结果(技能摘要 + 命名空间信息 + 评分 + 下载量)
|
||||
```
|
||||
|
||||
## 5 收藏流程
|
||||
|
||||
```
|
||||
收藏/取消收藏(需登录)→ 校验权限 → 写入/删除 skill_star
|
||||
→ 异步更新 skill.star_count(原子 SQL)
|
||||
```
|
||||
|
||||
## 6 评分流程
|
||||
|
||||
```
|
||||
提交评分 (score: 1-5)(需登录)→ 校验权限 → 写入/更新 skill_rating
|
||||
→ 异步重算 skill.rating_avg 和 rating_count(SELECT AVG + Redis 分布式锁防重复重算)
|
||||
```
|
||||
|
||||
## 7 异步事件汇总
|
||||
|
||||
| 事件 | 触发时机 | 消费方 |
|
||||
|------|---------|--------|
|
||||
| `SkillPublishedEvent` | 审核通过 | 搜索索引写入 |
|
||||
| `SkillYankedEvent` | 版本撤回 | 搜索索引移除 |
|
||||
| `SkillDownloadedEvent` | 下载完成 | 下载计数 |
|
||||
| `SkillStarredEvent` | 收藏/取消 | 收藏计数 |
|
||||
| `SkillRatedEvent` | 评分提交 | 评分重算 |
|
||||
| `ReviewCompletedEvent` | 审核完成 | 通知提交者(一期可选) |
|
||||
| `SkillPromotedEvent` | 提升到全局 | 搜索索引写入(新 skill) |
|
||||
|
||||
一期用 Spring ApplicationEvent + `@Async` 实现,后续可替换为消息队列。
|
||||
|
||||
### 审计日志写入策略
|
||||
|
||||
审计日志统一同步落库,与业务操作在同一请求内同步写入,不走异步事件。审计是企业内部平台的刚性需求,不可容忍丢失。
|
||||
|
||||
异步事件仅用于搜索索引、计数器等可容忍延迟的场景。如果后续需要更强一致性,引入 outbox 模式,不依赖 ApplicationEvent + @Async 承担可靠性。
|
||||
|
||||
### 异步事件可靠性保障
|
||||
|
||||
Spring ApplicationEvent + @Async 存在 Pod 被杀时事件丢失的风险。补充以下兜底机制:
|
||||
|
||||
- 搜索索引:定时任务每小时检查 `skill_version.status = PUBLISHED` 但 `skill_search_document` 中无对应记录的版本,补建索引
|
||||
- 计数器:可接受少量丢失,定时任务每天凌晨从 `skill_star` / `skill_rating` 表重算修正
|
||||
- 优雅停机:`@Async` 线程池配置 `awaitTerminationSeconds=25`,配合 30s shutdown timeout
|
||||
|
||||
## 8 分布式并发安全措施
|
||||
|
||||
| 操作 | 并发控制方式 |
|
||||
|------|-------------|
|
||||
| 审核通过/拒绝 | 乐观锁:`UPDATE review_task SET status=? WHERE id=? AND version=?` |
|
||||
| 版本发布 | 唯一约束:`(skill_id, version)` |
|
||||
| 计数器更新 | 原子 SQL:`SET count = count + 1` |
|
||||
| 评分重算 | 异步 + Redis 分布式锁防重复重算 |
|
||||
| 写操作幂等 | Redis 存储 `X-Request-Id`,TTL 24h |
|
||||
|
||||
### 幂等去重规范
|
||||
|
||||
基于 `idempotency_record` 表实现完整幂等:
|
||||
|
||||
- `X-Request-Id` 由客户端生成(UUID v4 格式)
|
||||
- 客户端不传时,服务端自动生成但不做幂等去重
|
||||
|
||||
去重流程:
|
||||
1. Redis `SETNX` key=`idempotent:{requestId}`(快速去重缓存,TTL=24h)
|
||||
- key 已存在:查询 `idempotency_record` 表返回原始结果
|
||||
2. key 不存在:插入 `idempotency_record`(status=`PROCESSING`)
|
||||
3. 执行业务逻辑
|
||||
4. 成功:更新 record 为 `COMPLETED`,填充 `resource_type` + `resource_id` + `response_status_code`
|
||||
5. 失败:更新 record 为 `FAILED`
|
||||
6. 重复请求时:查 record,COMPLETED 返回原始资源 ID,PROCESSING 返回 `409 Conflict`,FAILED 允许重试
|
||||
|
||||
适用范围:所有 POST/PUT/DELETE 写操作(发布、提审、创建 Token 等)
|
||||
|
||||
异常恢复策略:
|
||||
- Redis key 存在但 `idempotency_record` 无记录(进程在两步之间崩溃):视为脏状态,删除 Redis key,允许请求正常重入
|
||||
- `idempotency_record.status = FAILED`:删除对应 Redis key,允许客户端用相同 `request_id` 重试
|
||||
- `idempotency_record.status = PROCESSING` 超过 5 分钟未更新:视为僵死,标记为 FAILED,删除 Redis key,允许重试
|
||||
212
docs/06-api-design.md
Normal file
212
docs/06-api-design.md
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# Astron Skills 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/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 版本提交审核(前置:file_transfer_status=COMPLETED) |
|
||||
| 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 协议兼容层
|
||||
|
||||
一期不仅提供 Astron 自有 CLI API,还必须暴露一组兼容 ClawHub CLI 的 registry API。
|
||||
|
||||
- 目标:让现有 ClawHub CLI 可通过配置 registry base URL 直接对接 Astron Skills
|
||||
- 范围:覆盖 ClawHub CLI 所依赖的查询、版本解析、下载、发布、校验等核心接口
|
||||
- 要求:兼容层优先保持 ClawHub CLI 既有请求/响应语义;若内部领域模型不同,通过 adapter 层完成协议转换,而不是要求客户端适配 Astron 私有协议
|
||||
- 要求:兼容层纳入 OpenAPI 或独立兼容协议文档,并作为正式对外契约维护
|
||||
- 要求:兼容层与 Astron 自有 `/api/v1/cli/**` 并存,二者共享同一套权限、审计、限流与领域服务
|
||||
- 非目标:前端页面不直接依赖兼容层;兼容层用于服务已有 ClawHub CLI 和相关自动化脚本
|
||||
|
||||
兼容层最少需要覆盖的能力类别:
|
||||
|
||||
- Registry metadata:技能查询、技能详情、版本列表、标签/默认版本解析
|
||||
- Artifact resolution:按技能坐标或版本解析下载地址/下载流
|
||||
- Publish workflow:包上传、发布状态查询、提交审核
|
||||
- Integrity check:版本存在性校验、摘要/哈希比对、whoami/token 上下文确认
|
||||
|
||||
如 ClawHub CLI 的现有协议与 Astron 自有接口存在差异,文档以“兼容 ClawHub CLI 协议”为准,Astron 内部 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 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.10 API 设计原则
|
||||
|
||||
- 统一响应格式:`{ code, message, data, timestamp }`
|
||||
- 分页格式:`{ items, total, page, size }`
|
||||
- 错误码体系:业务错误码 + HTTP 状态码配合
|
||||
- 版本策略:URL path 版本 `/api/v1/`
|
||||
- 幂等性:写操作通过 `X-Request-Id` + Redis 去重(TTL 24h)
|
||||
121
docs/07-skill-protocol.md
Normal file
121
docs/07-skill-protocol.md
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Astron Skills 技能包协议
|
||||
|
||||
## 8.1 OpenSkills 互操作边界
|
||||
|
||||
Astron 的目标是客户端可互操作:Astron CLI 安装的技能可以被 Claude Code / OpenSkills 兼容客户端发现和使用,反之亦然。
|
||||
|
||||
### 互操作层(Astron CLI 必须兼容)
|
||||
|
||||
- SKILL.md 格式(frontmatter + markdown body)
|
||||
- 技能包目录结构约定(SKILL.md + references/ + scripts/ + assets/)
|
||||
- 四级目录优先级:Astron CLI 遵循 `.agent/skills` → `~/.agent/skills` → `.claude/skills` → `~/.claude/skills` 的发现顺序,与 OpenSkills/Claude 一致
|
||||
- 目录名作为 lookup key:安装后的目录名等于 `skill.slug`(即 SKILL.md 的 `name` 字段),客户端通过目录名发现技能
|
||||
- AGENTS.md `<skill>` 描述块格式:Astron CLI 生成的 AGENTS.md 索引区块与 OpenSkills 格式兼容
|
||||
|
||||
### 服务端职责边界
|
||||
|
||||
- 服务端返回技能元数据(name, description, version),不返回 `location`
|
||||
- `location` 是客户端本地安装路径,由 CLI 根据安装目录计算生成,写入 AGENTS.md
|
||||
- 服务端不生成、不修改 AGENTS.md,这是客户端职责
|
||||
|
||||
### Astron 私有扩展(不影响互操作)
|
||||
|
||||
- `<skills_system>` / `<available_skills>` 区块格式:Astron CLI 可自定义,但必须保证 `<skill>` 节点格式与 OpenSkills 一致
|
||||
- progressive disclosure(按需加载技能内容):Astron CLI 自行实现
|
||||
- `.astron/metadata.json`:Astron 私有元数据,其他客户端可忽略
|
||||
|
||||
## 8.2 SKILL.md 规范
|
||||
|
||||
服务端必须兼容的格式:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-skill # 必需,kebab-case
|
||||
description: When to use # 必需,1-2 句话
|
||||
---
|
||||
|
||||
# Markdown 正文(技能指令内容)
|
||||
```
|
||||
|
||||
解析规则:
|
||||
- `name` 和 `description` 为必需字段,缺失则校验失败
|
||||
- `name` 映射为 `skill.slug`(首次发布时),后续版本不可变更
|
||||
- `description` 映射为 `skill.summary`
|
||||
- frontmatter 完整解析结果存入 `skill_version.parsed_metadata_json`
|
||||
|
||||
平台扩展字段(可选,`x-astron-` 前缀):
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-skill
|
||||
description: When to use
|
||||
x-astron-category: code-review
|
||||
x-astron-runtime: claude-code # 预留
|
||||
x-astron-min-version: "1.0" # 预留
|
||||
---
|
||||
```
|
||||
|
||||
## 8.3 技能包目录结构
|
||||
|
||||
```
|
||||
my-skill/
|
||||
├── SKILL.md # 主入口文件(必需)
|
||||
├── references/ # 参考资料(可选)
|
||||
├── scripts/ # 脚本(可选)
|
||||
└── assets/ # 静态资源(可选)
|
||||
```
|
||||
|
||||
校验规则:
|
||||
- 根目录必须包含 `SKILL.md`
|
||||
- 文件类型白名单:`.md`, `.txt`, `.json`, `.yaml`, `.yml`, `.js`, `.ts`, `.py`, `.sh`, `.png`, `.jpg`, `.svg`
|
||||
- 单文件大小限制:1MB(可配置)
|
||||
- 总包大小限制:10MB(可配置)
|
||||
- 文件数量限制:100 个(可配置)
|
||||
|
||||
## 8.4 客户端安装目录约定
|
||||
|
||||
Astron CLI 遵循以下目录优先级,与 OpenSkills/Claude 保持互操作:
|
||||
|
||||
| 优先级 | 路径 | 说明 |
|
||||
|--------|------|------|
|
||||
| 1 | `./.agent/skills/` | 项目级,universal 模式 |
|
||||
| 2 | `~/.agent/skills/` | 全局级,universal 模式 |
|
||||
| 3 | `./.claude/skills/` | 项目级,Claude 默认 |
|
||||
| 4 | `~/.claude/skills/` | 全局级,Claude 默认 |
|
||||
|
||||
安装后目录名等于 `skill.slug`(SKILL.md 的 `name` 字段),确保其他兼容客户端可通过目录名发现。
|
||||
|
||||
## 8.5 与 AGENTS.md 的关系
|
||||
|
||||
- Astron CLI 安装技能后,通过 `sync` 命令在 AGENTS.md 中生成 `<skill>` 描述块
|
||||
- `<skill>` 块包含 `name`、`description`、`location`(本地安装路径),格式与 OpenSkills 一致
|
||||
- `location` 由 CLI 根据实际安装路径计算,不由服务端提供
|
||||
- 服务端不直接生成或修改 AGENTS.md,这是客户端职责
|
||||
|
||||
## 8.6 客户端本地元数据文件(Astron 私有实现)
|
||||
|
||||
以下为 Astron CLI 的私有实现细节,不属于互操作协议的一部分。其他客户端可忽略此文件。
|
||||
|
||||
CLI 安装后在本地写入 `.astron/metadata.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "astron-skills",
|
||||
"sourceType": "registry",
|
||||
"registryUrl": "https://skills.example.com",
|
||||
"namespace": "@ai-platform-team",
|
||||
"skillSlug": "code-review",
|
||||
"version": "1.2.0",
|
||||
"installedAt": "2026-03-11T10:00:00Z",
|
||||
"sha256": "abc123..."
|
||||
}
|
||||
```
|
||||
|
||||
## 8.7 版本解析规则
|
||||
|
||||
```
|
||||
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 标签(自定义标签)
|
||||
```
|
||||
146
docs/08-frontend-architecture.md
Normal file
146
docs/08-frontend-architecture.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Astron Skills 前端架构设计
|
||||
|
||||
## 1 技术栈
|
||||
|
||||
| 类别 | 选型 | 说明 |
|
||||
|------|------|------|
|
||||
| 框架 | React 19 + TypeScript | |
|
||||
| 构建 | Vite | |
|
||||
| 路由 | TanStack Router | |
|
||||
| 数据获取 | TanStack Query | 管理所有服务端数据(API 响应缓存、加载/错误状态) |
|
||||
| UI 组件 | shadcn/ui + Radix UI | |
|
||||
| 样式 | Tailwind CSS | |
|
||||
| 本地状态 | Zustand | 仅管理纯客户端状态 |
|
||||
| API 客户端 | openapi-fetch + openapi-typescript | |
|
||||
| 图标 | Lucide React | |
|
||||
|
||||
### 1.1 Zustand 与 TanStack Query 职责边界
|
||||
|
||||
- **TanStack Query**:管理所有服务端数据(API 响应缓存、加载/错误状态)
|
||||
- **Zustand**:仅管理纯客户端状态(UI 偏好、侧边栏展开、主题、当前选中的命名空间过滤等)
|
||||
- 禁止在 Zustand 中缓存服务端数据
|
||||
|
||||
## 2 页面结构
|
||||
|
||||
### 2.1 门户区(公开,匿名可访问)
|
||||
|
||||
| 页面 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 首页 | `/` | 精选/热门/最新、搜索入口 |
|
||||
| 搜索页 | `/search` | 关键词搜索 + 过滤 + 排序 |
|
||||
| 命名空间主页 | `/@{namespace}` | 空间介绍 + 技能列表 |
|
||||
| 技能详情页 | `/@{namespace}/{slug}` | README 渲染、版本、评分、收藏、下载 |
|
||||
| 版本历史 | `/@{namespace}/{slug}/versions` | 版本列表 + changelog |
|
||||
|
||||
门户区所有 PUBLIC 技能匿名可浏览和下载,无需登录。
|
||||
|
||||
### 2.2 个人中心(需登录)
|
||||
|
||||
| 页面 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 我的技能 | `/dashboard/skills` | 我发布的技能 + 审核状态 |
|
||||
| 发布技能 | `/dashboard/publish` | zip 上传 + 预览 + 提交审核 |
|
||||
| 我的收藏 | `/dashboard/stars` | 收藏列表 |
|
||||
| Token 管理 | `/dashboard/tokens` | 创建/查看/吊销 |
|
||||
| 我的命名空间 | `/dashboard/namespaces` | 参与的命名空间 |
|
||||
|
||||
### 2.3 命名空间管理(需空间 ADMIN)
|
||||
|
||||
| 页面 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 成员管理 | `/dashboard/namespaces/{slug}/members` | 成员管理 |
|
||||
| 空间审核 | `/dashboard/namespaces/{slug}/reviews` | 待审核列表 |
|
||||
|
||||
### 2.4 平台管理(需对应平台角色)
|
||||
|
||||
| 页面 | 路径 | 所需角色 | 说明 |
|
||||
|------|------|---------|------|
|
||||
| 审核中心 | `/admin/reviews` | SKILL_ADMIN | 全局待审核列表 |
|
||||
| 提升审核 | `/admin/promotions` | SKILL_ADMIN | 提升到全局的申请列表 |
|
||||
| 技能管理 | `/admin/skills` | SKILL_ADMIN | 隐藏/恢复/撤回 |
|
||||
| 用户管理 | `/admin/users` | USER_ADMIN | 用户列表、角色分配、准入审批、封禁/解封 |
|
||||
| 审计日志 | `/admin/audit-logs` | AUDITOR | 操作日志查询 |
|
||||
| 命名空间管理 | `/admin/namespaces` | SUPER_ADMIN | 创建/归档/冻结 |
|
||||
|
||||
SUPER_ADMIN 可访问所有管理页面。路由守卫检查用户是否持有对应角色。
|
||||
|
||||
## 3 布局结构
|
||||
|
||||
- 门户区:顶部导航 + 内容区,无侧边栏,突出浏览体验
|
||||
- Dashboard / Admin:顶部导航 + 左侧边栏,管理效率优先
|
||||
- 响应式:移动端侧边栏收起为抽屉
|
||||
|
||||
## 4 登录与鉴权
|
||||
|
||||
### 4.1 OAuth2 登录流程(前端视角)
|
||||
|
||||
```
|
||||
用户点击"登录"按钮
|
||||
│
|
||||
▼
|
||||
前端调用 GET /api/v1/auth/providers
|
||||
│
|
||||
▼
|
||||
渲染可用的 OAuth Provider 按钮(一期只有 GitHub)
|
||||
│
|
||||
▼
|
||||
用户点击 "Sign in with GitHub"
|
||||
│
|
||||
▼
|
||||
window.location.href = "/oauth2/authorization/github"
|
||||
│
|
||||
▼
|
||||
(浏览器跳转到 GitHub → 授权 → 回调后端 → 后端创建 Session)
|
||||
│
|
||||
▼
|
||||
后端重定向回前端页面(如 / 或用户之前访问的页面)
|
||||
│
|
||||
▼
|
||||
前端检测到 Session Cookie,调用 GET /api/v1/auth/me
|
||||
│
|
||||
▼
|
||||
获取用户信息,渲染登录态 UI
|
||||
```
|
||||
|
||||
前端不需要任何 OAuth 库,登录完全由后端 Spring Security 处理。前端只负责:
|
||||
1. 调用 `/api/v1/auth/providers` 获取可用 Provider 列表
|
||||
2. 跳转到对应的 `authorizationUrl`
|
||||
3. 回调后通过 `/api/v1/auth/me` 检测登录态
|
||||
|
||||
### 4.2 登录态检测
|
||||
|
||||
```
|
||||
页面加载 → GET /api/v1/auth/me
|
||||
│
|
||||
┌─────────┴──────────┐
|
||||
│ 200: 已登录 │ 401: 未登录
|
||||
│ 存入全局状态 │ 门户页正常展示(匿名浏览)
|
||||
│ 渲染登录态 UI │ Dashboard/Admin 重定向到登录
|
||||
└────────────────────┘
|
||||
```
|
||||
|
||||
- TanStack Router `beforeLoad` 做路由守卫
|
||||
- Admin 路由额外检查角色
|
||||
- 前端权限控制粒度详见 [03-authentication-design.md](./03-authentication-design.md) 前端权限控制粒度章节
|
||||
|
||||
## 5 API 集成工作流
|
||||
|
||||
```
|
||||
后端 Springdoc → openapi.json
|
||||
→ openapi-typescript 生成类型
|
||||
→ openapi-fetch 创建客户端
|
||||
→ TanStack Query 封装为 hooks
|
||||
```
|
||||
|
||||
## 6 文件上传
|
||||
|
||||
一期 Web 端:zip 上传 → 后端解压校验 → 返回预览 → 用户确认 → 提交审核。
|
||||
支持 drag-and-drop + 进度条。
|
||||
|
||||
## 7 关键交互
|
||||
|
||||
**技能详情页**:SKILL.md Markdown 渲染、右侧信息栏(版本/下载量/评分/收藏/标签/空间)、版本切换、安装命令一键复制。匿名用户可浏览和下载,收藏/评分按钮提示登录。
|
||||
|
||||
**搜索页**:实时搜索(debounce 300ms)、技能卡片、排序(相关度/下载量/评分/最新)、命名空间过滤。匿名用户可搜索 PUBLIC 技能。
|
||||
|
||||
**审核页面**:左侧列表 + 右侧内容预览(Markdown + 文件树)、通过/拒绝 + 意见输入。
|
||||
92
docs/09-deployment.md
Normal file
92
docs/09-deployment.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# Astron Skills 部署架构与运维
|
||||
|
||||
## 1 K8s 部署拓扑
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Ingress │
|
||||
│ (Nginx) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ /api/* │ /*
|
||||
▼ ▼
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ Spring Boot │ │ Nginx / CDN │
|
||||
│ replicas: 2+ │ │ 静态资源 │
|
||||
└────────┬─────────┘ └──────────────────┘
|
||||
│
|
||||
┌────────┴──────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌──────────────┐
|
||||
│ MySQL │ │ Redis │ │ S3 / MinIO │
|
||||
│ (主从) │ │ │ │ │
|
||||
└────────┘ └────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
## 2 服务配置
|
||||
|
||||
- 无状态设计,所有状态存储在 MySQL / Redis / S3
|
||||
- 健康检查:`/actuator/health`(liveness + readiness 分离)
|
||||
- 优雅停机:`spring.lifecycle.timeout-per-shutdown-phase=30s`
|
||||
- JVM:`-XX:MaxRAMPercentage=75.0`
|
||||
|
||||
## 3 环境 Profile
|
||||
|
||||
| Profile | 用途 | 特点 |
|
||||
|---------|------|------|
|
||||
| `local` | 本地开发 | 本地 MySQL/MinIO,Mock OAuth(见下方说明) |
|
||||
| `dev` | 开发环境 | 共享基础设施,GitHub OAuth 测试应用 |
|
||||
| `staging` | 预发布 | 与生产同构 |
|
||||
| `prod` | 生产 | 多 Pod,完整基础设施 |
|
||||
|
||||
### 本地开发 Mock 登录
|
||||
|
||||
`local` profile 下提供两种开发登录方式:
|
||||
|
||||
1. **MockAuthFilter**(默认):通过 `X-Mock-User-Id` Header 模拟登录,自动创建 Session,无需真实 OAuth 流程
|
||||
2. **GitHub OAuth 测试应用**:配置 `OAUTH2_GITHUB_CLIENT_ID` / `OAUTH2_GITHUB_CLIENT_SECRET` 后可走真实 OAuth 流程(GitHub 支持 `http://localhost` 回调)
|
||||
|
||||
MockAuthFilter 仅在 `local` profile 激活,通过 `@Profile("local")` 注解保证不会泄漏到其他环境。
|
||||
|
||||
## 4 配置管理
|
||||
|
||||
- 敏感配置:K8s Secret(数据库/Redis/S3 凭证、OAuth2 Client ID/Secret)
|
||||
- 非敏感配置:K8s ConfigMap(文件大小限制、Session TTL 等)
|
||||
|
||||
## 5 可观测性
|
||||
|
||||
| 维度 | 方案 |
|
||||
|------|------|
|
||||
| 日志 | JSON 格式 stdout,包含 traceId/requestId |
|
||||
| 指标 | Actuator + Micrometer → Prometheus |
|
||||
| 链路追踪 | 一期 requestId 透传,后续接 Jaeger/Zipkin |
|
||||
| 告警 | 基于 Prometheus(5xx 率、延迟 P99、Pod 重启) |
|
||||
|
||||
requestId 透传:Ingress 注入 → Spring Filter 读取放入 MDC → 日志自动携带 → 响应 Header 回传。
|
||||
|
||||
## 6 构建与发布
|
||||
|
||||
```
|
||||
代码提交 → CI Pipeline
|
||||
├── server: mvn package → JAR
|
||||
└── web: pnpm build → dist/
|
||||
│
|
||||
▼
|
||||
Docker 多阶段构建
|
||||
├── server → openjdk:21-jre-slim
|
||||
└── web → nginx:alpine
|
||||
│
|
||||
▼
|
||||
推送镜像 → K8s 滚动更新
|
||||
```
|
||||
|
||||
Makefile 顶层命令:`make dev-server`, `make dev-web`, `make build`, `make docker`, `make generate-api`
|
||||
|
||||
## 7 数据库迁移
|
||||
|
||||
Flyway 管理 schema 变更:
|
||||
- 脚本路径:`server/astron-skills-app/src/main/resources/db/migration/`
|
||||
- 命名:`V{version}__{description}.sql`
|
||||
- 多 Pod 安全:Flyway 自带数据库锁
|
||||
112
docs/10-delivery-roadmap.md
Normal file
112
docs/10-delivery-roadmap.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Astron Skills 交付路线
|
||||
|
||||
## Phase 0:设计定稿(当前阶段)
|
||||
|
||||
产出:架构设计文档、数据库 DDL、API OpenAPI spec 草案、前端线框图
|
||||
|
||||
## Phase 1:工程骨架 + 认证打通
|
||||
|
||||
### 后端
|
||||
|
||||
- Maven 多模块初始化(6 个模块)
|
||||
- Spring Boot 启动、配置、Profile 分层
|
||||
- Flyway + 数据库初始化
|
||||
- Redis 集成(Session + 分布式锁)
|
||||
- Spring Security OAuth2 Client 配置(GitHub OAuth 登录)
|
||||
- CustomOAuth2UserService + IdentityBindingService(自动注册/绑定)
|
||||
- Spring Session (Redis) 管理、API Token 签发校验
|
||||
- RBAC 基础(SUPER_ADMIN / SKILL_ADMIN / USER_ADMIN / AUDITOR + 命名空间角色)
|
||||
- 全局异常处理、requestId 透传、日志格式
|
||||
- Springdoc OpenAPI、健康检查
|
||||
- CSRF 防护(Cookie-to-Header 模式,CLI API 豁免)
|
||||
- 本地开发 MockAuthFilter(`local` profile)
|
||||
- 基础限流:Nginx Ingress `limit-req` 按 IP 限流(认证/搜索/下载接口)
|
||||
|
||||
### 前端
|
||||
|
||||
- Vite + React + TypeScript 初始化
|
||||
- shadcn/ui + Tailwind 配置
|
||||
- TanStack Router 路由骨架、TanStack Query 配置
|
||||
- openapi-fetch 客户端生成管线
|
||||
- 布局组件、OAuth 登录流程(调用 `/api/v1/auth/providers` → 跳转)
|
||||
- 登录态检测(`/api/v1/auth/me`)+ 路由守卫
|
||||
- Makefile 顶层编排
|
||||
|
||||
### 验收
|
||||
|
||||
前后端能跑,GitHub OAuth 登录可用,AccessPolicy 准入策略生效,`/api/v1/auth/me` 可用,Token 可用,OpenAPI spec 可访问,Ingress 基础限流生效
|
||||
|
||||
## Phase 2:命名空间 + Skill 核心链路
|
||||
|
||||
### 后端
|
||||
|
||||
- 命名空间 CRUD + 成员管理
|
||||
- 对象存储集成
|
||||
- 技能发布(上传 → 校验 → 存储 → draft)
|
||||
- 技能查询(详情、版本、文件)、下载(打包 + 可见性检查,PUBLIC 匿名可下载)
|
||||
- 标签管理、搜索(MySQL Full-Text,匿名搜索限 PUBLIC)
|
||||
- 异步事件基础设施
|
||||
- Rate Limiting 升级(应用层精细限流:按用户/端点分类,基于 Redis 滑动窗口)
|
||||
|
||||
### 前端
|
||||
|
||||
- 首页、搜索页、命名空间主页(匿名可访问)
|
||||
- 技能详情页、版本历史页(PUBLIC 匿名可浏览/下载)
|
||||
- 发布页、我的技能列表
|
||||
- 命名空间管理页
|
||||
|
||||
### 验收
|
||||
|
||||
完整发布 → 存储 → 查询 → 下载链路,搜索可用,命名空间隔离生效,匿名用户可浏览/下载公共技能
|
||||
|
||||
## Phase 3:审核流程 + 评分收藏 + CLI API / ClawHub 兼容层
|
||||
|
||||
### 后端
|
||||
|
||||
- 审核流程(提交 → 审核 → 发布,含乐观锁)
|
||||
- 团队技能提升到全局(promotion_request 流程)
|
||||
- 评分 + 收藏 + 计数器(原子更新)
|
||||
- CLI API(whoami、publish、resolve、check)
|
||||
- ClawHub CLI 协议兼容层(registry metadata、resolve、download、publish、check 等核心接口)
|
||||
- 协议适配器与兼容性测试(针对 ClawHub CLI 的真实请求/响应样例)
|
||||
- 审计日志(同步落库)、幂等去重(idempotency_record + Redis)
|
||||
|
||||
### 前端
|
||||
|
||||
- 审核中心、命名空间审核页、提升审核页
|
||||
- 评分组件 + 收藏按钮(匿名用户点击提示登录)、我的收藏页
|
||||
- Token 管理页
|
||||
- 管理后台(用户管理、角色分配、准入审批、封禁/解封)
|
||||
|
||||
### 验收
|
||||
|
||||
发布必须经审核,分级审核权限生效,Astron CLI 全流程可用,ClawHub CLI 通过兼容层可完成核心 registry 操作,评分收藏可用
|
||||
|
||||
## Phase 4:运维增强 + 打磨
|
||||
|
||||
- 审计日志查询页面
|
||||
- 技能隐藏/恢复/版本撤回
|
||||
- Prometheus 指标暴露
|
||||
- Docker 镜像 + K8s 部署清单
|
||||
- 性能优化、安全加固
|
||||
- 文档完善
|
||||
- 后续 OAuth Provider 扩展准备(GitLab、Google 等)
|
||||
|
||||
## Phase 5:治理闭环 + 社交
|
||||
|
||||
- 评论功能
|
||||
- 举报/标记机制(用户举报 → 管理员处理 → 隐藏/撤回)
|
||||
- 自动安全预检(`PrePublishValidator` 实现:敏感信息扫描、恶意脚本检测)
|
||||
- Webhook/事件通知(发布通知、审核结果通知)
|
||||
- 多 Provider 账号显式绑定/合并流程
|
||||
|
||||
## 主要风险与应对
|
||||
|
||||
| 风险 | 应对 |
|
||||
|------|------|
|
||||
| GitHub OAuth 回调配置复杂 | 本地用 MockAuthFilter 解耦,OAuth 联调可并行 |
|
||||
| 审核流程需求变更 | skill_version.status 已预留审核状态 |
|
||||
| 搜索效果不佳 | SPI 架构允许随时切换实现 |
|
||||
| 前后端接口频繁变更 | OpenAPI spec 先行,类型自动生成 |
|
||||
| 新增 OAuth Provider | Spring Security OAuth2 原生多 Provider 支持,只需配置 + 属性映射 |
|
||||
| ClawHub CLI 协议细节与现有模型不完全一致 | 增加兼容适配层与协议回归测试,避免把 Astron 内部模型直接暴露给兼容客户端 |
|
||||
Loading…
Add table
Reference in a new issue