707 lines
28 KiB
Markdown
707 lines
28 KiB
Markdown
# MemeMind-Server Architecture Diagrams & Flows
|
|
|
|
## 1. System Architecture Overview
|
|
|
|
```
|
|
┌───────────────────────────────────────────────────────────────┐
|
|
│ WeChat Mini-Game Client │
|
|
│ (Cocos Creator 3.8.8) │
|
|
│ │
|
|
│ • PageLoading.ts (startup) │
|
|
│ • LevelDataManager.ts (API calls) │
|
|
│ • PageLevel.ts (gameplay) │
|
|
│ • StorageManager.ts (localStorage) │
|
|
└────────────────────┬────────────────────────────────────────┘
|
|
│
|
|
│ HTTP Requests
|
|
│ GET /api/v1/wechat-game/levels
|
|
│ GET /api/v1/wechat-game/configs
|
|
│
|
|
┌────────────────────▼────────────────────────────────────────┐
|
|
│ MemeMind-Server (NestJS) │
|
|
│ http://ilookai.cn:3000/api │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ HTTP Layer │ │
|
|
│ │ • GlobalPrefix: /api │ │
|
|
│ │ • CORS: Enabled │ │
|
|
│ │ • ValidationPipe: Global validation │ │
|
|
│ │ • Swagger: /api/docs │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ┌──────────────────────▼──────────────────────────────┐ │
|
|
│ │ WechatGameController │ │
|
|
│ │ POST /v1/wechat-game/configs │ │
|
|
│ │ GET /v1/wechat-game/configs/:key │ │
|
|
│ │ GET /v1/wechat-game/levels │ │
|
|
│ │ GET /v1/wechat-game/levels/:id │ │
|
|
│ └──────────────────────┬──────────────────────────────┘ │
|
|
│ │ │
|
|
│ ┌──────────────────────▼──────────────────────────────┐ │
|
|
│ │ WechatGameService │ │
|
|
│ │ • getAllConfigs() │ │
|
|
│ │ • getConfigByKey(key) │ │
|
|
│ │ • getAllLevels() │ │
|
|
│ │ • getLevelById(id) │ │
|
|
│ │ • toResponseDto() │ │
|
|
│ │ • toLevelResponseDto() │ │
|
|
│ └──────────────────────┬──────────────────────────────┘ │
|
|
│ │ │
|
|
│ ┌──────────────────────▼──────────────────────────────┐ │
|
|
│ │ Repository Layer │ │
|
|
│ │ ├─ LevelRepository │ │
|
|
│ │ └─ GameConfigRepository │ │
|
|
│ └──────────────────────┬──────────────────────────────┘ │
|
|
│ │ │
|
|
│ ┌──────────────────────▼──────────────────────────────┐ │
|
|
│ │ TypeORM / MySQL │ │
|
|
│ │ ├─ levels table │ │
|
|
│ │ └─ game_configs table │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└───────────────────────────────────────────────────────────────┘
|
|
│
|
|
│ Response (JSON)
|
|
│ {success, data, message, timestamp}
|
|
│
|
|
┌────────────────────▼────────────────────────────────────────┐
|
|
│ WeChat Mini-Game Client │
|
|
│ • LevelDataManager stores in _apiData │
|
|
│ • PageLevel reads _apiData │
|
|
│ • Images preloaded via assetManager.loadRemote() │
|
|
└───────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Request-Response Flow
|
|
|
|
### Scenario 1: Get All Levels
|
|
|
|
```
|
|
Client Server
|
|
│ │
|
|
├─ GET /api/v1/wechat-game/levels
|
|
│─────────────────────────────>│
|
|
│ │
|
|
│ [Controller]
|
|
│ getAllLevels()
|
|
│ │
|
|
│ [Service]
|
|
│ levelRepository.findAllOrdered()
|
|
│ │
|
|
│ [Repository]
|
|
│ SELECT * FROM levels ORDER BY sort_order
|
|
│ │
|
|
│ [MySQL]
|
|
│ Returns Level[]
|
|
│ │
|
|
│ [Service]
|
|
│ Map to LevelResponseDto
|
|
│ Add level numbers
|
|
│ │
|
|
│ [Filter]
|
|
│ Wrap in ApiResponseDto.success()
|
|
│ │
|
|
│ <─────────────────────────────│
|
|
│ { │
|
|
│ "success": true, │
|
|
│ "data": { │
|
|
│ "levels": [...], │
|
|
│ "total": 50 │
|
|
│ }, │
|
|
│ "message": null, │
|
|
│ "timestamp": "2026-04-05..." │
|
|
│ } │
|
|
│ │
|
|
├─ Store in _apiData
|
|
├─ Preload images
|
|
└─ Ready for gameplay
|
|
```
|
|
|
|
### Scenario 2: Get Config by Key
|
|
|
|
```
|
|
Client Server
|
|
│ │
|
|
├─ GET /api/v1/wechat-game/configs/HINT_COST
|
|
│─────────────────────────────>│
|
|
│ │
|
|
│ [Controller]
|
|
│ getConfigByKey("HINT_COST")
|
|
│ │
|
|
│ [Service]
|
|
│ gameConfigRepository.findByKey()
|
|
│ │
|
|
│ [Repository]
|
|
│ SELECT * FROM game_configs
|
|
│ WHERE config_key = 'HINT_COST'
|
|
│ │
|
|
│ [MySQL]
|
|
│ Returns GameConfig or null
|
|
│ │
|
|
│ ┌─────────┴─────────┐
|
|
│ │ │
|
|
│ FOUND NOT FOUND
|
|
│ │ │
|
|
│ [Service] [Service]
|
|
│ Map to DTO throw NotFoundException
|
|
│ │ │
|
|
│ [Filter] [Filter]
|
|
│ success() catch exception
|
|
│ │ │
|
|
│ <─────────────────┤ │
|
|
│ { │ │
|
|
│ "success": true,│ │
|
|
│ "data": {...} │ │
|
|
│ } │ ┌────────────────┘
|
|
│ │ │
|
|
│ │ └─> {
|
|
│ │ "success": false,
|
|
│ │ "data": null,
|
|
│ │ "message": "Game config... not found",
|
|
│ │ "path": "/api/v1/..."
|
|
│ │ }
|
|
│ │
|
|
└──────────────────────────────────
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Module Dependency Graph
|
|
|
|
```
|
|
┌──────────────────────────────┐
|
|
│ AppModule (root) │
|
|
├──────────────────────────────┤
|
|
│ │
|
|
│ imports: [ │
|
|
│ AppConfigModule, │
|
|
│ TypeOrmModule, │
|
|
│ WechatGameModule │
|
|
│ ] │
|
|
│ │
|
|
└──┬───────────────────────────┘
|
|
│
|
|
├─────────────────────────────────────┐
|
|
│ │
|
|
│ │
|
|
┌──▼─────────────────┐ ┌──────────────▼──────────────┐
|
|
│ AppConfigModule │ │ WechatGameModule │
|
|
├────────────────────┤ ├─────────────────────────────┤
|
|
│ @Global() │ │ imports: [ │
|
|
│ │ │ TypeOrmModule.forFeature( │
|
|
│ imports: [ │ │ [GameConfig, Level] │
|
|
│ ConfigModule │ │ ) │
|
|
│ ] │ │ ] │
|
|
│ │ │ │
|
|
│ exports: [ │ │ controllers: [ │
|
|
│ ConfigModule │ │ WechatGameController │
|
|
│ ] │ │ ] │
|
|
│ │ │ │
|
|
│ │ │ providers: [ │
|
|
│ │ │ WechatGameService, │
|
|
│ │ │ LevelRepository, │
|
|
│ │ │ GameConfigRepository │
|
|
│ │ │ ] │
|
|
│ │ │ │
|
|
│ │ │ exports: [ │
|
|
│ │ │ WechatGameService │
|
|
│ │ │ ] │
|
|
│ │ │ │
|
|
└────────────────────┘ └─────────────────────────────┘
|
|
│
|
|
│
|
|
┌──────────▼───────────────┐
|
|
│ TypeOrmModule.forFeature│
|
|
├───────────────────────────┤
|
|
│ Registers: │
|
|
│ • Level entity │
|
|
│ • GameConfig entity │
|
|
│ • Auto-creates repos │
|
|
└──────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Data Model Relationships
|
|
|
|
```
|
|
┌────────────────────────────────┐
|
|
│ levels │
|
|
├────────────────────────────────┤
|
|
│ PK: id (VARCHAR 191) │
|
|
├────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ image_url (VARCHAR) │
|
|
│ answer (VARCHAR) │
|
|
│ hint1 (VARCHAR) │
|
|
│ hint2 (VARCHAR) │
|
|
│ hint3 (VARCHAR) │
|
|
│ sort_order (INT) │
|
|
│ created_at (DATETIME)│
|
|
│ updated_at (DATETIME)│
|
|
├────────────────────────────────┤
|
|
│ Indexes: │
|
|
│ • PK: id │
|
|
│ • idx_sort_order: sort_order │
|
|
├────────────────────────────────┤
|
|
│ Used by: │
|
|
│ • LevelRepository │
|
|
│ • WechatGameService │
|
|
└────────────────────────────────┘
|
|
|
|
┌────────────────────────────────┐
|
|
│ game_configs │
|
|
├────────────────────────────────┤
|
|
│ PK: id (UUID) │
|
|
├────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ config_key (VARCHAR) │
|
|
│ config_value (TEXT) │
|
|
│ description (VARCHAR) │
|
|
│ is_active (BOOLEAN) │
|
|
│ created_at (DATETIME)│
|
|
│ updated_at (DATETIME)│
|
|
├────────────────────────────────┤
|
|
│ Indexes: │
|
|
│ • PK: id │
|
|
│ • UNIQUE: config_key │
|
|
│ • idx_active: is_active │
|
|
├────────────────────────────────┤
|
|
│ Used by: │
|
|
│ • GameConfigRepository │
|
|
│ • WechatGameService │
|
|
└────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Service Method Call Chain
|
|
|
|
### GET /api/v1/wechat-game/levels
|
|
|
|
```
|
|
Controller.getAllLevels()
|
|
│
|
|
├─> service.getAllLevels()
|
|
│ │
|
|
│ ├─> levelRepository.findAllOrdered()
|
|
│ │ │
|
|
│ │ └─> repository.find({ order: { sortOrder: 'ASC' } })
|
|
│ │ │
|
|
│ │ └─> [SELECT * FROM levels ORDER BY sort_order ASC]
|
|
│ │
|
|
│ ├─> LOOP levels array:
|
|
│ │ ├─> toLevelResponseDto(level, index + 1)
|
|
│ │ │ │
|
|
│ │ │ └─> {
|
|
│ │ │ level: 1 (or 2, 3, ...)
|
|
│ │ │ id: level.id
|
|
│ │ │ imageUrl: level.imageUrl
|
|
│ │ │ answer: level.answer
|
|
│ │ │ hint1: level.hint1
|
|
│ │ │ hint2: level.hint2
|
|
│ │ │ hint3: level.hint3
|
|
│ │ │ sortOrder: level.sortOrder
|
|
│ │ │ createdAt: level.createdAt
|
|
│ │ │ updatedAt: level.updatedAt
|
|
│ │ │ }
|
|
│ │
|
|
│ └─> return {
|
|
│ levels: [LevelResponseDto[], ...]
|
|
│ total: count
|
|
│ }
|
|
│
|
|
└─> ApiResponseDto.success(data)
|
|
│
|
|
└─> {
|
|
success: true
|
|
data: { levels, total }
|
|
message: null
|
|
timestamp: new Date()
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Error Handling Flow
|
|
|
|
```
|
|
┌─────────────────┐
|
|
│ HTTP Request │
|
|
└────────┬────────┘
|
|
│
|
|
┌────▼─────────────┐
|
|
│ ValidationPipe │
|
|
│ (Global) │
|
|
└────┬─────────────┘
|
|
│
|
|
┌───┴────────────────────────┐
|
|
│ │
|
|
▼ Valid ▼ Invalid
|
|
Continue ValidationException
|
|
│
|
|
[ExceptionFilter]
|
|
catches @Catch()
|
|
│
|
|
ApiResponseDto.error()
|
|
│
|
|
{success: false,
|
|
message: "...",
|
|
path: "..."}
|
|
│
|
|
▼ Valid data
|
|
[Controller]
|
|
[Service]
|
|
[Repository]
|
|
[Database]
|
|
│
|
|
├─ Success ──┐
|
|
│ │
|
|
│ ▼
|
|
│ [Service] returns data
|
|
│ │
|
|
│ [Controller]
|
|
│ │
|
|
│ ApiResponseDto.success(data)
|
|
│ │
|
|
│ Return to client
|
|
│
|
|
└─ Exception ──┐
|
|
│
|
|
▼
|
|
NotFoundException
|
|
BadRequestException
|
|
(or any HttpException)
|
|
│
|
|
▼
|
|
[HttpExceptionFilter]
|
|
@Catch() catches exception
|
|
│
|
|
▼
|
|
Extract status & message
|
|
│
|
|
▼
|
|
ApiResponseDto.error(message)
|
|
│
|
|
▼
|
|
response.status(code).json(errorDto)
|
|
│
|
|
▼
|
|
Return to client with error
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Request Validation Pipeline
|
|
|
|
```
|
|
HTTP Request
|
|
│
|
|
├─ Path params extracted
|
|
│ @Param('id') id: string
|
|
│ @Param('key') key: string
|
|
│
|
|
├─ Query params extracted
|
|
│ @Query() dto: QueryDto
|
|
│
|
|
├─ Body params extracted (for POST/PUT)
|
|
│ @Body() dto: CreateDto
|
|
│
|
|
├─ Global ValidationPipe processes:
|
|
│ │
|
|
│ ├─ whitelist: true
|
|
│ │ └─ Remove unknown properties
|
|
│ │
|
|
│ ├─ forbidNonWhitelisted: true
|
|
│ │ └─ Throw if unknown properties found
|
|
│ │
|
|
│ └─ transform: true
|
|
│ └─ Transform strings to appropriate types
|
|
│ (e.g., "123" → 123)
|
|
│
|
|
├─ class-validator decorators checked
|
|
│ @IsString()
|
|
│ @IsNumber()
|
|
│ @IsEmail()
|
|
│ etc.
|
|
│
|
|
├─ If validation fails
|
|
│ └─> BadRequestException
|
|
│ └─> ExceptionFilter catches
|
|
│ └─> 400 status + error message
|
|
│
|
|
└─ If validation passes
|
|
└─> Continue to controller
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Data Transformation Chain
|
|
|
|
```
|
|
HTTP Request JSON
|
|
│
|
|
├─ Parse JSON
|
|
├─ Extract into DTO object
|
|
│ {
|
|
│ "@Type(() => Number)" hint: "5" ──> 5 (number)
|
|
│ "@Transform()" name: "JOHN" ──> "john" (lowercased)
|
|
│ }
|
|
│
|
|
├─ Validate against DTO decorators
|
|
│ @IsNotEmpty()
|
|
│ @IsNumber()
|
|
│ @Min(0)
|
|
│ @Max(100)
|
|
│
|
|
├─ Pass to Service
|
|
│
|
|
├─ Service transforms to Entity
|
|
│ DTO ──> Entity
|
|
│ {id, name} {id, name, timestamp}
|
|
│
|
|
├─ Database operations
|
|
│ Entity ──> SQL
|
|
│ TypeORM handles serialization
|
|
│
|
|
├─ Result from Database
|
|
│ Entity[] ──> Entity[]
|
|
│
|
|
├─ Service transforms Entity to ResponseDto
|
|
│ Entity ──> ResponseDto
|
|
│ Remove sensitive fields
|
|
│ Add computed fields
|
|
│
|
|
├─ Wrap in ApiResponseDto
|
|
│
|
|
└─ Send as JSON Response
|
|
{
|
|
success: true,
|
|
data: [...ResponseDtos...],
|
|
message: null,
|
|
timestamp: "..."
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Database Connection Lifecycle
|
|
|
|
```
|
|
[Application Start]
|
|
│
|
|
▼
|
|
[config.module.ts loads]
|
|
• validateEnvironment()
|
|
• Reads .env, .env.local, .env.production
|
|
│
|
|
▼
|
|
[app.module.ts initializes]
|
|
• TypeOrmModule.forRootAsync()
|
|
• Uses ConfigService to get DB params
|
|
│
|
|
├─ DB_HOST: localhost
|
|
├─ DB_PORT: 3306
|
|
├─ DB_USERNAME: meme_user
|
|
├─ DB_PASSWORD: (from env)
|
|
├─ DB_DATABASE: meme_mind
|
|
│
|
|
▼
|
|
[TypeORM connects to MySQL]
|
|
mysql2 driver establishes connection
|
|
│
|
|
├─ If NODE_ENV === 'development'
|
|
│ └─ synchronize: true
|
|
│ └─ Auto-create/update tables
|
|
│
|
|
├─ If NODE_ENV === 'production'
|
|
│ └─ synchronize: false
|
|
│ └─ Use migrations instead
|
|
│
|
|
▼
|
|
[Repositories instantiated]
|
|
@InjectRepository(Level)
|
|
@InjectRepository(GameConfig)
|
|
│
|
|
▼
|
|
[Ready to accept requests]
|
|
• findAllOrdered() ──> SELECT ...
|
|
• findByKey() ──> SELECT ...
|
|
│
|
|
▼
|
|
[Application shutdown]
|
|
TypeORM closes connection
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Environment to Runtime Configuration
|
|
|
|
```
|
|
.env.local / .env file
|
|
│
|
|
├─ NODE_ENV=development
|
|
├─ PORT=3000
|
|
├─ DB_HOST=localhost
|
|
├─ DB_PORT=3306
|
|
├─ DB_USERNAME=meme_user
|
|
├─ DB_PASSWORD=secret
|
|
├─ DB_DATABASE=meme_mind
|
|
│
|
|
▼
|
|
env.validation.ts
|
|
• plainToInstance(EnvironmentVariables, config)
|
|
• validateSync()
|
|
• Throws if validation fails
|
|
│
|
|
▼
|
|
config.module.ts
|
|
• ConfigModule.forRoot()
|
|
• isGlobal: true ──> Available everywhere
|
|
• validate: validateEnvironment
|
|
│
|
|
▼
|
|
database.config.ts
|
|
registerAs('database', () => ({
|
|
type: 'mysql',
|
|
host: configService.get('DB_HOST'),
|
|
port: configService.get('DB_PORT'),
|
|
username: configService.get('DB_USERNAME'),
|
|
password: configService.get('DB_PASSWORD'),
|
|
database: configService.get('DB_DATABASE'),
|
|
...
|
|
}))
|
|
│
|
|
▼
|
|
app.module.ts
|
|
TypeOrmModule.forRootAsync({
|
|
useFactory: (configService) => ({
|
|
...configService.get('database')
|
|
})
|
|
})
|
|
│
|
|
▼
|
|
main.ts
|
|
port = process.env.PORT ?? 3000
|
|
app.listen(port)
|
|
│
|
|
▼
|
|
Application Running
|
|
• Connected to MySQL
|
|
• Listening on port
|
|
• Ready for requests
|
|
```
|
|
|
|
---
|
|
|
|
## 11. API Response Mapping Example
|
|
|
|
### Request:
|
|
```
|
|
GET /api/v1/wechat-game/levels
|
|
```
|
|
|
|
### Database Results:
|
|
```sql
|
|
SELECT * FROM levels ORDER BY sort_order ASC LIMIT 2;
|
|
|
|
Results:
|
|
┌─────────────┬──────────────────────┬────────┬───────┬───────┬───────┬────────────┬─────────────┬─────────────┐
|
|
│ id │ image_url │ answer │ hint1 │ hint2 │ hint3 │ sort_order │ created_at │ updated_at │
|
|
├─────────────┼──────────────────────┼────────┼───────┼───────┼───────┼────────────┼─────────────┼─────────────┤
|
|
│ level-001 │ http://...img1.jpg │ meme │ image │ funny │ null │ 0 │ 2026-04-01 │ 2026-04-05 │
|
|
│ level-002 │ http://...img2.jpg │ code │ tech │ null │ null │ 1 │ 2026-04-02 │ 2026-04-05 │
|
|
└─────────────┴──────────────────────┴────────┴───────┴───────┴───────┴────────────┴─────────────┴─────────────┘
|
|
```
|
|
|
|
### Service Transformation:
|
|
```javascript
|
|
levels.map((level, index) => toLevelResponseDto(level, index + 1))
|
|
|
|
Result:
|
|
[
|
|
{
|
|
level: 1, // Computed: index + 1
|
|
id: "level-001",
|
|
imageUrl: "http://...img1.jpg",
|
|
answer: "meme",
|
|
hint1: "image",
|
|
hint2: "funny",
|
|
hint3: null,
|
|
sortOrder: 0,
|
|
createdAt: "2026-04-01T...",
|
|
updatedAt: "2026-04-05T..."
|
|
},
|
|
{
|
|
level: 2,
|
|
id: "level-002",
|
|
imageUrl: "http://...img2.jpg",
|
|
answer: "code",
|
|
hint1: "tech",
|
|
hint2: null,
|
|
hint3: null,
|
|
sortOrder: 1,
|
|
createdAt: "2026-04-02T...",
|
|
updatedAt: "2026-04-05T..."
|
|
}
|
|
]
|
|
```
|
|
|
|
### Final HTTP Response:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"levels": [
|
|
{
|
|
"level": 1,
|
|
"id": "level-001",
|
|
"imageUrl": "http://...img1.jpg",
|
|
"answer": "meme",
|
|
"hint1": "image",
|
|
"hint2": "funny",
|
|
"hint3": null,
|
|
"sortOrder": 0,
|
|
"createdAt": "2026-04-01T00:00:00.000Z",
|
|
"updatedAt": "2026-04-05T12:00:00.000Z"
|
|
},
|
|
{
|
|
"level": 2,
|
|
"id": "level-002",
|
|
"imageUrl": "http://...img2.jpg",
|
|
"answer": "code",
|
|
"hint1": "tech",
|
|
"hint2": null,
|
|
"hint3": null,
|
|
"sortOrder": 1,
|
|
"createdAt": "2026-04-02T00:00:00.000Z",
|
|
"updatedAt": "2026-04-05T12:00:00.000Z"
|
|
}
|
|
],
|
|
"total": 2
|
|
},
|
|
"message": null,
|
|
"timestamp": "2026-04-05T12:34:56.789Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary of Diagrams
|
|
|
|
1. **System Architecture**: High-level components (Client, Server, Database)
|
|
2. **Request-Response Flow**: Detailed flow for GET and error scenarios
|
|
3. **Module Dependency Graph**: How modules depend on each other
|
|
4. **Data Model Relationships**: Database table structures
|
|
5. **Service Method Call Chain**: Stack of calls from Controller to DB
|
|
6. **Error Handling Flow**: Exception catching and wrapping
|
|
7. **Request Validation Pipeline**: Validation process
|
|
8. **Data Transformation Chain**: DTO → Entity → DB → Entity → ResponseDto
|
|
9. **Database Connection Lifecycle**: Connection initialization
|
|
10. **Environment to Runtime**: How .env becomes runtime config
|
|
11. **API Response Mapping**: Real example of transformation
|
|
|
|
*Generated: 2026-04-05*
|