fn_b/docs/maintenance/EDITORIAL_API.md

162 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

# FNA_V3 独立文章 API
状态:2026-10-27本地实施并联调;2026-10-29已随独立test预览提交、推送和部署,10篇教程真实API迁移通过后,test编辑接口已关闭且临时密钥移除。正式站现已切换,正式10篇教程亦通过同一API迁移;生产临时密钥用后删除,编辑接口默认关闭,后续明确写作/发布任务再按绑定环境启用并完成后关闭。test执行和回执沿[发布记录](RELEASE_PREFLIGHT_2026-10-28.md#当前门槛先test预览确认后再切正式2026-10-29),日常本地调用不受影响。用户明确采用本站独立接收 API,不经过 ATOM;不新增 ATOM 依赖。入口为 `http://fna-v3.loc/api/editorial/v1`,生产地址尚未指定。
## 职责与数据
AI负责选题、正文、图解、官方资料核对与自然内链;本站负责保存草稿、生成实际模板预览、按冻结版本发布、记录结果和回退。文章目录和关联登记仍以[写作索引](../content/WRITING.md)为准;API不自动改写该Markdown,也不自动创造反向链接。将相关旧文的修改显式放进同一批次,预览后原子发布。
项目正文源保留可编辑稿;`editorial_drafts`保存收到并渲染后的冻结版本,发布读取该快照,不临时重新读取文件。`articles`为公开显示数据,草稿不写入它。`editorial_documents`按稳定`content_key`绑定本站文章;`editorial_versions`保留已发布版本和首次接管旧文的基线版本0;`editorial_publications`保存独立发布回执及后续处理状态。图片元信息在`editorial_assets`。
新API不调用旧接收器,不发送旧Telegram通知、IndexNow提交或ATOM请求。旧资源接收接口继续沿用原流程,不在本次改造范围;普通编辑API拒绝免费节点与小火箭ID两个自动资源栏目。旧接口/人工直接编辑同一文章会造成草稿基线变化,新API发布时返回409,不能静默覆盖并行修改。
## 凭据与环境
配置:`config/editorial.php`。
- 本地默认启用,但必须先有有效凭据。生产默认关闭,需部署时明确设置`EDITORIAL_API_ENABLED`、`EDITORIAL_BASE_URL`和独立搜索索引;本次没有生产凭据或上线操作。
- 本地安装命令`php artisan editorial:setup`仅允许本机`fna_v3`,已存在凭据不覆盖。凭据在`storage/app/private/editorial/credential.json`(目录700、文件600,Git忽略),结构使用`gemdocs.credential.v1`及`fields.api_key`,绑定项目、环境和base_url。程序在内存中读取,禁止打印、复制进正文、提交Git或放到命令参数。
- 认证为`Authorization: Bearer <专用密钥>`。应用提供下述CLI代为读取和发送,不需要AI将密钥带入工具输出。
- 所有编辑接口拒绝整页缓存;预览和私有图片使用30分钟签名地址,返回`noindex, nofollow`、`private, no-store`。持有签名链接的人在有效期内可预览,链接不应公开转载;失效后通过查询草稿重新取得链接。
- 发布接口按分钟限流;API错误不输出SQL、凭据或请求头。生产认证、HTTPS与访问路线仍按项目部署约束核实,不能因代码已存在就认为生产已可调用。
## 调用方式
以下命令调用当前环境绑定的API,`path`相对于`/api/editorial/v1/`,输出JSON结构为`http_status`与`data`。
```sh
php artisan editorial:request GET catalog
php artisan editorial:request GET source-articles/362
php artisan editorial:request POST assets --image=/绝对路径/操作图.png
php artisan editorial:request POST drafts --payload=/绝对路径/draft.json
php artisan editorial:request GET drafts/草稿UUID
php artisan editorial:request POST drafts/草稿UUID/publish --payload=/绝对路径/publish.json
php artisan editorial:request GET publications/发布请求UUID
```
示例中的中文路径和UUID均需替换。请求正文放文件,不把多行正文或密钥拼接进shell。CLI关闭HTTP代理和重定向,本地模式拒绝非本地目标;外部环境的调用仍需既有访问授权。
### 1 保存草稿
`POST /drafts`:
```json
{
"request_id": "29e2f78a-7adc-4bf6-9eef-1c5f4a586b13",
"articles": [
{
"content_key": "verge-windows-start",
"expected_revision": 0,
"adopt_article_id": 362,
"slug": "clash-verge-rev-windows",
"title": "Clash Verge Rev 使用教程:从安装到第一次连接(Windows)",
"summary": "下载安装、导入订阅、选择节点和开启系统代理。",
"category_slug": "windows",
"format": "markdown",
"body": "<h2 id=\"install\">下载与安装</h2>\n\n此处为实际正文。",
"cover": null,
"show_cover": false
}
]
}
```
这是结构示例,不可拿占位正文覆盖现有教程。完整正文应来自项目源文件或已核对的接收端原文。
- `request_id`为本次保存动作的新UUID;同UUID同正文重试返回同草稿,不递增版本;同UUID改正文返回409。每批最多10篇,单篇正文最多500000字符。
- `content_key`为写作索引中的稳定键。首次为0,后续`expected_revision`必须等于`GET /articles/{key}`返回的`latest_revision`,成功后递增。新草稿会使同文章的旧草稿失去发布资格,但不会改变线上已有正文。
- 新文章省略`adopt_article_id`。接管现有文章必须显式指定准确ID并沿用原slug;不因标题相似自动绑定。首次接管仅建立映射和旧版快照,不改公开正文。不能把已有键转绑另一篇文章,也不能改已绑定文章URL。
- `category_slug`必须为本站已启用的编辑栏目。`tag_ids`可选,必须是已有标签ID;省略时保留旧文标签,新文默认为空,不自动创建标签。
- `format`为`markdown`(默认)或`html`。Markdown支持基础HTML图解,但过滤危险链接、脚本等;富文本不接受任意脚本、事件属性或内联样式。段落锚点使用显式`id`。
- `cover`支持已存在的本站`/images/...`路径,或上传后的`asset:SHA256`;显式null可清除旧封面,省略时保留。`show_cover`控制正文头图,省略时保留旧值,新文默认不展示头图。
- 站内文章链接为`/a/slug.html#anchor`。本站完整域名链接不作为站内链接接受;引用公共官方资料可用外部HTTPS链接。文章目标必须已发布或在同批草稿内,锚点必须存在。分类链接校验真实父子关系,避免宽松路由把错误URL也返回200。
返回`draft_id`、`hash`、`status`及每篇文章的版本、预期路径和`preview_url`。查询草稿会生成新的短期预览链接;草稿内容不变。预览复用本站文章模板,关闭统计、点赞和热门列表;同批次文章之间的链接改为签名预览,避免跳到不存在的正式页。
### 新文章点赞设置
2026-10-29起,创建新文时支持可选`like_settings`,例如:
```json
"like_settings": {"seven_day": 12, "yearly": 50, "one_year": 200}
```
省略时采用[AI写作规范的默认参数](../content/WRITING.md#ai-新文章的点赞参数2026-10-29),在草稿保存时随机取值并冻结;草稿响应`articles[].like_settings`返回确认值。旧文改稿、接管及内容回退保留原点赞参数/进度,普通改稿显式传入此对象返回422;0可在新建时关闭对应项。旧冻结草稿不追加新默认值。
发布后鉴权`GET source-articles/{id}`的`like_growth`可核对剩余额度、持续年度参数及一次性年度目标、已支付量和开始时间。年度额度按发布时间计时,累计增加该额度后停止,与真实点赞和持续年度随机增长独立。迁移、调度、字段范围与回退见[文章自动点赞维护](ARTICLE_LIKES.md);先执行指定新增迁移,再使用新代码。
### 2 图片
`POST /assets`使用multipart的`image`字段。支持PNG/JPEG/WebP/GIF,最多8MB,并限制宽高和像素数;不接收SVG/HTML上传,不主动抓取远端图片URL。返回SHA256、尺寸和`asset:SHA256`引用:
```markdown
![导入订阅操作图](asset:实际返回的64位SHA256)
```
原图先在私有存储,不生成公开地址。签名预览通过临时图片路由读取;正式发布时校验哈希并生成本站`/images/editorial/SHA256.ext`地址,正文替换为此路径。上传文件无同名覆盖问题;失败批次可能留下已准备但未引用的内容寻址图片,不自动删除可能仍被其他版本使用的素材。
### 3 发布确认过的版本
`POST /drafts/{draft_id}/publish`:
```json
{
"request_id": "0cf94c73-bafb-4a6c-9cef-39d72e5bc24e",
"expected_hash": "替换成保存草稿返回的64位hash"
}
```
发布UUID与草稿UUID分开。确认所看版本后使用它的hash;不要重建另一份正文来发布。发布前再次检查最新版本、现有文章基线、栏目/标签、图片、目标链接及受影响旧文的入向锚点。批次中任一篇冲突或失效,整批文章与版本/回执事务回滚。
成功后按稳定映射创建/更新文章,保留已有URL、创建时间、计数、置顶和身份。批次里的所有正文修改同时提交;冻结发布回执与内容写入在同一数据库事务中。重复提交同一发布UUID返回原回执;同UUID换目标/版本返回409。同一草稿换一个发布UUID重试也只返回已有回执,不重复创建文章。
响应包含:
- `status: published`:本次内容已写入本站公开文章表;这是所调用环境的状态,本地成功不代表生产成功。
- `articles`:各文章的`content_key`、`article_id`、`revision`和URL。
- `effects.cache/search/sitemap`:各后续处理的独立结果。搜索暂时失败不撤销已提交正文,返回`pending`,不能重发正文来弥补搜索失败。
后续处理为刷新本站响应缓存与热门数据缓存、同步本站搜索,以及使用数据库驱动的sitemap。缓存未刷新时sitemap也标记pending。不调用搜索引擎主动提交。
### 4 查询、重试与回退
| 方法与路径 | 作用 |
| --- | --- |
| `GET /catalog` | 当前可用编辑栏目与文章元信息,含已接管键和版本 |
| `GET /source-articles/{id}` | 接管旧文前读取实际HTML、封面、标签、栏目与管理状态 |
| `GET /articles/{key}` | 最新草稿版本、已发布版本、稳定ID/路径、已发布历史列表 |
| `GET /articles/{key}/versions/{revision}` | 读取某个已发布版本的冻结内容;旧文接管基线为0 |
| `GET /drafts/{id}` | 查询保存结果,刷新预览地址 |
| `GET /publications/{id}` | 查询发布UUID的原回执和当前后续处理状态 |
| `POST /publications/{id}/synchronize` | 只重试未完成的缓存/搜索处理,不重写文章;搜索读取当前公开版本,避免旧重试覆盖新索引 |
| `POST /articles/{key}/rollback` | 从指定历史版本建立新草稿,供预览后另行发布 |
回退请求同样包含新`request_id`,并给出当前`expected_revision`和历史`target_revision`。它不会马上覆盖当前文章;返回新草稿后,沿同一预览/发布步骤恢复,版本继续递增、URL不变。若旧版锚点不能满足现有入向链接,需要将关联文的修正一起组批,不能强制制造断链。
HTTP401认证失败,403签名无效/过期,404对象不存在,409幂等/版本/所有权冲突,422格式或链接不满足条件,503功能关闭或凭据绑定无效。网络结果未知时,先按原UUID查询,不盲目生成新UUID重发。
## 搜索隔离
`Articles::searchableAs()`优先使用`editorial.search_index`。本地为独立`fna_v3_articles`,已把169篇现有公开文章初始化到新索引,未改原继承的`articles`索引。正常站内搜索和新API写入使用同一索引。`editorial:reindex`用于初始化/补全;后续每次发布只更新本批当前文章。
本地仅允许回环Meilisearch和`fna_v3_`前缀。生产必须明确专属索引及服务地址,不能沿用未知共享索引。集合/数据库搜索直接读取当前文章;未支持或未配置的驱动返回pending。Meilisearch任务等待成功后才报告done。
## 安装、验收与恢复
追加迁移:`database/migrations/2026_10_27_190000_create_editorial_tables.php`。执行时只指定此迁移,不使用`migrate:fresh`或批量重置现有数据库。后续代码部署遵守Git发布流程;本次本地安装不授权生产部署。
```sh
php artisan migrate --path=database/migrations/2026_10_27_190000_create_editorial_tables.php --force
php artisan editorial:setup
php artisan editorial:reindex
```
本地已完成:
- SQLite内存隔离功能测试:认证/环境绑定、缓存隔离、草稿私有、签名到期、幂等、版本冲突、原子发布、显式接管、并行修改保护、入向锚点、图片和代码换行、回退与搜索重试;结果见`output/editorial-api/tests.xml`。
- 真实本地HTTP:无认证401、图片上传与草稿预览、两文章互链整批发布、同UUID重试、新版、回退、正式HTTP200、真实站内搜索和sitemap;见`output/editorial-api/http-verification.json`。
- 1360/390预览截图:签名图片、同批跳转、无点赞、noindex、无横向溢出;浏览器只允许本地来源,未发线上请求。见`output/editorial-api/browser-verification.json`和同目录PNG。
- 临时文章2篇及本次测试版本/回执/图片已清理,原169篇文章完整行哈希一致。安装验收结束时接收表为空,后续正常稿件会保留版本和回执;凭据留在私有存储。
恢复时先关闭`EDITORIAL_API_ENABLED`,保留5张接收表、版本和回执及图片。已发布正文仍在`articles`可正常显示;撤销某次文章更新优先走历史版本回退。代码回退不得顺带drop表或清理可能被正式文章引用的图片。若撤回本地搜索切换,应在核对原索引后调整`EDITORIAL_SEARCH_INDEX`与模型配置,不删除未知共享索引。