Codex Assistant
昨天 58d9f460b2f8c34430285115e2557d18333c5cab
backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java
@@ -4,22 +4,47 @@
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解析器
@@ -38,7 +63,28 @@
    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;
    /**
     * 获取当前用户档案
@@ -48,12 +94,39 @@
        try {
            Long userId = userContextUtil.getCurrentUserId();
            logger.debug("进入userProfile,解析到用户ID: {}", userId);
            // 如果是匿名用户,返回基本的用户档案
            if (userId == null) {
                throw new RuntimeException("用户未登录");
                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<User> 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<String> roles = new ArrayList<>();
            
@@ -62,8 +135,13 @@
            Employee employee = employeeService.findByUserId(userId);
            logger.debug("员工查询结果: {}", employee != null ? ("id=" + employee.getId() + ", name=" + employee.getName()) : "无");
            if (employee != null) {
                profile.setName(employee.getName());
                profile.setPhone(employee.getPhone());
                // 如果员工信息存在,则覆盖基本信息
                if (employee.getName() != null) {
                    profile.setName(employee.getName());
                }
                if (employee.getPhone() != null) {
                    profile.setPhone(employee.getPhone());
                }
                roles.add("EMPLOYEE");
                
                EmployeeInfo employeeInfo = new EmployeeInfo(
@@ -80,8 +158,11 @@
            Judge judge = judgeService.findByUserId(userId);
            logger.debug("评委查询结果: {}", judge != null ? ("id=" + judge.getId() + ", name=" + judge.getName()) : "无");
            if (judge != null) {
                if (profile.getName() == 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");
@@ -101,10 +182,14 @@
            Player player = playerService.findByUserId(userId);
            logger.debug("学员查询结果: {}", player != null ? ("id=" + player.getId() + ", name=" + player.getName()) : "无");
            if (player != null) {
                if (profile.getName() == null) {
                // 如果学员信息存在且基本信息为空,则设置学员信息
                if (profile.getName() == null && player.getName() != null) {
                    profile.setName(player.getName());
                    profile.setPhone(player.getPhone());
                }
                // 不再从Player获取phone信息,phone统一从User实体获取
                // if (profile.getPhone() == null && player.getPhone() != null) {
                //     profile.setPhone(player.getPhone());
                // }
                roles.add("PLAYER");
                
                PlayerInfo playerInfo = new PlayerInfo(
@@ -117,7 +202,42 @@
            }
            
            profile.setRoles(roles);
            logger.debug("userProfile构建完成,roles={}, name={}, phone={}", roles, profile.getName(), profile.getPhone());
            // 确定主要角色类型(优先级: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<Media> 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;
@@ -133,7 +253,7 @@
    @QueryMapping
    public UserStats userStats() {
        try {
            Long userId = UserContextUtil.getCurrentUserId();
            Long userId = userContextUtil.getCurrentUserId();
            if (userId == null) {
                return null;
            }
@@ -157,17 +277,421 @@
    @QueryMapping
    public List<UserRegistration> myRegistrations(@Argument Integer limit) {
        try {
            Long userId = UserContextUtil.getCurrentUserId();
            Long userId = userContextUtil.getCurrentUserId();
            if (userId == null) {
                return new ArrayList<>();
            }
            // 这里应该实现真正的报名记录查询逻辑
            // 暂时返回空列表
            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<Object[]> results = query.getResultList();
            List<UserRegistration> 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) {
            e.printStackTrace();
            logger.error("获取用户报名记录失败", e);
            return new ArrayList<>();
        }
    }
    /**
     * 获取我的项目列表
     */
    @QueryMapping
    public List<UserProject> 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<Object[]> results = query.getResultList();
            List<UserProject> 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<SubmissionMediaResponse> 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<SubmissionMediaResponse> getSubmissionFiles(Long activityPlayerId) {
        try {
            // 查询提交的媒体文件 (target_type = 5 表示活动参与者提交的文件,state = 1 表示有效状态)
            List<Media> medias = mediaRepository.findByTargetTypeAndTargetIdAndState(5, activityPlayerId, 1);
            List<SubmissionMediaResponse> 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<User> 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<User> 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<User> 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<User> 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<User> 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<Media> 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());
        }
    }
}