# 成就系统模块

## 📋 模块概述

成就系统是一个独立的、可扩展的成就管理模块，采用**事件驱动 + 策略模式**设计，支持多种成就类型的自动检测和触发。

### 核心特性

- ✅ **事件驱动架构** - 通过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

