项目:XSearch | 版本:v1.0 | 生成日期:2026-10-05 | 依据:源码分析
XSearch 的运行环境有三条硬约束,每一条都能在源码里找到出处,部署前必须先理解,否则故障会以"看起来无关"的形式暴露。
第一,Python 必须是 3.12。后端镜像 Dockerfile 的基础镜像为 python:3.12-slim;XSearch/requirements-lock.txt 首行注释写明"2026-09-09 于 Python 3.12 实测通过的组合";CI(.github/workflows/ci.yml)的 actions/setup-python 同样固定 '3.12'。更根本的原因是锁定的 Django==6.1.1 自身要求 3.12+。
第二,Elasticsearch 不能用官方原版镜像。索引 mapping(crawler/pipelines.py 的 QuoteDocument)在建索引时声明 ik_max_word / ik_smart 分析器,官方 elasticsearch:8.x 镜像不带 IK 插件——问题不会在容器启动时暴露,而是第一次采集建索引时直接失败。因此项目用 deploy/es-ik.Dockerfile 定制了 xsearch-es-ik:8.15.3 镜像,且注释强调插件版本必须与基础镜像 8.15.3 严格一致,否则 ES 拒绝加载插件。
第三,gunicorn 必须 --workers 1。管理员 token、爬虫互斥锁、APScheduler 调度器全部是进程内状态(search/crawl_manager.py),Dockerfile 的 CMD 注释写明:多 worker 会导致状态互不可见、定时任务重复触发;需要横向扩容时应先把任务状态外置到 Redis/DB,再增加 worker。
其余组件版本汇总如下(均以源码为准):
| 组件 | 版本 | 依据文件 |
|---|---|---|
| Python | 3.12 | Dockerfile(python:3.12-slim)、requirements-lock.txt、ci.yml |
| Django / gunicorn | 6.1.1 / 23.0.0 | XSearch/requirements-lock.txt |
| Scrapy / scrapy-playwright / playwright | 2.18.0 / 0.0.48 / 1.60.0 | XSearch/requirements-lock.txt |
| APScheduler / Twisted / lxml | 3.11.2 / 26.4.0 / 5.3.0 | XSearch/requirements-lock.txt |
| elasticsearch(客户端)/ elasticsearch-dsl | 8.19.3 / 8.18.0 | XSearch/requirements-lock.txt |
| Elasticsearch(服务端)+ IK | 8.15.3 + analysis-ik 8.15.3 | deploy/es-ik.Dockerfile |
| Redis(服务端)/ redis-py | redis:7-alpine / 5.2.1 | docker-compose.yml、requirements-lock.txt |
| Node.js | 镜像与 CI 均为 20(README 标注下限 18+) | frontend.Dockerfile(node:20-alpine)、ci.yml |
| Vue / Vite / TypeScript / vue-tsc | ^3.5 / ^6.0 / ^5.9 / ^3.3 | frontend/package.json |
| Nginx | dev 态 nginx:alpine;生产态 nginx:1.27-alpine | frontend.Dockerfile、docker-compose.prod.yml |
| Playwright 浏览器 | chromium(仅 douyin_hot 爬虫需要) | Dockerfile、README.md |
资源层面,compose 为 ES 配置了 ES_JAVA_OPTS=-Xms512m -Xmx512m 与 ulimits.memlock: -1,即约 1–2GB 内存的机器即可跑起全套四容器;磁盘消耗主要来自 ES 数据卷与 cache/images 图片代理缓存,随采集量增长,生产部署把全部状态落在 bind mount 的 ./data(见第四节),需按采集规模自行预估容量。
本机开发态最大的便利是:不配置任何环境变量也能完整跑通。XSearch/xsearch/settings.py 第 24 行在 DJANGO_DEBUG 缺省时取 True,此时使用仅开发态的密钥 _DEV_ONLY_KEY(启动日志会打印"⚠️ 正在使用仅限开发态的 SECRET_KEY"),ALLOWED_HOSTS 自动补 localhost,127.0.0.1。以下步骤摘自 README.md 并经源码逐条核对。
第 1 步:启动 Elasticsearch 与 Redis
docker compose up -d elasticsearch redis这条命令会现场构建 deploy/es-ik.Dockerfile(compose 中 image: xsearch-es-ik:8.15.3 配 build.dockerfile: deploy/es-ik.Dockerfile):在官方 elasticsearch:8.15.3 之上,通过 infini.cloud 国内源安装 analysis-ik 插件(注释说明不用 GitHub 直连是因为 NAS 等环境不可靠)。ES 以 discovery.type=single-node、xpack.security.enabled=false 运行,端口只绑定回环 127.0.0.1:9200——compose 注释原话:"xpack.security 是关的,9200/6379 暴露到局域网等于把索引裸奔"。数据落在 named volume es_data。Redis 7 以 --appendonly yes 开 AOF 持久化,绑定 127.0.0.1:6379,数据卷 redis_data。两服务均带 healthcheck:ES 探 /_cluster/health,Redis 探 redis-cli ping。
第 2 步:安装并启动 Django 后端
cd XSearch
pip install -r requirements-lock.txt # 与 Docker 构建、CI 同一份锁定依赖
playwright install chromium # 仅采集抖音榜(douyin_hot)需要
python manage.py migrate # 初始化 SQLite(只存会话等框架数据)
python manage.py runserver 127.0.0.1:8000开发也可改用 requirements.txt 的宽松范围(django>=4.2、scrapy>=2.13 等),但为与镜像行为一致建议用 lock 文件。migrate 只初始化 Django 框架表——业务数据全在 Elasticsearch,search/models.py 已无业务模型。后端启动即调度器启动:search/apps.py 的 SearchConfig.ready 会拉起 APScheduler 并加载 schedules.json,定时任务随服务自启,无需访问页面激活。
第 3 步:安装并启动 Vue 前端
cd frontend
npm install
npm run dev # http://localhost:5173frontend/vite.config.ts 把 dev server 固定在 5173 端口,并把 /api 代理到 VITE_API_TARGET(缺省 http://127.0.0.1:8000),vite preview(4173 端口)同样配置了代理,因此开发态完全不用关心 CORS;settings.py 的 CORS 白名单也已内置 localhost:5173/4173 及其 127.0.0.1 形态(FRONTEND_ORIGINS 可追加)。npm run build 会先跑 vue-tsc --noEmit 全量类型检查再 vite build,产物在 frontend/dist。
第 4 步:配置管理员、采集数据、跑测试
管理功能("采集管理"/"数据管理"页)需要管理员账号。复制 XSearch/local_settings.py.example 为同目录 local_settings.py(已加入 .gitignore),填入 ADMIN_USERNAME / ADMIN_PASSWORD;该文件在 settings.py 末尾被 exec 进模块全局命名空间,优先级高于环境变量;两者都未配置时登录接口直接拒绝。采集既可在仓库根 XSearch/ 下用 CLI:
scrapy crawl news_rss # 新闻 RSS 聚合(中新网/IT之家/Solidot)
scrapy crawl news_backfill -a days=180 # 中新网历史回填(幂等,可重复跑)
scrapy crawl aihot_news # AI 精选 + 当日 AI 日报(-a full=1 拉全量)
scrapy crawl aihot_hot # AI 热点榜 Top10
scrapy crawl douyin_hot # 抖音热点榜(需 playwright chromium)也可登录前端"采集管理"页一键启动。最后验证环境完好:python manage.py test search——22 个测试类、110 个测试方法全部 mock 外部依赖,本机不需要 ES/Redis 即可通过。
全部环境变量在 XSearch/xsearch/settings.py、XSearch/crawler/settings.py 与 frontend/vite.config.ts 中读取,模板见 .env.example(共 17 个变量,含逐项注释)。生产形态的关键设计是 fail-closed:DJANGO_DEBUG=False 时,DJANGO_SECRET_KEY 缺失抛 ImproperlyConfigured 拒绝启动,DJANGO_ALLOWED_HOSTS 为空则 sys.exit(1)——docker-compose.prod.yml 注释称这是"有意的,避免用已知密钥/调试页面上线"。
| 变量 | 默认值 | 读取位置 | 语义与约束 |
|---|---|---|---|
DJANGO_DEBUG | True | xsearch/settings.py | 生产 compose 写死 False;False 时启用 SECURE_PROXY_SSL_HEADER、Cookie HttpOnly、X_FRAME_OPTIONS=DENY 等安全项 |
DJANGO_SECRET_KEY | 仅开发态密钥 | xsearch/settings.py | DEBUG=False 缺失即拒绝启动;生成:python -c "import secrets;print(secrets.token_urlsafe(50))" |
DJANGO_ALLOWED_HOSTS | 空(DEBUG 时补 localhost,127.0.0.1) | xsearch/settings.py | 逗号分隔;DEBUG=False 且为空 → sys.exit(1) |
DJANGO_TRUST_PROXY_HEADER | True | xsearch/settings.py | 管理员登录限速取客户端 IP 是否采信 X-Forwarded-For;代理不受信时设 False |
ES_URL | http://127.0.0.1:9200 | xsearch/settings.py 与 crawler/settings.py | Django 与 Scrapy 统一读取;容器部署注入 http://elasticsearch:9200 |
ES_INDEX | quotes | xsearch/settings.py | 文档索引名(历史遗留命名) |
REDIS_URL | redis://127.0.0.1:6379/0 | xsearch/settings.py | 热搜词 zset、列表缓存 |
ADMIN_USERNAME / ADMIN_PASSWORD | (空) | xsearch/settings.py | 建议写 XSearch/local_settings.py(优先级更高且不入 git);弱口令启动日志告警 |
ALERT_EMAIL_ENABLED | False | xsearch/settings.py | 不为 True 时 notify() 是 no-op |
ALERT_SMTP_HOST / ALERT_SMTP_PORT | smtp.qq.com / 465 | xsearch/settings.py | SSL SMTP |
ALERT_SMTP_USER / ALERT_SMTP_PASSWORD | (空) | xsearch/settings.py | QQ/163 需填 SMTP 授权码,非登录密码 |
ALERT_EMAIL_TO | (空 = 发给 USER) | xsearch/settings.py | 逗号分隔多收件人 |
AI_PROXIES | (空) | crawler/settings.py | 住宅代理列表(http://user:pass@host:port),逗号分隔,按请求轮换 |
FRONTEND_ORIGINS | (空) | xsearch/settings.py | 追加允许跨域的前端来源(内置 5173/4173 本地回环四个) |
VITE_API_TARGET | http://127.0.0.1:8000 | frontend/vite.config.ts | dev/preview 的 /api 代理目标 |
TZ | 容器缺省 | docker-compose.prod.yml | 生产 backend 写死 Asia/Shanghai |
使用方式上,两份 compose 的 backend 都带 env_file: .env——注意开发态 compose 也要求 .env 文件存在(没有会启动报错,文件注释提示"首次使用先执行:cp .env.example .env")。环境变量先加载,local_settings.py 后覆盖,因此调试个别配置时可以只改文件不动 .env。
仓库共有三个 Dockerfile,各管一层:
Dockerfile(后端):python:3.12-slim 基础上先 apt 安装编译链(gcc、g++、libffi/libssl/libxml2/libxslt/zlib)与 Playwright 运行所需的 17 个图形库(libnss3、libgbm1、libasound2 等);pip install --no-cache-dir -r requirements-lock.txt(构建参数 PIP_INDEX_URL 可指向清华源);playwright install chromium(构建参数 PLAYWRIGHT_DOWNLOAD_HOST 可指向 npmmirror);COPY XSearch/ . 后 mkdir -p cache/images jobs logs 预建运行时目录。暴露 8000,带 HEALTHCHECK(容器内无 curl,用 python 标准库 urllib 探测),CMD 为 python manage.py migrate --noinput && gunicorn xsearch.wsgi:application --bind 0.0.0.0:8000 --workers 1 --timeout 120——容器每次启动自动迁移再起服务。frontend.Dockerfile(前端):两阶段构建。builder 阶段 node:20-alpine 执行 npm install + npm run build;运行阶段 nginx:alpine,拷入 dist 产物与 deploy/nginx.conf 作为站点配置。值得注意:这里用的是 npm install 而非 CI 的 npm ci,镜像内依赖可能比 lock 略有漂移,属源码事实。deploy/es-ik.Dockerfile(ES):docker.elastic.co/elasticsearch/elasticsearch:8.15.3 加一行 bin/elasticsearch-plugin install --batch https://get.infini.cloud/elasticsearch/analysis-ik/8.15.3。配套的 .dockerignore 把 .git、.env、.env.*、frontend/node_modules、frontend/dist、XSearch/{logs,jobs,cache}、db.sqlite3、local_settings.py、crawl_history.json、schedules.json、__pycache__ 全部挡在构建上下文之外——既裁剪体积,也保证密钥与私有配置绝不进入镜像分层。
docker-compose.yml 定义四个服务,全部接入 bridge 网络 xsearch,使用 named volume 持久化:
| 服务 | 镜像 | 宿主端口 | 卷 | 健康检查(interval/timeout/retries) |
|---|---|---|---|---|
| elasticsearch | xsearch-es-ik:8.15.3(现场构建) | 127.0.0.1:9200 | es_data | curl /_cluster/health,30s/10s/5 |
| redis | redis:7-alpine | 127.0.0.1:6379 | redis_data | redis-cli ping,10s/5s/5 |
| backend | Dockerfile 构建 | 8000:8000 | backend_cache、backend_jobs、backend_logs | 镜像 HEALTHCHECK 打 /api/health/ |
| frontend | frontend.Dockerfile 构建 | 8080:80 | — | — |
backend 通过 environment 注入 ES_URL=http://elasticsearch:9200、REDIS_URL=redis://redis:6379/0(服务名即 DNS),depends_on 对 ES/Redis 带 condition: service_healthy,保证依赖就绪后才起 Django。常用命令:
cp .env.example .env # 首次必做(env_file 要求文件存在)
docker compose up -d # 四容器
docker compose logs -f backend
docker compose exec backend scrapy crawl news_rss # 进容器采集本机访问:前端 http://localhost:8080,后端 API http://localhost:8000,ES http://localhost:9200。
docker-compose.prod.yml 面向 Synology NAS(部署目录 /volume1/docker/xsearch),文件顶部注释列出的差异,结合服务定义可归纳为八点:
127.0.0.1:5600:80(再经 frp 隧道 xsearch-web → VPS Nginx → HTTPS)。原因有二:公网安全(xpack.security 关闭,9200 绝不能出内网),以及 NAS 上 9200/6379 已被共享 dev-stack 占用。image: 没有 build: 段,镜像在 NAS 上用 legacy builder 手工构建(命令见 4.7 节),启动时 up -d --no-build。nginx:1.27-alpine,只 ro 挂 ./deploy/nginx.conf 与 ./frontend/dist(Mac 上 npm run build 后上传的产物)。./data:data/es、data/redis、data/snapshots、data/cache、data/jobs、data/logs 六个目录,外加三个单文件 bind mount——data/db.sqlite3、data/schedules.json、data/crawl_history.json,容器重建不丢运行时状态(开发态没有挂这三个文件,这是生产态独有的设计)。./data/snapshots:/snapshots 并 ro 挂 deploy/es.elasticsearch.yml 覆盖容器内 elasticsearch.yml(内容仅三行:cluster.name: docker-cluster、network.host: 0.0.0.0、path.repo: /snapshots)。注释解释了原因:8.15.3 官方镜像的启动脚本只可靠转换 discovery.type、xpack.security.enabled 两个点号环境变量,path.repo 走 env 实测不生效,快照仓库注册会报 path.repo is empty;deploy/backup.sh 的热备份依赖此能力。start_period: 40s(开发态是 30s/10s/5 次无 start_period),backend 对 ES/Redis 的依赖同样是 condition: service_healthy。注释记载了教训:2026-09-10 首发时 ES 未就绪即启动爬虫,ES pipeline 连接超时导致整轮数据不入库。environment 直接写 DJANGO_DEBUG=False 与 TZ=Asia/Shanghai("不依赖 .env 是否记得设"),同时 .env 必须提供 DJANGO_SECRET_KEY 与 DJANGO_ALLOWED_HOSTS,缺失即 fail-closed 拒启。name: xsearch 固定项目名;且文件注释强调必须放在部署目录根目录运行——Synology 版 compose 把相对路径按 compose 文件所在目录解析,放进 deploy/ 会导致 ./data 与 env_file 全部错位。生产形态的完整链路如下图:公网流量经 VPS Nginx 终结 HTTPS,由 frp 隧道打到 NAS 内只绑回环的前端容器,前端容器再把 API 与 SEO 路径反代给后端,后端读写同网络的 ES/Redis,所有持久化状态落在宿主 ./data。
frp 服务端(frps)与 VPS Nginx 站点、证书续期的具体配置不在本仓库中,⚠️ 待确认;可确认的是隧道名 xsearch-web(compose 注释)与"VPS nginx + Certbot 证书"(settings.py 生产段注释)。
deploy/nginx.conf 是公网流量的唯一站点配置,五组 location 各有明确职责:
location /:try_files $uri $uri/ /index.html 兜底 Vue Router history 模式路由。location = /index.html:强制 Cache-Control: no-cache, must-revalidate 并关闭 etag。注释解释:视图懒加载的 chunk 文件名带 hash,浏览器一旦缓存旧 index.html,发版后引用的旧 chunk 已不存在,点导航就白屏。location = /robots.txt 与 = /sitemap.xml:反代 http://backend:8000(两者由 search/seo_views.py 动态生成,sitemap 从索引生成)。不配这两条会被 SPA 兜底成 index.html,抓取方拿到错误内容类型。这里 X-Forwarded-Proto 特意透传 $http_x_forwarded_proto 而非 $scheme:本容器在 frp 隧道内是明文 http,$scheme 会把 VPS Nginx 写好的 https 盖掉,Django 的 SECURE_PROXY_SSL_HEADER 拿到 http 会让 sitemap/robots 链接退回 http://;局域网直连时该变量为空、Nginx 不下发此头,Django 自然回落 http,行为仍正确。location /api/:反代后端,带 Host/X-Real-IP/X-Forwarded-For/X-Forwarded-Proto 四个头;proxy_buffering off、proxy_cache off、proxy_read_timeout 300s 服务实时轮询类接口。js|css|图片|字体 后缀 expires 30d + Cache-Control: public, immutable;另开 gzip(gzip_min_length 1024)。此外 /admin/(Django admin)不在转发规则内,默认到不了公网;这与 README"发布清单"的口径一致。
健康检查分两层。镜像层:Dockerfile 的 HEALTHCHECK(--interval=30s --timeout=10s --start-period=20s --retries=3)打 /api/health/,该接口真检 ES 与 Redis,任一不可用返回 503,urllib 抛错判 unhealthy。注释还记录了历史教训:以前打的是 /api/stats/,那个视图把依赖故障降级成 200 + es_ok:false,导致 ES 挂了容器仍显示 healthy,停更只能靠人打开网站撞见——所以 README 特别提醒 /api/stats/ 不能当探针。compose 层:ES 与 Redis 各自带 healthcheck,并作为 backend 的启动门控。前端 nginx 容器无健康检查(静态托管,意义有限)。
生产 compose 不构建镜像,先在 NAS 上用 legacy builder(Synology Docker 无 BuildKit)构建两个镜像,再启动:
cd /volume1/docker/xsearch
DOCKER_BUILDKIT=0 docker build -f deploy/es-ik.Dockerfile -t xsearch-es-ik:8.15.3 .
DOCKER_BUILDKIT=0 docker build -f Dockerfile -t xsearch-backend:latest \
--build-arg PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
--build-arg PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright .
docker compose -f docker-compose.prod.yml up -d --no-build两个 --build-arg 是给 NAS 出网环境用的国内镜像源(Dockerfile 的 ARG 定义,缺省为空、不影响 CI)。前端产物在 Mac 上 npm run build 后上传到 ./frontend/dist。ES 基础镜像选 8.15.3 还有一个运维考量(es-ik.Dockerfile 注释):与 NAS 共享 dev-es 同版本,基础镜像层已存在,免 1.3GB 拉取。
deploy/backup.sh 在 NAS 宿主上运行(Synology 任务计划或 crontab 每天一次),备份三样东西:① data/ 下的 schedules.json(cron 配置,"丢了就再也不会自动采集")、crawl_history.json、db.sqlite3 与 .env;② ES 快照——刻意走 _snapshot API 而非热拷数据目录(后者在 ES 运行时会备份出坏数据);③ 结束前自检备份目录非空,空则退出码非 0。脚本行为要点:目标目录必须显式给出(bash backup.sh <目标>),KEEP_DAYS 缺省 14 天,ES_CONTAINER 缺省 xsearch-es,支持 DRY_RUN=1 演练;用 case 守卫拒绝同盘备份("整卷丢失时一起没",注释还注明该 NAS 单盘 RAID1 无冗余,目标应落 USB 盘/云端/推给 Mac);备份目录 chmod 700;脚本自身不发通知——"备份失败往往伴随磁盘问题,本机写日志比外部依赖更可靠",退出码 1/2 供 cron 感知。
# NAS 上先演练再正式跑(Synology:控制面板 → 任务计划 → 用户自定义脚本,每日)
DRY_RUN=1 bash deploy/backup.sh /volume2/backup/xsearch
SRC_DIR=/volume1/docker/xsearch bash deploy/backup.sh /volume2/backup/xsearch >> logs/backup.log 2>&1恢复的顺序不能反(先起 ES 再灌状态文件):先把备份目录放回 /snapshots/xsearch,PUT /_snapshot/xsearch 注册仓库后对 bk_<时间戳> 快照执行 _restore(只恢复 quotes 索引);然后停后端 → 用备份覆盖 ./data/{schedules.json,crawl_history.json,db.sqlite3} 与 .env → 重起 backend;最后 curl -s localhost:5600/api/health/ 验证 200,并到"采集管理"页确认定时任务已恢复。完整命令见 README.md 的"备份与恢复"一节。
.github/workflows/ci.yml(workflow 名 CI)在 push 到 main、所有 pull_request 以及手动 workflow_dispatch 时触发;permissions: contents: read 收到最小权限。文件头注释点明定位:"最小 CI:把发布清单里靠人自觉的两条变成机器把关"——即后端测试全绿与前端类型检查/构建。测试不依赖 ES/Redis/SMTP(全部 mock),所以 CI 不需要起任何服务容器,两个 job 并行,总时长很短。
步骤依次为:actions/checkout@v4 → actions/setup-python@v5(Python 3.12,pip 缓存以 XSearch/requirements-lock.txt 为依赖键)→ 安装 requirements-lock.txt 与 ruff>=0.6(步骤注释:"与镜像构建用同一份锁定依赖:CI 通过 ≈ 镜像里也是这套版本")→ ruff check . → 在 XSearch/ 目录、DJANGO_DEBUG: 'True' 环境下执行 python manage.py test search --verbosity=2(测试态用开发密钥即可,生产 fail-closed 逻辑不受影响)。
步骤依次为:actions/checkout@v4 → actions/setup-node@v4(Node 20,npm 缓存以 frontend/package-lock.json 为依赖键)→ npm ci(注释提醒:lock 文件的 resolved 指向 npmmirror,若 CI 拉取失败应改用官方 registry 重锁)→ npm run build(等价于 vue-tsc --noEmit && vite build,类型检查不过即构建失败)→ 产物体积守门:取 dist/assets 下最大的 .js 文件,若不小于 1258291 字节(1.2MB)则失败,错误信息直指病根——"包体膨胀超过 1.2MB,检查是否又丢了路由懒加载"。
ruff.toml 只启用 select = ["E9", "F"]——E9 是语法/致命错误,F 是 pyflakes(未定义名、无用导入),line-length = 120,extend-exclude 排除 frontend/node_modules、frontend/dist、XSearch/static,per-file-ignores 给 Django 脚手架文件 XSearch/manage.py 与 XSearch/search/admin.py 豁免 F401。文件注释解释了这刻意收敛的选择:对一个 6.5k 行的既有代码库做批量风格改造,"风险(踩碎隐式契约、淹没真实 diff)远大于收益",要收紧再逐条加规则。也就是说,质量门禁的哲学是只挡真错误,不管风格。
cd XSearch
python manage.py test search以 search/tests.py 中 def test_ 计数为准:22 个测试类、110 个测试方法(README 正文写"85 条"为滞后数字,以源码为准)。全部 mock,不依赖 ES/Redis/SMTP,本机裸环境即可跑。覆盖面按 README 归纳:API 参数校验、CORS、管理员登录/登出/401、登录限速与解锁、采集接口鉴权、resume_job 路径穿越拦截、/api/health 真检依赖且不泄露错误细节、ES 异常降级、数据管理读写、cron 校验、正文清洗、robots 合规与指纹伪装域名边界、已移除模块不得回流、采集终态无轮询回写/僵尸 running 收尾/原子写与写失败如实返回/misfire 参数/调度事件可见化、告警模块(假 SMTP)。
前端没有单测框架,构建期质量由两道闸保证:npm run build 内嵌的 vue-tsc --noEmit 全量严格类型检查(tsconfig 开 strict 全套 + noUnusedLocals/noUnusedParameters),以及 CI 的 1.2MB 单 chunk 体积守门。
docker compose -f docker-compose.prod.yml ps——ES/Redis/backend 三个带 healthcheck 的服务应为 healthy。curl -s localhost:5600/api/health/ | head -c 200——200 表示 ES 与 Redis 都真通;503 说明至少一个依赖挂了(这正是 Docker HEALTHCHECK 打的端点)。curl -s localhost:5600/ 应返回版本与端点清单 JSON。curl -s https://你的域名/robots.txt 与 /sitemap.xml 必须返回 200 的正确内容类型,而不是被 SPA 兜底成 index.html;随后到 Google Search Console 提交 sitemap。news_rss(或 CLI docker compose exec backend scrapy crawl news_rss),观察状态/历史/日志尾部,再到搜索页与首页要闻确认新数据可见。python manage.py shell -c "from search.notify import notify; print(notify('selftest','告警通道自检'))"——收到邮件才算通。DRY_RUN=1 bash deploy/backup.sh <目标盘> 先看动作,再正式跑一次;并至少在空闲时完整走一遍恢复流程——"只备份不练恢复 = 没有备份"。README"发布清单"逐条核对:.env 三件套(DJANGO_DEBUG=False、DJANGO_ALLOWED_HOSTS=<域名>、随机 DJANGO_SECRET_KEY,缺一后端拒起);管理员改随机长口令(弱口令启动日志告警);ES 仅内网(切勿把 9200 暴露公网);依赖用 requirements-lock.txt 安装保证可复现;python manage.py test search 全绿再发布;frontend/dist 与后端同批发布(前端纯静态、无注入密钥);如需立即校正历史时区偏斜,对 crawled_at < 部署时刻 的文档跑一次 update_by_query 减 8 小时(先在"数据管理"页抽查);robots/sitemap 返回 200 后提交 Search Console;告警自检发一封;采集合规项复核(ROBOTSTXT_OBEY=True、默认 UA XSearchBot/1.0、只有确需渲染的域名才进 crawler/settings.py 的 FINGERPRINT_HOSTS)。
所有结论均来自对源码的逐文件分析,引用的文件路径相对 XSearch/ 仓库根目录。个别无法从源码确认的信息(frp 服务端与 VPS Nginx 的具体配置)以 ⚠️ 待确认 标注。