JDWA 技术文档
首页
  • 数据库
  • 前端开发
  • 后端开发
  • 开发工具
  • 虚拟化技术
  • KVM显卡直通
  • FPGA仿真固件
  • 项目实战
  • 踩坑记录
  • 开发心得
  • 软件工具
  • 学习资料
  • 开发环境
更新日志
关于我
Gitee
GitHub
首页
  • 数据库
  • 前端开发
  • 后端开发
  • 开发工具
  • 虚拟化技术
  • KVM显卡直通
  • FPGA仿真固件
  • 项目实战
  • 踩坑记录
  • 开发心得
  • 软件工具
  • 学习资料
  • 开发环境
更新日志
关于我
Gitee
GitHub
  • Green-U低碳生活平台(后端)

    • JDWA Green-U 后端技术文档索引
    • JDWA Green-U 项目架构概述
    • JDWA Green-U 数据库设计文档
    • JDWA Green-U 用户认证模块技术文档
    • JDWA Green-U 用户活动模块技术文档
    • JDWA Green-U 碳减排统计模块技术文档
    • JDWA Green-U 积分系统模块技术文档
    • JDWA Green-U 成就系统模块技术文档
    • JDWA Green-U 奖品兑换模块技术文档
    • JDWA Green-U API接口文档
    • JDWA Green-U 系统安全机制与防护措施
    • JDWA Green-U 部署与运维指南

JDWA Green-U 成就系统模块

本文档详细介绍了JDWA Green-U项目中成就系统模块的设计思路、业务规则和核心实现。该模块负责用户成就的定义、解锁条件设置、进度跟踪和展示,是激励用户持续参与绿色低碳活动的重要机制。

  • 模块概述
    • 业务价值
    • 功能范围
  • 核心功能设计
    • 成就类型定义
    • 成就等级设计
    • 成就解锁条件
    • 成就奖励机制
    • 成就进度跟踪
    • 成就解锁流程
  • 数据模型设计
    • 核心表结构
    • 核心实体类
  • 核心类与接口
    • 控制器 (JDWAAchievementController)
    • 成就服务接口 (JDWAAchievementService)
    • 成就服务实现 (JDWAAchievementServiceImpl)
    • 成就检查引擎 (JDWAAchievementCheckEngine)
  • API接口规范
    • 获取用户成就列表
    • 获取成就详情
    • 刷新用户成就状态
    • 领取成就奖励
    • 获取用户成就概览
    • 获取用户已解锁徽章列表
    • 设置展示徽章
    • 获取成就排行榜
  • 系统集成
    • 与其他模块的集成
    • 事件驱动集成机制
    • 用户成就解锁通知
    • 成就回顾与总结功能
  • 性能优化
    • 成就检查优化
    • 实现示例:异步检查队列
    • 缓存策略
    • 数据库优化
  • 常见问题解决
  • 未来优化计划
    • 短期优化
    • 长期规划
  • 参考资源
  • 总结

模块概述

成就系统是JDWA Green-U平台的重要激励机制之一,通过将用户的绿色行为转化为可视化的成就徽章和荣誉,满足用户的成长感和成就感。成就系统与积分系统相辅相成,共同构成平台的用户激励体系,引导用户持续参与环保活动。

业务价值

  • 激发用户的收集欲和完成欲,增强用户粘性
  • 引导用户尝试多种环保行为,扩大平台使用场景
  • 提供长期目标,保持用户活跃度
  • 增强环保行为的社交属性,促进用户分享
  • 建立用户环保成就感,形成正向反馈

功能范围

  • 成就类型与等级定义
  • 成就解锁条件设置
  • 成就进度跟踪与计算
  • 成就解锁与奖励发放
  • 成就展示与分享
  • 成就统计与排行

核心功能设计

成就类型定义

系统支持以下几种成就类型:

成就类型代码标识说明示例
累计型成就ACCUMULATIVE基于用户某项数据的累计值累计步行10000步、累计减碳100kg
计数型成就COUNTING基于用户完成特定行为的次数连续签到7天、完成20次骑行
里程碑成就MILESTONE用户达到特定状态或条件完善个人资料、绑定第三方账号
组合型成就COMBINATION需要同时满足多个条件一周内完成3种不同环保活动
社交型成就SOCIAL与用户社交行为相关邀请10位好友、分享5次环保成果

成就等级设计

每种成就类型下,可以设置多个等级的成就,形成成就链:

  1. 初级成就:容易达成,新用户快速获得成就感
  2. 中级成就:需要持续努力,中度用户的目标
  3. 高级成就:难度较大,资深用户的挑战
  4. 终极成就:极具挑战性,平台"专家级"用户的象征

例如,"碳减排达人"可以有以下等级:

  • 碳减排新手(累计减碳10kg)
  • 碳减排能手(累计减碳100kg)
  • 碳减排专家(累计减碳500kg)
  • 碳减排大师(累计减碳1000kg)

成就解锁条件

系统支持灵活的条件设置机制:

  1. 数值型条件:

    • 单一数值比较:如"累计步行距离 >= 10km"
    • 范围比较:如"单次骑行时间在30-60分钟之间"
    • 多条件组合:如"单日行走距离 >= 5km 且 步数 >= 10000"
  2. 时间型条件:

    • 特定时间段完成:如"早上6-8点完成户外运动"
    • 连续型条件:如"连续7天完成打卡"
    • 累计时间条件:如"累计骑行时间达到24小时"
  3. 复合型条件:

    • 与逻辑:同时满足多个条件
    • 或逻辑:满足条件集合中的任意一个
    • 计数逻辑:满足N个条件中的M个

成就奖励机制

成就解锁后可获得的奖励类型:

  1. 积分奖励:解锁成就获得平台积分
  2. 徽章奖励:获得独特的成就徽章,展示在个人主页
  3. 头像框:特殊成就可解锁专属头像框
  4. 称号奖励:获得与成就相关的用户称号
  5. 特权奖励:解锁特定功能或权限

成就进度跟踪

系统实时追踪用户的各项数据,更新成就完成进度:

  1. 实时进度:用户可随时查看各项成就的完成进度
  2. 进度展示:以进度条、百分比等形式直观展示
  3. 距离目标:显示"还差XX即可达成"的提示
  4. 快要完成提醒:成就接近完成时进行提醒

成就解锁流程

  1. 触发检查:

    • 定时检查:系统定时任务检查所有用户的成就状态
    • 事件触发:用户完成特定行为后立即检查相关成就
    • 手动触发:用户手动刷新成就列表
  2. 解锁条件验证:

    • 读取用户当前数据状态
    • 与成就条件进行比对
    • 确认是否满足解锁条件
  3. 成就解锁:

    • 更新用户成就记录
    • 发放成就奖励
    • 推送成就解锁通知
  4. 后续激励:

    • 展示下一级成就目标
    • 推荐相关成就挑战

数据模型设计

核心表结构

成就系统模块主要涉及以下数据表:

  • jdwa_achievement:成就定义表
  • jdwa_achievement_condition:成就条件表
  • jdwa_user_achievement:用户成就记录表
  • jdwa_achievement_reward:成就奖励表

详细表结构如下:

CREATE TABLE `jdwa_achievement` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '成就ID',
  `achievement_code` varchar(50) NOT NULL COMMENT '成就编码',
  `achievement_name` varchar(100) NOT NULL COMMENT '成就名称',
  `achievement_type` varchar(20) NOT NULL COMMENT '成就类型:ACCUMULATIVE/COUNTING/MILESTONE/COMBINATION/SOCIAL',
  `achievement_level` int(11) NOT NULL DEFAULT '1' COMMENT '成就等级:1-初级,2-中级,3-高级,4-终极',
  `parent_code` varchar(50) DEFAULT NULL COMMENT '父成就编码',
  `description` varchar(500) DEFAULT NULL COMMENT '成就描述',
  `badge_image` varchar(255) DEFAULT NULL COMMENT '徽章图片',
  `achievement_icon` varchar(255) DEFAULT NULL COMMENT '成就图标',
  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `sort_order` int(11) DEFAULT '0' COMMENT '排序顺序',
  `display_type` tinyint(4) DEFAULT '1' COMMENT '展示类型:0-隐藏,1-普通,2-突出显示',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_achievement_code` (`achievement_code`),
  KEY `idx_parent_code` (`parent_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成就定义表';

CREATE TABLE `jdwa_achievement_condition` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '条件ID',
  `achievement_id` bigint(20) NOT NULL COMMENT '成就ID',
  `achievement_code` varchar(50) NOT NULL COMMENT '成就编码',
  `condition_type` varchar(20) NOT NULL COMMENT '条件类型',
  `condition_key` varchar(50) NOT NULL COMMENT '条件键',
  `operator` varchar(20) NOT NULL COMMENT '操作符:EQ/GT/LT/GTE/LTE/BETWEEN/IN',
  `value_type` varchar(20) DEFAULT 'NUMBER' COMMENT '值类型:NUMBER/STRING/DATE/BOOLEAN',
  `target_value` varchar(500) NOT NULL COMMENT '目标值',
  `condition_logic` varchar(10) DEFAULT 'AND' COMMENT '条件逻辑:AND/OR',
  `condition_order` int(11) DEFAULT '0' COMMENT '条件顺序',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_achievement_id` (`achievement_id`),
  KEY `idx_achievement_code` (`achievement_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成就条件表';

CREATE TABLE `jdwa_user_achievement` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `achievement_id` bigint(20) NOT NULL COMMENT '成就ID',
  `achievement_code` varchar(50) NOT NULL COMMENT '成就编码',
  `current_value` varchar(50) DEFAULT NULL COMMENT '当前值',
  `target_value` varchar(50) DEFAULT NULL COMMENT '目标值',
  `progress` int(11) DEFAULT '0' COMMENT '进度百分比(0-100)',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-进行中,1-已完成,2-已领取奖励',
  `unlock_time` datetime DEFAULT NULL COMMENT '解锁时间',
  `reward_time` datetime DEFAULT NULL COMMENT '奖励领取时间',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_achievement` (`user_id`,`achievement_code`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_achievement_id` (`achievement_id`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户成就记录表';

CREATE TABLE `jdwa_achievement_reward` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '奖励ID',
  `achievement_id` bigint(20) NOT NULL COMMENT '成就ID',
  `achievement_code` varchar(50) NOT NULL COMMENT '成就编码',
  `reward_type` varchar(20) NOT NULL COMMENT '奖励类型:POINTS/BADGE/AVATAR_FRAME/TITLE/PRIVILEGE',
  `reward_key` varchar(50) DEFAULT NULL COMMENT '奖励键',
  `reward_value` varchar(255) DEFAULT NULL COMMENT '奖励值',
  `description` varchar(500) DEFAULT NULL COMMENT '奖励描述',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_achievement_id` (`achievement_id`),
  KEY `idx_achievement_code` (`achievement_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成就奖励表';

核心实体类

成就定义实体 (JDWAAchievement)

@Data
@TableName("jdwa_achievement")
public class JDWAAchievement {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String achievementCode;
    private String achievementName;
    private String achievementType;
    private Integer achievementLevel;
    private String parentCode;
    private String description;
    private String badgeImage;
    private String achievementIcon;
    private Integer status;
    private Integer sortOrder;
    private Integer displayType;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    
    @TableField(exist = false)
    private List<JDWAAchievementCondition> conditions;
    
    @TableField(exist = false)
    private List<JDWAAchievementReward> rewards;
}

成就条件实体 (JDWAAchievementCondition)

@Data
@TableName("jdwa_achievement_condition")
public class JDWAAchievementCondition {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long achievementId;
    private String achievementCode;
    private String conditionType;
    private String conditionKey;
    private String operator;
    private String valueType;
    private String targetValue;
    private String conditionLogic;
    private Integer conditionOrder;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

用户成就实体 (JDWAUserAchievement)

@Data
@TableName("jdwa_user_achievement")
public class JDWAUserAchievement {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long userId;
    private Long achievementId;
    private String achievementCode;
    private String currentValue;
    private String targetValue;
    private Integer progress;
    private Integer status;
    private LocalDateTime unlockTime;
    private LocalDateTime rewardTime;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    
    @TableField(exist = false)
    private JDWAAchievement achievement;
}

成就奖励实体 (JDWAAchievementReward)

@Data
@TableName("jdwa_achievement_reward")
public class JDWAAchievementReward {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long achievementId;
    private String achievementCode;
    private String rewardType;
    private String rewardKey;
    private String rewardValue;
    private String description;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

常量定义

public class JDWAAchievementConstants {
    // 成就类型
    public static final String ACHIEVEMENT_TYPE_ACCUMULATIVE = "ACCUMULATIVE";  // 累计型
    public static final String ACHIEVEMENT_TYPE_COUNTING = "COUNTING";  // 计数型
    public static final String ACHIEVEMENT_TYPE_MILESTONE = "MILESTONE";  // 里程碑型
    public static final String ACHIEVEMENT_TYPE_COMBINATION = "COMBINATION";  // 组合型
    public static final String ACHIEVEMENT_TYPE_SOCIAL = "SOCIAL";  // 社交型
    
    // 成就状态
    public static final int ACHIEVEMENT_STATUS_IN_PROGRESS = 0;  // 进行中
    public static final int ACHIEVEMENT_STATUS_COMPLETED = 1;  // 已完成
    public static final int ACHIEVEMENT_STATUS_REWARDED = 2;  // 已领取奖励
    
    // 奖励类型
    public static final String REWARD_TYPE_POINTS = "POINTS";  // 积分奖励
    public static final String REWARD_TYPE_BADGE = "BADGE";  // 徽章奖励
    public static final String REWARD_TYPE_AVATAR_FRAME = "AVATAR_FRAME";  // 头像框
    public static final String REWARD_TYPE_TITLE = "TITLE";  // 称号
    public static final String REWARD_TYPE_PRIVILEGE = "PRIVILEGE";  // 特权
    
    // 操作符
    public static final String OPERATOR_EQ = "EQ";  // 等于
    public static final String OPERATOR_GT = "GT";  // 大于
    public static final String OPERATOR_LT = "LT";  // 小于
    public static final String OPERATOR_GTE = "GTE";  // 大于等于
    public static final String OPERATOR_LTE = "LTE";  // 小于等于
    public static final String OPERATOR_BETWEEN = "BETWEEN";  // 区间
    public static final String OPERATOR_IN = "IN";  // 包含于
} 

核心类与接口

控制器 (JDWAAchievementController)

@RestController
@RequestMapping("/api/achievements")
public class JDWAAchievementController extends JDWABaseController {

    @Autowired
    private JDWAAchievementService achievementService;
    
    /**
     * 获取用户成就列表
     */
    @GetMapping("/list")
    public JDWAResult<Object> getUserAchievements(
            @RequestParam(required = false) String achievementType,
            @RequestParam(required = false) Integer status) {
        
        Long userId = getCurrentUserId();
        List<Map<String, Object>> achievements = achievementService.getUserAchievements(userId, achievementType, status);
        
        return JDWAResult.success("获取成功", achievements);
    }
    
    /**
     * 获取成就详情
     */
    @GetMapping("/detail/{achievementCode}")
    public JDWAResult<Object> getAchievementDetail(@PathVariable String achievementCode) {
        Long userId = getCurrentUserId();
        Map<String, Object> detail = achievementService.getAchievementDetail(userId, achievementCode);
        
        return JDWAResult.success("获取成功", detail);
    }
    
    /**
     * 刷新用户成就状态
     */
    @PostMapping("/refresh")
    public JDWAResult<Object> refreshAchievements() {
        Long userId = getCurrentUserId();
        List<String> unlockedAchievements = achievementService.refreshUserAchievements(userId);
        
        Map<String, Object> result = new HashMap<>();
        result.put("unlockedCount", unlockedAchievements.size());
        result.put("unlockedAchievements", unlockedAchievements);
        
        return JDWAResult.success("刷新成功", result);
    }
    
    /**
     * 领取成就奖励
     */
    @PostMapping("/reward/{achievementCode}")
    public JDWAResult<Object> getAchievementReward(@PathVariable String achievementCode) {
        Long userId = getCurrentUserId();
        Map<String, Object> rewards = achievementService.getAchievementReward(userId, achievementCode);
        
        return JDWAResult.success("奖励领取成功", rewards);
    }
    
    /**
     * 获取用户成就概览
     */
    @GetMapping("/overview")
    public JDWAResult<Object> getAchievementOverview() {
        Long userId = getCurrentUserId();
        Map<String, Object> overview = achievementService.getAchievementOverview(userId);
        
        return JDWAResult.success("获取成功", overview);
    }
    
    /**
     * 获取用户已解锁徽章列表
     */
    @GetMapping("/badges")
    public JDWAResult<Object> getUserBadges(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        
        Long userId = getCurrentUserId();
        Page<Map<String, Object>> badges = achievementService.getUserBadges(userId, pageNum, pageSize);
        
        return JDWAResult.success("获取成功", badges);
    }
    
    /**
     * 设置展示徽章
     */
    @PostMapping("/badge/display")
    public JDWAResult<Object> setDisplayBadge(@RequestBody Map<String, Object> params) {
        Long userId = getCurrentUserId();
        String achievementCode = (String) params.get("achievementCode");
        Boolean display = (Boolean) params.get("display");
        
        achievementService.setDisplayBadge(userId, achievementCode, display);
        
        return JDWAResult.success("设置成功", null);
    }
    
    /**
     * 获取成就排行榜
     */
    @GetMapping("/leaderboard")
    public JDWAResult<Object> getAchievementLeaderboard(
            @RequestParam(defaultValue = "total") String type,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        
        Page<Map<String, Object>> leaderboard = achievementService.getAchievementLeaderboard(type, pageNum, pageSize);
        
        return JDWAResult.success("获取成功", leaderboard);
    }
}

成就服务接口 (JDWAAchievementService)

public interface JDWAAchievementService {
    
    /**
     * 获取用户成就列表
     *
     * @param userId 用户ID
     * @param achievementType 成就类型(可选)
     * @param status 成就状态(可选)
     * @return 成就列表
     */
    List<Map<String, Object>> getUserAchievements(Long userId, String achievementType, Integer status);
    
    /**
     * 获取成就详情
     *
     * @param userId 用户ID
     * @param achievementCode 成就编码
     * @return 成就详情
     */
    Map<String, Object> getAchievementDetail(Long userId, String achievementCode);
    
    /**
     * 刷新用户成就状态
     * 
     * @param userId 用户ID
     * @return 新解锁的成就列表
     */
    List<String> refreshUserAchievements(Long userId);
    
    /**
     * 检查用户成就完成状态
     * 
     * @param userId 用户ID
     * @param achievementCode 成就编码
     * @return 是否完成
     */
    boolean checkAchievementCompletion(Long userId, String achievementCode);
    
    /**
     * 更新用户成就进度
     * 
     * @param userId 用户ID
     * @param conditionKey 条件键
     * @param value 当前值
     */
    void updateAchievementProgress(Long userId, String conditionKey, Object value);
    
    /**
     * 领取成就奖励
     * 
     * @param userId 用户ID
     * @param achievementCode 成就编码
     * @return 奖励信息
     */
    Map<String, Object> getAchievementReward(Long userId, String achievementCode);
    
    /**
     * 获取用户成就概览
     * 
     * @param userId 用户ID
     * @return 成就概览
     */
    Map<String, Object> getAchievementOverview(Long userId);
    
    /**
     * 获取用户已解锁徽章列表
     * 
     * @param userId 用户ID
     * @param pageNum 页码
     * @param pageSize 每页大小
     * @return 徽章列表
     */
    Page<Map<String, Object>> getUserBadges(Long userId, Integer pageNum, Integer pageSize);
    
    /**
     * 设置展示徽章
     * 
     * @param userId 用户ID
     * @param achievementCode 成就编码
     * @param display 是否展示
     */
    void setDisplayBadge(Long userId, String achievementCode, Boolean display);
    
    /**
     * 获取成就排行榜
     * 
     * @param type 排行类型(total-总成就数,monthly-本月解锁数)
     * @param pageNum 页码
     * @param pageSize 每页大小
     * @return 排行榜
     */
    Page<Map<String, Object>> getAchievementLeaderboard(String type, Integer pageNum, Integer pageSize);
    
    /**
     * 根据事件触发成就检查
     * 
     * @param userId 用户ID
     * @param eventType 事件类型
     * @param eventData 事件数据
     * @return 新解锁的成就列表
     */
    List<String> checkAchievementsByEvent(Long userId, String eventType, Map<String, Object> eventData);
}

成就服务实现 (JDWAAchievementServiceImpl)

由于代码较长,以下展示几个核心方法的实现:

@Slf4j
@Service
public class JDWAAchievementServiceImpl implements JDWAAchievementService {

    @Autowired
    private JDWAAchievementMapper achievementMapper;
    
    @Autowired
    private JDWAAchievementConditionMapper conditionMapper;
    
    @Autowired
    private JDWAUserAchievementMapper userAchievementMapper;
    
    @Autowired
    private JDWAAchievementRewardMapper rewardMapper;
    
    @Autowired
    private JDWAPointsService pointsService;
    
    @Autowired
    private JDWAUserService userService;
    
    @Override
    public List<Map<String, Object>> getUserAchievements(Long userId, String achievementType, Integer status) {
        // 查询所有有效成就
        List<JDWAAchievement> achievements = getActiveAchievements(achievementType);
        
        // 查询用户已有的成就记录
        Map<String, JDWAUserAchievement> userAchievements = getUserAchievementMap(userId);
        
        // 组装结果
        List<Map<String, Object>> result = new ArrayList<>();
        
        for (JDWAAchievement achievement : achievements) {
            // 如果指定了状态过滤,则跳过不符合的记录
            if (status != null) {
                JDWAUserAchievement userAchievement = userAchievements.get(achievement.getAchievementCode());
                if (userAchievement == null && status > 0) {
                    continue;  // 用户没有此成就记录,但要求已完成或已领取,跳过
                }
                if (userAchievement != null && userAchievement.getStatus() != status) {
                    continue;  // 状态不匹配,跳过
                }
            }
            
            Map<String, Object> map = new HashMap<>();
            map.put("id", achievement.getId());
            map.put("achievementCode", achievement.getAchievementCode());
            map.put("achievementName", achievement.getAchievementName());
            map.put("achievementType", achievement.getAchievementType());
            map.put("achievementLevel", achievement.getAchievementLevel());
            map.put("description", achievement.getDescription());
            map.put("badgeImage", achievement.getBadgeImage());
            map.put("achievementIcon", achievement.getAchievementIcon());
            
            // 设置成就状态信息
            JDWAUserAchievement userAchievement = userAchievements.get(achievement.getAchievementCode());
            if (userAchievement != null) {
                map.put("status", userAchievement.getStatus());
                map.put("progress", userAchievement.getProgress());
                map.put("currentValue", userAchievement.getCurrentValue());
                map.put("targetValue", userAchievement.getTargetValue());
                map.put("unlockTime", userAchievement.getUnlockTime());
            } else {
                map.put("status", JDWAAchievementConstants.ACHIEVEMENT_STATUS_IN_PROGRESS);
                map.put("progress", 0);
                map.put("currentValue", "0");
                
                // 获取目标值
                String targetValue = getAchievementTargetValue(achievement.getId());
                map.put("targetValue", targetValue);
                map.put("unlockTime", null);
            }
            
            result.add(map);
        }
        
        return result;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<String> refreshUserAchievements(Long userId) {
        log.info("刷新用户成就状态,用户ID: {}", userId);
        
        // 获取所有活跃的成就
        List<JDWAAchievement> achievements = getActiveAchievements(null);
        
        // 用户当前的成就状态
        Map<String, JDWAUserAchievement> userAchievements = getUserAchievementMap(userId);
        
        // 新解锁的成就列表
        List<String> unlockedAchievements = new ArrayList<>();
        
        // 检查每个成就
        for (JDWAAchievement achievement : achievements) {
            String achievementCode = achievement.getAchievementCode();
            
            // 跳过已完成的成就
            JDWAUserAchievement userAchievement = userAchievements.get(achievementCode);
            if (userAchievement != null && userAchievement.getStatus() > 0) {
                continue;
            }
            
            // 检查成就完成状态
            boolean completed = checkAchievementCompletionInternal(userId, achievement);
            
            if (completed) {
                // 成就完成,更新状态
                if (userAchievement == null) {
                    // 创建新的用户成就记录
                    userAchievement = new JDWAUserAchievement();
                    userAchievement.setUserId(userId);
                    userAchievement.setAchievementId(achievement.getId());
                    userAchievement.setAchievementCode(achievementCode);
                    userAchievement.setStatus(JDWAAchievementConstants.ACHIEVEMENT_STATUS_COMPLETED);
                    userAchievement.setUnlockTime(LocalDateTime.now());
                    userAchievement.setProgress(100);
                    userAchievement.setCurrentValue(getAchievementTargetValue(achievement.getId()));
                    userAchievement.setTargetValue(getAchievementTargetValue(achievement.getId()));
                    userAchievement.setCreateTime(LocalDateTime.now());
                    userAchievement.setUpdateTime(LocalDateTime.now());
                    
                    userAchievementMapper.insert(userAchievement);
                } else {
                    // 更新现有记录
                    userAchievement.setStatus(JDWAAchievementConstants.ACHIEVEMENT_STATUS_COMPLETED);
                    userAchievement.setUnlockTime(LocalDateTime.now());
                    userAchievement.setProgress(100);
                    userAchievement.setCurrentValue(getAchievementTargetValue(achievement.getId()));
                    userAchievement.setUpdateTime(LocalDateTime.now());
                    
                    userAchievementMapper.updateById(userAchievement);
                }
                
                unlockedAchievements.add(achievementCode);
                log.info("用户{}解锁成就:{}", userId, achievementCode);
            }
        }
        
        return unlockedAchievements;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> getAchievementReward(Long userId, String achievementCode) {
        // 获取用户成就记录
        JDWAUserAchievement userAchievement = getUserAchievement(userId, achievementCode);
        
        if (userAchievement == null) {
            throw new JDWAServiceException("未找到相关成就记录");
        }
        
        // 检查成就状态
        if (userAchievement.getStatus() != JDWAAchievementConstants.ACHIEVEMENT_STATUS_COMPLETED) {
            if (userAchievement.getStatus() < JDWAAchievementConstants.ACHIEVEMENT_STATUS_COMPLETED) {
                throw new JDWAServiceException("成就尚未完成,无法领取奖励");
            } else {
                throw new JDWAServiceException("奖励已领取,请勿重复操作");
            }
        }
        
        // 获取成就奖励
        List<JDWAAchievementReward> rewards = getAchievementRewards(userAchievement.getAchievementId());
        
        if (rewards.isEmpty()) {
            throw new JDWAServiceException("该成就暂无奖励");
        }
        
        // 处理各类奖励
        Map<String, Object> result = new HashMap<>();
        for (JDWAAchievementReward reward : rewards) {
            processReward(userId, reward, result);
        }
        
        // 更新用户成就状态为已领取奖励
        userAchievement.setStatus(JDWAAchievementConstants.ACHIEVEMENT_STATUS_REWARDED);
        userAchievement.setRewardTime(LocalDateTime.now());
        userAchievement.setUpdateTime(LocalDateTime.now());
        userAchievementMapper.updateById(userAchievement);
        
        return result;
    }
    
    /**
     * 处理各类奖励发放
     */
    private void processReward(Long userId, JDWAAchievementReward reward, Map<String, Object> result) {
        String rewardType = reward.getRewardType();
        String rewardKey = reward.getRewardKey();
        String rewardValue = reward.getRewardValue();
        
        switch (rewardType) {
            case JDWAAchievementConstants.REWARD_TYPE_POINTS:
                // 积分奖励
                int points = Integer.parseInt(rewardValue);
                pointsService.addPoints(userId, points, rewardKey, "ACHIEVEMENT_REWARD", 
                        reward.getAchievementCode(), "成就奖励:" + reward.getDescription(), null);
                
                // 记录到结果
                result.put("points", points);
                result.put("pointsType", rewardKey);
                break;
                
            case JDWAAchievementConstants.REWARD_TYPE_BADGE:
                // 徽章奖励,记录已在用户成就表中
                result.put("badge", reward.getDescription());
                result.put("badgeImage", rewardValue);
                break;
                
            case JDWAAchievementConstants.REWARD_TYPE_AVATAR_FRAME:
                // 头像框奖励
                userService.addUserAvatarFrame(userId, rewardValue);
                result.put("avatarFrame", reward.getDescription());
                result.put("avatarFrameImage", rewardValue);
                break;
                
            case JDWAAchievementConstants.REWARD_TYPE_TITLE:
                // 称号奖励
                userService.addUserTitle(userId, rewardValue);
                result.put("title", reward.getDescription());
                break;
                
            case JDWAAchievementConstants.REWARD_TYPE_PRIVILEGE:
                // 特权奖励
                userService.addUserPrivilege(userId, rewardKey);
                result.put("privilege", reward.getDescription());
                break;
                
            default:
                log.warn("未知奖励类型:{}", rewardType);
        }
    }
    
    /**
     * 根据事件更新相关成就进度
     */
    @Override
    public List<String> checkAchievementsByEvent(Long userId, String eventType, Map<String, Object> eventData) {
        log.info("检查事件触发的成就,用户ID: {}, 事件类型: {}", userId, eventType);
        
        // 根据事件类型查找相关的条件键
        List<String> conditionKeys = getConditionKeysByEventType(eventType);
        
        if (conditionKeys.isEmpty()) {
            return Collections.emptyList();
        }
        
        // 更新相关成就的进度
        for (String conditionKey : conditionKeys) {
            Object value = eventData.get(conditionKey);
            if (value != null) {
                updateAchievementProgress(userId, conditionKey, value);
            }
        }
        
        // 检查成就是否完成
        return refreshUserAchievements(userId);
    }
    
    /**
     * 获取事件相关的条件键
     */
    private List<String> getConditionKeysByEventType(String eventType) {
        List<String> keys = new ArrayList<>();
        
        // 根据事件类型映射到相关的条件键
        switch (eventType) {
            case "USER_ACTIVITY":
                keys.add("total_activity_count");
                keys.add("total_walking_distance");
                keys.add("total_cycling_distance");
                keys.add("total_carbon_reduction");
                break;
                
            case "USER_CHECK_IN":
                keys.add("check_in_count");
                keys.add("consecutive_check_in");
                break;
                
            case "USER_INVITE":
                keys.add("invite_user_count");
                break;
                
            case "USER_SHARE":
                keys.add("share_count");
                break;
                
            // 其他事件类型...
        }
        
        return keys;
    }
}

成就检查引擎 (JDWAAchievementCheckEngine)

成就条件检查的核心组件,负责解析和验证各类成就条件:

@Component
public class JDWAAchievementCheckEngine {
    
    @Autowired
    private JDWAUserService userService;
    
    @Autowired
    private JDWAActivityService activityService;
    
    @Autowired
    private JDWACarbonReductionService carbonReductionService;
    
    /**
     * 检查成就条件是否满足
     *
     * @param userId 用户ID
     * @param conditions 条件列表
     * @return 是否满足所有条件
     */
    public boolean checkConditions(Long userId, List<JDWAAchievementCondition> conditions) {
        if (conditions == null || conditions.isEmpty()) {
            return false;
        }
        
        boolean isAnd = true;  // 默认为AND逻辑
        
        // 检查是否有OR逻辑
        for (JDWAAchievementCondition condition : conditions) {
            if ("OR".equals(condition.getConditionLogic())) {
                isAnd = false;
                break;
            }
        }
        
        if (isAnd) {
            // AND逻辑:所有条件必须满足
            return conditions.stream().allMatch(condition -> checkSingleCondition(userId, condition));
        } else {
            // OR逻辑:任一条件满足即可
            return conditions.stream().anyMatch(condition -> checkSingleCondition(userId, condition));
        }
    }
    
    /**
     * 检查单个条件
     *
     * @param userId 用户ID
     * @param condition 条件
     * @return 是否满足
     */
    private boolean checkSingleCondition(Long userId, JDWAAchievementCondition condition) {
        String conditionKey = condition.getConditionKey();
        String operator = condition.getOperator();
        String targetValue = condition.getTargetValue();
        
        // 获取当前值
        Object currentValue = getCurrentValue(userId, conditionKey);
        if (currentValue == null) {
            return false;
        }
        
        // 根据值类型和操作符比较
        return compareValues(currentValue, targetValue, operator, condition.getValueType());
    }
    
    /**
     * 获取条件对应的当前值
     */
    private Object getCurrentValue(Long userId, String conditionKey) {
        // 根据条件键获取相应的用户数据
        switch (conditionKey) {
            case "total_activity_count":
                return activityService.getUserActivityCount(userId);
                
            case "total_walking_distance":
                return activityService.getUserTotalDistance(userId, "WALKING");
                
            case "total_cycling_distance":
                return activityService.getUserTotalDistance(userId, "CYCLING");
                
            case "total_carbon_reduction":
                return carbonReductionService.getUserTotalCarbonReduction(userId);
                
            case "check_in_count":
                return userService.getUserCheckInCount(userId);
                
            case "consecutive_check_in":
                return userService.getUserConsecutiveCheckIn(userId);
                
            case "invite_user_count":
                return userService.getUserInviteCount(userId);
                
            case "share_count":
                return userService.getUserShareCount(userId);
                
            // 其他条件键...
                
            default:
                return null;
        }
    }
    
    /**
     * 比较值
     */
    private boolean compareValues(Object currentValue, String targetValue, String operator, String valueType) {
        // 根据值类型转换
        if ("NUMBER".equals(valueType)) {
            return compareNumberValues(currentValue, targetValue, operator);
        } else if ("DATE".equals(valueType)) {
            return compareDateValues(currentValue, targetValue, operator);
        } else if ("STRING".equals(valueType)) {
            return compareStringValues(currentValue, targetValue, operator);
        } else if ("BOOLEAN".equals(valueType)) {
            return compareBooleanValues(currentValue, targetValue, operator);
        }
        
        return false;
    }
    
    /**
     * 比较数值
     */
    private boolean compareNumberValues(Object currentValue, String targetValue, String operator) {
        try {
            BigDecimal current = new BigDecimal(String.valueOf(currentValue));
            
            if (JDWAAchievementConstants.OPERATOR_BETWEEN.equals(operator)) {
                // 区间比较
                String[] range = targetValue.split(",");
                BigDecimal min = new BigDecimal(range[0]);
                BigDecimal max = new BigDecimal(range[1]);
                return current.compareTo(min) >= 0 && current.compareTo(max) <= 0;
            } else if (JDWAAchievementConstants.OPERATOR_IN.equals(operator)) {
                // IN比较
                String[] values = targetValue.split(",");
                for (String value : values) {
                    if (current.compareTo(new BigDecimal(value)) == 0) {
                        return true;
                    }
                }
                return false;
            } else {
                // 单值比较
                BigDecimal target = new BigDecimal(targetValue);
                
                switch (operator) {
                    case JDWAAchievementConstants.OPERATOR_EQ:
                        return current.compareTo(target) == 0;
                    case JDWAAchievementConstants.OPERATOR_GT:
                        return current.compareTo(target) > 0;
                    case JDWAAchievementConstants.OPERATOR_LT:
                        return current.compareTo(target) < 0;
                    case JDWAAchievementConstants.OPERATOR_GTE:
                        return current.compareTo(target) >= 0;
                    case JDWAAchievementConstants.OPERATOR_LTE:
                        return current.compareTo(target) <= 0;
                    default:
                        return false;
                }
            }
        } catch (Exception e) {
            return false;
        }
    }
    
    // 其他比较方法省略...
}

API接口规范

获取用户成就列表

  • 接口URL: /api/achievements/list

  • 请求方式: GET

  • 接口说明: 获取用户成就列表,支持按类型和状态筛选

  • 请求参数:

    • achievementType: 成就类型(可选)
    • status: 成就状态(可选,0-进行中,1-已完成,2-已领取奖励)
  • 响应结果:

{
  "code": 200,
  "message": "获取成功",
  "data": [
    {
      "id": 1,
      "achievementCode": "CARBON_REDUCTION_BEGINNER",
      "achievementName": "碳减排新手",
      "achievementType": "ACCUMULATIVE",
      "achievementLevel": 1,
      "description": "累计减碳10kg,为地球环保贡献第一步",
      "badgeImage": "/images/badge/carbon_beginner.png",
      "achievementIcon": "/images/achievement/carbon_beginner.png",
      "status": 1,
      "progress": 100,
      "currentValue": "15.5",
      "targetValue": "10",
      "unlockTime": "2025-05-15T14:30:25"
    },
    {
      "id": 2,
      "achievementCode": "CARBON_REDUCTION_ADVANCED",
      "achievementName": "碳减排能手",
      "achievementType": "ACCUMULATIVE",
      "achievementLevel": 2,
      "description": "累计减碳100kg,你的环保之路越走越远",
      "badgeImage": "/images/badge/carbon_advanced.png",
      "achievementIcon": "/images/achievement/carbon_advanced.png",
      "status": 0,
      "progress": 15,
      "currentValue": "15.5",
      "targetValue": "100",
      "unlockTime": null
    },
    // 更多成就...
  ]
}

获取成就详情

  • 接口URL: /api/achievements/detail/{achievementCode}

  • 请求方式: GET

  • 接口说明: 获取单个成就的详细信息

  • 请求参数:

    • achievementCode: 成就编码(路径参数)
  • 响应结果:

{
  "code": 200,
  "message": "获取成功",
  "data": {
    "id": 1,
    "achievementCode": "CARBON_REDUCTION_BEGINNER",
    "achievementName": "碳减排新手",
    "achievementType": "ACCUMULATIVE",
    "achievementLevel": 1,
    "description": "累计减碳10kg,为地球环保贡献第一步",
    "badgeImage": "/images/badge/carbon_beginner.png",
    "achievementIcon": "/images/achievement/carbon_beginner.png",
    "status": 1,
    "progress": 100,
    "currentValue": "15.5",
    "targetValue": "10",
    "unlockTime": "2025-05-15T14:30:25",
    "conditions": [
      {
        "conditionKey": "total_carbon_reduction",
        "operator": "GTE",
        "targetValue": "10"
      }
    ],
    "rewards": [
      {
        "rewardType": "POINTS",
        "rewardKey": "GREEN_POINTS",
        "rewardValue": "50",
        "description": "50绿色积分"
      },
      {
        "rewardType": "BADGE",
        "rewardValue": "/images/badge/carbon_beginner.png",
        "description": "碳减排新手徽章"
      }
    ],
    "nextLevel": {
      "achievementCode": "CARBON_REDUCTION_ADVANCED",
      "achievementName": "碳减排能手",
      "targetValue": "100",
      "progress": 15
    }
  }
}

刷新用户成就状态

  • 接口URL: /api/achievements/refresh

  • 请求方式: POST

  • 接口说明: 手动触发检查用户成就的完成状态

  • 请求参数: 无

  • 响应结果:

{
  "code": 200,
  "message": "刷新成功",
  "data": {
    "unlockedCount": 2,
    "unlockedAchievements": [
      "WALKING_BEGINNER",
      "CHECK_IN_7DAYS"
    ]
  }
}

领取成就奖励

  • 接口URL: /api/achievements/reward/{achievementCode}

  • 请求方式: POST

  • 接口说明: 领取已完成成就的奖励

  • 请求参数:

    • achievementCode: 成就编码(路径参数)
  • 响应结果:

{
  "code": 200,
  "message": "奖励领取成功",
  "data": {
    "points": 50,
    "pointsType": "GREEN_POINTS",
    "badge": "碳减排新手徽章",
    "badgeImage": "/images/badge/carbon_beginner.png"
  }
}

获取用户成就概览

  • 接口URL: /api/achievements/overview

  • 请求方式: GET

  • 接口说明: 获取用户成就完成数据统计

  • 请求参数: 无

  • 响应结果:

{
  "code": 200,
  "message": "获取成功",
  "data": {
    "totalAchievementCount": 28,
    "completedCount": 7,
    "completionRate": 25,
    "latestUnlocked": [
      {
        "achievementCode": "WALKING_BEGINNER",
        "achievementName": "步行初体验",
        "badgeImage": "/images/badge/walking_beginner.png",
        "unlockTime": "2025-05-18T10:25:12"
      },
      {
        "achievementCode": "CHECK_IN_7DAYS",
        "achievementName": "签到达人",
        "badgeImage": "/images/badge/check_in_7days.png",
        "unlockTime": "2025-05-16T08:05:33"
      }
    ],
    "nearCompletion": [
      {
        "achievementCode": "CARBON_REDUCTION_ADVANCED",
        "achievementName": "碳减排能手",
        "progress": 85,
        "currentValue": "85.2",
        "targetValue": "100"
      }
    ],
    "typeDistribution": {
      "ACCUMULATIVE": {
        "total": 12,
        "completed": 3
      },
      "COUNTING": {
        "total": 8,
        "completed": 2
      },
      "MILESTONE": {
        "total": 5,
        "completed": 1
      },
      "COMBINATION": {
        "total": 2,
        "completed": 0
      },
      "SOCIAL": {
        "total": 1,
        "completed": 1
      }
    }
  }
}

获取用户已解锁徽章列表

  • 接口URL: /api/achievements/badges

  • 请求方式: GET

  • 接口说明: 获取用户已解锁的成就徽章

  • 请求参数:

    • pageNum: 页码(默认1)
    • pageSize: 每页大小(默认10)
  • 响应结果:

{
  "code": 200,
  "message": "获取成功",
  "data": {
    "records": [
      {
        "achievementCode": "CARBON_REDUCTION_BEGINNER",
        "achievementName": "碳减排新手",
        "badgeImage": "/images/badge/carbon_beginner.png",
        "unlockTime": "2025-05-15T14:30:25",
        "isDisplayed": true
      },
      {
        "achievementCode": "WALKING_BEGINNER",
        "achievementName": "步行初体验",
        "badgeImage": "/images/badge/walking_beginner.png",
        "unlockTime": "2025-05-18T10:25:12",
        "isDisplayed": false
      },
      // 更多徽章...
    ],
    "total": 7,
    "size": 10,
    "current": 1,
    "pages": 1
  }
}

设置展示徽章

  • 接口URL: /api/achievements/badge/display
  • 请求方式: POST
  • 接口说明: 设置是否在个人资料中展示指定徽章
  • 请求参数:
{
  "achievementCode": "CARBON_REDUCTION_BEGINNER",
  "display": true
}
  • 响应结果:
{
  "code": 200,
  "message": "设置成功",
  "data": null
}

获取成就排行榜

  • 接口URL: /api/achievements/leaderboard

  • 请求方式: GET

  • 接口说明: 获取用户成就完成数量排行榜

  • 请求参数:

    • type: 排行类型(默认total,可选值:total-总成就数,monthly-本月解锁数)
    • pageNum: 页码(默认1)
    • pageSize: 每页大小(默认10)
  • 响应结果:

{
  "code": 200,
  "message": "获取成功",
  "data": {
    "records": [
      {
        "userId": 1001,
        "nickname": "环保达人",
        "avatar": "/images/avatar/1001.jpg",
        "achievementCount": 18,
        "ranking": 1,
        "displayBadges": [
          "/images/badge/carbon_master.png",
          "/images/badge/walking_expert.png"
        ]
      },
      {
        "userId": 1002,
        "nickname": "绿色先锋",
        "avatar": "/images/avatar/1002.jpg",
        "achievementCount": 15,
        "ranking": 2,
        "displayBadges": [
          "/images/badge/cycling_master.png"
        ]
      },
      // 更多排名...
    ],
    "total": 50,
    "size": 10,
    "current": 1,
    "pages": 5
  }
}

系统集成

与其他模块的集成

成就系统是一个横向的激励层,与系统多个模块有密切的集成关系:

  1. 用户活动模块:

    • 用户完成绿色出行活动后,触发相关成就检查
    • 提供活动数据用于累计型成就的进度计算
    • 活动里程碑达成时自动解锁对应成就
  2. 碳减排统计模块:

    • 获取碳减排统计数据用于环保成就的解锁判断
    • 碳减排达到特定里程碑时触发成就检查
  3. 积分系统:

    • 成就解锁时发放积分奖励
    • 积分里程碑达成时触发相关成就
  4. 用户管理模块:

    • 成就和徽章展示在用户个人主页
    • 特殊成就解锁用户称号和头像框
  5. 社交系统:

    • 成就解锁可选择性分享到社交动态
    • 邀请好友、社交互动产生相关成就

事件驱动集成机制

成就系统采用事件驱动的方式与其他模块集成,避免直接耦合:

/**
 * 事件发布示例 - 用户活动完成
 */
@Service
public class JDWAActivityServiceImpl implements JDWAActivityService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public JDWAActivity createActivity(Long userId, JDWAActivityRequest request) {
        // 创建活动记录...
        JDWAActivity activity = new JDWAActivity();
        activity.setUserId(userId);
        activity.setActivityType(request.getActivityType());
        activity.setDistance(request.getDistance());
        activity.setDuration(request.getDuration());
        
        // 计算碳减排量
        BigDecimal carbonReduction = calculateCarbonReduction(
                request.getActivityType(), request.getDistance());
        activity.setCarbonReduction(carbonReduction);
        
        // 保存活动记录
        save(activity);
        
        // 发布用户活动完成事件,触发成就检查
        Map<String, Object> eventData = new HashMap<>();
        eventData.put("activityId", activity.getId());
        eventData.put("activityType", activity.getActivityType());
        eventData.put("distance", activity.getDistance());
        eventData.put("duration", activity.getDuration());
        eventData.put("carbonReduction", activity.getCarbonReduction());
        eventData.put("total_activity_count", getUserActivityCount(userId));
        eventData.put("total_walking_distance", 
                "WALKING".equals(activity.getActivityType()) ? 
                getUserTotalDistance(userId, "WALKING") : null);
        eventData.put("total_cycling_distance", 
                "CYCLING".equals(activity.getActivityType()) ? 
                getUserTotalDistance(userId, "CYCLING") : null);
        eventData.put("total_carbon_reduction", getUserTotalCarbonReduction(userId));
        
        eventPublisher.publishEvent(new JDWAUserEvent(this, userId, "USER_ACTIVITY", eventData));
        
        return activity;
    }
}

/**
 * 事件监听器 - 成就检查
 */
@Component
@Slf4j
public class JDWAAchievementEventListener {
    
    @Autowired
    private JDWAAchievementService achievementService;
    
    @EventListener
    public void handleUserEvent(JDWAUserEvent event) {
        try {
            Long userId = event.getUserId();
            String eventType = event.getEventType();
            Map<String, Object> eventData = event.getEventData();
            
            // 根据事件类型触发成就检查
            List<String> unlockedAchievements = 
                    achievementService.checkAchievementsByEvent(userId, eventType, eventData);
            
            if (!unlockedAchievements.isEmpty()) {
                log.info("用户 {} 通过事件 {} 解锁了 {} 个成就", 
                        userId, eventType, unlockedAchievements.size());
            }
        } catch (Exception e) {
            // 成就处理发生异常不应影响主业务流程
            log.error("处理成就事件时发生异常", e);
        }
    }
}

用户成就解锁通知

当用户解锁新成就时,系统通过消息通知功能及时通知用户:

/**
 * 成就解锁通知发送
 */
@Component
public class JDWAAchievementNotificationSender {
    
    @Autowired
    private JDWANotificationService notificationService;
    
    @Autowired
    private JDWAAchievementMapper achievementMapper;
    
    /**
     * 发送成就解锁通知
     */
    public void sendAchievementUnlockNotification(Long userId, List<String> achievementCodes) {
        if (achievementCodes == null || achievementCodes.isEmpty()) {
            return;
        }
        
        for (String achievementCode : achievementCodes) {
            // 查询成就信息
            JDWAAchievement achievement = achievementMapper.selectByCode(achievementCode);
            if (achievement == null) {
                continue;
            }
            
            // 构建通知内容
            Map<String, Object> content = new HashMap<>();
            content.put("achievementName", achievement.getAchievementName());
            content.put("achievementCode", achievement.getAchievementCode());
            content.put("achievementIcon", achievement.getAchievementIcon());
            content.put("description", achievement.getDescription());
            
            // 发送通知
            notificationService.sendNotification(
                    userId, 
                    "ACHIEVEMENT_UNLOCK", 
                    "恭喜解锁新成就:" + achievement.getAchievementName(), 
                    content);
        }
    }
}

成就回顾与总结功能

系统提供周期性的成就回顾功能,增强用户的成就感:

/**
 * 定期成就回顾服务
 */
@Service
public class JDWAAchievementReviewService {
    
    @Autowired
    private JDWAUserAchievementMapper userAchievementMapper;
    
    @Autowired
    private JDWANotificationService notificationService;
    
    /**
     * 生成月度成就回顾
     */
    @Scheduled(cron = "0 0 8 1 * ?")  // 每月1号早上8点执行
    public void generateMonthlyAchievementReview() {
        // 获取上个月的日期范围
        LocalDate now = LocalDate.now();
        LocalDate firstDayOfLastMonth = now.minusMonths(1).withDayOfMonth(1);
        LocalDate lastDayOfLastMonth = now.withDayOfMonth(1).minusDays(1);
        
        // 查询活跃用户
        List<Long> activeUserIds = getActiveUsers(firstDayOfLastMonth, lastDayOfLastMonth);
        
        for (Long userId : activeUserIds) {
            try {
                // 查询用户上月解锁的成就
                List<JDWAUserAchievement> monthlyAchievements = 
                        userAchievementMapper.selectMonthlyUnlockedAchievements(
                                userId, 
                                firstDayOfLastMonth.atStartOfDay(), 
                                lastDayOfLastMonth.atTime(23, 59, 59));
                
                if (monthlyAchievements.isEmpty()) {
                    continue;
                }
                
                // 生成回顾内容
                Map<String, Object> content = generateReviewContent(userId, monthlyAchievements);
                
                // 发送成就回顾通知
                notificationService.sendNotification(
                        userId, 
                        "ACHIEVEMENT_REVIEW", 
                        "您上个月解锁了" + monthlyAchievements.size() + "个成就,点击查看回顾", 
                        content);
                
            } catch (Exception e) {
                // 单个用户处理异常不影响其他用户
                logger.error("为用户{}生成成就回顾时发生异常", userId, e);
            }
        }
    }
}

性能优化

成就检查优化

成就检查是一个计算密集型操作,为避免影响主业务流程和用户体验,系统采取以下优化策略:

  1. 异步检查:

    • 所有成就检查操作均采用异步方式执行
    • 用户活动保存成功后立即返回,成就检查在后台进行
    • 解锁成就后通过消息通知用户
  2. 批量处理:

    • 定时任务批量检查需要更新的成就进度
    • 将检查任务分批执行,避免一次处理过多数据
  3. 增量检查:

    • 只检查与当前事件相关的成就类型
    • 跳过已完成的成就检查
    • 对于组合型成就,先快速检查必要条件是否满足
  4. 合理设置触发条件:

    • 累计型成就设置合理的检查间隔
    • 里程碑型成就只在可能达成时检查

实现示例:异步检查队列

/**
 * 成就检查队列服务
 */
@Service
public class JDWAAchievementCheckQueueService {
    
    @Autowired
    private JDWAAchievementService achievementService;
    
    private ThreadPoolExecutor executor;
    
    @PostConstruct
    public void init() {
        // 创建线程池,核心线程数3,最大线程数5,使用有界队列避免OOM
        executor = new ThreadPoolExecutor(
                3, 
                5, 
                60, 
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000),
                new ThreadFactoryBuilder().setNameFormat("achievement-check-pool-%d").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }
    
    /**
     * 提交成就检查任务
     */
    public void submitCheckTask(Long userId, String eventType, Map<String, Object> eventData) {
        executor.execute(() -> {
            try {
                achievementService.checkAchievementsByEvent(userId, eventType, eventData);
            } catch (Exception e) {
                log.error("异步检查成就发生异常, userId: {}, eventType: {}", userId, eventType, e);
            }
        });
    }
    
    /**
     * 批量提交成就检查任务
     */
    public void submitBatchCheckTasks(List<AchievementCheckTask> tasks) {
        if (tasks == null || tasks.isEmpty()) {
            return;
        }
        
        // 批量提交任务
        for (AchievementCheckTask task : tasks) {
            submitCheckTask(task.getUserId(), task.getEventType(), task.getEventData());
        }
    }
    
    @PreDestroy
    public void shutdown() {
        if (executor != null) {
            executor.shutdown();
            try {
                if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                executor.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }
}

缓存策略

系统采用多层缓存策略提高访问性能:

  1. 成就定义缓存:

    • 成就定义及条件配置较少变动,适合长时间缓存
    • 使用本地缓存 + Redis 二级缓存
    • 配置修改时主动刷新缓存
  2. 用户成就进度缓存:

    • 用户当前值和目标值缓存,减少数据库查询
    • 使用带有短暂过期时间的Redis缓存
    • 写穿透策略确保数据一致性
  3. 热点数据缓存:

    • 成就排行榜使用定时更新的Redis缓存
    • 用户成就概览数据缓存,设置短期过期时间
/**
 * 成就缓存管理
 */
@Component
public class JDWAAchievementCacheManager {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 本地缓存 - 所有成就定义
    private LoadingCache<String, List<JDWAAchievement>> achievementCache;
    
    @PostConstruct
    public void init() {
        // 初始化本地缓存
        achievementCache = Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build(key -> loadAllAchievements());
    }
    
    /**
     * 获取所有成就定义
     */
    public List<JDWAAchievement> getAllAchievements() {
        return achievementCache.get("ALL");
    }
    
    /**
     * 获取用户成就进度
     */
    public Map<String, Object> getUserAchievementProgress(Long userId, String achievementCode) {
        String cacheKey = String.format("achievement:progress:%s:%s", userId, achievementCode);
        
        // 先查Redis缓存
        Map<String, Object> progress = (Map<String, Object>) redisTemplate.opsForValue().get(cacheKey);
        
        if (progress != null) {
            return progress;
        }
        
        // 缓存未命中,查询数据库
        progress = loadUserAchievementProgress(userId, achievementCode);
        
        // 写入缓存,设置5分钟过期
        if (progress != null) {
            redisTemplate.opsForValue().set(cacheKey, progress, 5, TimeUnit.MINUTES);
        }
        
        return progress;
    }
    
    /**
     * 更新用户成就进度缓存
     */
    public void updateUserAchievementProgress(Long userId, String achievementCode, Map<String, Object> progress) {
        String cacheKey = String.format("achievement:progress:%s:%s", userId, achievementCode);
        redisTemplate.opsForValue().set(cacheKey, progress, 5, TimeUnit.MINUTES);
    }
    
    /**
     * 清除用户成就缓存
     */
    public void clearUserAchievementCache(Long userId) {
        Set<String> keys = redisTemplate.keys(String.format("achievement:progress:%s:*", userId));
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
    
    /**
     * 更新排行榜缓存
     */
    @Scheduled(cron = "0 0 */2 * * ?")  // 每2小时更新一次
    public void updateLeaderboardCache() {
        // 更新总成就排行榜
        List<Map<String, Object>> totalLeaderboard = loadTotalAchievementLeaderboard();
        redisTemplate.opsForValue().set("achievement:leaderboard:total", totalLeaderboard, 2, TimeUnit.HOURS);
        
        // 更新月度排行榜
        List<Map<String, Object>> monthlyLeaderboard = loadMonthlyAchievementLeaderboard();
        redisTemplate.opsForValue().set("achievement:leaderboard:monthly", monthlyLeaderboard, 2, TimeUnit.HOURS);
    }
}

数据库优化

针对成就系统特点,采取以下数据库优化措施:

  1. 索引优化:

    • 用户成就表建立复合索引(user_id, status),提高状态查询效率
    • 解锁时间索引(unlock_time),支持时间范围和排序查询
    • 成就编码索引(achievement_code),提高编码查询速度
  2. 分页查询优化:

    • 成就记录查询采用ID范围分页,避免深度分页问题
    • 批量操作时采用分批处理,避免锁表时间过长
  3. 读写分离:

    • 高频统计查询使用从库
    • 事务性操作使用主库
  4. 定时统计:

    • 用户成就汇总数据通过定时任务预计算
    • 减少实时统计计算压力

常见问题解决

成就解锁条件已满足但未自动解锁

问题描述:用户完成了某项成就的解锁条件,但系统未自动解锁对应成就。

可能原因:

  • 异步检查队列处理延迟
  • 成就条件配置有误
  • 用户数据统计不准确
  • 事件未正确触发成就检查

解决方案:

  1. 用户可通过"刷新成就"按钮手动触发检查
  2. 检查成就条件配置是否正确
  3. 验证用户统计数据的准确性
  4. 确认事件触发机制是否正常工作
  5. 查看异步任务执行日志,确认是否有错误
成就进度更新不及时

问题描述:用户完成活动后,成就进度未实时更新。

可能原因:

  • 进度更新是异步处理的
  • 缓存未及时刷新
  • 进度计算中使用了定时统计数据

解决方案:

  1. 页面增加进度更新中的提示
  2. 优化关键路径的缓存策略
  3. 确保重要活动能立即反映在成就进度上
  4. 定时任务统计间隔可适当缩短
成就奖励领取失败

问题描述:用户无法成功领取已完成成就的奖励。

可能原因:

  • 奖励发放事务异常
  • 积分系统异常
  • 奖励配置错误
  • 网络或前端问题

解决方案:

  1. 检查成就奖励配置是否正确
  2. 查看系统日志定位具体错误
  3. 确认集成的奖励系统(如积分系统)是否正常
  4. 必要时手动为用户补发奖励
高并发场景下成就检查性能问题

问题描述:用户高峰期,成就检查导致系统响应变慢。

可能原因:

  • 成就检查逻辑效率低下
  • 异步线程池配置不合理
  • 数据库连接数不足
  • 缓存命中率低

解决方案:

  1. 优化成就检查算法,采用增量检查
  2. 调整异步线程池大小和队列容量
  3. 增加数据库连接池配置
  4. 优化缓存策略,提高命中率
  5. 高峰期可降低成就检查频率

未来优化计划

短期优化

  1. 个性化成就推荐:

    • 基于用户行为和偏好推荐适合的成就挑战
    • 突出显示用户即将达成的成就
    • 设计成就完成路径建议
  2. 社交化成就分享:

    • 优化成就解锁分享体验
    • 增加社交媒体分享功能
    • 支持成就解锁动态生成精美卡片
  3. 成就展示优化:

    • 丰富成就徽章展示效果
    • 提供成就墙、成就树等多种展示形式
    • 支持自定义成就徽章展示排序
  4. 数据分析增强:

    • 提供更详细的成就达成数据分析
    • 展示用户成就解锁趋势图
    • 与同龄人/同区域用户的成就对比

长期规划

  1. 动态成就系统:

    • 支持运营人员动态配置成就
    • 开发成就编辑器,无需修改代码即可创建新成就
    • 支持限时成就、活动成就动态发布
  2. AI成就推荐:

    • 基于机器学习的个性化成就推荐
    • 预测用户可能感兴趣的成就类型
    • 根据用户活动模式推荐最适合的成就挑战
  3. 多维成就体系:

    • 引入团队成就、家庭成就等多维度成就体系
    • 开发区域性环保成就,鼓励社区共同参与
    • 构建企业环保成就,推动企业参与环保行动
  4. 成就游戏化增强:

    • 引入成就探索模式,部分成就条件隐藏
    • 开发成就连击机制,连续解锁成就获得额外奖励
    • 设计成就挑战赛,阶段性举办成就竞赛活动

参考资源

  • 游戏化设计最佳实践
  • 成就系统用户体验研究
  • 环保激励机制案例分析
  • 行为心理学与成就系统
  • 高性能成就系统架构

总结

成就系统是JDWA Green-U平台的核心激励机制之一,通过将用户的环保行为转化为可视化的成就和徽章,有效提升了用户参与环保活动的积极性。系统设计了多种类型的成就和灵活的解锁条件,满足不同用户的成就需求,形成了长短期结合的用户激励体系。

通过与用户活动、碳减排统计、积分系统等模块的紧密集成,成就系统形成了完整的激励闭环。用户完成环保活动后,系统自动检查成就完成状态,解锁成就并发放奖励,同时鼓励用户分享成就,产生社交效应,从而吸引更多用户参与环保行动。

系统在设计中充分考虑了性能和可扩展性,采用异步处理、多级缓存、事件驱动等技术手段,确保在高并发场景下也能提供流畅的用户体验。未来将持续优化和丰富成就内容,增强社交分享和个性化推荐功能,构建更加完善的用户激励生态。

通过成就系统,JDWA Green-U平台不仅量化了用户的环保贡献,更建立了用户的环保成就感,激发用户持续参与环保活动的内在动力,为平台的长期用户留存和环保理念传播提供了有力支持。 </rewritten_file>

Prev
JDWA Green-U 积分系统模块技术文档
Next
JDWA Green-U 奖品兑换模块技术文档