feat: 支持登录、个人信息存储
This commit is contained in:
836
CLIENT_SERVER_INTEGRATION.md
Normal file
836
CLIENT_SERVER_INTEGRATION.md
Normal file
@@ -0,0 +1,836 @@
|
||||
# MemeMind Client-Server Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document explains how the Cocos Creator client communicates with the MemeMind-Server backend and what extensions would be needed to support the full game flow including user authentication, progress tracking, and point/life management.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Current Integration (Read-Only)
|
||||
|
||||
### Current API Call: Get All Levels
|
||||
|
||||
**Client File**: `/Users/richard/Documents/code/cocosProject/mp-xieyingeng/assets/scripts/managers/LevelDataManager.ts`
|
||||
|
||||
**Current Implementation**:
|
||||
```typescript
|
||||
async initialize(): Promise<void> {
|
||||
try {
|
||||
// Initialize() is called by PageLoading during startup
|
||||
const response = await HttpUtil.get<ApiResponse>(
|
||||
'https://ilookai.cn/api/v1/wechat-game/levels'
|
||||
);
|
||||
|
||||
if (response.success && response.data?.levels) {
|
||||
this._apiData = response.data.levels;
|
||||
this._levelDataCache.clear();
|
||||
|
||||
// Preload next level images asynchronously
|
||||
this.preloadNextLevel(0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load level data', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Server Endpoint**:
|
||||
```
|
||||
GET /api/v1/wechat-game/levels
|
||||
Status: 200
|
||||
Response: ApiResponseDto<LevelListResponseDto>
|
||||
```
|
||||
|
||||
**Response Format**:
|
||||
```typescript
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
levels: [
|
||||
{
|
||||
level: 1, // Level number (1-indexed)
|
||||
id: "level-001", // Unique ID
|
||||
imageUrl: "https://...", // Level image URL
|
||||
answer: "meme", // Correct answer
|
||||
hint1: "image", // First hint (free)
|
||||
hint2: "funny", // Second hint (costs 1 life)
|
||||
hint3: null, // Third hint (costs 1 life)
|
||||
sortOrder: 0, // Display order
|
||||
createdAt: "2026-04-01T...",
|
||||
updatedAt: "2026-04-05T..."
|
||||
},
|
||||
...
|
||||
],
|
||||
total: 50
|
||||
},
|
||||
message: null,
|
||||
timestamp: "2026-04-05T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
Client Startup
|
||||
│
|
||||
▼
|
||||
PageLoading.ts: _startPreload()
|
||||
│
|
||||
├─ LevelDataManager.initialize()
|
||||
│ │
|
||||
│ └─> HttpUtil.get('/api/v1/wechat-game/levels')
|
||||
│ │
|
||||
│ ▼
|
||||
│ MemeMind-Server
|
||||
│ │
|
||||
│ ├─> WechatGameController.getAllLevels()
|
||||
│ ├─> WechatGameService.getAllLevels()
|
||||
│ ├─> LevelRepository.findAllOrdered()
|
||||
│ └─> MySQL: SELECT * FROM levels ORDER BY sort_order
|
||||
│
|
||||
│ Response returned
|
||||
│ │
|
||||
│ ▼
|
||||
│ LevelDataManager._apiData = levels
|
||||
│ Preload images
|
||||
│
|
||||
├─ Progress: 80% -> 100%
|
||||
│
|
||||
▼
|
||||
PageHome displayed
|
||||
│
|
||||
▼
|
||||
User clicks "Start Game"
|
||||
│
|
||||
▼
|
||||
PageLevel loaded
|
||||
│
|
||||
├─> Reads from _apiData
|
||||
├─> Displays level image, hints, input
|
||||
└─> Ready for gameplay
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Missing Features for Full Integration
|
||||
|
||||
### 1. User Authentication
|
||||
|
||||
**What's needed**:
|
||||
- WeChat OpenID extraction via `wx.login()`
|
||||
- Backend user registration/login endpoint
|
||||
- JWT token generation and validation
|
||||
- User context in requests
|
||||
|
||||
**Implementation Plan**:
|
||||
|
||||
**Server Addition**:
|
||||
```typescript
|
||||
// src/modules/users/users.module.ts
|
||||
@Module({
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService, UsersRepository],
|
||||
imports: [TypeOrmModule.forFeature([User])],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
||||
// src/modules/users/entities/user.entity.ts
|
||||
@Entity('users')
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ unique: true })
|
||||
wxOpenId: string; // WeChat OpenID
|
||||
|
||||
@Column()
|
||||
nickname: string;
|
||||
|
||||
@Column({ default: 10 })
|
||||
currentLives: number;
|
||||
|
||||
@Column({ default: 0 })
|
||||
currentLevelIndex: number;
|
||||
|
||||
@Column({ default: 0 })
|
||||
totalPoints: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// Endpoint: POST /api/v1/users/login
|
||||
@Post('login')
|
||||
async login(@Body() dto: LoginDto): Promise<ApiResponseDto<LoginResponseDto>> {
|
||||
// dto.code from wx.login()
|
||||
// Exchange code for wxOpenId via WeChat API
|
||||
// Create or fetch user
|
||||
// Generate JWT token
|
||||
// Return user + token
|
||||
}
|
||||
```
|
||||
|
||||
**Client Addition**:
|
||||
```typescript
|
||||
// In PageLoading.ts or main.ts
|
||||
async function initializeUser() {
|
||||
try {
|
||||
const code = await wx.login();
|
||||
const response = await HttpUtil.post('/api/v1/users/login', {
|
||||
code: code.code
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
const { user, token } = response.data;
|
||||
StorageManager.setToken(token); // New: Store JWT
|
||||
StorageManager.setUserId(user.id); // New: Store user ID
|
||||
return user;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login failed', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Level Submission & Answer Validation
|
||||
|
||||
**What's needed**:
|
||||
- POST endpoint for level submissions
|
||||
- Answer validation (case-insensitive, trim whitespace)
|
||||
- Update user progress
|
||||
- Award points/lives
|
||||
- Handle wrong answers
|
||||
|
||||
**Implementation Plan**:
|
||||
|
||||
**Server Addition**:
|
||||
```typescript
|
||||
// src/modules/levels/level-submission.module.ts
|
||||
@Module({
|
||||
controllers: [LevelSubmissionController],
|
||||
providers: [LevelSubmissionService],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([UserProgress, Level]),
|
||||
UsersModule,
|
||||
WechatGameModule,
|
||||
],
|
||||
})
|
||||
export class LevelSubmissionModule {}
|
||||
|
||||
// Endpoint: POST /api/v1/levels/:levelId/submit
|
||||
@Post(':levelId/submit')
|
||||
@UseGuards(JwtAuthGuard) // Require authentication
|
||||
async submitAnswer(
|
||||
@Param('levelId') levelId: string,
|
||||
@Body() dto: SubmitAnswerDto,
|
||||
@Req() request: any,
|
||||
): Promise<ApiResponseDto<SubmitAnswerResponseDto>> {
|
||||
// request.user.id from JWT
|
||||
// Validate answer (case-insensitive, trim)
|
||||
// If correct:
|
||||
// - Award 1 life
|
||||
// - Update currentLevelIndex
|
||||
// - Record submission
|
||||
// If wrong:
|
||||
// - Record wrong attempt
|
||||
// - Maybe deduct lives?
|
||||
// Return result
|
||||
}
|
||||
|
||||
// src/modules/levels/dto/submit-answer.dto.ts
|
||||
export class SubmitAnswerDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
answer: string;
|
||||
|
||||
@IsNumber()
|
||||
timeTaken: number; // Seconds to solve
|
||||
|
||||
@IsNumber()
|
||||
hintsUsed: number; // How many hints revealed
|
||||
}
|
||||
|
||||
export class SubmitAnswerResponseDto {
|
||||
success: boolean; // Correct answer?
|
||||
message: string; // "Correct!" or "Wrong!"
|
||||
newLives: number; // Updated lives
|
||||
newLevel: number; // Next level index
|
||||
pointsEarned: number; // Points for this solve
|
||||
totalPoints: number; // Total accumulated points
|
||||
}
|
||||
```
|
||||
|
||||
**Client Change**:
|
||||
```typescript
|
||||
// In PageLevel.ts: showSuccess() method
|
||||
private async showSuccess(): void {
|
||||
this.stopCountdown();
|
||||
this.playSuccessSound();
|
||||
|
||||
// NEW: Submit to server
|
||||
try {
|
||||
const token = StorageManager.getToken();
|
||||
const response = await HttpUtil.post(
|
||||
`/api/v1/levels/${this._currentLevel.id}/submit`,
|
||||
{
|
||||
answer: this._userAnswer,
|
||||
timeTaken: this._elapsedTime,
|
||||
hintsUsed: this._hintsUsed
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
// Update local storage with new progress
|
||||
StorageManager.setLives(response.data.newLives);
|
||||
StorageManager.onLevelCompleted(response.data.newLevel - 1);
|
||||
|
||||
// Show points earned
|
||||
this.showPointsNotification(response.data.pointsEarned);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Submission failed', error);
|
||||
}
|
||||
|
||||
this._showPassModal();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. User Progress Tracking
|
||||
|
||||
**What's needed**:
|
||||
- Endpoint to get user progress
|
||||
- Endpoint to update progress
|
||||
- Sync between client localStorage and server
|
||||
- Handle offline mode
|
||||
|
||||
**Implementation Plan**:
|
||||
|
||||
**Server Addition**:
|
||||
```typescript
|
||||
// Endpoint: GET /api/v1/users/me/progress
|
||||
@Get('me/progress')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async getProgress(
|
||||
@Req() request: any,
|
||||
): Promise<ApiResponseDto<UserProgressDto>> {
|
||||
// request.user.id from JWT
|
||||
// Return user progress
|
||||
}
|
||||
|
||||
export class UserProgressDto {
|
||||
id: string;
|
||||
userId: string;
|
||||
currentLevelIndex: number;
|
||||
maxLevelUnlocked: number;
|
||||
totalPoints: number;
|
||||
currentLives: number;
|
||||
completedLevels: {
|
||||
levelId: string;
|
||||
completedAt: Date;
|
||||
timeTaken: number;
|
||||
hintsUsed: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
// Endpoint: POST /api/v1/users/me/progress/sync
|
||||
@Post('me/progress/sync')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async syncProgress(
|
||||
@Req() request: any,
|
||||
@Body() dto: SyncProgressDto,
|
||||
): Promise<ApiResponseDto<SyncProgressResponseDto>> {
|
||||
// Merge client progress with server
|
||||
// Handle conflicts (prefer latest)
|
||||
// Return merged progress
|
||||
}
|
||||
```
|
||||
|
||||
**Client Update**:
|
||||
```typescript
|
||||
// In StorageManager.ts: Add sync methods
|
||||
static async syncWithServer(): Promise<void> {
|
||||
try {
|
||||
const token = this.getToken();
|
||||
const localProgress = this.getCurrentLevelIndex();
|
||||
const localLives = this.getLives();
|
||||
|
||||
const response = await HttpUtil.post(
|
||||
'/api/v1/users/me/progress/sync',
|
||||
{
|
||||
currentLevelIndex: localProgress,
|
||||
currentLives: localLives,
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
}
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
// Update local with server's merged data
|
||||
this.setLives(response.data.currentLives);
|
||||
// Update progress for each completed level
|
||||
response.data.completedLevels.forEach((level) => {
|
||||
this.onLevelCompleted(level.levelIndex);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Sync failed', error);
|
||||
// Continue with local data
|
||||
}
|
||||
}
|
||||
|
||||
// Call on app startup and periodically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Hint Usage & Cost Management
|
||||
|
||||
**What's needed**:
|
||||
- Track which hints have been used
|
||||
- Deduct lives when using premium hints
|
||||
- Prevent excessive hint usage
|
||||
|
||||
**Implementation Plan**:
|
||||
|
||||
**Server Addition**:
|
||||
```typescript
|
||||
// Track hint usage per level per user
|
||||
@Entity('level_hints')
|
||||
export class LevelHint {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
user: User;
|
||||
|
||||
@Column()
|
||||
levelId: string;
|
||||
|
||||
@Column()
|
||||
hint1Revealed: boolean; // Always free
|
||||
|
||||
@Column()
|
||||
hint2Revealed: boolean; // Costs 1 life
|
||||
|
||||
@Column()
|
||||
hint3Revealed: boolean; // Costs 1 life
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// Endpoint: POST /api/v1/levels/:levelId/reveal-hint
|
||||
@Post(':levelId/reveal-hint')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async revealHint(
|
||||
@Param('levelId') levelId: string,
|
||||
@Body() dto: RevealHintDto, // { hintNumber: 2 }
|
||||
@Req() request: any,
|
||||
): Promise<ApiResponseDto<RevealHintResponseDto>> {
|
||||
// Validate hint number
|
||||
// Check if already revealed
|
||||
// If hint 2 or 3: deduct 1 life
|
||||
// Update hint tracking
|
||||
// Return hint text
|
||||
}
|
||||
```
|
||||
|
||||
**Client Change**:
|
||||
```typescript
|
||||
// In PageLevel.ts: onUnlockClue() method
|
||||
private async onUnlockClue(clueIndex: number): void {
|
||||
const token = StorageManager.getToken();
|
||||
|
||||
// Hint index 1 is always free (hint1)
|
||||
// Hints 2 and 3 cost 1 life each
|
||||
if (clueIndex > 0 && !this._freeCluePassed) {
|
||||
const currentLives = StorageManager.getLives();
|
||||
if (currentLives <= 0) {
|
||||
this._showToast('没有生命了!(No lives left!)');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await HttpUtil.post(
|
||||
`/api/v1/levels/${this._currentLevel.id}/reveal-hint`,
|
||||
{ hintNumber: clueIndex + 1 },
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
const hint = response.data.hintText;
|
||||
this._showHint(hint);
|
||||
|
||||
// Deduct life if premium hint
|
||||
if (clueIndex > 0) {
|
||||
StorageManager.consumeLife();
|
||||
StorageManager.setLives(response.data.remainingLives);
|
||||
this._updateLivesDisplay();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to unlock hint', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Leaderboard & Statistics
|
||||
|
||||
**What's needed**:
|
||||
- Track total points per user
|
||||
- Track completion time
|
||||
- Leaderboard endpoint
|
||||
- User statistics endpoint
|
||||
|
||||
**Implementation Plan**:
|
||||
|
||||
**Server Addition**:
|
||||
```typescript
|
||||
// Endpoint: GET /api/v1/leaderboard?limit=100
|
||||
@Get('leaderboard')
|
||||
async getLeaderboard(
|
||||
@Query('limit') limit: number = 100,
|
||||
): Promise<ApiResponseDto<LeaderboardDto>> {
|
||||
// Select top users by totalPoints
|
||||
// Include rank, nickname, points, completedLevels
|
||||
}
|
||||
|
||||
// Endpoint: GET /api/v1/users/me/statistics
|
||||
@Get('me/statistics')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async getStatistics(
|
||||
@Req() request: any,
|
||||
): Promise<ApiResponseDto<StatisticsDto>> {
|
||||
// Return user statistics
|
||||
// Total levels completed
|
||||
// Average time per level
|
||||
// Total hints used
|
||||
// Current streak, etc.
|
||||
}
|
||||
|
||||
export class StatisticsDto {
|
||||
totalLevelsCompleted: number;
|
||||
currentLevelIndex: number;
|
||||
totalPoints: number;
|
||||
currentLives: number;
|
||||
averageTimePerLevel: number; // Seconds
|
||||
totalTimeSpent: number; // Seconds
|
||||
totalHintsUsed: number;
|
||||
perfectSolves: number; // Solved in first try
|
||||
longestStreak: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 3: API Authentication Pattern
|
||||
|
||||
### JWT Guard Implementation
|
||||
|
||||
**Server**:
|
||||
```typescript
|
||||
// src/modules/auth/guards/jwt.guard.ts
|
||||
@Injectable()
|
||||
export class JwtAuthGuard implements CanActivate {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('No token provided');
|
||||
}
|
||||
|
||||
try {
|
||||
const secret = this.configService.get('JWT_SECRET');
|
||||
const decoded = verify(token, secret);
|
||||
request.user = decoded; // Attach to request
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage on endpoints
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('me/progress')
|
||||
async getProgress(@Req() request: any) {
|
||||
const userId = request.user.id; // From JWT payload
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Client**:
|
||||
```typescript
|
||||
// In HttpUtil.ts: Add auth header
|
||||
static async post<T>(
|
||||
url: string,
|
||||
data: any,
|
||||
options?: RequestOptions
|
||||
): Promise<ApiResponse<T>> {
|
||||
const token = StorageManager.getToken();
|
||||
|
||||
const headers = {
|
||||
...options?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// Make request with headers
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Data Sync Strategy
|
||||
|
||||
### Scenario 1: Online Mode
|
||||
|
||||
```
|
||||
User completes level
|
||||
│
|
||||
▼
|
||||
[Client] Submits answer
|
||||
│
|
||||
├─ HTTP POST /api/v1/levels/:id/submit
|
||||
│
|
||||
▼
|
||||
[Server] Validates and updates
|
||||
│
|
||||
├─ Check answer
|
||||
├─ Award life/points
|
||||
├─ Update progress
|
||||
├─ Store submission
|
||||
│
|
||||
▼
|
||||
[Response] Returned to client
|
||||
│
|
||||
├─ Update localStorage
|
||||
├─ Show success modal
|
||||
├─ Move to next level
|
||||
│
|
||||
▼
|
||||
Progress synced
|
||||
```
|
||||
|
||||
### Scenario 2: Offline Mode
|
||||
|
||||
```
|
||||
User completes level (no connection)
|
||||
│
|
||||
├─ Submit fails (no network)
|
||||
│
|
||||
▼
|
||||
[Client] Stores locally
|
||||
│
|
||||
├─ StorageManager.recordOfflineSubmission()
|
||||
├─ Update lives/progress locally
|
||||
├─ Show success modal (assume correct)
|
||||
│
|
||||
▼
|
||||
When connection returns
|
||||
│
|
||||
├─ StorageManager.syncPendingSubmissions()
|
||||
│
|
||||
▼
|
||||
[Server] Receives batch of submissions
|
||||
│
|
||||
├─ Validate all answers
|
||||
├─ Apply corrections if needed
|
||||
├─ Return merged state
|
||||
│
|
||||
▼
|
||||
[Client] Reconciles state
|
||||
│
|
||||
├─ If conflicts: server wins
|
||||
├─ Update localStorage
|
||||
├─ Show notification of changes
|
||||
│
|
||||
▼
|
||||
Progress synced
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 5: Implementation Roadmap
|
||||
|
||||
### Phase 1: Basic Auth (Week 1)
|
||||
- [ ] User entity & table
|
||||
- [ ] Login endpoint with wx.login() code exchange
|
||||
- [ ] JWT token generation
|
||||
- [ ] JwtAuthGuard for protected routes
|
||||
- [ ] Client login integration
|
||||
- [ ] Token storage in StorageManager
|
||||
|
||||
### Phase 2: Progress Tracking (Week 2)
|
||||
- [ ] UserProgress entity & table
|
||||
- [ ] GET /api/v1/users/me/progress
|
||||
- [ ] POST /api/v1/levels/:id/submit
|
||||
- [ ] Update client PageLevel.ts to submit answers
|
||||
- [ ] Sync endpoint for merging progress
|
||||
- [ ] Offline submission queue
|
||||
|
||||
### Phase 3: Hint System Integration (Week 3)
|
||||
- [ ] LevelHint entity & tracking
|
||||
- [ ] POST /api/v1/levels/:id/reveal-hint
|
||||
- [ ] Life deduction logic
|
||||
- [ ] Client hint unlock cost
|
||||
|
||||
### Phase 4: Leaderboard & Stats (Week 4)
|
||||
- [ ] Statistics calculation
|
||||
- [ ] Leaderboard endpoint
|
||||
- [ ] Client leaderboard page
|
||||
- [ ] Personal statistics page
|
||||
|
||||
### Phase 5: Polish & Optimization (Week 5)
|
||||
- [ ] Caching layer (@nestjs/cache-manager)
|
||||
- [ ] Rate limiting (@nestjs/throttler)
|
||||
- [ ] Request logging middleware
|
||||
- [ ] Performance monitoring
|
||||
- [ ] Database indexing optimization
|
||||
|
||||
---
|
||||
|
||||
## Part 6: Environment Configuration
|
||||
|
||||
### Server .env.production
|
||||
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
|
||||
# Database
|
||||
DB_HOST=production-db-host
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=prod_user
|
||||
DB_PASSWORD=secure_password
|
||||
DB_DATABASE=meme_mind_prod
|
||||
|
||||
# Authentication
|
||||
JWT_SECRET=very-secure-secret-key-32-chars-min
|
||||
JWT_EXPIRATION=7d
|
||||
|
||||
# WeChat
|
||||
WECHAT_APPID=your_wechat_appid
|
||||
WECHAT_SECRET=your_wechat_secret
|
||||
|
||||
# API
|
||||
API_BASE_URL=https://ilookai.cn/api
|
||||
CORS_ORIGIN=https://yourdomain.com
|
||||
```
|
||||
|
||||
### Client Storage Keys
|
||||
|
||||
```typescript
|
||||
// New keys needed:
|
||||
- 'auth_token' → JWT token
|
||||
- 'user_id' → Current user ID
|
||||
- 'offline_submissions' → Queue of submissions to send
|
||||
- 'last_sync' → Timestamp of last sync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 7: Error Handling
|
||||
|
||||
### Common API Errors
|
||||
|
||||
```typescript
|
||||
// 401 Unauthorized
|
||||
{
|
||||
success: false,
|
||||
data: null,
|
||||
message: 'Invalid token',
|
||||
path: '/api/v1/users/me/progress'
|
||||
}
|
||||
|
||||
// 404 Not Found
|
||||
{
|
||||
success: false,
|
||||
data: null,
|
||||
message: 'Level with id "xyz" not found',
|
||||
path: '/api/v1/levels/xyz'
|
||||
}
|
||||
|
||||
// 400 Bad Request (validation)
|
||||
{
|
||||
success: false,
|
||||
data: null,
|
||||
message: 'Validation failed: answer must be a string',
|
||||
path: '/api/v1/levels/123/submit'
|
||||
}
|
||||
|
||||
// 500 Server Error
|
||||
{
|
||||
success: false,
|
||||
data: null,
|
||||
message: 'Internal server error',
|
||||
path: '/api/v1/levels'
|
||||
}
|
||||
```
|
||||
|
||||
### Client Handling
|
||||
|
||||
```typescript
|
||||
async function handleApiError(error: unknown) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const status = error.response?.status;
|
||||
const message = error.response?.data?.message;
|
||||
|
||||
if (status === 401) {
|
||||
// Token expired - redirect to login
|
||||
StorageManager.clearToken();
|
||||
navigateTo('PageHome');
|
||||
} else if (status === 404) {
|
||||
showToast(`Not found: ${message}`);
|
||||
} else if (status === 400) {
|
||||
showToast(`Invalid input: ${message}`);
|
||||
} else {
|
||||
showToast(`Error: ${message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The current integration only handles reading level data. To support the full game loop with:
|
||||
- ✅ User authentication (JWT)
|
||||
- ✅ Answer submission & validation
|
||||
- ✅ Progress tracking & sync
|
||||
- ✅ Hint system with cost management
|
||||
- ✅ Leaderboard & statistics
|
||||
|
||||
You need to implement the endpoints, models, and client logic described in this guide. The roadmap suggests a 5-week implementation with phases for each feature.
|
||||
|
||||
---
|
||||
|
||||
*Generated: 2026-04-05 | For MemeMind Project*
|
||||
Reference in New Issue
Block a user