JDWA Green-U 成就系统模块
本文档详细介绍了JDWA Green-U项目中成就系统模块的设计思路、业务规则和核心实现。该模块负责用户成就的定义、解锁条件设置、进度跟踪和展示,是激励用户持续参与绿色低碳活动的重要机制。
模块概述
成就系统是JDWA Green-U平台的重要激励机制之一,通过将用户的绿色行为转化为可视化的成就徽章和荣誉,满足用户的成长感和成就感。成就系统与积分系统相辅相成,共同构成平台的用户激励体系,引导用户持续参与环保活动。
业务价值
- 激发用户的收集欲和完成欲,增强用户粘性
- 引导用户尝试多种环保行为,扩大平台使用场景
- 提供长期目标,保持用户活跃度
- 增强环保行为的社交属性,促进用户分享
- 建立用户环保成就感,形成正向反馈
功能范围
- 成就类型与等级定义
- 成就解锁条件设置
- 成就进度跟踪与计算
- 成就解锁与奖励发放
- 成就展示与分享
- 成就统计与排行
核心功能设计
成就类型定义
系统支持以下几种成就类型:
成就类型 | 代码标识 | 说明 | 示例 |
---|---|---|---|
累计型成就 | ACCUMULATIVE | 基于用户某项数据的累计值 | 累计步行10000步、累计减碳100kg |
计数型成就 | COUNTING | 基于用户完成特定行为的次数 | 连续签到7天、完成20次骑行 |
里程碑成就 | MILESTONE | 用户达到特定状态或条件 | 完善个人资料、绑定第三方账号 |
组合型成就 | COMBINATION | 需要同时满足多个条件 | 一周内完成3种不同环保活动 |
社交型成就 | SOCIAL | 与用户社交行为相关 | 邀请10位好友、分享5次环保成果 |
成就等级设计
每种成就类型下,可以设置多个等级的成就,形成成就链:
- 初级成就:容易达成,新用户快速获得成就感
- 中级成就:需要持续努力,中度用户的目标
- 高级成就:难度较大,资深用户的挑战
- 终极成就:极具挑战性,平台"专家级"用户的象征
例如,"碳减排达人"可以有以下等级:
- 碳减排新手(累计减碳10kg)
- 碳减排能手(累计减碳100kg)
- 碳减排专家(累计减碳500kg)
- 碳减排大师(累计减碳1000kg)
成就解锁条件
系统支持灵活的条件设置机制:
数值型条件:
- 单一数值比较:如"累计步行距离 >= 10km"
- 范围比较:如"单次骑行时间在30-60分钟之间"
- 多条件组合:如"单日行走距离 >= 5km 且 步数 >= 10000"
时间型条件:
- 特定时间段完成:如"早上6-8点完成户外运动"
- 连续型条件:如"连续7天完成打卡"
- 累计时间条件:如"累计骑行时间达到24小时"
复合型条件:
- 与逻辑:同时满足多个条件
- 或逻辑:满足条件集合中的任意一个
- 计数逻辑:满足N个条件中的M个
成就奖励机制
成就解锁后可获得的奖励类型:
- 积分奖励:解锁成就获得平台积分
- 徽章奖励:获得独特的成就徽章,展示在个人主页
- 头像框:特殊成就可解锁专属头像框
- 称号奖励:获得与成就相关的用户称号
- 特权奖励:解锁特定功能或权限
成就进度跟踪
系统实时追踪用户的各项数据,更新成就完成进度:
- 实时进度:用户可随时查看各项成就的完成进度
- 进度展示:以进度条、百分比等形式直观展示
- 距离目标:显示"还差XX即可达成"的提示
- 快要完成提醒:成就接近完成时进行提醒
成就解锁流程
触发检查:
- 定时检查:系统定时任务检查所有用户的成就状态
- 事件触发:用户完成特定行为后立即检查相关成就
- 手动触发:用户手动刷新成就列表
解锁条件验证:
- 读取用户当前数据状态
- 与成就条件进行比对
- 确认是否满足解锁条件
成就解锁:
- 更新用户成就记录
- 发放成就奖励
- 推送成就解锁通知
后续激励:
- 展示下一级成就目标
- 推荐相关成就挑战
数据模型设计
核心表结构
成就系统模块主要涉及以下数据表:
- 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
}
}
系统集成
与其他模块的集成
成就系统是一个横向的激励层,与系统多个模块有密切的集成关系:
用户活动模块:
- 用户完成绿色出行活动后,触发相关成就检查
- 提供活动数据用于累计型成就的进度计算
- 活动里程碑达成时自动解锁对应成就
碳减排统计模块:
- 获取碳减排统计数据用于环保成就的解锁判断
- 碳减排达到特定里程碑时触发成就检查
积分系统:
- 成就解锁时发放积分奖励
- 积分里程碑达成时触发相关成就
用户管理模块:
- 成就和徽章展示在用户个人主页
- 特殊成就解锁用户称号和头像框
社交系统:
- 成就解锁可选择性分享到社交动态
- 邀请好友、社交互动产生相关成就
事件驱动集成机制
成就系统采用事件驱动的方式与其他模块集成,避免直接耦合:
/**
* 事件发布示例 - 用户活动完成
*/
@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);
}
}
}
}
性能优化
成就检查优化
成就检查是一个计算密集型操作,为避免影响主业务流程和用户体验,系统采取以下优化策略:
异步检查:
- 所有成就检查操作均采用异步方式执行
- 用户活动保存成功后立即返回,成就检查在后台进行
- 解锁成就后通过消息通知用户
批量处理:
- 定时任务批量检查需要更新的成就进度
- 将检查任务分批执行,避免一次处理过多数据
增量检查:
- 只检查与当前事件相关的成就类型
- 跳过已完成的成就检查
- 对于组合型成就,先快速检查必要条件是否满足
合理设置触发条件:
- 累计型成就设置合理的检查间隔
- 里程碑型成就只在可能达成时检查
实现示例:异步检查队列
/**
* 成就检查队列服务
*/
@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();
}
}
}
}
缓存策略
系统采用多层缓存策略提高访问性能:
成就定义缓存:
- 成就定义及条件配置较少变动,适合长时间缓存
- 使用本地缓存 + Redis 二级缓存
- 配置修改时主动刷新缓存
用户成就进度缓存:
- 用户当前值和目标值缓存,减少数据库查询
- 使用带有短暂过期时间的Redis缓存
- 写穿透策略确保数据一致性
热点数据缓存:
- 成就排行榜使用定时更新的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);
}
}
数据库优化
针对成就系统特点,采取以下数据库优化措施:
索引优化:
- 用户成就表建立复合索引(user_id, status),提高状态查询效率
- 解锁时间索引(unlock_time),支持时间范围和排序查询
- 成就编码索引(achievement_code),提高编码查询速度
分页查询优化:
- 成就记录查询采用ID范围分页,避免深度分页问题
- 批量操作时采用分批处理,避免锁表时间过长
读写分离:
- 高频统计查询使用从库
- 事务性操作使用主库
定时统计:
- 用户成就汇总数据通过定时任务预计算
- 减少实时统计计算压力
常见问题解决
成就解锁条件已满足但未自动解锁
问题描述:用户完成了某项成就的解锁条件,但系统未自动解锁对应成就。
可能原因:
- 异步检查队列处理延迟
- 成就条件配置有误
- 用户数据统计不准确
- 事件未正确触发成就检查
解决方案:
- 用户可通过"刷新成就"按钮手动触发检查
- 检查成就条件配置是否正确
- 验证用户统计数据的准确性
- 确认事件触发机制是否正常工作
- 查看异步任务执行日志,确认是否有错误
成就进度更新不及时
问题描述:用户完成活动后,成就进度未实时更新。
可能原因:
- 进度更新是异步处理的
- 缓存未及时刷新
- 进度计算中使用了定时统计数据
解决方案:
- 页面增加进度更新中的提示
- 优化关键路径的缓存策略
- 确保重要活动能立即反映在成就进度上
- 定时任务统计间隔可适当缩短
成就奖励领取失败
问题描述:用户无法成功领取已完成成就的奖励。
可能原因:
- 奖励发放事务异常
- 积分系统异常
- 奖励配置错误
- 网络或前端问题
解决方案:
- 检查成就奖励配置是否正确
- 查看系统日志定位具体错误
- 确认集成的奖励系统(如积分系统)是否正常
- 必要时手动为用户补发奖励
高并发场景下成就检查性能问题
问题描述:用户高峰期,成就检查导致系统响应变慢。
可能原因:
- 成就检查逻辑效率低下
- 异步线程池配置不合理
- 数据库连接数不足
- 缓存命中率低
解决方案:
- 优化成就检查算法,采用增量检查
- 调整异步线程池大小和队列容量
- 增加数据库连接池配置
- 优化缓存策略,提高命中率
- 高峰期可降低成就检查频率
未来优化计划
短期优化
个性化成就推荐:
- 基于用户行为和偏好推荐适合的成就挑战
- 突出显示用户即将达成的成就
- 设计成就完成路径建议
社交化成就分享:
- 优化成就解锁分享体验
- 增加社交媒体分享功能
- 支持成就解锁动态生成精美卡片
成就展示优化:
- 丰富成就徽章展示效果
- 提供成就墙、成就树等多种展示形式
- 支持自定义成就徽章展示排序
数据分析增强:
- 提供更详细的成就达成数据分析
- 展示用户成就解锁趋势图
- 与同龄人/同区域用户的成就对比
长期规划
动态成就系统:
- 支持运营人员动态配置成就
- 开发成就编辑器,无需修改代码即可创建新成就
- 支持限时成就、活动成就动态发布
AI成就推荐:
- 基于机器学习的个性化成就推荐
- 预测用户可能感兴趣的成就类型
- 根据用户活动模式推荐最适合的成就挑战
多维成就体系:
- 引入团队成就、家庭成就等多维度成就体系
- 开发区域性环保成就,鼓励社区共同参与
- 构建企业环保成就,推动企业参与环保行动
成就游戏化增强:
- 引入成就探索模式,部分成就条件隐藏
- 开发成就连击机制,连续解锁成就获得额外奖励
- 设计成就挑战赛,阶段性举办成就竞赛活动
参考资源
总结
成就系统是JDWA Green-U平台的核心激励机制之一,通过将用户的环保行为转化为可视化的成就和徽章,有效提升了用户参与环保活动的积极性。系统设计了多种类型的成就和灵活的解锁条件,满足不同用户的成就需求,形成了长短期结合的用户激励体系。
通过与用户活动、碳减排统计、积分系统等模块的紧密集成,成就系统形成了完整的激励闭环。用户完成环保活动后,系统自动检查成就完成状态,解锁成就并发放奖励,同时鼓励用户分享成就,产生社交效应,从而吸引更多用户参与环保行动。
系统在设计中充分考虑了性能和可扩展性,采用异步处理、多级缓存、事件驱动等技术手段,确保在高并发场景下也能提供流畅的用户体验。未来将持续优化和丰富成就内容,增强社交分享和个性化推荐功能,构建更加完善的用户激励生态。
通过成就系统,JDWA Green-U平台不仅量化了用户的环保贡献,更建立了用户的环保成就感,激发用户持续参与环保活动的内在动力,为平台的长期用户留存和环保理念传播提供了有力支持。 </rewritten_file>