OScraper 定位为一个独立的媒体目录刮削工作台:用户可以配置 OpenList 连接,或把本地目录挂载到 /media,再创建受控根目录。应用按需浏览或扫描该目录,识别电影、电视剧和动画,预览 TMDB 匹配及文件变更计划,确认后异步完成可选重命名、NFO/图片生成和元数据写入。
应用外壳参考 Seshat:
- 后端:Go + Gin + GORM
- 前端:React + TypeScript + Vite + Appica UI + Tailwind CSS
- 状态:TanStack Query 管理服务端数据,Zustand 管理认证会话
- 数据库:仅支持 SQLite,面向单实例部署
- 鉴权:JWT Bearer Token、
token_version主动失效、首次管理员初始化 - 日志:接口日志和结构化业务日志分离,异步写入独立日志数据库
- 部署:前端、Go API 和 Nginx 打包为单一 Docker 镜像
- UI:中英文、深浅色主题、自适应侧边栏、无障碍基础能力
刮削领域行为移植自 ostrm 的手动刮削链路,但用 Go 重新实现并保留原有测试语义,不直接复制 Spring、MyBatis、Quartz 或 Java 服务结构。
开发状态(2026-08-20):首版闭环已经实现,包括鉴权与日志、OpenList/本地受控目录、只读扫描、TMDB 匹配、完整电影/剧集预览、分集元数据、存储写入、持久化作业、检查点重试和管理 UI。真实服务灰度属于部署验收,执行步骤见
docs/operations.md。
- 管理 OpenList 连接:地址、Token、账号根路径、QPS/QPM、连接测试。
- 管理刮削目标:存储来源(OpenList/本地)、受控根目录、媒体类型和重命名权限;本地根目录必须位于
/media。 - 按需加载目录树;大型目录首次只读取当前层。
- 扫描目标目录并形成媒体候选:
- 电影:第一层电影目录;根目录平铺视频按 TMDB ID 或“标题+年份”保守归组,同一电影的多个清晰度/剪辑版本形成一个候选。
- 电视剧/动画:第一层剧集根目录;其下递归读取季和媒体文件。
- 本地正则解析文件名;低置信度时可选调用 OpenAI 兼容接口辅助识别。
- TMDB 搜索、按年份优选、按 TMDB ID 精确指定,以及电影/剧集详情预览。
- 只读预览:匹配信息、海报、背景图、目录创建、目录/文件重命名、元数据上传清单、冲突和风险提示。
- 确认后异步执行:可选整理目录和文件、生成 NFO、下载图片、写入 OpenList 或本地目录。
- 作业进度、历史、失败原因、操作级检查点和失败续跑。
- API 日志、业务日志、用户与系统设置管理。
- STRM 文件生成及孤立 STRM 清理。
- Emby/Jellyfin 媒体库刷新。
- ostrm 的定时转换任务、Quartz、数据上报和通知系统。
- 自动删除 OpenList 源文件。
- 未经预览确认的批量重命名。
- 剧集逐集 TMDB 元数据和逐集 NFO;首版只生成剧集根级
tvshow.nfo、海报和背景图。
- 目标目录定时扫描。
- 对已确认规则的候选批量刮削。
- WebSocket/SSE 实时进度;首版扫描使用 1 秒轮询、写作业使用 2 秒轮询。
- 通知渠道和媒体服务器刷新。
- 更多元数据源及刮削 Provider 插件。
预览绝不修改 OpenList。执行接口只接受服务端生成的 preview_id,不信任客户端回传的重命名计划。预览记录包含目录指纹和过期时间;执行前再次读取目录指纹,内容变化则返回 409 preview.stale,要求重新预览。
OpenList 的目录创建、移动、重命名和上传不是一个数据库事务。应用为每一步写操作保存操作记录,并使用幂等检查实现续跑:
- 源不存在、目标已存在:视为该步已成功。
- 源和目标都存在:停止并报告冲突。
- 源和目标都不存在:停止并要求人工检查。
- 仅大小写变化的重命名:先改为确定性的临时名,再改为目标名。
- 重命名和移动永不覆盖;元数据上传只覆盖预览中明确列出的元数据文件。
不自动进行高风险的“回滚重命名”;失败后保留检查点并从安全阶段续跑。
所有路径必须经过统一标准化,并满足:
- 绝对路径,以
/开头;清理重复斜杠和尾斜杠。 - 不允许
.、..、NUL、控制字符或反斜杠逃逸。 - 浏览、预览和执行路径必须是配置的 OpenList
base_path与刮削目标root_path的真实子路径,而不是字符串前缀命中。 - 新名称不能包含
/、\\、.、..,并执行长度和控制字符校验。 - OpenList
File-Path请求头按 UTF-8 百分号编码,空格编码为%20。
本地路径还必须满足:只允许 /media 的真实子路径;逐层使用 Lstat 阻止符号链接逃逸;媒体重命名使用不覆盖目标的原子操作;元数据先写同目录临时文件并 fsync 后替换。跨文件系统移动不会自动退化为复制后删除。
从 ostrm 迁移以下行为及其测试用例:目录树加载、路径约束、媒体解析、TMDB 匹配、重命名规划、冲突检查、元数据生成、OpenList 写操作、作业检查点和续跑。Java 框架代码不进入新仓库。
flowchart LR
U["Browser"] --> N["Nginx :3113"]
N -->|"/"| F["React SPA"]
N -->|"/api/*"| A["Gin API :8080"]
A --> AU["Auth / RBAC"]
A --> OS["OpenList source service"]
A --> SC["Catalog application service"]
A --> JL["Job application service"]
A --> LG["Logging service"]
SC --> OP["Parser and rename planner"]
SC --> LS["Local storage /media"]
SC --> TP["TMDB provider"]
SC -. optional .-> AI["OpenAI-compatible recognizer"]
SC --> SW["Persistent scan queue"]
JL --> W["Job executor / bounded queue"]
W --> OS
W --> LS
W --> NF["NFO and image generator"]
A --> DB[("Business DB")]
JL --> DB
LG --> LDB[("Log SQLite DB")]
W --> WS[("/data/work/jobs")]
OS --> OL["OpenList API"]
首版为单进程、单副本应用。internal/app 是组合根,统一创建 HTTP 路由、扫描运行时、Job 队列和数据清理任务,并按顺序关闭。路由层只负责协议注册,不执行初始化副作用。扫描和写作业使用彼此隔离的持久化有界队列;HTTP 创建扫描后返回 202,前端轮询状态。因为业务库、作业工作区和日志库位于本地挂载盘,首版不承诺多副本部署。
| 层级 | 选型 | 设计理由 |
|---|---|---|
| 前端 | React 19、TypeScript、Vite | 与 Seshat 一致,组件和工程约定可直接参考 |
| UI | Appica UI、Tailwind CSS 4 | 复用 Seshat 的面板、表单、弹窗、Toast、主题和响应式布局 |
| 路由 | React Router | 页面懒加载和认证守卫 |
| 服务端状态 | TanStack Query | 目录、预览、作业和日志查询缓存及轮询 |
| 会话状态 | Zustand | Token 与当前用户,保持职责单一 |
| 后端 | Go、Gin | 与 Seshat 一致,部署产物小,适合 I/O 密集型刮削作业 |
| ORM | GORM | 统一模型、事务和版本化迁移 |
| 数据库 | SQLite | 单机开箱即用,符合应用的单实例定位 |
| API 文档 | Swaggo | 从 Handler 注释生成 OpenAPI |
| 认证 | JWT HS256 + BCrypt | 沿用 Seshat 模型,并用数据库版本使旧 Token 即时失效 |
| 作业 | 数据库作业表 + 有界 Go Worker | 无需引入 Redis,满足单容器恢复和限流需求 |
| 国际化 | i18next/react-i18next | 英文默认,浏览器语言自动选择,手动偏好持久化 |
| 部署 | 多阶段 Docker + Nginx | 单镜像交付,amd64/arm64 均可构建 |
Go module 使用 oscraper,前端包名使用 oscraper-web。
backend/
├── main.go
├── config/
│ └── config.go
├── internal/
│ ├── auth/ # 密码、JWT、token_version、首次管理员
│ ├── handler/ # HTTP 参数绑定与响应,不放领域逻辑
│ ├── middleware/ # request id、JWT、管理员、访问日志、CORS
│ ├── model/ # GORM 模型
│ ├── repository/ # 数据访问
│ ├── service/
│ │ ├── connection/ # OpenList 连接管理
│ │ ├── target/ # 刮削目标管理
│ │ ├── catalog/ # 目录树和媒体候选
│ │ ├── preview/ # 识别、匹配、预览快照
│ │ ├── scrape/ # 执行编排
│ │ ├── job/ # 作业状态机、恢复、Worker
│ │ └── setting/ # TMDB、AI、日志等设置
│ ├── openlist/
│ │ ├── client.go # /api/me 与 /api/fs/*
│ │ ├── limiter.go # 每连接 QPS/QPM 双令牌桶
│ │ └── types.go
│ ├── media/
│ │ ├── parser.go
│ │ ├── season_parser.go
│ │ ├── tmdb_id.go
│ │ ├── planner.go
│ │ └── naming.go
│ ├── provider/
│ │ ├── tmdb/
│ │ └── ai/
│ ├── metadata/
│ │ ├── nfo.go
│ │ └── image.go
│ └── logging/
├── pkg/
│ ├── database/
│ ├── response/
│ └── cryptoutil/
└── docs/ # 生成的 Swagger
领域包不能直接引用 Gin。Handler 中的 HTTP DTO 负责 JSON/校验并转换为不带传输标签的应用命令,Service 负责用例和业务规则,OpenList/TMDB/AI 包仅处理外部协议。Job 的提交/重试/取消由 JobService 编排,具体存储变更和产物处理由 JobExecutor 执行。
| 表 | 关键字段 | 说明 |
|---|---|---|
users |
username、password_hash、is_admin、requires_admin_setup、token_version | 参考 Seshat |
openlist_connections |
name、base_url、encrypted_token、username、base_path、qps、qpm、enabled | Token 只加密存储,不通过列表接口返回 |
scrape_targets |
source_type、connection_id、name、root_path、library_type、rename_enabled、enabled | 来源为 openlist/local;本地目标不需要 connection_id |
scan_runs |
target_id、status、started_at、completed_at、summary | 一次只读目录扫描 |
media_candidates |
scan_id、path、kind、fingerprint、parsed_title、year、status | 电影目录、平铺电影或剧集根目录 |
scrape_previews |
candidate_id、tmdb_id、media_type、fingerprint、match_json、plan_json、expires_at | 服务端执行凭据 |
scrape_jobs |
target_id、preview_id、status、stage、progress、message、error_code、checkpoint、actor_id | 持久化作业状态 |
scrape_job_operations |
job_id、sequence、type、source_path、target_path、status、attempts、last_error | 每个 OpenList 写操作的幂等检查点 |
system_settings |
key、value、is_secret | TMDB、AI、保留期等全局配置 |
admin_audit_logs |
actor_id、action、target、detail、occurred_at | 高风险管理操作,不能由日志清理功能删除 |
首个开发版本可用 GORM AutoMigrate 起步;首个公开版本前固定 schema,并改为嵌入式、带版本号的 SQL migration,防止后续自动迁移产生不可控变更。
stateDiagram-v2
[*] --> PENDING
PENDING --> RUNNING
RUNNING --> SUCCEEDED
RUNNING --> FAILED
PENDING --> CANCELED
FAILED --> PENDING: retry from checkpoint
RUNNING --> FAILED: process stopped / lease expired
执行阶段:
PREPARING -> RENAMING -> GENERATING -> UPLOADING -> VERIFYING -> COMPLETED
PREPARING:校验预览、目录指纹、目标范围和 TMDB 快照。RENAMING:按操作表顺序执行目录创建、目录重命名、文件移动和文件重命名。GENERATING:在持久化工作区生成 NFO 并下载图片。UPLOADING:将预览列出的元数据文件上传到最终目录。VERIFYING:重新读取最终目录,确认名称和上传文件存在。COMPLETED:记录结果并清理工作区。
应用启动时把遗留的 RUNNING 作业标记为可重试 FAILED;用户可从检查点续跑。后续可升级为带租约的自动恢复。
| 用途 | 方法 | OpenList 接口 | 关键行为 |
|---|---|---|---|
| 测试连接 | GET | /api/me |
校验 Token、账号状态,读取 username/base_path |
| 列目录 | POST | /api/fs/list |
page=1、per_page=0、按需 refresh |
| 校验路径 | POST | /api/fs/get |
确认目标存在且为目录 |
| 重命名 | POST | /api/fs/rename |
overwrite=false |
| 建目录 | POST | /api/fs/mkdir |
先检查是否已存在 |
| 移动 | POST | /api/fs/move |
同源目录批量 names,不覆盖目标 |
| 上传 | PUT | /api/fs/put |
File-Path 编码、As-Task=false、仅元数据允许覆盖 |
所有文件系统接口共享该 OpenList 连接的双层令牌桶。QPS 控制突发,QPM 控制持续总量;任一值为 0 表示对应维度不限流。不同连接的额度相互隔离。
HTTP 客户端统一配置连接、响应头和总超时,限制错误响应体大小;只接受 HTTP/HTTPS。因为 OpenList 常部署在内网,允许 RFC1918 和 loopback 目标,但阻止链路本地、组播、未指定地址及云元数据地址,并对跳转后的地址再次校验。
- 从目录路径中提取
{tmdbid-N};存在时直接加载 TMDB 详情。 - 按目标媒体类型运行本地解析器,提取标题、年份、季、集和置信度。
- 本地置信度低于 70 且启用 AI 时,调用 OpenAI 兼容接口;要求 JSON Schema 结构化结果,并再次本地校验。
- 用户可在预览页修改标题、年份,或直接输入 TMDB ID。
- 搜索结果优先精确年份,否则选评分最高的有效结果;必须展示给用户确认。
AI 不能猜测输入中不存在的年份或季集;失败、超时、结构不合法时回退到本地解析,不阻断手动修正。
标准目录和媒体基础名:
标题 (年份) {tmdbid-ID}
示例:
后室 (2026) {tmdbid-1083381}/
├── 后室 (2026) {tmdbid-1083381}.mkv
├── 后室 (2026) {tmdbid-1083381}.nfo
├── 后室 (2026) {tmdbid-1083381}-poster.jpg
└── 后室 (2026) {tmdbid-1083381}-backdrop.jpg
电影目录包含多个正片时,规划器为每个视频提取或接收可编辑的唯一版本标签,并生成 标准电影名 - 版本标签.ext。字幕和外部音轨跟随所属视频版本;版本专属旧 NFO 保留原名,统一生成目录级 movie.nfo。预告片、花絮和样片不计入版本,分段电影不与多版本模式混用,单部电影最多支持 8 个版本。
根目录平铺电影按 TMDB ID 优先、否则按“标准化标题+年份”形成候选;缺少年份且没有 TMDB ID 时仍按单文件处理。开启整理后,同组视频及其唯一关联伴随文件移动到同一个标准电影目录。无法自动得到唯一版本标签的候选保持阻断,用户可在预览中编辑标签后重新生成计划。
剧名 (年份) {tmdbid-ID}/
├── tvshow.nfo
├── poster.jpg
├── fanart.jpg
└── Season 01/
├── 剧名 - S01E01.mkv
└── 剧名 - S01E01.zh-CN.ass
- 季目录识别支持
第4季、第四季、Season 4、独立S04、Specials和特别篇。 - 一个目录出现不同季号时视为歧义,不自动改名。
- 根目录平铺剧集只有在文件名包含明确季集标记时才创建季目录并成组移动伴随文件。
tvshow.nfo、poster.jpg、fanart.jpg等剧集共享文件保留在剧集根目录。- 动画复用 TV 的 TMDB 类型,并额外支持绝对集数;没有季目录时默认 Season 01,但必须在预览中显式提示。
以下情况预览标红且禁止执行:
- 多个源目录或文件映射到同一目标。
- 目标目录或目标媒体文件已经存在。
- 源路径已离开目标根目录。
- 所选路径是 TV/Anime 目标根目录而不是剧集根目录。
- 电影目标根目录同时包含多部电影却试图作为一个候选执行。
- 季目录或季集解析存在歧义。
- 全局 Worker 默认 2 个,可通过环境变量调整为 1–4。
- 同一刮削目标最多一个执行作业;同一候选最多一个活动作业。
- 同一 OpenList 连接的写阶段串行化,读和 TMDB 请求可以并行。
- 作业队列有固定容量,满时返回
429 job.queue_full,不无限堆积内存。 POST execute支持Idempotency-Key;同一用户、预览和 Key 返回同一个作业。- 每完成一个写操作立即提交数据库检查点。
- 工作区位于
/data/work/jobs/<job_id>,以便容器重启后保留生成文件;成功后删除,失败作业按保留期清理。 - 执行期间编辑或删除连接/目标被禁止;只能先禁用新作业,待活动作业结束。
统一响应:
{
"code": 0,
"message": "success",
"data": {}
}错误同时返回稳定的 error_code,前端按当前语言翻译;服务端原始消息只作为诊断回退。
| 方法 | 路径 | 权限 | 用途 |
|---|---|---|---|
| POST | /api/auth/login |
公开 | 登录 |
| POST | /api/auth/logout |
登录 | 使同版本 Token 失效 |
| POST | /api/auth/setup-admin |
引导管理员 | 首次设置正式凭据 |
| GET | /api/user/profile |
登录 | 当前用户 |
| PUT | /api/user/password |
登录 | 改密并递增 token_version |
| GET/PUT | /api/admin/users/* |
管理员 | 用户和角色管理,后续启用注册时使用 |
空数据库创建受限的 admin/admin 引导账户;引导账户只能完成管理员初始化、查看个人信息和退出。生产模式要求 JWT_SECRET 至少 32 位且不能使用默认值。公开注册默认关闭。
| 方法 | 路径 | 用途 |
|---|---|---|
| GET/POST | /api/openlist-connections |
列表/创建 |
| GET/PUT/DELETE | /api/openlist-connections/:id |
详情/编辑/删除 |
| POST | /api/openlist-connections/test |
保存前测试 |
| POST | /api/openlist-connections/:id/test |
测试已保存配置 |
| POST | /api/openlist-connections/:id/rotate-token |
更新 Token |
列表和详情只返回 has_token 与掩码,不返回明文 Token。
| 方法 | 路径 | 用途 |
|---|---|---|
| GET/POST | /api/scrape-targets |
列表/创建目标 |
| GET/PUT/DELETE | /api/scrape-targets/:id |
详情/编辑/删除 |
| GET | /api/scrape-targets/:id/tree |
读取根层目录 |
| GET | /api/scrape-targets/:id/tree/children?path= |
按需读取下一层 |
| POST | /api/scrape-targets/:id/scans |
创建只读扫描 |
| GET | /api/scrape-targets/:id/scans/:scanId |
扫描状态与摘要 |
| GET | /api/scrape-targets/:id/candidates |
候选列表、筛选和分页 |
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /api/scrape-targets/:id/previews |
自动识别并预览 |
| POST | /api/scrape-targets/:id/previews/search |
用标题/年份重新匹配 |
| POST | /api/scrape-targets/:id/previews/tmdb |
用 TMDB ID 精确匹配 |
| POST | /api/scrape-targets/:id/jobs |
使用 preview_id 提交作业 |
| GET | /api/scrape-jobs |
作业历史 |
| GET | /api/scrape-jobs/:id |
状态、阶段、进度和结果 |
| GET | /api/scrape-jobs/:id/operations |
操作级明细 |
| POST | /api/scrape-jobs/:id/retry |
从检查点续跑 |
| POST | /api/scrape-jobs/:id/cancel |
仅取消尚未执行的作业 |
执行请求示例:
{
"preview_id": 123,
"rename_media": true,
"confirm_directory_fingerprint": "sha256:..."
}| 方法 | 路径 | 用途 |
|---|---|---|
| GET/PUT | /api/settings/scraping |
TMDB、语言、图片尺寸、超时和代理 |
| POST | /api/settings/scraping/test-tmdb |
测试 TMDB |
| GET/PUT | /api/settings/ai |
可选 AI 配置 |
| POST | /api/settings/ai/test |
测试 AI |
| GET | /api/admin/logs |
API 请求日志 |
| GET | /api/admin/application-logs |
业务日志 |
| GET | /api/admin/audit-logs |
高风险操作审计 |
- 管理员:连接、目标、系统凭据、用户、日志和所有刮削操作。
- 普通用户:只读浏览、预览和查看作业;是否允许提交不带重命名的刮削由系统开关控制。
- 重命名、Token 轮换、目标删除、日志清理始终要求管理员。
- 只接受
Authorization: Bearer <token>,不使用 Cookie。 - Token 存在 LocalStorage,前端请求设置
credentials: omit。 - Token 有效期 24 小时,包含 user_id、username、is_admin、token_version 和 exp。
- 每次认证从数据库读取 token_version 和最新角色;退出、改密、角色变化时递增版本。
- 使用严格 CSP、禁止内联脚本、构建产物不注入运行时 Secret,降低 LocalStorage Token 的 XSS 风险。
- OpenList Token、TMDB API Key、AI API Key 使用 AES-256-GCM 加密后保存。
CREDENTIAL_ENCRYPTION_KEY由部署环境提供,和JWT_SECRET分开;生产模式缺失或长度不合格时拒绝启动。- API 只允许覆盖或轮换 Secret,不提供读取明文接口。
- 日志对 authorization、cookie、token、secret、password、signature、api-key 等字段递归脱敏。
沿用 Seshat 的双日志设计:
记录 request_id、时间、方法、路由模板、状态码、耗时、请求/响应字节、客户端、用户、connection_id、target_id、job_id 和稳定错误码。默认不保存普通请求体;仅在明确的调试场景保存大小受限且已脱敏的摘要。
统一方法:
logging.Info("scrape", "job stage completed", logging.Fields{
"request_id": requestID,
"job_id": jobID,
"target_id": targetID,
"stage": "UPLOADING",
})稳定 source 使用 server、auth、openlist、catalog、preview、scrape、job、tmdb、ai。绝不记录 Token、API Key、完整 Authorization 或带签名的下载 URL。
以下操作写入不可从日志页面清除的审计表:连接创建/编辑/删除、Token 轮换、开启重命名、作业提交/重试、系统凭据更新、用户角色变化、日志清理。
接口日志和业务日志固定写入 /cache/logs/app/api-logs.db,使用 WAL、异步有界队列和批量写入;队列满时增加 dropped 计数,不阻塞刮削主流程。默认保留 7 天,可配置 1–30 天。
概览
刮削
├── OpenList 连接
├── 刮削目标
├── 媒体工作台
└── 作业记录
管理
├── 系统设置
├── API 日志
├── 业务日志
└── 审计日志
个人资料
- 登录/管理员初始化:复用 Seshat 流程。
- 概览:连接健康度、目标数、待复核候选、运行中/失败作业、最近活动。
- OpenList 连接:卡片或表格、测试连接、Token 轮换、QPS/QPM。
- 刮削目标:根目录、媒体类型、重命名权限、最近扫描和快捷操作。
- 媒体工作台:
- 左栏:懒加载目录树/候选列表。
- 中栏:本地识别与 TMDB 搜索结果。
- 右栏:海报、背景、媒体详情、变更计划和风险。
- 底部固定操作区:重新匹配、仅写元数据、整理并写元数据。
- 作业记录:状态筛选、阶段进度、操作明细、失败续跑。
- 系统设置:TMDB、AI、代理、日志保留期、普通用户权限。
- 日志页:沿用 Seshat 的数据表、筛选、详情和导出交互。
- 沿用 Seshat 的 240px 桌面侧边栏、移动端抽屉、顶部栏和用户区。
- 品牌主色使用 emerald,风险写操作使用 amber/red,不用颜色作为唯一状态信息。
- 沿用半透明面板、1.25rem 圆角、轻阴影和背景网格,但工作台主体减少装饰,优先信息密度。
- 支持系统/亮色/暗色主题。
- 所有用户可见文本、aria-label、title、空状态和错误提示均从中英文 i18n 资源读取。
- 日期、数字和持续时间按当前 locale 格式化;英文为默认语言,手动选择优先于浏览器语言并持久化。
- 破坏性确认弹窗必须展示 OpenList 连接、目标目录、重命名数量和将覆盖的元数据文件数量。
flowchart LR
B["Browse/select"] --> R["Recognize"]
R -->|"no match"| M["Manual title/year/TMDB ID"]
M --> R
R -->|"matched"| P["Preview plan"]
P -->|"conflict"| B
P --> C["Confirm"]
C --> J["Job progress"]
J -->|"failed"| X["Inspect and retry"]
J -->|"success"| D["Verify final directory"]
| 容器目录 | 用途 |
|---|---|
/data/db/openlist-scraper.db |
SQLite 业务库 |
/data/work/jobs |
可恢复作业工作区 |
/media |
宿主机本地媒体目录,只允许本地目标访问 |
/cache/logs/app/api-logs.db |
API/业务日志库 |
/cache/logs/nginx |
Nginx 日志 |
/cache/tmp |
可丢弃临时缓存 |
| 变量 | 默认值 | 说明 |
|---|---|---|
SQLITE_PATH |
/data/db/openlist-scraper.db |
SQLite 文件 |
LOCAL_MEDIA_ROOT |
/media |
本地媒体安全根目录,容器部署不建议修改 |
JWT_SECRET |
无生产默认值 | 至少 32 位 |
CREDENTIAL_ENCRYPTION_KEY |
无 | 32-byte key 的 base64 表示 |
TZ |
UTC |
应用时区 |
PUID |
1000 |
容器内应用运行用户 ID,必须大于 0 |
PGID |
1000 |
容器内应用运行组 ID,必须大于 0 |
MEDIA_GID |
空 | 可选的媒体目录附加组 ID,用于媒体共享组与主组不同的 NAS/宿主机 |
UMASK |
002 |
应用新建文件和目录的权限掩码 |
SCRAPE_WORKERS |
2 |
1–4 |
SCRAPE_QUEUE_SIZE |
100 |
有界作业队列 |
API_LOG_QUEUE_SIZE |
5000 |
日志队列 |
API_LOG_BATCH_SIZE |
100 |
日志批次 |
外部 Web 端口建议使用 3113,避免与 Seshat 默认的 3112 冲突;Go API 固定监听容器内 8080。入口进程以 root 完成用户映射及 /data、/cache 初始化后,Nginx 和 Go 服务均以 PUID:PGID 运行;入口进程不修改 /media 的所有权。
收到 SIGTERM 后:停止接收新作业,HTTP 最多等待 10 秒,Worker 保存当前操作检查点,日志队列刷新,最后退出。OpenList 的单个进行中请求允许完成,但不启动下一写操作。
| ostrm 源 | OScraper 目标 | 策略 |
|---|---|---|
OpenlistApiService |
internal/openlist/client.go |
重写协议层,保留官方端点、编码、错误和限流测试 |
OpenlistApiRateLimiter |
internal/openlist/limiter.go |
迁移双令牌桶行为 |
ManualScrapingService |
service/preview + service/scrape + media/planner |
拆分 1900+ 行巨型服务 |
ManualScrapingJobService |
service/job |
迁移持久化状态、单目标互斥和失败续跑 |
TaskMediaParser |
internal/media/parser.go |
先移植测试向量,再实现 |
SeasonDirectoryNameParser |
internal/media/season_parser.go |
保留中文数字、Specials、歧义规则 |
TmdbIdExtractor |
internal/media/tmdb_id.go |
原样迁移语义 |
TmdbApiService |
internal/provider/tmdb |
重写 HTTP、超时、代理、重试和响应模型 |
AiFileNameRecognitionService |
internal/provider/ai |
可选能力,严格结构化输出 |
NfoGeneratorService |
internal/metadata/nfo.go |
保留 XML 转义和 Kodi 兼容字段 |
CoverImageService |
internal/metadata/image.go |
流式下载、大小和内容类型限制 |
ManualScrapingServiceTest |
Go 表驱动测试 | 端口行为测试,不逐行翻译实现 |
不迁移 TaskExecutionService 的 STRM 流程、Handler Chain、Quartz、媒体服务器、通知和 Java/Spring 基础设施。
- 路径标准化和真实子路径判断。
- 电影/TV/动画解析、中文季数、Specials、绝对集数。
- 平铺电影和伴随文件归组。
- 平铺剧集创建季目录和伴随文件迁移。
- 目标冲突、大小写重命名、歧义季目录。
- TMDB 按年份和评分选择、TMDB ID 直查。
- NFO XML 转义和文件命名。
- JWT、token_version、引导管理员和权限。
- Secret 加密、错误字段和日志递归脱敏。
- 双层令牌桶及不同连接隔离。
- 使用
httptest.Server模拟 OpenList 和 TMDB。 - 校验
/api/fs/list|rename|mkdir|move|put的方法、请求头、请求体和错误映射。 - 预览后目录变化必须返回 409。
- 作业在每个操作点中断后都能幂等续跑。
- 同一目标第二个作业被拒绝,不同目标可并发。
- 连接和目标删除受活动作业保护。
- 使用临时 SQLite 数据库运行核心 repository 测试。
- i18n 中英文 key、插值变量和语言优先级一致。
- JWT 启动恢复、401 清会话、管理员守卫。
- 目录树按需加载,不误选前缀相似路径。
- 无匹配、手动 TMDB ID、预览过期、冲突和失败续跑交互。
- 破坏性确认弹窗完整展示影响范围。
- 作业轮询在完成/失败后停止。
- 添加局域网 OpenList,测试成功后 Token 不在任何列表或日志中出现。
- 电影根目录包含同一 TMDB ID 或相同标题年份的多个平铺版本时合并为一个候选;无可靠身份的信息仍保持独立。
- 电视剧从
Show/S1/raw.S01E01.mkv预览为标准根目录、Season 01 和标准文件名。 - 执行前外部新增同名目标,作业停止且不覆盖。
- 上传两张图片后进程重启,重试只继续剩余步骤。
- TMDB/AI 不可用时仍可用 TMDB ID 完成预览;AI 失败不会阻断本地解析。
- 英文、中文、亮色、暗色和移动端布局均可完成主流程。
- 初始化 Go/React 工程、Docker、Compose、Nginx、CI。
- 建立统一响应、配置、数据库、i18n、主题和测试基座。
- 把 Seshat 可复用的通用实现迁入并改名,不复制 Webhook/通知业务。
完成标准:前后端可启动,健康检查、Swagger、双语登录页和容器构建通过。
- 引导管理员、JWT、token_version、角色守卫。
- API/业务/审计日志与后台查询。
- Secret 加密和 TMDB/AI 基础设置。
完成标准:安全基线和日志脱敏测试通过。
- 连接 CRUD、测试、Token 轮换、限流。
- 目标 CRUD、路径校验、懒加载目录树。
- 移植 OpenList client/rate limiter 测试。
完成标准:可安全浏览指定根目录,不能越界。
- 移植解析器、季目录、TMDB ID 和重命名规划测试。
- TMDB Provider、可选 AI Provider。
- 候选扫描、预览快照、目录指纹和 UI 工作台。
完成标准:电影、TV、动画和平铺电影都能形成准确、只读的变更预览。
实施状态:已完成。预览会强制刷新 OpenList 清单并校验扫描指纹,展开电影、季目录、剧集与伴随文件重命名,检查目标占用和重复映射,并把 NFO XML 与可用图片源固化到 24 小时预览快照。
- 作业状态机、Worker、操作表、工作区。
- OpenList mkdir/move/rename/put。
- NFO/图片生成、上传、验证、失败续跑。
完成标准:故障注入覆盖每个阶段,重试不重复或覆盖媒体文件。
实施状态:已完成。作业与操作检查点持久化保存;提交支持幂等键和有界队列;同一连接写入串行化并复用 QPS/QPM 限流;执行前复核指纹,逐步检查源/目标状态,生成并流式下载受限图片,上传后验证最终路径。进程中断的作业启动时转为可重试失败状态,已成功操作不会重复执行。
- 概览、作业历史、筛选、日志导出、完整中英文。
- ARM64/AMD64 镜像、升级迁移、备份恢复文档。
- 性能和安全测试、真实 OpenList 小规模灰度。
完成标准:全部验收场景通过,再考虑批量和定时能力。
实施状态:代码和自动化交付项已完成。概览、作业筛选/进度/明细、失败重试、待执行取消、三类日志搜索与 CSV 导出、中英文界面、版本化迁移、保留期清理、备份恢复文档和多架构镜像工作流均已提供。真实 OpenList 灰度必须由部署者在具备实际连接和 TMDB 凭据的环境按运维清单执行,不在单元测试中模拟为“已通过”。
- 用户能在浏览器中完成“连接 OpenList → 选择受控根目录 → 选中媒体 → 校正 TMDB → 查看完整预览 → 确认执行 → 查看进度/结果”的闭环。
- 所有源媒体改名/移动均经过明确预览,冲突时不覆盖。
- 应用重启后失败作业能从持久化检查点安全续跑。
- OpenList Token、TMDB Key、AI Key 不通过 API 或日志泄露。
- 中英文、深浅色和桌面/移动端均可用。
- 核心 ostrm 手动刮削行为由 Go 测试覆盖,且不引入 STRM/Quartz/Spring 依赖。