feat: 容器内统一运维脚本auvops.sh,recollect-daily并入

- 新增 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 <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-11 16:57:27 +08:00
co-authored by Claude
parent eb66ab02d0
commit b0dbeef3fd
2 changed files with 150 additions and 62 deletions
+150
View File
@@ -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'
# <python 代码>
# 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 "$@"
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env bash
# 服务器端脚本:清理指定交易日旧数据,并用新选股逻辑重新采集入库。
#
# 适用场景:核心股追踪选股逻辑更新后,服务器上当天数据仍是旧口径,
# 需要删掉重采(采集器幂等,不删会被 _has_collected 跳过)。
#
# 用法(在服务器上执行):
# ./recollect-daily.sh [日期] # 日期默认取服务器当天,格式 YYYY-MM-DD
# ./recollect-daily.sh 2026-08-10 # 显式指定交易日
#
# 依赖:docker 容器名为 auvdocker-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 "✅ 完成"