微信优惠券增加美团/京东中间页,领券前获取微信头像授权(主功能) --------- Co-authored-by: guke <guke@autohome.com.cn> Reviewed-on: #75 Co-authored-by: guke <guke@wonderable.ai> Co-committed-by: guke <guke@wonderable.ai>
7.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project overview
Shaguabijia (傻瓜比价) App backend — FastAPI + SQLAlchemy 2.0 + JWT. Covers user auth (Jiguang one-click / SMS), welfare wallet (coins/cash/signin/tasks/savings), WeChat Pay withdrawals, ad-reward callbacks (Pangle/GroMore S2S), Meituan CPS (coupon forwarding / price comparison), and an admin backend.
Commands
# Install
pip install -e ".[dev]"
# Run app server (port 8770, auto-migrates, auto-reload)
./run.sh # or: uvicorn app.main:app --reload --port 8770
# Run admin server (port 8771, separate process)
uvicorn app.admin.main:admin_app --reload --port 8771
# Database
alembic upgrade head # apply all migrations (idempotent)
alembic revision --autogenerate -m "description" # generate new migration
# Tests
pytest -q # all tests
pytest tests/test_auth.py -q # single file
pytest -k "test_sms_login" -q # single test by name
# Lint
ruff check .
ruff check --fix .
Architecture: two FastAPI apps
This repo runs two separate FastAPI processes sharing the same app/ codebase (models, repositories, integrations, config):
| App server | Admin server | |
|---|---|---|
| Entry | app/main.py → app:app |
app/admin/main.py → admin_app |
| Port | 8770 | 8771 |
| Auth | User JWT (JWT_SECRET_KEY) |
Admin JWT (ADMIN_JWT_SECRET, independent) |
| Audience | Mobile app clients | Internal admin dashboard |
| Docs | /docs (non-prod only) |
/admin/docs (non-prod only) |
The two apps are intentionally decoupled — app.main never imports app.admin. Admin has its own auth chain (app/admin/deps.py, app/admin/security.py), role-based guards (require_role), and routers under app/admin/routers/.
Layered request flow
api/v1/ (thin: parse → delegate → respond + HTTP errors)
├── integrations/ (external SDKs: signature, encryption, HTTP calls)
└── repositories/ (data access + transactions)
└── models/ (SQLAlchemy ORM, DeclarativeBase)
api/v1/: Route handlers. Keep these thin — parse request, call repository or integration, return response. Never put business logic or external HTTP here.api/deps.py: Shared FastAPI dependencies —get_current_user(Bearer JWT → User ORM object),get_db(request-scoped session).integrations/: All external service logic — Jiguang REST + RSA decryption, WeChat Pay V3 signing/encryption, Meituan CPS gateway signing, Pangle callback signature verification, SMS sending. This is the layer you change when swapping vendors.repositories/: Data access. Each file wraps SQLAlchemy queries + transactions for one domain (user, wallet, signin, savings, ad_reward, etc.). Some repositories also call integrations (e.g.,wallet.pycallsintegrations/wxpay.pyfor withdrawals).models/: ORM table definitions (SQLAlchemy 2.0Mappedstyle,DeclarativeBase). Every new model must be imported inapp/models/__init__.pyso Alembic can discover it.schemas/: Pydantic request/response contracts.core/: Infrastructure — config (pydantic-settings), JWT (security.py), in-memory rate limiter (ratelimit.py), reward constants (rewards.py), logging setup, pricebot router (consistent-hash load balancing), withdraw reconcile worker.
Internal (server-to-server) endpoints
Endpoints under app/api/internal/ are for server-to-server communication (pricebot → app-server), NOT for clients. They use a shared secret header X-Internal-Secret (compared via hmac.compare_digest) instead of user JWT. If INTERNAL_API_SECRET is empty, these endpoints return 503.
Auth system
- User login: Jiguang one-click (
integrations/jiguang.py— REST token verification + RSA decryption with multi-padding retry) or SMS code (mock by default;SMS_MOCK=true). - Tokens: JWT access (2h) + refresh (30d). Both are JWT with
typclaim ("access"vs"refresh") to prevent refresh-as-access. Seecore/security.py. - Admin auth: Separate JWT secret (
ADMIN_JWT_SECRET), 12h expiry, no refresh. Username + bcrypt password login. Role-based access viarequire_role()guard inapp/admin/deps.py(super_adminbypasses all role checks). - Rate limiting: In-memory fixed-window by client IP (
core/ratelimit.py). Single-worker only; disabled in tests viaRATE_LIMIT_ENABLED=false.
Database
- Dev: SQLite (
sqlite:///./data/app.db),check_same_thread=False, no connection pool. - Prod: PostgreSQL — just change
DATABASE_URLin.env. Pool size 10 + max overflow 20, pool_recycle 3600. - Migrations: Alembic with
render_as_batchfor SQLite compatibility. ~60+ migration files inalembic/versions/(filenames are descriptive, not hex prefixes). Migration chain usesdown_revisionwithin each file. - New models: Define in
app/models/, import inapp/models/__init__.py, then runalembic revision --autogenerate.
Config
All config via pydantic-settings in app/core/config.py. Single Settings class with env vars / .env file. Access anywhere via from app.core.config import settings. Key patterns:
*_configuredproperties gate features gracefully (e.g.,mt_cps_configured,wxpay_configured,pangle_callback_configured) — missing credentials → endpoints return empty/503 rather than crashing at startup.- Prod validation:
_enforce_prod_secretsmodel validator blocks startup ifAPP_ENV=prodwith weak JWT secrets.
Testing
tests/conftest.py: Sets env vars BEFORE imports, creates temp SQLite file, builds all tables withBase.metadata.create_all(), tears down withdrop_all()+ unlink.- External integrations are monkeypatched in tests (e.g., WeChat Pay, Jiguang, Pangle callbacks) — tests never make real HTTP calls.
TestClientfrom FastAPI is used for all tests. Rate limiting is disabled globally in tests.
Key integration details
- Jiguang one-click login: REST call to verify
loginToken, then RSA decrypt the returned phone number. Multiple padding schemes tried in order (PKCS1v15, OAEP with SHA1/SHA256) because Jiguang's encryption padding varies. - WeChat Pay withdrawals: V3 API merchant transfer to user WeChat balance. Lazy-loads merchant certificates from
secrets/. Withdrawal flow: bind WeChat → create withdraw order → auto-reconcile worker polls pending orders. - Pangle ad rewards: S2S callback verification via SHA256 signature. Multiple
m-keysecrets supported (one per ad placement). Callback is idempotent bytrans_id. Test grant endpoint (AD_REWARD_TEST_GRANT_ENABLED) for local debugging — must be false in prod. - Meituan CPS: Gateway signature-based API calls. Proxy support (
MT_CPS_PROXY) for local dev (direct connection causes SSL EOF). Coupon endpoints gracefully return empty when credentials are missing. - Pricebot forwarding:
/api/v1/coupon/stepand/api/v1/compare/*proxy to pricebot-backend. Multi-instance support with consistent-hash routing bytrace_id(seecore/pricebot_router.py). - CPS redirect:
/c/{code}is a public (no auth) short-link redirect — records a click then 302s to Meituan. Click recording failure never blocks the redirect.
Money and units
All monetary amounts are in cents (*_cents fields). Coins/gold have their own unit. Conversion constants are in core/rewards.py.
Scripts
Key operational scripts in scripts/:
migrate.sh— run migrations standalonecreate_admin.py— create admin userdaily_auto_exchange.py— auto-convert coins to cash (triggered by systemd timer)reconcile_withdraws.py— reconcile withdrawal orders with WeChat Paysim_pangle_callback.py— simulate Pangle S2S callback for testing