|
@@ -0,0 +1,293 @@
|
|
|
+package org.dromara.system.service.impl.app;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import org.dromara.common.core.utils.MapstructUtils;
|
|
|
+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.app.GameUser;
|
|
|
+import org.dromara.system.domain.app.WxLoginResult;
|
|
|
+import org.dromara.system.domain.vo.GameAthleteVo;
|
|
|
+import org.dromara.system.domain.vo.GameEventProjectVo;
|
|
|
+import org.dromara.system.domain.vo.GameEventVo;
|
|
|
+import org.dromara.system.domain.vo.app.UserEventInfoVo;
|
|
|
+import org.dromara.system.domain.vo.app.UserLoginVo;
|
|
|
+import org.dromara.system.mapper.*;
|
|
|
+import org.dromara.system.mapper.app.GameUserMapper;
|
|
|
+import org.dromara.system.service.app.IUserEventService;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.reactive.function.client.WebClient;
|
|
|
+import reactor.core.publisher.Mono;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class UserEventServiceImpl implements IUserEventService {
|
|
|
+ @Autowired
|
|
|
+ private GameUserMapper gameUserMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private GameAthleteMapper gameAthleteMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private GameScoreMapper gameScoreMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private GameEventProjectMapper gameEventProjectMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private GameEventMapper gameEventMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private WebClient webClient;
|
|
|
+
|
|
|
+ // Jackson ObjectMapper用于JSON解析
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ // 微信小程序配置(需要在配置文件中设置)
|
|
|
+ @Value("${wechat.miniapp.appid:}")
|
|
|
+ private String appId;
|
|
|
+
|
|
|
+ @Value("${wechat.miniapp.secret:}")
|
|
|
+ private String secret;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public UserEventInfoVo login(UserLoginVo loginVo) {
|
|
|
+ try {
|
|
|
+ // 步骤1:通过code获取微信openid和session_key
|
|
|
+ WxLoginResult wxResult = getWxLoginResult(loginVo.getCode());
|
|
|
+ if (wxResult == null || wxResult.getOpenid() == null || wxResult.getOpenid().isEmpty()) {
|
|
|
+ throw new RuntimeException("微信登录失败,无法获取用户信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 步骤2:查找或创建用户
|
|
|
+ GameUser user = findOrCreateUser(wxResult.getOpenid(), loginVo);
|
|
|
+
|
|
|
+ // 步骤3:返回用户基本信息
|
|
|
+ UserEventInfoVo result = new UserEventInfoVo();
|
|
|
+ result.setUserId(user.getUserId());
|
|
|
+ result.setUsername(user.getUsername());
|
|
|
+
|
|
|
+ return result;
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("微信登录失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过code获取微信登录结果
|
|
|
+ */
|
|
|
+ private WxLoginResult getWxLoginResult(String code) {
|
|
|
+ try {
|
|
|
+ if (appId == null || appId.isEmpty() || secret == null || secret.isEmpty()) {
|
|
|
+ throw new RuntimeException("微信小程序配置未设置");
|
|
|
+ }
|
|
|
+
|
|
|
+ String url = String.format(
|
|
|
+ "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
|
|
+ appId, secret, code
|
|
|
+ );
|
|
|
+
|
|
|
+ // 先获取字符串响应,避免Content-Type不匹配问题
|
|
|
+ String responseBody = webClient.get()
|
|
|
+ .uri(url)
|
|
|
+ .retrieve()
|
|
|
+ .onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
|
|
+ response -> response.bodyToMono(String.class)
|
|
|
+ .flatMap(body -> Mono.error(new RuntimeException("HTTP错误: " + response.statusCode() + ", 响应: " + body))))
|
|
|
+ .bodyToMono(String.class)
|
|
|
+ .timeout(Duration.ofSeconds(30)) // 30秒超时
|
|
|
+ .block(); // 同步等待结果
|
|
|
+
|
|
|
+ if (responseBody == null || responseBody.trim().isEmpty()) {
|
|
|
+ throw new RuntimeException("微信接口返回空结果");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动解析JSON响应
|
|
|
+ WxLoginResult result = parseWxResponse(responseBody);
|
|
|
+
|
|
|
+ if (result.getErrcode() != null && result.getErrcode() != 0) {
|
|
|
+ throw new RuntimeException("微信接口返回错误:" + result.getErrmsg() + " (错误码: " + result.getErrcode() + ")");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result.getOpenid() == null || result.getOpenid().isEmpty()) {
|
|
|
+ throw new RuntimeException("微信接口未返回openid");
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ } catch (Exception e) {
|
|
|
+ if (e instanceof RuntimeException) {
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ throw new RuntimeException("调用微信接口失败:" + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析微信API响应
|
|
|
+ */
|
|
|
+ private WxLoginResult parseWxResponse(String responseBody) {
|
|
|
+ try {
|
|
|
+ // 使用Jackson解析JSON
|
|
|
+ WxLoginResult result = objectMapper.readValue(responseBody, WxLoginResult.class);
|
|
|
+ return result;
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("解析微信响应失败:" + e.getMessage() + ", 响应内容: " + responseBody);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查找或创建用户
|
|
|
+ */
|
|
|
+ private GameUser findOrCreateUser(String openid, UserLoginVo loginVo) {
|
|
|
+ // 先通过openid查找用户
|
|
|
+ GameUser user = gameUserMapper.selectByOpenid(openid);
|
|
|
+
|
|
|
+ if (user == null) {
|
|
|
+ // 用户不存在,创建新用户
|
|
|
+ user = new GameUser();
|
|
|
+ user.setOpenid(openid);
|
|
|
+ user.setUsername("wx_" + openid.substring(0, Math.min(8, openid.length()))); // 生成用户名
|
|
|
+ user.setNickname(loginVo.getNickName() != null ? loginVo.getNickName() : "微信用户");
|
|
|
+ user.setAvatar(loginVo.getAvatarUrl());
|
|
|
+ // 注意:如果数据库password字段允许为NULL,则不需要设置密码
|
|
|
+ // 如果数据库password字段不允许为NULL,请取消注释下面这行代码
|
|
|
+ // user.setPassword("WX_LOGIN_" + System.currentTimeMillis());
|
|
|
+ user.setCreateTime(new Date());
|
|
|
+ user.setUpdateTime(new Date());
|
|
|
+ user.setStatus("0"); // 正常状态
|
|
|
+ user.setDelFlag("0");
|
|
|
+
|
|
|
+ // 插入用户
|
|
|
+ gameUserMapper.insert(user);
|
|
|
+ } else {
|
|
|
+ // 用户存在,更新信息
|
|
|
+ if (loginVo.getNickName() != null && !loginVo.getNickName().isEmpty()) {
|
|
|
+ user.setNickname(loginVo.getNickName());
|
|
|
+ }
|
|
|
+ if (loginVo.getAvatarUrl() != null && !loginVo.getAvatarUrl().isEmpty()) {
|
|
|
+ user.setAvatar(loginVo.getAvatarUrl());
|
|
|
+ }
|
|
|
+ user.setUpdateTime(new Date());
|
|
|
+
|
|
|
+ // 更新用户
|
|
|
+ gameUserMapper.updateById(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ return user;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public UserEventInfoVo getUserEventInfo(Long userId) {
|
|
|
+ // 步骤1:查询用户基本信息
|
|
|
+ GameUser user = gameUserMapper.selectUserById(userId);
|
|
|
+ if (user == null) {
|
|
|
+ throw new RuntimeException("用户不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 步骤2:查询用户关联的运动员信息
|
|
|
+ GameAthlete athlete = gameAthleteMapper.selectByUserId(userId);
|
|
|
+ if (athlete == null) {
|
|
|
+ throw new RuntimeException("用户未关联运动员信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 步骤3:查询运动员参与的项目ID列表
|
|
|
+ List<Long> projectIds = gameScoreMapper.selectProjectIdsByAthleteId(athlete.getAthleteId());
|
|
|
+ if (CollectionUtils.isEmpty(projectIds)) {
|
|
|
+ // 用户没有参与任何项目
|
|
|
+ return buildEmptyUserEventInfo(user, athlete);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 步骤4:查询项目详细信息
|
|
|
+ List<GameEventProject> projects = gameEventProjectMapper.selectBatchIds(projectIds);
|
|
|
+
|
|
|
+ // 步骤5:查询赛事信息
|
|
|
+ GameEvent event = gameEventMapper.selectById(projects.get(0).getEventId());
|
|
|
+
|
|
|
+ // 步骤6:查询运动员在各项目中的成绩
|
|
|
+ List<GameScore> scores = gameScoreMapper.selectByAthleteAndProjects(athlete.getAthleteId(), projectIds);
|
|
|
+
|
|
|
+ // 步骤7:组装数据返回
|
|
|
+ return assembleUserEventInfo(user, athlete, event, projects, scores);
|
|
|
+ }
|
|
|
+
|
|
|
+ private UserEventInfoVo buildEmptyUserEventInfo(GameUser user, GameAthlete athlete) {
|
|
|
+ UserEventInfoVo result = new UserEventInfoVo();
|
|
|
+ result.setUserId(user.getUserId());
|
|
|
+ result.setUsername(user.getUsername());
|
|
|
+
|
|
|
+ // 设置运动员信息
|
|
|
+ GameAthleteVo athleteInfo = MapstructUtils.convert(athlete, GameAthleteVo.class);
|
|
|
+ result.setAthleteInfo(athleteInfo);
|
|
|
+
|
|
|
+ // 设置空的赛事和项目信息
|
|
|
+ result.setEventInfo(null);
|
|
|
+ result.setProjectList(new ArrayList<>());
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private UserEventInfoVo assembleUserEventInfo(GameUser user, GameAthlete athlete,
|
|
|
+ GameEvent event, List<GameEventProject> projects,
|
|
|
+ List<GameScore> scores) {
|
|
|
+ UserEventInfoVo result = new UserEventInfoVo();
|
|
|
+ result.setUserId(user.getUserId());
|
|
|
+ result.setUsername(user.getUsername());
|
|
|
+
|
|
|
+ // 组装运动员信息
|
|
|
+ GameAthleteVo athleteInfo = MapstructUtils.convert(athlete, GameAthleteVo.class);
|
|
|
+ result.setAthleteInfo(athleteInfo);
|
|
|
+
|
|
|
+ // 组装赛事信息
|
|
|
+ GameEventVo eventInfo = MapstructUtils.convert(event, GameEventVo.class);
|
|
|
+ result.setEventInfo(eventInfo);
|
|
|
+
|
|
|
+ // 组装项目信息
|
|
|
+ List<GameEventProjectVo> projectList = new ArrayList<>();
|
|
|
+ for (GameEventProject project : projects) {
|
|
|
+ GameEventProjectVo projectInfo = MapstructUtils.convert(project, GameEventProjectVo.class);
|
|
|
+
|
|
|
+ // 查找该项目的成绩信息
|
|
|
+ GameScore score = findScoreByProjectId(scores, project.getProjectId());
|
|
|
+ if (score != null && projectInfo != null) {
|
|
|
+ // 将成绩信息添加到项目信息中,可以通过扩展字段或备注字段存储
|
|
|
+ // 这里我们使用备注字段来存储额外的成绩信息
|
|
|
+ String scoreInfo = String.format("个人成绩:%s, 团队成绩:%s, 积分:%d, 排名:%d, 奖项:%s",
|
|
|
+ score.getIndividualPerformance() != null ? score.getIndividualPerformance().toString() : "无",
|
|
|
+ score.getTeamPerformance() != null ? score.getTeamPerformance().toString() : "无",
|
|
|
+ score.getScorePoint() != null ? score.getScorePoint() : 0,
|
|
|
+ score.getScoreRank() != null ? score.getScoreRank() : 0,
|
|
|
+ calculateAward(score.getScoreRank()));
|
|
|
+ projectInfo.setRemark(scoreInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ projectList.add(projectInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按项目开始时间排序
|
|
|
+ projectList.sort(Comparator.comparing(GameEventProjectVo::getStartTime));
|
|
|
+ result.setProjectList(projectList);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private GameScore findScoreByProjectId(List<GameScore> scores, Long projectId) {
|
|
|
+ return scores.stream()
|
|
|
+ .filter(score -> score.getProjectId().equals(projectId))
|
|
|
+ .findFirst()
|
|
|
+ .orElse(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String calculateAward(Integer rank) {
|
|
|
+ if (rank == null) return "无";
|
|
|
+ if (rank == 1) return "金牌";
|
|
|
+ if (rank == 2) return "银牌";
|
|
|
+ if (rank == 3) return "铜牌";
|
|
|
+ return "无";
|
|
|
+ }
|
|
|
+}
|