Skip to content

Commit 4776550

Browse files
authored
Merge pull request #498 from iflytek/feature/cloud-url-builtin-skills
feat(bootstrap): sync built-in skills from cloud manifest
2 parents 4f52680 + 8045e52 commit 4776550

26 files changed

Lines changed: 2393 additions & 4 deletions
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# 云存储链接内置 Skills 配置指南
2+
3+
本文说明如何通过仓库内 manifest 配置 SkillHub 内置 Skills,以及应用启动时这些 Skills 如何从云存储同步到 `@global` 空间。
4+
5+
适用场景:
6+
7+
- 希望 SkillHub 新部署实例默认带有一批官方内置 Skills。
8+
- 不希望把完整 Skill 包目录长期放在代码仓库和镜像中。
9+
- 内置 Skill 包已经上传到官方可控的云存储域名。
10+
11+
## 1. 方案概览
12+
13+
内置 Skills 不再以本地目录包的形式直接随仓库维护。当前方案只在仓库中维护一个 manifest 文件,应用启动时根据 manifest 中的云存储 URL 下载 zip 包,并通过 SkillHub 现有发布链路发布到 `@global`
14+
15+
流程:
16+
17+
```text
18+
维护 manifest -> 构建/部署 SkillHub 镜像 -> 应用 ready -> 后台读取 manifest -> 下载云存储 zip 包 -> 校验包内容 -> 发布到 @global -> 对所有用户公开可见
19+
```
20+
21+
核心文件:
22+
23+
```text
24+
server/skillhub-app/src/main/resources/builtin-skills/manifest.json
25+
```
26+
27+
首版 manifest 只需要维护三个字段:
28+
29+
- `slug`:Skill 在 `@global` 下的 slug。
30+
- `version`:期望同步的 Skill 版本。
31+
- `url`:Skill zip 包的云存储 HTTPS 链接。
32+
33+
## 2. Manifest 配置
34+
35+
manifest 文件格式如下:
36+
37+
```json
38+
{
39+
"skills": [
40+
{
41+
"slug": "skillhub-hello",
42+
"version": "1.0.0",
43+
"url": "https://bjcdn.openstorage.cn/<path-to-builtin-skill-zip>/skillhub-hello-1.0.0.zip"
44+
}
45+
]
46+
}
47+
```
48+
49+
可以配置多个 Skills,也可以为同一个 `slug` 配置多个版本:
50+
51+
```json
52+
{
53+
"skills": [
54+
{
55+
"slug": "skillhub-hello",
56+
"version": "1.0.0",
57+
"url": "https://bjcdn.openstorage.cn/<path-to-builtin-skill-zip>/skillhub-hello-1.0.0.zip"
58+
},
59+
{
60+
"slug": "skillhub-hello",
61+
"version": "1.1.0",
62+
"url": "https://bjcdn.openstorage.cn/<path-to-builtin-skill-zip>/skillhub-hello-1.1.0.zip"
63+
},
64+
{
65+
"slug": "skillhub-guide",
66+
"version": "1.0.0",
67+
"url": "https://bjcdn.openstorage.cn/<path-to-builtin-skill-zip>/skillhub-guide-1.0.0.zip"
68+
}
69+
]
70+
}
71+
```
72+
73+
配置要求:
74+
75+
- `skills` 必须是数组。
76+
- 每一项必须同时填写 `slug``version``url`
77+
- `slug` 必须符合 SkillHub slug 规则。
78+
- 同一个 `slug + version` 重复出现时,只处理第一条,后续重复项会被跳过。
79+
- manifest 最多处理前 100 条 entries。
80+
- 同一个 `slug` 的多个版本建议按从旧到新的顺序排列;运行时按 manifest 文件顺序处理,不做自动版本排序。
81+
82+
## 3. Skill 包要求
83+
84+
manifest 中的 `url` 必须指向 zip 包。zip 包需要满足 SkillHub Skill 包协议:
85+
86+
- zip 可以在根目录直接包含 `SKILL.md`,也可以包含一个单独的顶层 Skill 目录,并在该目录下包含 `SKILL.md`
87+
- `SKILL.md` frontmatter 中必须包含合法的 `name``description``version` 等元数据。
88+
- `SKILL.md` 中的 `name` 经过 slug 归一化后,必须等于 manifest 中的 `slug`
89+
- `SKILL.md` 中的 `version` 必须等于 manifest 中的 `version`
90+
- 包内容仍会经过 SkillHub 现有发布校验,包括文件数量、文件大小、扩展名、文件类型等规则。
91+
92+
示例:
93+
94+
```text
95+
skillhub-hello-1.0.0.zip
96+
├── SKILL.md
97+
├── README.md
98+
└── scripts/
99+
└── check.js
100+
```
101+
102+
同样支持标准单目录 Skill 包:
103+
104+
```text
105+
skillhub-hello-1.0.0.zip
106+
└── skillhub-hello/
107+
├── SKILL.md
108+
└── README.md
109+
```
110+
111+
如果 zip 中存在多个顶层目录,或在多个目录中同时出现 `SKILL.md`,同步器会跳过该项并记录错误,避免误选入口。
112+
113+
## 4. URL 安全限制
114+
115+
内置 Skill 同步由后端在启动时主动下载远程文件,因此 URL 有严格限制。
116+
117+
首版只允许:
118+
119+
- `https://` 协议。
120+
- host 为 `bjcdn.openstorage.cn`
121+
- host 为 `bjcdn.openstorage.cn` 的子域名,例如 `assets.bjcdn.openstorage.cn`
122+
- 默认 HTTPS 端口,或显式 `:443`
123+
124+
以下 URL 会被跳过:
125+
126+
- `http://...`
127+
-`bjcdn.openstorage.cn` 及其子域名。
128+
- 带 userinfo 的 URL,例如 `https://user:pass@bjcdn.openstorage.cn/file.zip`
129+
- 非 443 端口,例如 `https://bjcdn.openstorage.cn:8443/file.zip`
130+
- `localhost`、IP 地址、IPv6 literal 等 host。
131+
- 需要 HTTP redirect 才能拿到文件的链接。
132+
133+
如果某一项 URL 不符合规则,SkillHub 会记录日志并跳过该项,不会阻塞应用启动。
134+
135+
## 5. 启动同步流程
136+
137+
应用 ready 后同步器会在后台执行一次,不阻塞应用 ready。
138+
139+
详细流程:
140+
141+
1. 检查 `skillhub.builtin-skills.enabled` 是否开启。
142+
2. 读取 `classpath:builtin-skills/manifest.json`
143+
3. 查询 `@global` 命名空间是否存在;如果不存在,跳过同步。
144+
4. 确保系统发布者 `builtin-skill-publisher` 存在,并且该账号带有系统账号标记。
145+
5. 如果该用户 ID 已被非系统账号占用,直接跳过本次内置 Skill 同步,不授予 `@global` 权限。
146+
6. 如果系统发布者还不是 `@global` 成员,则创建 `OWNER` 成员记录;已有成员记录不会自动改角色。
147+
7. 按 manifest 顺序处理每一个 item。
148+
8. 下载前先检查 `@global/{slug}` 和目标版本是否已经存在;如果已经确定应跳过,则不发起远程下载。
149+
9. 只有需要发布新 Skill 或新版本时,才下载对应 zip 包。
150+
10. 解包并校验 Skill 入口 `SKILL.md`
151+
11. 校验 manifest 中的 `slug``version` 与包内元数据一致。
152+
12. 发布前再次检查是否已存在同名 Skill 或同版本,处理并发启动场景。
153+
13. 需要发布时调用现有 `SkillPublishService.publishFromEntries(...)`
154+
14. 发布完成后,该 Skill 位于 `@global/{slug}`,可见性为 `PUBLIC`
155+
156+
同步逻辑不会直接写数据库 seed 数据。它复用现有发布服务,因此会保留现有的包校验、对象存储写入、版本记录、latest version 更新、事件和搜索索引同步。
157+
158+
## 6. 幂等与冲突处理
159+
160+
内置 Skill 同步支持重复启动和多次部署。
161+
162+
幂等键:
163+
164+
```text
165+
@global/{slug} + version
166+
```
167+
168+
行为说明:
169+
170+
| 场景 | 行为 |
171+
|---|---|
172+
| `@global/{slug}` 不存在 | 发布 manifest 中的 Skill |
173+
| `@global/{slug}` 已存在,owner 是 `builtin-skill-publisher`,但目标版本不存在 | 发布新版本 |
174+
| 同版本已存在且已发布 | 下载前跳过 |
175+
| 同版本已存在但不是 `PUBLISHED` | 下载前跳过并记录日志 |
176+
| `@global/{slug}` 已被其他 owner 创建或发布 | 下载前跳过并记录 warning |
177+
178+
这意味着内置同步不会接管用户或管理员已经创建的同 slug Skill;即使该 Skill 仍处于待审、未发布或已拒绝状态,也会跳过对应 manifest item。
179+
同版本已存在时,同步器不会重新下载远端 zip,也不会验证远端对象内容是否发生漂移。
180+
181+
如果多实例同时启动,可能出现多个实例同时尝试发布同一个内置版本。同步器会在发布失败后重新查询目标版本;如果发现同版本已经以相同内容发布成功,则视为并发场景下的正常跳过。
182+
183+
## 7. 开关配置
184+
185+
内置 Skill 同步默认开启。
186+
187+
Spring 配置项:
188+
189+
```yaml
190+
skillhub:
191+
builtin-skills:
192+
enabled: true
193+
```
194+
195+
环境变量:
196+
197+
```dotenv
198+
SKILLHUB_BUILTIN_SKILLS_ENABLED=true
199+
```
200+
201+
如需禁用启动同步:
202+
203+
```dotenv
204+
SKILLHUB_BUILTIN_SKILLS_ENABLED=false
205+
```
206+
207+
禁用后,应用 ready 后不会读取 manifest,也不会下载或发布任何内置 Skill。
208+
209+
## 8. 维护流程
210+
211+
新增一个内置 Skill 的推荐步骤:
212+
213+
1. 准备 Skill 包,并确认 zip 根目录直接包含 `SKILL.md`,或只有一个顶层 Skill 目录且该目录包含 `SKILL.md`
214+
2. 检查 `SKILL.md` 中的 `name``version`
215+
3. 上传 zip 到 `bjcdn.openstorage.cn` 或其子域名下的官方云存储路径。
216+
4.`server/skillhub-app/src/main/resources/builtin-skills/manifest.json` 中新增一项。
217+
5. 确保 manifest 中的 `slug` 等于 `SKILL.md name` 归一化后的 slug。
218+
6. 确保 manifest 中的 `version` 等于 `SKILL.md version`
219+
7. 本地或测试环境启动 SkillHub,查看后端日志确认同步结果。
220+
8. 在 Web UI 或 API 中确认 `@global/{slug}` 已公开可见。
221+
222+
更新一个已有内置 Skill 的推荐步骤:
223+
224+
1. 不要覆盖已经发布过的旧版本 zip 内容。
225+
2.`SKILL.md` 中提升 `version`
226+
3. 重新打包并上传新的 zip 文件。
227+
4. 在 manifest 中新增一条同 `slug`、新 `version` 的记录。
228+
5. 保留旧版本记录,除非产品明确不再需要该旧版本在新实例中预置。
229+
230+
不推荐:
231+
232+
- 修改旧版本 zip 内容但保持同一个 `version`
233+
- 把 URL 指向会发生内容变化的临时对象。
234+
- 使用需要登录、签名跳转或重定向的下载链接。
235+
236+
## 9. 日志与排查
237+
238+
启动时可以通过后端日志观察同步结果。
239+
240+
常见日志含义:
241+
242+
| 日志含义 | 处理建议 |
243+
|---|---|
244+
| manifest not found | 确认 `builtin-skills/manifest.json` 是否被打进 classpath |
245+
| publisher account id already exists but is not a system account | `builtin-skill-publisher` 已被普通账号占用;需要人工处理账号冲突后再启用内置同步 |
246+
| slug, version, and url are required | 检查 manifest item 是否缺字段或字段不是字符串 |
247+
| slug is invalid | 检查 slug 是否符合 SkillHub slug 规则 |
248+
| URL is not allowed | 检查 URL 是否为 HTTPS、host 是否为 `bjcdn.openstorage.cn` 或其子域名 |
249+
| package download failed | 检查云存储对象是否存在、是否返回 HTTP 200、是否超时 |
250+
| package must contain SKILL.md | 检查 zip 是否存在唯一可识别的 `SKILL.md` 入口 |
251+
| manifest version does not match package version | 检查 manifest `version``SKILL.md version` 是否一致 |
252+
| slug already belongs to another user | 说明 `@global/{slug}` 已被非内置发布者创建或发布,内置同步不会覆盖 |
253+
| published fingerprint differs | 并发发布异常后发现同一内置版本已存在但内容不同,需要人工确认是否发生了版本冲突 |
254+
255+
如果某个 manifest item 失败,后续 item 仍会继续处理,应用可用状态不受影响。
256+
257+
## 10. 验收检查
258+
259+
配置或新增内置 Skill 后,建议至少完成以下检查:
260+
261+
- manifest JSON 格式合法。
262+
- 每个 item 都包含 `slug``version``url`
263+
- URL 使用 `https://bjcdn.openstorage.cn/...` 或可信子域名。
264+
- zip 根目录直接包含 `SKILL.md`,或只有一个顶层 Skill 目录且该目录包含 `SKILL.md`
265+
- `SKILL.md name` 归一化后的 slug 与 manifest `slug` 一致。
266+
- `SKILL.md version` 与 manifest `version` 一致。
267+
- 启动日志没有该 item 的 warning 或 error。
268+
- Web UI 中可以看到 `@global/{slug}`
269+
- Skill 可被匿名或登录用户按公开 Skill 规则发现。

server/skillhub-app/src/main/java/com/iflytek/skillhub/SkillhubApplication.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.iflytek.skillhub;
22

3+
import com.iflytek.skillhub.bootstrap.BuiltinSkillProperties;
34
import com.iflytek.skillhub.config.ProfileFieldPolicyProperties;
45
import com.iflytek.skillhub.config.ProfileModerationProperties;
56
import org.springframework.boot.SpringApplication;
@@ -10,7 +11,11 @@
1011
* Main Spring Boot entry point for the SkillHub backend application.
1112
*/
1213
@SpringBootApplication
13-
@EnableConfigurationProperties({ProfileModerationProperties.class, ProfileFieldPolicyProperties.class})
14+
@EnableConfigurationProperties({
15+
BuiltinSkillProperties.class,
16+
ProfileModerationProperties.class,
17+
ProfileFieldPolicyProperties.class
18+
})
1419
public class SkillhubApplication {
1520
public static void main(String[] args) {
1621
SpringApplication.run(SkillhubApplication.class, args);

0 commit comments

Comments
 (0)