项目:XSearch | 版本:v1.3 | 生成日期:2026-10-06 | 依据:源码分析
这套文档共 21 篇,本篇是收尾的"地图与词典",有三个用途。用途一,术语表:XSearch 的词汇来自三个世界(爬虫工程、Elasticsearch 检索、Django/Vue Web 开发),本篇把其中约五十个高频术语按"这个项目里它具体指什么"定义,并给出源码锚点——同一个词在不同项目里含义有差异(比如"采集任务"在这里有精确的五态机),以本篇的定义为准。用途二,源码文件与篇目的反向索引:拿着一个文件名,立刻查到哪些篇目讲过它、各讲到什么深度,避免在 21 篇里逐篇翻找。用途三,按任务的阅读路径:四个高频任务场景(加新爬虫站点、修搜索无结果、上线检查、排查爬虫被封)各给一条"按序读哪些篇、按序动哪些文件"的路线。三个用途共同的前提是:本套文档的所有结论都来自源码分析,路径引用相对 XSearch/ 仓库根(frontend/ 与 XSearch/XSearch/ 是仓库内的前后端两个工程)。
这一组的术语有两条互相缠绕的主线:一条是框架机制线(spider → 中间件 → item → pipeline 的数据通道),一条是治理线(合规白名单、robots、限速、五态任务机)。读表时建议两条线分开走:先把"一个请求从发出到入库经过谁"顺着框架线走一遍,再回头理解每个环节上挂着什么治理约束——你会发现治理几乎全部作用在框架的配置点上(settings 与 custom_settings),而不是业务代码里,这正是这个爬虫子系统"业务文件里找不到一行反爬逻辑"的原因。另一个值得提前建立的认知是:本组的"采集任务"是 Web 层概念(CrawlManager 管理的一次子进程运行),与"spider"(一段可执行的采集代码)不是同义词——一次任务运行一个 spider,但 spider 也可以完全脱离任务机用命令行跑。
| 术语 | 本项目中的定义 | 源码锚点 |
|---|---|---|
| spider(爬虫) | 一个继承 scrapy.Spider 的类,对应一个数据源的采集逻辑;本项目 5 个:news_rss / news_backfill / aihot_news / aihot_hot / douyin_hot | XSearch/crawler/spiders/*.py;Web 白名单 SPIDERS 在 search/crawl_manager.py |
| item(条目) | spider 解析产出的标准化 dict,是写入侧契约;最低要求四键:title/content/url/source,url_object_id 决定幂等 | 《15-接口全量参考》第六节矩阵;crawler/pipelines.py 的 index_item() |
| pipeline(管道) | item 落库前的横切处理层;本项目只挂一个 EsArticlePipeline: 300(清洗、_id 赋值、upsert) | crawler/pipelines.py |
| 下载中间件 | 按优先级依次处理每个请求/响应/异常的钩子链;本项目三个自定义件(指纹 543/代理 544/Playwright 降级 545)排在内置 UA 中间件(500)之后 | crawler/middlewares.py、crawler/settings.py 的 DOWNLOADER_MIDDLEWARES |
| 合规白名单(FINGERPRINT_HOSTS) | 唯一允许"伪装成浏览器指纹"的域名清单,当前只有 douyin 两域名;新增条目 = 扩大风险面 | crawler/settings.py 34 行;测试 CrawlerComplianceTests |
| robots.txt 合规 | ROBOTSTXT_OBEY = True + 2026-09-27 逐源核对记录;被 robots 拒绝的路径直接放弃而非关开关 | crawler/settings.py 15-18 行;《12-FAQ》C5 |
| 自报身份 | 默认 UA XSearchBot/1.0 (+github 链接),让站方能按 UA 放行或精准封禁 | crawler/settings.py 29 行 |
| 礼貌限速 | DOWNLOAD_DELAY=1 + AutoThrottle(target 2.0/max 3)的组合;aihot 两爬虫另设 2 秒、backfill 1.5 秒 | crawler/settings.py 21-24 行;各 spider custom_settings |
| 429/503 主动放弃 | AIHOT 官方 API 限流时把状态码纳入 HANDLE_HTTPSTATUS_LIST,记 aihot/ratelimited 统计后放弃本次,不退避重试 | crawler/spiders/aihot_hot.py 33-37、59-62 行 |
| JOBDIR | Scrapy 的断点恢复目录(jobs/{spider}_{时间戳}),持久化已爬 URL 与请求队列;成功后清理、失败保留供恢复 | crawl_manager.py 的 JOBS_DIR 与 _cleanup_job_dir |
| 采集任务(CrawlManager) | Web/定时触发的"一次爬虫子进程运行",全系统同时最多一个(进程内 RLock 互斥);五态机 running/completed/failed/empty/interrupted | search/crawl_manager.py 的 CrawlManager.start();历史落 crawl_history.json |
| 盯梢线程(_watch_exit) | 阻塞在 proc.wait() 的守护线程,子进程一退出立即回写终态——不依赖前端轮询(旧实现的"历史永久 running"由此根治) | crawl_manager.py 198-204 行 |
| empty 状态 | 进程正常退出但 item_scraped_count == 0:风控拦截/页面改版的信号,单独标记并触发冷却告警 | crawl_manager.py _check_and_update_current;《12-FAQ》C1 |
| 榜单清理(_prune_stale) | aihot_hot 收尾时 delete_by_query 删除本轮未出现的旧热点——榜单是"当前状态"而非累积 | crawler/spiders/aihot_hot.py 125-147 行 |
| strip_html | 写入侧正文清洗:整块删除 script/style/noscript、残留开标签、全部标签并反转义实体 | crawler/spiders/news_rss.py 56-69 行 |
检索侧的术语围绕三个中心组织。第一个中心是 mapping:索引字段与分词器在 QuoteDocument 一处声明,"双分析器"(建索 ik_max_word、检索 ik_smart+停用词)是其中最需要理解的决策——它同时决定了召回质量与检索噪音水平,且改任何一个都要重建索引才对存量生效。第二个中心是查询语义:must/must_not/post_filter 三层的分工(打分召回 / 无条件排除 / 不影响聚合的过滤)与纠错链(phrase suggester → fuzziness → 热搜词引导)共同构成搜索接口的行为边界。第三个中心是数据契约:_id 幂等、source 维度、aware UTC 时区约定——这三个词没有一个是"检索功能",但没有它们,去重、分面、时间筛选全会静默失真。读这组术语时手边最好开着《04-数据模型》与《18-代码走读实录》第二章对照。
| 术语 | 本项目中的定义 | 源码锚点 |
|---|---|---|
| quotes 索引 | 业务文档唯一权威存储(历史遗留命名,可经 ES_INDEX 覆盖);mapping 由爬虫侧维护,Django 无业务模型 | crawler/pipelines.py 的 QuoteDocument.Index;search/models.py |
| mapping | 索引字段与类型的声明(21 字段 + suggest);QuoteDocument.init() 只在索引不存在时创建,已存在不更新 | crawler/pipelines.py 59-89 行;《11-扩展开发指南》SOP-2 |
| IK 分词 | 中文分词插件(analysis-ik),ES 必须用自建镜像 xsearch-es-ik:8.15.3;双分析器:建索 ik_max_word 细粒度、检索 ik_smart 粗分 | deploy/es-ik.Dockerfile;crawler/pipelines.py 67-69 行 |
| 停用词表(CN_STOPWORDS) | 检索侧过滤的约 50 个高频虚词("的/了/是"…),防 OR 语义下"搜肖申克的救赎、出来沉默的羔羊"的噪音 | crawler/pipelines.py 47-56 行 |
| 高亮 | ES 对命中词元加 <span class="kw"> 标签;前端 renderHighlight() 整体转义后仅还原为 <mark class="kw">(唯一携带 HTML 的响应内容,防 XSS) | api_views.py 的 _HIGHLIGHT;frontend/src/api.ts |
| completion 补全 | suggest 字段(input 为 title+author)驱动的搜索框补全,fuzziness 2 容错;接口为 /api/suggest/,ES 故障静默降级 [] | api_views.py 的 api_suggest |
| phrase suggester | 短语建议器:0 结果时给"您是不是要找"的相近词,命中则换词重搜并置 corrected | api_views.py 的 _did_you_mean |
| fuzziness AUTO | 编辑距离容错的模糊重搜("猪申克"→"肖申克"),并放宽词元命中率为 2<50%;命中置 fuzzy: true | api_views.py _run_search(fuzzy=True) |
| post_filter | 过滤聚合后结果但不影响分面计数的 ES 机制;source/days 筛选必须放这里,分面导航才"切换不互相污染" | api_views.py api_search 285-314 行 |
| 分面(facets) | 来源分布(terms 聚合 size 20)与时间分布(date_range 累计口径 7/30/90)两桶聚合,支撑搜索页的筛选 UI | api_views.py 的 _FACET_AGGS |
| 榜单排除(must_not) | douyin_hot/aihot_hot 两个"当前状态"来源被搜索与要闻接口永久排除(无发布时间语义,会霸榜);无开关 | api_views.py 的 RANKING_SOURCES/SEARCH_EXCLUDED_SOURCES |
_id 幂等 upsert | doc.meta.id = url_object_id 或 md5(url+content[:200]),同 id 重复采集即覆盖更新——全系统去重的基石 | crawler/pipelines.py 186 行;ADR-05(《16-决策记录ADR》) |
| source 字段 | 全系统最重要的过滤/聚合维度(Keyword 不分词);7 个活跃值 + 存量 news_people;分组常量 NEWS_SOURCES/AI_SOURCES/RANKING_SOURCES | api_views.py 558-562 行;《04-数据模型》2.2 |
| aware UTC 约定 | 写入侧 crawled_at/create_date 统一带时区 UTC(naive 时间曾被 ES 当 UTC、容器 TZ=Asia/Shanghai 导致整体偏移 8 小时),展示侧 _display_date() 换算站点时区 | crawler/pipelines.py 的 utc_now();测试 TzAwareWriteTests 扫源码防回归 |
这一组的术语有一个统一的母题:单进程架构下的可靠性补丁。单 worker(token、互斥、调度器全在进程内)是这个系统的地基,围绕它长出了一系列补偿机制——盯梢线程让任务状态不依赖消费者、原子写让状态文件经得起 kill 与 bind mount、冷却告警让"悄悄停更"变成一封邮件、降级语义让依赖故障不升级为页面故障。另一个母题在前端:既然没有状态管理库、没有单测框架、没有 UI 上的复杂路由库,那么"URL 即状态"、"类型契约"、"体积守门"就是补位的三大支柱——它们各自替代了一个"本可以引入的依赖"。读这组术语时留意每个词条的源码锚点:几乎每一条都对应一段记录了事故成因的注释,那是这些术语最好的扩展阅读。
| 术语 | 本项目中的定义 | 源码锚点 |
|---|---|---|
| 热搜词 | Redis zset search_keywords_set:每次搜索 zincrby 计数、裁剪前 100;stats 读 Top8、空结果/首页读 Top6 引导 | api_views.py 的 _record_search_keyword;api_stats |
| Redis 纯加速层 | Redis 挂掉只损失缓存与热搜词、绝不阻塞主流程;所有访问 try-except 包裹,3 秒双超时 | api_views.py 60-61 行;ADR-06(《16-决策记录ADR》) |
| 缓存联动失效 | 按来源清理数据后精确删除新闻列表缓存(scan_iter 匹配 rankings_news:{source}:p*,含聚合键),60 秒内生效而非等 TTL | api_views.py 的 _purge_rankings_cache |
| 单 worker 约束 | gunicorn --workers 1 是硬部署约束:token 表、登录限速、爬虫互斥、调度器全是进程内状态,多 worker 状态互不可见 | Dockerfile CMD 注释;ADR-04(《16-决策记录ADR》) |
| token 滑动续期 | 管理员 token(secrets.token_hex(32))存进程内字典,12 小时有效且每次成功请求续期;登出即吊销、重启全失效 | api_views.py 的 require_admin/_admin_tokens |
| 登录四道防线 | 锁定前置检查(429 先于密码比较)→ hmac.compare_digest 常数时间比较 → 按 IP 计数(300s 窗 5 次锁 900s)→ XFF 采信策略可配 | api_views.py 178-216 行;《08-安全与权限设计》2.3 |
| XFF 采信 | DJANGO_TRUST_PROXY_HEADER 控制登录限速取 X-Forwarded-For 首段还是 REMOTE_ADDR;True 的前提是公网入口经可信反代 | xsearch/settings.py 82 行;_client_ip |
| fail-closed | 配置缺失即拒绝启动的哲学:生产态缺 DJANGO_SECRET_KEY 抛 ImproperlyConfigured、ALLOWED_HOSTS 空 sys.exit(1);账号未配置登录返回 503 而非 500 | xsearch/settings.py 24-77 行;ADR-10(《16-决策记录ADR》) |
| 原子写(_write_json_atomic) | .tmp 完整写 + fsync + 原地覆盖主文件——规避单文件 bind mount 上 os.replace 必然 EBUSY 的线上事故;配套 _load_json_with_recovery 回退 .tmp、备份 .corrupt-* 现场 | search/crawl_manager.py 24-75 行 |
| misfire(错过触发) | 定时任务到达触发时刻但进程忙/刚重启而错过;misfire_grace_time=3600 + coalesce=True 允许 1 小时内合并补跑一次,并写 misfired 记录 + 告警 | crawl_manager.py 的 _add_to_scheduler/_on_scheduler_event |
| SSRF 三重防护 | 图片代理 api_img 的白名单按点边界精确匹配 + 拒绝 URL 内嵌用户名密码 + 回源不跟随重定向/10 秒超时/20MB 上限 | api_views.py 1073-1146 行;《08》第五节 |
| URL 即状态 | SearchView 把 q/source/sort/days/p 全部编码进 query string,界面事件只改 URL、路由 watch 统一发请求——深链/前进后退/分享天然可用 | frontend/src/views/SearchView.vue;《02-页面设计》4.2 |
| 类型契约(api.ts interface) | 前端 22 个 interface 与后端响应字段一一对应,npm run build 的 vue-tsc 全量类型检查充当"契约测试" | frontend/src/api.ts;《09》第六节 |
| 体积守门 | CI 取 dist 最大 JS,≥ 1258291 字节(1.2MB)即失败——防"路由懒加载退化成单 chunk"的回归 | .github/workflows/ci.yml frontend job;《10》第五节 |
| 图片代理 | 后端携带图床站内 Referer 拉取防盗链图片并磁盘缓存(MD5 文件名、2000 文件上限、1% 概率触发淘汰) | api_views.py 的 api_img;frontend/src/api.ts 的 imgUrl() |
| 冷却告警 | notify() 同 key 事件 30 分钟只发一封(_COOLDOWN=1800),后台守护线程发信绝不阻塞请求路径 | search/notify.py |
用法:改某个文件之前先查这张表,把标注的篇目过一遍("机制"篇讲实现细节、"SOP"篇讲怎么改)。未列出的文件(migrations、admin.py、static/ 遗留等)在《07-模块详解》有存在性说明。
| 源码文件 | 讲过它的篇目 |
|---|---|
XSearch/xsearch/settings.py | 14(逐段走读 + fail-closed 详解)、16-ADR-10、08、06 |
XSearch/xsearch/urls.py | 15(29 条路由总表)、07(3.2)、03 |
XSearch/xsearch/middleware.py | 07(3.2)、08(CORS)、12(FAQ-D7) |
XSearch/search/api_views.py | 18(2.x 逐段走读 api_search)、15(全量详表)、19(示例集)、03(契约意图)、07(3.3 函数清单)、08(鉴权/SSRF/XSS)、10(超时与缓存)、16-ADR-07/09 |
XSearch/search/crawl_manager.py | 18(3.x 逐段走读 start/resume_job)、05(流程一/三)、07(3.4 类与方法清单)、04(JSON 状态文件)、14(原子写)、16-ADR-03/11 |
XSearch/search/apps.py | 07(3.5)、14、16-ADR-10 |
XSearch/search/notify.py | 07(3.5)、05(流程一告警链)、10(4.1 后台线程理由) |
XSearch/search/seo_views.py | 07(3.5)、15(#11/#12)、08(DISALLOWED) |
XSearch/search/tests.py | 09(22 类 110 方法逐类分布)、05/08/12(测试即事故档案) |
XSearch/crawler/settings.py | 14(第六节逐项)、16-ADR-08(合规)、07(2.1 装配清单) |
XSearch/crawler/pipelines.py | 07(2.4 走读)、04(mapping 主模型)、15(第五节字段全表)、11(SOP-2)、16-ADR-01/02/05 |
XSearch/crawler/middlewares.py | 18(1.4 走读)、07(2.3)、08(白名单边界)、16-ADR-08 |
XSearch/crawler/ai/fingerprint.py | 07(2.2)、00(指纹降级模板) |
XSearch/crawler/spiders/news_rss.py | 18(1.1-1.3/1.5 逐段走读)、07(2.5)、11(SOP-6)、15(第六节) |
XSearch/crawler/spiders/aihot_hot.py | 07(2.5)、11(SOP-1 骨架参照)、04(_id 与榜单清理) |
XSearch/crawler/spiders/aihot_news.py | 07(2.5)、15(第六节 item 矩阵) |
XSearch/crawler/spiders/news_backfill.py | 07(2.5)、06(命令行用法)、04(幂等语义) |
XSearch/crawler/spiders/douyin_hot.py | 07(2.5)、08(合规张力)、12(FAQ-C2/C7)、16-ADR-08 |
XSearch/requirements.txt / requirements-lock.txt | 17(2.1-2.6 逐个解析)、06(版本基线) |
frontend/package.json / tsconfig.json | 17(3.1-3.2 逐个解析)、02(工程与原则)、09(两道闸) |
frontend/src/api.ts | 19(interface 对照)、15(第七节双清单)、08(renderHighlight)、02 |
frontend/src/main.ts / auth.ts | 02(路由与守卫)、07(4.1)、10(懒加载) |
frontend/src/views/*.vue(9 页面) | 02(4.1-4.10 逐页)、07(4.2 视图矩阵)、11(SOP-4) |
Dockerfile / frontend.Dockerfile / deploy/es-ik.Dockerfile | 14(第三节逐段)、06(4.1)、16-ADR-02/04、17(版本锚点) |
docker-compose.yml / docker-compose.prod.yml | 06(4.2/4.3 八处差异)、14(第四节)、08(端口策略) |
deploy/nginx.conf | 06(4.5 五组 location)、08(7.2 转发即防火墙)、10(缓存策略) |
deploy/backup.sh | 06(4.8)、12(FAQ-F4) |
ruff.toml / .github/workflows/ci.yml | 09(第三节/第二、六节)、16-ADR-12、06(第五节) |
.env.example / local_settings.py.example | 14(第二节/第七节)、06(第三节)、16-ADR-10 |
README.md / AI升级说明.md | 00(多次引用)、16-ADR-08(合规立场)、13(文档漂移清单) |
路线:《11-扩展开发指南》SOP-1(操作规程)→《15-接口全量参考》第六节(item 契约矩阵)→《18-代码走读实录》第一章(news_rss 逐段走读,作为骨架范本;aihot_hot.py 是另一类 API 型骨架)→《16-决策记录ADR》ADR-08(合规边界的由来)→ 完成后《09》第七节(补测路线)。
动线:合规预检(读目标站 robots、确认无需登录/无验证码;FINGERPRINT_HOSTS 保持不动)→ 新建 XSearch/crawler/spiders/<name>.py(产出含 url_object_id 与 source 的标准化 item dict)→ crawl_manager.SPIDERS 加白名单条目 → api_views.py 来源分组常量归类(新闻/AI/榜单三选一,榜单自动被搜索排除)→ 前端 Tab/标签映射(可选)→ search/tests.py 补解析函数单测与一致性断言。四条红线见《11》第八节:不动合规配置、不新增进程内状态、不改 _id/source 既有取值、收尾必跑 110 个测试与 npm run build。
完成标志:命令行 scrapy crawl <name> 跑通且 ES 里能查到新来源文档;采集管理页出现新爬虫并能一键启动;item_scraped_count 与管道的 es_saved 计数一致;解析函数有固化真实样本的单测。最容易跳过的是第 4 步的"归类"——不把来源加进分组常量,新内容虽可检索,但要闻混排与搜索排除等行为不会按预期生效。
路线:《12-FAQ与故障排查》第二节决策树(先走完 S1-S6 的分诊)→《05-核心流程》流程二(搜索请求全链路,理解纠错与分面)→《18-代码走读实录》第二章(api_search 逐段走读,定位代码级原因)→《19-数据契约与示例集》第一节与第十节(用 curl 与 ES DSL 直接验证)。
动线:先分层——curl /api/stats/ 看 es_ok 与 total(数据层有没有货)→ curl /api/search/?q=... 直测后端(区分前端问题还是后端问题)→ 换一个确定存在的词(区分分词问题还是数据问题)→ 检查是否榜单内容(must_not 排除是设计行为,去榜单页看)→ 刚采完的数据等 1 秒刷新窗口与 60/120 秒缓存。代码级原因的四个高发点:索引建于 stock ES(无 IK,建索引就失败)、查询词全停用词、post_filter 条件写错、纠错链分支回归。若需改查询逻辑,改前读《18》2.2 的 must/must_not/post_filter 语义分工,改后补《09》对应测试。
完成标志:能对"为什么没结果"给出一句可归因的解释(数据没采到 / 被榜单排除 / 分词语义 / 前端消费问题),而不是"重启试试"。这条路径排练一次的最大收益是建立分层的直觉:XSearch 的搜索链路上每一层(数据、索引、查询、消费)都有独立的观测点,顺序走一遍的成本远低于在代码里盲找。
路线:《06-部署运行》4.4(部署拓扑)+ 6.4(发布清单,逐条勾)→《08-安全与权限设计》第九节(十项已知隐患,确认部署形态命不命中)→《14-配置与环境详解》第八节(配置生效路径:改什么要重启什么)→《16-决策记录ADR》ADR-04/10(单 worker 与 fail-closed 的必然性)→《12-FAQ》第一节(日志入口总表,出事时用)。
动线:.env 三件套(DJANGO_DEBUG=False、DJANGO_ALLOWED_HOSTS、随机 DJANGO_SECRET_KEY)→ 管理员改随机长口令(弱口令启动告警)→ ES/Redis 仅内网(9200 绝不暴露公网,xpack 关闭下网络隔离是唯一防线)→ 用 requirements-lock.txt 安装 → python manage.py test search 全绿 → 前端 dist 与后端同批发布 → 部署后按《06》6.3 冒烟:/api/health/ 200、robots/sitemap 内容类型正确、采集链路跑通、调度器启动日志逐条打印、告警自检发一封、备份演练 DRY_RUN=1。frp 与 VPS Nginx 配置在仓库外(⚠️ 待确认),核对 HSTS 与 HTTPS 强制在该层完成。
完成标志:冒烟八条全部通过且告警邮件真实到达——注意"backend 反复重启"若出现,先查 fail-closed 三件套的日志文案(缺密钥、Host 列表空、migrate 失败各有明确提示),这是《12-FAQ》D2 的固定剧本。发布清单里唯一无法从仓库验证的环节是公网链路的 frp/VPS Nginx,务必亲自 curl https://域名/robots.txt 验证内容类型,确认没有被 SPA 兜底规则吞掉。
路线:《12-FAQ与故障排查》第三节决策树(failed/empty/running 三分支分诊)→《16-决策记录ADR》ADR-08(合规白名单的设计与张力)→《08-安全与权限设计》第四节(爬虫合规四件套)→《10-性能设计与优化》第三节(限速参数全景)。
动线:先看采集历史状态与日志尾部——failed 看退出码与 Traceback;empty(正常退出 0 条)按爬虫分诊:news_rss 看每源"feed 解析出 N 条"是否为 0(源站改版),aihot 看日志 aihot/ratelimited(限流,等冷却再试,不要退避重试加重压力),douyin 看 /tmp/douyin_hot_debug.png 截图(页面改版对比)。风控类症状(444/412/403):444 是抖音对"不像浏览器"请求的主动拒绝——确认 FINGERPRINT_HOSTS 白名单、browserforge 可用性、chromium 已装;412 的历史成因是指纹头没盖住内置 UA(已修);403 常提示需要登录态——项目不采集登录后数据,这是预期边界而非可修复故障。处置纪律:不关 ROBOTSTXT_OBEY、不给白名单加站(除非完成合规预检并同步更新 CrawlerComplianceTests)、不提高限速(1.5-2 秒间隔是对源站的礼貌承诺)。若某源长期被封,两条正路:与站方沟通按 UA 放行,或按《16》ADR-08 的思路下线该源。
完成标志:分清"被封"与"没被封"——empty 不等于封禁(feed 改版、0 条解析失败同样产生 empty),444/412/403 也不全等于风控(403 的最常见解释恰恰是项目主动不做的登录态)。这个路径最重要的产出是一份结论:该源在当前合规立场下还能不能采、以什么方式采;结论写进 PR 描述,延续 2026-09-27 那次逐源核对留下的格式。
全 21 篇的分工一句话版:00 总览、01 架构、02 页面、03 接口意图、04 数据模型、05 流程、06 部署、07 模块深读、08 安全、09 测试、10 性能、11 扩展 SOP、12 排障、13 技术债、14 配置、15 接口速查;16-20 为本批新增:16 决策考古、17 依赖全景、18 代码走读、19 数据契约示例、20 术语与索引(本篇)。它们之间的引用关系遵循一个稳定的方向:00-07 讲"是什么",08-10 讲"质量三面"(安全/测试/性能),11-12 讲"怎么动"(扩展/排障),13-15 讲"账本与速查",16-20 讲"为什么与怎么查"(决策/依赖/代码/契约/索引)。篇与篇之间刻意不做内容复制——同一件事在多篇出现时,只有一篇是"权威篇"(如 token 生命周期在《08》、原子写在《14》、mapping 在《04》),其余位置一律以"见《0X-xxx》"引用;发现两篇口径冲突时,先核对源码,再以权威篇为准修订另一篇。
最后给一份"最小必读"建议:接手这个仓库的人,无论目标是什么,都值得先按序读完三篇——《00-项目总览》建立全景、《16-决策记录ADR》理解每面墙为什么立在那里、《18-代码走读实录》看懂代码的真实风格;此后按第二节的反向索引与第三节的任务路径按需取用。四条任务路径之外,其实还存在第五条隐形的路径——"给这套文档补一篇":当某个主题在 21 篇里都找不到权威篇时,按本批新增五篇的格式(元信息头、真实路径引用、诚实标注 ⚠️ 待确认、与既有篇目分工而非重复)扩展即可,本篇第二节的总索引表应同步加一行。这个项目的文档与代码共享同一条价值观:诚实记录代价、显式表达边界、让后来者的每一次"为什么"都有处可查。若你在源码中发现了与本套文档冲突的事实,以源码为准,并请顺手修订对应篇目——文档也是仓库的一部分,它的腐烂速度取决于使用者的维护意愿。