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

1192 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MemeMind-Server Backend Analysis
**Date**: 2026-04-05
**Project**: MemeMind-Server (NestJS + TypeORM)
**Location**: `/Users/richard/Documents/code/xieyingeng/MemeMind-Server`
## Executive Summary
MemeMind-Server is a NestJS backend for a WeChat mini-game with a clean layered architecture. It provides two main modules: **WechatGame** (game level and config management) and **AppConfig** (environment setup). The server uses TypeORM with MySQL as the database and follows enterprise patterns including repositories, DTOs, and global error handling.
---
## 1. Project Structure
```
MemeMind-Server/
├── src/
│ ├── main.ts # App entry point
│ ├── app.module.ts # Root module (imports config & wechat-game)
│ ├── common/
│ │ ├── dto/
│ │ │ └── api-response.dto.ts # Generic API response wrapper
│ │ └── filters/
│ │ └── http-exception.filter.ts # Global exception handler
│ ├── config/
│ │ ├── config.module.ts # Environment config setup
│ │ ├── database.config.ts # TypeORM & MySQL config
│ │ └── env.validation.ts # Environment variable validation
│ └── modules/
│ └── wechat-game/
│ ├── wechat-game.module.ts # Module definition
│ ├── wechat-game.controller.ts # 4 REST endpoints
│ ├── wechat-game.service.ts # Business logic
│ ├── entities/
│ │ ├── level.entity.ts # Level data model
│ │ └── game-config.entity.ts # Configuration model
│ ├── dto/
│ │ ├── level-response.dto.ts # Level API response
│ │ └── game-config-response.dto.ts # Config API response
│ └── repositories/
│ ├── level.repository.ts # Level DB access
│ ├── level.repository.interface.ts
│ ├── game-config.repository.ts # Config DB access
│ └── game-config.repository.interface.ts
├── test/
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── package.json
├── tsconfig.json
├── tsconfig.build.json
└── nest-cli.json
```
---
## 2. Package Dependencies
### Core Framework
- **@nestjs/core** ^11.0.1 - NestJS core
- **@nestjs/common** ^11.0.1 - Common utilities
- **@nestjs/platform-express** ^11.0.1 - Express integration
- **@nestjs/config** ^4.0.3 - Environment management
- **@nestjs/typeorm** ^11.0.0 - TypeORM integration
- **@nestjs/swagger** ^11.2.6 - API documentation
### Database
- **typeorm** ^0.3.28 - ORM framework
- **mysql2** ^3.19.1 - MySQL driver
### Data Validation & Transformation
- **class-validator** ^0.15.1 - DTO validation
- **class-transformer** ^0.5.1 - DTO transformation
- **reflect-metadata** ^0.2.2 - Reflection for decorators
### Utilities
- **rxjs** ^7.8.1 - Reactive programming
### Development Tools
- **@nestjs/cli** ^11.0.0
- **@nestjs/testing** ^11.0.1
- **typescript** ^5.7.3
- **ts-jest** ^29.2.5
- **jest** ^30.0.0
- **prettier** ^3.4.2
- **eslint** ^9.18.0
---
## 3. Application Setup (main.ts)
**File**: `src/main.ts` (46 lines)
```typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Global prefix for all routes: /api
app.setGlobalPrefix('api');
// CORS enabled (supports WeChat mini-game cross-origin requests)
app.enableCors({
origin: true,
credentials: true,
});
// Global validation pipe
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip unknown properties
forbidNonWhitelisted: true, // Throw on unknown properties
transform: true, // Auto-transform DTOs
}),
);
// Global exception filter
app.useGlobalFilters(new HttpExceptionFilter());
// Swagger documentation at /api/docs
const config = new DocumentBuilder()
.setTitle('MemeMind Server API')
.setDescription('微信小游戏 MemeMind 服务端 API 文档')
.setVersion('1.0')
.build();
const port = process.env.PORT ?? 3000;
await app.listen(port);
}
```
### Key Setup Details
- **Global prefix**: All routes start with `/api`
- **CORS**: Enabled for WeChat mini-game frontend
- **Validation**: Auto-validates all DTO inputs, strips unknown fields
- **Exception handling**: Global filter wraps all errors in `ApiResponseDto`
- **Swagger**: Auto-generated API docs at `/api/docs`
- **Default port**: 3000 (configurable via `PORT` env var)
---
## 4. Root Module Configuration (app.module.ts)
**File**: `src/app.module.ts` (30 lines)
```typescript
@Module({
imports: [
AppConfigModule, // Environment setup
TypeOrmModule.forRootAsync({ // Dynamic DB config
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get<string>('database.host'),
port: configService.get<number>('database.port'),
username: configService.get<string>('database.username'),
password: configService.get<string>('database.password'),
database: configService.get<string>('database.database'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: NODE_ENV !== 'production', // Auto-create tables in dev
logging: NODE_ENV !== 'production', // Log queries in dev
autoLoadEntities: true, // Auto-discover entities
}),
}),
WechatGameModule, // Game module
],
})
export class AppModule {}
```
### Key Configuration
- **Async TypeORM config**: Reads database settings from environment
- **Auto-synchronization**: In development, automatically creates/updates tables
- **Auto-load entities**: Discovers all `.entity.ts` files
- **Logging**: Database queries logged in development mode
---
## 5. Environment Configuration
### Files
- **config.module.ts**: Global config setup
- **database.config.ts**: Database connection parameters
- **env.validation.ts**: Type-safe environment variables
### Configuration Structure (env.validation.ts)
```typescript
class EnvironmentVariables {
@IsEnum(Environment)
NODE_ENV: Environment = 'development'; // 'development' | 'production' | 'test'
@IsNumber()
PORT: number = 3000;
@IsString()
DB_HOST: string = 'localhost';
@IsNumber()
DB_PORT: number = 3306;
@IsString()
DB_USERNAME: string = 'meme_user';
@IsString()
DB_PASSWORD: string = '';
@IsString()
DB_DATABASE: string = 'meme_mind';
}
```
### Environment Files
The app looks for environment files in this order:
1. `.env.local` (local override)
2. `.env.production` (production override)
3. `.env` (default)
### Database Configuration (database.config.ts)
- **Default MySQL connection**: localhost:3306
- **Fallback credentials**: meme_user / (no password)
- **Default database**: meme_mind
- **Auto-synchronize**: ✅ Enabled in development
- **Query logging**: ✅ Enabled in development
---
## 6. Global Response Format
### ApiResponseDto (src/common/dto/api-response.dto.ts)
All API responses follow this generic wrapper:
```typescript
interface ApiResponseDto<T> {
success: boolean; // Request successful?
data: T | null; // Response data (null on error)
message: string | null; // Error message (null on success)
timestamp: Date; // Response timestamp
// Static factory methods
static success<T>(data: T): ApiResponseDto<T>
static error<T>(message: string): ApiResponseDto<T | null>
}
```
### Example Success Response
```json
{
"success": true,
"data": {
"levels": [...],
"total": 50
},
"message": null,
"timestamp": "2026-04-05T10:30:00.000Z"
}
```
### Example Error Response
```json
{
"success": false,
"data": null,
"message": "Level with id 'xyz' not found",
"timestamp": "2026-04-05T10:30:00.000Z",
"path": "/api/v1/wechat-game/levels/xyz"
}
```
---
## 7. Global Exception Handler
### HttpExceptionFilter (src/common/filters/http-exception.filter.ts)
```typescript
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// Extracts HTTP status code from exception
// Wraps error in ApiResponseDto format
// Includes request path for debugging
// Logs unexpected errors
}
}
```
**Features**:
- ✅ Catches all exceptions (HTTP and runtime errors)
- ✅ Wraps all errors in standard `ApiResponseDto` format
- ✅ Includes request URL in error response
- ✅ Logs errors to console
- ✅ Proper HTTP status codes (404, 400, 500, etc.)
---
## 8. WechatGame Module Architecture
### Module Structure (wechat-game.module.ts)
```typescript
@Module({
imports: [TypeOrmModule.forFeature([GameConfig, Level])],
controllers: [WechatGameController],
providers: [WechatGameService, GameConfigRepository, LevelRepository],
exports: [WechatGameService],
})
export class WechatGameModule {}
```
### Layer Architecture
```
┌─────────────────────────────────────┐
│ HTTP Requests (REST Controller) │
│ Base: /api/v1/wechat-game │
└──────────────┬──────────────────────┘
┌──────────────▼──────────────────────┐
│ Business Logic (Service) │
│ • Query logic │
│ • DTO mapping │
│ • Error handling │
└──────────────┬──────────────────────┘
┌──────────────▼──────────────────────┐
│ Data Access (Repositories) │
│ • Level Repository │
│ • GameConfig Repository │
└──────────────┬──────────────────────┘
┌──────────────▼──────────────────────┐
│ TypeORM (Database Layer) │
│ • MySQL queries │
│ • Transaction management │
└─────────────────────────────────────┘
```
---
## 9. REST API Endpoints
### Base URL: `/api/v1/wechat-game`
**Endpoint 1: Get All Game Configs**
```
GET /api/v1/wechat-game/configs
Response: ApiResponseDto<GameConfigListResponseDto>
Status: 200
```
**Endpoint 2: Get Config by Key**
```
GET /api/v1/wechat-game/configs/:key
Response: ApiResponseDto<GameConfigResponseDto>
Status: 200 | 404 (not found)
```
**Endpoint 3: Get All Levels**
```
GET /api/v1/wechat-game/levels
Response: ApiResponseDto<LevelListResponseDto>
Status: 200
Description: Returns levels sorted by sort_order
```
**Endpoint 4: Get Level by ID**
```
GET /api/v1/wechat-game/levels/:id
Response: ApiResponseDto<LevelResponseDto>
Status: 200 | 404 (not found)
```
---
## 10. Data Models
### Entity 1: Level (src/modules/wechat-game/entities/level.entity.ts)
```typescript
@Entity('levels')
export class Level {
@PrimaryColumn({ type: 'varchar', length: 191 })
id: string; // UUID or custom ID
@Column({ type: 'varchar', length: 191, name: 'image_url' })
imageUrl: string; // Game image URL
@Column({ type: 'varchar', length: 191 })
answer: string; // Correct answer (single string)
@Column({ type: 'varchar', length: 191, nullable: true })
hint1: string | null; // First hint (free)
@Column({ type: 'varchar', length: 191, nullable: true })
hint2: string | null; // Second hint (costs 1 life)
@Column({ type: 'varchar', length: 191, nullable: true })
hint3: string | null; // Third hint (costs 1 life)
@Column({ type: 'int', name: 'sort_order', default: 0 })
sortOrder: number; // Order of display
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
```
**Database Table**: `levels`
### Entity 2: GameConfig (src/modules/wechat-game/entities/game-config.entity.ts)
```typescript
@Entity('game_configs')
export class GameConfig {
@PrimaryGeneratedColumn('uuid')
id: string; // Auto-generated UUID
@Column({ type: 'varchar', length: 255, name: 'config_key' })
configKey: string; // Unique config identifier
@Column({ type: 'text', name: 'config_value' })
configValue: string; // Config value (stored as text)
@Column({
type: 'varchar',
length: 100,
nullable: true,
})
description: string | null; // Optional description
@Column({ type: 'boolean', default: true, name: 'is_active' })
isActive: boolean; // Is this config active?
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
```
**Database Table**: `game_configs`
---
## 11. Service Layer (WechatGameService)
**File**: `src/modules/wechat-game/wechat-game.service.ts` (93 lines)
### Methods
#### 1. getAllConfigs()
```typescript
async getAllConfigs(): Promise<GameConfigListResponseDto> {
const configs = await this.gameConfigRepository.findActiveConfigs();
return {
configs: configs.map((config) => this.toResponseDto(config)),
total: configs.length,
};
}
```
- Returns only `isActive: true` configurations
- Maps entities to DTOs
- Returns list with total count
#### 2. getConfigByKey(key: string)
```typescript
async getConfigByKey(key: string): Promise<GameConfigResponseDto> {
const config = await this.gameConfigRepository.findByKey(key);
if (!config) {
throw new NotFoundException(`Game config with key "${key}" not found`);
}
return this.toResponseDto(config);
}
```
- Throws 404 if not found
- Maps entity to DTO
#### 3. getAllLevels()
```typescript
async getAllLevels(): Promise<LevelListResponseDto> {
const levels = await this.levelRepository.findAllOrdered();
return {
levels: levels.map((level, index) =>
this.toLevelResponseDto(level, index + 1), // 1-indexed level number
),
total: levels.length,
};
}
```
- Fetches levels in sort_order
- Adds computed `level` number (1-indexed)
- Returns list with total count
#### 4. getLevelById(id: string)
```typescript
async getLevelById(id: string): Promise<LevelResponseDto> {
const levels = await this.levelRepository.findAllOrdered();
const levelIndex = levels.findIndex((l) => l.id === id);
if (levelIndex === -1) {
throw new NotFoundException(`Level with id "${id}" not found`);
}
return this.toLevelResponseDto(levels[levelIndex], levelIndex + 1);
}
```
- Fetches all levels and finds by ID (not optimal - see improvements)
- Throws 404 if not found
- Adds computed `level` number
#### 5. toResponseDto(config)
```typescript
private toResponseDto(config: GameConfig): GameConfigResponseDto {
return {
id: config.id,
configKey: config.configKey,
configValue: config.configValue,
description: config.description,
isActive: config.isActive,
createdAt: config.createdAt,
updatedAt: config.updatedAt,
};
}
```
#### 6. toLevelResponseDto(level, levelNumber)
```typescript
private toLevelResponseDto(level: Level, levelNumber: number): LevelResponseDto {
return {
level: levelNumber, // Computed field
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,
};
}
```
---
## 12. Repository Pattern
### Repository Interfaces
**ILevelRepository**:
```typescript
interface ILevelRepository {
findAll(): Promise<Level[]>;
findById(id: string): Promise<Level | null>;
findAllOrdered(): Promise<Level[]>; // Ordered by sortOrder ASC
}
```
**IGameConfigRepository**:
```typescript
interface IGameConfigRepository {
findAll(): Promise<GameConfig[]>;
findById(id: string): Promise<GameConfig | null>;
findByKey(key: string): Promise<GameConfig | null>;
findActiveConfigs(): Promise<GameConfig[]>; // Where isActive = true
}
```
### Implementations
**LevelRepository** (src/modules/wechat-game/repositories/level.repository.ts):
```typescript
@Injectable()
export class LevelRepository implements ILevelRepository {
constructor(
@InjectRepository(Level)
private readonly repository: Repository<Level>,
) {}
async findAll(): Promise<Level[]> {
return this.repository.find();
}
async findById(id: string): Promise<Level | null> {
return this.repository.findOne({ where: { id } });
}
async findAllOrdered(): Promise<Level[]> {
return this.repository.find({
order: { sortOrder: 'ASC' },
});
}
}
```
**GameConfigRepository** (src/modules/wechat-game/repositories/game-config.repository.ts):
```typescript
@Injectable()
export class GameConfigRepository implements IGameConfigRepository {
constructor(
@InjectRepository(GameConfig)
private readonly repository: Repository<GameConfig>,
) {}
async findAll(): Promise<GameConfig[]> {
return this.repository.find();
}
async findById(id: string): Promise<GameConfig | null> {
return this.repository.findOne({ where: { id } });
}
async findByKey(key: string): Promise<GameConfig | null> {
return this.repository.findOne({ where: { configKey: key } });
}
async findActiveConfigs(): Promise<GameConfig[]> {
return this.repository.find({ where: { isActive: true } });
}
}
```
---
## 13. Data Transfer Objects (DTOs)
### LevelResponseDto (src/modules/wechat-game/dto/level-response.dto.ts)
```typescript
export class LevelResponseDto {
@ApiProperty({ description: '关卡编号' })
level: number; // Computed level number (1-indexed)
@ApiProperty({ description: '关卡ID' })
id: string;
@ApiProperty({ description: '图片URL' })
imageUrl: string;
@ApiProperty({ description: '答案' })
answer: string;
@ApiProperty({ description: '提示1', nullable: true })
hint1: string | null;
@ApiProperty({ description: '提示2', nullable: true })
hint2: string | null;
@ApiProperty({ description: '提示3', nullable: true })
hint3: string | null;
@ApiProperty({ description: '排序顺序' })
sortOrder: number;
@ApiProperty({ description: '创建时间' })
createdAt: Date;
@ApiProperty({ description: '更新时间' })
updatedAt: Date;
}
export class LevelListResponseDto {
@ApiProperty({ type: [LevelResponseDto], description: '关卡列表' })
levels: LevelResponseDto[];
@ApiProperty({ description: '关卡总数' })
total: number;
}
```
### GameConfigResponseDto (src/modules/wechat-game/dto/game-config-response.dto.ts)
```typescript
export class GameConfigResponseDto {
@ApiProperty({ description: '配置ID' })
id: string;
@ApiProperty({ description: '配置键名' })
configKey: string;
@ApiProperty({ description: '配置值' })
configValue: string;
@ApiProperty({ description: '配置描述', nullable: true })
description: string | null;
@ApiProperty({ description: '是否激活' })
isActive: boolean;
@ApiProperty({ description: '创建时间' })
createdAt: Date;
@ApiProperty({ description: '更新时间' })
updatedAt: Date;
}
export class GameConfigListResponseDto {
@ApiProperty({ type: [GameConfigResponseDto], description: '配置列表' })
configs: GameConfigResponseDto[];
@ApiProperty({ description: '配置总数' })
total: number;
}
```
---
## 14. Controller (WechatGameController)
**File**: `src/modules/wechat-game/wechat-game.controller.ts` (69 lines)
```typescript
@ApiTags('微信小游戏')
@Controller('v1/wechat-game')
export class WechatGameController {
constructor(private readonly wechatGameService: WechatGameService) {}
@Get('configs')
@ApiOperation({
summary: '获取所有游戏配置',
description: '获取所有激活的游戏配置列表',
})
async getAllConfigs(): Promise<ApiResponseDto<GameConfigListResponseDto>> {
const data = await this.wechatGameService.getAllConfigs();
return ApiResponseDto.success(data);
}
@Get('configs/:key')
@ApiOperation({
summary: '根据key获取配置',
})
async getConfigByKey(
@Param('key') key: string,
): Promise<ApiResponseDto<GameConfigResponseDto>> {
const data = await this.wechatGameService.getConfigByKey(key);
return ApiResponseDto.success(data);
}
@Get('levels')
@ApiOperation({
summary: '获取所有关卡',
description: '获取所有关卡列表按sort_order排序',
})
async getAllLevels(): Promise<ApiResponseDto<LevelListResponseDto>> {
const data = await this.wechatGameService.getAllLevels();
return ApiResponseDto.success(data);
}
@Get('levels/:id')
@ApiOperation({
summary: '根据ID获取关卡',
})
async getLevelById(
@Param('id') id: string,
): Promise<ApiResponseDto<LevelResponseDto>> {
const data = await this.wechatGameService.getLevelById(id);
return ApiResponseDto.success(data);
}
}
```
### Features
- ✅ All endpoints wrapped in generic `ApiResponseDto`
- ✅ Full Swagger documentation (`@ApiOperation`, `@ApiResponse`)
- ✅ Proper HTTP methods and status codes
- ✅ Route parameters validated by NestJS
---
## 15. Integration with Client
### Client API Call Pattern
From the Cocos Creator client (`LevelDataManager.ts`), the server is called:
```typescript
// Client calls
const response = await HttpUtil.get<ApiResponse>('https://ilookai.cn/api/v1/wechat-game/levels');
// Server Response
{
"success": true,
"data": {
"levels": [
{
"level": 1,
"id": "level-001",
"imageUrl": "https://example.com/image.jpg",
"answer": "answer text",
"hint1": "hint 1",
"hint2": "hint 2",
"hint3": "hint 3",
"sortOrder": 0,
"createdAt": "2026-04-05T00:00:00Z",
"updatedAt": "2026-04-05T00:00:00Z"
},
...
],
"total": 50
},
"message": null,
"timestamp": "2026-04-05T10:30:00.000Z"
}
```
### Client Integration Points
1. **Level Loading**: Client calls `GET /api/v1/wechat-game/levels`
2. **Single Level Fetch**: Client calls `GET /api/v1/wechat-game/levels/:id` (though it doesn't currently)
3. **Config Management**: Client can call `GET /api/v1/wechat-game/configs` for game settings
---
## 16. Architecture Patterns
### 1. Dependency Injection
- NestJS constructor injection for all dependencies
- Repository interfaces decoupled from implementations
- Testable design
### 2. Repository Pattern
- Repositories abstract database access
- Implements `IRepository` interfaces
- Easy to mock for testing
### 3. Service Layer Pattern
- Services contain business logic
- Controllers delegate to services
- Services delegate to repositories
### 4. DTO Pattern
- DTOs separate API contracts from internal entities
- Validation and transformation in DTOs
- Swagger documentation on DTO fields
### 5. Global Exception Handling
- Single exception filter wraps all errors
- Consistent error response format
- Centralized logging
### 6. Configuration Management
- Environment-based configuration
- Typed environment variables
- Validation on startup
---
## 17. TypeORM Configuration Details
### Auto-Discovery
- Entities auto-loaded from: `**/*.entity{.ts,.js}`
- Repositories auto-created by `@InjectRepository()`
### Development Features
- ✅ Database synchronization (auto-create tables)
- ✅ Query logging to console
- ✅ SQL visible in development
### Production Features
- ✅ No auto-sync (safer)
- ✅ No query logging (better performance)
- ✅ Connection pooling via mysql2
### Column Mapping
- Snake_case in database: `image_url`, `sort_order`, `created_at`
- Camel case in entities: `imageUrl`, `sortOrder`, `createdAt`
- Auto-converted via TypeORM decorators
---
## 18. Security Patterns
### Global Validation Pipe
```typescript
new ValidationPipe({
whitelist: true, // ✅ Remove unknown properties
forbidNonWhitelisted: true, // ✅ Throw if unknown properties
transform: true, // ✅ Auto-transform types
})
```
### Considerations for Production
- ⚠️ No authentication middleware currently
- ⚠️ No rate limiting
- ⚠️ No request logging middleware
- ⚠️ CORS allows all origins (fine for public API)
- ⚠️ No input sanitization beyond whitelist
### Recommendations
1. Add JWT authentication for admin endpoints
2. Add rate limiting (e.g., `@nestjs/throttler`)
3. Add request logging (e.g., Morgan middleware)
4. Add input validation for text fields (sanitize HTML)
5. Add API key authentication for WeChat client
---
## 19. Performance Considerations
### Current Implementation
```
GET /api/v1/wechat-game/levels/:id
└─> getAllLevels() - Fetches ALL levels
└─> findAllOrdered() - Database query for all levels
└─> Memory search for :id - O(n) in application
```
**Problem**: `getLevelById()` fetches all levels, then searches in memory
### Optimization
```typescript
// BETTER - Direct database query
async getLevelById(id: string): Promise<LevelResponseDto> {
const level = await this.levelRepository.findById(id);
if (!level) throw new NotFoundException();
// Need to get level index for number calculation
const index = await this.levelRepository.getLevelIndex(id);
return this.toLevelResponseDto(level, index + 1);
}
```
### Database Indexing
- Consider adding index on `levels.sort_order` for sorting
- Consider adding index on `game_configs.config_key` for lookups
- Consider adding composite index on `game_configs(config_key, is_active)`
---
## 20. API Response Flow Diagram
```
┌─────────────────────────┐
│ HTTP Request │
│ GET /api/v1/... │
└────────────┬────────────┘
┌────────────▼────────────┐
│ Global Validation Pipe │ ← Validates path params
└────────────┬────────────┘
┌────────────▼────────────────────────┐
│ Controller Method │
│ @Get('levels/:id') │
└────────────┬────────────────────────┘
┌────────────▼────────────────────────┐
│ Service Method │
│ getLevelById(id) │
│ - Repository query │
│ - DTO mapping │
│ - Null check → NotFoundException │
└────────────┬────────────────────────┘
Success ───────────────┐
or Error │
│ │
┌────────────▼────────────────────────┐
│ Global Exception Filter (if error) │
│ Wraps in ApiResponseDto │
└────────────┬────────────────────────┘
┌────────────▼────────────────────────┐
│ ApiResponseDto.success() or error()│
│ Wraps in standard response format │
└────────────┬────────────────────────┘
┌────────────▼────────────────────────┐
│ HTTP Response (200, 400, 404, 500) │
│ JSON: {success, data, message} │
└─────────────────────────────────────┘
```
---
## 21. Key Findings
### Strengths ✅
1. **Clean architecture**: Proper separation of concerns (Controller → Service → Repository)
2. **Type safety**: Full TypeScript with DTOs and entity types
3. **Error handling**: Centralized exception filter with consistent format
4. **Documentation**: Swagger auto-documentation on all endpoints
5. **Configuration**: Type-safe environment variables with validation
6. **Database**: TypeORM with MySQL, auto-sync in dev
7. **Validation**: Global validation pipe with whitelist protection
8. **Modularity**: Feature-based module structure
### Potential Issues ⚠️
1. **Performance**: `getLevelById()` fetches all levels instead of direct query
2. **Authentication**: No auth mechanism for sensitive endpoints
3. **Rate limiting**: No rate limiting implemented
4. **Caching**: No caching layer (clients could cache locally)
5. **Pagination**: No pagination on large datasets
6. **Middleware**: Limited middleware (no logging, no request IDs)
7. **Testing**: No E2E tests implemented yet
### Missing Features 🔧
1. Authentication & Authorization (guards, JWT tokens)
2. POST/PUT/DELETE endpoints (read-only currently)
3. User management module
4. Score/points tracking
5. Level submission endpoints
6. Statistics/leaderboard endpoints
---
## 22. Database Schema
### levels table
```sql
CREATE TABLE levels (
id VARCHAR(191) PRIMARY KEY,
image_url VARCHAR(191) NOT NULL,
answer VARCHAR(191) NOT NULL,
hint1 VARCHAR(191),
hint2 VARCHAR(191),
hint3 VARCHAR(191),
sort_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_sort_order (sort_order)
);
```
### game_configs table
```sql
CREATE TABLE game_configs (
id CHAR(36) PRIMARY KEY, -- UUID
config_key VARCHAR(255) NOT NULL UNIQUE,
config_value TEXT NOT NULL,
description VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_config_key (config_key),
KEY idx_active (is_active)
);
```
---
## 23. Extension Points
### Adding User Authentication
```typescript
// 1. Create users.entity.ts
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
wxOpenId: string; // WeChat OpenID
@Column()
nickname: string;
@Column()
lives: number; // Current lives
@Column()
currentLevel: number; // Progress
}
// 2. Create user.module.ts with UserController, UserService, UserRepository
// 3. Add JWT guard to protected endpoints
// 4. Add user context to requests
```
### Adding Level Submission Endpoint
```typescript
// POST /api/v1/wechat-game/levels/:id/submit
@Post('levels/:id/submit')
async submitAnswer(
@Param('id') id: string,
@Body() dto: SubmitAnswerDto,
): Promise<ApiResponseDto<SubmitAnswerResponseDto>> {
// Validate answer
// Update user progress
// Award points/lives
// Return result
}
```
### Adding Caching
```typescript
// Import cache module
import { CacheModule } from '@nestjs/cache-manager';
// In WechatGameModule
@Module({
imports: [
CacheModule.register({
ttl: 3600, // 1 hour
}),
TypeOrmModule.forFeature([GameConfig, Level]),
],
})
// In service
constructor(
private cacheManager: Cache,
private levelRepository: LevelRepository,
) {}
async getAllLevels() {
const cached = await this.cacheManager.get('all_levels');
if (cached) return cached;
const levels = await this.levelRepository.findAllOrdered();
await this.cacheManager.set('all_levels', levels, 3600);
return levels;
}
```
---
## 24. Quick Reference
### Starting the Server
```bash
npm install # Install dependencies
npm run start:dev # Development with watch mode
npm run start:prod # Production mode
npm run build # Build TypeScript
npm test # Run tests
npm run test:e2e # End-to-end tests
```
### Project Commands
```bash
npm run format # Format code with Prettier
npm run lint # Lint and fix with ESLint
npm run test:watch # Watch tests
npm run test:cov # Test coverage
```
### Environment Setup
```bash
# .env file
NODE_ENV=development
PORT=3000
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=meme_user
DB_PASSWORD=password
DB_DATABASE=meme_mind
```
### Swagger UI
- **Location**: `http://localhost:3000/api/docs`
- **API Endpoint**: `http://localhost:3000/api/docs-json`
- Auto-updated from decorators
### Database Connection
```
Host: localhost
Port: 3306
Username: meme_user
Database: meme_mind
```
---
## Summary
MemeMind-Server is a well-structured NestJS backend with:
- ✅ Clean layered architecture (Controller → Service → Repository)
- ✅ Type-safe with full TypeScript
- ✅ Comprehensive error handling and validation
- ✅ Auto-generated Swagger documentation
- ✅ MySQL persistence with TypeORM
- ✅ Environment-based configuration
- ✅ Ready for feature expansion (auth, scoring, leaderboards)
The server currently provides **read-only access** to game levels and configurations, with endpoints designed to support the WeChat mini-game client's level loading and configuration management needs.
---
*Generated: 2026-04-05 | Last Updated: 2026-04-05*