7.9 KiB
7.9 KiB
流云图库 — 元数据管线架构文档
版本: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(强制覆盖,不比对旧值)
│
└── 下一个相册
人名提取策略
按优先级依次尝试:
- 人物库精确匹配 —
person_merger::match_title(),在已有 persons 表中查询标题子串 - jieba 词性标注 —
extract_person_from_tags(),优先取 nr(人名)类词 - 标题模式匹配 —
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 人"
}
关键设计原则
- 幂等性:管线可反复执行,每次都是完整重新解析 + 强制覆盖写入
- 渐进改进:解析逻辑改进后,老数据在下次管线执行时自动受益
- 强制覆盖:提取到即写入,不做新旧比对,确保数据始终使用最新逻辑
- 中文优先:人名提取优先中文,中英文混合被拆分为主名+别名
- 多层过滤:标签经过 5 层过滤链,确保质量
- 纯中文/纯英文:人名主体不允许中英混杂