|
@@ -0,0 +1,386 @@
|
|
|
|
|
+package com.train.service;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.train.entity.*;
|
|
|
|
|
+import com.train.mapper.*;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 徽章与成长体系服务:
|
|
|
|
|
+ * - ensureCatalog():目录兜底同步(schema 种子 + 代码常量双保险)
|
|
|
|
|
+ * - syncBadges(uid):自动判定并颁发符合条件的徽章
|
|
|
|
|
+ * - grantBadge(uid, badgeKey, adminId, remark):手动颁发(幂等)
|
|
|
|
|
+ * - mine(uid):查询学员全量徽章 + 段位
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class BadgeService {
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainBadgeMapper trainBadgeMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainUserBadgeMapper trainUserBadgeMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainCertificateMapper trainCertificateMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainClassMapper trainClassMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainCourseMapper trainCourseMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainInviteMapper trainInviteMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainRoadmapMapper trainRoadmapMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainGroupMemberMapper trainGroupMemberMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainVoteMapper trainVoteMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TrainEnrollmentMapper trainEnrollmentMapper;
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 32 枚徽章常量(兜底) ==========
|
|
|
|
|
+ private static final List<Map<String, String>> CATALOG = Arrays.asList(
|
|
|
|
|
+ // module 1-12
|
|
|
|
|
+ badge("mod_s1", "module", "启航·上", "#7C3AED", "电子", "完成 S1 家庭目录树检查", 1),
|
|
|
|
|
+ badge("mod_s2", "module", "启航·下", "#7C3AED", "电子", "完成 S2 路演两轮投票", 2),
|
|
|
|
|
+ badge("mod_a1", "module", "财富诊断", "#F59E0B", "电子", "完成 L1-A1 提交真实财富报告", 3),
|
|
|
|
|
+ badge("mod_a2", "module", "财富系统", "#F59E0B", "电子", "完成 L1-A2 自动化生效", 4),
|
|
|
|
|
+ badge("mod_h1", "module", "健康趋势", "#10B981", "电子", "完成 L1-H1 提交风险清单", 5),
|
|
|
|
|
+ badge("mod_h2", "module", "健康系统", "#10B981", "电子", "完成 L1-H2 解读菌群报告", 6),
|
|
|
|
|
+ badge("mod_g1", "module", "成长拆解", "#8B5CF6", "电子", "完成 L1-G1 习惯实验", 7),
|
|
|
|
|
+ badge("mod_g2", "module", "成长系统", "#8B5CF6", "电子", "完成 L1-G2 学期计划", 8),
|
|
|
|
|
+ badge("mod_b1", "module", "系统架构", "#1D4ED8", "电子", "完成 L2-B1 ≥5条联动规则", 9),
|
|
|
|
|
+ badge("mod_b2", "module", "自动化工厂", "#1D4ED8", "电子", "完成 L2-B2 ≥8条自动化", 10),
|
|
|
|
|
+ badge("mod_b3", "module", "仪表盘", "#1D4ED8", "电子", "完成 L2-B3 看板上线", 11),
|
|
|
|
|
+ badge("mod_b4", "module", "季度闭环", "#1D4ED8", "电子", "完成 L2-B4 系统体检", 12),
|
|
|
|
|
+ // course 13-17
|
|
|
|
|
+ badge("course_start", "course", "启航徽章", "#7C3AED", "实体+证书", "L0 完课", 13),
|
|
|
|
|
+ badge("course_wealth", "course", "财富专精徽章", "#F59E0B", "电子", "L1-A 完课", 14),
|
|
|
|
|
+ badge("course_health", "course", "健康专精徽章", "#10B981", "电子", "L1-B 完课", 15),
|
|
|
|
|
+ badge("course_growth", "course", "成长规划专精徽章", "#8B5CF6", "电子", "L1-C 完课", 16),
|
|
|
|
|
+ badge("course_system", "course", "系统师徽章", "#14B8A6", "电子", "L2 完课+L3 结业", 17),
|
|
|
|
|
+ // skill 18-23
|
|
|
|
|
+ badge("skill_mask", "skill", "脱敏达人", "#64748B", "电子", "找茬赛满分/脱敏零问题", 18),
|
|
|
|
|
+ badge("skill_prompt", "skill", "提示词匠人", "#64748B", "电子", "改编提示词≥10条被采用", 19),
|
|
|
|
|
+ badge("skill_automation", "skill", "自动化搭建者", "#64748B", "电子", "自建自动化≥5条稳定1月", 20),
|
|
|
|
|
+ badge("skill_pitch", "skill", "路演讲者", "#64748B", "电子", "L0路演获最想抄走票", 21),
|
|
|
|
|
+ badge("skill_board", "skill", "看板设计师", "#64748B", "电子", "仪表盘获最佳", 22),
|
|
|
|
|
+ badge("skill_retest", "skill", "复测坚持者", "#64748B", "电子", "连续2季度复测", 23),
|
|
|
|
|
+ // milestone 24-28
|
|
|
|
|
+ badge("ms_21day", "milestone", "21天全勤", "#F59E0B", "实体", "L3打卡≥21天", 24),
|
|
|
|
|
+ badge("ms_light", "milestone", "传灯徽章", "#F97316", "实体", "成功转介≥3人报名", 25),
|
|
|
|
|
+ badge("ms_repeat", "milestone", "回炉徽章", "#F97316", "电子", "复训≥3模块", 26),
|
|
|
|
|
+ badge("ms_share", "milestone", "分享徽章", "#F97316", "实体", "沙龙案例分享≥1次", 27),
|
|
|
|
|
+ badge("ms_review", "milestone", "年度复盘", "#F59E0B", "电子", "完成三本账年度复盘", 28),
|
|
|
|
|
+ // lecturer 29-32
|
|
|
|
|
+ badge("lec_t1", "lecturer", "讲师·铜", "#F97316", "实体+讲师证", "T1认证", 29),
|
|
|
|
|
+ badge("lec_t2", "lecturer", "讲师·银", "#D4D4D8", "电子", "T2认证", 30),
|
|
|
|
|
+ badge("lec_t3", "lecturer", "讲师·金", "#F59E0B", "电子", "T3认证", 31),
|
|
|
|
|
+ badge("lec_t4", "lecturer", "讲师·珊瑚金", "#F97316", "电子", "T4认证", 32)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ private static Map<String, String> badge(String key, String category, String name,
|
|
|
|
|
+ String color, String carrier, String conditionDesc, int sort) {
|
|
|
|
|
+ Map<String, String> m = new LinkedHashMap<>();
|
|
|
|
|
+ m.put("badge_key", key);
|
|
|
|
|
+ m.put("category", category);
|
|
|
|
|
+ m.put("name", name);
|
|
|
|
|
+ m.put("color", color);
|
|
|
|
|
+ m.put("carrier", carrier);
|
|
|
|
|
+ m.put("condition_desc", conditionDesc);
|
|
|
|
|
+ m.put("sort", String.valueOf(sort));
|
|
|
|
|
+ return m;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== a) 目录兜底同步 ==========
|
|
|
|
|
+ public void ensureCatalog() {
|
|
|
|
|
+ Long count = trainBadgeMapper.selectCount(new LambdaQueryWrapper<TrainBadge>());
|
|
|
|
|
+ if (count != null && count > 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("[BadgeService] 徽章目录为空,执行代码常量兜底插入 {} 条", CATALOG.size());
|
|
|
|
|
+ for (Map<String, String> row : CATALOG) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ TrainBadge b = new TrainBadge();
|
|
|
|
|
+ b.setBadgeKey(row.get("badge_key"));
|
|
|
|
|
+ b.setCategory(row.get("category"));
|
|
|
|
|
+ b.setName(row.get("name"));
|
|
|
|
|
+ b.setColor(row.get("color"));
|
|
|
|
|
+ b.setCarrier(row.get("carrier"));
|
|
|
|
|
+ b.setConditionDesc(row.get("condition_desc"));
|
|
|
|
|
+ b.setSort(Integer.parseInt(row.get("sort")));
|
|
|
|
|
+ b.setCreatedAt(new Date());
|
|
|
|
|
+ trainBadgeMapper.insert(b);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 重复插入(badge_key UNIQUE)被吞,属预期
|
|
|
|
|
+ log.debug("插入徽章 {} 失败(可能已存在): {}", row.get("badge_key"), e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== b) 自动判定并颁发 ==========
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public void syncBadges(Long uid) {
|
|
|
|
|
+ ensureCatalog();
|
|
|
|
|
+
|
|
|
|
|
+ // 已拥有的徽章 key 集合
|
|
|
|
|
+ List<TrainUserBadge> existingList = trainUserBadgeMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainUserBadge>().eq(TrainUserBadge::getUid, uid));
|
|
|
|
|
+ Set<String> ownedKeys = existingList.stream()
|
|
|
|
|
+ .map(TrainUserBadge::getBadgeKey)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ // 证书列表
|
|
|
|
|
+ List<TrainCertificate> certs = trainCertificateMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainCertificate>().eq(TrainCertificate::getUid, uid));
|
|
|
|
|
+ Set<String> certLevels = certs.stream()
|
|
|
|
|
+ .map(TrainCertificate::getLevel)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ // 收集待颁发的徽章 key
|
|
|
|
|
+ Set<String> toGrant = new LinkedHashSet<>();
|
|
|
|
|
+
|
|
|
|
|
+ // --- L0 相关 ---
|
|
|
|
|
+ if (certLevels.contains("L0")) {
|
|
|
|
|
+ toGrant.add("mod_s1");
|
|
|
|
|
+ toGrant.add("mod_s2");
|
|
|
|
|
+ toGrant.add("course_start");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- L2 相关 ---
|
|
|
|
|
+ if (certLevels.contains("L2")) {
|
|
|
|
|
+ toGrant.add("mod_b1");
|
|
|
|
|
+ toGrant.add("mod_b2");
|
|
|
|
|
+ toGrant.add("mod_b3");
|
|
|
|
|
+ toGrant.add("mod_b4");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- course_system:L2 + L3 ---
|
|
|
|
|
+ if (certLevels.contains("L2") && certLevels.contains("L3")) {
|
|
|
|
|
+ toGrant.add("course_system");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- L1 证书按课程名细分 ---
|
|
|
|
|
+ // 对每张 level=L1 的证书,解析课程名(cert -> class -> course -> name)
|
|
|
|
|
+ Set<String> l1CourseNames = new HashSet<>();
|
|
|
|
|
+ for (TrainCertificate cert : certs) {
|
|
|
|
|
+ if (!"L1".equals(cert.getLevel()) || cert.getClassId() == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ TrainClass clazz = trainClassMapper.selectById(cert.getClassId());
|
|
|
|
|
+ if (clazz == null || clazz.getCourseId() == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ TrainCourse course = trainCourseMapper.selectById(clazz.getCourseId());
|
|
|
|
|
+ if (course == null || course.getName() == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ l1CourseNames.add(course.getName());
|
|
|
|
|
+ }
|
|
|
|
|
+ for (String courseName : l1CourseNames) {
|
|
|
|
|
+ if (courseName.contains("财富")) {
|
|
|
|
|
+ toGrant.add("mod_a1");
|
|
|
|
|
+ toGrant.add("mod_a2");
|
|
|
|
|
+ toGrant.add("course_wealth");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (courseName.contains("健康")) {
|
|
|
|
|
+ toGrant.add("mod_h1");
|
|
|
|
|
+ toGrant.add("mod_h2");
|
|
|
|
|
+ toGrant.add("course_health");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (courseName.contains("成长")) {
|
|
|
|
|
+ toGrant.add("mod_g1");
|
|
|
|
|
+ toGrant.add("mod_g2");
|
|
|
|
|
+ toGrant.add("course_growth");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- skill_pitch(路演讲者)---
|
|
|
|
|
+ // uid 所在的组 → 该组的 roadmap speaker_uid=uid → 该组在 round=2 投票中被投中
|
|
|
|
|
+ if (!ownedKeys.contains("skill_pitch")) {
|
|
|
|
|
+ List<TrainGroupMember> members = trainGroupMemberMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainGroupMember>().eq(TrainGroupMember::getUid, uid));
|
|
|
|
|
+ for (TrainGroupMember m : members) {
|
|
|
|
|
+ if (m.getGroupId() == null) continue;
|
|
|
|
|
+ // 查该组是否有 speaker_uid=uid 的路演记录
|
|
|
|
|
+ TrainRoadmap roadmap = trainRoadmapMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainRoadmap>()
|
|
|
|
|
+ .eq(TrainRoadmap::getGroupId, m.getGroupId())
|
|
|
|
|
+ .eq(TrainRoadmap::getSpeakerUid, uid)
|
|
|
|
|
+ .last("LIMIT 1"));
|
|
|
|
|
+ if (roadmap == null) continue;
|
|
|
|
|
+ // 该组在 round=2 投票中是否被投中
|
|
|
|
|
+ Long voteCount = trainVoteMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainVote>()
|
|
|
|
|
+ .eq(TrainVote::getRound, 2)
|
|
|
|
|
+ .eq(TrainVote::getTargetGroupId, m.getGroupId()));
|
|
|
|
|
+ if (voteCount != null && voteCount > 0) {
|
|
|
|
|
+ toGrant.add("skill_pitch");
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- ms_light(传灯):成功转介 >= 3 人 ---
|
|
|
|
|
+ if (!ownedKeys.contains("ms_light")) {
|
|
|
|
|
+ Long inviteCount = trainInviteMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainInvite>()
|
|
|
|
|
+ .eq(TrainInvite::getInviterId, uid)
|
|
|
|
|
+ .eq(TrainInvite::getSuccessful, 1));
|
|
|
|
|
+ if (inviteCount != null && inviteCount >= 3) {
|
|
|
|
|
+ toGrant.add("ms_light");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 批量颁发(幂等:已存在的跳过)
|
|
|
|
|
+ for (String key : toGrant) {
|
|
|
|
|
+ if (!ownedKeys.contains(key)) {
|
|
|
|
|
+ insertUserBadge(uid, key, "系统自动判定", 0L);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== c) 手动颁发(幂等) ==========
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public boolean grantBadge(Long uid, String badgeKey, Long adminId, String remark) {
|
|
|
|
|
+ ensureCatalog();
|
|
|
|
|
+
|
|
|
|
|
+ // 检查徽章键是否在目录中
|
|
|
|
|
+ TrainBadge badge = trainBadgeMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainBadge>().eq(TrainBadge::getBadgeKey, badgeKey).last("LIMIT 1"));
|
|
|
|
|
+ if (badge == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 幂等:已存在则直接返回
|
|
|
|
|
+ Long exists = trainUserBadgeMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainUserBadge>()
|
|
|
|
|
+ .eq(TrainUserBadge::getUid, uid)
|
|
|
|
|
+ .eq(TrainUserBadge::getBadgeKey, badgeKey));
|
|
|
|
|
+ if (exists != null && exists > 0) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String proof = (remark != null && !remark.trim().isEmpty()) ? remark.trim() : badge.getConditionDesc();
|
|
|
|
|
+ insertUserBadge(uid, badgeKey, proof, adminId);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== d) 学员端查询 ==========
|
|
|
|
|
+ public Map<String, Object> mine(Long uid) {
|
|
|
|
|
+ // 先触发自动判定
|
|
|
|
|
+ syncBadges(uid);
|
|
|
|
|
+
|
|
|
|
|
+ // 全量徽章目录(按 sort 升序)
|
|
|
|
|
+ List<TrainBadge> allBadges = trainBadgeMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainBadge>().orderByAsc(TrainBadge::getSort));
|
|
|
|
|
+
|
|
|
|
|
+ // 该用户已获得的徽章
|
|
|
|
|
+ List<TrainUserBadge> userBadges = trainUserBadgeMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainUserBadge>().eq(TrainUserBadge::getUid, uid));
|
|
|
|
|
+
|
|
|
|
|
+ // 构建 badgeKey -> UserBadge 的 map
|
|
|
|
|
+ Map<String, TrainUserBadge> earnedMap = userBadges.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(TrainUserBadge::getBadgeKey, ub -> ub, (a, b) -> a));
|
|
|
|
|
+
|
|
|
|
|
+ // 构建 badge key -> badge catalog 的 map
|
|
|
|
|
+ Map<String, TrainBadge> catalogMap = allBadges.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(TrainBadge::getBadgeKey, b -> b, (a, b) -> a));
|
|
|
|
|
+
|
|
|
|
|
+ // badges 列表
|
|
|
|
|
+ List<Map<String, Object>> badges = new ArrayList<>();
|
|
|
|
|
+ int earnedCount = 0;
|
|
|
|
|
+ for (TrainBadge b : allBadges) {
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("badgeKey", b.getBadgeKey());
|
|
|
|
|
+ item.put("category", b.getCategory());
|
|
|
|
|
+ item.put("name", b.getName());
|
|
|
|
|
+ item.put("color", b.getColor());
|
|
|
|
|
+ item.put("carrier", b.getCarrier());
|
|
|
|
|
+ item.put("condition", b.getConditionDesc());
|
|
|
|
|
+ TrainUserBadge ub = earnedMap.get(b.getBadgeKey());
|
|
|
|
|
+ boolean earned = (ub != null);
|
|
|
|
|
+ item.put("earned", earned);
|
|
|
|
|
+ item.put("earnedAt", ub != null ? ub.getEarnedAt() : null);
|
|
|
|
|
+ if (earned) {
|
|
|
|
|
+ earnedCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ badges.add(item);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 段位计算(取最高档)
|
|
|
|
|
+ Map<String, String> grade = calcGrade(uid, earnedMap);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
|
|
+ result.put("grade", grade);
|
|
|
|
|
+ result.put("earnedCount", earnedCount);
|
|
|
|
|
+ result.put("badges", badges);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 段位计算 ==========
|
|
|
|
|
+ private Map<String, String> calcGrade(Long uid, Map<String, TrainUserBadge> earnedMap) {
|
|
|
|
|
+ Map<String, String> grade = new LinkedHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 讲师(任一 lec_t1..t4 已获)
|
|
|
|
|
+ if (earnedMap.containsKey("lec_t1") || earnedMap.containsKey("lec_t2")
|
|
|
|
|
+ || earnedMap.containsKey("lec_t3") || earnedMap.containsKey("lec_t4")) {
|
|
|
|
|
+ grade.put("key", "lecturer");
|
|
|
|
|
+ grade.put("name", "讲师");
|
|
|
|
|
+ return grade;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 系统师
|
|
|
|
|
+ if (earnedMap.containsKey("course_system")) {
|
|
|
|
|
+ grade.put("key", "system");
|
|
|
|
|
+ grade.put("name", "系统师");
|
|
|
|
|
+ return grade;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 专精
|
|
|
|
|
+ if (earnedMap.containsKey("course_wealth") || earnedMap.containsKey("course_health")
|
|
|
|
|
+ || earnedMap.containsKey("course_growth")) {
|
|
|
|
|
+ grade.put("key", "expert");
|
|
|
|
|
+ grade.put("name", "专精");
|
|
|
|
|
+ return grade;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 启航
|
|
|
|
|
+ if (earnedMap.containsKey("course_start")) {
|
|
|
|
|
+ grade.put("key", "starter");
|
|
|
|
|
+ grade.put("name", "启航");
|
|
|
|
|
+ return grade;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 见习(参加 ≥1 期活动:存在报名记录)
|
|
|
|
|
+ Long enrollCount = trainEnrollmentMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
|
|
+ .eq(TrainEnrollment::getUid, uid)
|
|
|
|
|
+ .isNotNull(TrainEnrollment::getId));
|
|
|
|
|
+ if (enrollCount != null && enrollCount > 0) {
|
|
|
|
|
+ grade.put("key", "novice");
|
|
|
|
|
+ grade.put("name", "见习");
|
|
|
|
|
+ return grade;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 未入门(无任何学习记录)
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 内部辅助 ==========
|
|
|
|
|
+ private void insertUserBadge(Long uid, String badgeKey, String proof, Long grantedBy) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ TrainUserBadge ub = new TrainUserBadge();
|
|
|
|
|
+ ub.setUid(uid);
|
|
|
|
|
+ ub.setBadgeKey(badgeKey);
|
|
|
|
|
+ ub.setProof(proof);
|
|
|
|
|
+ ub.setGrantedBy(grantedBy);
|
|
|
|
|
+ ub.setEarnedAt(new Date());
|
|
|
|
|
+ trainUserBadgeMapper.insert(ub);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 重复插入(uid+badge_key UNIQUE)被吞,属预期
|
|
|
|
|
+ log.debug("用户 {} 颁发徽章 {} 失败(可能已存在): {}", uid, badgeKey, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|