feat: 支持关卡配置分享
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.15.1",
|
"class-validator": "^0.15.1",
|
||||||
"mysql2": "^3.19.1",
|
"mysql2": "^3.19.1",
|
||||||
|
"nanoid": "^3.3.11",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"typeorm": "^0.3.28"
|
"typeorm": "^0.3.28"
|
||||||
@@ -78,4 +79,4 @@
|
|||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -41,6 +41,9 @@ importers:
|
|||||||
mysql2:
|
mysql2:
|
||||||
specifier: ^3.19.1
|
specifier: ^3.19.1
|
||||||
version: 3.19.1(@types/node@22.19.15)
|
version: 3.19.1(@types/node@22.19.15)
|
||||||
|
nanoid:
|
||||||
|
specifier: ^3.3.11
|
||||||
|
version: 3.3.11
|
||||||
reflect-metadata:
|
reflect-metadata:
|
||||||
specifier: ^0.2.2
|
specifier: ^0.2.2
|
||||||
version: 0.2.2
|
version: 0.2.2
|
||||||
@@ -2492,6 +2495,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==}
|
resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
|
nanoid@3.3.11:
|
||||||
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
napi-postinstall@0.3.4:
|
napi-postinstall@0.3.4:
|
||||||
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
|
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||||
@@ -6047,6 +6055,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lru.min: 1.1.4
|
lru.min: 1.1.4
|
||||||
|
|
||||||
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
napi-postinstall@0.3.4: {}
|
napi-postinstall@0.3.4: {}
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { AppConfigModule } from './config/config.module';
|
import { AppConfigModule } from './config/config.module';
|
||||||
import { WechatGameModule } from './modules/wechat-game/wechat-game.module';
|
import { WechatGameModule } from './modules/wechat-game/wechat-game.module';
|
||||||
import { AuthModule } from './modules/auth/auth.module';
|
import { AuthModule } from './modules/auth/auth.module';
|
||||||
|
import { ShareModule } from './modules/share/share.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -26,6 +27,7 @@ import { AuthModule } from './modules/auth/auth.module';
|
|||||||
}),
|
}),
|
||||||
WechatGameModule,
|
WechatGameModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
ShareModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
27
src/modules/share/dto/create-share.dto.ts
Normal file
27
src/modules/share/dto/create-share.dto.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
IsString,
|
||||||
|
IsNotEmpty,
|
||||||
|
MaxLength,
|
||||||
|
ArrayMinSize,
|
||||||
|
ArrayMaxSize,
|
||||||
|
IsArray,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateShareDto {
|
||||||
|
@ApiProperty({ description: '分享标题', example: '我的挑战' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: '标题不能为空' })
|
||||||
|
@MaxLength(100, { message: '标题不能超过100个字符' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '6个关卡ID',
|
||||||
|
example: ['id1', 'id2', 'id3', 'id4', 'id5', 'id6'],
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(6, { message: '需要恰好6个关卡' })
|
||||||
|
@ArrayMaxSize(6, { message: '需要恰好6个关卡' })
|
||||||
|
@IsString({ each: true })
|
||||||
|
levelIds: string[];
|
||||||
|
}
|
||||||
49
src/modules/share/dto/share-response.dto.ts
Normal file
49
src/modules/share/dto/share-response.dto.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateShareResponseDto {
|
||||||
|
@ApiProperty({ description: '分享码' })
|
||||||
|
shareCode: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分享标题' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关卡数量' })
|
||||||
|
levelCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShareLevelDto {
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
level: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
imageUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
answer: string;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
hint1: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
hint2: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
hint3: string | null;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JoinShareResponseDto {
|
||||||
|
@ApiProperty({ description: '分享码' })
|
||||||
|
shareCode: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分享标题' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关卡列表', type: [ShareLevelDto] })
|
||||||
|
levels: ShareLevelDto[];
|
||||||
|
}
|
||||||
46
src/modules/share/entities/share-config.entity.ts
Normal file
46
src/modules/share/entities/share-config.entity.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from '../../auth/entities/user.entity';
|
||||||
|
import { ShareParticipant } from './share-participant.entity';
|
||||||
|
|
||||||
|
@Entity('share_configs')
|
||||||
|
export class ShareConfig {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index('idx_share_code', { unique: true })
|
||||||
|
@Column({ type: 'varchar', length: 8, name: 'share_code' })
|
||||||
|
shareCode: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100 })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 191, name: 'sharer_id' })
|
||||||
|
sharerId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User)
|
||||||
|
@JoinColumn({ name: 'sharer_id' })
|
||||||
|
sharer: User;
|
||||||
|
|
||||||
|
/** 有序 JSON 数组,存储 6 个关卡 ID */
|
||||||
|
@Column({ type: 'json', name: 'level_ids' })
|
||||||
|
levelIds: string[];
|
||||||
|
|
||||||
|
@OneToMany(() => ShareParticipant, (p) => p.shareConfig)
|
||||||
|
participants: ShareParticipant[];
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
37
src/modules/share/entities/share-participant.entity.ts
Normal file
37
src/modules/share/entities/share-participant.entity.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
Index,
|
||||||
|
Unique,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from '../../auth/entities/user.entity';
|
||||||
|
import { ShareConfig } from './share-config.entity';
|
||||||
|
|
||||||
|
@Entity('share_participants')
|
||||||
|
@Unique('uq_share_participant', ['shareConfigId', 'participantId'])
|
||||||
|
export class ShareParticipant {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index('idx_share_config_id')
|
||||||
|
@Column({ type: 'varchar', length: 191, name: 'share_config_id' })
|
||||||
|
shareConfigId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => ShareConfig, (sc) => sc.participants)
|
||||||
|
@JoinColumn({ name: 'share_config_id' })
|
||||||
|
shareConfig: ShareConfig;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 191, name: 'participant_id' })
|
||||||
|
participantId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User)
|
||||||
|
@JoinColumn({ name: 'participant_id' })
|
||||||
|
participant: User;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
21
src/modules/share/repositories/share-config.repository.ts
Normal file
21
src/modules/share/repositories/share-config.repository.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { ShareConfig } from '../entities/share-config.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ShareConfigRepository {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ShareConfig)
|
||||||
|
private readonly repository: Repository<ShareConfig>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(data: Partial<ShareConfig>): Promise<ShareConfig> {
|
||||||
|
const entity = this.repository.create(data);
|
||||||
|
return this.repository.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByShareCode(code: string): Promise<ShareConfig | null> {
|
||||||
|
return this.repository.findOne({ where: { shareCode: code } });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { ShareParticipant } from '../entities/share-participant.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ShareParticipantRepository {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ShareParticipant)
|
||||||
|
private readonly repository: Repository<ShareParticipant>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** 添加参与者(已存在则忽略) */
|
||||||
|
async addParticipant(
|
||||||
|
shareConfigId: string,
|
||||||
|
participantId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.repository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(ShareParticipant)
|
||||||
|
.values({ shareConfigId, participantId })
|
||||||
|
.orIgnore()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
async countByShareConfigId(shareConfigId: string): Promise<number> {
|
||||||
|
return this.repository.count({ where: { shareConfigId } });
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/modules/share/share.controller.ts
Normal file
54
src/modules/share/share.controller.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Body, Controller, Param, Post, UseGuards } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOperation,
|
||||||
|
ApiResponse,
|
||||||
|
ApiTags,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import { ShareService } from './share.service';
|
||||||
|
import { CreateShareDto } from './dto/create-share.dto';
|
||||||
|
import {
|
||||||
|
CreateShareResponseDto,
|
||||||
|
JoinShareResponseDto,
|
||||||
|
} from './dto/share-response.dto';
|
||||||
|
import { ApiResponseDto } from '../../common/dto/api-response.dto';
|
||||||
|
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||||
|
import type { JwtPayload } from '../../common/guards/jwt-auth.guard';
|
||||||
|
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||||
|
|
||||||
|
@ApiTags('分享挑战')
|
||||||
|
@Controller('v1/share')
|
||||||
|
export class ShareController {
|
||||||
|
constructor(private readonly shareService: ShareService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: '创建分享', description: '选择6关+标题,生成分享码' })
|
||||||
|
@ApiResponse({ status: 201, description: '创建成功' })
|
||||||
|
@ApiResponse({ status: 400, description: '参数错误' })
|
||||||
|
async createShare(
|
||||||
|
@CurrentUser() user: JwtPayload,
|
||||||
|
@Body() dto: CreateShareDto,
|
||||||
|
): Promise<ApiResponseDto<CreateShareResponseDto>> {
|
||||||
|
const data = await this.shareService.createShare(user.sub, dto);
|
||||||
|
return ApiResponseDto.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':code/join')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '接受分享',
|
||||||
|
description: '通过分享码加入挑战并获取关卡数据',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: '成功' })
|
||||||
|
@ApiResponse({ status: 404, description: '分享不存在' })
|
||||||
|
async joinShare(
|
||||||
|
@CurrentUser() user: JwtPayload,
|
||||||
|
@Param('code') code: string,
|
||||||
|
): Promise<ApiResponseDto<JoinShareResponseDto>> {
|
||||||
|
const data = await this.shareService.joinShare(user.sub, code);
|
||||||
|
return ApiResponseDto.success(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/modules/share/share.module.ts
Normal file
21
src/modules/share/share.module.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ShareController } from './share.controller';
|
||||||
|
import { ShareService } from './share.service';
|
||||||
|
import { ShareConfig } from './entities/share-config.entity';
|
||||||
|
import { ShareParticipant } from './entities/share-participant.entity';
|
||||||
|
import { ShareConfigRepository } from './repositories/share-config.repository';
|
||||||
|
import { ShareParticipantRepository } from './repositories/share-participant.repository';
|
||||||
|
import { WechatGameModule } from '../wechat-game/wechat-game.module';
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([ShareConfig, ShareParticipant]),
|
||||||
|
WechatGameModule,
|
||||||
|
AuthModule,
|
||||||
|
],
|
||||||
|
controllers: [ShareController],
|
||||||
|
providers: [ShareService, ShareConfigRepository, ShareParticipantRepository],
|
||||||
|
})
|
||||||
|
export class ShareModule {}
|
||||||
111
src/modules/share/share.service.ts
Normal file
111
src/modules/share/share.service.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { ShareConfigRepository } from './repositories/share-config.repository';
|
||||||
|
import { ShareParticipantRepository } from './repositories/share-participant.repository';
|
||||||
|
import { LevelRepository } from '../wechat-game/repositories/level.repository';
|
||||||
|
import { CreateShareDto } from './dto/create-share.dto';
|
||||||
|
import {
|
||||||
|
CreateShareResponseDto,
|
||||||
|
JoinShareResponseDto,
|
||||||
|
ShareLevelDto,
|
||||||
|
} from './dto/share-response.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ShareService {
|
||||||
|
constructor(
|
||||||
|
private readonly shareConfigRepository: ShareConfigRepository,
|
||||||
|
private readonly shareParticipantRepository: ShareParticipantRepository,
|
||||||
|
private readonly levelRepository: LevelRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createShare(
|
||||||
|
userId: string,
|
||||||
|
dto: CreateShareDto,
|
||||||
|
): Promise<CreateShareResponseDto> {
|
||||||
|
const uniqueIds = [...new Set(dto.levelIds)];
|
||||||
|
if (uniqueIds.length !== 6) {
|
||||||
|
throw new BadRequestException('关卡ID不能重复,需要恰好6个不同的关卡');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单次查询验证所有关卡存在
|
||||||
|
const levels = await this.levelRepository.findByIds(uniqueIds);
|
||||||
|
if (levels.length !== uniqueIds.length) {
|
||||||
|
const foundIds = new Set(levels.map((l) => l.id));
|
||||||
|
const missing = uniqueIds.filter((id) => !foundIds.has(id));
|
||||||
|
throw new NotFoundException(`以下关卡不存在: ${missing.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 8 位分享码(碰撞重试)
|
||||||
|
let shareCode: string;
|
||||||
|
let attempts = 0;
|
||||||
|
do {
|
||||||
|
shareCode = nanoid(8);
|
||||||
|
const existing =
|
||||||
|
await this.shareConfigRepository.findByShareCode(shareCode);
|
||||||
|
if (!existing) break;
|
||||||
|
attempts++;
|
||||||
|
} while (attempts < 3);
|
||||||
|
|
||||||
|
if (attempts >= 3) {
|
||||||
|
throw new BadRequestException('生成分享码失败,请重试');
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await this.shareConfigRepository.create({
|
||||||
|
shareCode,
|
||||||
|
title: dto.title,
|
||||||
|
sharerId: userId,
|
||||||
|
levelIds: dto.levelIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
shareCode: config.shareCode,
|
||||||
|
title: config.title,
|
||||||
|
levelCount: config.levelIds.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async joinShare(
|
||||||
|
userId: string,
|
||||||
|
code: string,
|
||||||
|
): Promise<JoinShareResponseDto> {
|
||||||
|
const config = await this.shareConfigRepository.findByShareCode(code);
|
||||||
|
if (!config) {
|
||||||
|
throw new NotFoundException('分享不存在或已过期');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId !== config.sharerId) {
|
||||||
|
await this.shareParticipantRepository.addParticipant(config.id, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单次查询获取所有关卡,再按 levelIds 顺序排列
|
||||||
|
const allLevels = await this.levelRepository.findByIds(config.levelIds);
|
||||||
|
const levelMap = new Map(allLevels.map((l) => [l.id, l]));
|
||||||
|
|
||||||
|
const levels: ShareLevelDto[] = config.levelIds.map((id, index) => {
|
||||||
|
const level = levelMap.get(id);
|
||||||
|
if (!level) {
|
||||||
|
throw new NotFoundException(`关卡 ${id} 不存在`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: level.id,
|
||||||
|
level: index + 1,
|
||||||
|
imageUrl: level.imageUrl,
|
||||||
|
answer: level.answer,
|
||||||
|
hint1: level.hint1,
|
||||||
|
hint2: level.hint2,
|
||||||
|
hint3: level.hint3,
|
||||||
|
sortOrder: level.sortOrder,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
shareCode: config.shareCode,
|
||||||
|
title: config.title,
|
||||||
|
levels,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@ import { Level } from '../entities/level.entity';
|
|||||||
export interface ILevelRepository {
|
export interface ILevelRepository {
|
||||||
findAll(): Promise<Level[]>;
|
findAll(): Promise<Level[]>;
|
||||||
findById(id: string): Promise<Level | null>;
|
findById(id: string): Promise<Level | null>;
|
||||||
|
findByIds(ids: string[]): Promise<Level[]>;
|
||||||
findAllOrdered(): Promise<Level[]>;
|
findAllOrdered(): Promise<Level[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
import { Level } from '../entities/level.entity';
|
import { Level } from '../entities/level.entity';
|
||||||
import { ILevelRepository } from './level.repository.interface';
|
import { ILevelRepository } from './level.repository.interface';
|
||||||
|
|
||||||
@@ -19,6 +19,11 @@ export class LevelRepository implements ILevelRepository {
|
|||||||
return this.repository.findOne({ where: { id } });
|
return this.repository.findOne({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findByIds(ids: string[]): Promise<Level[]> {
|
||||||
|
if (ids.length === 0) return [];
|
||||||
|
return this.repository.find({ where: { id: In(ids) } });
|
||||||
|
}
|
||||||
|
|
||||||
async findAllOrdered(): Promise<Level[]> {
|
async findAllOrdered(): Promise<Level[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
order: { sortOrder: 'ASC' },
|
order: { sortOrder: 'ASC' },
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ import { LevelRepository } from './repositories/level.repository';
|
|||||||
imports: [TypeOrmModule.forFeature([GameConfig, Level])],
|
imports: [TypeOrmModule.forFeature([GameConfig, Level])],
|
||||||
controllers: [WechatGameController],
|
controllers: [WechatGameController],
|
||||||
providers: [WechatGameService, GameConfigRepository, LevelRepository],
|
providers: [WechatGameService, GameConfigRepository, LevelRepository],
|
||||||
exports: [WechatGameService],
|
exports: [WechatGameService, LevelRepository],
|
||||||
})
|
})
|
||||||
export class WechatGameModule {}
|
export class WechatGameModule {}
|
||||||
|
|||||||
Reference in New Issue
Block a user