fix(dl): 落地页底图+logo 纳入 git → 修生产 /media 404 毛坯 (#97)
## 背景 用户反馈浏览器落地页仍是「毛坯」(裸黄底、缺优惠券卡/价格卡/logo)。 ## 真因(实测线上) `https://app-api.shaguabijia.com/media/dl.html` 的 **HTML 早已是完善版且已部署**,但它引用的两张静态图被 `.gitignore` 的 `data/media/*` 规则挡在库外、从未随部署进生产 `/media`: | 资源 | 线上 | 本地/git | |---|---|---| | `/media/coupon-page-bg.png`(底图:优惠券卡+跨平台比价价格卡) | **404**(22B `{"detail":"Not Found"}`) | 之前被 gitignore | | `/media/sb-brand.png`(logo) | **404** | 之前被 gitignore | | `/media/shaguabijia.apk`(对照) | 200 ✓ | — | → 两图加载失败 → 页面回退裸黄底 = 毛坯。 ## 改动 照 `dl.html` / `taobao_landing.jpg` 既有白名单写法,把这两张图加进 `.gitignore` 例外并入库。 - `.gitignore`:`!data/media/coupon-page-bg.png`、`!data/media/sb-brand.png` - 入库两图,md5 与原型一致(coupon-page-bg `f2adf28a` / sb-brand `e5b6e004`) **无代码/HTML 改动**(dl.html 本就对齐原型,见上一 commit `6d4cbc8`)。 ## 部署后生效 合并并部署本分支后,生产 `/media/` 即含这两图,落地页恢复完整。对应 6-29 UI 对齐 #10。 --------- Co-authored-by: no_gen_mu <liujianhishen@gmail.com> Reviewed-on: #97 Co-authored-by: liujiahui <liujiahui@wonderable.ai> Co-committed-by: liujiahui <liujiahui@wonderable.ai>
This commit was merged in pull request #97.
This commit is contained in:
+5
-2
@@ -23,14 +23,17 @@ dist/
|
||||
*.db-journal
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
# data/ 整体不入库(运行时数据/上传文件/大二进制)。例外:邀请落地页 dl.html——
|
||||
# 它既是生产落地页又是本地测试资产,纳入 git 便于同事一致测试
|
||||
# data/ 整体不入库(运行时数据/上传文件/大二进制)。例外:邀请落地页 dl.html 及其
|
||||
# 引用的静态插画(coupon-page-bg.png 底图 + sb-brand.png logo)——既是生产落地页资产
|
||||
# 又是本地测试资产,纳入 git 便于同事一致测试、且随部署进生产 /media(否则线上 404→落地页毛坯)。
|
||||
# (见 docs/邀请功能-实现原理与本地测试.md)。其余(avatars/ / *.apk / app.db 等)仍忽略。
|
||||
data/*
|
||||
!data/media/
|
||||
data/media/*
|
||||
!data/media/dl.html
|
||||
!data/media/taobao_landing.jpg
|
||||
!data/media/coupon-page-bg.png
|
||||
!data/media/sb-brand.png
|
||||
|
||||
secrets/*
|
||||
!secrets/.gitkeep
|
||||
|
||||
@@ -202,6 +202,9 @@ def withdraw(req: WithdrawRequest, user: CurrentUser, db: DbSession) -> Withdraw
|
||||
order = crud_wallet.create_withdraw(
|
||||
db, user.id, req.amount_cents, source=req.source,
|
||||
user_name=req.user_name, out_bill_no=req.out_bill_no,
|
||||
# 0.01 元调试提现:放行低于最低额的小额。双闸——客户端仅 debug 包在「0.01 元提现」开关开时
|
||||
# 连同 skip_review 一起下发;服务端仅非 prod 才认。生产恒 False,最低额校验照常。
|
||||
allow_sub_min=(req.skip_review and not settings.is_prod),
|
||||
)
|
||||
except crud_wallet.InvalidWithdrawAmountError as e:
|
||||
raise HTTPException(
|
||||
|
||||
+35
-10
@@ -2,18 +2,23 @@
|
||||
|
||||
通过 `uvicorn app.main:app --reload` 启动。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
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.analytics import router as analytics_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_record import router as compare_record_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.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.device import router as device_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.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.wxpay import router as wxpay_router
|
||||
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 (
|
||||
start_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.pricebot_client import aclose_pricebot_client, get_pricebot_client
|
||||
from app.core.withdraw_reconcile_worker import (
|
||||
@@ -137,6 +138,30 @@ app.include_router(cps_redirect_router)
|
||||
# 用户上传文件(头像)静态服务。生产可改由 nginx 直接 serve MEDIA_ROOT。
|
||||
_media_root = Path(settings.MEDIA_ROOT)
|
||||
_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(
|
||||
settings.MEDIA_URL_PREFIX,
|
||||
StaticFiles(directory=str(_media_root)),
|
||||
|
||||
@@ -216,13 +216,21 @@ def try_reward_on_compare(db: Session, invitee_user_id: int) -> CompareRewardRes
|
||||
def get_stats(db: Session, inviter_id: int) -> tuple[int, int]:
|
||||
"""返回 (已成功邀请人数, 累计从邀请获得的金币)。
|
||||
|
||||
"已邀请好友数"口径 = 完成一次比价(已触发邀请奖励金)的被邀请人数,即 compare_reward_granted=True。
|
||||
不再数"仅绑定未比价"的关系——否则会出现"已邀请 1、可提现余额 0"(好友下载登录但没比价),
|
||||
与产品口径"已邀请好友数 × 2元 = 累计提现 + 可提现余额"对不上。过滤后该恒等式天然成立
|
||||
(每个计入的好友都恰好发过 1 笔 2 元,钱要么在余额要么已提现)。
|
||||
|
||||
金币口径(inviter_coin 之和)自 v3 起恒 0(邀请人收益改走邀请奖励金,见 get_reward_stats /
|
||||
try_reward_on_compare);保留返回位兼容旧响应字段 coins_earned。
|
||||
"""
|
||||
count = db.execute(
|
||||
select(func.count())
|
||||
.select_from(InviteRelation)
|
||||
.where(InviteRelation.inviter_user_id == inviter_id)
|
||||
.where(
|
||||
InviteRelation.inviter_user_id == inviter_id,
|
||||
InviteRelation.compare_reward_granted.is_(True),
|
||||
)
|
||||
).scalar_one()
|
||||
coins = db.execute(
|
||||
select(func.coalesce(func.sum(InviteRelation.inviter_coin), 0))
|
||||
|
||||
@@ -610,6 +610,7 @@ def create_withdraw(
|
||||
source: str = "coin_cash",
|
||||
user_name: str | None = None,
|
||||
out_bill_no: str | None = None,
|
||||
allow_sub_min: bool = False,
|
||||
) -> WithdrawOrder:
|
||||
"""发起提现:原子扣款 + 建单 reviewing(待人工审核),**不打款**。
|
||||
|
||||
@@ -619,8 +620,13 @@ def create_withdraw(
|
||||
重复发起多笔提现(审核拒绝再退回)。
|
||||
#2 out_bill_no 客户端幂等键:同号重试返回该单现状(reviewing 等审核),不重复扣款建单。
|
||||
实名 user_name 在此存下(WithdrawOrder.user_name),供异步审核打款时传给微信(达额需实名)。
|
||||
|
||||
allow_sub_min:放行低于"提现最低额"的小额(用于 0.01 元调试提现)。仅由 endpoint 在
|
||||
`skip_review and not is_prod`(debug 包 + 非生产双闸)时置 True;仍受 schema gt=0 与 max 上限约束。
|
||||
生产恒为 False → 最低额校验照常,绝不可能提 0.01。
|
||||
"""
|
||||
if amount_cents < rewards.get_withdraw_min_cents(db) or amount_cents > rewards.get_withdraw_max_cents(db):
|
||||
min_c = 0 if allow_sub_min else rewards.get_withdraw_min_cents(db)
|
||||
if amount_cents < min_c or amount_cents > rewards.get_withdraw_max_cents(db):
|
||||
raise InvalidWithdrawAmountError
|
||||
|
||||
# 提现即要求已绑微信:否则审核通过也打不了款,提前拦更友好
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 847 KiB |
+13
-4
@@ -198,7 +198,7 @@
|
||||
选择「<span class="guide-highlight">在浏览器打开</span>」
|
||||
<span class="guide-final">在浏览器里按提示<span class="guide-target">去应用商店下载</span></span>
|
||||
</h2>
|
||||
<div class="guide-dismiss" id="wxGuideDismiss">我知道了 ✕</div>
|
||||
<!-- <div class="guide-dismiss" id="wxGuideDismiss">我知道了 ✕</div> -->
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||||
@@ -263,7 +263,7 @@
|
||||
var wxGuide = document.getElementById("wxGuide");
|
||||
function showWxGuide() { wxGuide.classList.add("show"); }
|
||||
function hideWxGuide() { wxGuide.classList.remove("show"); }
|
||||
document.getElementById("wxGuideDismiss").addEventListener("click", hideWxGuide);
|
||||
// document.getElementById("wxGuideDismiss").addEventListener("click", hideWxGuide); // 「我知道了 ✕」已注释隐藏
|
||||
if (isWeChat) showWxGuide(); // 微信里一进页面就提示去浏览器(微信内下载必被拦)
|
||||
|
||||
function showToast(text) {
|
||||
@@ -273,15 +273,24 @@
|
||||
showToast.timer = setTimeout(function () { toast.classList.remove("show"); }, 1400);
|
||||
}
|
||||
|
||||
// ===== 下载按钮:微信内引导去浏览器;iOS 提示;安卓写邀请码 + 跳应用商店 =====
|
||||
// ===== 应用商店下载:微信内引导去浏览器;iOS 提示;安卓写邀请码 + 跳应用商店 =====
|
||||
function handleDownload() {
|
||||
if (isWeChat) { showWxGuide(); return; }
|
||||
if (isIOS) { alert("iOS 版即将上线,请前往 App Store 搜索「傻瓜比价」"); return; }
|
||||
copyInviteCode(); // 先把邀请码写进剪贴板(供 App 首启归因),链路丢了还有 landing-track 指纹兜底
|
||||
openStore();
|
||||
}
|
||||
// ===== 官网下载:微信/iOS 同上引导;安卓直接跳 APK 直链 → 弹系统下载弹窗 =====
|
||||
// APK_URL 跟随页面 host:本地走 LAN、生产走 app-api.shaguabijia.com(见邀请功能文档约定)。
|
||||
var APK_URL = location.origin + "/media/shaguabijia.apk";
|
||||
function handleWebsiteDownload() {
|
||||
if (isWeChat) { showWxGuide(); return; }
|
||||
if (isIOS) { alert("iOS 版即将上线,请前往 App Store 搜索「傻瓜比价」"); return; }
|
||||
copyInviteCode(); // 同样先写邀请码进剪贴板(供 App 首启归因)
|
||||
window.location.href = APK_URL;
|
||||
}
|
||||
document.getElementById("dlbtn").addEventListener("click", handleDownload);
|
||||
document.getElementById("dlbtn2").addEventListener("click", handleDownload);
|
||||
document.getElementById("dlbtn2").addEventListener("click", handleWebsiteDownload);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -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