#!/bin/bash # 部署 search_suggestions 到远程服务器(在能 ssh 进服务器的机器上跑) # # 用法: bash scripts/deploy_search_suggestions.sh # # 假设: # - 你已经能 ssh news@207.57.129.228 免密登录 # - 远程路径 /srv/news 是部署根 # - docker compose 服务名: api / worker / frontend / postgres / redis set -euo pipefail REMOTE="news@207.57.129.228" APP_DIR="/srv/news" COMPOSE_DIR="$APP_DIR" log() { echo -e "\033[1;36m[$(date +'%H:%M:%S')]\033[0m $*"; } ok() { echo -e "\033[1;32m ✓\033[0m $*"; } err() { echo -e "\033[1;31m ✗\033[0m $*" >&2; } warn(){ echo -e "\033[1;33m !\033[0m $*"; } dc() { ssh "$REMOTE" "cd $APP_DIR && sg docker -c 'docker compose $*'"; } log "=== 1/9 拉代码 ===" ssh "$REMOTE" "cd $APP_DIR && git pull origin main" ok "git pull done" log "=== 2/9 跑 alembic 迁移 ===" dc exec -T api alembic upgrade head ok "alembic upgrade head" log "=== 3/9 验证 schema ===" dc exec -T postgres psql -U "\$POSTGRES_USER" -d "\$POSTGRES_DB" -c " SELECT table_name FROM information_schema.tables WHERE table_name IN ('search_keywords', 'search_title_suggestions') ORDER BY table_name; " # 预期: 2 行 log "=== 4/9 验证 trigger 挂上 ===" dc exec -T postgres psql -U "\$POSTGRES_USER" -d "\$POSTGRES_DB" -c " SELECT tgname, tgenabled FROM pg_trigger WHERE tgrelid = 'articles'::regclass AND NOT tgisinternal AND tgname LIKE 'trg_articles%'; " # 预期: trg_articles_rebuild_title_suggestions | O log "=== 5/9 重建 worker(让新加的 _refresh_search_keywords job 生效) ===" dc up -d --no-deps --force-recreate worker sleep 5 dc ps worker ok "worker recreated" log "=== 6/9 重建 api(让 search router 生效) ===" dc up -d --no-deps --force-recreate api sleep 5 dc ps api ok "api recreated" log "=== 7/9 重建 frontend(让 NAutoComplete 生效) ===" dc up -d --no-deps --build frontend sleep 10 dc ps frontend ok "frontend rebuilt" log "=== 8/9 回灌历史 articles ===" dc exec -T api python -m app.scripts.backfill_search_suggestions # 预期: backfill start: N ... backfill done: N rows in X.Xs ... refresh_search_keywords() done log "=== 9/9 端到端 API 测试 ===" # 读 owner 密码 OWNER_PASS=$(ssh "$REMOTE" "sudo cat /root/.owner_pass 2>/dev/null || echo ''") if [ -z "$OWNER_PASS" ]; then warn "找不到 /root/.owner_pass,改用交互式输入 owner 密码" read -s -p "owner password: " OWNER_PASS echo fi # 登录拿 token log " 登录拿 token..." TOKEN=$(curl -s --max-time 10 -X POST "http://127.0.0.1/api/v1/auth/login" \ -H "Content-Type: application/json" \ -d "{\"username\":\"owner\",\"password\":\"$OWNER_PASS\"}" \ | python -c "import sys,json; d=json.load(sys.stdin); print(d.get('access_token',''))") if [ -z "$TOKEN" ]; then err "登录失败,跳过 API 测试(检查密码)" exit 1 fi ok "got token (${#TOKEN} chars)" log " 测试 1: 空 q (应当 422 参数校验)" curl -s -o /dev/null -w " HTTP %{http_code}\n" \ "http://127.0.0.1/api/v1/search/suggestions" \ -H "Authorization: Bearer $TOKEN" log " 测试 2: 正常 q='美'" curl -s --max-time 10 "http://127.0.0.1/api/v1/search/suggestions?q=美&limit=10" \ -H "Authorization: Bearer $TOKEN" | python -m json.tool log " 测试 3: 正常 q='美联储'" curl -s --max-time 10 "http://127.0.0.1/api/v1/search/suggestions?q=%E7%BE%8E%E8%81%94%E5%82%A8&limit=10" \ -H "Authorization: Bearer $TOKEN" | python -m json.tool log " 测试 4: 长 prefix (>20 字符,应当 422)" curl -s -o /dev/null -w " HTTP %{http_code}\n" \ "http://127.0.0.1/api/v1/search/suggestions?q=this_is_a_very_long_prefix_x" \ -H "Authorization: Bearer $TOKEN" log " 测试 5: 不存在的字符 q='zzzzz' (应当返回空)" curl -s --max-time 10 "http://127.0.0.1/api/v1/search/suggestions?q=zzzzz&limit=10" \ -H "Authorization: Bearer $TOKEN" | python -m json.tool log " 测试 6: 未鉴权 (应当 401)" curl -s -o /dev/null -w " HTTP %{http_code}\n" \ "http://127.0.0.1/api/v1/search/suggestions?q=美" log "=== 10/10 worker 日志看 search_keywords 刷新 ===" dc logs worker --tail 50 2>&1 | grep -i "search_keywords" || warn "没找到 search_keywords 相关日志(可能要等 10s)" log "=== 11/11 trigger 实际工作: 拿最新文章看 search_title_suggestions ===" ARTICLE_ID=$(dc exec -T postgres psql -U "\$POSTGRES_USER" -d "\$POSTGRES_DB" -t -c \ "SELECT id FROM articles WHERE title_zh IS NOT NULL ORDER BY id DESC LIMIT 1;" \ | tr -d ' ' | tr -d '\r') echo " 最新带 title_zh 的文章 id: $ARTICLE_ID" if [ -n "$ARTICLE_ID" ]; then dc exec -T postgres psql -U "\$POSTGRES_USER" -d "\$POSTGRES_DB" -c " SELECT article_id, title_lang, array_length(prefix_keys, 1) AS prefix_count, published_at FROM search_title_suggestions WHERE article_id = $ARTICLE_ID; " # 预期: 一行,prefix_count 应该是标题字符数 fi log "=== 12/12 search_keywords 表有数据吗 ===" dc exec -T postgres psql -U "\$POSTGRES_USER" -d "\$POSTGRES_DB" -c \ "SELECT source, count(*) FROM search_keywords GROUP BY source;" # 预期: 1 行,ts_stat | N echo echo "================================================" echo -e "\033[1;32m 部署 + 测试完成!请把上面输出贴回去排查\033[0m" echo "================================================"