Files
schoolNews/schoolNewsServ/achievement/README.md
2025-10-24 18:28:49 +08:00

397 lines
12 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.

# 成就系统模块
## 📋 模块概述
成就系统是一个独立的、可扩展的成就管理模块,采用**事件驱动 + 策略模式**设计,支持多种成就类型的自动检测和触发。
### 核心特性
-**事件驱动架构** - 通过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