package com.rongyichuang.user.resolver; import com.rongyichuang.auth.dto.LoginResponse.EmployeeInfo; import com.rongyichuang.auth.dto.LoginResponse.JudgeInfo; import com.rongyichuang.auth.dto.LoginResponse.PlayerInfo; import com.rongyichuang.common.util.UserContextUtil; import com.rongyichuang.auth.util.JwtUtil; import com.rongyichuang.employee.entity.Employee; import com.rongyichuang.employee.service.EmployeeService; import com.rongyichuang.judge.entity.Judge; import com.rongyichuang.judge.service.JudgeService; import com.rongyichuang.player.entity.Player; import com.rongyichuang.player.service.PlayerService; import com.rongyichuang.player.repository.ActivityPlayerRepository; import com.rongyichuang.common.repository.MediaRepository; import com.rongyichuang.common.entity.Media; import com.rongyichuang.common.enums.MediaTargetType; import com.rongyichuang.media.service.MediaV2Service; import com.rongyichuang.media.dto.MediaSaveInput; import com.rongyichuang.media.dto.MediaSaveResponse; import com.rongyichuang.user.dto.response.UserProfile; import com.rongyichuang.user.dto.response.UserStats; import com.rongyichuang.user.dto.response.UserRegistration; import com.rongyichuang.user.dto.response.UserProject; import com.rongyichuang.user.dto.request.UserInput; import com.rongyichuang.user.dto.response.UserProfileInfo; import com.rongyichuang.user.entity.User; import com.rongyichuang.user.repository.UserRepository; import com.rongyichuang.player.dto.response.SubmissionMediaResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.graphql.data.method.annotation.Argument; import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.graphql.data.method.annotation.MutationMapping; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.time.LocalDate; import java.time.format.DateTimeFormatter; /** * 用户GraphQL解析器 */ @Controller public class UserResolver { private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(UserResolver.class); @Autowired private EmployeeService employeeService; @Autowired private JudgeService judgeService; @Autowired private PlayerService playerService; @Autowired private ActivityPlayerRepository activityPlayerRepository; @Autowired private MediaRepository mediaRepository; @Autowired private UserRepository userRepository; @PersistenceContext private EntityManager entityManager; @Autowired private UserContextUtil userContextUtil; @Autowired private JwtUtil jwtUtil; @Autowired private MediaV2Service mediaV2Service; @Value("${app.media-url}") private String mediaBaseUrl; /** * 获取当前用户档案 */ @QueryMapping public UserProfile userProfile() { try { Long userId = userContextUtil.getCurrentUserId(); logger.debug("进入userProfile,解析到用户ID: {}", userId); // 如果是匿名用户,返回基本的用户档案 if (userId == null) { logger.debug("匿名用户访问userProfile,返回基本档案"); UserProfile profile = new UserProfile(); profile.setId("0"); // 匿名用户ID设为0 profile.setName("匿名用户"); profile.setRoles(new ArrayList<>()); profile.setUserType("user"); // 匿名用户类型为普通用户 profile.setCreatedAt(java.time.LocalDateTime.now().toString()); return profile; } UserProfile profile = new UserProfile(); profile.setId(userId.toString()); // 获取用户基本信息 Optional userOpt = userRepository.findById(userId); User user = null; if (userOpt.isPresent()) { user = userOpt.get(); // 设置基本信息(姓名和电话号码) profile.setName(user.getName()); profile.setPhone(user.getPhone()); // 设置性别 if (user.getGender() != null) { profile.setGender(user.getGender() == 1 ? "MALE" : "FEMALE"); } // 设置生日 if (user.getBirthday() != null) { profile.setBirthday(user.getBirthday().toString()); } } List roles = new ArrayList<>(); // 检查是否是员工 logger.debug("开始查询员工信息,userId={}", userId); Employee employee = employeeService.findByUserId(userId); logger.debug("员工查询结果: {}", employee != null ? ("id=" + employee.getId() + ", name=" + employee.getName()) : "无"); if (employee != null) { // 如果员工信息存在,则覆盖基本信息 if (employee.getName() != null) { profile.setName(employee.getName()); } if (employee.getPhone() != null) { profile.setPhone(employee.getPhone()); } roles.add("EMPLOYEE"); EmployeeInfo employeeInfo = new EmployeeInfo( employee.getId(), employee.getName(), employee.getRoleId(), employee.getDescription() ); profile.setEmployee(employeeInfo); } // 检查是否是评委 logger.debug("开始查询评委信息,userId={}", userId); Judge judge = judgeService.findByUserId(userId); logger.debug("评委查询结果: {}", judge != null ? ("id=" + judge.getId() + ", name=" + judge.getName()) : "无"); if (judge != null) { // 如果评委信息存在且基本信息为空,则设置评委信息 if (profile.getName() == null && judge.getName() != null) { profile.setName(judge.getName()); } if (profile.getPhone() == null && judge.getPhone() != null) { profile.setPhone(judge.getPhone()); } roles.add("JUDGE"); JudgeInfo judgeInfo = new JudgeInfo( judge.getId(), judge.getName(), judge.getTitle(), judge.getCompany(), judge.getDescription() ); profile.setJudge(judgeInfo); } // 检查是否是学员 logger.debug("开始查询学员信息,userId={}", userId); Player player = playerService.findByUserId(userId); logger.debug("学员查询结果: {}", player != null ? ("id=" + player.getId() + ", name=" + player.getName()) : "无"); if (player != null) { // 如果学员信息存在且基本信息为空,则设置学员信息 if (profile.getName() == null && player.getName() != null) { profile.setName(player.getName()); } if (profile.getPhone() == null && player.getPhone() != null) { profile.setPhone(player.getPhone()); } roles.add("PLAYER"); PlayerInfo playerInfo = new PlayerInfo( player.getId(), player.getName(), player.getPhone(), player.getDescription() ); profile.setPlayer(playerInfo); } profile.setRoles(roles); // 确定主要角色类型(优先级:employee > judge > player) String userType; if (employee != null) { userType = "employee"; } else if (judge != null) { userType = "judge"; } else if (player != null) { userType = "player"; } else { userType = "user"; // 普通用户 } profile.setUserType(userType); logger.debug("设置用户类型: {}", userType); // 获取用户头像 try { List avatarMediaList = mediaRepository.findByTargetTypeAndTargetIdAndState( MediaTargetType.USER_AVATAR.getValue(), userId, 1 ); if (!avatarMediaList.isEmpty()) { Media avatarMedia = avatarMediaList.get(0); String fullAvatarUrl = buildFullMediaUrl(avatarMedia.getPath()); profile.setAvatar(fullAvatarUrl); logger.debug("找到用户头像: {} -> {}", avatarMedia.getPath(), fullAvatarUrl); } else { logger.debug("用户{}没有找到头像", userId); } } catch (Exception e) { logger.warn("获取用户头像失败: {}", e.getMessage()); } logger.debug("userProfile构建完成,roles={}, name={}, phone={}, avatar={}", roles, profile.getName(), profile.getPhone(), profile.getAvatar()); profile.setCreatedAt(java.time.LocalDateTime.now().toString()); return profile; } catch (Exception e) { logger.error("获取用户档案失败", e); throw new RuntimeException("获取用户档案失败: " + e.getMessage(), e); } } /** * 获取用户统计数据 */ @QueryMapping public UserStats userStats() { try { Long userId = userContextUtil.getCurrentUserId(); if (userId == null) { return null; } UserStats stats = new UserStats(); stats.setTotalRegistrations(0); stats.setOngoingActivities(0); stats.setCompletedActivities(0); stats.setAwards(0); return stats; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取我的报名记录 */ @QueryMapping public List myRegistrations(@Argument Integer limit) { try { Long userId = userContextUtil.getCurrentUserId(); if (userId == null) { return new ArrayList<>(); } // 构建SQL查询,获取用户的报名记录 StringBuilder sql = new StringBuilder(); sql.append("SELECT ap.id, a.id as activity_id, a.name as activity_name, "); sql.append("ap.project_name, ap.state, ap.create_time, "); sql.append("m.path as cover_image_path "); sql.append("FROM t_activity_player ap "); sql.append("INNER JOIN t_player p ON ap.player_id = p.id "); sql.append("INNER JOIN t_activity a ON ap.activity_id = a.id "); sql.append("LEFT JOIN t_media m ON a.cover_image_id = m.id "); sql.append("WHERE p.user_id = :userId "); sql.append("ORDER BY ap.create_time DESC"); if (limit != null && limit > 0) { sql.append(" LIMIT :limit"); } Query query = entityManager.createNativeQuery(sql.toString()); query.setParameter("userId", userId); if (limit != null && limit > 0) { query.setParameter("limit", limit); } @SuppressWarnings("unchecked") List results = query.getResultList(); List registrations = new ArrayList<>(); for (Object[] row : results) { UserRegistration registration = new UserRegistration(); registration.setId(String.valueOf(row[0])); // 创建ActivityInfo UserRegistration.ActivityInfo activityInfo = new UserRegistration.ActivityInfo(); activityInfo.setId(String.valueOf(row[1])); activityInfo.setTitle((String) row[2]); // 设置封面图片 if (row[6] != null) { UserRegistration.MediaInfo mediaInfo = new UserRegistration.MediaInfo(); mediaInfo.setPath((String) row[6]); mediaInfo.setFullUrl(buildFullMediaUrl((String) row[6])); activityInfo.setCoverImage(mediaInfo); } registration.setActivity(activityInfo); // 设置状态 Integer state = (Integer) row[4]; String status = "pending"; if (state != null) { switch (state) { case 0: status = "pending"; break; case 1: status = "approved"; break; case 2: status = "rejected"; break; default: status = "unknown"; break; } } registration.setStatus(status); // 设置报名时间 if (row[5] != null) { registration.setRegistrationTime(row[5].toString()); } registrations.add(registration); } return registrations; } catch (Exception e) { logger.error("获取用户报名记录失败", e); return new ArrayList<>(); } } /** * 获取我的项目列表 */ @QueryMapping public List myProjects() { try { Long userId = userContextUtil.getCurrentUserId(); logger.debug("获取用户项目列表,userId: {}", userId); if (userId == null) { return new ArrayList<>(); } // 查询用户的所有项目(state = 0, 1, 2) // 使用 t_activity_player join t_player join t_user 的方式通过用户ID查询 String sql = """ SELECT ap.id, ap.project_name, a.name as activity_name, ap.state, ap.create_time FROM t_activity_player ap JOIN t_player p ON ap.player_id = p.id JOIN t_activity a ON a.id = ap.activity_id WHERE p.user_id = ? AND ap.state IN (0, 1, 2) ORDER BY ap.create_time DESC """; Query query = entityManager.createNativeQuery(sql); query.setParameter(1, userId); @SuppressWarnings("unchecked") List results = query.getResultList(); List projects = new ArrayList<>(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); for (Object[] row : results) { String id = row[0].toString(); String projectName = row[1] != null ? row[1].toString() : ""; String activityName = row[2] != null ? row[2].toString() : ""; String status = row[3] != null ? row[3].toString() : "0"; String createTime = row[4] != null ? row[4].toString() : ""; UserProject project = new UserProject(id, projectName, activityName, status, createTime); // 查询项目的提交文件 List submissionFiles = getSubmissionFiles(Long.parseLong(id)); project.setSubmissionFiles(submissionFiles); projects.add(project); } logger.debug("查询到 {} 个项目", projects.size()); return projects; } catch (Exception e) { logger.error("获取用户项目列表失败", e); return new ArrayList<>(); } } /** * 获取项目的提交文件 * @param activityPlayerId 活动参与者ID * @return 提交文件列表 */ private List getSubmissionFiles(Long activityPlayerId) { try { // 查询提交的媒体文件 (target_type = 5 表示活动参与者提交的文件,state = 1 表示有效状态) List medias = mediaRepository.findByTargetTypeAndTargetIdAndState(5, activityPlayerId, 1); List submissionFiles = new ArrayList<>(); for (Media media : medias) { SubmissionMediaResponse response = new SubmissionMediaResponse(); response.setId(media.getId()); response.setName(media.getName()); response.setPath(media.getPath()); response.setUrl(buildFullMediaUrl(media.getPath())); response.setFullUrl(buildFullMediaUrl(media.getPath())); response.setFullThumbUrl(buildFullMediaUrl(media.getThumbPath())); response.setFileExt(media.getFileExt()); response.setFileSize(media.getFileSize() != null ? media.getFileSize().longValue() : null); response.setMediaType(media.getMediaType()); response.setThumbUrl(buildFullMediaUrl(media.getThumbPath())); submissionFiles.add(response); } return submissionFiles; } catch (Exception e) { logger.error("获取项目提交文件失败,activityPlayerId: {}", activityPlayerId, e); return new ArrayList<>(); } } /** * 构建完整的媒体URL * @param path 媒体路径 * @return 完整的URL */ private String buildFullMediaUrl(String path) { if (!StringUtils.hasText(path)) { return null; } // 如果路径已经是完整URL,直接返回 if (path.startsWith("http://") || path.startsWith("https://")) { return path; } // 构建完整URL if (StringUtils.hasText(mediaBaseUrl)) { // 确保baseUrl以/结尾,path不以/开头 String baseUrl = mediaBaseUrl.endsWith("/") ? mediaBaseUrl : mediaBaseUrl + "/"; String relativePath = path.startsWith("/") ? path.substring(1) : path; return baseUrl + relativePath; } // 如果没有配置baseUrl,返回原路径 return path; } /** * 保存用户信息 */ @MutationMapping public UserProfileInfo saveUserInfo(@Argument UserInput input) { try { Long userId = userContextUtil.getCurrentUserIdIncludingAnonymous(); logger.debug("进入saveUserInfo,解析到用户ID(包括匿名用户): {}", userId); if (userId == null) { throw new RuntimeException("用户未登录"); } User user; boolean isNewUser = false; // 检查手机号是否存在 if (StringUtils.hasText(input.getPhone())) { Optional existingUserByPhone = userRepository.findByPhone(input.getPhone()); if (existingUserByPhone.isPresent()) { // 手机号已存在,更新现有用户 user = existingUserByPhone.get(); logger.debug("手机号{}已存在,更新现有用户,用户ID: {}", input.getPhone(), user.getId()); // 如果当前用户ID与手机号对应的用户ID不同,需要处理匿名用户转正的情况 if (!user.getId().equals(userId)) { logger.debug("匿名用户{}转正为正式用户{}", userId, user.getId()); // 检查匿名用户是否有需要合并的信息 if (userId < 0) { // 这是匿名用户转正,检查是否有临时信息需要合并 Optional anonymousUserOpt = userRepository.findById(userId); if (anonymousUserOpt.isPresent()) { User anonymousUser = anonymousUserOpt.get(); logger.debug("发现匿名用户记录,准备合并信息"); // 合并匿名用户的信息到正式用户(如果正式用户没有这些信息) if (!StringUtils.hasText(user.getName()) && StringUtils.hasText(anonymousUser.getName())) { user.setName(anonymousUser.getName()); logger.debug("合并匿名用户的姓名: {}", anonymousUser.getName()); } if (user.getGender() == null && anonymousUser.getGender() != null) { user.setGender(anonymousUser.getGender()); logger.debug("合并匿名用户的性别: {}", anonymousUser.getGender()); } if (user.getBirthday() == null && anonymousUser.getBirthday() != null) { user.setBirthday(anonymousUser.getBirthday()); logger.debug("合并匿名用户的生日: {}", anonymousUser.getBirthday()); } // 删除匿名用户记录(可选,避免数据冗余) try { userRepository.delete(anonymousUser); logger.debug("删除匿名用户记录: {}", userId); } catch (Exception e) { logger.warn("删除匿名用户记录失败: {}", e.getMessage()); } } } userId = user.getId(); // 使用正式用户ID } } else { // 手机号不存在,检查当前用户ID是否存在 Optional userOpt = userRepository.findById(userId); if (userOpt.isPresent()) { // 用户ID存在,更新信息 user = userOpt.get(); logger.debug("更新现有用户信息,用户ID: {}", userId); } else { // 用户ID不存在,创建新用户 user = new User(); user.setId(userId); isNewUser = true; logger.debug("创建新用户,用户ID: {}", userId); } } } else { // 没有提供手机号,直接根据用户ID查找或创建 Optional userOpt = userRepository.findById(userId); if (userOpt.isPresent()) { user = userOpt.get(); logger.debug("更新现有用户信息,用户ID: {}", userId); } else { user = new User(); user.setId(userId); isNewUser = true; logger.debug("创建新用户,用户ID: {}", userId); } } // 更新用户基本信息(不包含头像) if (StringUtils.hasText(input.getName())) { user.setName(input.getName()); } if (StringUtils.hasText(input.getPhone())) { user.setPhone(input.getPhone()); } // 处理性别转换:MALE -> 1, FEMALE -> 0 if ("MALE".equals(input.getGender())) { user.setGender(1); } else if ("FEMALE".equals(input.getGender())) { user.setGender(0); } // 处理生日转换 if (StringUtils.hasText(input.getBirthday())) { try { LocalDate birthday = LocalDate.parse(input.getBirthday()); user.setBirthday(birthday); } catch (Exception e) { logger.warn("生日格式解析失败: {}", input.getBirthday(), e); } } // 处理wxopenid更新:从JWT token中获取当前用户的wxopenid try { String token = userContextUtil.getTokenFromRequest(); if (token != null && jwtUtil.validateToken(token)) { Long currentUserId = jwtUtil.getUserIdFromToken(token); String wxopenidFromToken = jwtUtil.getWxOpenidFromToken(token); // 如果token中包含wxopenid,则更新用户的wxopenid if (StringUtils.hasText(wxopenidFromToken)) { logger.debug("从token中获取到wxopenid: {}", wxopenidFromToken); // 检查这个openid是否已经被其他用户使用 Optional existingUserWithOpenid = userRepository.findByWxOpenid(wxopenidFromToken); if (existingUserWithOpenid.isEmpty() || existingUserWithOpenid.get().getId().equals(user.getId())) { user.setWxOpenid(wxopenidFromToken); logger.debug("设置用户wxopenid: {}", wxopenidFromToken); } else { logger.warn("wxopenid {} 已被其他用户使用,用户ID: {}", wxopenidFromToken, existingUserWithOpenid.get().getId()); } } else { logger.debug("token中未包含wxopenid信息"); } } } catch (Exception e) { logger.warn("处理wxopenid更新时发生异常: {}", e.getMessage(), e); } // 保存用户基本信息 user = userRepository.save(user); logger.debug("用户信息保存成功,用户ID: {}, 是否新用户: {}", user.getId(), isNewUser); // 处理头像保存 if (StringUtils.hasText(input.getAvatar())) { try { logger.debug("开始保存用户头像,路径: {}", input.getAvatar()); // 构建MediaSaveInput MediaSaveInput mediaSaveInput = new MediaSaveInput(); mediaSaveInput.setTargetType("player"); // 使用"player"作为目标类型 mediaSaveInput.setTargetId(user.getId()); mediaSaveInput.setPath(input.getAvatar()); mediaSaveInput.setMediaType(1); // 1表示图片 mediaSaveInput.setFileSize(0L); // 设置默认文件大小为0,避免数据库约束错误 // 从路径中提取文件名和扩展名 String fileName = input.getAvatar(); if (fileName.contains("/")) { fileName = fileName.substring(fileName.lastIndexOf("/") + 1); } mediaSaveInput.setFileName(fileName); if (fileName.contains(".")) { String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1); mediaSaveInput.setFileExt(fileExt); } // 保存头像媒体记录 MediaSaveResponse saveResponse = mediaV2Service.saveMedia(mediaSaveInput); if (saveResponse.getSuccess()) { logger.debug("头像保存成功,媒体ID: {}", saveResponse.getMediaId()); } else { logger.warn("头像保存失败: {}", saveResponse.getMessage()); } } catch (Exception e) { logger.error("保存头像时发生错误", e); // 头像保存失败不影响用户信息保存 } } // 构建返回结果 UserProfileInfo result = new UserProfileInfo(); result.setId(user.getId().toString()); result.setName(user.getName()); result.setPhone(user.getPhone()); // 正确处理性别转换:1 -> MALE, 0 -> FEMALE, null -> null if (user.getGender() != null) { result.setGender(user.getGender() == 1 ? "MALE" : "FEMALE"); } else { result.setGender(null); } result.setBirthday(user.getBirthday() != null ? user.getBirthday().toString() : null); result.setWxOpenId(user.getWxOpenid()); result.setUnionId(user.getWxUnionid()); // 查找并设置头像URL(从t_media表获取) try { List avatarMedias = mediaRepository.findByTargetTypeAndTargetIdAndState( MediaTargetType.USER_AVATAR.getValue(), user.getId(), 1 ); if (!avatarMedias.isEmpty()) { Media avatarMedia = avatarMedias.get(0); result.setAvatar(buildFullMediaUrl(avatarMedia.getPath())); logger.debug("设置头像URL: {}", result.getAvatar()); } } catch (Exception e) { logger.warn("获取头像失败: {}", e.getMessage(), e); } return result; } catch (Exception e) { logger.error("保存用户信息失败", e); throw new RuntimeException("保存用户信息失败: " + e.getMessage()); } } }