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:
Executable
+150
@@ -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 "$@"
|
||||||
@@ -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 "✅ 完成"
|
|
||||||
Reference in New Issue
Block a user