Files
mp-xieyingeng/.claude/plan.md
2026-04-05 13:37:58 +08:00

205 lines
7.5 KiB
Markdown

# Implementation Plan: User Auth + Server-Side Lives Management
## Current State Analysis
### Client (Cocos Creator)
- **Currency**: "Lives" (生命值), new user starts with 10
- **Earning**: +1 life per correct answer (`PageLevel.showSuccess()``addLife()`)
- **Spending**: -1 life per hint unlock (hints 2 & 3 only, `PageLevel.onUnlockClue()``consumeLife()`)
- **Storage**: All local via `StorageManager` using `sys.localStorage`
- **No auth**: WxSDK only handles sharing/vibration, no `wx.login`
- **HttpUtil**: Has GET/POST, but no auth headers
### Server (NestJS)
- Read-only: 4 GET endpoints (configs + levels)
- No auth, no user system, no guards
- Uses repository pattern consistently
---
## Phase 1: Server - Auth Module (WeChat Login)
### 1.1 Install dependencies
```bash
cd MemeMind-Server
pnpm add @nestjs/jwt axios
```
### 1.2 New files to create
**Entity: `src/modules/auth/entities/user.entity.ts`**
- `id` (UUID, PK)
- `openid` (varchar 128, unique index)
- `sessionKey` (varchar 255, nullable) - WeChat session_key
- `nickname` (varchar 100, nullable)
- `avatarUrl` (text, nullable)
- `lives` (int, default 10) - 生命值/积分
- `createdAt`, `updatedAt`
**DTO: `src/modules/auth/dto/wx-login.dto.ts`**
- `WxLoginRequestDto` - `{ code: string }`
- `WxLoginResponseDto` - `{ token: string, user: { id, nickname, lives } }`
**Repository: `src/modules/auth/repositories/user.repository.ts`**
- `findByOpenid(openid)`, `findById(id)`, `create(data)`, `save(user)`
**Service: `src/modules/auth/auth.service.ts`**
- `wxLogin(code)`: Call WeChat API `https://api.weixin.qq.com/sns/jscode2session` with appid/secret + code → get openid/session_key → find or create user → sign JWT → return token + user info
**Guard: `src/common/guards/jwt-auth.guard.ts`**
- Custom Guard: extract Bearer token from header → verify JWT → attach user to request
**Controller: `src/modules/auth/auth.controller.ts`**
- `POST /v1/auth/wx-login` - public endpoint, accepts `{ code }`, returns `{ token, user }`
**Module: `src/modules/auth/auth.module.ts`**
- Imports: JwtModule, TypeOrmModule.forFeature([User])
- Exports: JwtModule (so other modules can use JwtService)
### 1.3 Environment variables
Add to `.env` and `env.validation.ts`:
- `WX_APPID` - 微信小程序 AppID
- `WX_SECRET` - 微信小程序 AppSecret
- `JWT_SECRET` - JWT signing secret
---
## Phase 2: Server - User Assets API (Lives Management)
### 2.1 New files
**DTO: `src/modules/auth/dto/user-assets.dto.ts`**
- `UserAssetsResponseDto` - `{ lives: number }`
- `ConsumeLifeRequestDto` - `{ reason: 'hint_unlock', levelId?: string, hintIndex?: number }`
- `EarnLifeRequestDto` - `{ reason: 'level_complete', levelId: string }`
**Endpoints added to auth controller (or new user controller):**
- `GET /v1/user/assets` - [Auth Required] Get current lives
- `POST /v1/user/assets/consume` - [Auth Required] Consume 1 life (for hint unlock)
- `POST /v1/user/assets/earn` - [Auth Required] Earn 1 life (for level completion)
### 2.2 Business logic safety
- **Consume**: Check lives > 0 before deducting, return error if insufficient
- **Earn**: Server validates the reason, +1 life
- **Idempotency consideration**: For level_complete, track completed levels per user to prevent duplicate rewards
### 2.3 New Entity: `src/modules/auth/entities/user-level-progress.entity.ts`
- `id` (UUID, PK)
- `userId` (varchar, FK → User)
- `levelId` (varchar, FK → Level)
- `completedAt` (datetime)
- Unique index on (userId, levelId) - prevent duplicate completion rewards
---
## Phase 3: Server - Protect Existing Endpoints + Loading Data API
### 3.1 Composite loading endpoint
**`GET /v1/user/game-data`** - [Auth Required] Returns everything needed at loading:
```json
{
"user": { "id": "...", "lives": 10 },
"levels": [ ... ], // reuse existing level data
"progress": { "completedLevelIds": ["level-1", "level-2"] }
}
```
This replaces the client making multiple API calls during loading.
### 3.2 Auth on existing endpoints
Keep `/v1/wechat-game/levels` and `/v1/wechat-game/configs` as **public** (no auth needed for level data).
New user-specific endpoints require auth.
---
## Phase 4: Client - WeChat Login Integration
### 4.1 WxSDK - Add login method
```typescript
static login(): Promise<string> // returns wx code
```
Calls `wx.login()` → returns `code`
### 4.2 New file: `assets/scripts/utils/AuthManager.ts`
Singleton managing auth state:
- `login()`: WxSDK.login() → POST /v1/auth/wx-login → store token + user data
- `getToken()`: return cached token
- `getUserLives()`: return cached lives
- `isLoggedIn()`: boolean
- Store token in localStorage
### 4.3 HttpUtil - Add auth support
- Add `setAuthToken(token)` static method
- Modify GET/POST to attach `Authorization: Bearer <token>` header when token exists
---
## Phase 5: Client - Connect Lives to Server
### 5.1 New file: `assets/scripts/utils/UserAssetsManager.ts`
Singleton managing user assets (lives) with server sync:
- `fetchAssets()`: GET /v1/user/assets → update local lives
- `consumeLife(reason, levelId?, hintIndex?)`: POST /v1/user/assets/consume → update local
- `earnLife(reason, levelId)`: POST /v1/user/assets/earn → update local
- Falls back to local StorageManager if network fails
### 5.2 PageLoading - Updated flow
```
start()
→ WxSDK.login() get code
→ POST /auth/wx-login → get token + user data (including lives)
→ Store token, sync lives to StorageManager
→ GET /levels (existing, now with auth optional)
→ Preload assets
→ Open PageHome
```
### 5.3 PageLevel - Updated logic
- `onUnlockClue()`: Call `UserAssetsManager.consumeLife('hint_unlock', levelId, hintIndex)` instead of `StorageManager.consumeLife()`
- `showSuccess()``nextLevel()`: Call `UserAssetsManager.earnLife('level_complete', levelId)` instead of `StorageManager.addLife()`
- Keep StorageManager as local cache/fallback
---
## File Change Summary
### Server - New Files (10 files)
1. `src/modules/auth/auth.module.ts`
2. `src/modules/auth/auth.controller.ts`
3. `src/modules/auth/auth.service.ts`
4. `src/modules/auth/entities/user.entity.ts`
5. `src/modules/auth/entities/user-level-progress.entity.ts`
6. `src/modules/auth/repositories/user.repository.ts`
7. `src/modules/auth/repositories/user-level-progress.repository.ts`
8. `src/modules/auth/dto/wx-login.dto.ts`
9. `src/modules/auth/dto/user-assets.dto.ts`
10. `src/common/guards/jwt-auth.guard.ts`
### Server - Modified Files (3 files)
1. `src/app.module.ts` - Import AuthModule
2. `src/config/env.validation.ts` - Add WX_APPID, WX_SECRET, JWT_SECRET
3. `src/main.ts` - Add Bearer auth to Swagger config
### Client - New Files (2 files)
1. `assets/scripts/utils/AuthManager.ts`
2. `assets/scripts/utils/UserAssetsManager.ts`
### Client - Modified Files (4 files)
1. `assets/scripts/utils/WxSDK.ts` - Add `login()` method
2. `assets/scripts/utils/HttpUtil.ts` - Add auth token support
3. `assets/PageLoading.ts` - Add login flow before loading
4. `assets/prefabs/PageLevel.ts` - Use UserAssetsManager for earn/consume
---
## API Endpoints Summary
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/v1/auth/wx-login` | No | WeChat code → JWT token |
| GET | `/v1/user/assets` | Yes | Get user lives |
| POST | `/v1/user/assets/consume` | Yes | Consume 1 life (hint) |
| POST | `/v1/user/assets/earn` | Yes | Earn 1 life (level complete) |
| GET | `/v1/user/game-data` | Yes | Loading composite endpoint |
| GET | `/v1/wechat-game/levels` | No | Existing, stays public |
| GET | `/v1/wechat-game/configs` | No | Existing, stays public |