48 KiB
36 Zihua Mobile API Design Draft (V1)
This document is based on docs/36字花-数据库与实施计划.md and the PRD. It provides an initial mobile-facing API inventory and field definitions.
Scope: platform-wide single period number and single draw result; channels are used only for attribution, commission sharing, and risk control—not for splitting game rounds.
Addendum (2026-04): §1.5 describes server-side Redis hot-spot caching (GameHotDataRedis). It does not change any interface URL, parameter, or response field contracts; it is for integration testing and operations reference only.
Addendum (2026-05): §1.3 / §1.4 / §2.3 document single-device login and POST /api/user/logout (Redis-backed device_id binding).
1. Design Conventions
1.1 Base Conventions
- Protocol: HTTPS + JSON
- Path naming:
/api/{module}/{action}, must match regex^/api/[a-z]+/[a-z]+[A-Z][a-zA-Z]*$ - HTTP methods: All mobile business APIs (
/api/*, excluding/api/v1/authToken) usePOST. Query-style endpoints also acceptGET(for browser/debug tools); clients should uniformly usePOST.- For
POST, headerContent-Type: application/json; parameters in JSON body - In
GETcompatibility mode, parameters go in the URL query string - Exception: Notice module
/api/notice/noticeList,/api/notice/noticeConfirmsupportGETonly; parameters always use URL query string - Notice list without auth:
GET /api/notice/noticeListdoes not requireauth-tokenoruser-token; ifuser-tokenis still sent, popout items can return the user’s realis_read - Auth endpoint
/api/v1/authTokenremainsGET
- For
- Time: UTC timestamp (seconds) + server timezone configuration
- Amounts: numeric transport (e.g.
"100.00"); client display uses two decimal places (storage remainsdecimal(18,2)) - Idempotency: critical write APIs require
idempotency_key - Required headers:
auth-token: API auth token fromGET /api/v1/authToken(signature-based API access credential)user-token: user session token; required on login-protected APIs
- Language header:
lang=zh: Chinese (default)lang=en: English
1.2 Common Response Shape
{
"code": 1,
"message": "ok",
"data": {}
}
-
code=1means success; any other value is a business error -
All
/api/*response messages support Chinese and English: default Chinese;lang=enreturns English,lang=zhreturns Chinese -
Suggested error code ranges (by nature):
1000-1099: parameter errors (missing fields, type errors, format errors, out of range)1100-1199: auth errors (not logged in, token expired, insufficient permission)2000-2999: business errors (insufficient balance, round not found, order not found, notice not found)3000-3099: flow errors (illegal flow/state, e.g. bet after lock, duplicate confirm, illegal state transition)5000-5999: system errors (service exception, dependency timeout, unknown error)
-
Recommended base error codes (first release):
-
1: success1001: missing parameter1002: invalid parameter format1003: illegal parameter value1101: not logged in or session expired1103: no permission2001: insufficient balance2002: round not found2003: order not found2004: notice not found3001: operation not allowed in current flow3002: betting closed, bets not allowed3003: duplicate request (idempotency conflict)5000: system busy, try again later
1.3 Authentication
- API auth (
auth-token): all mobile business APIs must send headerauth-token(issued by/api/v1/authToken) - User session (
user-token): login-protected APIs send headeruser-token; on expiry, refresh or log in again - Single-device session (server rule):
device_idin theauthTokenquery is bound to thatauth-tokenin Redis- Each account allows one active device only: a successful login on a new device clears other
user-token/refresh_tokenrows and binds the newdevice_id - Old devices calling login-protected APIs get
code=1101with message like “logged in on another device, please sign in again” (lang=enfor English) - Clients must use a stable unique
device_idper install (do not randomize on every launch)
1.4 Obtain API Auth Token (auth-token)
- GET
/api/v1/authToken - Purpose: obtain
auth-token(required on all API request headers)
Request example:
/api/v1/authToken?secret=564d14asdasd113e46542asd6das1a2a×tamp=1776331077&device_id=1&signature=AD84C49880896DBC16C59C7B122D1FF7
Request parameters:
secret: string (client secret; server validates against envAUTH_TOKEN_SECRET)timestamp: int (request timestamp; server allows ±300 seconds from server time)device_id: string (device id)signature: string (signature value)
Signature algorithm:
- Parameters in signature (excluding
signature):device_id,secret,timestamp - Sort parameter names a-z, concatenate as
key=value&key=value... - Compute:
signature = strtoupper(md5(concatenated string))
Response parameters:
auth_token: string (API auth token; put in headerauth-token)expires_in: int (TTL in seconds)server_time: int (server timestamp for clock sync)
Server behavior (single-device):
- On success, stores
device_idfor thisauth_tokenin Redis with TTL =expires_in - Login, login-protected HTTP APIs, and WebSocket handshake compare this
device_idwith the user’s active device
Possible error codes:
1001missing parameter1002invalid parameter format1103invalid secret / signature error3001invalid timestamp
1.5 Server Performance & Redis Hot-Spot Cache (Implementation Notes)
No client contract change: request paths, parameters, response JSON shape, and error codes are unchanged by caching; this section only explains how the server reduces latency, read paths, and consistency notes.
Difference from “framework file cache”
| Config | Scope |
|---|---|
CACHE_DRIVER (config/cache.php, e.g. file) |
Think-ORM / get_sys_config() etc. system config table model cache under runtime/cache; not used on this game hot-path. |
GAME_HOT_CACHE_* (config/game_hot_cache.php) |
Game user / game_config / game_record row-level JSON cache via support\Redis (config/redis.php), key prefix dfw:v1:. |
Server cache coverage (read paths relevant to mobile)
- User: member auth prefers Redis
userrow snapshot; on miss, DB then backfill. After balance, streak, bet-flow, etc. change and commit,GameHotDataCoordinator::afterUserCommitted($userId):GameHotDataRedis::userReplaceCacheFromDbaligns with DB, then enqueues idempotent refresh tasks (GameHotDataWriteQueue/GameHotDataQueueConsumer) for peak shaving—not a substitute for synchronous reload. - Game config:
game_configcached byconfig_key. DirectDbupdates in admin must callGameHotDataCoordinator::afterGameConfigKeyCommitted($key)(modelGameConfigevents and standalone form controllers wired); standalone save usesGameHotDataLock(TYPE_GAME_CONFIG) perconfig_key. Do not only delete cache keys without reload—max inconsistency window is TTL. - Round: active round, round by
id, latestgame_record, etc.; after write,GameHotDataCoordinator::afterGameRecordCommittedrefreshes Redis keys and enqueues. Draw/lock paths may useGameHotDataLock(TYPE_GAME_RECORD) per record id.
Environment variables (see repo root .env-example)
GAME_HOT_CACHE_ENABLED: enable Redis hot cache (false= always DB).GAME_HOT_CACHE_TTL_GAME_CONFIG/GAME_HOT_CACHE_TTL_GAME_RECORD/GAME_HOT_CACHE_TTL_USER: TTL seconds; write-then-sync reload is primary, TTL is fallback only.GAME_HOT_CACHE_ENABLE_WRITE_QUEUEand queue length, consumer interval, etc.: idempotent refresh after write (config/game_hot_cache.php).
Consistency notes (integration / testing)
- Scripts that bypass coordinator and only change DB without
GameHotDataCoordinatormay be briefly inconsistent with Redis; avoid in production. POST /api/game/betPlacedebit path uses the same per-user Redis lock as admin wallet adjust (GameHotDataRedis::userAdminMutationLockTry) andWHERE coin = ?conditional update, mutually exclusive with concurrent payout/admin adjust; on failure returns Chinese messages listed in §4.2.- Clients may still use §3.2
dictionaryListversionfor local cache; server dictionary also has Redis acceleration—both can coexist.
2. Auth & Account Module (user)
2.1 Register
- POST
/api/user/register - Purpose: phone-only registration with invite attribution (admin/channel); auto-login on success
- Headers:
auth-tokenrequired (device id is taken from that token’s bounddevice_id; see §1.3)
Request parameters:
username: string, phone number (registration account; mainland China mobile only)password: string, plaintext over HTTPS (login password; server stores salted hash)invite_code: string, required (sub-agent invite code; bindschannel_idand ownership)
Response parameters:
user-token: string (session token for login-protected APIs)refresh_token: string, optional (refresh access token)expires_in: int (seconds, token TTL)user: object (non-sensitive fields only; noid)uuid: string (public user id, 10 chars)username: string (nickname/display name)coin: string (current balance)channel_id: int (attribution channel id)risk_flags: int (risk status bitmask)
2.2 Login
- POST
/api/user/login - Headers:
auth-tokenrequired (bounddevice_idis the login device; do not senddevice_idin body)
Request parameters:
username: string (login account; currently phone)password: string (login password)
Server behavior:
- On success, clears other sessions for this user and sets active device to the
auth-token’sdevice_id(§1.3)
Response parameters:
user-token: string (access token for login-protected APIs)refresh_token: string, optionalexpires_in: int (access token remaining seconds)user: object (non-sensitive fields only; noid)uuid: string (public user id, 10 chars)username: string (nickname/display name)coin: string (current balance)channel_id: int (attribution channel id)risk_flags: int (risk status bitmask)
2.3 Logout
- POST
/api/user/logout - Purpose: sign out, invalidate session, release single-device lock
Headers:
auth-token: requireduser-token: required when logged in
Request parameters (optional):
refresh_token: string (recommended; server deletes it; if omitted while logged in, server tries current session refresh token)
Response:
data: empty object{}
Server behavior:
- Deletes current
user-token, clears Redis activedevice_idfor the user - Deletes
refresh_tokenwhen provided - Idempotent: returns
code=1even if already logged out (client can clear local storage)
Possible error codes:
1101: missing or invalidauth-token(same as §1.4)
2.4 Current User Profile
- POST
/api/user/profile
Response parameters (amount fields as 2-decimal strings, aligned with wallet display):
Basic profile
uuid: string (public user id, 10 chars)username: string (nickname)head_image: string (avatar URL)phone: string (mobile)email: string (email)register_invite_code: string (invite code snapshot at registration)channel_id: int (attribution channel id)risk_flags: int (risk status bitmask)current_streak: int (current win streak count)last_bet_period_no: string (period no of last valid bet)create_time: int (registration timestamp)
Funds & withdraw quota
coin/coin_balance: string (current balance; same value)frozen_balance: string (frozen balance; fixed0.00when none)total_deposit_coin: string (lifetime deposits)total_withdraw_coin: string (lifetime withdraws; incremented after acceptance)bet_flow_coin: string (bet flow / turnover; after settlement, +total_amountper bet 1:1)max_withdrawable: string (max single withdraw allowed now =min(coin_balance, max_withdraw_by_flow))withdraw_flow: object (bet-flow / withdraw quota diagnostic snapshot; includespending_withdraw)ratio: string (bet-flow multiplier;0= unlimited)net_deposit: string (net deposit = max(0, total deposit − total withdraw))required_bet_flow: string (required bet flow by threshold rule; display only)remaining_bet_flow: string (remaining bet flow by threshold; display only)eligible: bool (meets overall threshold; display only; actual gate ismax_withdrawable)max_withdraw_by_flow: string/null (cap from bet flow only;nullwhenratio=0)flow_unlimited: bool (unlimited bet-flow mode)pending_withdraw: objectcount: int (pending-review withdraw orders)max: int (max pending-review withdraws per user, currently3; exceeds →withdrawCreatereturnscode=2004)
2.5 Refresh Token (Optional)
- POST
/api/user/refreshToken - Headers:
auth-tokenrequired; activedevice_idmust match (§1.3)
Request parameters:
refresh_token: string (credential to renew access token)
Response parameters:
user-token: string (new access token)expires_in: int (new token TTL)
3. Game Lobby & Dictionary Module (game/lobby)
3.1 Lobby Init
- POST
/api/game/lobbyInit - Purpose: one-shot current round, config, 36 zihua dictionary, user quick display
Response parameters:
server_time: int (server time for client clock sync)runtime_enabled: bool (game runtime switch;false= admin maintenance—no new bets, idle won’t auto-create periods, post-payout won’t auto-create next; current open round still draws, pays out, and settles. Mobile should disable bet entry and show maintenance copy)period: objectperiod_no: string (global period number)status: string (betting/locked/settling/finished/void;void= period voided)countdown: int (countdown seconds)lock_at: int (lock timestamp)open_at: int (expected draw timestamp)
bet_config: objectpick_max_number_count: int (max numbers per bet; fromgame_config.config_key = pick_max_number_count; default matches seed, usually 10; range 1–36)chips: object (quick chip map; fixed keys"1"…"6", values are per-number stake strings, 2 decimals; same as admingame_config.bet_chips)default_bet_chip_id: int (default chip id fromgame_config.default_bet_chip_id; invalid → first valid chip)min_bet_per_number: string (min per number; ≤ selected chip and admin limits)max_bet_per_number: string (max per number)
dictionary: array