# 垂直视频查询 API 文档 本文档说明垂直视频查询 API 的调用协议、参数规则、数据库查询逻辑、去重与分页语义、错误处理、日志、部署和性能注意事项。 ## 1. 接口概览 | 项目 | 内容 | |---|---| | 业务接口 | `POST /api/v1/crawler/videos/query` | | 请求格式 | `application/json` | | 鉴权 | 接口自身不校验 Token;公网访问由 Nginx 控制 | | 时间标准 | 东八区(UTC+8) | | 默认时间范围 | 最近 3 天 | | 默认页大小 | 500 条 | | 最大页大小 | 由 `API_MAX_LIMIT` 控制,默认 500 条 | | 分页方式 | 基于去重结果最大 `id` 的游标分页 | | 去重方式 | 相同 `out_video_id` 保留最大 `id` 对应的记录 | | 数据库操作 | 只读,不修改数据库结构和数据 | 此外提供两个运维探针: | 路径 | 说明 | |---|---| | `GET /health` | 进程存活检查,不访问数据库 | | `GET /ready` | 就绪检查,执行 `SELECT 1 AS ok` 验证数据库连接 | 探针由 Nginx 示例限制为仅本机访问,不应暴露为公网业务接口。 ## 2. 访问控制与请求追踪 ### 2.1 公网访问控制 业务接口自身不校验 Token。公网部署时应在 Nginx 层限制新加坡调度服务器的固定出口 IP, 并启用请求速率和连接数限制;不要将后端 `8888` 端口直接暴露到公网。 ### 2.2 Request ID 调用方可以传入: ```http X-Request-ID: scheduler-task-123-page-1 ``` 允许字符为字母、数字、点、下划线和短横线,最长 128 个字符。未传或格式不正确时,API 自动生成 UUID。 响应头会返回同一个 `X-Request-ID`。本地日志和阿里云 SLS 也记录该值,可用于串联调度日志、API 日志和数据库异常。 ## 3. 请求参数 请求体为 JSON 对象。未定义字段会被拒绝,避免拼写错误被静默忽略。 | 参数 | 类型 | 必填 | 默认值 | 限制与说明 | |---|---|---:|---|---| | `platforms` | `string[]` | 否 | `xiaoniangao`、`xiaoniangaotuijianliu` | 只支持这两个平台;1~20 个;单项最长 50;自动去除首尾空格和重复值 | | `keywords` | `string[]` | 否 | `[]` | 最多 20 个;单项最长 200;按标题模糊匹配 | | `filter_match_mode` | `integer` | 否 | `2` | `1` 表示过滤条件 OR;`2` 表示过滤条件 AND | | `filters` | `object[]` | 否 | `[]` | 最多 50 个结构化过滤条件 | | `limit` | `integer` | 否 | `500` | 范围为 1~`API_MAX_LIMIT`;超出范围直接返回参数错误 | | `cursor` | `object` | 否 | `null` | 第一页不传;下一页原样传回响应的 `next_cursor` | ### 3.1 创建时间规则 创建时间和其他筛选字段一样放在 `filters` 中,`field` 固定为 `create_time`。时间值支持: - 13 位毫秒时间戳,例如 `1786982400000` - 10 位秒时间戳,例如 `1786982400` - 仅包含数字的时间戳字符串 - ISO 日期时间字符串,例如 `2026-08-18 00:00:00` 或 `2026-08-18T00:00:00` 时间戳统一按东八区转换为数据库查询时间。 单边或双边范围示例: ```json "filters": [ {"field": "create_time", "operator": ">=", "value": 1786896000000}, {"field": "create_time", "operator": "<", "value": 1786982400000} ] ``` 完全没有 `create_time` 条件时,API 默认查询当前时间之前3天。多条创建时间条件固定使用 AND,不受 `filter_match_mode` 影响。默认时间范围的首屏时间锚点会写入 `next_cursor.query_time`,下一页原样回传游标即可保持范围不漂移。 ### 3.2 关键词规则 关键词只匹配 `video_title`,每个关键词使用: ```sql video_title LIKE '%关键词%' ``` 多个关键词之间固定使用 OR。例如: ```json {"keywords": ["早", "养生"]} ``` 等价于: ```sql (video_title LIKE '%早%' OR video_title LIKE '%养生%') ``` 关键词条件与平台、创建时间、视频地址和 `filters` 之间使用 AND。 `%关键词%` 无法有效利用普通 B-Tree 索引,短关键词或高频词可能扫描较多数据。 ### 3.3 结构化过滤条件 每个过滤条件格式为: ```json { "field": "like_cnt", "operator": ">", "value": 3 } ``` 每个 `filters` 元素严格只允许以下三个 key: | key | 必填 | 说明 | |---|---:|---| | `field` | 是 | 白名单过滤字段 | | `operator` | 是 | 白名单比较操作符 | | `value` | 是 | 与操作符匹配的单值或数组 | 缺少任意 key 会返回“不能为空”;增加 `column`、`sql`、`name` 等任何未知 key 会返回 400。例如: ```text 参数校验失败: 不支持的参数: filters.0.column 参数校验失败: filters.0.operator不能为空 ``` 允许字段: | 字段 | 含义 | 值类型 | |---|---|---| | `play_cnt` | 播放数 | 数字 | | `like_cnt` | 点赞数 | 数字 | | `share_cnt` | 分享数 | 数字 | | `collection_cnt` | 收藏数 | 数字 | | `comment_cnt` | 评论数 | 数字 | | `duration` | 视频时长,单位秒 | 数字 | | `publish_time` | 视频发布时间 | 时间或时间戳 | | `create_time` | 数据创建时间 | 时间或时间戳 | 允许操作符: | 操作符 | `value` 要求 | 示例 | |---|---|---| | `>` | 单值 | `{"field":"like_cnt","operator":">","value":3}` | | `>=` | 单值 | `{"field":"play_cnt","operator":">=","value":1000}` | | `=` | 单值 | `{"field":"duration","operator":"=","value":60}` | | `<` | 单值 | `{"field":"duration","operator":"<","value":60}` | | `<=` | 单值 | `{"field":"comment_cnt","operator":"<=","value":100}` | | `between` | 恰好两个值的数组,包含边界 | `{"field":"duration","operator":"between","value":[60,300]}` | | `in` | 非空数组 | `{"field":"play_cnt","operator":"in","value":[100,500]}` | | `not_in` | 非空数组 | `{"field":"duration","operator":"not_in","value":[0,1]}` | 数字字段拒绝布尔值、`NaN`、正负无穷、SQL 片段和不能转换为有限数字的字符串。字段名和操作符均为白名单,所有值都通过 SQL 参数绑定,不直接拼接到 SQL。 `between` 的起始值不能大于结束值;`in`、`not_in` 以及其他数组型筛选值最多允许 100 项。过滤条件对象中的未知字段也会被拒绝。 `publish_time` 当前数据库类型为 `varchar(100)`,API 会把时间值格式化成 `YYYY-MM-DD HH:MM:SS` 后比较。数据库中的历史值需要保持相同且可排序的格式,否则筛选结果可能不准确。本接口不修改数据库字段类型。 ### 3.4 过滤条件组合 ```json "filter_match_mode": 2 ``` - `1`:除 `create_time` 外的过滤条件使用 OR - `2`:除 `create_time` 外的过滤条件使用 AND,默认值 例如: ```json { "filter_match_mode": 2, "filters": [ {"field": "like_cnt", "operator": ">", "value": 3}, {"field": "duration", "operator": "between", "value": [60, 300]} ] } ``` 对应: ```sql like_cnt > 3 AND duration BETWEEN 60 AND 300 ``` `filter_match_mode` 不改变关键词之间的 OR 规则。同一字段的多条条件始终使用 AND, 因此 `play_cnt >= 100` 和 `play_cnt <= 1000` 会组成播放量区间;不同字段之间才应用 `filter_match_mode`。多条 `create_time` 条件也始终使用 AND,并作为整个查询的时间范围。 ### 3.5 游标参数 第一页不传 `cursor`: ```json { "keywords": ["早"], "limit": 500 } ``` 如果响应 `has_more=true`,下一页原样传入: ```json { "keywords": ["早"], "limit": 500, "cursor": { "id": 6815000, "query_time": 1786982400000 } } ``` `cursor.id` 必须是大于 0 的整数。`query_time` 仅在使用默认近3天范围时返回。不要自行修改游标,也不要在分页过程中修改其他筛选参数。 ## 4. 完整请求示例 ```json { "platforms": ["xiaoniangao", "xiaoniangaotuijianliu"], "keywords": ["早", "养生"], "filter_match_mode": 2, "filters": [ {"field": "create_time", "operator": ">=", "value": 1786896000000}, {"field": "create_time", "operator": "<", "value": 1786982400000}, {"field": "play_cnt", "operator": ">=", "value": 1000}, {"field": "like_cnt", "operator": ">", "value": 3}, {"field": "duration", "operator": "between", "value": [60, 300]}, {"field": "publish_time", "operator": ">=", "value": 1785772800000} ], "limit": 500 } ``` ### 4.1 参数校验失败示例 不支持的平台: ```json { "platforms": ["douyin"] } ``` 返回: ```json { "code": 400, "msg": "参数校验失败: platforms: 不支持的平台: douyin", "data": [ { "type": "value_error", "loc": ["platforms"] } ] } ``` 不支持的顶层参数: ```json { "page_index": 2 } ``` 返回消息包含: ```text 参数校验失败: 不支持的参数: page_index ``` 不支持的过滤字段或操作符会指出完整路径,例如: ```text 参数校验失败: filters.0.field不支持值 unknown_field 参数校验失败: filters.0.operator不支持值 contains ``` ## 5. 数据库查询逻辑 查询依次应用以下规则: 1. `platform IN (...)` 2. 应用显式的 `create_time` 条件;未提供时限定为最近3天 3. `video_url <> ''` 4. 有关键词时,执行标题模糊匹配 5. 有结构化过滤条件时,按 `filter_match_mode` 组合 6. 在完整筛选范围内按 `out_video_id` 去重 7. 在去重结果上应用 `id` 游标 8. 多取 1 条判断是否还有下一页 9. 通过主键回表读取完整视频字段 核心 SQL 形态: ```sql SELECT cv.<视频字段> FROM crawler_video AS cv INNER JOIN ( SELECT MAX(source.id) AS selected_id FROM crawler_video AS source WHERE <平台、时间、视频地址、关键词、过滤条件> GROUP BY CASE WHEN source.out_video_id = '' THEN source.id ELSE 0 END, source.out_video_id HAVING MAX(source.id) < :cursor_id ORDER BY selected_id DESC LIMIT :page_size_plus_one ) AS dedup ON dedup.selected_id = cv.id ORDER BY cv.id DESC; ``` 第一页没有 `HAVING` 条件。 ### 5.1 去重语义 - 相同非空 `out_video_id` 只保留 `id` 最大的一条,即最后入库记录。 - `out_video_id=''` 的记录按各自 `id` 分组,因此不会全部合并成一条。 - 去重发生在分页之前,所以重复记录不占用返回页容量,也不会跨页再次出现。 - 当前语义是“最大 `id`/最后入库”,不是严格比较 `publish_time`。 ### 5.2 分页语义 - 排序方式为 `id DESC`。 - 下一页通过 `HAVING MAX(id) < cursor.id` 应用于分组后的结果。 - 不使用 OFFSET,因此翻到后续页面时不会产生越来越大的跳过成本。 - 固定时间范围后,新插入且 `id` 大于当前游标的数据不会插入到本轮分页中间。 ## 6. 响应格式 ### 6.1 成功响应 ```json { "code": 0, "msg": "", "data": { "data": [ { "video_id": 10001, "user_id": 20001, "out_user_id": "external-user-id", "platform": "xiaoniangao", "strategy": "recommend", "out_video_id": "external-video-id", "video_title": "早上好", "cover_url": "https://example.com/cover.jpg", "video_url": "https://example.com/video.mp4", "duration": 60, "publish_time": "2026-08-18 08:00:00", "play_cnt": 1000, "like_cnt": 20, "share_cnt": 2, "collection_cnt": 3, "comment_cnt": 5, "width": 1080, "height": 1920, "id": 6815000, "create_time": "2026-08-18 08:10:00" } ], "count": 1, "has_more": true, "next_cursor": {"id": 6815000, "query_time": 1786982400000} } } ``` 字段说明: | 字段 | 说明 | |---|---| | `code` | 业务码;成功固定为 0 | | `msg` | 成功时为空字符串 | | `data.data` | 当前页视频数组 | | `data.count` | 当前页实际返回数量,不包含额外探测行 | | `data.has_more` | 是否存在下一页 | | `data.next_cursor` | 下一页游标;没有下一页时为 `null` | 响应头包含: ```http X-Request-ID: <本次请求ID> Cache-Control: no-store ``` ## 7. 错误响应 错误响应统一为: ```json { "code": 422, "msg": "like_cnt筛选值必须是数字: invalid", "data": null } ``` | HTTP 状态 | 场景 | 调用方处理建议 | |---:|---|---| | 400 | JSON 无效、字段类型错误、未知参数、时间范围错误 | 修正请求,不重试 | | 401 | Token 缺失或错误 | 修正鉴权,不重试 | | 422 | 结构化过滤值不符合业务要求 | 修正参数,不重试 | | 500 | 数据库查询异常或内部异常 | 记录 `X-Request-ID`,排查日志;谨慎重试 | | 503 | 查询并发已满,或 `/ready` 检查失败 | 指数退避后重试 | | 504 | 数据库查询超过 `API_QUERY_TIMEOUT` | 缩小时间范围/关键词或稍后重试 | 调度端当前只自动重试网络异常以及 HTTP 429、502、503、504,最多 3 次;退避约为 1 秒、2 秒、4 秒并附加随机抖动。400、401、422 不会重试。 ## 8. 并发、超时与资源保护 默认配置: | 配置 | 默认值 | 说明 | |---|---:|---| | `API_HOST` | `127.0.0.1` | 只监听本机,由 Nginx 对外代理 | | `API_PORT` | `8888` | API 本地监听端口 | | `DB_POOL_SIZE` | `12` | 单进程数据库连接池上限 | | `DB_POOL_RECYCLE` | `3600` | 数据库连接回收秒数 | | `API_MAX_CONCURRENT_REQUESTS` | `8` | 查询并发配置上限 | | `API_QUEUE_TIMEOUT` | `2.0` | 等待查询并发槽位的最长秒数 | | `API_QUERY_TIMEOUT` | `30` | 单条查询最长秒数 | | `API_MAX_LIMIT` | `500` | 单页最大返回条数 | | `API_LOG_QUEUE_SIZE` | `1000` | SLS 异步日志队列长度 | | `API_LOG_FLUSH_TIMEOUT` | `5.0` | 单批 SLS 上报及关闭刷新超时 | 实际查询并发取以下两者最小值: ```text min(API_MAX_CONCURRENT_REQUESTS, DB_POOL_SIZE) ``` 这样可以避免大量查询进入数据库连接池内部无界等待。超过并发槽位且 2 秒内未获得执行机会时返回 503。 如果使用多个 API 进程,总数据库连接数约为: ```text 进程数 × DB_POOL_SIZE ``` 扩容进程前必须确认数据库最大连接数和实际负载。 ## 9. 日志与监控 ### 9.1 本地日志 本地访问日志记录: - `request_id` - 请求方法和 URL - 脱敏后的请求参数 - HTTP 状态码 - 请求总耗时 - 失败时的错误原因和脱敏堆栈 不记录响应视频内容,避免日志量过大。 ### 9.2 阿里云 SLS 请求完成后只写入有界内存队列,由后台任务批量上报,业务响应不等待 SLS。 后台策略: - 每批最多 50 条 - 上报失败最多重试 3 次 - 队列满时写本地错误日志并丢弃当前 SLS 事件 - 服务关闭时尝试刷新队列,超时后取消日志任务并优雅关闭数据库连接池 主要字段: - `TraceId` / `request_id` - `path`、`method` - `status_code`、`success` - `request_duration_ms` - `query_duration_ms` - `query_result_count` - `query_success` - `failure_stage` - `error_type` - `message` - 脱敏后的 `request_params` API日志全部使用SLS顶层字段,不再写入重复的 `data` 字符串。字典和数组字段使用紧凑JSON字符串, 布尔值统一为小写 `true`/`false`;值为空的 `failure_stage`、`error_type` 等字段不会写入。 `account=None` 和自定义 `timestamp` 也不会出现在API日志中,时间以SLS自带的 `__time__` 为准。 这样可以直接统计每日请求量、成功率、P95/P99耗时和错误分布。 API访问日志使用独立的SLS目的地,默认配置为: ```dotenv API_ALIYUN_LOG_PROJECT=crawler-log-prod API_ALIYUN_LOGSTORE=crawler-api-access API_ALIYUN_LOG_ENDPOINT=cn-hangzhou.log.aliyuncs.com ``` 部署前需要在阿里云日志服务中创建 `crawler-api-access` Logstore,并为上面的核心指标建立索引。 原有爬虫日志继续写入 `crawler-fetch`,两者不会混在一起。如果需要使用其他Project或Logstore, 只修改环境变量并重启API即可。 ## 10. curl 示例 以下示例假设: ```bash export CHUI_ZHI_API_URL="https://api.example.com/api/v1/crawler/videos/query" ``` ### 10.1 默认最近 3 天 ```bash curl --request POST "$CHUI_ZHI_API_URL" \ --header "Content-Type: application/json" \ --header "X-Request-ID: manual-test-001" \ --data '{}' ``` ### 10.2 关键词模糊搜索 ```bash curl --request POST "$CHUI_ZHI_API_URL" \ --header "Content-Type: application/json" \ --data '{ "keywords": ["早"], "limit": 500 }' ``` ### 10.3 指定时间及数值条件 ```bash curl --request POST "$CHUI_ZHI_API_URL" \ --header "Content-Type: application/json" \ --data '{ "filter_match_mode": 2, "filters": [ {"field": "create_time", "operator": ">=", "value": 1786896000000}, {"field": "create_time", "operator": "<", "value": 1786982400000}, {"field": "like_cnt", "operator": ">", "value": 3}, {"field": "duration", "operator": "between", "value": [60, 300]} ], "limit": 200 }' ``` ### 10.4 下一页 ```bash curl --request POST "$CHUI_ZHI_API_URL" \ --header "Content-Type: application/json" \ --data '{ "keywords": ["早"], "limit": 500, "cursor": {"id": 6815000, "query_time": 1786982400000} }' ``` 后续页必须继续使用第一页的关键词、过滤条件和 `limit`,并原样传递响应中的 `next_cursor`。 ### 10.5 健康检查 在 API 所在服务器执行: ```bash curl --fail http://127.0.0.1:8888/health curl --fail http://127.0.0.1:8888/ready ``` ## 11. 启动与公网部署 ### 11.1 使用现有 deploy.sh 一起部署(推荐) 现有 `deploy.sh` 已同时管理主爬虫和垂直视频 API。在服务器的 `/root/AutoScraperX/.env` 中增加: ```dotenv API_HOST=127.0.0.1 API_PORT=8888 API_ALIYUN_LOG_PROJECT=crawler-log-prod API_ALIYUN_LOGSTORE=crawler-api-access API_ALIYUN_LOG_ENDPOINT=cn-hangzhou.log.aliyuncs.com ``` 执行完整部署: ```bash cd /root/AutoScraperX bash deploy.sh ``` 部署脚本按以下顺序执行: 1. 拉取 `master` 最新代码。 2. 创建或更新 `/root/AutoScraperX/venv`。 3. 安装 `requirements.txt`。 4. 在停止旧服务前检查 API 配置。 5. 根据 PID 文件优雅停止旧 API 和主爬虫;首次升级时兼容清理旧版无 PID 进程。 6. 启动 `python -m api.app`(Uvicorn单Worker)。 7. 最多等待 15 秒调用 `/ready`,确认进程和数据库都可用。 8. API 就绪后启动 `main.py`。 9. 主进程启动检查失败时,同时停止本次启动的 API,避免留下半部署状态。 部署产物: | 文件 | 用途 | |---|---| | `logs/autoscraperx_deploy.log` | 部署过程日志 | | `logs/api_startup.log` | API 标准输出和启动异常 | | `logs/main_startup.log` | 主爬虫标准输出和启动异常 | | `run/api.pid` | API 进程 PID | | `run/main.pid` | 主爬虫进程 PID | 部署完成后验证: ```bash curl --fail http://127.0.0.1:8888/health curl --fail http://127.0.0.1:8888/ready cat /root/AutoScraperX/run/api.pid cat /root/AutoScraperX/run/main.pid tail -n 100 /root/AutoScraperX/logs/autoscraperx_deploy.log ``` 如果服务器目录或虚拟环境路径不同,可以覆盖: ```bash APP_DIR=/data/AutoScraperX VENV_DIR=/data/AutoScraperX/venv bash deploy.sh ``` ### 11.2 单独启动 API(仅用于本地调试) ```bash cd /root/AutoScraperX ./run_api.sh prod ``` `run_api.sh` 优先使用 `venv/bin/python`,不存在时使用 `.venv/bin/python`。 默认监听: ```text 127.0.0.1:8888 ``` 启动窗口会打印所有已注册的路径和处理方法。 ### 11.3 Nginx(首次部署配置一次) 示例文件: ```text deploy/nginx/chui_zhi_api.conf.example ``` 部署要点: - API 保持监听 `127.0.0.1:8888` - Nginx 对外监听 HTTPS 443 - 只允许业务路径使用 POST - 请求体最大 1 MB - 开启 JSON gzip - 配置请求速率和单 IP 连接限制 - 透传 `X-Request-ID`、真实 IP 和协议 - `/health`、`/ready` 只允许本机访问 - 如果新加坡调度服务器有固定出口 IP,建议启用 IP 白名单 安装示例: ```bash cp deploy/nginx/chui_zhi_api.conf.example /etc/nginx/conf.d/chui_zhi_api.conf # 编辑域名、证书路径和可选IP白名单 nginx -t systemctl reload nginx ``` `deploy.sh` 不会自动覆盖 Nginx 配置、域名和证书。Nginx 首次配置完成后,后续运行 `deploy.sh` 只需重启其后端 API。 ### 11.4 systemd(可选替代方案) 示例文件: ```text deploy/systemd/autoscraperx-api.service.example ``` systemd 配置了进程异常自动重启、SIGTERM 优雅退出和文件句柄上限。它是 `deploy.sh + nohup` 的替代方案,同一 API 不要同时由两套方式管理。 ## 12. 调度端调用约定 调度端配置: ```bash export CHUI_ZHI_API_URL="https://api.example.com/api/v1/crawler/videos/query" ``` 调度流程: 1. 把抓取计划转换为 API 的结构化参数。 2. 请求第一页。 3. 上传并保存当前页视频。 4. `has_more=true` 时原样传递 `next_cursor` 请求下一页。 5. `has_more=false` 时结束。 调度端复用 `requests.Session`,保持 TCP 连接;每一页处理完成后才请求下一页,避免一次性把全部视频加载到内存。 调度端仍会在入库前检查该计划是否已经存在对应内容,保证任务重试时尽量避免重复上传和保存。 ## 13. 当前性能参考 2026-08-18 使用当前实现实测: - 平台:默认两个平台 - 时间:最近 3 天 - 关键词:`早` - 页大小:500,SQL 实际多取 1 条 - 三次纯 SQL 耗时:约 4.03 秒、4.22 秒、4.18 秒 - 平均纯 SQL:约 4.14 秒 - 首次数据库连接:约 0.15 秒 该结果只代表当时数据库数据量、网络和负载,不是固定 SLA。短关键词 `%早%` 命中范围较大,通常比长关键词更慢。 建议持续通过 SLS 的 `query_duration_ms` 观察: - 平均查询耗时 - P95/P99 查询耗时 - 超时数量 - 503 并发拒绝数量 - 各关键词和时间范围的结果量 如果查询长期接近 30 秒,应优先缩小时间范围或页大小,并结合生产数据执行 `EXPLAIN`。数据库结构优化需单独评估和审批,本接口不会自动执行建索引或字段变更。 ## 14. 代码位置 | 功能 | 文件 | |---|---| | 稳定ASGI启动入口 | `api/app.py` | | FastAPI应用、请求上下文、校验错误兜底和生命周期 | `api/fastapi_app.py` | | API参数基类、注册、执行、响应、异常及数据库查询保护 | `api/base.py` | | 本地访问日志、SLS上报、脱敏及日志队列 | `api/reporting.py` | | 业务Router集中注册 | `api/routes.py` | | API公共异常 | `api/errors.py` | | 垂直视频入参、SQL、查询、分页及Router | `api/chui_zhi/videos.py` | | API 和数据库配置 | `config/base.py` | | 异步 MySQL 连接池 | `core/base/async_mysql_client.py` | | 阿里云 SLS 批量日志 | `core/utils/log/aliyun_log.py` | | 启动脚本 | `run_api.sh` | | Nginx 示例 | `deploy/nginx/chui_zhi_api.conf.example` | | systemd 示例 | `deploy/systemd/autoscraperx-api.service.example` | | API 测试 | `test/test_video_query_api.py` | ## 15. 新增接口约定 每个接口模块只管理请求模型和业务实现。请求模型继承 `ApiParams`,接口继承 `BaseApi`; 子类只需声明必填的 `path` 并实现 `logic`,直接返回业务数据。请求方法默认是 `POST`,路由名称和说明自动生成。 路由注册、`code/msg/data` 响应、业务异常、请求ID、本地访问日志和阿里云SLS上报均由基类处理。 数据库列表查询统一使用基类的 `fetch_all()`,并发限制、查询超时、耗时和结果数量也会自动处理。 参数在进入子类前校验失败、404/405 和健康检查由全局中间件兜底,使用同一套响应和上报逻辑,且不会重复上报。 ```python from fastapi import APIRouter, Request from api.base import ApiParams, BaseApi router = APIRouter(prefix='/api/v1/example', tags=['示例']) class ExampleParams(ApiParams): value: str class ExampleApi(BaseApi): """执行示例功能。""" path = '/run' async def logic(self, params: ExampleParams, request: Request): return {'value': params.value} ExampleApi().register(router) ``` 最后只需在 `api/routes.py` 中 `include_router`。子类不要捕获公共异常、不要重复封装 `code/msg/data`,也不要自行记录或上报访问日志。