# 成就系统模块 ## 📋 模块概述 成就系统是一个独立的、可扩展的成就管理模块,采用**事件驱动 + 策略模式**设计,支持多种成就类型的自动检测和触发。 ### 核心特性 - ✅ **事件驱动架构** - 通过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 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> result = achievementService.processAchievementEvent(event); if (result.isSuccess() && !result.getData().isEmpty()) { // 用户获得了新成就 logger.info("用户获得{}个新成就", result.getData().size()); } } } ``` ### 3. 查询用户成就 ```java // 获取当前用户的所有成就 ResultDomain result = achievementService.getMyAchievements(null); // 获取指定用户的成就 ResultDomain result = achievementService.getUserAchievements(userID, null); // 获取用户的某类型成就 ResultDomain result = achievementService.getUserAchievements(userID, 1); // 类型1:勋章 ``` ### 4. 查询成就进度 ```java // 获取当前用户的所有成就进度 ResultDomain result = achievementService.getMyAchievementProgress(null); // 获取特定成就的进度 ResultDomain result = achievementService.getMyAchievementProgress(achievementID); ``` ### 5. 获取统计和排行榜 ```java // 获取用户成就统计 ResultDomain> stats = achievementService.getUserAchievementStatistics(userID); /* 返回数据示例: { "totalAchievements": 15, // 总成就数 "totalPoints": 500, // 总积分 "achievementTypes": 3, // 成就类型数 "latestObtainTime": "2025-10-24 10:30:00" // 最新获得时间 } */ // 获取成就排行榜(前10名) ResultDomain> 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