成就系统模块
📋 模块概述
成就系统是一个独立的、可扩展的成就管理模块,采用事件驱动 + 策略模式设计,支持多种成就类型的自动检测和触发。
核心特性
- ✅ 事件驱动架构 - 通过Spring事件机制,自动监听业务事件并触发成就检测
- ✅ 策略模式 - 可扩展的成就检测器,支持多种成就类型
- ✅ 进度追踪 - 实时追踪用户朝向每个成就的进度
- ✅ 异步处理 - 成就检测异步执行,不阻塞主业务流程
- ✅ 完整统计 - 用户成就统计、排行榜等功能
🏗️ 系统架构
achievement/
├── checker/ # 成就检测器(策略模式)
│ ├── AchievementChecker.java # 检测器接口
│ ├── AbstractAchievementChecker.java # 抽象基类
│ └── impl/ # 具体检测器实现
│ ├── LearningTimeChecker.java # 学习时长检测器
│ ├── CourseCompleteChecker.java # 课程完成检测器
│ ├── ResourceViewChecker.java # 资源浏览检测器
│ ├── ResourceCollectChecker.java # 资源收藏检测器
│ ├── TaskCompleteChecker.java # 任务完成检测器
│ └── ContinuousLoginChecker.java # 连续登录检测器
├── controller/ # 控制器层
│ └── AchievementController.java
├── service/ # 服务层
│ └── impl/
│ └── ACHAchievementServiceImpl.java
├── mapper/ # 数据访问层
│ ├── AchievementMapper.java
│ ├── UserAchievementMapper.java
│ └── UserAchievementProgressMapper.java
├── listener/ # 事件监听器
│ └── AchievementEventListener.java
└── resources/
└── mapper/ # MyBatis XML映射文件
├── AchievementMapper.xml
├── UserAchievementMapper.xml
└── UserAchievementProgressMapper.xml
📊 数据库设计
1. tb_achievement - 成就定义表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | VARCHAR(32) | 主键 |
| achievement_id | VARCHAR(32) | 成就唯一标识 |
| name | VARCHAR(100) | 成就名称 |
| description | VARCHAR(500) | 成就描述 |
| icon | VARCHAR(255) | 成就图标URL |
| type | INT | 成就类型(1-勋章 2-等级) |
| level | INT | 成就等级 |
| condition_type | INT | 触发条件类型 |
| condition_value | INT | 条件值 |
| points | INT | 获得积分 |
| order_num | INT | 排序号 |
| deleted | TINYINT | 是否删除 |
2. tb_user_achievement - 用户成就表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | VARCHAR(32) | 主键 |
| user_id | VARCHAR(32) | 用户ID |
| achievement_id | VARCHAR(32) | 成就ID |
| obtain_time | DATETIME | 获得时间 |
3. tb_user_achievement_progress - 用户成就进度表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | VARCHAR(32) | 主键 |
| user_id | VARCHAR(32) | 用户ID |
| achievement_id | VARCHAR(32) | 成就ID |
| current_value | INT | 当前进度值 |
| target_value | INT | 目标值 |
| progress_percentage | INT | 进度百分比 |
| completed | TINYINT | 是否已完成 |
| last_update_time | DATETIME | 最后更新时间 |
🎯 成就条件类型
| 类型编码 | 条件类型 | 说明 |
|---|---|---|
| 1 | LEARNING_TIME | 学习时长(分钟) |
| 2 | RESOURCE_VIEW_COUNT | 浏览资源数量 |
| 3 | COURSE_COMPLETE_COUNT | 完成课程数量 |
| 4 | CONTINUOUS_LOGIN_DAYS | 连续登录天数 |
| 5 | RESOURCE_COLLECT_COUNT | 收藏资源数量 |
| 6 | TASK_COMPLETE_COUNT | 完成任务数量 |
| 7 | POINTS_EARNED | 获得积分数量 |
| 8 | COMMENT_COUNT | 发表评论数量 |
| 9 | CHAPTER_COMPLETE_COUNT | 完成章节数量 |
| 10 | TOTAL_LOGIN_DAYS | 累计登录天数 |
🚀 使用方法
1. 创建成就定义
// 创建一个"学习100小时"的成就
TbAchievement achievement = new TbAchievement();
achievement.setName("学习达人");
achievement.setDescription("累计学习时长达到100小时");
achievement.setType(1); // 勋章类型
achievement.setLevel(3); // 金牌等级
achievement.setConditionType(1); // 学习时长条件
achievement.setConditionValue(6000); // 6000分钟 = 100小时
achievement.setPoints(100); // 奖励100积分
ResultDomain<TbAchievement> result = achievementService.createAchievement(achievement);
2. 触发成就事件(在业务代码中)
方式一:使用Spring事件发布器
@Service
public class LearningRecordService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void updateLearningTime(String userID, Integer minutes) {
// 业务逻辑:更新学习记录
// ...
// 发布成就事件
AchievementEvent event = AchievementEvent.builder(userID, AchievementEventType.LEARNING_TIME_UPDATED)
.value(minutes) // 本次学习时长
.extra("courseID", "course123")
.build();
eventPublisher.publishEvent(event);
}
}
方式二:直接调用成就服务
@Service
public class CourseService {
@Autowired
private AchievementService achievementService;
public void completeCourse(String userID, String courseID) {
// 业务逻辑:完成课程
// ...
// 触发成就检测
AchievementEvent event = new AchievementEvent(
userID,
AchievementEventType.COURSE_COMPLETED,
1 // 完成1门课程
);
ResultDomain<List<TbUserAchievement>> result =
achievementService.processAchievementEvent(event);
if (result.isSuccess() && !result.getData().isEmpty()) {
// 用户获得了新成就
logger.info("用户获得{}个新成就", result.getData().size());
}
}
}
3. 查询用户成就
// 获取当前用户的所有成就
ResultDomain<TbUserAchievement> result = achievementService.getMyAchievements(null);
// 获取指定用户的成就
ResultDomain<TbUserAchievement> result = achievementService.getUserAchievements(userID, null);
// 获取用户的某类型成就
ResultDomain<TbUserAchievement> result = achievementService.getUserAchievements(userID, 1); // 类型1:勋章
4. 查询成就进度
// 获取当前用户的所有成就进度
ResultDomain<TbUserAchievementProgress> result =
achievementService.getMyAchievementProgress(null);
// 获取特定成就的进度
ResultDomain<TbUserAchievementProgress> result =
achievementService.getMyAchievementProgress(achievementID);
5. 获取统计和排行榜
// 获取用户成就统计
ResultDomain<Map<String, Object>> stats =
achievementService.getUserAchievementStatistics(userID);
/*
返回数据示例:
{
"totalAchievements": 15, // 总成就数
"totalPoints": 500, // 总积分
"achievementTypes": 3, // 成就类型数
"latestObtainTime": "2025-10-24 10:30:00" // 最新获得时间
}
*/
// 获取成就排行榜(前10名)
ResultDomain<Map<String, Object>> ranking =
achievementService.getAchievementRanking(10);
🔧 扩展新的成就类型
1. 添加新的条件类型枚举
// 在 AchievementConditionType.java 中添加
SHARE_COUNT(11, "分享次数");
2. 添加新的事件类型
// 在 AchievementEventType.java 中添加
RESOURCE_SHARED("resource_shared", "分享资源");
3. 创建新的检测器
@Component
public class ShareChecker extends AbstractAchievementChecker {
@Override
protected AchievementConditionType getSupportedConditionType() {
return AchievementConditionType.SHARE_COUNT;
}
@Override
protected AchievementEventType[] getSupportedEventTypes() {
return new AchievementEventType[]{
AchievementEventType.RESOURCE_SHARED
};
}
// 如有特殊逻辑,可重写 calculateNewProgress 和 check 方法
}
4. 在业务代码中触发事件
// 用户分享资源时
AchievementEvent event = AchievementEvent.builder(userID, AchievementEventType.RESOURCE_SHARED)
.value(1)
.extra("resourceID", resourceID)
.build();
eventPublisher.publishEvent(event);
📝 API接口列表
成就管理(管理员)
POST /achievement/create- 创建成就PUT /achievement/update- 更新成就DELETE /achievement/delete/{achievementID}- 删除成就GET /achievement/list- 获取成就列表POST /achievement/page- 分页查询成就GET /achievement/detail/{achievementID}- 获取成就详情
用户成就查询
GET /achievement/user/{userID}- 获取用户成就GET /achievement/my- 获取当前用户成就GET /achievement/check/{userID}/{achievementID}- 检查是否已获得
成就授予(管理员)
POST /achievement/grant- 手动授予成就DELETE /achievement/revoke- 撤销成就
成就进度
GET /achievement/progress/{userID}- 获取用户进度GET /achievement/progress/my- 获取当前用户进度
成就检测
POST /achievement/event/process- 处理成就事件GET /achievement/condition/check/{userID}/{achievementID}- 检查条件GET /achievement/available/{userID}- 获取可获得的成就
统计与排行
GET /achievement/statistics/{userID}- 获取用户统计GET /achievement/ranking- 获取排行榜GET /achievement/recent/{achievementID}- 获取最近获得者
⚠️ 注意事项
-
事件发布建议使用异步:成就检测已经是异步的,但建议业务代码中发布事件也使用异步方式,避免阻塞主流程
-
进度计算:
- 累积型成就(如学习时长):每次事件累加到进度上
- 状态型成就(如连续登录):每次事件直接设置进度值
-
性能优化:
- 成就检测器会自动筛选相关成就,不会检测所有成就
- 已获得的成就会自动跳过,不会重复检测
-
事务处理:成就授予操作已添加事务控制,确保数据一致性
🎉 使用示例
完整示例:资源浏览触发成就
@Service
public class ResourceService {
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 用户浏览资源
*/
public void viewResource(String userID, String resourceID) {
// 1. 业务逻辑:记录浏览记录
saveBrowseRecord(userID, resourceID);
// 2. 发布成就事件
AchievementEvent event = AchievementEvent.builder(
userID,
AchievementEventType.RESOURCE_VIEWED
)
.value(1) // 浏览了1个资源
.extra("resourceID", resourceID)
.extra("timestamp", System.currentTimeMillis())
.build();
// 3. 发布事件,异步处理
eventPublisher.publishEvent(event);
// 业务流程继续,不阻塞
}
}
成就系统会自动:
- 接收事件
- 查找相关成就(浏览资源类型)
- 更新用户进度
- 判断是否达成
- 自动授予成就
- 可选:发送通知给用户
🔍 故障排查
成就未触发
- 检查事件是否正确发布
- 检查成就定义的条件类型是否正确
- 查看日志中是否有异常信息
- 确认成就状态为启用(deleted=0)
进度不更新
- 检查数据库表是否存在
- 确认Mapper扫描路径正确
- 检查事件值(eventValue)是否正确设置
📚 相关文档
作者: yslg
版本: 1.0.0
最后更新: 2025-10-24