HarmonyOS应用<趣答>开发第08篇:AchievementService成就服务实现详解——激励用户持续学习的引擎
·

📖 引言
在知识问答学习应用中,成就服务(AchievementService)是激励用户持续学习的核心组件。它负责管理成就的解锁、进度跟踪、奖励发放等关键操作,通过设置各种成就目标,让用户在完成学习任务的同时获得成就感和满足感。
本文将详细讲解 AchievementService 的完整实现,包括成就数据管理、解锁机制、进度跟踪等核心功能。通过本文,你将掌握:
- 如何设计成就数据管理机制
- 如何实现成就解锁和进度跟踪
- 如何计算成就奖励和稀有度
- 如何在实际项目中管理成就数据
🎯 学习目标
完成本文后,你将能够:
- ✅ 理解 AchievementService 的核心职责和功能
- ✅ 实现成就数据的增删改查
- ✅ 管理成就解锁和进度跟踪
- ✅ 计算成就奖励和稀有度
- ✅ 集成成就与用户数据的关联
💡 需求分析
功能模块设计
| 模块 | 功能描述 | 技术要点 |
|---|---|---|
| 成就数据管理 | CRUD操作成就数据 | 数据持久化、缓存 |
| 解锁机制 | 管理成就解锁条件 | 连续打卡、关卡通关、正确率等 |
| 进度跟踪 | 跟踪用户成就完成进度 | 当前进度、目标、完成状态 |
| 奖励计算 | 计算成就奖励 | 积分、经验、徽章 |
| 稀有度系统 | 管理成就稀有度 | 普通、稀有、史诗、传说 |
🛠️ 核心实现
步骤1: AchievementService 接口设计
功能说明
定义 AchievementService 的核心接口,规范服务的方法签名和返回类型。
完整代码
// services/interfaces/AchievementServiceInterface.ts
import { ServiceResponse } from './ServiceInterface';
import { Achievement, AchievementProgress } from '../models/Achievement';
import { User } from '../models/User';
/**
* 成就服务接口
*/
export interface AchievementServiceInterface {
/**
* 获取所有成就
* @returns 成就列表
*/
getAllAchievements(): ServiceResponse<Achievement[]>;
/**
* 根据ID获取成就
* @param achievementId - 成就ID
* @returns 成就信息
*/
getAchievementById(achievementId: string): ServiceResponse<Achievement | undefined>;
/**
* 根据类型获取成就
* @param type - 成就类型
* @returns 成就列表
*/
getAchievementsByType(type: string): ServiceResponse<Achievement[]>;
/**
* 根据稀有度获取成就
* @param rarity - 稀有度
* @returns 成就列表
*/
getAchievementsByRarity(rarity: string): ServiceResponse<Achievement[]>;
/**
* 获取用户已解锁的成就
* @param user - 用户对象
* @returns 已解锁成就列表
*/
getUnlockedAchievements(user: User): ServiceResponse<Achievement[]>;
/**
* 获取用户未解锁的成就(带进度)
* @param user - 用户对象
* @returns 未解锁成就列表(带进度)
*/
getLockedAchievements(user: User): ServiceResponse<Array<Achievement & { progress: AchievementProgress }>>;
/**
* 获取成就进度
* @param achievementId - 成就ID
* @param user - 用户对象
* @returns 进度信息
*/
getAchievementProgress(achievementId: string, user: User): ServiceResponse<AchievementProgress>;
/**
* 检查并解锁成就
* @param user - 用户对象
* @returns 新解锁的成就列表
*/
checkAndUnlockAchievements(user: User): Promise<ServiceResponse<Achievement[]>>;
/**
* 手动解锁成就(管理员功能)
* @param achievementId - 成就ID
* @param user - 用户对象
* @returns 是否成功
*/
unlockAchievement(achievementId: string, user: User): ServiceResponse<boolean>;
/**
* 获取成就奖励
* @param achievement - 成就对象
* @returns 奖励信息
*/
getAchievementReward(achievement: Achievement): ServiceResponse<{ score: number; experience: number }>;
/**
* 计算成就完成率
* @param user - 用户对象
* @returns 完成率(0-100)
*/
calculateCompletionRate(user: User): ServiceResponse<number>;
/**
* 添加新成就
* @param achievement - 成就信息
* @returns 创建的成就
*/
addAchievement(achievement: Achievement): ServiceResponse<Achievement>;
/**
* 更新成就信息
* @param achievementId - 成就ID
* @param updates - 更新内容
* @returns 更新后的成就
*/
updateAchievement(achievementId: string, updates: Partial<Achievement>): ServiceResponse<Achievement>;
/**
* 删除成就
* @param achievementId - 成就ID
* @returns 是否成功
*/
deleteAchievement(achievementId: string): ServiceResponse<boolean>;
}
代码解析
1. 接口方法分类
| 分类 | 方法 | 功能说明 |
|---|---|---|
| 查询 | getAllAchievements(), getAchievementById() |
获取成就信息 |
| 筛选 | getAchievementsByType(), getAchievementsByRarity() |
按类型/稀有度筛选 |
| 用户相关 | getUnlockedAchievements(), getLockedAchievements() |
获取用户成就状态 |
| 进度管理 | getAchievementProgress(), checkAndUnlockAchievements() |
进度跟踪和解锁 |
| 奖励 | getAchievementReward() |
获取成就奖励 |
| 统计 | calculateCompletionRate() |
计算完成率 |
| 数据管理 | addAchievement(), updateAchievement(), deleteAchievement() |
成就CRUD操作 |
步骤2: AchievementService 实现
功能说明
实现 AchievementService 的核心逻辑,包括成就数据管理、解锁机制、进度跟踪等功能。
完整代码
// services/AchievementService.ts
import { Singleton } from './base/BaseService';
import { BaseService } from './base/BaseService';
import { ServiceResponse, ServiceError, createSuccessResponse, createErrorResponse } from './interfaces/ServiceInterface';
import { AchievementServiceInterface } from './interfaces/AchievementServiceInterface';
import { Achievement, AchievementType, AchievementProgress, AchievementCondition, AchievementReward, Rarity } from '../models/Achievement';
import { User } from '../models/User';
/**
* 成就服务实现
*/
@Singleton
export class AchievementService extends BaseService implements AchievementServiceInterface {
private achievements: Achievement[] = [];
constructor() {
super();
this.initializeAchievements();
}
/**
* 初始化成就数据
*/
private initializeAchievements(): void {
this.achievements = this.loadAchievementData();
}
/**
* 加载成就数据(模拟)
*/
private loadAchievementData(): Achievement[] {
return [
// 连续打卡成就
this.createConsecutiveDaysAchievement(7, Rarity.RARE),
this.createConsecutiveDaysAchievement(14, Rarity.EPIC),
this.createConsecutiveDaysAchievement(30, Rarity.LEGENDARY),
this.createConsecutiveDaysAchievement(7, Rarity.RARE),
// 关卡通关成就
this.createLevelClearAchievement(5, Rarity.COMMON),
this.createLevelClearAchievement(10, Rarity.RARE),
this.createLevelClearAchievement(20, Rarity.EPIC),
this.createLevelClearAchievement(40, Rarity.LEGENDARY),
// 正确率成就
this.createAccuracyAchievement(80, Rarity.COMMON),
this.createAccuracyAchievement(90, Rarity.RARE),
this.createAccuracyAchievement(95, Rarity.EPIC),
this.createAccuracyAchievement(100, Rarity.LEGENDARY),
// 答题数量成就
this.createTotalQuestionsAchievement(100, Rarity.COMMON),
this.createTotalQuestionsAchievement(500, Rarity.RARE),
this.createTotalQuestionsAchievement(1000, Rarity.EPIC),
this.createTotalQuestionsAchievement(5000, Rarity.LEGENDARY),
// 特殊成就
this.createSpecialAchievement('first_login', '初次见面', '完成首次登录', Rarity.COMMON),
this.createSpecialAchievement('first_level_clear', '初战告捷', '完成首次关卡通关', Rarity.COMMON),
this.createSpecialAchievement('first_perfect', '完美主义', '首次获得3星评价', Rarity.RARE),
this.createSpecialAchievement('all_subjects', '全科达人', '完成所有学科的第一关', Rarity.EPIC)
];
}
/**
* 创建连续打卡成就
*/
private createConsecutiveDaysAchievement(days: number, rarity: Rarity): Achievement {
const name = days === 7 ? '坚持不懈' : days === 14 ? '两周连胜' : days === 30 ? '月度冠军' : '连续打卡';
const description = `连续学习${days}天`;
const rewards = this.getRewardsByRarity(rarity);
return {
id: `achievement_consecutive_${days}`,
name,
description,
icon: `https://example.com/achievements/consecutive_${days}.png`,
type: AchievementType.CONSECUTIVE_DAYS,
condition: { type: 'consecutive_days', target: days },
reward: rewards,
rarity
};
}
/**
* 创建关卡通关成就
*/
private createLevelClearAchievement(count: number, rarity: Rarity): Achievement {
const name = count === 5 ? '初露锋芒' : count === 10 ? '渐入佳境' : count === 20 ? '登堂入室' : '通关大师';
const description = `累计通关${count}个关卡`;
const rewards = this.getRewardsByRarity(rarity);
return {
id: `achievement_level_clear_${count}`,
name,
description,
icon: `https://example.com/achievements/level_clear_${count}.png`,
type: AchievementType.LEVEL_CLEAR,
condition: { type: 'level_clear', target: count },
reward: rewards,
rarity
};
}
/**
* 创建正确率成就
*/
private createAccuracyAchievement(accuracy: number, rarity: Rarity): Achievement {
const name = accuracy === 80 ? '百发百中' : accuracy === 90 ? '精准射手' : accuracy === 95 ? '神枪手' : '完美无缺';
const description = `单次测验正确率达到${accuracy}%`;
const rewards = this.getRewardsByRarity(rarity);
return {
id: `achievement_accuracy_${accuracy}`,
name,
description,
icon: `https://example.com/achievements/accuracy_${accuracy}.png`,
type: AchievementType.ACCURACY,
condition: { type: 'accuracy', target: accuracy },
reward: rewards,
rarity
};
}
/**
* 创建答题数量成就
*/
private createTotalQuestionsAchievement(count: number, rarity: Rarity): Achievement {
const name = count === 100 ? '答题新手' : count === 500 ? '答题能手' : count === 1000 ? '答题高手' : '答题大师';
const description = `累计答题${count}道`;
const rewards = this.getRewardsByRarity(rarity);
return {
id: `achievement_questions_${count}`,
name,
description,
icon: `https://example.com/achievements/questions_${count}.png`,
type: AchievementType.TOTAL_QUESTIONS,
condition: { type: 'total_questions', target: count },
reward: rewards,
rarity
};
}
/**
* 创建特殊成就
*/
private createSpecialAchievement(type: string, name: string, description: string, rarity: Rarity): Achievement {
const rewards = this.getRewardsByRarity(rarity);
return {
id: `achievement_${type}`,
name,
description,
icon: `https://example.com/achievements/${type}.png`,
type: AchievementType.SPECIAL,
condition: { type, target: 1 },
reward: rewards,
rarity
};
}
/**
* 根据稀有度获取奖励
*/
private getRewardsByRarity(rarity: Rarity): AchievementReward {
const baseRewards: Record<Rarity, AchievementReward> = {
[Rarity.COMMON]: { score: 50, experience: 25, badge: '' },
[Rarity.RARE]: { score: 100, experience: 50, badge: '' },
[Rarity.EPIC]: { score: 200, experience: 100, badge: '' },
[Rarity.LEGENDARY]: { score: 500, experience: 250, badge: '' }
};
return baseRewards[rarity];
}
/**
* 获取所有成就
*/
getAllAchievements(): ServiceResponse<Achievement[]> {
return createSuccessResponse<Achievement[]>(this.achievements);
}
/**
* 根据ID获取成就
*/
getAchievementById(achievementId: string): ServiceResponse<Achievement | undefined> {
const achievement = this.achievements.find(a => a.id === achievementId);
return createSuccessResponse<Achievement | undefined>(achievement);
}
/**
* 根据类型获取成就
*/
getAchievementsByType(type: string): ServiceResponse<Achievement[]> {
const filtered = this.achievements.filter(a => a.type === type);
return createSuccessResponse<Achievement[]>(filtered);
}
/**
* 根据稀有度获取成就
*/
getAchievementsByRarity(rarity: string): ServiceResponse<Achievement[]> {
const filtered = this.achievements.filter(a => a.rarity === rarity);
return createSuccessResponse<Achievement[]>(filtered);
}
/**
* 获取用户已解锁的成就
*/
getUnlockedAchievements(user: User): ServiceResponse<Achievement[]> {
const unlocked = this.achievements.filter(a => user.achievements.includes(a.id));
return createSuccessResponse<Achievement[]>(unlocked);
}
/**
* 获取用户未解锁的成就(带进度)
*/
getLockedAchievements(user: User): ServiceResponse<Array<Achievement & { progress: AchievementProgress }>> {
const locked = this.achievements
.filter(a => !user.achievements.includes(a.id))
.map(a => ({
...a,
progress: this.calculateProgress(user, a)
}));
return createSuccessResponse<Array<Achievement & { progress: AchievementProgress }>>(locked);
}
/**
* 获取成就进度
*/
getAchievementProgress(achievementId: string, user: User): ServiceResponse<AchievementProgress> {
const achievement = this.achievements.find(a => a.id === achievementId);
if (!achievement) {
return createErrorResponse<AchievementProgress>(ServiceError.NOT_FOUND, '成就不存在');
}
const progress = this.calculateProgress(user, achievement);
return createSuccessResponse<AchievementProgress>(progress);
}
/**
* 计算成就进度
*/
private calculateProgress(user: User, achievement: Achievement): AchievementProgress {
let current = 0;
const target = achievement.condition.target;
switch (achievement.type) {
case AchievementType.CONSECUTIVE_DAYS:
current = user.consecutiveDays;
break;
case AchievementType.LEVEL_CLEAR:
current = user.completedLevels.length;
break;
case AchievementType.ACCURACY:
current = Math.round(user.statistics.averageAccuracy * 100);
break;
case AchievementType.TOTAL_QUESTIONS:
current = user.statistics.totalQuestions;
break;
case AchievementType.SPECIAL:
current = this.calculateSpecialProgress(user, achievement);
break;
}
current = Math.min(current, target);
return {
achievementId: achievement.id,
current,
target,
isCompleted: current >= target
};
}
/**
* 计算特殊成就进度
*/
private calculateSpecialProgress(user: User, achievement: Achievement): number {
const conditionType = achievement.condition.type;
switch (conditionType) {
case 'first_login':
return user.createdAt ? 1 : 0;
case 'first_level_clear':
return user.completedLevels.length > 0 ? 1 : 0;
case 'first_perfect':
return Array.from(user.levelStars.values()).some(stars => stars === 3) ? 1 : 0;
case 'all_subjects':
// 检查是否完成所有学科的第一关
const subjects = ['math', 'geography', 'life', 'literature', 'sports'];
const completedAllFirstLevels = subjects.every(subject =>
user.completedLevels.includes(`${subject}_level_1`)
);
return completedAllFirstLevels ? 1 : 0;
default:
return 0;
}
}
/**
* 检查并解锁成就
*/
async checkAndUnlockAchievements(user: User): Promise<ServiceResponse<Achievement[]>> {
const unlocked: Achievement[] = [];
for (const achievement of this.achievements) {
// 跳过已解锁的成就
if (user.achievements.includes(achievement.id)) {
continue;
}
// 检查是否满足解锁条件
const progress = this.calculateProgress(user, achievement);
if (progress.isCompleted) {
unlocked.push(achievement);
user.achievements.push(achievement.id);
// 发放奖励
const reward = this.getAchievementReward(achievement).data;
if (reward) {
user.totalScore += reward.score;
user.totalExperience += reward.experience;
}
}
}
return createSuccessResponse<Achievement[]>(unlocked);
}
/**
* 手动解锁成就
*/
unlockAchievement(achievementId: string, user: User): ServiceResponse<boolean> {
const achievement = this.achievements.find(a => a.id === achievementId);
if (!achievement) {
return createErrorResponse<boolean>(ServiceError.NOT_FOUND, '成就不存在');
}
// 检查是否已解锁
if (user.achievements.includes(achievementId)) {
return createErrorResponse<boolean>(ServiceError.CONFLICT, '成就已解锁');
}
// 解锁成就
user.achievements.push(achievementId);
// 发放奖励
const reward = this.getAchievementReward(achievement).data;
if (reward) {
user.totalScore += reward.score;
user.totalExperience += reward.experience;
}
return createSuccessResponse<boolean>(true);
}
/**
* 获取成就奖励
*/
getAchievementReward(achievement: Achievement): ServiceResponse<{ score: number; experience: number }> {
return createSuccessResponse<{ score: number; experience: number }>({
score: achievement.reward.score,
experience: achievement.reward.experience
});
}
/**
* 计算成就完成率
*/
calculateCompletionRate(user: User): ServiceResponse<number> {
const total = this.achievements.length;
const unlocked = user.achievements.length;
const rate = total > 0 ? (unlocked / total) * 100 : 0;
return createSuccessResponse<number>(rate);
}
/**
* 添加新成就
*/
addAchievement(achievement: Achievement): ServiceResponse<Achievement> {
// 检查ID是否已存在
if (this.achievements.find(a => a.id === achievement.id)) {
return createErrorResponse<Achievement>(ServiceError.CONFLICT, '成就ID已存在');
}
this.achievements.push(achievement);
return createSuccessResponse<Achievement>(achievement);
}
/**
* 更新成就信息
*/
updateAchievement(achievementId: string, updates: Partial<Achievement>): ServiceResponse<Achievement> {
const index = this.achievements.findIndex(a => a.id === achievementId);
if (index === -1) {
return createErrorResponse<Achievement>(ServiceError.NOT_FOUND, '成就不存在');
}
this.achievements[index] = { ...this.achievements[index], ...updates };
return createSuccessResponse<Achievement>(this.achievements[index]);
}
/**
* 删除成就
*/
deleteAchievement(achievementId: string): ServiceResponse<boolean> {
const index = this.achievements.findIndex(a => a.id === achievementId);
if (index === -1) {
return createErrorResponse<boolean>(ServiceError.NOT_FOUND, '成就不存在');
}
this.achievements.splice(index, 1);
return createSuccessResponse<boolean>(true);
}
}
代码解析
1. 成就类型设计
export enum AchievementType {
CONSECUTIVE_DAYS = '连续打卡', // 连续学习天数
LEVEL_CLEAR = '关卡通关', // 通关指定关卡
ACCURACY = '正确率', // 达到指定正确率
SPECIAL = '特殊成就', // 特殊条件达成
TOTAL_QUESTIONS = '答题数量' // 累计答题数量
}
2. 稀有度设计
export enum Rarity {
COMMON = '普通', // 绿色,基础奖励
RARE = '稀有', // 蓝色,2倍奖励
EPIC = '史诗', // 紫色,4倍奖励
LEGENDARY = '传说' // 橙色,10倍奖励
}
3. 进度计算逻辑
private calculateProgress(user: User, achievement: Achievement): AchievementProgress {
let current = 0;
const target = achievement.condition.target;
switch (achievement.type) {
case AchievementType.CONSECUTIVE_DAYS:
current = user.consecutiveDays;
break;
case AchievementType.LEVEL_CLEAR:
current = user.completedLevels.length;
break;
case AchievementType.ACCURACY:
current = Math.round(user.statistics.averageAccuracy * 100);
break;
case AchievementType.TOTAL_QUESTIONS:
current = user.statistics.totalQuestions;
break;
}
return {
achievementId: achievement.id,
current: Math.min(current, target),
target,
isCompleted: current >= target
};
}
4. 成就解锁流程
async checkAndUnlockAchievements(user: User): Promise<ServiceResponse<Achievement[]>> {
const unlocked: Achievement[] = [];
for (const achievement of this.achievements) {
// 跳过已解锁的
if (user.achievements.includes(achievement.id)) {
continue;
}
// 检查条件
const progress = this.calculateProgress(user, achievement);
if (progress.isCompleted) {
user.achievements.push(achievement.id);
// 发放奖励
user.totalScore += achievement.reward.score;
user.totalExperience += achievement.reward.experience;
unlocked.push(achievement);
}
}
return createSuccessResponse<Achievement[]>(unlocked);
}
⚠️ 常见问题与解决方案
问题1: 重复解锁成就
现象:
同一成就被多次解锁,导致奖励重复发放。
错误代码:
// ❌ 错误:没有检查是否已解锁
checkAndUnlockAchievements(user: User): ServiceResponse<Achievement[]> {
for (const achievement of this.achievements) {
const progress = this.calculateProgress(user, achievement);
if (progress.isCompleted) {
user.achievements.push(achievement.id); // 可能重复添加
}
}
}
正确代码:
// ✅ 正确:检查后添加
checkAndUnlockAchievements(user: User): ServiceResponse<Achievement[]> {
for (const achievement of this.achievements) {
// 跳过已解锁的
if (user.achievements.includes(achievement.id)) {
continue;
}
const progress = this.calculateProgress(user, achievement);
if (progress.isCompleted) {
user.achievements.push(achievement.id);
}
}
}
规则/建议:
- 解锁前检查是否已解锁
- 使用
includes()或 Set 数据结构 - 避免重复添加
问题2: 进度计算超出目标值
现象:
用户进度超过目标值,导致显示异常。
错误代码:
// ❌ 错误:进度可能超出目标
current = user.statistics.totalQuestions; // 可能超过 target
正确代码:
// ✅ 正确:限制进度不超过目标
current = Math.min(user.statistics.totalQuestions, target);
规则/建议:
- 使用
Math.min()限制进度 - 进度条显示时注意边界情况
- 保持数据一致性
问题3: 稀有度与奖励不匹配
现象:
稀有度与奖励设置不匹配,导致奖励不公平。
错误代码:
// ❌ 错误:传说成就奖励太低
{
rarity: Rarity.LEGENDARY,
reward: { score: 50, experience: 25 }
}
正确代码:
// ✅ 正确:稀有度越高奖励越丰厚
{
rarity: Rarity.LEGENDARY,
reward: { score: 500, experience: 250 }
}
规则/建议:
- 稀有度越高,奖励越丰厚
- 建议比例:普通=1,稀有=2,史诗=4,传说=10
- 保持奖励系统公平
问题4: 特殊成就条件判断不完整
现象:
特殊成就的条件判断逻辑不完整。
错误代码:
// ❌ 错误:没有处理特殊条件
case AchievementType.SPECIAL:
current = 0;
break;
正确代码:
// ✅ 正确:完整处理特殊条件
case AchievementType.SPECIAL:
current = this.calculateSpecialProgress(user, achievement);
break;
规则/建议:
- 特殊成就需要单独实现条件判断
- 使用
calculateSpecialProgress()方法 - 根据
condition.type进行具体判断
问题5: 成就ID格式不统一
现象:
成就ID格式不统一,导致数据管理困难。
错误代码:
// ❌ 错误:ID格式不统一
const achievement1 = { id: 'achievement_1' };
const achievement2 = { id: 'consecutive_7' };
正确代码:
// ✅ 正确:ID格式统一
const achievement1 = { id: 'achievement_first_login' };
const achievement2 = { id: 'achievement_consecutive_7' };
规则/建议:
- ID格式:
achievement_{type}_{identifier} - 使用小写字母和下划线
- 保持命名一致性
📝 本章小结
核心知识点
本文详细讲解了 AchievementService 成就服务的实现,主要包括:
1. 成就数据管理
- 成就类型分类(连续打卡、关卡通关、正确率、答题数量、特殊)
- 稀有度系统(普通、稀有、史诗、传说)
2. 解锁机制
- 进度计算和条件判断
- 自动解锁和手动解锁
3. 奖励系统
- 根据稀有度计算奖励
- 积分和经验发放
4. 进度跟踪
- 实时计算成就进度
- 显示完成状态
最佳实践总结
✅ 成就ID格式
id: 'achievement_consecutive_7' // achievement_{type}_{identifier}
✅ 进度计算
current = Math.min(actualValue, target); // 限制不超过目标
✅ 解锁检查
if (user.achievements.includes(achievement.id)) {
continue; // 跳过已解锁
}
✅ 稀有度奖励
const rewards = {
[Rarity.COMMON]: { score: 50, experience: 25 },
[Rarity.RARE]: { score: 100, experience: 50 },
[Rarity.EPIC]: { score: 200, experience: 100 },
[Rarity.LEGENDARY]: { score: 500, experience: 250 }
};
下一步预告
在下一篇文章中,我们将:
- 🎨 讲解页面组件详解系列文章
- 📚 介绍首页、关卡选择页、答题页等核心页面
- 🏷️ 探索页面组件的设计和实现
🔗 相关链接
- 项目源码: Atomgit仓库
💡 提示: 建议结合项目源码阅读,动手实践效果更好!
更多推荐


所有评论(0)