diff --git a/schoolNewsServ/.bin/mysql/sql/createTableAchievement.sql b/schoolNewsServ/.bin/mysql/sql/createTableAchievement.sql new file mode 100644 index 0000000..6e4ce15 --- /dev/null +++ b/schoolNewsServ/.bin/mysql/sql/createTableAchievement.sql @@ -0,0 +1,326 @@ +-- ============================================= +-- 成就系统完整建表SQL +-- 作者: yslg +-- 日期: 2025-10-24 +-- 版本: 1.0.0 +-- 说明: 包含成就系统的所有数据表及初始数据 +-- ============================================= + +-- ============================================= +-- 1. 成就定义表 (tb_achievement) +-- ============================================= + +DROP TABLE IF EXISTS `tb_achievement`; + +CREATE TABLE `tb_achievement` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键ID', + `achievement_id` VARCHAR(32) NOT NULL COMMENT '成就唯一标识', + `name` VARCHAR(100) NOT NULL COMMENT '成就名称', + `description` VARCHAR(500) DEFAULT NULL COMMENT '成就描述', + `icon` VARCHAR(255) DEFAULT NULL COMMENT '成就图标URL', + `type` INT DEFAULT 1 COMMENT '成就类型:1-勋章 2-等级', + `level` INT DEFAULT 1 COMMENT '成就等级:1-铜牌 2-银牌 3-金牌 4-钻石', + `condition_type` INT NOT NULL COMMENT '触发条件类型:1-学习时长 2-资源数量 3-课程数量 4-连续登录 5-收藏数量 6-任务数量 7-积分数量 8-评论数量 9-章节数量 10-累计登录', + `condition_value` INT NOT NULL COMMENT '条件值(达成所需的数值)', + `points` INT DEFAULT 0 COMMENT '获得积分奖励', + `order_num` INT DEFAULT 0 COMMENT '排序号', + `creator` VARCHAR(32) DEFAULT NULL COMMENT '创建者ID', + `updater` VARCHAR(32) DEFAULT NULL COMMENT '更新者ID', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` DATETIME DEFAULT NULL COMMENT '删除时间', + `deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否 1-是', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_achievement_id` (`achievement_id`), + UNIQUE KEY `uk_name` (`name`, `deleted`), + KEY `idx_type` (`type`), + KEY `idx_level` (`level`), + KEY `idx_condition_type` (`condition_type`), + KEY `idx_deleted` (`deleted`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='成就定义表'; + +-- ============================================= +-- 2. 用户成就表 (tb_user_achievement) +-- ============================================= + +DROP TABLE IF EXISTS `tb_user_achievement`; + +CREATE TABLE `tb_user_achievement` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键ID', + `user_id` VARCHAR(32) NOT NULL COMMENT '用户ID', + `achievement_id` VARCHAR(32) NOT NULL COMMENT '成就ID', + `obtain_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '获得时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_achievement` (`user_id`, `achievement_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_achievement_id` (`achievement_id`), + KEY `idx_obtain_time` (`obtain_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户成就表'; + +-- ============================================= +-- 3. 用户成就进度表 (tb_user_achievement_progress) +-- ============================================= + +DROP TABLE IF EXISTS `tb_user_achievement_progress`; + +CREATE TABLE `tb_user_achievement_progress` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键ID', + `user_id` VARCHAR(32) NOT NULL COMMENT '用户ID', + `achievement_id` VARCHAR(32) NOT NULL COMMENT '成就ID', + `current_value` INT DEFAULT 0 COMMENT '当前进度值', + `target_value` INT DEFAULT 0 COMMENT '目标进度值', + `progress_percentage` INT DEFAULT 0 COMMENT '进度百分比(0-100)', + `completed` TINYINT(1) DEFAULT 0 COMMENT '是否已完成:0-否 1-是', + `last_update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_achievement_progress` (`user_id`, `achievement_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_achievement_id` (`achievement_id`), + KEY `idx_completed` (`completed`), + KEY `idx_last_update_time` (`last_update_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户成就进度表'; + +-- ============================================= +-- 初始化成就数据 +-- ============================================= + +-- 清空已有数据(可选) +-- DELETE FROM `tb_achievement`; + +-- ============================================= +-- 学习时长类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH001', 'learning_time_bronze', '初学者', '累计学习时长达到10小时', 1, 1, 1, 600, 10, 1, 0), +('ACH002', 'learning_time_silver', '勤学者', '累计学习时长达到50小时', 1, 2, 1, 3000, 50, 2, 0), +('ACH003', 'learning_time_gold', '学习达人', '累计学习时长达到100小时', 1, 3, 1, 6000, 100, 3, 0), +('ACH004', 'learning_time_diamond', '学习狂人', '累计学习时长达到500小时', 1, 4, 1, 30000, 500, 4, 0); + +-- ============================================= +-- 课程完成类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH005', 'course_complete_bronze', '课程新手', '完成1门课程', 1, 1, 3, 1, 10, 5, 0), +('ACH006', 'course_complete_silver', '课程能手', '完成5门课程', 1, 2, 3, 5, 50, 6, 0), +('ACH007', 'course_complete_gold', '课程专家', '完成20门课程', 1, 3, 3, 20, 200, 7, 0), +('ACH008', 'course_complete_diamond', '课程大师', '完成50门课程', 1, 4, 3, 50, 500, 8, 0); + +-- ============================================= +-- 资源浏览类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH009', 'resource_view_bronze', '好奇宝宝', '浏览10个资源', 1, 1, 2, 10, 5, 9, 0), +('ACH010', 'resource_view_silver', '探索者', '浏览50个资源', 1, 2, 2, 50, 25, 10, 0), +('ACH011', 'resource_view_gold', '资源猎人', '浏览200个资源', 1, 3, 2, 200, 100, 11, 0), +('ACH012', 'resource_view_diamond', '知识探险家', '浏览500个资源', 1, 4, 2, 500, 250, 12, 0); + +-- ============================================= +-- 连续登录类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH013', 'login_streak_bronze', '每日一练', '连续登录7天', 1, 1, 4, 7, 20, 13, 0), +('ACH014', 'login_streak_silver', '坚持不懈', '连续登录30天', 1, 2, 4, 30, 100, 14, 0), +('ACH015', 'login_streak_gold', '毅力之王', '连续登录100天', 1, 3, 4, 100, 500, 15, 0), +('ACH016', 'login_streak_diamond', '永不放弃', '连续登录365天', 1, 4, 4, 365, 2000, 16, 0); + +-- ============================================= +-- 任务完成类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH017', 'task_complete_bronze', '任务新兵', '完成5个任务', 1, 1, 6, 5, 15, 17, 0), +('ACH018', 'task_complete_silver', '任务达人', '完成20个任务', 1, 2, 6, 20, 60, 18, 0), +('ACH019', 'task_complete_gold', '任务专家', '完成50个任务', 1, 3, 6, 50, 150, 19, 0), +('ACH020', 'task_complete_diamond', '任务大师', '完成100个任务', 1, 4, 6, 100, 300, 20, 0); + +-- ============================================= +-- 收藏资源类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH021', 'collect_bronze', '收藏家', '收藏10个资源', 1, 1, 5, 10, 10, 21, 0), +('ACH022', 'collect_silver', '资料库', '收藏50个资源', 1, 2, 5, 50, 50, 22, 0), +('ACH023', 'collect_gold', '知识宝库', '收藏100个资源', 1, 3, 5, 100, 100, 23, 0), +('ACH024', 'collect_diamond', '珍宝收藏家', '收藏200个资源', 1, 4, 5, 200, 200, 24, 0); + +-- ============================================= +-- 章节完成类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH025', 'chapter_complete_bronze', '章节探索者', '完成10个章节', 1, 1, 9, 10, 20, 25, 0), +('ACH026', 'chapter_complete_silver', '章节达人', '完成50个章节', 1, 2, 9, 50, 100, 26, 0), +('ACH027', 'chapter_complete_gold', '章节专家', '完成100个章节', 1, 3, 9, 100, 200, 27, 0); + +-- ============================================= +-- 累计登录类成就 +-- ============================================= + +INSERT INTO `tb_achievement` (`id`, `achievement_id`, `name`, `description`, `type`, `level`, `condition_type`, `condition_value`, `points`, `order_num`, `deleted`) VALUES +('ACH028', 'total_login_bronze', '常客', '累计登录30天', 1, 1, 10, 30, 30, 28, 0), +('ACH029', 'total_login_silver', '老朋友', '累计登录100天', 1, 2, 10, 100, 100, 29, 0), +('ACH030', 'total_login_gold', '忠实用户', '累计登录365天', 1, 3, 10, 365, 500, 30, 0); + +-- ============================================= +-- 数据验证查询 +-- ============================================= + +-- 查询所有成就数量 +SELECT + '成就总数' AS '统计项', + COUNT(*) AS '数量' +FROM tb_achievement +WHERE deleted = 0 + +UNION ALL + +-- 按类型统计 +SELECT + CONCAT('类型', type, '成就') AS '统计项', + COUNT(*) AS '数量' +FROM tb_achievement +WHERE deleted = 0 +GROUP BY type + +UNION ALL + +-- 按等级统计 +SELECT + CASE level + WHEN 1 THEN '铜牌成就' + WHEN 2 THEN '银牌成就' + WHEN 3 THEN '金牌成就' + WHEN 4 THEN '钻石成就' + END AS '统计项', + COUNT(*) AS '数量' +FROM tb_achievement +WHERE deleted = 0 +GROUP BY level +ORDER BY '统计项'; + +-- ============================================= +-- 常用查询SQL示例 +-- ============================================= + +-- 1. 查询所有启用的成就(按排序号) +-- SELECT * FROM tb_achievement WHERE deleted = 0 ORDER BY order_num; + +-- 2. 查询某用户的所有成就 +-- SELECT +-- ua.id, +-- ua.user_id, +-- a.achievement_id, +-- a.name, +-- a.description, +-- a.icon, +-- a.type, +-- a.level, +-- a.points, +-- ua.obtain_time +-- FROM tb_user_achievement ua +-- INNER JOIN tb_achievement a ON ua.achievement_id = a.achievement_id +-- WHERE ua.user_id = 'USER_ID' AND a.deleted = 0 +-- ORDER BY ua.obtain_time DESC; + +-- 3. 查询某用户的成就进度 +-- SELECT +-- p.id, +-- p.user_id, +-- a.achievement_id, +-- a.name, +-- a.description, +-- p.current_value, +-- p.target_value, +-- p.progress_percentage, +-- p.completed, +-- p.last_update_time +-- FROM tb_user_achievement_progress p +-- INNER JOIN tb_achievement a ON p.achievement_id = a.achievement_id +-- WHERE p.user_id = 'USER_ID' AND a.deleted = 0 +-- ORDER BY p.progress_percentage DESC, p.last_update_time DESC; + +-- 4. 查询成就排行榜(按用户获得成就数量) +-- SELECT +-- ua.user_id, +-- u.username, +-- u.nickname, +-- COUNT(*) as achievement_count, +-- SUM(a.points) as total_points, +-- MAX(ua.obtain_time) as latest_obtain_time +-- 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 achievement_count DESC, total_points DESC, latest_obtain_time DESC +-- LIMIT 10; + +-- 5. 查询某成就的最近获得者 +-- SELECT +-- ua.id, +-- ua.user_id, +-- u.username, +-- u.nickname, +-- ua.obtain_time +-- FROM tb_user_achievement ua +-- INNER JOIN tb_sys_user u ON ua.user_id = u.id +-- WHERE ua.achievement_id = 'ACHIEVEMENT_ID' +-- ORDER BY ua.obtain_time DESC +-- LIMIT 10; + +-- 6. 查询某用户可以领取的成就(进度已达100%但未获得) +-- SELECT +-- p.user_id, +-- a.achievement_id, +-- a.name, +-- a.description, +-- a.points, +-- p.current_value, +-- p.target_value +-- FROM tb_user_achievement_progress p +-- INNER JOIN tb_achievement a ON p.achievement_id = a.achievement_id +-- LEFT JOIN tb_user_achievement ua ON p.user_id = ua.user_id AND p.achievement_id = ua.achievement_id +-- WHERE p.user_id = 'USER_ID' +-- AND p.completed = 1 +-- AND ua.id IS NULL +-- AND a.deleted = 0; + +-- ============================================= +-- 条件类型说明 +-- ============================================= +-- 1 - 学习时长(分钟) +-- 2 - 浏览资源数量 +-- 3 - 完成课程数量 +-- 4 - 连续登录天数 +-- 5 - 收藏资源数量 +-- 6 - 完成任务数量 +-- 7 - 获得积分数量 +-- 8 - 发表评论数量 +-- 9 - 完成章节数量 +-- 10 - 累计登录天数 + +-- ============================================= +-- 成就类型说明 +-- ============================================= +-- 1 - 勋章 +-- 2 - 等级 + +-- ============================================= +-- 成就等级说明 +-- ============================================= +-- 1 - 铜牌 +-- 2 - 银牌 +-- 3 - 金牌 +-- 4 - 钻石 + +-- ============================================= +-- 建表完成 +-- ============================================= +SELECT '成就系统表结构创建完成!' AS '状态', NOW() AS '时间'; + diff --git a/schoolNewsServ/achievement/README.md b/schoolNewsServ/achievement/README.md new file mode 100644 index 0000000..05bb2cc --- /dev/null +++ b/schoolNewsServ/achievement/README.md @@ -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 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 + diff --git a/schoolNewsServ/achievement/docs/MIGRATION.md b/schoolNewsServ/achievement/docs/MIGRATION.md new file mode 100644 index 0000000..e79f18a --- /dev/null +++ b/schoolNewsServ/achievement/docs/MIGRATION.md @@ -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 + + org.xyzh + api-usercenter + ${school-news.version} + +``` + +#### 新的依赖配置(需要添加): +```xml + + + org.xyzh + api-achievement + ${school-news.version} + + + + + org.xyzh + achievement + ${school-news.version} + +``` + +## 🔧 代码迁移步骤 + +### 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 result = userAchievementService.getUserAchievements(userID, type); +``` + +**新代码:** +```java +ResultDomain 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 result = + achievementService.getMyAchievementProgress(achievementID); +``` + +### 3. 策略模式检测器 +- 可扩展的成就检测器 +- 支持多种成就类型 +- 易于添加新的成就类型 + +### 4. 统计和排行榜 +```java +// 获取用户统计 +ResultDomain> stats = + achievementService.getUserAchievementStatistics(userID); + +// 获取排行榜 +ResultDomain> 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 + diff --git a/schoolNewsServ/achievement/pom.xml b/schoolNewsServ/achievement/pom.xml new file mode 100644 index 0000000..bbfc852 --- /dev/null +++ b/schoolNewsServ/achievement/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + org.xyzh + school-news + ${school-news.version} + + + org.xyzh + achievement + ${school-news.version} + jar + achievement + 成就模块 + + + 21 + 21 + + + + + org.xyzh + api-achievement + ${school-news.version} + + + org.xyzh + api-system + ${school-news.version} + + + org.xyzh + api-study + ${school-news.version} + + + + org.xyzh + common-all + ${school-news.version} + + + org.xyzh + system + ${school-news.version} + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + com.mysql + mysql-connector-j + + + + com.zaxxer + HikariCP + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.projectlombok + lombok + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/AbstractAchievementChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/AbstractAchievementChecker.java new file mode 100644 index 0000000..fbc1e36 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/AbstractAchievementChecker.java @@ -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; + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/AchievementChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/AchievementChecker.java new file mode 100644 index 0000000..969ae60 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/AchievementChecker.java @@ -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); +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ContinuousLoginChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ContinuousLoginChecker.java new file mode 100644 index 0000000..e0d169d --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ContinuousLoginChecker.java @@ -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; + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/CourseCompleteChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/CourseCompleteChecker.java new file mode 100644 index 0000000..d9c6673 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/CourseCompleteChecker.java @@ -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 + }; + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/LearningTimeChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/LearningTimeChecker.java new file mode 100644 index 0000000..5a0ce35 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/LearningTimeChecker.java @@ -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 + }; + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ResourceCollectChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ResourceCollectChecker.java new file mode 100644 index 0000000..08d5ede --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ResourceCollectChecker.java @@ -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 + }; + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ResourceViewChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ResourceViewChecker.java new file mode 100644 index 0000000..e6a1ba2 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/ResourceViewChecker.java @@ -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 + }; + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/TaskCompleteChecker.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/TaskCompleteChecker.java new file mode 100644 index 0000000..b8dd671 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/checker/impl/TaskCompleteChecker.java @@ -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 + }; + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/controller/AchievementController.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/controller/AchievementController.java new file mode 100644 index 0000000..3896215 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/controller/AchievementController.java @@ -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 createAchievement(@RequestBody TbAchievement achievement) { + return achievementService.createAchievement(achievement); + } + + /** + * 更新成就 + */ + @PutMapping("/update") + public ResultDomain updateAchievement(@RequestBody TbAchievement achievement) { + return achievementService.updateAchievement(achievement); + } + + /** + * 删除成就 + */ + @DeleteMapping("/delete/{achievementID}") + public ResultDomain deleteAchievement(@PathVariable String achievementID) { + return achievementService.deleteAchievement(achievementID); + } + + /** + * 获取所有成就列表 + */ + @GetMapping("/list") + public ResultDomain getAllAchievements( + @RequestParam(required = false) Integer type, + @RequestParam(required = false) Integer level) { + return achievementService.getAllAchievements(type, level); + } + + /** + * 分页查询成就 + */ + @PostMapping("/page") + public ResultDomain getAchievementPage( + @RequestBody(required = false) TbAchievement filter, + PageParam pageParam) { + return achievementService.getAchievementPage(filter, pageParam); + } + + /** + * 获取成就详情 + */ + @GetMapping("/detail/{achievementID}") + public ResultDomain getAchievementDetail(@PathVariable String achievementID) { + return achievementService.getAchievementDetail(achievementID); + } + + // ==================== 用户成就查询 ==================== + + /** + * 获取用户已获得的成就 + */ + @GetMapping("/user/{userID}") + public ResultDomain getUserAchievements( + @PathVariable String userID, + @RequestParam(required = false) Integer type) { + return achievementService.getUserAchievements(userID, type); + } + + /** + * 获取当前用户的成就列表 + */ + @GetMapping("/my") + public ResultDomain getMyAchievements( + @RequestParam(required = false) Integer type) { + return achievementService.getMyAchievements(type); + } + + /** + * 检查用户是否已获得成就 + */ + @GetMapping("/check/{userID}/{achievementID}") + public ResultDomain hasAchievement( + @PathVariable String userID, + @PathVariable String achievementID) { + return achievementService.hasAchievement(userID, achievementID); + } + + // ==================== 成就授予(管理员)==================== + + /** + * 手动授予用户成就 + */ + @PostMapping("/grant") + public ResultDomain grantAchievement( + @RequestParam String userID, + @RequestParam String achievementID) { + return achievementService.grantAchievement(userID, achievementID); + } + + /** + * 撤销用户成就 + */ + @DeleteMapping("/revoke") + public ResultDomain revokeAchievement( + @RequestParam String userID, + @RequestParam String achievementID) { + return achievementService.revokeAchievement(userID, achievementID); + } + + // ==================== 成就进度查询 ==================== + + /** + * 获取用户成就进度 + */ + @GetMapping("/progress/{userID}") + public ResultDomain getUserAchievementProgress( + @PathVariable String userID, + @RequestParam(required = false) String achievementID) { + return achievementService.getUserAchievementProgress(userID, achievementID); + } + + /** + * 获取当前用户的成就进度 + */ + @GetMapping("/progress/my") + public ResultDomain getMyAchievementProgress( + @RequestParam(required = false) String achievementID) { + return achievementService.getMyAchievementProgress(achievementID); + } + + // ==================== 成就检测 ==================== + + /** + * 处理成就事件(内部接口,由其他服务调用) + */ + @PostMapping("/event/process") + public ResultDomain> processAchievementEvent(@RequestBody AchievementEvent event) { + return achievementService.processAchievementEvent(event); + } + + /** + * 检查用户是否满足成就条件 + */ + @GetMapping("/condition/check/{userID}/{achievementID}") + public ResultDomain checkAchievementCondition( + @PathVariable String userID, + @PathVariable String achievementID) { + return achievementService.checkAchievementCondition(userID, achievementID); + } + + /** + * 批量检查用户可获得的成就 + */ + @GetMapping("/available/{userID}") + public ResultDomain> checkAvailableAchievements(@PathVariable String userID) { + return achievementService.checkAvailableAchievements(userID); + } + + // ==================== 成就统计 ==================== + + /** + * 获取用户成就统计 + */ + @GetMapping("/statistics/{userID}") + public ResultDomain> getUserAchievementStatistics(@PathVariable String userID) { + return achievementService.getUserAchievementStatistics(userID); + } + + /** + * 获取成就排行榜 + */ + @GetMapping("/ranking") + public ResultDomain> getAchievementRanking( + @RequestParam(required = false, defaultValue = "10") Integer limit) { + return achievementService.getAchievementRanking(limit); + } + + /** + * 获取最近获得成就的用户 + */ + @GetMapping("/recent/{achievementID}") + public ResultDomain> getRecentAchievers( + @PathVariable String achievementID, + @RequestParam(required = false, defaultValue = "10") Integer limit) { + return achievementService.getRecentAchievers(achievementID, limit); + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/listener/AchievementEventListener.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/listener/AchievementEventListener.java new file mode 100644 index 0000000..6cd50a5 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/listener/AchievementEventListener.java @@ -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> 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 achievements) { + // TODO: 实现通知逻辑 + // 1. 发送系统消息 + // 2. 发送推送通知 + // 3. 记录通知日志 + logger.info("通知用户 {} 获得成就,数量: {}", userID, achievements.size()); + } +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/AchievementMapper.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/AchievementMapper.java new file mode 100644 index 0000000..3b6d76f --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/AchievementMapper.java @@ -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 { + + /** + * @description 查询成就列表 + * @param filter 过滤条件 + * @return List 成就列表 + */ + List selectAchievements(@Param("filter") TbAchievement filter); + + /** + * @description 根据成就ID查询成就信息 + * @param achievementId 成就ID + * @return TbAchievement 成就信息 + */ + TbAchievement selectByAchievementId(@Param("achievementId") String achievementId); + + /** + * @description 根据类型查询成就列表 + * @param type 成就类型 + * @return List 成就列表 + */ + List selectByType(@Param("type") Integer type); + + /** + * @description 根据类型和等级查询成就列表 + * @param type 成就类型 + * @param level 成就等级 + * @return List 成就列表 + */ + List selectByTypeAndLevel(@Param("type") Integer type, @Param("level") Integer level); + + /** + * @description 根据条件类型查询成就列表 + * @param conditionType 条件类型 + * @return List 成就列表 + */ + List selectByConditionType(@Param("conditionType") Integer conditionType); + + /** + * @description 查询成就排行榜 + * @param limit 限制数量 + * @return List 成就排行榜 + */ + List 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 achievementList); + + /** + * @description 批量删除成就 + * @param ids 成就ID列表 + * @return int 影响行数 + */ + int batchDeleteAchievements(@Param("ids") List ids); + + /** + * @description 分页查询成就 + * @param filter 过滤条件 + * @param pageParam 分页参数 + * @return List 成就列表 + */ + List selectAchievementsPage(@Param("filter") TbAchievement filter, @Param("pageParam") PageParam pageParam); + + /** + * @description 统计成就总数 + * @param filter 过滤条件 + * @return long 总数 + */ + long countAchievements(@Param("filter") TbAchievement filter); +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/UserAchievementMapper.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/UserAchievementMapper.java new file mode 100644 index 0000000..d4905c4 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/UserAchievementMapper.java @@ -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 { + + /** + * @description 查询用户成就列表 + * @param filter 过滤条件 + * @return List 用户成就列表 + */ + List selectUserAchievements(@Param("filter") TbUserAchievement filter); + + /** + * @description 根据用户ID查询成就记录 + * @param userId 用户ID + * @return List 成就记录列表 + */ + List 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 成就记录列表 + */ + List selectByUserIdAndType(@Param("userId") String userId, @Param("type") Integer type); + + /** + * @description 查询用户成就统计 + * @param userId 用户ID + * @return Map 统计信息 + */ + Map selectAchievementStatistics(@Param("userId") String userId); + + /** + * @description 查询成就的最近获得者 + * @param achievementId 成就ID + * @param limit 查询数量 + * @return List 用户成就列表 + */ + List selectRecentAchievers(@Param("achievementId") String achievementId, @Param("limit") Integer limit); + + /** + * @description 查询成就排行榜(按用户获得成就数量) + * @param limit 排行榜条数 + * @return List> 排行榜数据 + */ + List> 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 userAchievementList); + + /** + * @description 分页查询用户成就 + * @param filter 过滤条件 + * @param pageParam 分页参数 + * @return List 用户成就列表 + */ + List selectUserAchievementsPage(@Param("filter") TbUserAchievement filter, @Param("pageParam") PageParam pageParam); + + /** + * @description 统计用户成就总数 + * @param filter 过滤条件 + * @return long 总数 + */ + long countUserAchievements(@Param("filter") TbUserAchievement filter); +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/UserAchievementProgressMapper.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/UserAchievementProgressMapper.java new file mode 100644 index 0000000..8715db1 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/mapper/UserAchievementProgressMapper.java @@ -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 { + + /** + * @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 成就进度列表 + */ + List selectByUserId(@Param("userId") String userId); + + /** + * @description 根据用户ID和条件类型查询进度 + * @param userId 用户ID + * @param conditionType 条件类型 + * @return List 成就进度列表 + */ + List selectByUserIdAndConditionType(@Param("userId") String userId, @Param("conditionType") Integer conditionType); + + /** + * @description 查询未完成的进度 + * @param userId 用户ID + * @return List 成就进度列表 + */ + List 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 progressList); + + /** + * @description 删除进度记录 + * @param userId 用户ID + * @param achievementId 成就ID + * @return int 影响行数 + */ + int deleteProgress(@Param("userId") String userId, @Param("achievementId") String achievementId); +} + diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java new file mode 100644 index 0000000..f2f3fef --- /dev/null +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java @@ -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 checkers; + + // ==================== 成就定义管理 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public ResultDomain createAchievement(TbAchievement achievement) { + ResultDomain 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 updateAchievement(TbAchievement achievement) { + ResultDomain 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 deleteAchievement(String achievementID) { + ResultDomain 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 getAllAchievements(Integer type, Integer level) { + ResultDomain resultDomain = new ResultDomain<>(); + try { + List 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 getAchievementPage(TbAchievement filter, PageParam pageParam) { + ResultDomain resultDomain = new ResultDomain<>(); + try { + if (filter == null) { + filter = new TbAchievement(); + filter.setDeleted(false); + } + + List 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 getAchievementDetail(String achievementID) { + ResultDomain 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 getUserAchievements(String userID, Integer type) { + ResultDomain resultDomain = new ResultDomain<>(); + try { + if (!StringUtils.hasText(userID)) { + resultDomain.fail("用户ID不能为空"); + return resultDomain; + } + + List 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 getMyAchievements(Integer type) { + TbSysUser user = LoginUtil.getCurrentUser(); + if (user == null) { + ResultDomain resultDomain = new ResultDomain<>(); + resultDomain.fail("请先登录"); + return resultDomain; + } + return getUserAchievements(user.getID(), type); + } + + @Override + public ResultDomain hasAchievement(String userID, String achievementID) { + ResultDomain 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 grantAchievement(String userID, String achievementID) { + ResultDomain 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 revokeAchievement(String userID, String achievementID) { + ResultDomain 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 getUserAchievementProgress(String userID, String achievementID) { + ResultDomain resultDomain = new ResultDomain<>(); + try { + if (!StringUtils.hasText(userID)) { + resultDomain.fail("用户ID不能为空"); + return resultDomain; + } + + List 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 getMyAchievementProgress(String achievementID) { + TbSysUser user = LoginUtil.getCurrentUser(); + if (user == null) { + ResultDomain resultDomain = new ResultDomain<>(); + resultDomain.fail("请先登录"); + return resultDomain; + } + return getUserAchievementProgress(user.getID(), achievementID); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ResultDomain updateProgress(String userID, String achievementID, Integer incrementValue) { + ResultDomain 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> processAchievementEvent(AchievementEvent event) { + ResultDomain> resultDomain = new ResultDomain<>(); + List newAchievements = new ArrayList<>(); + + try { + if (event == null || !StringUtils.hasText(event.getUserID()) || event.getEventType() == null) { + resultDomain.fail("事件参数不完整"); + return resultDomain; + } + + logger.debug("处理成就事件: {}", event); + + // 获取该事件类型相关的所有成就 + List 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 checkAchievementCondition(String userID, String achievementID) { + ResultDomain 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> checkAvailableAchievements(String userID) { + ResultDomain> resultDomain = new ResultDomain<>(); + try { + if (!StringUtils.hasText(userID)) { + resultDomain.fail("用户ID不能为空"); + return resultDomain; + } + + // 获取所有成就 + TbAchievement filter = new TbAchievement(); + filter.setDeleted(false); + List allAchievements = achievementMapper.selectAchievements(filter); + + // 获取用户进度 + List progressList = progressMapper.selectIncompletedByUserId(userID); + Map progressMap = progressList.stream() + .collect(Collectors.toMap( + TbUserAchievementProgress::getAchievementID, + TbUserAchievementProgress::getCurrentValue + )); + + // 筛选可获得的成就 + List 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> getUserAchievementStatistics(String userID) { + ResultDomain> resultDomain = new ResultDomain<>(); + try { + if (!StringUtils.hasText(userID)) { + resultDomain.fail("用户ID不能为空"); + return resultDomain; + } + + Map 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> getAchievementRanking(Integer limit) { + ResultDomain> resultDomain = new ResultDomain<>(); + try { + if (limit == null || limit <= 0) { + limit = 10; // 默认10条 + } + + List> ranking = userAchievementMapper.selectUserAchievementRanking(limit); + + Map 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> getRecentAchievers(String achievementID, Integer limit) { + ResultDomain> resultDomain = new ResultDomain<>(); + try { + if (!StringUtils.hasText(achievementID)) { + resultDomain.fail("成就ID不能为空"); + return resultDomain; + } + + if (limit == null || limit <= 0) { + limit = 10; + } + + List 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 getAchievementsByEventType(AchievementEventType eventType) { + // 获取所有成就 + TbAchievement filter = new TbAchievement(); + filter.setDeleted(false); + List 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; + } +} + diff --git a/schoolNewsServ/achievement/src/main/resources/mapper/AchievementMapper.xml b/schoolNewsServ/achievement/src/main/resources/mapper/AchievementMapper.xml new file mode 100644 index 0000000..9d8fcd2 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/resources/mapper/AchievementMapper.xml @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, achievement_id, name, description, icon, type, level, condition_type, + condition_value, points, order_num, creator, updater, create_time, update_time, + delete_time, deleted + + + + + + + + AND id = #{filter.ID} + + + AND achievement_id = #{filter.achievementID} + + + AND name LIKE CONCAT('%', #{filter.name}, '%') + + + AND type = #{filter.type} + + + AND level = #{filter.level} + + + AND condition_type = #{filter.conditionType} + + + AND points = #{filter.points} + + + AND creator = #{filter.creator} + + + AND deleted = #{filter.deleted} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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} + ) + + + + + UPDATE tb_achievement + + + achievement_id = #{achievementID}, + + + name = #{name}, + + + description = #{description}, + + + icon = #{icon}, + + + type = #{type}, + + + level = #{level}, + + + condition_type = #{conditionType}, + + + condition_value = #{conditionValue}, + + + points = #{points}, + + + order_num = #{orderNum}, + + + updater = #{updater}, + + + update_time = #{updateTime}, + + + delete_time = #{deleteTime}, + + + deleted = #{deleted}, + + + WHERE id = #{ID} + + + + + DELETE FROM tb_achievement + WHERE id = #{ID} + + + + + 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 + + ( + #{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} + ) + + + + + + DELETE FROM tb_achievement + WHERE id IN + + #{id} + + + + + + + + + + diff --git a/schoolNewsServ/achievement/src/main/resources/mapper/UserAchievementMapper.xml b/schoolNewsServ/achievement/src/main/resources/mapper/UserAchievementMapper.xml new file mode 100644 index 0000000..a63fcb4 --- /dev/null +++ b/schoolNewsServ/achievement/src/main/resources/mapper/UserAchievementMapper.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + id, user_id, achievement_id, obtain_time + + + + + + + + AND ua.id = #{filter.ID} + + + AND ua.user_id = #{filter.userID} + + + AND ua.achievement_id = #{filter.achievementID} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO tb_user_achievement ( + id, user_id, achievement_id, obtain_time + ) VALUES ( + #{ID}, #{userID}, #{achievementID}, #{obtainTime} + ) + + + + + UPDATE tb_user_achievement + + + user_id = #{userID}, + + + achievement_id = #{achievementID}, + + + obtain_time = #{obtainTime}, + + + WHERE id = #{ID} + + + + + DELETE FROM tb_user_achievement + WHERE id = #{id} + + + + + DELETE FROM tb_user_achievement + WHERE user_id = #{userId} AND achievement_id = #{achievementId} + + + + + INSERT INTO tb_user_achievement ( + id, user_id, achievement_id, obtain_time + ) VALUES + + ( + #{item.ID}, #{item.userID}, #{item.achievementID}, #{item.obtainTime} + ) + + + + + + + + + + diff --git a/schoolNewsServ/achievement/src/main/resources/mapper/UserAchievementProgressMapper.xml b/schoolNewsServ/achievement/src/main/resources/mapper/UserAchievementProgressMapper.xml new file mode 100644 index 0000000..21dcbae --- /dev/null +++ b/schoolNewsServ/achievement/src/main/resources/mapper/UserAchievementProgressMapper.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + id, user_id, achievement_id, current_value, target_value, progress_percentage, + completed, last_update_time, create_time + + + + + + + + + + + + + + + + + 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} + ) + + + + + UPDATE tb_user_achievement_progress + + + current_value = #{currentValue}, + + + target_value = #{targetValue}, + + + progress_percentage = #{progressPercentage}, + + + completed = #{completed}, + + + last_update_time = #{lastUpdateTime}, + + + WHERE id = #{ID} + + + + + 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} + + + + + INSERT INTO tb_user_achievement_progress ( + id, user_id, achievement_id, current_value, target_value, + progress_percentage, completed, last_update_time, create_time + ) VALUES + + ( + #{item.ID}, #{item.userID}, #{item.achievementID}, #{item.currentValue}, + #{item.targetValue}, #{item.progressPercentage}, #{item.completed}, + #{item.lastUpdateTime}, #{item.createTime} + ) + + + + + + DELETE FROM tb_user_achievement_progress + WHERE user_id = #{userId} AND achievement_id = #{achievementId} + + +