Files
MemeMind-Server/docs/superpowers/plans/2026-04-07-share-level-progress-implementation.md
richarjiang 3d52cfe843 feat(share): 分享挑战关卡进度记录功能
- 新增 Level.timeLimit 字段支持关卡时间限制
- 新增 ShareLevelProgress 实体记录单关通关进度
- 新增 ShareLevelProgressRepository
- 新增 DTO: ReportLevelProgressDto, ReportLevelProgressResponseDto
- 新增 POST /v1/share/progress 接口用于上报进度
- 支持仅首次通关有效判断
- 支持时间限制内通关判断
- 不可变模式更新进度记录
- 数据库迁移脚本

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 11:46:54 +08:00

14 KiB
Raw Blame History

分享挑战关卡进度记录功能实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 实现用户在分享挑战中的单关通关进度上报功能,支持记录通关时间、是否通过、时间限制判断。

Architecture: 在现有 Share 模块基础上,新增 ShareLevelProgress 实体和 Repository通过 ShareService.reportLevelProgress 方法处理业务逻辑,在 ShareController 新增 POST /v1/share/progress 接口。

Tech Stack: NestJS, TypeORM, MySQL


文件清单

操作 文件路径
新增 src/modules/share/entities/share-level-progress.entity.ts
新增 src/modules/share/repositories/share-level-progress.repository.ts
新增 src/modules/share/dto/report-level-progress.dto.ts
新增 src/modules/share/dto/share-level-progress-response.dto.ts
修改 src/modules/share/repositories/share-participant.repository.ts
修改 src/modules/share/share.service.ts
修改 src/modules/share/share.controller.ts
修改 src/modules/share/share.module.ts
修改 src/modules/wechat-game/entities/level.entity.ts
修改 src/database/migrations/*.sql

Task 1: Level 实体增加 timeLimit 字段

Files:

  • Modify: src/modules/wechat-game/entities/level.entity.ts

  • Step 1: 修改 Level 实体

// src/modules/wechat-game/entities/level.entity.ts
// 在 sortOrder 字段后添加

@Column({ type: 'int', name: 'time_limit', nullable: true, default: null })
timeLimit!: number | null;
  • Step 2: Commit
git add src/modules/wechat-game/entities/level.entity.ts
git commit -m "feat(level): add timeLimit field for level time restriction"

Task 2: 创建 ShareLevelProgress 实体

Files:

  • Create: src/modules/share/entities/share-level-progress.entity.ts

  • Step 1: 创建实体文件

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  ManyToOne,
  JoinColumn,
  Index,
  Unique,
} from 'typeorm';
import { ShareParticipant } from './share-participant.entity';
import { Level } from '../../wechat-game/entities/level.entity';

@Entity('share_level_progress')
@Unique('uq_participant_level', ['participantId', 'levelId'])
export class ShareLevelProgress {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Index('idx_slp_participant_id')
  @Column({ type: 'char', length: 36, name: 'participant_id' })
  participantId!: string;

  @ManyToOne(() => ShareParticipant)
  @JoinColumn({ name: 'participant_id' })
  participant!: ShareParticipant;

  @Index('idx_slp_level_id')
  @Column({ type: 'char', length: 191, name: 'level_id' })
  levelId!: string;

  @ManyToOne(() => Level)
  @JoinColumn({ name: 'level_id' })
  level!: Level;

  @Column({ type: 'tinyint', width: 1, default: 0 })
  passed!: boolean;

  @Column({ type: 'int', default: 0, name: 'time_spent' })
  timeSpent!: number;

  @CreateDateColumn({ name: 'completed_at' })
  completedAt!: Date;
}
  • Step 2: Commit
git add src/modules/share/entities/share-level-progress.entity.ts
git commit -m "feat(share): add ShareLevelProgress entity"

Task 3: 创建 ShareLevelProgressRepository

Files:

  • Create: src/modules/share/repositories/share-level-progress.repository.ts

  • Step 1: 创建 Repository

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ShareLevelProgress } from '../entities/share-level-progress.entity';

@Injectable()
export class ShareLevelProgressRepository {
  constructor(
    @InjectRepository(ShareLevelProgress)
    private readonly repository: Repository<ShareLevelProgress>,
  ) {}

  async findByParticipantId(participantId: string): Promise<ShareLevelProgress[]> {
    return this.repository.find({ where: { participantId } });
  }

  async findByParticipantAndLevel(
    participantId: string,
    levelId: string,
  ): Promise<ShareLevelProgress | null> {
    return this.repository.findOne({ where: { participantId, levelId } });
  }

  create(data: Partial<ShareLevelProgress>): ShareLevelProgress {
    return this.repository.create(data);
  }

  async save(progress: ShareLevelProgress): Promise<ShareLevelProgress> {
    return this.repository.save(progress);
  }
}
  • Step 2: Commit
git add src/modules/share/repositories/share-level-progress.repository.ts
git commit -m "feat(share): add ShareLevelProgressRepository"

Task 4: 创建 DTO 文件

Files:

  • Create: src/modules/share/dto/report-level-progress.dto.ts

  • Create: src/modules/share/dto/share-level-progress-response.dto.ts

  • Step 1: 创建 ReportLevelProgressDto

import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator';

export class ReportLevelProgressDto {
  @ApiProperty({ description: '分享码' })
  @IsString()
  @IsNotEmpty()
  shareCode!: string;

  @ApiProperty({ description: '关卡 ID' })
  @IsString()
  @IsNotEmpty()
  levelId!: string;

  @ApiProperty({ description: '是否通过' })
  @IsBoolean()
  passed!: boolean;

  @ApiProperty({ description: '通关时间(秒)' })
  @IsNumber()
  @Min(0)
  timeSpent!: number;
}
  • Step 2: 创建 ReportLevelProgressResponseDto
import { ApiProperty } from '@nestjs/swagger';

export class ReportLevelProgressResponseDto {
  @ApiProperty({ description: '是否通过' })
  passed!: boolean;

  @ApiProperty({ description: '该关卡时间限制null 表示无限制' })
  timeLimit!: number | null;

  @ApiProperty({ description: '是否在时间限制内通过' })
  withinTimeLimit!: boolean;
}
  • Step 3: Commit
git add src/modules/share/dto/report-level-progress.dto.ts src/modules/share/dto/share-level-progress-response.dto.ts
git commit -m "feat(share): add DTOs for level progress reporting"

Task 5: ShareParticipantRepository 补充方法

Files:

  • Modify: src/modules/share/repositories/share-participant.repository.ts

  • Step 1: 读取现有文件确认内容

// src/modules/share/repositories/share-participant.repository.ts
// 在现有方法后添加 findByShareConfigAndParticipant 方法
  • Step 2: 添加新方法
async findByShareConfigAndParticipant(
  shareConfigId: string,
  participantId: string,
): Promise<ShareParticipant | null> {
  return this.repository.findOne({ where: { shareConfigId, participantId } });
}
  • Step 3: Commit
git add src/modules/share/repositories/share-participant.repository.ts
git commit -m "feat(share): add findByShareConfigAndParticipant to ShareParticipantRepository"

Task 6: ShareService 新增 reportLevelProgress 方法

Files:

  • Modify: src/modules/share/share.service.ts

  • Step 1: 读取现有文件确认 import 和 constructor

// 需要新增的 imports
import { ShareLevelProgressRepository } from './repositories/share-level-progress.repository';
import { ReportLevelProgressDto } from './dto/report-level-progress.dto';
import { ReportLevelProgressResponseDto } from './dto/share-level-progress-response.dto';
  • Step 2: 在 ShareService 中添加方法
async reportLevelProgress(
  userId: string,
  dto: ReportLevelProgressDto,
): Promise<ReportLevelProgressResponseDto> {
  // 1. 查找分享配置
  const config = await this.shareConfigRepository.findByShareCode(dto.shareCode);
  if (!config) {
    throw new NotFoundException('分享不存在或已过期');
  }

  // 2. 查找或创建 ShareParticipant
  let participant = await this.shareParticipantRepository.findByShareConfigAndParticipant(
    config.id,
    userId,
  );
  if (!participant) {
    participant = await this.shareParticipantRepository.create({
      shareConfigId: config.id,
      participantId: userId,
    });
    participant = await this.shareParticipantRepository.save(participant);
  }

  // 3. 如果 passed=true检查是否已有通关记录
  if (dto.passed) {
    const existing = await this.shareLevelProgressRepository.findByParticipantAndLevel(
      participant.id,
      dto.levelId,
    );
    if (existing?.passed) {
      return {
        passed: true,
        timeLimit: null,
        withinTimeLimit: false,
      };
    }
  }

  // 4. 查找关卡获取时间限制
  const level = await this.levelRepository.findById(dto.levelId);
  if (!level) {
    throw new NotFoundException('关卡不存在');
  }

  // 5. 判断是否在时间限制内通过
  const withinTimeLimit = dto.passed
    ? level.timeLimit === null || dto.timeSpent <= level.timeLimit
    : false;

  // 6. 创建或更新进度
  let progress = await this.shareLevelProgressRepository.findByParticipantAndLevel(
    participant.id,
    dto.levelId,
  );

  if (progress) {
    progress.passed = dto.passed;
    progress.timeSpent = dto.timeSpent;
    if (dto.passed) {
      progress.completedAt = new Date();
    }
  } else {
    progress = this.shareLevelProgressRepository.create({
      participantId: participant.id,
      levelId: dto.levelId,
      passed: dto.passed,
      timeSpent: dto.timeSpent,
      completedAt: dto.passed ? new Date() : null,
    });
  }

  await this.shareLevelProgressRepository.save(progress);

  return {
    passed: dto.passed,
    timeLimit: level.timeLimit,
    withinTimeLimit,
  };
}
  • Step 3: Commit
git add src/modules/share/share.service.ts
git commit -m "feat(share): add reportLevelProgress method"

Task 7: ShareController 新增接口

Files:

  • Modify: src/modules/share/share.controller.ts

  • Step 1: 添加 import

import { ReportLevelProgressDto } from './dto/report-level-progress.dto';
import { ReportLevelProgressResponseDto } from './dto/share-level-progress-response.dto';
  • Step 2: 添加 Controller 方法
@Post('progress')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({
  summary: '上报单关进度',
  description: '用户在分享挑战中通关后上报进度,仅首次通关(passed=true)有效',
})
@ApiResponse({ status: 200, description: '成功' })
@ApiResponse({ status: 404, description: '分享或关卡不存在' })
async reportLevelProgress(
  @CurrentUser() user: JwtPayload,
  @Body() dto: ReportLevelProgressDto,
): Promise<ApiResponseDto<ReportLevelProgressResponseDto>> {
  const data = await this.shareService.reportLevelProgress(user.sub, dto);
  return ApiResponseDto.success(data);
}
  • Step 3: Commit
git add src/modules/share/share.controller.ts
git commit -m "feat(share): add POST /v1/share/progress endpoint"

Task 8: ShareModule 更新

Files:

  • Modify: src/modules/share/share.module.ts

  • Step 1: 修改 import 和 providers

import { ShareLevelProgress } from './entities/share-level-progress.entity';
import { ShareLevelProgressRepository } from './repositories/share-level-progress.repository';

// TypeOrmModule.forFeature 中添加 ShareLevelProgress
TypeOrmModule.forFeature([ShareConfig, ShareParticipant, ShareLevelProgress]),

// providers 中添加 ShareLevelProgressRepository
providers: [
  ShareService,
  ShareConfigRepository,
  ShareParticipantRepository,
  ShareLevelProgressRepository,
],
  • Step 2: Commit
git add src/modules/share/share.module.ts
git commit -m "feat(share): register ShareLevelProgress in ShareModule"

Task 9: 数据库迁移 SQL

Files:

  • Create: src/database/migrations/002_add_share_level_progress.sql(或按项目规范)

  • Step 1: 创建迁移 SQL

-- Level 表增加 time_limit 字段
ALTER TABLE levels
ADD COLUMN time_limit INT DEFAULT NULL COMMENT '通关时间限制NULL 表示无限制'
AFTER sort_order;

-- 新建 share_level_progress 表
CREATE TABLE IF NOT EXISTS share_level_progress (
  id              CHAR(36) PRIMARY KEY,
  participant_id  CHAR(36) NOT NULL COMMENT '关联 share_participants.id',
  level_id        CHAR(191) NOT NULL COMMENT '关卡ID',
  passed          TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否通过',
  time_spent      INT NOT NULL DEFAULT 0 COMMENT '通关时间(秒)',
  completed_at    DATETIME DEFAULT NULL COMMENT '通关时间戳',

  UNIQUE KEY uq_participant_level (participant_id, level_id),
  INDEX idx_slp_participant_id (participant_id),
  INDEX idx_slp_level_id (level_id),

  FOREIGN KEY (participant_id) REFERENCES share_participants(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  • Step 2: Commit
git add src/database/migrations/002_add_share_level_progress.sql
git commit -m "chore: add migration for share level progress tables"

Task 10: 编译验证

  • Step 1: 运行 TypeScript 编译检查
cd /Users/richard/Documents/code/xieyingeng/MemeMind-Server && npx tsc --noEmit

预期:无编译错误

  • Step 2: 如果有错误,修复后重新编译

  • Step 3: Commit如果有代码修改


自检清单

完成实现后,对照设计文档检查:

  • Level 实体有 timeLimit 字段
  • ShareLevelProgress 实体有 participantId, levelId, passed, timeSpent, completedAt 字段
  • ShareLevelProgress 有唯一索引 (participantId, levelId)
  • ShareLevelProgressRepository 有 findByParticipantId, findByParticipantAndLevel, create, save 方法
  • ShareParticipantRepository 有 findByShareConfigAndParticipant 方法
  • ShareService.reportLevelProgress 实现了完整业务逻辑
  • Controller 接口为 POST /v1/share/progress
  • DTO 包含 shareCode, levelId, passed, timeSpent
  • 响应 DTO 包含 passed, timeLimit, withinTimeLimit
  • ShareModule 注册了 ShareLevelProgress 实体和 Repository
  • 数据库迁移 SQL 包含 Level 表修改和 share_level_progress 表创建