feat(badges): 添加用户勋章系统,支持睡眠挑战勋章自动授予

实现完整的用户勋章功能模块:
- 新增 BadgeConfig 和 UserBadge 数据模型,支持勋章配置和用户勋章管理
- 新增 BadgeService 服务,提供勋章授予、查询、展示状态管理等核心功能
- 在挑战服务中集成勋章授予逻辑,完成首次睡眠打卡授予 goodSleep 勋章,完成睡眠挑战授予 sleepChallengeMonth 勋章
- 新增用户勋章相关接口:获取用户勋章列表、获取可用勋章列表、标记勋章已展示
- 支持勋章分类(睡眠、运动、饮食等)、排序、启用状态管理
- 支持勋章来源追踪(挑战、系统、手动授予)和元数据记录
This commit is contained in:
richarjiang
2025-11-14 17:08:02 +08:00
parent f04c2ccd5d
commit 7b4d7c4459
8 changed files with 792 additions and 3 deletions

157
src/users/dto/badge.dto.ts Normal file
View File

@@ -0,0 +1,157 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, IsString, IsBoolean, IsDateString, IsNotEmpty } from 'class-validator';
import { BaseResponseDto } from '../../base.dto';
// 勋章基本信息
export class BadgeInfoDto {
@ApiProperty({ description: '勋章代码', example: 'goodSleep' })
code: string;
@ApiProperty({ description: '勋章名称', example: '好眠达人' })
name: string;
@ApiProperty({ description: '勋章描述', example: '完成首次睡眠挑战' })
description: string;
@ApiProperty({ description: '勋章图片URL' })
imageUrl: string;
@ApiProperty({ description: '勋章分类', example: 'sleep' })
category: string;
@ApiProperty({ description: '排序顺序', example: 1 })
sortOrder: number;
}
// 用户勋章信息
export class UserBadgeDto {
@ApiProperty({ description: '记录ID', example: 1 })
id: number;
@ApiProperty({ description: '勋章代码', example: 'goodSleep' })
code: string;
@ApiProperty({ description: '勋章名称', example: '好眠达人' })
name: string;
@ApiProperty({ description: '勋章描述', example: '完成首次睡眠挑战' })
description: string;
@ApiProperty({ description: '勋章图片URL' })
imageUrl: string;
@ApiProperty({ description: '勋章分类', example: 'sleep' })
category: string;
@ApiProperty({ description: '获得时间' })
awardedAt: Date;
@ApiProperty({ description: '授予来源', example: 'challenge' })
source: string;
@ApiProperty({ description: '来源ID如挑战ID', required: false })
sourceId?: string;
@ApiProperty({ description: '元数据', required: false })
metadata?: Record<string, any>;
@ApiProperty({ description: '是否已展示过', example: false })
isShow: boolean;
}
// 可用勋章信息(包含是否已获得)
export class AvailableBadgeDto extends BadgeInfoDto {
@ApiProperty({ description: '是否已获得', example: false })
isAwarded: boolean;
@ApiProperty({ description: '获得时间(如果已获得)', required: false })
awardedAt?: Date;
@ApiProperty({ description: '是否已展示过(如果已获得)', required: false })
isShow?: boolean;
}
// 获取用户勋章列表响应
export class GetUserBadgesResponseDto implements BaseResponseDto<{
badges: UserBadgeDto[];
total: number;
}> {
@ApiProperty({ description: '响应状态码', example: 0 })
code: number;
@ApiProperty({ description: '响应消息', example: 'success' })
message: string;
@ApiProperty({
description: '用户勋章数据',
example: {
badges: [
{
id: 1,
code: 'goodSleep',
name: '好眠达人',
description: '完成首次睡眠挑战',
imageUrl: 'https://example.com/badge.png',
category: 'sleep',
awardedAt: '2025-01-14T07:00:00Z',
source: 'challenge',
sourceId: 'challenge-uuid',
},
],
total: 1,
},
})
data: {
badges: UserBadgeDto[];
total: number;
};
}
// 获取所有可用勋章响应
export class GetAvailableBadgesResponseDto implements BaseResponseDto<AvailableBadgeDto[]> {
@ApiProperty({ description: '响应状态码', example: 0 })
code: number;
@ApiProperty({ description: '响应消息', example: 'success' })
message: string;
@ApiProperty({
description: '所有可用勋章列表',
example: [
{
code: 'goodSleep',
name: '好眠达人',
description: '完成首次睡眠挑战',
imageUrl: 'https://example.com/badge.png',
category: 'sleep',
sortOrder: 1,
isAwarded: true,
awardedAt: '2025-01-14T07:00:00Z',
},
],
})
data: AvailableBadgeDto[];
}
// 标记勋章已展示请求
export class MarkBadgeShownDto {
@ApiProperty({ description: '勋章代码', example: 'goodSleep' })
@IsString()
@IsNotEmpty()
badgeCode: string;
}
// 标记勋章已展示响应
export class MarkBadgeShownResponseDto implements BaseResponseDto<{ success: boolean }> {
@ApiProperty({ description: '响应状态码', example: 0 })
code: number;
@ApiProperty({ description: '响应消息', example: 'success' })
message: string;
@ApiProperty({
description: '操作结果',
example: { success: true },
})
data: { success: boolean };
}