官网下载 APK 直链:强制下载头 + 发布脚本
落地页「官网下载」指向 /media/shaguabijia.apk,但该文件靠手动 cp、且 StaticFiles 给 .apk 的 Content-Type 不对又无 attachment 头,部分国产浏览器不触发下载转甩应用市场。新增 download_apk 路由(挂在静态目录之前),显式回 application/vnd.android.package-archive + Content-Disposition:attachment 强制下载,文件缺失回 404。新增 scripts/publish_apk.sh 把「编 release 包 + 放到 data/media/shaguabijia.apk」固化成一条命令。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+35
-10
@@ -2,18 +2,23 @@
|
|||||||
|
|
||||||
通过 `uvicorn app.main:app --reload` 启动。
|
通过 `uvicorn app.main:app --reload` 启动。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
|
from app.api.internal.app_version import router as internal_app_version_router
|
||||||
|
from app.api.internal.launch_confirm import router as internal_launch_confirm_router
|
||||||
|
from app.api.internal.price import router as internal_price_router
|
||||||
|
from app.api.internal.store import router as internal_store_router
|
||||||
from app.api.v1.ad import router as ad_router
|
from app.api.v1.ad import router as ad_router
|
||||||
from app.api.v1.analytics import router as analytics_router
|
from app.api.v1.analytics import router as analytics_router
|
||||||
from app.api.v1.auth import router as auth_router
|
from app.api.v1.auth import router as auth_router
|
||||||
@@ -21,12 +26,8 @@ from app.api.v1.compare import router as compare_router
|
|||||||
from app.api.v1.compare_milestone import router as compare_milestone_router
|
from app.api.v1.compare_milestone import router as compare_milestone_router
|
||||||
from app.api.v1.compare_record import router as compare_record_router
|
from app.api.v1.compare_record import router as compare_record_router
|
||||||
from app.api.v1.coupon import router as coupon_router
|
from app.api.v1.coupon import router as coupon_router
|
||||||
from app.api.v1.device import router as device_router
|
|
||||||
from app.api.v1.cps_redirect import router as cps_redirect_router
|
from app.api.v1.cps_redirect import router as cps_redirect_router
|
||||||
from app.api.internal.app_version import router as internal_app_version_router
|
from app.api.v1.device import router as device_router
|
||||||
from app.api.internal.launch_confirm import router as internal_launch_confirm_router
|
|
||||||
from app.api.internal.price import router as internal_price_router
|
|
||||||
from app.api.internal.store import router as internal_store_router
|
|
||||||
from app.api.v1.feedback import router as feedback_router
|
from app.api.v1.feedback import router as feedback_router
|
||||||
from app.api.v1.invite import router as invite_router
|
from app.api.v1.invite import router as invite_router
|
||||||
from app.api.v1.meituan import router as meituan_router
|
from app.api.v1.meituan import router as meituan_router
|
||||||
@@ -40,14 +41,14 @@ from app.api.v1.user import router as user_router
|
|||||||
from app.api.v1.wallet import router as wallet_router
|
from app.api.v1.wallet import router as wallet_router
|
||||||
from app.api.v1.wxpay import router as wxpay_router
|
from app.api.v1.wxpay import router as wxpay_router
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.heartbeat_monitor_worker import (
|
|
||||||
start_heartbeat_monitor,
|
|
||||||
stop_heartbeat_monitor,
|
|
||||||
)
|
|
||||||
from app.core.daily_exchange_worker import (
|
from app.core.daily_exchange_worker import (
|
||||||
start_daily_exchange_worker,
|
start_daily_exchange_worker,
|
||||||
stop_daily_exchange_worker,
|
stop_daily_exchange_worker,
|
||||||
)
|
)
|
||||||
|
from app.core.heartbeat_monitor_worker import (
|
||||||
|
start_heartbeat_monitor,
|
||||||
|
stop_heartbeat_monitor,
|
||||||
|
)
|
||||||
from app.core.logging import setup_logging
|
from app.core.logging import setup_logging
|
||||||
from app.core.pricebot_client import aclose_pricebot_client, get_pricebot_client
|
from app.core.pricebot_client import aclose_pricebot_client, get_pricebot_client
|
||||||
from app.core.withdraw_reconcile_worker import (
|
from app.core.withdraw_reconcile_worker import (
|
||||||
@@ -137,6 +138,30 @@ app.include_router(cps_redirect_router)
|
|||||||
# 用户上传文件(头像)静态服务。生产可改由 nginx 直接 serve MEDIA_ROOT。
|
# 用户上传文件(头像)静态服务。生产可改由 nginx 直接 serve MEDIA_ROOT。
|
||||||
_media_root = Path(settings.MEDIA_ROOT)
|
_media_root = Path(settings.MEDIA_ROOT)
|
||||||
_media_root.mkdir(parents=True, exist_ok=True)
|
_media_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
# 官网下载的 APK 直链(落地页 dl.html「官网下载」按钮指向 /media/shaguabijia.apk)。
|
||||||
|
# 必须在 StaticFiles 挂载【之前】注册,否则被静态挂载吃掉。
|
||||||
|
# StaticFiles 给 .apk 的 Content-Type 不对、且无 attachment 头 → 部分国产浏览器不触发下载、转甩应用市场;
|
||||||
|
# 这里显式回 application/vnd.android.package-archive + Content-Disposition:attachment 强制浏览器下载。
|
||||||
|
# 文件由 scripts/publish_apk.sh 编 release 包后放到 data/media/shaguabijia.apk(*.apk 不入 git,需部署时放)。
|
||||||
|
_APK_PATH = _media_root / "shaguabijia.apk"
|
||||||
|
|
||||||
|
|
||||||
|
@app.get(f"{settings.MEDIA_URL_PREFIX}/shaguabijia.apk", tags=["meta"], include_in_schema=False)
|
||||||
|
def download_apk() -> FileResponse:
|
||||||
|
if not _APK_PATH.is_file():
|
||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
raise HTTPException(status_code=404, detail="安装包未就绪")
|
||||||
|
return FileResponse(
|
||||||
|
_APK_PATH,
|
||||||
|
media_type="application/vnd.android.package-archive",
|
||||||
|
filename="shaguabijia.apk",
|
||||||
|
headers={"Content-Disposition": 'attachment; filename="shaguabijia.apk"'},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
app.mount(
|
app.mount(
|
||||||
settings.MEDIA_URL_PREFIX,
|
settings.MEDIA_URL_PREFIX,
|
||||||
StaticFiles(directory=str(_media_root)),
|
StaticFiles(directory=str(_media_root)),
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 编 release 正式包并发布到官网下载位 data/media/shaguabijia.apk。
|
||||||
|
#
|
||||||
|
# 背景:落地页 dl.html「官网下载」按钮指向 /media/shaguabijia.apk(见 app/main.py download_apk 路由)。
|
||||||
|
# 该 apk 不入 git(.gitignore 忽略 *.apk),也没有 CI 自动放 —— 以前全靠手动 cp,常忘/放成 debug 包。
|
||||||
|
# 本脚本把"编 release + 放到位"固化成一条命令。发版时跑一次即可。
|
||||||
|
#
|
||||||
|
# 用法: bash scripts/publish_apk.sh
|
||||||
|
# 产物: <app-server>/data/media/shaguabijia.apk(release 签名、指向生产后端的正式包)
|
||||||
|
#
|
||||||
|
# 注意:① 需要 app/jishisongfu-release.jks 存在(release 签名,仅 CTO 持有);缺失会编译失败。
|
||||||
|
# ② 服务器部署不在本脚本职责内 —— 它只把 apk 放到本仓 data/media/;上线由部署流程把
|
||||||
|
# data/media/ 同步到生产(或 nginx serve 同目录)。
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 路径全部相对脚本位置算,跟 CWD 无关。
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SERVER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
ANDROID_DIR="$(cd "$SERVER_DIR/../shaguabijia-app-android" && pwd)"
|
||||||
|
DEST="$SERVER_DIR/data/media/shaguabijia.apk"
|
||||||
|
APK_OUT="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk"
|
||||||
|
|
||||||
|
# Android Studio 自带 JBR(本机构建环境约定)。已设 JAVA_HOME 则尊重现有值。
|
||||||
|
export JAVA_HOME="${JAVA_HOME:-D:/android-studio/jbr}"
|
||||||
|
|
||||||
|
echo "[publish_apk] Android 工程: $ANDROID_DIR"
|
||||||
|
if [ ! -f "$ANDROID_DIR/app/jishisongfu-release.jks" ]; then
|
||||||
|
echo "[publish_apk] ✗ 缺 app/jishisongfu-release.jks(release 签名),无法出正式包。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[publish_apk] 编 release 包(assembleRelease,R8+release 签名,BASE_URL=生产)…"
|
||||||
|
( cd "$ANDROID_DIR" && ./gradlew :app:assembleRelease )
|
||||||
|
|
||||||
|
if [ ! -f "$APK_OUT" ]; then
|
||||||
|
echo "[publish_apk] ✗ 没找到产物 $APK_OUT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$DEST")"
|
||||||
|
cp "$APK_OUT" "$DEST"
|
||||||
|
SIZE_MB=$(( $(wc -c < "$DEST") / 1024 / 1024 ))
|
||||||
|
echo "[publish_apk] ✓ 已发布 → $DEST (${SIZE_MB}MB)"
|
||||||
|
echo "[publish_apk] 官网下载链接(生产):https://app-api.shaguabijia.com/media/shaguabijia.apk"
|
||||||
|
echo "[publish_apk] 部署:把 data/media/ 同步到生产服务器(本脚本不负责上线)。"
|
||||||
Reference in New Issue
Block a user