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,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>