Files
rssKeeper/docs/API.md
T
congsh 4286731348 feat: 代理支持、外部API增强、调度器修复、每日文章看板
- 添加 HTTP 代理支持(国内直连、外网走代理)
- 外部 API 新增全文搜索、源健康度/错误筛选、未读筛选
- 修复 APScheduler 线程静默崩溃(_safe_fetch 异常保护)
- 健康检查暴露调度器状态
- Dashboard 新增每日文章数柱状图(按 published_at)
- 文章列表 API 补上 content 字段,日期筛选修复时间范围
- 修复外部 API 双重 external 前缀
- User-Agent 改为 Chrome 标识缓解 403
- 添加完整 API 接口文档

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 09:58:32 +08:00

751 lines
15 KiB
Markdown
Raw 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.
# RSSKeeper API 接口文档
Base URL: `http://<host>:7329`
所有接口返回 JSON 格式数据。
---
## 目录
- [健康检查](#健康检查)
- [RSS 源管理](#rss-源管理)
- [获取源列表](#获取源列表)
- [获取源详情](#获取源详情)
- [添加源](#添加源)
- [更新源](#更新源)
- [删除源](#删除源)
- [触发抓取](#触发抓取)
- [批量抓取](#批量抓取)
- [自动发现](#自动发现)
- [获取分类列表](#获取分类列表)
- [导入 OPML](#导入-opml)
- [导出 OPML](#导出-opml)
- [文章管理](#文章管理)
- [获取文章列表](#获取文章列表)
- [获取文章详情](#获取文章详情)
- [全文搜索](#全文搜索)
- [标记已读](#标记已读)
- [仪表盘](#仪表盘)
- [统计概览](#统计概览)
- [健康度详情](#健康度详情)
- [最近活动](#最近活动)
- [外部 API(供 AI 集成)](#外部-api)
- [获取最近文章](#获取最近文章)
- [全文搜索](#全文搜索external)
- [获取源列表(含筛选)](#获取源列表含筛选)
- [获取指定源文章](#获取指定源文章)
- [获取每日摘要](#获取每日摘要)
---
## 通用字段说明
### 健康度 (health_status)
| 值 | 含义 |
|---|------|
| `healthy` | 健康:成功率 >= 90%,7天内有抓取 |
| `warning` | 警告:成功率 50%-90%,或超过3天未更新 |
| `unhealthy` | 异常:成功率 < 50%,或超过7天未更新 |
| `unknown` | 未知:尚未进行过任何抓取 |
### 错误类型 (error_type)
| 值 | 含义 |
|---|------|
| `url_invalid` | URL 已失效(404 |
| `forbidden` | 被站点拒绝(403 |
| `rate_limited` | 频率限制(429 |
| `timeout` | 连接超时 |
| `dns_failure` | DNS 解析失败 |
| `connection_refused` | 连接被拒绝 |
| `connection_reset` | 连接中断 |
| `ssl_error` | SSL/TLS 错误 |
| `unreachable` | 服务器不可达 |
| `url_malformed` | URL 格式错误 |
| `server_error` | 服务器错误(5xx |
| `unknown` | 其他未知错误 |
---
## 健康检查
### `GET /api/health`
检查服务是否运行。
**响应:**
```json
{
"status": "ok",
"service": "rssKeeper"
}
```
---
## RSS 源管理
### 获取源列表
### `GET /api/feeds`
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `skip` | int | 否 | 0 | 跳过条数(分页偏移) |
| `limit` | int | 否 | 100 | 每页条数 |
| `category` | string | 否 | - | 按分类筛选 |
| `search` | string | 否 | - | 按名称/URL/描述搜索 |
| `is_active` | bool | 否 | - | 按启用状态筛选 |
| `health_status` | string | 否 | - | 按健康度筛选:`healthy`/`warning`/`unhealthy`/`unknown` |
**响应:**
```json
{
"total": 383,
"items": [
{
"id": 1,
"url": "https://example.com/feed.xml",
"title": "Example Feed",
"description": "Feed description",
"category": "科技",
"is_active": true,
"fetch_interval_minutes": 60,
"last_fetch_at": "2026-06-11T08:33:36.474905",
"last_fetch_status": "success",
"last_error": "",
"error_type": "",
"success_count": 5,
"fail_count": 0,
"article_count": 42,
"health_status": "healthy",
"next_fetch_time": "2026-06-11T09:33:36.000000+00:00",
"created_at": "2026-06-11T08:33:24.591074"
}
]
}
```
---
### 获取源详情
### `GET /api/feeds/{feed_id}`
**路径参数:**
| 参数 | 类型 | 说明 |
|------|------|------|
| `feed_id` | int | RSS 源 ID |
**响应:** 同列表中的单条 items 结构。
---
### 添加源
### `POST /api/feeds`
**请求体:**
```json
{
"url": "https://example.com/feed.xml",
"title": "Example",
"description": "",
"category": "科技",
"is_active": true,
"fetch_interval_minutes": 60
}
```
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `url` | string | **是** | - | RSS 源地址 |
| `title` | string | 否 | "" | 源名称(留空则自动抓取) |
| `description` | string | 否 | "" | 描述 |
| `category` | string | 否 | "" | 分类 |
| `is_active` | bool | 否 | true | 是否启用 |
| `fetch_interval_minutes` | int | 否 | 60 | 抓取间隔(分钟),最小 15 |
**响应:**
```json
{
"id": 1,
"message": "RSS 源添加成功,正在后台抓取",
"url": "https://example.com/feed.xml"
}
```
添加成功后会自动在后台触发首次抓取。
**错误:** `409` — 该 RSS 源已存在
---
### 更新源
### `PUT /api/feeds/{feed_id}`
**请求体(只需传要修改的字段):**
```json
{
"title": "新名称",
"category": "新闻",
"is_active": false,
"fetch_interval_minutes": 120
}
```
**响应:**
```json
{
"message": "RSS 源更新成功"
}
```
---
### 删除源
### `DELETE /api/feeds/{feed_id}`
删除 RSS 源,**级联删除**关联的所有文章和抓取日志。
**响应:**
```json
{
"message": "RSS 源已删除"
}
```
---
### 触发抓取
### `POST /api/feeds/{feed_id}/fetch`
手动触发单个源的抓取。同步执行,返回抓取结果。
**响应:**
```json
{
"success": true,
"articles_count": 5,
"feed_title": "Example Feed"
}
```
---
### 批量抓取
### `POST /api/feeds/batch-fetch`
并发同步抓取多个源。适用于"全部抓取"等场景。
**请求体:**
```json
{
"feed_ids": [1, 2, 3, 4, 5]
}
```
**响应:**
```json
{
"message": "完成:4 个成功,1 个失败",
"total": 5,
"success": 4,
"fail": 1
}
```
---
### 自动发现
### `POST /api/feeds/discover`
从任意网页自动发现 RSS/Atom feed URL。
**参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `url` | string | **是** | 网页地址 |
**响应:**
```json
{
"source_url": "https://example.com",
"found_feeds": [
"https://example.com/feed.xml",
"https://example.com/rss"
]
}
```
---
### 获取分类列表
### `GET /api/feeds/categories`
返回所有已使用的分类。
**响应:**
```json
["科技", "新闻", "设计"]
```
---
### 导入 OPML
### `POST /api/feeds/import-opml`
从 OPML 内容批量导入 RSS 源。
**请求体:**
```json
{
"opml_content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><opml version=\"2.0\">..."
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `opml_content` | string | **是** | OPML 文件内容(最大 5MB |
**响应:**
```json
{
"added": 15,
"skipped": 3,
"message": "成功导入 15 个 RSS 源"
}
```
---
### 导出 OPML
### `GET /api/feeds/export-opml`
导出所有 RSS 源为 OPML 格式。
**响应:**
```json
{
"opml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><opml version=\"2.0\">..."
}
```
---
## 文章管理
### 获取文章列表
### `GET /api/articles`
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `skip` | int | 否 | 0 | 分页偏移 |
| `limit` | int | 否 | 50 | 每页条数 |
| `feed_id` | int | 否 | - | 按源筛选 |
| `category` | string | 否 | - | 按分类筛选 |
| `search` | string | 否 | - | 按标题/链接搜索 |
| `since` | string | 否 | - | 起始时间(ISO 格式) |
| `until` | string | 否 | - | 截止时间(ISO 格式) |
| `is_read` | bool | 否 | - | 按已读状态筛选 |
**响应:**
```json
{
"total": 120,
"items": [
{
"id": 1,
"feed_id": 1,
"title": "文章标题",
"link": "https://example.com/article",
"author": "作者",
"published_at": "2026-06-11T06:00:00",
"content": "文章正文内容...",
"summary": "文章摘要...",
"is_read": false,
"created_at": "2026-06-11T08:33:36.474905",
"feed_title": "Example Feed",
"category": "科技"
}
]
}
```
---
### 获取文章详情
### `GET /api/articles/{article_id}`
**响应:** 同列表中的单条 items 结构。
---
### 全文搜索
### `GET /api/articles/search/fulltext`
使用 SQLite FTS5 进行全文搜索。
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `q` | string | **是** | - | 搜索关键词 |
| `skip` | int | 否 | 0 | 分页偏移 |
| `limit` | int | 否 | 50 | 每页条数 |
**响应:** 同文章列表格式。
---
### 标记已读
### `PUT /api/articles/{article_id}/read`
**响应:**
```json
{
"message": "已标记为已读"
}
```
---
## 仪表盘
### 统计概览
### `GET /api/dashboard/stats`
**响应:**
```json
{
"total_feeds": 383,
"active_feeds": 383,
"total_articles": 1024,
"healthy_feeds": 202,
"warning_feeds": 0,
"unhealthy_feeds": 167,
"today_fetches": 45,
"today_success": 40,
"today_success_rate": 88.9
}
```
---
### 健康度详情
### `GET /api/dashboard/health`
获取每个 RSS 源的健康状态详情。
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `skip` | int | 否 | 0 | 分页偏移 |
| `limit` | int | 否 | 100 | 每页条数 |
**响应:**
```json
{
"total": 383,
"items": [
{
"id": 1,
"title": "Example Feed",
"url": "https://example.com/feed.xml",
"is_active": true,
"health_status": "healthy",
"health_label": "健康",
"success_rate": 100.0,
"success_count": 5,
"fail_count": 0,
"total_fetches": 5,
"last_fetch_at": "2026-06-11T08:33:36.474905",
"days_since_fetch": 0,
"article_count": 42,
"last_error": "",
"recent_logs": [
{
"status": "success",
"articles_fetched": 3,
"response_time_ms": 450,
"created_at": "2026-06-11T08:33:36.474905",
"error_message": null
}
]
}
]
}
```
---
### 最近活动
### `GET /api/dashboard/recent-activity`
获取最近的抓取活动日志。
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `limit` | int | 否 | 20 | 返回条数 |
**响应:**
```json
{
"items": [
{
"id": 1,
"feed_id": 1,
"feed_title": "Example Feed",
"status": "success",
"articles_fetched": 3,
"error_message": "",
"response_time_ms": 450,
"created_at": "2026-06-11T08:33:36.474905"
}
]
}
```
---
## 外部 API
供 AI 助手、外部系统调用的接口。前缀:`/api/v1/external`
### 获取最近文章
### `GET /api/v1/external/recent`
获取最近 N 小时的文章,支持多条件组合筛选。
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `hours` | int | 否 | 24 | 回溯小时数 |
| `limit` | int | 否 | 50 | 最大返回条数 |
| `feed_id` | int | 否 | - | 按源 ID 筛选 |
| `category` | string | 否 | - | 按分类筛选 |
| `search` | string | 否 | - | 按标题/摘要关键词筛选 |
| `unread_only` | bool | 否 | false | 只返回未读文章 |
**响应:**
```json
{
"query": { "hours": 24, "limit": 50, "feed_id": null, "category": null, "search": null, "unread_only": false },
"count": 15,
"articles": [
{
"id": 1,
"title": "文章标题",
"link": "https://example.com/article",
"author": "作者",
"summary": "摘要文本",
"content": "正文内容(超过10000字符时返回摘要)",
"published_at": "2026-06-11T06:00:00",
"created_at": "2026-06-11T08:33:36",
"feed_title": "Example Feed",
"category": "科技"
}
]
}
```
---
### 全文搜索
### `GET /api/v1/external/search`
使用 FTS5 全文搜索引擎检索文章内容。**供 AI 按关键词精准查找文章**。
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `q` | string | **是** | - | 搜索关键词 |
| `limit` | int | 否 | 50 | 最大返回条数(1-200 |
| `offset` | int | 否 | 0 | 分页偏移 |
| `category` | string | 否 | - | 按分类二次筛选 |
| `feed_id` | int | 否 | - | 按源 ID 二次筛选 |
**响应:**
```json
{
"query": "LLM",
"total": 12,
"offset": 0,
"limit": 50,
"articles": [
{
"id": 15674,
"title": "文章标题",
"summary": "匹配的摘要...",
"link": "https://example.com/article",
"published_at": "2026-06-11T06:00:00",
"created_at": "2026-06-11T08:33:36",
"feed_id": 1,
"feed_title": "Example Feed",
"category": "科技"
}
]
}
```
---
### 获取源列表(含筛选)
### `GET /api/v1/external/feeds`
获取 RSS 源列表,支持按健康度、错误类型、分类等多维度筛选。
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `health_status` | string | 否 | - | 按健康度筛选:`healthy`/`warning`/`unhealthy`/`unknown` |
| `category` | string | 否 | - | 按分类筛选 |
| `error_type` | string | 否 | - | 按错误类型筛选(见通用字段说明) |
| `is_active` | bool | 否 | true | 按启用状态筛选 |
**响应:**
```json
{
"count": 167,
"feeds": [
{
"id": 1,
"title": "Example Feed",
"url": "https://example.com/feed.xml",
"category": "科技",
"is_active": true,
"health_status": "unhealthy",
"error_type": "timeout",
"article_count": 42,
"last_fetch_at": "2026-06-11T08:33:36",
"last_error": "HTTPSConnectionPool..."
}
]
}
```
**示例:**
- 查看所有异常源:`/api/v1/external/feeds?health_status=unhealthy`
- 查看 URL 失效的源:`/api/v1/external/feeds?error_type=url_invalid`
- 查看指定分类:`/api/v1/external/feeds?category=科技`
---
### 获取指定源文章
### `GET /api/v1/external/feeds/{feed_id}/articles`
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `feed_id` | int | **是** | - | RSS 源 ID(路径参数) |
| `limit` | int | 否 | 100 | 最大返回条数 |
| `since` | string | 否 | - | 起始时间过滤 |
| `search` | string | 否 | - | 按标题/摘要关键词筛选 |
| `unread_only` | bool | 否 | false | 只返回未读文章 |
**响应:**
```json
{
"feed": { "id": 1, "title": "Example Feed", "url": "https://example.com/feed.xml" },
"count": 42,
"articles": [
{
"id": 1,
"title": "文章标题",
"link": "https://example.com/article",
"author": "作者",
"summary": "摘要",
"published_at": "2026-06-11T06:00:00"
}
]
}
```
---
### 获取每日摘要
### `GET /api/v1/external/summary`
获取指定日期的文章摘要,按分类分组。
**参数:**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `date` | string | 否 | 今天 | 日期,格式 `YYYY-MM-DD` |
| `category` | string | 否 | - | 按分类筛选 |
**响应:**
```json
{
"date": "2026-06-11",
"total_articles": 35,
"by_category": {
"科技": [
{ "title": "文章标题", "link": "https://...", "feed": "Feed 名称", "summary": "文章摘要..." }
],
"新闻": [...]
}
}
```