Files
guke 3dca126411 fix(dev): 启动脚本钉定 .venv 解释器 + watchfiles 降噪 (#75)
微信优惠券增加美团/京东中间页,领券前获取微信头像授权(主功能)

---------

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>
2026-06-25 17:47:30 +08:00

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.pyapp:app app/admin/main.pyadmin_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.py calls integrations/wxpay.py for withdrawals).
  • models/: ORM table definitions (SQLAlchemy 2.0 Mapped style, DeclarativeBase). Every new model must be imported in app/models/__init__.py so 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 typ claim ("access" vs "refresh") to prevent refresh-as-access. See core/security.py.
  • Admin auth: Separate JWT secret (ADMIN_JWT_SECRET), 12h expiry, no refresh. Username + bcrypt password login. Role-based access via require_role() guard in app/admin/deps.py (super_admin bypasses all role checks).
  • Rate limiting: In-memory fixed-window by client IP (core/ratelimit.py). Single-worker only; disabled in tests via RATE_LIMIT_ENABLED=false.

Database

  • Dev: SQLite (sqlite:///./data/app.db), check_same_thread=False, no connection pool.
  • Prod: PostgreSQL — just change DATABASE_URL in .env. Pool size 10 + max overflow 20, pool_recycle 3600.
  • Migrations: Alembic with render_as_batch for SQLite compatibility. ~60+ migration files in alembic/versions/ (filenames are descriptive, not hex prefixes). Migration chain uses down_revision within each file.
  • New models: Define in app/models/, import in app/models/__init__.py, then run alembic 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:

  • *_configured properties 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_secrets model validator blocks startup if APP_ENV=prod with weak JWT secrets.

Testing

  • tests/conftest.py: Sets env vars BEFORE imports, creates temp SQLite file, builds all tables with Base.metadata.create_all(), tears down with drop_all() + unlink.
  • External integrations are monkeypatched in tests (e.g., WeChat Pay, Jiguang, Pangle callbacks) — tests never make real HTTP calls.
  • TestClient from 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-key secrets supported (one per ad placement). Callback is idempotent by trans_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/step and /api/v1/compare/* proxy to pricebot-backend. Multi-instance support with consistent-hash routing by trace_id (see core/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 standalone
  • create_admin.py — create admin user
  • daily_auto_exchange.py — auto-convert coins to cash (triggered by systemd timer)
  • reconcile_withdraws.py — reconcile withdrawal orders with WeChat Pay
  • sim_pangle_callback.py — simulate Pangle S2S callback for testing