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

510 lines
14 KiB
Markdown
Raw 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.
# 分享挑战关卡进度记录功能实现计划
> **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 实体**
```typescript
// 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**
```bash
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: 创建实体文件**
```typescript
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**
```bash
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**
```typescript
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**
```bash
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**
```typescript
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**
```typescript
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**
```bash
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: 读取现有文件确认内容**
```typescript
// src/modules/share/repositories/share-participant.repository.ts
// 在现有方法后添加 findByShareConfigAndParticipant 方法
```
- [ ] **Step 2: 添加新方法**
```typescript
async findByShareConfigAndParticipant(
shareConfigId: string,
participantId: string,
): Promise<ShareParticipant | null> {
return this.repository.findOne({ where: { shareConfigId, participantId } });
}
```
- [ ] **Step 3: Commit**
```bash
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**
```typescript
// 需要新增的 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 中添加方法**
```typescript
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**
```bash
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**
```typescript
import { ReportLevelProgressDto } from './dto/report-level-progress.dto';
import { ReportLevelProgressResponseDto } from './dto/share-level-progress-response.dto';
```
- [ ] **Step 2: 添加 Controller 方法**
```typescript
@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**
```bash
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**
```typescript
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**
```bash
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**
```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**
```bash
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 编译检查**
```bash
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 表创建