serv-成就模块初始

This commit is contained in:
2025-10-24 18:28:49 +08:00
parent bc84bd82cc
commit d593a554fc
21 changed files with 3396 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,331 @@
# 成就模块迁移指南
## 📝 迁移概述
成就相关功能已从 `usercenter` 模块迁移到独立的 `achievement` 模块。
### 迁移日期
2025-10-24
### 迁移原因
-**模块职责分离** - usercenter负责用户中心achievement专注于成就系统
-**更好的可维护性** - 独立模块易于管理和扩展
-**清晰的依赖关系** - 减少模块间的耦合
-**可扩展性** - 便于添加新的成就类型和功能
## 🔄 包路径变更
### API接口变更
**旧路径(已删除):**
```java
org.xyzh.api.usercenter.achievement.UserAchievementService
```
**新路径:**
```java
org.xyzh.api.achievement.AchievementService
```
### 服务实现变更
**旧路径(已删除):**
```java
org.xyzh.usercenter.service.UCUserAchievementService
org.xyzh.usercenter.service.impl.UCUserAchievementServiceImpl
```
**新路径:**
```java
org.xyzh.api.achievement.AchievementService
org.xyzh.achievement.service.impl.ACHAchievementServiceImpl
```
### Mapper变更
**旧路径(已删除):**
```java
org.xyzh.usercenter.mapper.AchievementMapper
org.xyzh.usercenter.mapper.UserAchievementMapper
```
**新路径:**
```java
org.xyzh.achievement.mapper.AchievementMapper
org.xyzh.achievement.mapper.UserAchievementMapper
org.xyzh.achievement.mapper.UserAchievementProgressMapper // 新增
```
### Controller变更
**旧路径(已删除):**
```java
org.xyzh.usercenter.controller.UserAchievementController
```
**新路径:**
```java
org.xyzh.achievement.controller.AchievementController
```
## 📦 依赖变更
### 如果你的模块之前依赖了成就功能
#### 旧的依赖配置(需要删除):
```xml
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-usercenter</artifactId> <!-- 包含成就接口 -->
<version>${school-news.version}</version>
</dependency>
```
#### 新的依赖配置(需要添加):
```xml
<!-- API接口依赖 -->
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-achievement</artifactId>
<version>${school-news.version}</version>
</dependency>
<!-- 如果需要使用实现类通常不需要使用Dubbo远程调用 -->
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>achievement</artifactId>
<version>${school-news.version}</version>
</dependency>
```
## 🔧 代码迁移步骤
### 1. 更新导入语句
**旧代码:**
```java
import org.xyzh.api.usercenter.achievement.UserAchievementService;
```
**新代码:**
```java
import org.xyzh.api.achievement.AchievementService;
```
### 2. 更新服务注入
**旧代码:**
```java
@Autowired
private UserAchievementService userAchievementService;
```
**新代码:**
```java
@Autowired
private AchievementService achievementService;
```
### 3. 更新方法调用
大部分方法签名保持不变,只需更新服务名称:
**旧代码:**
```java
ResultDomain<TbUserAchievement> result = userAchievementService.getUserAchievements(userID, type);
```
**新代码:**
```java
ResultDomain<TbUserAchievement> result = achievementService.getUserAchievements(userID, type);
```
### 4. 新增功能调用
新的成就系统增加了事件驱动机制:
```java
// 发布成就事件
AchievementEvent event = AchievementEvent.builder(userID, AchievementEventType.COURSE_COMPLETED)
.value(1)
.build();
// 方式1使用Spring事件发布推荐
eventPublisher.publishEvent(event);
// 方式2直接调用服务
achievementService.processAchievementEvent(event);
```
## 🗄️ 数据库变更
### 新增表
```sql
-- 用户成就进度表(新增)
CREATE TABLE tb_user_achievement_progress (
id VARCHAR(32) NOT NULL,
user_id VARCHAR(32) NOT NULL,
achievement_id VARCHAR(32) NOT NULL,
current_value INT DEFAULT 0,
target_value INT DEFAULT 0,
progress_percentage INT DEFAULT 0,
completed TINYINT(1) DEFAULT 0,
last_update_time DATETIME,
create_time DATETIME,
PRIMARY KEY (id),
UNIQUE KEY uk_user_achievement_progress (user_id, achievement_id)
);
```
### 已有表
`tb_achievement``tb_user_achievement` 表结构保持不变,无需迁移数据。
## 🌐 REST API变更
### 接口路径变更
**旧路径:**
```
/usercenter/achievement/*
```
**新路径:**
```
/achievement/*
```
### 具体接口映射
| 功能 | 旧接口 | 新接口 | 变化 |
|-----|-------|-------|-----|
| 获取所有成就 | GET /usercenter/achievement/list | GET /achievement/list | 路径变更 |
| 获取用户成就 | GET /usercenter/achievement/user/{userID} | GET /achievement/user/{userID} | 路径变更 |
| 获取我的成就 | - | GET /achievement/my | **新增** |
| 授予成就 | POST /usercenter/achievement/grant | POST /achievement/grant | 路径变更 |
| 检查条件 | GET /usercenter/achievement/condition/{userID}/{achievementID} | GET /achievement/condition/check/{userID}/{achievementID} | 路径变更 |
| 获取进度 | - | GET /achievement/progress/{userID} | **新增** |
| 处理事件 | - | POST /achievement/event/process | **新增** |
| 获取统计 | - | GET /achievement/statistics/{userID} | **新增** |
| 排行榜 | - | GET /achievement/ranking | **新增** |
## ✨ 新增功能
### 1. 事件驱动机制
```java
// 自动监听业务事件并触发成就检测
@Autowired
private ApplicationEventPublisher eventPublisher;
AchievementEvent event = AchievementEvent.builder(userID, eventType)
.value(value)
.build();
eventPublisher.publishEvent(event);
```
### 2. 成就进度追踪
```java
// 查询用户的成就进度
ResultDomain<TbUserAchievementProgress> result =
achievementService.getMyAchievementProgress(achievementID);
```
### 3. 策略模式检测器
- 可扩展的成就检测器
- 支持多种成就类型
- 易于添加新的成就类型
### 4. 统计和排行榜
```java
// 获取用户统计
ResultDomain<Map<String, Object>> stats =
achievementService.getUserAchievementStatistics(userID);
// 获取排行榜
ResultDomain<Map<String, Object>> ranking =
achievementService.getAchievementRanking(10);
```
## 🚨 注意事项
### 1. 破坏性变更
#### ❌ 已删除的类(不再可用)
- `org.xyzh.api.usercenter.achievement.UserAchievementService`
- `org.xyzh.usercenter.service.UCUserAchievementService`
- `org.xyzh.usercenter.mapper.AchievementMapper`
- `org.xyzh.usercenter.controller.UserAchievementController`
#### ✅ 对应的新类
- `org.xyzh.api.achievement.AchievementService`
- `org.xyzh.achievement.service.impl.ACHAchievementServiceImpl`
- `org.xyzh.achievement.mapper.AchievementMapper`
- `org.xyzh.achievement.controller.AchievementController`
### 2. 接口变化
新的 `AchievementService` 接口方法更多、更完善:
- ✅ 所有旧方法都有对应的新方法
- ✅ 新增了事件处理、进度查询、统计等功能
- ✅ 方法签名大部分保持兼容
### 3. 前端调用
如果你的前端代码调用了成就相关API需要
1. 更新API路径`/usercenter/achievement/*``/achievement/*`
2. 检查返回数据格式(大部分保持兼容)
3. 利用新增的API进度、统计、排行榜等
## 📋 迁移检查清单
迁移时请检查以下项目:
- [ ] 更新 pom.xml 依赖
- [ ] 更新 import 语句
- [ ] 更新服务注入
- [ ] 更新方法调用
- [ ] 测试成就创建功能
- [ ] 测试成就授予功能
- [ ] 测试成就查询功能
- [ ] 测试事件触发功能(如有)
- [ ] 更新前端API路径如有
- [ ] 更新相关文档
- [ ] 执行集成测试
## 🆘 常见问题
### Q1: 编译报错找不到 UserAchievementService
**A:** 更新 import 语句为:
```java
import org.xyzh.api.achievement.AchievementService;
```
### Q2: 旧的成就数据会丢失吗?
**A:** 不会。数据库表结构保持不变,所有数据完整保留。
### Q3: 需要修改数据库吗?
**A:** 需要执行新表创建SQL`tb_user_achievement_progress`),见 `.bin/mysql/sql/createTableAchievement.sql`
### Q4: 如何使用新的事件驱动功能?
**A:** 参考 `README.md` 中的"使用方法"章节。
### Q5: admin模块已经自动配置好了吗
**A:** 是的admin模块的pom.xml已自动添加achievement依赖。
## 📚 相关文档
- [成就系统使用文档](../README.md)
- [数据库建表SQL](../../.bin/mysql/sql/createTableAchievement.sql)
## 👥 联系支持
如果在迁移过程中遇到问题,请联系:
- 开发者yslg
- 创建Issue或PR
---
**迁移完成日期**: 2025-10-24
**版本**: 1.0.0

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>school-news</artifactId>
<version>${school-news.version}</version>
</parent>
<groupId>org.xyzh</groupId>
<artifactId>achievement</artifactId>
<version>${school-news.version}</version>
<packaging>jar</packaging>
<name>achievement</name>
<description>成就模块</description>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-achievement</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-system</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-study</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>common-all</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>system</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 禁用 Spring Boot 插件,因为 achievement 是 admin 的一部分 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,74 @@
package org.xyzh.achievement.checker;
import org.xyzh.common.core.enums.AchievementConditionType;
import org.xyzh.common.core.enums.AchievementEventType;
import org.xyzh.common.core.event.AchievementEvent;
import org.xyzh.common.dto.usercenter.TbAchievement;
/**
* @description 成就检测器抽象基类
* @filename AbstractAchievementChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
public abstract class AbstractAchievementChecker implements AchievementChecker {
/**
* 支持的条件类型
*/
protected abstract AchievementConditionType getSupportedConditionType();
/**
* 支持的事件类型(可多个)
*/
protected abstract AchievementEventType[] getSupportedEventTypes();
@Override
public boolean supports(TbAchievement achievement) {
if (achievement == null || achievement.getConditionType() == null) {
return false;
}
return achievement.getConditionType().equals(getSupportedConditionType().getCode());
}
@Override
public boolean supportsEventType(AchievementEventType eventType) {
if (eventType == null) {
return false;
}
for (AchievementEventType supportedType : getSupportedEventTypes()) {
if (supportedType == eventType) {
return true;
}
}
return false;
}
@Override
public boolean check(AchievementEvent event, TbAchievement achievement, Integer currentProgress) {
if (event == null || achievement == null) {
return false;
}
Integer newProgress = calculateNewProgress(event, currentProgress);
Integer targetValue = achievement.getConditionValue();
return newProgress != null && targetValue != null && newProgress >= targetValue;
}
@Override
public Integer calculateNewProgress(AchievementEvent event, Integer currentProgress) {
if (currentProgress == null) {
currentProgress = 0;
}
Integer eventValue = event.getEventValue();
if (eventValue == null) {
eventValue = 1; // 默认增加1
}
return currentProgress + eventValue;
}
}

View File

@@ -0,0 +1,47 @@
package org.xyzh.achievement.checker;
import org.xyzh.common.core.enums.AchievementEventType;
import org.xyzh.common.core.event.AchievementEvent;
import org.xyzh.common.dto.usercenter.TbAchievement;
/**
* @description 成就检测器接口 - 策略模式
* @filename AchievementChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
public interface AchievementChecker {
/**
* @description 判断该检测器是否支持该成就类型
* @param achievement 成就
* @return boolean 是否支持
*/
boolean supports(TbAchievement achievement);
/**
* @description 判断该检测器是否支持该事件类型
* @param eventType 事件类型
* @return boolean 是否支持
*/
boolean supportsEventType(AchievementEventType eventType);
/**
* @description 检查用户是否满足成就条件
* @param event 成就事件
* @param achievement 成就定义
* @param currentProgress 当前进度值
* @return boolean 是否满足条件
*/
boolean check(AchievementEvent event, TbAchievement achievement, Integer currentProgress);
/**
* @description 计算新的进度值
* @param event 成就事件
* @param currentProgress 当前进度值
* @return Integer 新的进度值
*/
Integer calculateNewProgress(AchievementEvent event, Integer currentProgress);
}

View File

@@ -0,0 +1,38 @@
package org.xyzh.achievement.checker.impl;
import org.springframework.stereotype.Component;
import org.xyzh.achievement.checker.AbstractAchievementChecker;
import org.xyzh.common.core.enums.AchievementConditionType;
import org.xyzh.common.core.enums.AchievementEventType;
import org.xyzh.common.core.event.AchievementEvent;
/**
* @description 连续登录天数成就检测器
* @filename ContinuousLoginChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Component
public class ContinuousLoginChecker extends AbstractAchievementChecker {
@Override
protected AchievementConditionType getSupportedConditionType() {
return AchievementConditionType.CONTINUOUS_LOGIN_DAYS;
}
@Override
protected AchievementEventType[] getSupportedEventTypes() {
return new AchievementEventType[]{
AchievementEventType.CONTINUOUS_LOGIN,
AchievementEventType.USER_LOGIN
};
}
@Override
public Integer calculateNewProgress(AchievementEvent event, Integer currentProgress) {
// 对于连续登录,直接使用事件值(连续天数)
return event.getEventValue() != null ? event.getEventValue() : currentProgress;
}
}

View File

@@ -0,0 +1,30 @@
package org.xyzh.achievement.checker.impl;
import org.springframework.stereotype.Component;
import org.xyzh.achievement.checker.AbstractAchievementChecker;
import org.xyzh.common.core.enums.AchievementConditionType;
import org.xyzh.common.core.enums.AchievementEventType;
/**
* @description 完成课程数量成就检测器
* @filename CourseCompleteChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Component
public class CourseCompleteChecker extends AbstractAchievementChecker {
@Override
protected AchievementConditionType getSupportedConditionType() {
return AchievementConditionType.COURSE_COMPLETE_COUNT;
}
@Override
protected AchievementEventType[] getSupportedEventTypes() {
return new AchievementEventType[]{
AchievementEventType.COURSE_COMPLETED
};
}
}

View File

@@ -0,0 +1,30 @@
package org.xyzh.achievement.checker.impl;
import org.springframework.stereotype.Component;
import org.xyzh.achievement.checker.AbstractAchievementChecker;
import org.xyzh.common.core.enums.AchievementConditionType;
import org.xyzh.common.core.enums.AchievementEventType;
/**
* @description 学习时长成就检测器
* @filename LearningTimeChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Component
public class LearningTimeChecker extends AbstractAchievementChecker {
@Override
protected AchievementConditionType getSupportedConditionType() {
return AchievementConditionType.LEARNING_TIME;
}
@Override
protected AchievementEventType[] getSupportedEventTypes() {
return new AchievementEventType[]{
AchievementEventType.LEARNING_TIME_UPDATED
};
}
}

View File

@@ -0,0 +1,30 @@
package org.xyzh.achievement.checker.impl;
import org.springframework.stereotype.Component;
import org.xyzh.achievement.checker.AbstractAchievementChecker;
import org.xyzh.common.core.enums.AchievementConditionType;
import org.xyzh.common.core.enums.AchievementEventType;
/**
* @description 收藏资源数量成就检测器
* @filename ResourceCollectChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Component
public class ResourceCollectChecker extends AbstractAchievementChecker {
@Override
protected AchievementConditionType getSupportedConditionType() {
return AchievementConditionType.RESOURCE_COLLECT_COUNT;
}
@Override
protected AchievementEventType[] getSupportedEventTypes() {
return new AchievementEventType[]{
AchievementEventType.RESOURCE_COLLECTED
};
}
}

View File

@@ -0,0 +1,30 @@
package org.xyzh.achievement.checker.impl;
import org.springframework.stereotype.Component;
import org.xyzh.achievement.checker.AbstractAchievementChecker;
import org.xyzh.common.core.enums.AchievementConditionType;
import org.xyzh.common.core.enums.AchievementEventType;
/**
* @description 浏览资源数量成就检测器
* @filename ResourceViewChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Component
public class ResourceViewChecker extends AbstractAchievementChecker {
@Override
protected AchievementConditionType getSupportedConditionType() {
return AchievementConditionType.RESOURCE_VIEW_COUNT;
}
@Override
protected AchievementEventType[] getSupportedEventTypes() {
return new AchievementEventType[]{
AchievementEventType.RESOURCE_VIEWED
};
}
}

View File

@@ -0,0 +1,30 @@
package org.xyzh.achievement.checker.impl;
import org.springframework.stereotype.Component;
import org.xyzh.achievement.checker.AbstractAchievementChecker;
import org.xyzh.common.core.enums.AchievementConditionType;
import org.xyzh.common.core.enums.AchievementEventType;
/**
* @description 完成任务数量成就检测器
* @filename TaskCompleteChecker.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Component
public class TaskCompleteChecker extends AbstractAchievementChecker {
@Override
protected AchievementConditionType getSupportedConditionType() {
return AchievementConditionType.TASK_COMPLETE_COUNT;
}
@Override
protected AchievementEventType[] getSupportedEventTypes() {
return new AchievementEventType[]{
AchievementEventType.TASK_COMPLETED
};
}
}

View File

@@ -0,0 +1,218 @@
package org.xyzh.achievement.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.xyzh.api.achievement.AchievementService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.event.AchievementEvent;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.usercenter.TbAchievement;
import org.xyzh.common.dto.usercenter.TbUserAchievement;
import org.xyzh.common.dto.usercenter.TbUserAchievementProgress;
import java.util.List;
import java.util.Map;
/**
* @description 成就控制器
* @filename AchievementController.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@RestController
@RequestMapping("/achievement")
public class AchievementController {
private static final Logger logger = LoggerFactory.getLogger(AchievementController.class);
@Autowired
private AchievementService achievementService;
// ==================== 成就定义管理(管理员)====================
/**
* 创建成就
*/
@PostMapping("/create")
public ResultDomain<TbAchievement> createAchievement(@RequestBody TbAchievement achievement) {
return achievementService.createAchievement(achievement);
}
/**
* 更新成就
*/
@PutMapping("/update")
public ResultDomain<TbAchievement> updateAchievement(@RequestBody TbAchievement achievement) {
return achievementService.updateAchievement(achievement);
}
/**
* 删除成就
*/
@DeleteMapping("/delete/{achievementID}")
public ResultDomain<Void> deleteAchievement(@PathVariable String achievementID) {
return achievementService.deleteAchievement(achievementID);
}
/**
* 获取所有成就列表
*/
@GetMapping("/list")
public ResultDomain<TbAchievement> getAllAchievements(
@RequestParam(required = false) Integer type,
@RequestParam(required = false) Integer level) {
return achievementService.getAllAchievements(type, level);
}
/**
* 分页查询成就
*/
@PostMapping("/page")
public ResultDomain<TbAchievement> getAchievementPage(
@RequestBody(required = false) TbAchievement filter,
PageParam pageParam) {
return achievementService.getAchievementPage(filter, pageParam);
}
/**
* 获取成就详情
*/
@GetMapping("/detail/{achievementID}")
public ResultDomain<TbAchievement> getAchievementDetail(@PathVariable String achievementID) {
return achievementService.getAchievementDetail(achievementID);
}
// ==================== 用户成就查询 ====================
/**
* 获取用户已获得的成就
*/
@GetMapping("/user/{userID}")
public ResultDomain<TbUserAchievement> getUserAchievements(
@PathVariable String userID,
@RequestParam(required = false) Integer type) {
return achievementService.getUserAchievements(userID, type);
}
/**
* 获取当前用户的成就列表
*/
@GetMapping("/my")
public ResultDomain<TbUserAchievement> getMyAchievements(
@RequestParam(required = false) Integer type) {
return achievementService.getMyAchievements(type);
}
/**
* 检查用户是否已获得成就
*/
@GetMapping("/check/{userID}/{achievementID}")
public ResultDomain<Boolean> hasAchievement(
@PathVariable String userID,
@PathVariable String achievementID) {
return achievementService.hasAchievement(userID, achievementID);
}
// ==================== 成就授予(管理员)====================
/**
* 手动授予用户成就
*/
@PostMapping("/grant")
public ResultDomain<TbUserAchievement> grantAchievement(
@RequestParam String userID,
@RequestParam String achievementID) {
return achievementService.grantAchievement(userID, achievementID);
}
/**
* 撤销用户成就
*/
@DeleteMapping("/revoke")
public ResultDomain<Void> revokeAchievement(
@RequestParam String userID,
@RequestParam String achievementID) {
return achievementService.revokeAchievement(userID, achievementID);
}
// ==================== 成就进度查询 ====================
/**
* 获取用户成就进度
*/
@GetMapping("/progress/{userID}")
public ResultDomain<TbUserAchievementProgress> getUserAchievementProgress(
@PathVariable String userID,
@RequestParam(required = false) String achievementID) {
return achievementService.getUserAchievementProgress(userID, achievementID);
}
/**
* 获取当前用户的成就进度
*/
@GetMapping("/progress/my")
public ResultDomain<TbUserAchievementProgress> getMyAchievementProgress(
@RequestParam(required = false) String achievementID) {
return achievementService.getMyAchievementProgress(achievementID);
}
// ==================== 成就检测 ====================
/**
* 处理成就事件(内部接口,由其他服务调用)
*/
@PostMapping("/event/process")
public ResultDomain<List<TbUserAchievement>> processAchievementEvent(@RequestBody AchievementEvent event) {
return achievementService.processAchievementEvent(event);
}
/**
* 检查用户是否满足成就条件
*/
@GetMapping("/condition/check/{userID}/{achievementID}")
public ResultDomain<Boolean> checkAchievementCondition(
@PathVariable String userID,
@PathVariable String achievementID) {
return achievementService.checkAchievementCondition(userID, achievementID);
}
/**
* 批量检查用户可获得的成就
*/
@GetMapping("/available/{userID}")
public ResultDomain<List<TbAchievement>> checkAvailableAchievements(@PathVariable String userID) {
return achievementService.checkAvailableAchievements(userID);
}
// ==================== 成就统计 ====================
/**
* 获取用户成就统计
*/
@GetMapping("/statistics/{userID}")
public ResultDomain<Map<String, Object>> getUserAchievementStatistics(@PathVariable String userID) {
return achievementService.getUserAchievementStatistics(userID);
}
/**
* 获取成就排行榜
*/
@GetMapping("/ranking")
public ResultDomain<Map<String, Object>> getAchievementRanking(
@RequestParam(required = false, defaultValue = "10") Integer limit) {
return achievementService.getAchievementRanking(limit);
}
/**
* 获取最近获得成就的用户
*/
@GetMapping("/recent/{achievementID}")
public ResultDomain<List<TbUserAchievement>> getRecentAchievers(
@PathVariable String achievementID,
@RequestParam(required = false, defaultValue = "10") Integer limit) {
return achievementService.getRecentAchievers(achievementID, limit);
}
}

View File

@@ -0,0 +1,69 @@
package org.xyzh.achievement.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.xyzh.api.achievement.AchievementService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.event.AchievementEvent;
import org.xyzh.common.dto.usercenter.TbUserAchievement;
import java.util.List;
/**
* @description 成就事件监听器 - 监听业务事件并自动触发成就检测
* @filename AchievementEventListener.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Component
public class AchievementEventListener {
private static final Logger logger = LoggerFactory.getLogger(AchievementEventListener.class);
@Autowired
private AchievementService achievementService;
/**
* 监听成就事件
* 使用@Async异步处理避免阻塞主业务流程
*/
@Async
@EventListener
public void handleAchievementEvent(AchievementEvent event) {
try {
logger.debug("接收到成就事件: {}", event);
// 处理成就事件
ResultDomain<List<TbUserAchievement>> result = achievementService.processAchievementEvent(event);
if (result.isSuccess() && result.getData() != null && !result.getData().isEmpty()) {
logger.info("用户 {} 通过事件 {} 获得 {} 个新成就",
event.getUserID(),
event.getEventType(),
result.getData().size());
// 这里可以添加通知逻辑,如发送消息给用户
notifyUserAboutNewAchievements(event.getUserID(), result.getData());
}
} catch (Exception e) {
logger.error("处理成就事件异常: {}", e.getMessage(), e);
}
}
/**
* 通知用户获得新成就(示例方法,需要根据实际业务实现)
*/
private void notifyUserAboutNewAchievements(String userID, List<TbUserAchievement> achievements) {
// TODO: 实现通知逻辑
// 1. 发送系统消息
// 2. 发送推送通知
// 3. 记录通知日志
logger.info("通知用户 {} 获得成就,数量: {}", userID, achievements.size());
}
}

View File

@@ -0,0 +1,122 @@
package org.xyzh.achievement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.usercenter.TbAchievement;
import java.util.List;
/**
* @description 成就数据访问层
* @filename AchievementMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Mapper
public interface AchievementMapper extends BaseMapper<TbAchievement> {
/**
* @description 查询成就列表
* @param filter 过滤条件
* @return List<TbAchievement> 成就列表
*/
List<TbAchievement> selectAchievements(@Param("filter") TbAchievement filter);
/**
* @description 根据成就ID查询成就信息
* @param achievementId 成就ID
* @return TbAchievement 成就信息
*/
TbAchievement selectByAchievementId(@Param("achievementId") String achievementId);
/**
* @description 根据类型查询成就列表
* @param type 成就类型
* @return List<TbAchievement> 成就列表
*/
List<TbAchievement> selectByType(@Param("type") Integer type);
/**
* @description 根据类型和等级查询成就列表
* @param type 成就类型
* @param level 成就等级
* @return List<TbAchievement> 成就列表
*/
List<TbAchievement> selectByTypeAndLevel(@Param("type") Integer type, @Param("level") Integer level);
/**
* @description 根据条件类型查询成就列表
* @param conditionType 条件类型
* @return List<TbAchievement> 成就列表
*/
List<TbAchievement> selectByConditionType(@Param("conditionType") Integer conditionType);
/**
* @description 查询成就排行榜
* @param limit 限制数量
* @return List<TbAchievement> 成就排行榜
*/
List<TbAchievement> selectAchievementRanking(@Param("limit") Integer limit);
/**
* @description 检查成就名称是否存在
* @param name 成就名称
* @param excludeId 排除的成就ID用于更新时排除自身
* @return int 存在的数量
*/
int countByName(@Param("name") String name, @Param("excludeId") String excludeId);
/**
* @description 插入成就
* @param achievement 成就
* @return int 影响行数
*/
int insertAchievement(TbAchievement achievement);
/**
* @description 更新成就
* @param achievement 成就
* @return int 影响行数
*/
int updateAchievement(TbAchievement achievement);
/**
* @description 删除成就
* @param achievement 成就
* @return int 影响行数
*/
int deleteAchievement(TbAchievement achievement);
/**
* @description 批量插入成就
* @param achievementList 成就列表
* @return int 影响行数
*/
int batchInsertAchievements(@Param("achievementList") List<TbAchievement> achievementList);
/**
* @description 批量删除成就
* @param ids 成就ID列表
* @return int 影响行数
*/
int batchDeleteAchievements(@Param("ids") List<String> ids);
/**
* @description 分页查询成就
* @param filter 过滤条件
* @param pageParam 分页参数
* @return List<TbAchievement> 成就列表
*/
List<TbAchievement> selectAchievementsPage(@Param("filter") TbAchievement filter, @Param("pageParam") PageParam pageParam);
/**
* @description 统计成就总数
* @param filter 过滤条件
* @return long 总数
*/
long countAchievements(@Param("filter") TbAchievement filter);
}

View File

@@ -0,0 +1,125 @@
package org.xyzh.achievement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.usercenter.TbUserAchievement;
import java.util.List;
import java.util.Map;
/**
* @description 用户成就数据访问层
* @filename UserAchievementMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Mapper
public interface UserAchievementMapper extends BaseMapper<TbUserAchievement> {
/**
* @description 查询用户成就列表
* @param filter 过滤条件
* @return List<TbUserAchievement> 用户成就列表
*/
List<TbUserAchievement> selectUserAchievements(@Param("filter") TbUserAchievement filter);
/**
* @description 根据用户ID查询成就记录
* @param userId 用户ID
* @return List<TbUserAchievement> 成就记录列表
*/
List<TbUserAchievement> selectByUserId(@Param("userId") String userId);
/**
* @description 根据用户ID和成就ID查询成就记录
* @param userId 用户ID
* @param achievementId 成就ID
* @return TbUserAchievement 成就记录
*/
TbUserAchievement selectByUserIdAndAchievementId(@Param("userId") String userId, @Param("achievementId") String achievementId);
/**
* @description 根据用户ID和成就类型查询成就记录
* @param userId 用户ID
* @param type 成就类型
* @return List<TbUserAchievement> 成就记录列表
*/
List<TbUserAchievement> selectByUserIdAndType(@Param("userId") String userId, @Param("type") Integer type);
/**
* @description 查询用户成就统计
* @param userId 用户ID
* @return Map<String, Object> 统计信息
*/
Map<String, Object> selectAchievementStatistics(@Param("userId") String userId);
/**
* @description 查询成就的最近获得者
* @param achievementId 成就ID
* @param limit 查询数量
* @return List<TbUserAchievement> 用户成就列表
*/
List<TbUserAchievement> selectRecentAchievers(@Param("achievementId") String achievementId, @Param("limit") Integer limit);
/**
* @description 查询成就排行榜(按用户获得成就数量)
* @param limit 排行榜条数
* @return List<Map<String, Object>> 排行榜数据
*/
List<Map<String, Object>> selectUserAchievementRanking(@Param("limit") Integer limit);
/**
* @description 插入用户成就
* @param userAchievement 用户成就
* @return int 影响行数
*/
int insertUserAchievement(TbUserAchievement userAchievement);
/**
* @description 更新用户成就
* @param userAchievement 用户成就
* @return int 影响行数
*/
int updateUserAchievement(TbUserAchievement userAchievement);
/**
* @description 删除用户成就
* @param id 用户成就ID
* @return int 影响行数
*/
int deleteUserAchievement(@Param("id") String id);
/**
* @description 根据用户ID和成就ID删除
* @param userId 用户ID
* @param achievementId 成就ID
* @return int 影响行数
*/
int deleteByUserIdAndAchievementId(@Param("userId") String userId, @Param("achievementId") String achievementId);
/**
* @description 批量插入用户成就
* @param userAchievementList 用户成就列表
* @return int 影响行数
*/
int batchInsertUserAchievements(@Param("userAchievementList") List<TbUserAchievement> userAchievementList);
/**
* @description 分页查询用户成就
* @param filter 过滤条件
* @param pageParam 分页参数
* @return List<TbUserAchievement> 用户成就列表
*/
List<TbUserAchievement> selectUserAchievementsPage(@Param("filter") TbUserAchievement filter, @Param("pageParam") PageParam pageParam);
/**
* @description 统计用户成就总数
* @param filter 过滤条件
* @return long 总数
*/
long countUserAchievements(@Param("filter") TbUserAchievement filter);
}

View File

@@ -0,0 +1,88 @@
package org.xyzh.achievement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.dto.usercenter.TbUserAchievementProgress;
import java.util.List;
/**
* @description 用户成就进度数据访问层
* @filename UserAchievementProgressMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Mapper
public interface UserAchievementProgressMapper extends BaseMapper<TbUserAchievementProgress> {
/**
* @description 根据用户ID和成就ID查询进度
* @param userId 用户ID
* @param achievementId 成就ID
* @return TbUserAchievementProgress 成就进度
*/
TbUserAchievementProgress selectByUserIdAndAchievementId(@Param("userId") String userId, @Param("achievementId") String achievementId);
/**
* @description 根据用户ID查询所有进度
* @param userId 用户ID
* @return List<TbUserAchievementProgress> 成就进度列表
*/
List<TbUserAchievementProgress> selectByUserId(@Param("userId") String userId);
/**
* @description 根据用户ID和条件类型查询进度
* @param userId 用户ID
* @param conditionType 条件类型
* @return List<TbUserAchievementProgress> 成就进度列表
*/
List<TbUserAchievementProgress> selectByUserIdAndConditionType(@Param("userId") String userId, @Param("conditionType") Integer conditionType);
/**
* @description 查询未完成的进度
* @param userId 用户ID
* @return List<TbUserAchievementProgress> 成就进度列表
*/
List<TbUserAchievementProgress> selectIncompletedByUserId(@Param("userId") String userId);
/**
* @description 插入进度记录
* @param progress 成就进度
* @return int 影响行数
*/
int insertProgress(TbUserAchievementProgress progress);
/**
* @description 更新进度记录
* @param progress 成就进度
* @return int 影响行数
*/
int updateProgress(TbUserAchievementProgress progress);
/**
* @description 增加进度值
* @param userId 用户ID
* @param achievementId 成就ID
* @param incrementValue 增量值
* @return int 影响行数
*/
int incrementProgress(@Param("userId") String userId, @Param("achievementId") String achievementId, @Param("incrementValue") Integer incrementValue);
/**
* @description 批量插入进度记录
* @param progressList 成就进度列表
* @return int 影响行数
*/
int batchInsertProgress(@Param("progressList") List<TbUserAchievementProgress> progressList);
/**
* @description 删除进度记录
* @param userId 用户ID
* @param achievementId 成就ID
* @return int 影响行数
*/
int deleteProgress(@Param("userId") String userId, @Param("achievementId") String achievementId);
}

View File

@@ -0,0 +1,754 @@
package org.xyzh.achievement.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.xyzh.achievement.checker.AchievementChecker;
import org.xyzh.achievement.mapper.AchievementMapper;
import org.xyzh.achievement.mapper.UserAchievementMapper;
import org.xyzh.achievement.mapper.UserAchievementProgressMapper;
import org.xyzh.api.achievement.AchievementService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.enums.AchievementEventType;
import org.xyzh.common.core.event.AchievementEvent;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.user.TbSysUser;
import org.xyzh.common.dto.usercenter.TbAchievement;
import org.xyzh.common.dto.usercenter.TbUserAchievement;
import org.xyzh.common.dto.usercenter.TbUserAchievementProgress;
import org.xyzh.common.utils.IDUtils;
import org.xyzh.system.utils.LoginUtil;
import java.util.*;
import java.util.stream.Collectors;
/**
* @description 成就服务实现类
* @filename ACHAchievementServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-10-24
*/
@Service
public class ACHAchievementServiceImpl implements AchievementService {
private static final Logger logger = LoggerFactory.getLogger(ACHAchievementServiceImpl.class);
@Autowired
private AchievementMapper achievementMapper;
@Autowired
private UserAchievementMapper userAchievementMapper;
@Autowired
private UserAchievementProgressMapper progressMapper;
@Autowired
private List<AchievementChecker> checkers;
// ==================== 成就定义管理 ====================
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbAchievement> createAchievement(TbAchievement achievement) {
ResultDomain<TbAchievement> resultDomain = new ResultDomain<>();
try {
// 参数验证
if (achievement == null || !StringUtils.hasText(achievement.getName())) {
resultDomain.fail("成就名称不能为空");
return resultDomain;
}
// 检查名称是否已存在
int count = achievementMapper.countByName(achievement.getName(), null);
if (count > 0) {
resultDomain.fail("成就名称已存在");
return resultDomain;
}
// 设置默认值
if (achievement.getID() == null) {
achievement.setID(IDUtils.generateID());
}
if (achievement.getAchievementID() == null) {
achievement.setAchievementID(IDUtils.generateID());
}
achievement.setCreateTime(new Date());
achievement.setDeleted(false);
// 插入数据库
int result = achievementMapper.insertAchievement(achievement);
if (result > 0) {
resultDomain.success("创建成就成功", achievement);
} else {
resultDomain.fail("创建成就失败");
}
return resultDomain;
} catch (Exception e) {
logger.error("创建成就异常: {}", e.getMessage(), e);
resultDomain.fail("创建成就失败: " + e.getMessage());
return resultDomain;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbAchievement> updateAchievement(TbAchievement achievement) {
ResultDomain<TbAchievement> resultDomain = new ResultDomain<>();
try {
// 参数验证
if (achievement == null || !StringUtils.hasText(achievement.getID())) {
resultDomain.fail("成就ID不能为空");
return resultDomain;
}
// 检查成就是否存在
TbAchievement existing = achievementMapper.selectById(achievement.getID());
if (existing == null) {
resultDomain.fail("成就不存在");
return resultDomain;
}
// 检查名称是否重复
if (StringUtils.hasText(achievement.getName())) {
int count = achievementMapper.countByName(achievement.getName(), achievement.getID());
if (count > 0) {
resultDomain.fail("成就名称已存在");
return resultDomain;
}
}
achievement.setUpdateTime(new Date());
int result = achievementMapper.updateAchievement(achievement);
if (result > 0) {
resultDomain.success("更新成就成功", achievement);
} else {
resultDomain.fail("更新成就失败");
}
return resultDomain;
} catch (Exception e) {
logger.error("更新成就异常: {}", e.getMessage(), e);
resultDomain.fail("更新成就失败: " + e.getMessage());
return resultDomain;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<Void> deleteAchievement(String achievementID) {
ResultDomain<Void> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(achievementID)) {
resultDomain.fail("成就ID不能为空");
return resultDomain;
}
TbAchievement achievement = achievementMapper.selectByAchievementId(achievementID);
if (achievement == null) {
resultDomain.fail("成就不存在");
return resultDomain;
}
// 逻辑删除
achievement.setDeleted(true);
achievement.setDeleteTime(new Date());
int result = achievementMapper.updateAchievement(achievement);
if (result > 0) {
resultDomain.success("删除成就成功", (Void) null);
} else {
resultDomain.fail("删除成就失败");
}
return resultDomain;
} catch (Exception e) {
logger.error("删除成就异常: {}", e.getMessage(), e);
resultDomain.fail("删除成就失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<TbAchievement> getAllAchievements(Integer type, Integer level) {
ResultDomain<TbAchievement> resultDomain = new ResultDomain<>();
try {
List<TbAchievement> list;
if (type != null && level != null) {
list = achievementMapper.selectByTypeAndLevel(type, level);
} else if (type != null) {
list = achievementMapper.selectByType(type);
} else {
TbAchievement filter = new TbAchievement();
filter.setDeleted(false);
list = achievementMapper.selectAchievements(filter);
}
resultDomain.success("获取成就列表成功", list);
return resultDomain;
} catch (Exception e) {
logger.error("获取成就列表异常: {}", e.getMessage(), e);
resultDomain.fail("获取成就列表失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<TbAchievement> getAchievementPage(TbAchievement filter, PageParam pageParam) {
ResultDomain<TbAchievement> resultDomain = new ResultDomain<>();
try {
if (filter == null) {
filter = new TbAchievement();
filter.setDeleted(false);
}
List<TbAchievement> list = achievementMapper.selectAchievementsPage(filter, pageParam);
long total = achievementMapper.countAchievements(filter);
pageParam.setTotalElements(total);
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
resultDomain.success("获取成就分页成功", new PageDomain<>(pageParam, list));
return resultDomain;
} catch (Exception e) {
logger.error("获取成就分页异常: {}", e.getMessage(), e);
resultDomain.fail("获取成就分页失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<TbAchievement> getAchievementDetail(String achievementID) {
ResultDomain<TbAchievement> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(achievementID)) {
resultDomain.fail("成就ID不能为空");
return resultDomain;
}
TbAchievement achievement = achievementMapper.selectByAchievementId(achievementID);
if (achievement == null || achievement.getDeleted()) {
resultDomain.fail("成就不存在");
return resultDomain;
}
resultDomain.success("获取成就详情成功", achievement);
return resultDomain;
} catch (Exception e) {
logger.error("获取成就详情异常: {}", e.getMessage(), e);
resultDomain.fail("获取成就详情失败: " + e.getMessage());
return resultDomain;
}
}
// ==================== 用户成就管理 ====================
@Override
public ResultDomain<TbUserAchievement> getUserAchievements(String userID, Integer type) {
ResultDomain<TbUserAchievement> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID)) {
resultDomain.fail("用户ID不能为空");
return resultDomain;
}
List<TbUserAchievement> list;
if (type != null) {
list = userAchievementMapper.selectByUserIdAndType(userID, type);
} else {
list = userAchievementMapper.selectByUserId(userID);
}
resultDomain.success("获取用户成就成功", list);
return resultDomain;
} catch (Exception e) {
logger.error("获取用户成就异常: {}", e.getMessage(), e);
resultDomain.fail("获取用户成就失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<TbUserAchievement> getMyAchievements(Integer type) {
TbSysUser user = LoginUtil.getCurrentUser();
if (user == null) {
ResultDomain<TbUserAchievement> resultDomain = new ResultDomain<>();
resultDomain.fail("请先登录");
return resultDomain;
}
return getUserAchievements(user.getID(), type);
}
@Override
public ResultDomain<Boolean> hasAchievement(String userID, String achievementID) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID) || !StringUtils.hasText(achievementID)) {
resultDomain.fail("参数不能为空");
return resultDomain;
}
TbUserAchievement userAchievement = userAchievementMapper.selectByUserIdAndAchievementId(userID, achievementID);
resultDomain.success("检查成功", userAchievement != null);
return resultDomain;
} catch (Exception e) {
logger.error("检查用户成就异常: {}", e.getMessage(), e);
resultDomain.fail("检查用户成就失败: " + e.getMessage());
return resultDomain;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbUserAchievement> grantAchievement(String userID, String achievementID) {
ResultDomain<TbUserAchievement> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID) || !StringUtils.hasText(achievementID)) {
resultDomain.fail("参数不能为空");
return resultDomain;
}
// 检查成就是否存在
TbAchievement achievement = achievementMapper.selectByAchievementId(achievementID);
if (achievement == null || achievement.getDeleted()) {
resultDomain.fail("成就不存在");
return resultDomain;
}
// 检查用户是否已获得
TbUserAchievement existing = userAchievementMapper.selectByUserIdAndAchievementId(userID, achievementID);
if (existing != null) {
resultDomain.fail("用户已获得该成就");
return resultDomain;
}
// 创建用户成就记录
TbUserAchievement userAchievement = new TbUserAchievement();
userAchievement.setID(IDUtils.generateID());
userAchievement.setUserID(userID);
userAchievement.setAchievementID(achievementID);
userAchievement.setObtainTime(new Date());
int result = userAchievementMapper.insertUserAchievement(userAchievement);
if (result > 0) {
// 更新进度为完成
updateProgressToCompleted(userID, achievementID);
logger.info("用户 {} 获得成就: {}", userID, achievement.getName());
resultDomain.success("授予成就成功", userAchievement);
} else {
resultDomain.fail("授予成就失败");
}
return resultDomain;
} catch (Exception e) {
logger.error("授予成就异常: {}", e.getMessage(), e);
resultDomain.fail("授予成就失败: " + e.getMessage());
return resultDomain;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<Void> revokeAchievement(String userID, String achievementID) {
ResultDomain<Void> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID) || !StringUtils.hasText(achievementID)) {
resultDomain.fail("参数不能为空");
return resultDomain;
}
int result = userAchievementMapper.deleteByUserIdAndAchievementId(userID, achievementID);
if (result > 0) {
// 重置进度
progressMapper.deleteProgress(userID, achievementID);
resultDomain.success("撤销成就成功", (Void) null);
} else {
resultDomain.fail("撤销成就失败");
}
return resultDomain;
} catch (Exception e) {
logger.error("撤销成就异常: {}", e.getMessage(), e);
resultDomain.fail("撤销成就失败: " + e.getMessage());
return resultDomain;
}
}
// ==================== 成就进度管理 ====================
@Override
public ResultDomain<TbUserAchievementProgress> getUserAchievementProgress(String userID, String achievementID) {
ResultDomain<TbUserAchievementProgress> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID)) {
resultDomain.fail("用户ID不能为空");
return resultDomain;
}
List<TbUserAchievementProgress> list;
if (StringUtils.hasText(achievementID)) {
TbUserAchievementProgress progress = progressMapper.selectByUserIdAndAchievementId(userID, achievementID);
list = progress != null ? Collections.singletonList(progress) : new ArrayList<>();
} else {
list = progressMapper.selectByUserId(userID);
}
resultDomain.success("获取成就进度成功", list);
return resultDomain;
} catch (Exception e) {
logger.error("获取成就进度异常: {}", e.getMessage(), e);
resultDomain.fail("获取成就进度失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<TbUserAchievementProgress> getMyAchievementProgress(String achievementID) {
TbSysUser user = LoginUtil.getCurrentUser();
if (user == null) {
ResultDomain<TbUserAchievementProgress> resultDomain = new ResultDomain<>();
resultDomain.fail("请先登录");
return resultDomain;
}
return getUserAchievementProgress(user.getID(), achievementID);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbUserAchievementProgress> updateProgress(String userID, String achievementID, Integer incrementValue) {
ResultDomain<TbUserAchievementProgress> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID) || !StringUtils.hasText(achievementID)) {
resultDomain.fail("参数不能为空");
return resultDomain;
}
// 获取或创建进度记录
TbUserAchievementProgress progress = getOrCreateProgress(userID, achievementID);
if (progress == null) {
resultDomain.fail("获取进度失败");
return resultDomain;
}
// 更新进度
progress.setCurrentValue(progress.getCurrentValue() + incrementValue);
progress.setLastUpdateTime(new Date());
progressMapper.updateProgress(progress);
resultDomain.success("更新进度成功", progress);
return resultDomain;
} catch (Exception e) {
logger.error("更新进度异常: {}", e.getMessage(), e);
resultDomain.fail("更新进度失败: " + e.getMessage());
return resultDomain;
}
}
// ==================== 成就检测与触发 ====================
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<List<TbUserAchievement>> processAchievementEvent(AchievementEvent event) {
ResultDomain<List<TbUserAchievement>> resultDomain = new ResultDomain<>();
List<TbUserAchievement> newAchievements = new ArrayList<>();
try {
if (event == null || !StringUtils.hasText(event.getUserID()) || event.getEventType() == null) {
resultDomain.fail("事件参数不完整");
return resultDomain;
}
logger.debug("处理成就事件: {}", event);
// 获取该事件类型相关的所有成就
List<TbAchievement> achievements = getAchievementsByEventType(event.getEventType());
if (achievements.isEmpty()) {
resultDomain.success("无相关成就", newAchievements);
return resultDomain;
}
// 遍历每个成就进行检测
for (TbAchievement achievement : achievements) {
// 检查用户是否已获得
if (userAchievementMapper.selectByUserIdAndAchievementId(event.getUserID(), achievement.getAchievementID()) != null) {
continue; // 已获得,跳过
}
// 获取合适的检测器
AchievementChecker checker = getCheckerForAchievement(achievement);
if (checker == null || !checker.supportsEventType(event.getEventType())) {
continue;
}
// 获取当前进度
TbUserAchievementProgress progress = getOrCreateProgress(event.getUserID(), achievement.getAchievementID());
Integer currentProgress = progress.getCurrentValue();
// 计算新进度
Integer newProgress = checker.calculateNewProgress(event, currentProgress);
// 更新进度
progress.setCurrentValue(newProgress);
progress.setTargetValue(achievement.getConditionValue());
progress.setLastUpdateTime(new Date());
progressMapper.updateProgress(progress);
// 检查是否达成
if (checker.check(event, achievement, currentProgress)) {
TbUserAchievement userAchievement = grantAchievementInternal(event.getUserID(), achievement.getAchievementID());
if (userAchievement != null) {
newAchievements.add(userAchievement);
logger.info("用户 {} 通过事件 {} 获得成就: {}", event.getUserID(), event.getEventType(), achievement.getName());
}
}
}
resultDomain.success("处理成就事件成功", newAchievements);
return resultDomain;
} catch (Exception e) {
logger.error("处理成就事件异常: {}", e.getMessage(), e);
resultDomain.fail("处理成就事件失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<Boolean> checkAchievementCondition(String userID, String achievementID) {
ResultDomain<Boolean> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID) || !StringUtils.hasText(achievementID)) {
resultDomain.fail("参数不能为空");
return resultDomain;
}
// 获取成就定义
TbAchievement achievement = achievementMapper.selectByAchievementId(achievementID);
if (achievement == null) {
resultDomain.fail("成就不存在");
return resultDomain;
}
// 获取进度
TbUserAchievementProgress progress = progressMapper.selectByUserIdAndAchievementId(userID, achievementID);
Integer currentValue = progress != null ? progress.getCurrentValue() : 0;
Integer targetValue = achievement.getConditionValue();
boolean satisfied = currentValue >= targetValue;
resultDomain.success("检查完成", satisfied);
return resultDomain;
} catch (Exception e) {
logger.error("检查成就条件异常: {}", e.getMessage(), e);
resultDomain.fail("检查成就条件失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<List<TbAchievement>> checkAvailableAchievements(String userID) {
ResultDomain<List<TbAchievement>> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID)) {
resultDomain.fail("用户ID不能为空");
return resultDomain;
}
// 获取所有成就
TbAchievement filter = new TbAchievement();
filter.setDeleted(false);
List<TbAchievement> allAchievements = achievementMapper.selectAchievements(filter);
// 获取用户进度
List<TbUserAchievementProgress> progressList = progressMapper.selectIncompletedByUserId(userID);
Map<String, Integer> progressMap = progressList.stream()
.collect(Collectors.toMap(
TbUserAchievementProgress::getAchievementID,
TbUserAchievementProgress::getCurrentValue
));
// 筛选可获得的成就
List<TbAchievement> availableAchievements = allAchievements.stream()
.filter(achievement -> {
Integer currentValue = progressMap.getOrDefault(achievement.getAchievementID(), 0);
return currentValue >= achievement.getConditionValue();
})
.collect(Collectors.toList());
resultDomain.success("检查可获得成就成功", availableAchievements);
return resultDomain;
} catch (Exception e) {
logger.error("检查可获得成就异常: {}", e.getMessage(), e);
resultDomain.fail("检查可获得成就失败: " + e.getMessage());
return resultDomain;
}
}
// ==================== 成就统计 ====================
@Override
public ResultDomain<Map<String, Object>> getUserAchievementStatistics(String userID) {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(userID)) {
resultDomain.fail("用户ID不能为空");
return resultDomain;
}
Map<String, Object> statistics = userAchievementMapper.selectAchievementStatistics(userID);
if (statistics == null) {
statistics = new HashMap<>();
statistics.put("totalAchievements", 0);
statistics.put("totalPoints", 0);
}
resultDomain.success("获取统计信息成功", statistics);
return resultDomain;
} catch (Exception e) {
logger.error("获取统计信息异常: {}", e.getMessage(), e);
resultDomain.fail("获取统计信息失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<Map<String, Object>> getAchievementRanking(Integer limit) {
ResultDomain<Map<String, Object>> resultDomain = new ResultDomain<>();
try {
if (limit == null || limit <= 0) {
limit = 10; // 默认10条
}
List<Map<String, Object>> ranking = userAchievementMapper.selectUserAchievementRanking(limit);
Map<String, Object> result = new HashMap<>();
result.put("ranking", ranking);
result.put("limit", limit);
resultDomain.success("获取排行榜成功", result);
return resultDomain;
} catch (Exception e) {
logger.error("获取排行榜异常: {}", e.getMessage(), e);
resultDomain.fail("获取排行榜失败: " + e.getMessage());
return resultDomain;
}
}
@Override
public ResultDomain<List<TbUserAchievement>> getRecentAchievers(String achievementID, Integer limit) {
ResultDomain<List<TbUserAchievement>> resultDomain = new ResultDomain<>();
try {
if (!StringUtils.hasText(achievementID)) {
resultDomain.fail("成就ID不能为空");
return resultDomain;
}
if (limit == null || limit <= 0) {
limit = 10;
}
List<TbUserAchievement> achievers = userAchievementMapper.selectRecentAchievers(achievementID, limit);
resultDomain.success("获取最近获得者成功", achievers);
return resultDomain;
} catch (Exception e) {
logger.error("获取最近获得者异常: {}", e.getMessage(), e);
resultDomain.fail("获取最近获得者失败: " + e.getMessage());
return resultDomain;
}
}
// ==================== 私有辅助方法 ====================
/**
* 获取或创建进度记录
*/
private TbUserAchievementProgress getOrCreateProgress(String userID, String achievementID) {
TbUserAchievementProgress progress = progressMapper.selectByUserIdAndAchievementId(userID, achievementID);
if (progress == null) {
// 创建新的进度记录
TbAchievement achievement = achievementMapper.selectByAchievementId(achievementID);
if (achievement == null) {
return null;
}
progress = new TbUserAchievementProgress();
progress.setID(IDUtils.generateID());
progress.setUserID(userID);
progress.setAchievementID(achievementID);
progress.setCurrentValue(0);
progress.setTargetValue(achievement.getConditionValue());
progress.setCompleted(false);
progress.setLastUpdateTime(new Date());
progress.setCreateTime(new Date());
progressMapper.insertProgress(progress);
}
return progress;
}
/**
* 更新进度为已完成
*/
private void updateProgressToCompleted(String userID, String achievementID) {
TbUserAchievementProgress progress = progressMapper.selectByUserIdAndAchievementId(userID, achievementID);
if (progress != null) {
progress.setCompleted(true);
progress.setCurrentValue(progress.getTargetValue());
progress.setLastUpdateTime(new Date());
progressMapper.updateProgress(progress);
}
}
/**
* 内部授予成就方法(不检查是否已获得)
*/
private TbUserAchievement grantAchievementInternal(String userID, String achievementID) {
try {
TbUserAchievement userAchievement = new TbUserAchievement();
userAchievement.setID(IDUtils.generateID());
userAchievement.setUserID(userID);
userAchievement.setAchievementID(achievementID);
userAchievement.setObtainTime(new Date());
int result = userAchievementMapper.insertUserAchievement(userAchievement);
if (result > 0) {
updateProgressToCompleted(userID, achievementID);
return userAchievement;
}
return null;
} catch (Exception e) {
logger.error("内部授予成就失败: {}", e.getMessage(), e);
return null;
}
}
/**
* 根据事件类型获取相关成就
*/
private List<TbAchievement> getAchievementsByEventType(AchievementEventType eventType) {
// 获取所有成就
TbAchievement filter = new TbAchievement();
filter.setDeleted(false);
List<TbAchievement> allAchievements = achievementMapper.selectAchievements(filter);
// 筛选支持该事件类型的成就
return allAchievements.stream()
.filter(achievement -> {
AchievementChecker checker = getCheckerForAchievement(achievement);
return checker != null && checker.supportsEventType(eventType);
})
.collect(Collectors.toList());
}
/**
* 获取成就对应的检测器
*/
private AchievementChecker getCheckerForAchievement(TbAchievement achievement) {
for (AchievementChecker checker : checkers) {
if (checker.supports(achievement)) {
return checker;
}
}
return null;
}
}

View File

@@ -0,0 +1,246 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.achievement.mapper.AchievementMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.usercenter.TbAchievement">
<id column="id" property="ID" />
<result column="achievement_id" property="achievementID" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="icon" property="icon" />
<result column="type" property="type" />
<result column="level" property="level" />
<result column="condition_type" property="conditionType" />
<result column="condition_value" property="conditionValue" />
<result column="points" property="points" />
<result column="order_num" property="orderNum" />
<result column="creator" property="creator" />
<result column="updater" property="updater" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
<result column="delete_time" property="deleteTime" />
<result column="deleted" property="deleted" />
</resultMap>
<!-- 字段列表 -->
<sql id="Base_Column_List">
id, achievement_id, name, description, icon, type, level, condition_type,
condition_value, points, order_num, creator, updater, create_time, update_time,
delete_time, deleted
</sql>
<!-- 查询条件 -->
<sql id="Base_Where_Clause">
<where>
<if test="filter != null">
<if test="filter.ID != null and filter.ID != ''">
AND id = #{filter.ID}
</if>
<if test="filter.achievementID != null and filter.achievementID != ''">
AND achievement_id = #{filter.achievementID}
</if>
<if test="filter.name != null and filter.name != ''">
AND name LIKE CONCAT('%', #{filter.name}, '%')
</if>
<if test="filter.type != null">
AND type = #{filter.type}
</if>
<if test="filter.level != null">
AND level = #{filter.level}
</if>
<if test="filter.conditionType != null">
AND condition_type = #{filter.conditionType}
</if>
<if test="filter.points != null">
AND points = #{filter.points}
</if>
<if test="filter.creator != null and filter.creator != ''">
AND creator = #{filter.creator}
</if>
<if test="filter.deleted != null">
AND deleted = #{filter.deleted}
</if>
</if>
</where>
</sql>
<!-- 查询成就列表 -->
<select id="selectAchievements" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_achievement
<include refid="Base_Where_Clause" />
ORDER BY order_num ASC, create_time DESC
</select>
<!-- 根据成就ID查询成就信息 -->
<select id="selectByAchievementId" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_achievement
WHERE achievement_id = #{achievementId} AND deleted = 0
</select>
<!-- 根据类型查询成就列表 -->
<select id="selectByType" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_achievement
WHERE type = #{type} AND deleted = 0
ORDER BY order_num ASC, create_time DESC
</select>
<!-- 根据类型和等级查询成就列表 -->
<select id="selectByTypeAndLevel" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_achievement
WHERE type = #{type} AND level = #{level} AND deleted = 0
ORDER BY order_num ASC, create_time DESC
</select>
<!-- 根据条件类型查询成就列表 -->
<select id="selectByConditionType" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_achievement
WHERE condition_type = #{conditionType} AND deleted = 0
ORDER BY order_num ASC, create_time DESC
</select>
<!-- 查询成就排行榜 -->
<select id="selectAchievementRanking" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_achievement
WHERE deleted = 0
ORDER BY points DESC, level DESC, order_num ASC
<if test="limit != null and limit > 0">
LIMIT #{limit}
</if>
</select>
<!-- 检查成就名称是否存在 -->
<select id="countByName" resultType="int">
SELECT COUNT(1)
FROM tb_achievement
WHERE name = #{name} AND deleted = 0
<if test="excludeId != null and excludeId != ''">
AND id != #{excludeId}
</if>
</select>
<!-- 插入成就 -->
<insert id="insertAchievement" parameterType="org.xyzh.common.dto.usercenter.TbAchievement">
INSERT INTO tb_achievement (
id, achievement_id, name, description, icon, type, level, condition_type,
condition_value, points, order_num, creator, updater, create_time, update_time,
delete_time, deleted
) VALUES (
#{ID}, #{achievementID}, #{name}, #{description}, #{icon}, #{type}, #{level},
#{conditionType}, #{conditionValue}, #{points}, #{orderNum}, #{creator}, #{updater},
#{createTime}, #{updateTime}, #{deleteTime}, #{deleted}
)
</insert>
<!-- 更新成就 -->
<update id="updateAchievement" parameterType="org.xyzh.common.dto.usercenter.TbAchievement">
UPDATE tb_achievement
<set>
<if test="achievementID != null and achievementID != ''">
achievement_id = #{achievementID},
</if>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="description != null and description != ''">
description = #{description},
</if>
<if test="icon != null and icon != ''">
icon = #{icon},
</if>
<if test="type != null">
type = #{type},
</if>
<if test="level != null">
level = #{level},
</if>
<if test="conditionType != null">
condition_type = #{conditionType},
</if>
<if test="conditionValue != null">
condition_value = #{conditionValue},
</if>
<if test="points != null">
points = #{points},
</if>
<if test="orderNum != null">
order_num = #{orderNum},
</if>
<if test="updater != null and updater != ''">
updater = #{updater},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="deleteTime != null">
delete_time = #{deleteTime},
</if>
<if test="deleted != null">
deleted = #{deleted},
</if>
</set>
WHERE id = #{ID}
</update>
<!-- 删除成就 -->
<delete id="deleteAchievement" parameterType="org.xyzh.common.dto.usercenter.TbAchievement">
DELETE FROM tb_achievement
WHERE id = #{ID}
</delete>
<!-- 批量插入成就 -->
<insert id="batchInsertAchievements" parameterType="java.util.List">
INSERT INTO tb_achievement (
id, achievement_id, name, description, icon, type, level, condition_type,
condition_value, points, order_num, creator, updater, create_time, update_time,
delete_time, deleted
) VALUES
<foreach collection="achievementList" item="item" separator=",">
(
#{item.ID}, #{item.achievementID}, #{item.name}, #{item.description}, #{item.icon},
#{item.type}, #{item.level}, #{item.conditionType}, #{item.conditionValue},
#{item.points}, #{item.orderNum}, #{item.creator}, #{item.updater},
#{item.createTime}, #{item.updateTime}, #{item.deleteTime}, #{item.deleted}
)
</foreach>
</insert>
<!-- 批量删除成就 -->
<delete id="batchDeleteAchievements">
DELETE FROM tb_achievement
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 分页查询成就 -->
<select id="selectAchievementsPage" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_achievement
<include refid="Base_Where_Clause" />
ORDER BY order_num ASC, create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<!-- 统计成就总数 -->
<select id="countAchievements" resultType="long">
SELECT COUNT(1)
FROM tb_achievement
<include refid="Base_Where_Clause" />
</select>
</mapper>

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.achievement.mapper.UserAchievementMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.usercenter.TbUserAchievement">
<id column="id" property="ID" />
<result column="user_id" property="userID" />
<result column="achievement_id" property="achievementID" />
<result column="obtain_time" property="obtainTime" />
</resultMap>
<!-- 字段列表 -->
<sql id="Base_Column_List">
id, user_id, achievement_id, obtain_time
</sql>
<!-- 查询条件 -->
<sql id="Base_Where_Clause">
<where>
<if test="filter != null">
<if test="filter.ID != null and filter.ID != ''">
AND ua.id = #{filter.ID}
</if>
<if test="filter.userID != null and filter.userID != ''">
AND ua.user_id = #{filter.userID}
</if>
<if test="filter.achievementID != null and filter.achievementID != ''">
AND ua.achievement_id = #{filter.achievementID}
</if>
</if>
</where>
</sql>
<!-- 查询用户成就列表 -->
<select id="selectUserAchievements" resultMap="BaseResultMap">
SELECT
ua.id, ua.user_id, ua.achievement_id, ua.obtain_time
FROM tb_user_achievement ua
<include refid="Base_Where_Clause" />
ORDER BY ua.obtain_time DESC
</select>
<!-- 根据用户ID查询成就记录 -->
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_user_achievement
WHERE user_id = #{userId}
ORDER BY obtain_time DESC
</select>
<!-- 根据用户ID和成就ID查询成就记录 -->
<select id="selectByUserIdAndAchievementId" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_user_achievement
WHERE user_id = #{userId} AND achievement_id = #{achievementId}
LIMIT 1
</select>
<!-- 根据用户ID和成就类型查询成就记录 -->
<select id="selectByUserIdAndType" resultMap="BaseResultMap">
SELECT
ua.id, ua.user_id, ua.achievement_id, ua.obtain_time
FROM tb_user_achievement ua
INNER JOIN tb_achievement a ON ua.achievement_id = a.achievement_id
WHERE ua.user_id = #{userId} AND a.type = #{type} AND a.deleted = 0
ORDER BY ua.obtain_time DESC
</select>
<!-- 查询用户成就统计 -->
<select id="selectAchievementStatistics" resultType="map">
SELECT
COUNT(*) AS totalAchievements,
IFNULL(SUM(a.points), 0) AS totalPoints,
COUNT(DISTINCT a.type) AS achievementTypes,
MAX(ua.obtain_time) AS latestObtainTime
FROM tb_user_achievement ua
INNER JOIN tb_achievement a ON ua.achievement_id = a.achievement_id
WHERE ua.user_id = #{userId} AND a.deleted = 0
</select>
<!-- 查询成就的最近获得者 -->
<select id="selectRecentAchievers" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_user_achievement
WHERE achievement_id = #{achievementId}
ORDER BY obtain_time DESC
<if test="limit != null and limit > 0">
LIMIT #{limit}
</if>
</select>
<!-- 查询成就排行榜(按用户获得成就数量) -->
<select id="selectUserAchievementRanking" resultType="map">
SELECT
ua.user_id AS userID,
u.username,
u.nickname,
COUNT(*) AS achievementCount,
IFNULL(SUM(a.points), 0) AS totalPoints,
MAX(ua.obtain_time) AS latestObtainTime
FROM tb_user_achievement ua
INNER JOIN tb_achievement a ON ua.achievement_id = a.achievement_id
INNER JOIN tb_sys_user u ON ua.user_id = u.id
WHERE a.deleted = 0
GROUP BY ua.user_id, u.username, u.nickname
ORDER BY achievementCount DESC, totalPoints DESC, latestObtainTime DESC
<if test="limit != null and limit > 0">
LIMIT #{limit}
</if>
</select>
<!-- 插入用户成就 -->
<insert id="insertUserAchievement" parameterType="org.xyzh.common.dto.usercenter.TbUserAchievement">
INSERT INTO tb_user_achievement (
id, user_id, achievement_id, obtain_time
) VALUES (
#{ID}, #{userID}, #{achievementID}, #{obtainTime}
)
</insert>
<!-- 更新用户成就 -->
<update id="updateUserAchievement" parameterType="org.xyzh.common.dto.usercenter.TbUserAchievement">
UPDATE tb_user_achievement
<set>
<if test="userID != null and userID != ''">
user_id = #{userID},
</if>
<if test="achievementID != null and achievementID != ''">
achievement_id = #{achievementID},
</if>
<if test="obtainTime != null">
obtain_time = #{obtainTime},
</if>
</set>
WHERE id = #{ID}
</update>
<!-- 删除用户成就 -->
<delete id="deleteUserAchievement">
DELETE FROM tb_user_achievement
WHERE id = #{id}
</delete>
<!-- 根据用户ID和成就ID删除 -->
<delete id="deleteByUserIdAndAchievementId">
DELETE FROM tb_user_achievement
WHERE user_id = #{userId} AND achievement_id = #{achievementId}
</delete>
<!-- 批量插入用户成就 -->
<insert id="batchInsertUserAchievements" parameterType="java.util.List">
INSERT INTO tb_user_achievement (
id, user_id, achievement_id, obtain_time
) VALUES
<foreach collection="userAchievementList" item="item" separator=",">
(
#{item.ID}, #{item.userID}, #{item.achievementID}, #{item.obtainTime}
)
</foreach>
</insert>
<!-- 分页查询用户成就 -->
<select id="selectUserAchievementsPage" resultMap="BaseResultMap">
SELECT
ua.id, ua.user_id, ua.achievement_id, ua.obtain_time
FROM tb_user_achievement ua
<include refid="Base_Where_Clause" />
ORDER BY ua.obtain_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<!-- 统计用户成就总数 -->
<select id="countUserAchievements" resultType="long">
SELECT COUNT(1)
FROM tb_user_achievement ua
<include refid="Base_Where_Clause" />
</select>
</mapper>

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.achievement.mapper.UserAchievementProgressMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.usercenter.TbUserAchievementProgress">
<id column="id" property="ID" />
<result column="user_id" property="userID" />
<result column="achievement_id" property="achievementID" />
<result column="current_value" property="currentValue" />
<result column="target_value" property="targetValue" />
<result column="progress_percentage" property="progressPercentage" />
<result column="completed" property="completed" />
<result column="last_update_time" property="lastUpdateTime" />
<result column="create_time" property="createTime" />
</resultMap>
<!-- 字段列表 -->
<sql id="Base_Column_List">
id, user_id, achievement_id, current_value, target_value, progress_percentage,
completed, last_update_time, create_time
</sql>
<!-- 根据用户ID和成就ID查询进度 -->
<select id="selectByUserIdAndAchievementId" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_user_achievement_progress
WHERE user_id = #{userId} AND achievement_id = #{achievementId}
LIMIT 1
</select>
<!-- 根据用户ID查询所有进度 -->
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_user_achievement_progress
WHERE user_id = #{userId}
ORDER BY last_update_time DESC
</select>
<!-- 根据用户ID和条件类型查询进度 -->
<select id="selectByUserIdAndConditionType" resultMap="BaseResultMap">
SELECT
p.id, p.user_id, p.achievement_id, p.current_value, p.target_value,
p.progress_percentage, p.completed, p.last_update_time, p.create_time
FROM tb_user_achievement_progress p
INNER JOIN tb_achievement a ON p.achievement_id = a.achievement_id
WHERE p.user_id = #{userId} AND a.condition_type = #{conditionType} AND a.deleted = 0
ORDER BY p.last_update_time DESC
</select>
<!-- 查询未完成的进度 -->
<select id="selectIncompletedByUserId" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_user_achievement_progress
WHERE user_id = #{userId} AND completed = 0
ORDER BY last_update_time DESC
</select>
<!-- 插入进度记录 -->
<insert id="insertProgress" parameterType="org.xyzh.common.dto.usercenter.TbUserAchievementProgress">
INSERT INTO tb_user_achievement_progress (
id, user_id, achievement_id, current_value, target_value,
progress_percentage, completed, last_update_time, create_time
) VALUES (
#{ID}, #{userID}, #{achievementID}, #{currentValue}, #{targetValue},
#{progressPercentage}, #{completed}, #{lastUpdateTime}, #{createTime}
)
</insert>
<!-- 更新进度记录 -->
<update id="updateProgress" parameterType="org.xyzh.common.dto.usercenter.TbUserAchievementProgress">
UPDATE tb_user_achievement_progress
<set>
<if test="currentValue != null">
current_value = #{currentValue},
</if>
<if test="targetValue != null">
target_value = #{targetValue},
</if>
<if test="progressPercentage != null">
progress_percentage = #{progressPercentage},
</if>
<if test="completed != null">
completed = #{completed},
</if>
<if test="lastUpdateTime != null">
last_update_time = #{lastUpdateTime},
</if>
</set>
WHERE id = #{ID}
</update>
<!-- 增加进度值 -->
<update id="incrementProgress">
UPDATE tb_user_achievement_progress
SET
current_value = current_value + #{incrementValue},
progress_percentage = LEAST(100, ROUND(((current_value + #{incrementValue}) * 100.0 / target_value), 0)),
completed = IF((current_value + #{incrementValue}) >= target_value, 1, 0),
last_update_time = NOW()
WHERE user_id = #{userId} AND achievement_id = #{achievementId}
</update>
<!-- 批量插入进度记录 -->
<insert id="batchInsertProgress" parameterType="java.util.List">
INSERT INTO tb_user_achievement_progress (
id, user_id, achievement_id, current_value, target_value,
progress_percentage, completed, last_update_time, create_time
) VALUES
<foreach collection="progressList" item="item" separator=",">
(
#{item.ID}, #{item.userID}, #{item.achievementID}, #{item.currentValue},
#{item.targetValue}, #{item.progressPercentage}, #{item.completed},
#{item.lastUpdateTime}, #{item.createTime}
)
</foreach>
</insert>
<!-- 删除进度记录 -->
<delete id="deleteProgress">
DELETE FROM tb_user_achievement_progress
WHERE user_id = #{userId} AND achievement_id = #{achievementId}
</delete>
</mapper>