Files
MemeMind-Server/ARCHITECTURE_DIAGRAMS.md
2026-04-05 13:38:12 +08:00

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*