397 lines
12 KiB
Markdown
397 lines
12 KiB
Markdown
# 成就系统模块
|
||
|
||
## 📋 模块概述
|
||
|
||
成就系统是一个独立的、可扩展的成就管理模块,采用**事件驱动 + 策略模式**设计,支持多种成就类型的自动检测和触发。
|
||
|
||
### 核心特性
|
||
|
||
- ✅ **事件驱动架构** - 通过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. 创建成就定义
|
||
|
||
```java
|
||
// 创建一个"学习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事件发布器
|
||
|
||
```java
|
||
@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);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 方式二:直接调用成就服务
|
||
|
||
```java
|
||
@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. 查询用户成就
|
||
|
||
```java
|
||
// 获取当前用户的所有成就
|
||
ResultDomain<TbUserAchievement> result = achievementService.getMyAchievements(null);
|
||
|
||
// 获取指定用户的成就
|
||
ResultDomain<TbUserAchievement> result = achievementService.getUserAchievements(userID, null);
|
||
|
||
// 获取用户的某类型成就
|
||
ResultDomain<TbUserAchievement> result = achievementService.getUserAchievements(userID, 1); // 类型1:勋章
|
||
```
|
||
|
||
### 4. 查询成就进度
|
||
|
||
```java
|
||
// 获取当前用户的所有成就进度
|
||
ResultDomain<TbUserAchievementProgress> result =
|
||
achievementService.getMyAchievementProgress(null);
|
||
|
||
// 获取特定成就的进度
|
||
ResultDomain<TbUserAchievementProgress> result =
|
||
achievementService.getMyAchievementProgress(achievementID);
|
||
```
|
||
|
||
### 5. 获取统计和排行榜
|
||
|
||
```java
|
||
// 获取用户成就统计
|
||
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. 添加新的条件类型枚举
|
||
|
||
```java
|
||
// 在 AchievementConditionType.java 中添加
|
||
SHARE_COUNT(11, "分享次数");
|
||
```
|
||
|
||
### 2. 添加新的事件类型
|
||
|
||
```java
|
||
// 在 AchievementEventType.java 中添加
|
||
RESOURCE_SHARED("resource_shared", "分享资源");
|
||
```
|
||
|
||
### 3. 创建新的检测器
|
||
|
||
```java
|
||
@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. 在业务代码中触发事件
|
||
|
||
```java
|
||
// 用户分享资源时
|
||
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}` - 获取最近获得者
|
||
|
||
## ⚠️ 注意事项
|
||
|
||
1. **事件发布建议使用异步**:成就检测已经是异步的,但建议业务代码中发布事件也使用异步方式,避免阻塞主流程
|
||
|
||
2. **进度计算**:
|
||
- 累积型成就(如学习时长):每次事件累加到进度上
|
||
- 状态型成就(如连续登录):每次事件直接设置进度值
|
||
|
||
3. **性能优化**:
|
||
- 成就检测器会自动筛选相关成就,不会检测所有成就
|
||
- 已获得的成就会自动跳过,不会重复检测
|
||
|
||
4. **事务处理**:成就授予操作已添加事务控制,确保数据一致性
|
||
|
||
## 🎉 使用示例
|
||
|
||
### 完整示例:资源浏览触发成就
|
||
|
||
```java
|
||
@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);
|
||
|
||
// 业务流程继续,不阻塞
|
||
}
|
||
}
|
||
```
|
||
|
||
成就系统会自动:
|
||
1. 接收事件
|
||
2. 查找相关成就(浏览资源类型)
|
||
3. 更新用户进度
|
||
4. 判断是否达成
|
||
5. 自动授予成就
|
||
6. 可选:发送通知给用户
|
||
|
||
## 🔍 故障排查
|
||
|
||
### 成就未触发
|
||
|
||
1. 检查事件是否正确发布
|
||
2. 检查成就定义的条件类型是否正确
|
||
3. 查看日志中是否有异常信息
|
||
4. 确认成就状态为启用(deleted=0)
|
||
|
||
### 进度不更新
|
||
|
||
1. 检查数据库表是否存在
|
||
2. 确认Mapper扫描路径正确
|
||
3. 检查事件值(eventValue)是否正确设置
|
||
|
||
## 📚 相关文档
|
||
|
||
- [数据库建表SQL](../.bin/mysql/sql/createTableAchievement.sql)
|
||
- [迁移指南](./docs/MIGRATION.md)
|
||
|
||
---
|
||
|
||
**作者**: yslg
|
||
**版本**: 1.0.0
|
||
**最后更新**: 2025-10-24
|
||
|