3dca126411
微信优惠券增加美团/京东中间页,领券前获取微信头像授权(主功能) --------- 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>
117 lines
7.7 KiB
Markdown
117 lines
7.7 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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.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
|