Files
rust-imager/docs/pipeline-architecture.md
lychang 36969a4fa0 update
2026-07-19 23:56:28 +08:00

7.9 KiB
Raw Blame History

流云图库 — 元数据管线架构文档

版本:v2.0 · 更新:2026-07-19


总览

流云图库是一个 Rust 编写的图片管理应用,核心功能是从相册标题中自动提取人物名称和分类标签,并建立人物-相册关联关系。所有提取和关联逻辑由一套三阶段的处理管线(Pipeline) 驱动。

相册标题(如 "水淼Aqua cosplay Zani – Wuthering Waves 69 photos")
    │
    ▼
  ┌─ Pipeline ──────────────────────────────────────┐
  │  Phase 1  全量解析    提取人名 + 提取标签       │
  │  Phase 2  人物合并    按中文核心合并重复人物     │
  │  Phase 3  缓存刷新    重新加载内存缓存           │
  └──────────────────────────────────────────────────┘
    │
    ├──→ albums 表(person_name_original, tags 等)
    ├──→ persons 表(人物主表)
    ├──→ person_aliases 表(别名关联)
    └──→ album_tags 表(标签关联)

启动流程与触发时机

应用启动后,管线任务按顺序串行执行三个阶段:

启动管线(单次,后台任务 A)
    │
    ├── ① 扫描导入新相册(import)
    ├── ② 全量解析 + 人物合并(run_pipeline)
    └── ③ 刷新 AlbumsCache
  • 启动管线:import → run_pipeline → refreshCache,三件事顺序串行,不并发
  • 定时管线:每 30 分钟触发一次,执行 run_pipeline → refreshCache(不重复 import)
  • API 触发:POST /api/pipeline/run 可手动触发 run_pipeline

Phase 1:全量解析

入口函数: phase_parse_all(db) → (person_updated, tag_updated)

对数据库中每一个相册独立执行以下流程:

for each album:
    │
    ├── jieba 分词(词性标注)
    │   ├──→ 提取推荐人名(词性 nr/nrfg/nrt/nz)
    │   └──→ 提取推荐标签(词性 n/nz/ns/eng/x)
    │
    ├── 确定最佳人名
    │   ├── 有人物库匹配?→ 用库匹配结果
    │   └── 否则 → 用 jieba 推荐
    │
    ├── 写入 DB(强制覆盖,不比对旧值)
    │
    ├── 提取标签
    │   ├── 三步提取:分类词库 → jieba → 正则兜底
    │   ├── unique_tags() 去重+排序+坏标签过滤
    │   └── 写入 DB(强制覆盖,不比对旧值)
    │
    └── 下一个相册

人名提取策略

按优先级依次尝试:

  1. 人物库精确匹配 — person_merger::match_title(),在已有 persons 表中查询标题子串
  2. jieba 词性标注 — extract_person_from_tags(),优先取 nr(人名)类词
  3. 标题模式匹配 — extract_person_name_from_title(),尝试 cosplay 格式、分隔符切分、正则扫描

人名处理规则

  • 禁止中英混杂:人名主体为纯中文或纯英文(中文优先级更高)
  • 中英文混合(如"水淼Aqua")由 split_person_name_with_aliases 拆分为:主名="水淼" + 别名="Aqua"
  • 别名保存到 person_aliases 表,用于后续搜索匹配

标签分类体系

分类 标识 示例词
风格 style 日系、韩系、复古、甜美、sexy
服装 outfit 比基尼、旗袍、制服、丝袜
场景 scene 室内、户外、街拍、浴室、beach
身体 body 美腿、翘臀、玉足、美背
情绪 mood 清纯、诱惑、唯美、冷艳
主题 topic 写真、私房、旅拍、cosplay
名词 noun jieba 提取的普通名词
专名 proper jieba 提取的专有名词
地名 location jieba 提取的地名
其他 other 正则兜底提取的剩余关键词

标签过滤链(从外到内共 5 层)

1. is_punctuation_only()  →  检测不含任何字/字母/数字,则跳过
2. 停用词表过滤           →  photos、cosplay、美女、模特 等 50+ 词
3. 人名排除               →  排除标签提取时确定的人名
4. 词性排除               →  形容词(a)、动词(v)、名动词(vn)、机构(nt) 等不产出标签
5. unique_tags() 最终过滤  →  调用 is_bad_tag() 二次确认

强制覆盖写入

每次管线执行都是完全重新解析,提取到人名就写入覆盖、提取到标签就写入覆盖。不做"新旧好坏"比对:

  • 人名:jieba/fuzzy 提取到 → 直接写入 albums.person_name_*,创建/更新 persons 记录及别名关联
  • 标签:经过 5 层过滤链后仍有结果 → 直接写入 albums.tags,重建 album_tags 表

这样每次启动或定时触发时,标签和人名都会用最新的解析逻辑重新计算并强制覆盖,解析逻辑改进后老数据自动受益。


Phase 2:人物合并

入口函数: phase_merge_persons(db) → merged_count

按中文核心自动合并重复人物:

1. 扫描所有 persons,提取其中文核心(开头的连续中文字符)
2. 将中文核心相同的 person 分为一组
3. 每组选相册数最多的作为目标(dst)
4. 其余合并到目标:
   - 移动所有相册关联
   - 移动所有别名
   - 源人物原名也作为目标别名
   - 删除源人物
5. 更新目标的统计计数

示例:杨晨晨Yome 和 杨晨晨Sugar 的中文核心都是 "杨晨晨" → 合并为一个人物


核心数据表结构

albums 表(关键字段)

字段 类型 说明
id TEXT UUID
title TEXT 相册标题,来源是文件夹名或 index.json
person_name_original TEXT 提取的原始人名(可能带英文后缀)
person_name_cleaned TEXT 清洗后的人名(仅中文主名)
tags TEXT JSON 数组,如 ["比基尼","室内","清纯"]
all_words TEXT JSON 数组,如 [["比基尼","outfit"],["室内","scene"]]
processed_at TEXT 最后处理时间

persons 表

字段 类型 说明
id INTEGER 自增主键
name TEXT 人物名(通常是中文主名)
display_name TEXT 显示名
total_albums INTEGER 关联相册数
total_images INTEGER 关联图片数

person_aliases 表

字段 类型 说明
id INTEGER 自增主键
person_id INTEGER 关联 persons.id
alias_type TEXT 别名类型(english/nickname/chinese/original)
alias_name TEXT 别名文本

album_tags 表

字段 类型 说明
id INTEGER 自增主键
album_id TEXT 关联 albums.id
tag TEXT 标签文本

定时任务

每 30 分钟触发一次,顺序执行:

1. run_pipeline() → Phase 1(全量解析)+ Phase 2(人物合并)
2. 刷新 AlbumsCache(内存缓存,为 API 提供快速查询)

API 接口

方法 路径 说明
POST /api/pipeline/run 手动触发一次完整管线

返回:

{
  "success": true,
  "persons_updated": 12,
  "tags_updated": 45,
  "persons_merged": 3,
  "message": "管线执行完毕:更新人名 12 个,更新标签 45 个,合并人物 3 人"
}

关键设计原则

  1. 幂等性:管线可反复执行,每次都是完整重新解析 + 强制覆盖写入
  2. 渐进改进:解析逻辑改进后,老数据在下次管线执行时自动受益
  3. 强制覆盖:提取到即写入,不做新旧比对,确保数据始终使用最新逻辑
  4. 中文优先:人名提取优先中文,中英文混合被拆分为主名+别名
  5. 多层过滤:标签经过 5 层过滤链,确保质量
  6. 纯中文/纯英文:人名主体不允许中英混杂