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

28 KiB

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:

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:

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:

{
  "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