From 1890a8380b0e6e72c57f766517caacf5fc579e2b Mon Sep 17 00:00:00 2001 From: houhuan Date: Tue, 9 Dec 2025 13:59:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.py | 80 +++++++++++++++++++++++++++++++++------------- docker-compose.yml | 1 + scripts/deploy.ps1 | 25 +++++++++++++++ scripts/deploy.sh | 25 +++++++++++++++ 4 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 scripts/deploy.ps1 create mode 100644 scripts/deploy.sh diff --git a/backend/app.py b/backend/app.py index d6ab73d..977b880 100644 --- a/backend/app.py +++ b/backend/app.py @@ -24,25 +24,26 @@ app = Flask(__name__, static_folder="../frontend", static_url_path="/static") CORS(app) base_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -default_db_path = os.path.join(base_dir, "data", "data.db") -if os.name == 'nt': - default_db_url = f"sqlite:///{default_db_path}" +db_path = os.path.join(base_dir, "data", "data.db") +db_url_env = os.getenv('DATABASE_URL') +if db_url_env: + app.config['SQLALCHEMY_DATABASE_URI'] = db_url_env else: - default_db_url = f"sqlite:////{default_db_path}" -db_url = os.getenv('DATABASE_URL') or default_db_url -app.config['SQLALCHEMY_DATABASE_URI'] = db_url + if os.name == 'nt': + app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{db_path}" + else: + app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:////{db_path}" -def _ensure_sqlite_dir(url: str): - if not isinstance(url, str) or not url.startswith('sqlite'): - return - p = url.replace('sqlite:////', '/').replace('sqlite:///', '') - d = os.path.dirname(p) - if d and not os.path.exists(d): +def _ensure_sqlite_dir(): + d = os.path.dirname(db_path) + if not os.path.exists(d): os.makedirs(d, exist_ok=True) -_ensure_sqlite_dir(db_url) +_ensure_sqlite_dir() db = SQLAlchemy(app) +scheduler = None + class DailyRevenue(db.Model): __tablename__ = 'daily_revenue' id = db.Column(db.Integer, primary_key=True) @@ -106,15 +107,16 @@ def daily_job(target_date=None): if target_date is None: target_date = datetime.now().date() - existing = DailyRevenue.query.filter_by(date=target_date).first() - if existing: - if not existing.is_final: - existing.is_final = True - existing.source = existing.source or 'generator' - db.session.commit() - # 补推消息 - push_feishu(target_date.isoformat(), existing.amount, "daily_finalize") - return + existing = DailyRevenue.query.filter_by(date=target_date).first() + if existing: + if not existing.is_final: + existing.is_final = True + existing.source = existing.source or 'generator' + db.session.commit() + push_feishu(target_date.isoformat(), existing.amount, "daily_finalize") + else: + push_feishu(target_date.isoformat(), existing.amount, "daily_exists") + return amount = gen_amount_for_date(target_date, cfg) rev = DailyRevenue(date=target_date, amount=amount, is_final=True, source='generator') @@ -328,6 +330,38 @@ def api_series7(): cur += timedelta(days=1) return jsonify(series) +@app.route('/api/admin/reload_cutoff', methods=['POST']) +def admin_reload_cutoff(): + token = os.getenv('ADMIN_TOKEN') + if token and request.headers.get('X-Admin-Token') != token: + return jsonify({"error": "unauthorized"}), 401 + cfg = load_config() + ct = cfg.get("cutoff_time") + ch = cfg.get("cutoff_hour", 23) + cm = 0 + if isinstance(ct, str) and re.match(r'^\d{1,2}:\d{2}$', ct): + try: + p = ct.split(':') + ch = int(p[0]); cm = int(p[1]) + except Exception: + pass + try: + ch = int(ch) + except Exception: + ch = 23 + if ch < 0 or ch > 23: + ch = 23 + if cm < 0 or cm > 59: + cm = 0 + global scheduler + if scheduler: + try: + scheduler.add_job(daily_job, "cron", hour=ch, minute=cm, id="daily", replace_existing=True) + except Exception: + pass + settle_today_if_due() + return jsonify({"ok": True, "cutoff_time": f"{ch:02d}:{cm:02d}"}) + @app.route("/api/revenue") def api_revenue(): """查询历史营业额""" @@ -508,7 +542,7 @@ if __name__ == "__main__": ch = 23 if cm < 0 or cm > 59: cm = 0 - scheduler.add_job(daily_job, "cron", hour=ch, minute=cm) + scheduler.add_job(daily_job, "cron", hour=ch, minute=cm, id="daily", replace_existing=True) scheduler.start() with app.app_context(): sync_log_to_db() diff --git a/docker-compose.yml b/docker-compose.yml index e2769cb..31ba061 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,4 +12,5 @@ services: - AUTO_IMPORT_ON_START=1 volumes: - ./data:/app/data:Z + - ./config.json:/app/config.json:ro restart: unless-stopped diff --git a/scripts/deploy.ps1 b/scripts/deploy.ps1 new file mode 100644 index 0000000..a68a679 --- /dev/null +++ b/scripts/deploy.ps1 @@ -0,0 +1,25 @@ +param( + [string]$RepoUrl = "", + [string]$Branch = "main", + [string]$RepoDir = ".", + [string]$ComposeFile = "docker-compose.yml", + [string]$BaseUrl = "http://localhost:57778", + [string]$AdminToken = "" +) +if (!(Test-Path $RepoDir)) { New-Item -ItemType Directory -Path $RepoDir | Out-Null } +if (!(Test-Path (Join-Path $RepoDir ".git"))) { + if ($RepoUrl -eq "") { throw "RepoUrl is required for clone" } + git clone -b $Branch $RepoUrl $RepoDir +} +Set-Location $RepoDir +git remote set-url origin $RepoUrl +git fetch origin +git checkout $Branch +git pull origin $Branch +if (!(Test-Path $ComposeFile)) { throw "compose not found: $ComposeFile" } +docker compose -f $ComposeFile up -d --build +$headers = @{} +if ($AdminToken -ne "") { $headers["X-Admin-Token"] = $AdminToken } +try { Invoke-RestMethod -Method Post -Uri "$BaseUrl/api/admin/reload_cutoff" -Headers $headers | Out-Null } catch { } +try { Invoke-RestMethod -Method Get -Uri "$BaseUrl/api/metrics" | ConvertTo-Json -Depth 3 | Write-Output } catch { } +try { Invoke-RestMethod -Method Post -Uri "$BaseUrl/api/admin/test_push" -Headers $headers | ConvertTo-Json -Depth 3 | Write-Output } catch { } diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..1322c4e --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail +REPO_URL=${1:-} +BRANCH=${2:-main} +REPO_DIR=${3:-.} +COMPOSE_FILE=${4:-docker-compose.yml} +BASE_URL=${5:-http://localhost:57778} +ADMIN_TOKEN=${6:-} +mkdir -p "$REPO_DIR" +if [ ! -d "$REPO_DIR/.git" ]; then + [ -z "$REPO_URL" ] && { echo "RepoUrl required"; exit 1; } + git clone -b "$BRANCH" "$REPO_URL" "$REPO_DIR" +fi +cd "$REPO_DIR" +git remote set-url origin "$REPO_URL" +git fetch origin +git checkout "$BRANCH" +git pull origin "$BRANCH" +[ -f "$COMPOSE_FILE" ] || { echo "compose not found: $COMPOSE_FILE"; exit 1; } +docker compose -f "$COMPOSE_FILE" up -d --build +HDR=( ) +if [ -n "$ADMIN_TOKEN" ]; then HDR=( -H "X-Admin-Token: $ADMIN_TOKEN" ); fi +curl -s -X POST "${BASE_URL}/api/admin/reload_cutoff" "${HDR[@]}" || true +curl -s "${BASE_URL}/api/metrics" || true +curl -s -X POST "${BASE_URL}/api/admin/test_push" "${HDR[@]}" || true