Skip to content

Commit 372d3f2

Browse files
committed
skill
1 parent ea0a3ee commit 372d3f2

7 files changed

Lines changed: 145 additions & 663 deletions

File tree

.agents/skills/yunyu-admin-operator/references/connection-management.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44

55
## 一、连接信息组成
66

7-
调用后台接口前,至少需要以下两个字段
7+
调用后台接口前,至少需要以下字段
88

99
1. `baseUrl`
10-
目标后台域名或基础地址,例如:
10+
目标后端域名或基础地址,例如:
1111
- `http://127.0.0.1:20000`
12-
- `https://admin.example.com`
13-
2. `token`
12+
- `https://yunyu-server-native.ideaflow.top`
13+
2. `frontendBaseUrl`(可选,文章操作时需要)
14+
目标前端域名或基础地址,用于调用前端提供的 Markdown 渲染接口,例如:
15+
- `http://127.0.0.1:19999`
16+
- `https://yunyu.ideaflow.top`
17+
仅在新增或编辑文章时需要;纯数据操作(分类/标签/配置管理等)不需要此字段。
18+
3. `token`
1419
后台登录后的 Bearer token,不需要手动加 `Bearer ` 前缀时,由调用方按实际请求格式处理。
1520

1621
## 二、本地保存位置
@@ -26,6 +31,7 @@
2631
```json
2732
{
2833
"baseUrl": "http://127.0.0.1:20000",
34+
"frontendBaseUrl": "http://127.0.0.1:19999",
2935
"token": "<SECRET>",
3036
"updatedAt": "2026-04-27T00:00:00+08:00"
3137
}
@@ -34,6 +40,7 @@
3440
说明:
3541

3642
- `baseUrl``token` 是必填。
43+
- `frontendBaseUrl` 是选填,仅在需要 Markdown 渲染(创建/编辑文章)时使用。
3744
- `updatedAt` 用于记录最后一次更新连接信息的时间,便于排查失效问题。
3845
- 该文件只用于本地 skill 运行,不应提交到仓库。
3946

@@ -47,7 +54,7 @@
4754
2. 如果存在有效 `baseUrl``token`,优先直接使用。
4855
3. 如果文件不存在、字段缺失或用户明确要求切换环境,再向用户索要新的 `baseUrl``token`
4956
4. 收到新的连接信息后,覆盖写回本地连接文件,供后续复用:
50-
`bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh set --base-url <URL> --token <TOKEN>`
57+
`bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh set --base-url <URL> --token <TOKEN> [--frontend-base-url <URL>]`
5158

5259
## 四、脚本用法
5360

@@ -57,7 +64,8 @@
5764
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh show
5865
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh get baseUrl
5966
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh get token
60-
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh set --base-url <URL> --token <TOKEN>
67+
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh get frontendBaseUrl
68+
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh set --base-url <URL> --token <TOKEN> [--frontend-base-url <URL>]
6169
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh clear
6270
```
6371

.agents/skills/yunyu-admin-operator/references/post-management.md

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,58 @@
22

33
本文档用于指导 agent 通过后台接口完成文章新增、编辑与相关 AI 辅助操作。
44

5+
## 前置:Markdown 渲染接口
6+
7+
新增或编辑文章正文时,必须先调用前端提供的 Markdown 渲染接口,拿到与后台编辑器一致的 HTML 和目录 JSON,再提交给后端。
8+
9+
**渲染接口地址:**
10+
```
11+
POST {frontendBaseUrl}/api/render-markdown
12+
Content-Type: application/json
13+
```
14+
15+
**请求体:**
16+
```json
17+
{ "markdown": "# 标题\n\n正文内容" }
18+
```
19+
20+
**响应体:**
21+
```json
22+
{
23+
"html": "<h1 id=\"...\">标题</h1>\n<p>正文内容</p>\n",
24+
"toc": [{ "id": "...", "text": "标题", "level": 1 }],
25+
"tocJson": "[{\"id\":\"...\",\"text\":\"标题\",\"level\":1}]",
26+
"plainText": "标题 正文内容",
27+
"readingMinutes": 1
28+
}
29+
```
30+
31+
**说明:**
32+
- `html` → 对应后端接口的 `contentHtml` 字段
33+
- `tocJson` → 对应后端接口的 `contentTocJson` 字段
34+
- `frontendBaseUrl` 从本地连接文件中读取;如果不存在,可向用户索要
35+
- 前端渲染接口与后台编辑器使用完全相同的 `markdown-it` + Shiki 管线,保证渲染结果一致**
36+
37+
**完整流程:**
38+
39+
```text
40+
1. 取文章本地 Markdown 内容
41+
42+
43+
2. POST {frontendBaseUrl}/api/render-markdown
44+
body: { markdown: "..." }
45+
46+
47+
3. 拿到 html + tocJson
48+
49+
50+
4. POST /api/admin/posts(创建)或 PUT /api/admin/posts/{id}(编辑)
51+
body: { contentMarkdown, contentHtml, contentTocJson, ...其他字段 }
52+
53+
⚠️ 创建文章时,html + tocJson 必须和基础字段一起在 POST 中提交,
54+
不要先创建再 PUT 补全。一次完成,不要分两步。
55+
```
56+
557
## 零、统一前置步骤
658

759
在查询或写入文章相关后台接口前,先处理连接信息:
@@ -20,46 +72,53 @@
2072
建议按以下顺序操作:
2173

2274
1. 先确认用户是否已经提供文章标题和正文。
23-
2. 如果用户没有指定分类或标签,先查询可用列表:
75+
2. 如果在创建前需要生成正文的 HTML 和目录,先调用渲染接口:
76+
- `POST {frontendBaseUrl}/api/render-markdown`
77+
- 传入正文 Markdown,拿到 `html``tocJson` 等字段
78+
- 后续提交时一并传给后端
79+
3. 如果用户没有指定分类或标签,先查询可用列表:
2480
- `GET /api/admin/categories`
2581
- `GET /api/admin/tags`
26-
3. 根据文章标题和正文语义,从已有分类、标签里挑选最合适的项:
82+
4. 根据文章标题和正文语义,从已有分类、标签里挑选最合适的项:
2783
- 若确实存在明显匹配项,应自动选中对应分类 / 标签
2884
- 若没有明显匹配项,可以留空,不要为了凑字段乱选
29-
4. 专题默认视为不设置
85+
5. 专题默认视为"不设置"
3086
- 只有用户明确说本次也要挂专题时,才查询 `GET /api/admin/topics`
3187
- 用户未指定时,不调用专题相关接口
32-
5. `slug``summary``seoTitle``seoDescription`
88+
6. `slug``summary``seoTitle``seoDescription`
3389
- 如果用户已提供,优先使用用户提供值。
3490
- 如果用户未提供,优先由当前 AI agent 根据标题和正文直接生成。
3591
- 默认不要为了补这些字段再额外调用 `POST /api/admin/posts/ai/meta/generate`
3692
- 只有当用户明确要求使用后台 AI 元信息生成能力,或需要和后台内置生成结果保持一致时,才考虑调用该接口。
3793
- 调用该接口时优先传 `title``contentMarkdown`;后端会统一组装提示词并返回 OpenAI Chat 风格结果。
38-
6. 文章状态默认建议:
94+
7. 文章状态默认建议:
3995
- 若用户没有明确要求立即发布,默认传 `DRAFT`
40-
- 若用户明确说直接发布,传 `PUBLISHED`
41-
- 若用户明确说保存但先下线,传 `OFFLINE`
42-
7. 布尔默认建议:
96+
- 若用户明确说"直接发布",传 `PUBLISHED`
97+
- 若用户明确说"保存但先下线",传 `OFFLINE`
98+
8. 布尔默认建议:
4399
- `isTop=false`
44100
- `isRecommend=false`
45101
- `allowComment=true`
46-
8. 内容权限默认不启用。
47-
- 应主动提示用户:当前默认不启用内容权限,要不要开启文章访问控制或隐藏内容权限?
48-
9. 若用户不启用内容权限:
102+
9. 内容权限默认不启用。
103+
- 应主动提示用户:"当前默认不启用内容权限,要不要开启文章访问控制或隐藏内容权限?"
104+
10. 若用户不启用内容权限:
49105
- 仍建议提交默认的禁用结构,保持与当前后台编辑器一致。
50-
10. 若用户启用整篇文章访问控制:
106+
11. 若用户启用整篇文章访问控制:
51107
- 至少选择一个规则:`LOGIN` / `WECHAT_ACCESS_CODE` / `ACCESS_CODE`
52108
- 若包含 `ACCESS_CODE`,必须同时填写:
53109
- `articleAccessCode`
54110
- `articleAccessCodeHint`
55-
11. 若用户启用尾部隐藏内容:
56-
- 必须填写:
57-
- `tailHiddenAccess.enabled=true`
58-
- `tailHiddenAccess.title`
59-
- `tailHiddenAccess.ruleTypes`
60-
- `tailHiddenContentMarkdown`
61-
12. 创建文章最终调用:
111+
12. 若用户启用尾部隐藏内容:
112+
- 必须填写:
113+
- `tailHiddenAccess.enabled=true`
114+
- `tailHiddenAccess.title`
115+
- `tailHiddenAccess.ruleTypes`
116+
- `tailHiddenContentMarkdown`
117+
13. 创建文章最终调用:
62118
- `POST /api/admin/posts`
119+
- 请求体中应包含从渲染接口获取的 `contentHtml``contentTocJson`
120+
- **⚠️ 关键约束:渲染结果(contentHtml、contentTocJson)必须一次性带入 POST 请求,禁止先创建再 PUT 补全。**
121+
创建时只传基础字段后续再 PUT 补充渲染结果属于冗余请求,既浪费 token 也无必要;POST 创建本就支持所有字段同时提交。
63122

64123
## 二、编辑文章
65124

@@ -70,12 +129,18 @@
70129
1. 先定位目标文章。
71130
2.`GET /api/admin/posts/{postId}` 读取当前完整详情。
72131
3. 在原详情基础上合并本次修改。
73-
4. 因为更新接口要求很多字段保留完整语义,不能只传一个零散字段然后丢掉其他字段。
74-
5. 若用户只说“改摘要”或“改标题”:
132+
4. 如果本次修改涉及正文内容,先调用渲染接口获取新的 HTML 和目录:
133+
- `POST {frontendBaseUrl}/api/render-markdown`
134+
- 传入新的正文 Markdown,拿到 `html``tocJson`
135+
- 在最终提交时替换 `contentHtml``contentTocJson`
136+
5. 如果本次修改不涉及正文(只改状态、封面、标签等),**不需要调用渲染接口**
137+
- `contentMarkdown` 设置为 `null` 即可,后端不会清空已有正文(详见后端增量更新逻辑)
138+
6. 因为更新接口要求很多字段保留完整语义,不能只传一个零散字段然后丢掉其他字段。
139+
7. 若用户只说"改摘要"或"改标题":
75140
- 也应该先获取原文详情
76141
- 再仅修改目标字段
77142
- 其余字段保持原值
78-
6. 更新文章最终调用:
143+
8. 更新文章最终调用:
79144
- `PUT /api/admin/posts/{postId}`
80145

81146
## 三、文章创建与编辑时的重点字段

.agents/skills/yunyu-admin-operator/scripts/admin_connection.sh

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ usage() {
1616
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh show
1717
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh get baseUrl
1818
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh get token
19-
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh set --base-url <URL> --token <TOKEN>
19+
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh get frontendBaseUrl
20+
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh set --base-url <URL> --token <TOKEN> [--frontend-base-url <URL>]
2021
bash .agents/skills/yunyu-admin-operator/scripts/admin_connection.sh clear
2122
2223
说明:
2324
show 展示当前连接信息,token 会做脱敏处理。
24-
get <field> 读取指定字段,支持 baseUrl / token / updatedAt。
25+
get <field> 读取指定字段,支持 baseUrl / token / frontendBaseUrl / updatedAt。
2526
set 保存或覆盖连接信息。
2627
clear 删除本地连接信息文件。
2728
USAGE
@@ -86,13 +87,17 @@ mask_token() {
8687
# 展示当前连接信息。
8788
show_connection() {
8889
require_connection_file
89-
local base_url token updated_at masked_token
90+
local base_url frontend_base_url token updated_at masked_token
9091
base_url="$(read_field baseUrl)"
92+
frontend_base_url="$(read_field frontendBaseUrl 2>/dev/null || true)"
9193
token="$(read_field token)"
9294
updated_at="$(read_field updatedAt 2>/dev/null || true)"
9395
masked_token="$(mask_token "$token")"
9496

9597
echo "baseUrl: $base_url"
98+
if [[ -n "$frontend_base_url" ]]; then
99+
echo "frontendBaseUrl: $frontend_base_url"
100+
fi
96101
echo "token: $masked_token"
97102
if [[ -n "$updated_at" ]]; then
98103
echo "updatedAt: $updated_at"
@@ -102,6 +107,7 @@ show_connection() {
102107
# 保存连接信息。
103108
set_connection() {
104109
local base_url=""
110+
local frontend_base_url=""
105111
local token=""
106112

107113
while [[ $# -gt 0 ]]; do
@@ -110,6 +116,10 @@ set_connection() {
110116
base_url="${2:-}"
111117
shift 2
112118
;;
119+
--frontend-base-url)
120+
frontend_base_url="${2:-}"
121+
shift 2
122+
;;
113123
--token)
114124
token="${2:-}"
115125
shift 2
@@ -132,15 +142,16 @@ set_connection() {
132142
fi
133143

134144
ensure_local_dir
135-
python3 - "$CONNECTION_FILE" "$base_url" "$token" <<'PY'
145+
python3 - "$CONNECTION_FILE" "$base_url" "$frontend_base_url" "$token" <<'PY'
136146
import json
137147
import sys
138148
from datetime import datetime, timezone, timedelta
139149
from pathlib import Path
140150
141151
file_path = Path(sys.argv[1])
142152
base_url = sys.argv[2]
143-
token = sys.argv[3]
153+
frontend_base_url = sys.argv[3] or None
154+
token = sys.argv[4]
144155
shanghai = timezone(timedelta(hours=8))
145156
updated_at = datetime.now(shanghai).isoformat(timespec="seconds")
146157
@@ -149,6 +160,8 @@ payload = {
149160
"token": token,
150161
"updatedAt": updated_at,
151162
}
163+
if frontend_base_url:
164+
payload["frontendBaseUrl"] = frontend_base_url
152165
file_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
153166
PY
154167

docs/前端/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
- `12-封面古诗词打字机组件接入方案.md`
2727
- `14-前台轻动态装饰组件方案.md`
2828
- `15-后台列表公共组件技术方案.md`
29+
- `16-前台多主题页面架构方案.md`
2930
- `ui/01-基础视觉与交互规范.md`
3031
- `ui/02-文章内容展示UI规范.md`
3132
- `ui/03-后台布局与工作台规范.md`

yunyu-server/src/main/java/com/ideaflow/yunyu/module/post/admin/service/AdminPostService.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,13 @@ private void saveOrUpdatePostContent(Long postId,
304304
ContentAccessConfig contentAccessConfig,
305305
String tailHiddenContentMarkdown,
306306
String tailHiddenContentHtml) {
307-
String markdown = contentMarkdown == null ? "" : contentMarkdown.trim();
307+
// 如果 contentMarkdown 为 null,说明调用方只更新主表字段(如状态/封面),
308+
// 不打算修改正文,跳过整个正文更新,避免 null 被当作 "" 覆盖数据库已有内容。
309+
if (contentMarkdown == null) {
310+
return;
311+
}
312+
313+
String markdown = contentMarkdown.trim();
308314
String html = contentHtml == null || contentHtml.isBlank() ? markdown : contentHtml.trim();
309315
String tocJson = contentTocJson == null || contentTocJson.isBlank() ? null : contentTocJson.trim();
310316
String normalizedVideoUrl = normalizeOptionalValue(videoUrl);

0 commit comments

Comments
 (0)