Quellcode durchsuchen

feat(app): 完善体验端报名和信息管理功能

- 在 ExperienceEnrollSubmitBo 中新增队伍名称字段
- 新增 ExperienceUpdateInfoBo 用于处理个人信息修改
- 实现体验端个人信息修改接口 /updateInfo
- 优化 GameEventMapper 中的 SQL 查询条件
- 完善 submitExperienceEnroll 方法,增加项目限额校验和队伍处理逻辑
- 实现 updateExperienceMyInfo 服务方法支持个人信息更新
- 添加分布式锁防止并发报名冲突
- 重构报名逻辑支持团队项目和详细的限额检查
zhou vor 1 Woche
Ursprung
Commit
f34d3e807b

+ 5 - 4
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/ExperienceVersionController.java

@@ -10,6 +10,7 @@ import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.system.domain.GameEvent;
 import org.dromara.system.domain.vo.app.*;
 import org.dromara.system.domain.bo.ExperienceEnrollSubmitBo;
+import org.dromara.system.domain.bo.ExperienceUpdateInfoBo;
 import org.dromara.system.mapper.*;
 import org.dromara.system.service.app.IUserEventService;
 import org.dromara.system.service.IGameScoreService;
@@ -105,10 +106,10 @@ public class ExperienceVersionController {
     /**
      * 6、我的---个人信息修改
      */
-//    @PostMapping("/updateInfo")
-//    public R<Boolean> updateInfo(@RequestBody @Validated ExperienceUpdateInfoBo updateInfoBo) {
-//
-//    }
+    @PostMapping("/updateInfo")
+    public R<Boolean> updateInfo(@RequestBody @Validated ExperienceUpdateInfoBo updateInfoBo) {
+        return R.ok(TenantHelper.ignore(() -> userEventService.updateExperienceMyInfo(updateInfoBo)));
+    }
 
     /**
      * 7、赛事菜单查询接口

+ 5 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/ExperienceEnrollSubmitBo.java

@@ -76,4 +76,9 @@ public class ExperienceEnrollSubmitBo implements Serializable {
      */
     private String emergencyContactPhone;
 
+    /**
+     * 队伍名称
+     */
+    private String teamName;
+
 }

+ 53 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/ExperienceUpdateInfoBo.java

@@ -0,0 +1,53 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 体验端个人信息修改BO
+ *
+ * @author system
+ */
+@Data
+public class ExperienceUpdateInfoBo implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @NotNull(message = "用户ID不能为空")
+    private Long userId;
+
+    /**
+     * 姓名
+     */
+    @NotBlank(message = "姓名不能为空")
+    private String name;
+
+    /**
+     * 手机号
+     */
+    @NotBlank(message = "手机号码不能为空")
+    private String phone;
+
+    /**
+     * 性别
+     */
+    @NotBlank(message = "性别不能为空")
+    private String gender;
+
+    /**
+     * 年龄
+     */
+    private Long age;
+
+    /**
+     * 单位
+     */
+    private String unit;
+}

+ 3 - 4
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameEventMapper.java

@@ -59,10 +59,9 @@ public interface GameEventMapper extends BaseMapperPlus<GameEvent, GameEventVo>
     @Select("SELECT e.event_id as id,e.event_code as bianhao,e.event_name as name,e.create_time,ec.config_value as ServerUrl "
             +
             "FROM game_event e " +
-            "left join game_event_config ec on ec.event_id = e.event_id " +
-            "WHERE e.event_id in (select event_id from game_referee where referee_id = #{refereeId}) and e.del_flag = '0' "
-            +
-            "and ec.config_key = 'upload_path' ")
+            "left join game_event_config ec on ec.event_id = e.event_id and ec.config_key = 'upload_path' " +
+            "WHERE e.event_id in " +
+        "(select event_id from game_referee where referee_id = #{refereeId}) and e.del_flag = '0' ")
     List<GameAppEvent> selectRefereeEventList(Long refereeId);
 
     /**

+ 4 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/app/IUserEventService.java

@@ -4,6 +4,7 @@ import org.dromara.system.domain.vo.app.ExperienceMyInfoVo;
 import org.dromara.system.domain.vo.app.ExperienceMyRecordVo;
 import org.dromara.system.domain.vo.app.ExperienceEnrollInfoVo;
 import org.dromara.system.domain.bo.ExperienceEnrollSubmitBo;
+import org.dromara.system.domain.bo.ExperienceUpdateInfoBo;
 import org.dromara.system.domain.vo.app.UserEventInfoVo;
 import org.dromara.system.domain.vo.app.UserLoginVo;
 
@@ -27,4 +28,7 @@ public interface IUserEventService {
 
     // 体验端线上报名--信息提交
     Boolean submitExperienceEnroll(ExperienceEnrollSubmitBo submitBo);
+
+    // 体验端修改“我的”个人信息
+    Boolean updateExperienceMyInfo(ExperienceUpdateInfoBo updateInfoBo);
 }

+ 307 - 28
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/app/UserEventServiceImpl.java

@@ -5,10 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
-import org.dromara.system.domain.GameAthlete;
-import org.dromara.system.domain.GameEvent;
-import org.dromara.system.domain.GameEventProject;
-import org.dromara.system.domain.GameScore;
+import org.dromara.system.domain.*;
 import org.dromara.system.domain.app.GameUser;
 import org.dromara.system.domain.app.WxLoginResult;
 import org.dromara.system.domain.vo.GameAthleteVo;
@@ -23,7 +20,9 @@ import java.math.BigDecimal;
 import org.dromara.system.domain.vo.app.ExperienceEnrollInfoVo;
 import org.dromara.system.domain.vo.app.ExperienceProjectVo;
 import org.dromara.system.domain.bo.ExperienceEnrollSubmitBo;
+import org.dromara.system.domain.bo.ExperienceUpdateInfoBo;
 import cn.hutool.json.JSONUtil;
+import com.baomidou.lock.annotation.Lock4j;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import org.dromara.system.mapper.*;
 import org.dromara.system.mapper.app.GameUserMapper;
@@ -55,6 +54,9 @@ public class UserEventServiceImpl implements IUserEventService {
     @Autowired
     private GameEventMapper gameEventMapper;
 
+    @Autowired
+    private GameTeamMapper gameTeamMapper;
+
     @Autowired
     private WebClient webClient;
 
@@ -366,11 +368,11 @@ public class UserEventServiceImpl implements IUserEventService {
         // 1. 获取赛事下的项目列表
         List<GameEventProject> projects = gameEventProjectMapper.selectList(
                 Wrappers.lambdaQuery(GameEventProject.class)
-                    .select(GameEventProject::getProjectId, GameEventProject::getProjectName, GameEventProject::getClassification)
+                        .select(GameEventProject::getProjectId, GameEventProject::getProjectName,
+                                GameEventProject::getClassification)
                         .eq(GameEventProject::getEventId, eventId)
                         .eq(GameEventProject::getDelFlag, "0")
-                        .orderByAsc(GameEventProject::getProjectId)
-        );
+                        .orderByAsc(GameEventProject::getProjectId));
         List<ExperienceProjectVo> projectVos = new ArrayList<>();
         if (CollectionUtils.isNotEmpty(projects)) {
             for (GameEventProject project : projects) {
@@ -386,13 +388,12 @@ public class UserEventServiceImpl implements IUserEventService {
         // 2. 根据手机号和赛事ID查询是否已经有运动员报名信息
         GameAthlete athlete = gameAthleteMapper.selectOne(
                 Wrappers.lambdaQuery(GameAthlete.class)
-                    .select(GameAthlete::getAthleteId, GameAthlete::getName, GameAthlete::getPhone,
-                        GameAthlete::getIdCard, GameAthlete::getGender, GameAthlete::getTshirtSize,
-                        GameAthlete::getEmergencyContactName, GameAthlete::getEmergencyContactPhone)
+                        .select(GameAthlete::getAthleteId, GameAthlete::getName, GameAthlete::getPhone,
+                                GameAthlete::getIdCard, GameAthlete::getGender, GameAthlete::getTshirtSize,
+                                GameAthlete::getEmergencyContactName, GameAthlete::getEmergencyContactPhone)
                         .eq(GameAthlete::getPhone, phone.trim())
                         .eq(GameAthlete::getEventId, eventId)
-                        .eq(GameAthlete::getDelFlag, "0")
-        );
+                        .eq(GameAthlete::getDelFlag, "0"));
 
         if (athlete != null) {
             infoVo.setAthleteId(athlete.getAthleteId());
@@ -408,37 +409,315 @@ public class UserEventServiceImpl implements IUserEventService {
     }
 
     @Override
+    @Lock4j(keys = { "#submitBo.projectId" })
     @Transactional(rollbackFor = Exception.class)
     public Boolean submitExperienceEnroll(ExperienceEnrollSubmitBo submitBo) {
-        // 1. 验证项目是否存在于该赛事
-        boolean project = gameEventProjectMapper.exists(
+        // 1. 验证项目是否存在于该赛事并获取项目详细限制
+        GameEventProject eventProject = gameEventProjectMapper.selectOne(
                 Wrappers.lambdaQuery(GameEventProject.class)
+                        .select(GameEventProject::getProjectId, GameEventProject::getProjectName,
+                                GameEventProject::getClassification, GameEventProject::getLimitPerson,
+                                GameEventProject::getLimitMale, GameEventProject::getLimitFemale,
+                                GameEventProject::getLimitTeam, GameEventProject::getScoreRule)
                         .eq(GameEventProject::getProjectId, submitBo.getProjectId())
                         .eq(GameEventProject::getEventId, submitBo.getEventId())
-                        .eq(GameEventProject::getDelFlag, "0")
-        );
-        if (!project) {
+                        .eq(GameEventProject::getDelFlag, "0"));
+        if (eventProject == null) {
             throw new ServiceException("所选项目在当前赛事中不存在或已被删除");
         }
-        GameAthlete athlete = gameAthleteMapper.selectOne(Wrappers.lambdaQuery(GameAthlete.class)
-            .eq(GameAthlete::getAthleteId, submitBo.getAthleteId())
-            .eq(GameAthlete::getDelFlag, "0")
-            .select(GameAthlete::getProjectValue)
-        );
-        if (athlete == null){
-            throw new ServiceException("运动员信息不存在或已被删除");
+
+        // 2. 获取或新建并绑定运动员信息
+        GameAthlete athlete = null;
+        boolean isNewAthlete = false;
+        // 优先根据传入的 athleteId 查询
+        if (submitBo.getAthleteId() != null) {
+            athlete = gameAthleteMapper.selectOne(Wrappers.lambdaQuery(GameAthlete.class)
+                .select(GameAthlete::getAthleteId, GameAthlete::getName, GameAthlete::getPhone,
+                    GameAthlete::getIdCard, GameAthlete::getGender, GameAthlete::getTshirtSize,
+                    GameAthlete::getEmergencyContactName, GameAthlete::getEmergencyContactPhone)
+                    .eq(GameAthlete::getAthleteId, submitBo.getAthleteId())
+                    .eq(GameAthlete::getDelFlag, "0"));
+        }
+        // 如果没有传入 athleteId 或查不到,则根据手机号和赛事ID查询
+        if (athlete == null) {
+            athlete = gameAthleteMapper.selectOne(Wrappers.lambdaQuery(GameAthlete.class)
+                .select(GameAthlete::getAthleteId, GameAthlete::getName, GameAthlete::getPhone,
+                        GameAthlete::getIdCard, GameAthlete::getGender, GameAthlete::getTshirtSize,
+                        GameAthlete::getEmergencyContactName, GameAthlete::getEmergencyContactPhone)
+                    .eq(GameAthlete::getPhone, submitBo.getPhone().trim())
+                    .eq(GameAthlete::getEventId, submitBo.getEventId())
+                    .eq(GameAthlete::getDelFlag, "0"));
+        }
+        // 如果仍找不到,则新建运动员信息
+        if (athlete == null) {
+            // 新建运动员时校验必要信息是否已上传
+            if (StringUtils.isBlank(submitBo.getName())) {
+                throw new ServiceException("姓名不能为空");
+            }
+            if (StringUtils.isBlank(submitBo.getPhone())) {
+                throw new ServiceException("手机号码不能为空");
+            }
+            if (StringUtils.isBlank(submitBo.getGender())) {
+                throw new ServiceException("性别不能为空");
+            }
+
+            athlete = new GameAthlete();
+            athlete.setEventId(submitBo.getEventId());
+            athlete.setName(submitBo.getName().trim());
+            athlete.setPhone(submitBo.getPhone().trim());
+            athlete.setGender(submitBo.getGender().trim());
+            athlete.setIdCard(submitBo.getIdCard().trim());
+            athlete.setTshirtSize(submitBo.getTshirtSize().trim());
+            athlete.setEmergencyContactName(submitBo.getEmergencyContactName().trim());
+            athlete.setEmergencyContactPhone(submitBo.getEmergencyContactPhone().trim());
+            athlete.setStatus("0");
+            athlete.setDelFlag("0");
+
+            // 寻找绑定的 userId
+            GameUser user = gameUserMapper.selectOne(Wrappers.lambdaQuery(GameUser.class)
+                    .eq(GameUser::getPhone, submitBo.getPhone().trim())
+                    .eq(GameUser::getDelFlag, "0")
+                    .select(GameUser::getUserId));
+            if (user != null) {
+                athlete.setUserId(user.getUserId());
+            }
+
+            isNewAthlete = true;
         }
+
+        // 3. 如果是团队项目,处理队伍绑定与新建
+        GameTeam team = null;
+        if ("1".equals(submitBo.getClassification())) {
+            if (StringUtils.isBlank(submitBo.getTeamName())) {
+                throw new ServiceException("团队项目必须提供队伍名称");
+            }
+
+            // 查找已有队伍
+            team = gameTeamMapper.selectOne(Wrappers.lambdaQuery(GameTeam.class)
+                    .select(GameTeam::getTeamId)
+                    .eq(GameTeam::getEventId, submitBo.getEventId())
+                    .eq(GameTeam::getTeamName, submitBo.getTeamName().trim())
+                    .eq(GameTeam::getDelFlag, "0"));
+
+            // 找不到匹配的队伍则新建
+            if (team == null) {
+                team = new GameTeam();
+                team.setEventId(submitBo.getEventId());
+                team.setTeamName(submitBo.getTeamName().trim());
+                team.setStatus("0");
+                team.setDelFlag("0");
+                gameTeamMapper.insert(team);
+            }
+
+            // 绑定队伍 ID
+            athlete.setTeamId(team.getTeamId());
+        }
+
+        // 4. 解析已报项目列表
         List<Long> list = JSONUtil.toList(athlete.getProjectValue(), Long.class);
         if (list == null) {
             list = new ArrayList<>();
         }
-        if (list.contains(submitBo.getProjectId())){
+
+        // 如果该项目已经被报过,直接返回成功,不做限额限制判断
+        if (list.contains(submitBo.getProjectId())) {
+            // 如果运动员是之前就存在的,这次直接更新可能变化了的信息(比如团队项目下绑定了新的队伍 ID)
+            if (!isNewAthlete) {
+                gameAthleteMapper.updateById(athlete);
+            }
             return true;
         }
+
+        // 5. 校验该赛事每人的项目报名限制数
+        GameEvent event = gameEventMapper.selectOne(Wrappers.lambdaQuery(GameEvent.class)
+            .select(GameEvent::getEventId, GameEvent::getLimitApplication)
+            .eq(GameEvent::getEventId, submitBo.getEventId()));
+        if (event == null) {
+            throw new ServiceException("该赛事不存在或已被删除");
+        }
+        if (event.getLimitApplication() != null && event.getLimitApplication() > 0) {
+            if (list.size() >= event.getLimitApplication()) {
+                throw new ServiceException("已超出该赛事每人最多可报项目限制(" + event.getLimitApplication() + "个)");
+            }
+        }
+
+        // 6. 校验项目各维度限制数限制
+        String gender = athlete.getGender();
+        // A. 个人项目限制校验
+        if ("0".equals(submitBo.getClassification())) {
+            // 项目总人数限制
+            if (eventProject.getLimitPerson() != null && eventProject.getLimitPerson() > 0) {
+                Long enrolledCount = gameAthleteMapper.selectCount(Wrappers.lambdaQuery(GameAthlete.class)
+                        .eq(GameAthlete::getEventId, submitBo.getEventId())
+                        .eq(GameAthlete::getDelFlag, "0")
+                        .and(wrapper -> wrapper
+                                .apply("JSON_CONTAINS(project_value, CAST({0} AS JSON))", submitBo.getProjectId())
+                                .or()
+                                .apply("JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR)))",
+                                        submitBo.getProjectId())));
+                if (enrolledCount >= eventProject.getLimitPerson()) {
+                    throw new ServiceException("该项目报名人数已满(" + eventProject.getLimitPerson() + "人),请尝试其他项目");
+                }
+            }
+
+            // 个人项目男生人数限制
+            if (eventProject.getLimitMale() != null && eventProject.getLimitMale() > 0 && "1".equals(gender)) {
+                Long maleCount = gameAthleteMapper.selectCount(Wrappers.lambdaQuery(GameAthlete.class)
+                        .eq(GameAthlete::getEventId, submitBo.getEventId())
+                        .eq(GameAthlete::getGender, "1")
+                        .eq(GameAthlete::getDelFlag, "0")
+                        .and(wrapper -> wrapper
+                                .apply("JSON_CONTAINS(project_value, CAST({0} AS JSON))", submitBo.getProjectId())
+                                .or()
+                                .apply("JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR)))",
+                                        submitBo.getProjectId())));
+                if (maleCount >= eventProject.getLimitMale()) {
+                    throw new ServiceException("该项目男生报名人数已满(" + eventProject.getLimitMale() + "人)");
+                }
+            }
+
+            // 个人项目女生人数限制
+            if (eventProject.getLimitFemale() != null && eventProject.getLimitFemale() > 0 && "2".equals(gender)) {
+                Long femaleCount = gameAthleteMapper.selectCount(Wrappers.lambdaQuery(GameAthlete.class)
+                        .eq(GameAthlete::getEventId, submitBo.getEventId())
+                        .eq(GameAthlete::getGender, "2")
+                        .eq(GameAthlete::getDelFlag, "0")
+                        .and(wrapper -> wrapper
+                                .apply("JSON_CONTAINS(project_value, CAST({0} AS JSON))", submitBo.getProjectId())
+                                .or()
+                                .apply("JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR)))",
+                                        submitBo.getProjectId())));
+                if (femaleCount >= eventProject.getLimitFemale()) {
+                    throw new ServiceException("该项目女生报名人数已满(" + eventProject.getLimitFemale() + "人)");
+                }
+            }
+        }
+        // B. 团体项目限制校验
+        else if ("1".equals(submitBo.getClassification()) && team != null) {
+            // 1) 团体项目队伍总数限制
+            if (eventProject.getLimitTeam() != null && eventProject.getLimitTeam() > 0) {
+                // 判断该队伍是否已经报名该项目
+                List<Long> teamProjects = JSONUtil.toList(team.getProjectValue(), Long.class);
+                boolean teamAlreadyEnrolled = teamProjects != null && teamProjects.contains(submitBo.getProjectId());
+
+                if (!teamAlreadyEnrolled) {
+                    Long enrolledTeamCount = gameTeamMapper.selectCount(Wrappers.lambdaQuery(GameTeam.class)
+                            .eq(GameTeam::getEventId, submitBo.getEventId())
+                            .eq(GameTeam::getDelFlag, "0")
+                            .and(wrapper -> wrapper
+                                    .apply("JSON_CONTAINS(project_value, CAST({0} AS JSON))", submitBo.getProjectId())
+                                    .or()
+                                    .apply("JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR)))",
+                                            submitBo.getProjectId())));
+                    if (enrolledTeamCount >= eventProject.getLimitTeam()) {
+                        throw new ServiceException("该项目报名队伍数已满(" + eventProject.getLimitTeam() + "队)");
+                    }
+                }
+            }
+
+            // 2) 队伍内每队人数限制 (对应 limitPerson)
+            if (eventProject.getLimitPerson() != null && eventProject.getLimitPerson() > 0) {
+                Long teamEnrolledCount = gameAthleteMapper.selectCount(Wrappers.lambdaQuery(GameAthlete.class)
+                        .eq(GameAthlete::getTeamId, team.getTeamId())
+                        .eq(GameAthlete::getDelFlag, "0")
+                        .and(wrapper -> wrapper
+                                .apply("JSON_CONTAINS(project_value, CAST({0} AS JSON))", submitBo.getProjectId())
+                                .or()
+                                .apply("JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR)))",
+                                        submitBo.getProjectId())));
+                if (teamEnrolledCount >= eventProject.getLimitPerson()) {
+                    throw new ServiceException("本队伍在该项目中的报名人数已达上限(" + eventProject.getLimitPerson() + "人)");
+                }
+            }
+
+            // 3) 队伍内每队男生人数限制
+            if (eventProject.getLimitMale() != null && eventProject.getLimitMale() > 0 && "1".equals(gender)) {
+                Long teamMaleCount = gameAthleteMapper.selectCount(Wrappers.lambdaQuery(GameAthlete.class)
+                        .eq(GameAthlete::getTeamId, team.getTeamId())
+                        .eq(GameAthlete::getGender, "1")
+                        .eq(GameAthlete::getDelFlag, "0")
+                        .and(wrapper -> wrapper
+                                .apply("JSON_CONTAINS(project_value, CAST({0} AS JSON))", submitBo.getProjectId())
+                                .or()
+                                .apply("JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR)))",
+                                        submitBo.getProjectId())));
+                if (teamMaleCount >= eventProject.getLimitMale()) {
+                    throw new ServiceException("本队伍在该项目中的男生报名人数已满(" + eventProject.getLimitMale() + "人)");
+                }
+            }
+
+            // 4) 队伍内每队女生人数限制
+            if (eventProject.getLimitFemale() != null && eventProject.getLimitFemale() > 0 && "2".equals(gender)) {
+                Long teamFemaleCount = gameAthleteMapper.selectCount(Wrappers.lambdaQuery(GameAthlete.class)
+                        .eq(GameAthlete::getTeamId, team.getTeamId())
+                        .eq(GameAthlete::getGender, "2")
+                        .eq(GameAthlete::getDelFlag, "0")
+                        .and(wrapper -> wrapper
+                                .apply("JSON_CONTAINS(project_value, CAST({0} AS JSON))", submitBo.getProjectId())
+                                .or()
+                                .apply("JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR)))",
+                                        submitBo.getProjectId())));
+                if (teamFemaleCount >= eventProject.getLimitFemale()) {
+                    throw new ServiceException("本队伍在该项目中的女生报名人数已满(" + eventProject.getLimitFemale() + "人)");
+                }
+            }
+        }
+
+        // 7. 保存/更新运动员信息与项目关联
         list.add(submitBo.getProjectId());
         athlete.setProjectValue(JSONUtil.toJsonStr(list));
-        return gameAthleteMapper.update(athlete, Wrappers.lambdaUpdate(GameAthlete.class)
-            .eq(GameAthlete::getAthleteId, submitBo.getAthleteId())
-        ) > 0;
+        boolean success;
+        if (isNewAthlete) {
+            success = gameAthleteMapper.insert(athlete) > 0;
+        } else {
+            success = gameAthleteMapper.updateById(athlete) > 0;
+        }
+
+        // 8. 团队项目时同步更新队伍参与的项目列表
+        if (success && "1".equals(submitBo.getClassification()) && team != null) {
+            List<Long> teamProjects = JSONUtil.toList(team.getProjectValue(), Long.class);
+            if (teamProjects == null) {
+                teamProjects = new ArrayList<>();
+            }
+            if (!teamProjects.contains(submitBo.getProjectId())) {
+                teamProjects.add(submitBo.getProjectId());
+                team.setProjectValue(JSONUtil.toJsonStr(teamProjects));
+                gameTeamMapper.updateById(team);
+            }
+        }
+        return success;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateExperienceMyInfo(ExperienceUpdateInfoBo bo) {
+        // 1. 判断用户是否存在
+        GameUser exists = gameUserMapper.selectOne(Wrappers.lambdaQuery(GameUser.class)
+                .eq(GameUser::getUserId, bo.getUserId())
+                .eq(GameUser::getDelFlag, "0")
+                .select(GameUser::getPhone));
+        if (exists == null) {
+            throw new ServiceException("用户不存在");
+        }
+
+        // 2. 更新用户的手机号、姓名
+        boolean flag = gameUserMapper.update(null, Wrappers.lambdaUpdate(GameUser.class)
+                .eq(GameUser::getUserId, bo.getUserId())
+                .set(StringUtils.isNotBlank(bo.getPhone()), GameUser::getPhone, bo.getPhone())
+                .set(StringUtils.isNotBlank(bo.getName()), GameUser::getNickname, bo.getName())
+                .set(StringUtils.isNotBlank(bo.getName()), GameUser::getUsername, bo.getName())) > 0;
+
+        // 3. 更新对应的运动员信息(根据 userId 更新所有关联记录)
+        if (flag) {
+            return gameAthleteMapper.update(null, Wrappers.lambdaUpdate(GameAthlete.class)
+                    .eq(GameAthlete::getPhone, exists.getPhone())
+                    .eq(GameAthlete::getDelFlag, "0")
+                    .set(StringUtils.isNotBlank(bo.getName()), GameAthlete::getName, bo.getName())
+                    .set(StringUtils.isNotBlank(bo.getPhone()), GameAthlete::getPhone, bo.getPhone())
+                    .set(StringUtils.isNotBlank(bo.getGender()), GameAthlete::getGender, bo.getGender())
+                    .set(bo.getAge() != null, GameAthlete::getAge, bo.getAge())
+                    .set(StringUtils.isNotBlank(bo.getUnit()), GameAthlete::getUnit, bo.getUnit())) > 0;
+        }
+        return flag;
     }
 }