From 46368b8c8966f355cd8f65e0dca9c9d37ba8be96 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Sun, 15 Mar 2026 15:52:51 +0800 Subject: [PATCH] feat(wechat-game): add level list API endpoints Add CRUD endpoints for game levels with level field in response: - GET /v1/wechat-game/levels - list all levels ordered by sort_order - GET /v1/wechat-game/levels/:id - get single level by ID New files: - Level entity mapping to levels table - LevelRepository with ordered query support - LevelResponseDto with level field (1-based index from sort_order) --- .../wechat-game/dto/level-response.dto.ts | 41 ++++++++++++++ .../wechat-game/entities/level.entity.ts | 37 +++++++++++++ .../level.repository.interface.ts | 7 +++ .../repositories/level.repository.ts | 27 ++++++++++ .../wechat-game/wechat-game.controller.ts | 39 +++++++++++++- src/modules/wechat-game/wechat-game.module.ts | 6 ++- .../wechat-game/wechat-game.service.ts | 54 ++++++++++++++++++- 7 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 src/modules/wechat-game/dto/level-response.dto.ts create mode 100644 src/modules/wechat-game/entities/level.entity.ts create mode 100644 src/modules/wechat-game/repositories/level.repository.interface.ts create mode 100644 src/modules/wechat-game/repositories/level.repository.ts diff --git a/src/modules/wechat-game/dto/level-response.dto.ts b/src/modules/wechat-game/dto/level-response.dto.ts new file mode 100644 index 0000000..4816a0e --- /dev/null +++ b/src/modules/wechat-game/dto/level-response.dto.ts @@ -0,0 +1,41 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class LevelResponseDto { + @ApiProperty({ description: '关卡编号' }) + level: number; + + @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; +} diff --git a/src/modules/wechat-game/entities/level.entity.ts b/src/modules/wechat-game/entities/level.entity.ts new file mode 100644 index 0000000..02b2401 --- /dev/null +++ b/src/modules/wechat-game/entities/level.entity.ts @@ -0,0 +1,37 @@ +import { + Entity, + PrimaryColumn, + Column, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity('levels') +export class Level { + @PrimaryColumn({ type: 'varchar', length: 191 }) + id: string; + + @Column({ type: 'varchar', length: 191, name: 'image_url' }) + imageUrl: string; + + @Column({ type: 'varchar', length: 191 }) + answer: string; + + @Column({ type: 'varchar', length: 191, nullable: true }) + hint1: string | null; + + @Column({ type: 'varchar', length: 191, nullable: true }) + hint2: string | null; + + @Column({ type: 'varchar', length: 191, nullable: true }) + hint3: string | null; + + @Column({ type: 'int', name: 'sort_order', default: 0 }) + sortOrder: number; + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; +} diff --git a/src/modules/wechat-game/repositories/level.repository.interface.ts b/src/modules/wechat-game/repositories/level.repository.interface.ts new file mode 100644 index 0000000..474298b --- /dev/null +++ b/src/modules/wechat-game/repositories/level.repository.interface.ts @@ -0,0 +1,7 @@ +import { Level } from '../entities/level.entity'; + +export interface ILevelRepository { + findAll(): Promise; + findById(id: string): Promise; + findAllOrdered(): Promise; +} diff --git a/src/modules/wechat-game/repositories/level.repository.ts b/src/modules/wechat-game/repositories/level.repository.ts new file mode 100644 index 0000000..1846aa4 --- /dev/null +++ b/src/modules/wechat-game/repositories/level.repository.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Level } from '../entities/level.entity'; +import { ILevelRepository } from './level.repository.interface'; + +@Injectable() +export class LevelRepository implements ILevelRepository { + constructor( + @InjectRepository(Level) + private readonly repository: Repository, + ) {} + + async findAll(): Promise { + return this.repository.find(); + } + + async findById(id: string): Promise { + return this.repository.findOne({ where: { id } }); + } + + async findAllOrdered(): Promise { + return this.repository.find({ + order: { sortOrder: 'ASC' }, + }); + } +} diff --git a/src/modules/wechat-game/wechat-game.controller.ts b/src/modules/wechat-game/wechat-game.controller.ts index e886811..0162143 100644 --- a/src/modules/wechat-game/wechat-game.controller.ts +++ b/src/modules/wechat-game/wechat-game.controller.ts @@ -5,6 +5,10 @@ import { GameConfigResponseDto, GameConfigListResponseDto, } from './dto/game-config-response.dto'; +import { + LevelResponseDto, + LevelListResponseDto, +} from './dto/level-response.dto'; import { ApiResponseDto } from '../../common/dto/api-response.dto'; @ApiTags('微信小游戏') @@ -13,7 +17,10 @@ export class WechatGameController { constructor(private readonly wechatGameService: WechatGameService) {} @Get('configs') - @ApiOperation({ summary: '获取所有游戏配置', description: '获取所有激活的游戏配置列表' }) + @ApiOperation({ + summary: '获取所有游戏配置', + description: '获取所有激活的游戏配置列表', + }) @ApiResponse({ status: 200, description: '成功获取配置列表' }) async getAllConfigs(): Promise> { const data = await this.wechatGameService.getAllConfigs(); @@ -21,7 +28,10 @@ export class WechatGameController { } @Get('configs/:key') - @ApiOperation({ summary: '根据key获取配置', description: '根据配置键名获取单个游戏配置' }) + @ApiOperation({ + summary: '根据key获取配置', + description: '根据配置键名获取单个游戏配置', + }) @ApiResponse({ status: 200, description: '成功获取配置' }) @ApiResponse({ status: 404, description: '配置不存在' }) async getConfigByKey( @@ -30,4 +40,29 @@ export class WechatGameController { const data = await this.wechatGameService.getConfigByKey(key); return ApiResponseDto.success(data); } + + @Get('levels') + @ApiOperation({ + summary: '获取所有关卡', + description: '获取所有关卡列表,按sort_order排序', + }) + @ApiResponse({ status: 200, description: '成功获取关卡列表' }) + async getAllLevels(): Promise> { + const data = await this.wechatGameService.getAllLevels(); + return ApiResponseDto.success(data); + } + + @Get('levels/:id') + @ApiOperation({ + summary: '根据ID获取关卡', + description: '根据关卡ID获取单个关卡信息', + }) + @ApiResponse({ status: 200, description: '成功获取关卡' }) + @ApiResponse({ status: 404, description: '关卡不存在' }) + async getLevelById( + @Param('id') id: string, + ): Promise> { + const data = await this.wechatGameService.getLevelById(id); + return ApiResponseDto.success(data); + } } diff --git a/src/modules/wechat-game/wechat-game.module.ts b/src/modules/wechat-game/wechat-game.module.ts index f5c5729..7865c1c 100644 --- a/src/modules/wechat-game/wechat-game.module.ts +++ b/src/modules/wechat-game/wechat-game.module.ts @@ -3,12 +3,14 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { WechatGameController } from './wechat-game.controller'; import { WechatGameService } from './wechat-game.service'; import { GameConfig } from './entities/game-config.entity'; +import { Level } from './entities/level.entity'; import { GameConfigRepository } from './repositories/game-config.repository'; +import { LevelRepository } from './repositories/level.repository'; @Module({ - imports: [TypeOrmModule.forFeature([GameConfig])], + imports: [TypeOrmModule.forFeature([GameConfig, Level])], controllers: [WechatGameController], - providers: [WechatGameService, GameConfigRepository], + providers: [WechatGameService, GameConfigRepository, LevelRepository], exports: [WechatGameService], }) export class WechatGameModule {} diff --git a/src/modules/wechat-game/wechat-game.service.ts b/src/modules/wechat-game/wechat-game.service.ts index 40a972c..5671bf9 100644 --- a/src/modules/wechat-game/wechat-game.service.ts +++ b/src/modules/wechat-game/wechat-game.service.ts @@ -1,13 +1,21 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { GameConfigRepository } from './repositories/game-config.repository'; +import { LevelRepository } from './repositories/level.repository'; import { GameConfigResponseDto, GameConfigListResponseDto, } from './dto/game-config-response.dto'; +import { + LevelResponseDto, + LevelListResponseDto, +} from './dto/level-response.dto'; @Injectable() export class WechatGameService { - constructor(private readonly gameConfigRepository: GameConfigRepository) {} + constructor( + private readonly gameConfigRepository: GameConfigRepository, + private readonly levelRepository: LevelRepository, + ) {} async getAllConfigs(): Promise { const configs = await this.gameConfigRepository.findActiveConfigs(); @@ -28,7 +36,31 @@ export class WechatGameService { return this.toResponseDto(config); } - private toResponseDto(config: import('./entities/game-config.entity').GameConfig): GameConfigResponseDto { + async getAllLevels(): Promise { + const levels = await this.levelRepository.findAllOrdered(); + + return { + levels: levels.map((level, index) => + this.toLevelResponseDto(level, index + 1), + ), + total: levels.length, + }; + } + + async getLevelById(id: string): Promise { + 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); + } + + private toResponseDto( + config: import('./entities/game-config.entity').GameConfig, + ): GameConfigResponseDto { return { id: config.id, configKey: config.configKey, @@ -39,4 +71,22 @@ export class WechatGameService { updatedAt: config.updatedAt, }; } + + private toLevelResponseDto( + level: import('./entities/level.entity').Level, + levelNumber: number, + ): LevelResponseDto { + return { + level: levelNumber, + 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, + }; + } }