From b0dbeef3fdf7c0cd79ee67abd1953908c73fed9a Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:57:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=B9=E5=99=A8=E5=86=85=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E8=BF=90=E7=BB=B4=E8=84=9A=E6=9C=ACauvops.sh=EF=BC=8C?= =?UTF-8?q?recollect-daily=E5=B9=B6=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 backend/auvops.sh:容器内直接执行的统一运维工具 (cache-clear/clean-expired/cache-count/recollect/sh/help) - heredoc传python代码,日期等参数走环境变量防注入 - 后续运维操作收敛到该文件,加 cmd_xxx + case 注册一行即可 - 删除 recollect-daily.sh,其能力并入 auvops.sh recollect Co-Authored-By: Claude --- backend/auvops.sh | 150 +++++++++++++++++++++++++++++++++++++++++++++ recollect-daily.sh | 62 ------------------- 2 files changed, 150 insertions(+), 62 deletions(-) create mode 100755 backend/auvops.sh delete mode 100755 recollect-daily.sh diff --git a/backend/auvops.sh b/backend/auvops.sh new file mode 100755 index 0000000..a0be088 --- /dev/null +++ b/backend/auvops.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# auvops.sh - AUV 容器内运维工具(在容器内直接执行,非宿主机 docker exec) +# +# 拷贝进容器 /app(backend 根)后运行。后续新增运维能力都收敛到这个文件: +# 加一个 cmd_xxx 函数 + 在 main 的 case 里注册一行即可。 +# +# 用法(容器内): +# ./auvops.sh cache-clear # 清空全部缓存(当日数据全新) +# ./auvops.sh clean-expired # 只清理已过期的缓存 +# ./auvops.sh cache-count # 查看缓存条数 +# ./auvops.sh recollect [DATE] # 删除并重采指定交易日(默认当天) +# ./auvops.sh sh # 进入交互式 shell +# ./auvops.sh help # 查看帮助 +# +# 依赖: 容器内 python 可用(能 import services.*),工作目录自动切到脚本所在目录。 +# +# 示例: +# ./auvops.sh cache-clear +# ./auvops.sh recollect 2026-08-10 +# ./auvops.sh cache-count + +set -euo pipefail + +# 容器内 backend 根:脚本所在目录(/app) +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${ROOT}" + +# 容器内用 python;本机验证时可 PY=./venv/bin/python ./auvops.sh ... +PY="${PY:-python}" +"${PY}" -c "import services" >/dev/null 2>&1 || { + echo "❌ 无法在 ${ROOT} 下 import services(确认已在容器内 /app 且 python 可用)" >&2 + exit 1 +} + +# 在容器内跑 python 代码。日期等参数通过环境变量传,避免拼接进代码字符串。 +# 用法: py_env "KEY1=val1" "KEY2=val2" <<'EOF' +# +# EOF +py_env() { + local env_args=() + while [[ "$#" -gt 0 ]]; do + env_args+=("${1%%=*}=${1#*=}"); shift + done + env "${env_args[@]}" "${PY}" - +} + +# ---- 子命令实现 ---- + +# 清空全部缓存(当日数据全新) +cmd_cache_clear() { + echo "① 清空全部缓存" + "${PY}" - <<'EOF' +from services.cache import clear_all +clear_all() +print(" cache 表已清空") +EOF + echo "✅ 缓存已清空" +} + +# 只清理已过期缓存 +cmd_clean_expired() { + echo "① 清理过期缓存" + "${PY}" - <<'EOF' +from services.cache import clean_expired +clean_expired() +print(" 过期缓存已清理") +EOF + echo "✅ 完成" +} + +# 查看缓存条数 +cmd_cache_count() { + "${PY}" - <<'EOF' +from database import get_connection +conn = get_connection() +try: + n = conn.execute("SELECT COUNT(*) FROM cache").fetchone()[0] + print(f"缓存条数: {n}") +finally: + conn.close() +EOF +} + +# 删除指定交易日旧数据,并用新选股逻辑重新采集入库 +cmd_recollect() { + local date="${1:-$(date +%F)}" + if ! [[ "${date}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + echo "❌ 日期格式错误:${date}(应为 YYYY-MM-DD)" >&2 + exit 1 + fi + + echo "================================================" + echo "🚀 容器: ${HOSTNAME:-unknown} 交易日: ${date}" + echo "================================================" + + echo "① 删除 ${date} 旧数据" + py_env "TARGET_DATE=${date}" <<'EOF' +import os +from database import get_connection +date = os.environ["TARGET_DATE"] +conn = get_connection() +try: + for tbl in ("daily_core_stocks", "daily_core_stock_themes", "daily_top_themes"): + cur = conn.execute("DELETE FROM " + tbl + " WHERE trade_date = ?", (date,)) + print(f" {tbl}: 删除 {cur.rowcount} 行") + conn.commit() +finally: + conn.close() +EOF + + echo "② 用新选股逻辑重采 ${date}" + py_env "TARGET_DATE=${date}" <<'EOF' +import asyncio +import os +from services.daily_collector import collect_daily +result = asyncio.run(collect_daily(os.environ["TARGET_DATE"])) +print(" 结果:", result) +EOF + echo "✅ 完成" +} + +# 进入交互式 shell +cmd_sh() { + "${PY}" || true +} + +# 帮助 +usage() { + awk 'NR >= 2 && /^#/ { sub(/^# ?/, ""); print; next } NR >= 2 && !/^#/ { exit }' "${BASH_SOURCE[0]}" +} + +# ---- 入口 ---- +main() { + if [[ "$#" -eq 0 ]]; then + usage + exit 1 + fi + local cmd="$1"; shift + case "${cmd}" in + cache-clear|cc) cmd_cache_clear "$@" ;; + clean-expired) cmd_clean_expired "$@" ;; + cache-count) cmd_cache_count "$@" ;; + recollect) cmd_recollect "$@" ;; + sh|shell|python) cmd_sh "$@" ;; + help|-h|--help) usage ;; + *) echo "❌ 未知命令: ${cmd}(./auvops.sh help 查看用法)" >&2; exit 1 ;; + esac +} + +main "$@" diff --git a/recollect-daily.sh b/recollect-daily.sh deleted file mode 100755 index e95d4e4..0000000 --- a/recollect-daily.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# 服务器端脚本:清理指定交易日旧数据,并用新选股逻辑重新采集入库。 -# -# 适用场景:核心股追踪选股逻辑更新后,服务器上当天数据仍是旧口径, -# 需要删掉重采(采集器幂等,不删会被 _has_collected 跳过)。 -# -# 用法(在服务器上执行): -# ./recollect-daily.sh [日期] # 日期默认取服务器当天,格式 YYYY-MM-DD -# ./recollect-daily.sh 2026-08-10 # 显式指定交易日 -# -# 依赖:docker 容器名为 auv(docker-compose.yml 中 container_name) - -set -euo pipefail - -CONTAINER="${CONTAINER:-auv}" -DATE="${1:-$(date +%F)}" - -# 校验日期格式,防止注入/拼错 -if ! [[ "${DATE}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then - echo "❌ 日期格式错误:${DATE}(应为 YYYY-MM-DD)" - exit 1 -fi - -echo "================================================" -echo "🚀 容器: ${CONTAINER} 交易日: ${DATE}" -echo "================================================" - -# 确认容器在运行 -if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then - echo "❌ 容器 ${CONTAINER} 未在运行" - exit 1 -fi - -echo "" -echo "① 删除 ${DATE} 旧数据" -# 通过环境变量传日期,避免日期值混入 python -c 的字符串拼接 -docker exec -e TARGET_DATE="${DATE}" "${CONTAINER}" python -c " -import os -from database import get_connection -date = os.environ['TARGET_DATE'] -conn = get_connection() -try: - for tbl in ('daily_core_stocks', 'daily_core_stock_themes', 'daily_top_themes'): - cur = conn.execute('DELETE FROM ' + tbl + ' WHERE trade_date = ?', (date,)) - print(f' {tbl}: 删除 {cur.rowcount} 行') - conn.commit() -finally: - conn.close() -" - -echo "" -echo "② 用新选股逻辑重采 ${DATE}" -docker exec -e TARGET_DATE="${DATE}" "${CONTAINER}" python -c " -import asyncio -import os -from services.daily_collector import collect_daily -result = asyncio.run(collect_daily(os.environ['TARGET_DATE'])) -print(' 结果:', result) -" - -echo "" -echo "✅ 完成"