backend/src/main/java/com/rongyichuang/auth/service/AuthService.java
@@ -2,7 +2,15 @@
import com.rongyichuang.auth.dto.LoginRequest;
import com.rongyichuang.auth.dto.LoginResponse;
import com.rongyichuang.auth.dto.PhoneDecryptResponse;
import com.rongyichuang.auth.dto.WxLoginRequest;
import com.rongyichuang.auth.dto.WxLoginResponse;
import com.rongyichuang.auth.entity.WxLoginRecord;
import com.rongyichuang.auth.util.JwtUtil;
import com.rongyichuang.service.WechatApiService;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.rongyichuang.employee.entity.Employee;
import com.rongyichuang.employee.repository.EmployeeRepository;
import com.rongyichuang.judge.entity.Judge;
@@ -11,14 +19,24 @@
import com.rongyichuang.player.repository.PlayerRepository;
import com.rongyichuang.user.entity.User;
import com.rongyichuang.user.repository.UserRepository;
import com.rongyichuang.common.entity.Media;
import com.rongyichuang.common.repository.MediaRepository;
import com.rongyichuang.common.enums.MediaTargetType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
 * 认证服务类
@@ -45,6 +63,18 @@
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private WxLoginRecordService wxLoginRecordService;
    @Autowired
    private WechatApiService wechatApiService;
    @Autowired
    private MediaRepository mediaRepository;
    @Value("${app.base-url}")
    private String baseUrl;
    /**
     * 用户登录
@@ -85,10 +115,10 @@
            throw new BadCredentialsException("没有权限");
        }
        // 6. 生成JWT token
        // 7. 生成JWT token
        String token = jwtUtil.generateToken(user.getId(), user.getPhone());
        // 7. 确定主要角色类型(优先级:employee > judge > player)
        // 8. 确定主要角色类型(优先级:employee > judge > player)
        String userType;
        if (employeeOpt.isPresent()) {
            userType = "employee";
@@ -98,7 +128,7 @@
            userType = "player";
        }
        // 8. 构建用户信息
        // 9. 构建用户信息
        LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo(
                user.getId(),
                user.getName(),
@@ -106,7 +136,7 @@
                userType
        );
        // 9. 设置所有关联的角色信息
        // 10. 设置所有关联的角色信息
        if (employeeOpt.isPresent()) {
            Employee employee = employeeOpt.get();
            userInfo.setEmployee(new LoginResponse.EmployeeInfo(
@@ -138,8 +168,7 @@
                    player.getId(),
                    player.getName(),
                    player.getPhone(),
                    player.getDescription(),
                    player.getAuditState()
                    player.getDescription()
            ));
            if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) {
                logger.info("学员登录成功,ID: {}, 姓名: {}", player.getId(), player.getName());
@@ -148,4 +177,573 @@
        return new LoginResponse(token, userInfo);
    }
    /**
     * 微信登录
     */
    public WxLoginResponse wxLogin(WxLoginRequest wxLoginRequest) throws JsonProcessingException, JsonMappingException {
        logger.info("=== 开始微信登录流程 ===");
        logger.info("登录时间: {}", java.time.LocalDateTime.now());
        logger.info("请求参数详情:");
        logger.info("- code: {}", wxLoginRequest.getCode());
        logger.info("- code长度: {}", wxLoginRequest.getCode() != null ? wxLoginRequest.getCode().length() : 0);
        logger.info("- wxOpenid: {}", wxLoginRequest.getWxOpenid());
        logger.info("- wxUnionid: {}", wxLoginRequest.getWxUnionid());
        logger.info("- phone: {}", wxLoginRequest.getPhone());
        logger.info("- loginIp: {}", wxLoginRequest.getLoginIp());
        logger.info("- deviceInfo: {}", wxLoginRequest.getDeviceInfo());
        logger.info("- phoneAuthorized: {}", wxLoginRequest.getPhoneAuthorized());
        // 1. 通过code调用微信API获取openid和unionid
        WechatApiService.WechatUserInfo wechatUserInfo = null;
        if (wxLoginRequest.getCode() != null && !wxLoginRequest.getCode().trim().isEmpty()) {
            logger.info("步骤1: 调用微信API获取用户信息");
            logger.info("使用code: {}", wxLoginRequest.getCode());
            try {
                wechatUserInfo = wechatApiService.getWechatUserInfo(wxLoginRequest.getCode());
                // 将从微信API获取的信息设置到请求对象中
                wxLoginRequest.setWxOpenid(wechatUserInfo.getOpenid());
                wxLoginRequest.setWxUnionid(wechatUserInfo.getUnionid());
                wxLoginRequest.setSessionKey(wechatUserInfo.getSessionKey());
                logger.info("✅ 从微信API获取用户信息成功");
                logger.info("- openid: {}", wechatUserInfo.getOpenid());
                logger.info("- unionid: {}", wechatUserInfo.getUnionid());
                logger.info("- sessionKey长度: {}", wechatUserInfo.getSessionKey() != null ? wechatUserInfo.getSessionKey().length() : 0);
            } catch (Exception e) {
                logger.error("❌ 调用微信API失败");
                logger.error("异常信息: {}", e.getMessage());
                logger.error("异常堆栈:", e);
                throw e;
            }
        } else {
            logger.warn("⚠️ 微信登录code为空,将使用请求中的openid和unionid");
            logger.info("请求中的openid: {}", wxLoginRequest.getWxOpenid());
            logger.info("请求中的unionid: {}", wxLoginRequest.getWxUnionid());
        }
        User user = null;
        boolean isNewUser = false;
        logger.info("步骤2: 查找或创建用户");
        // 2. 首先尝试通过openid查找用户
        if (wxLoginRequest.getWxOpenid() != null && !wxLoginRequest.getWxOpenid().trim().isEmpty()) {
            logger.info("尝试通过openid查找用户: {}", wxLoginRequest.getWxOpenid());
            try {
                Optional<User> userOpt = userRepository.findByWxOpenid(wxLoginRequest.getWxOpenid());
                if (userOpt.isPresent()) {
                    user = userOpt.get();
                    logger.info("✅ 通过openid找到用户");
                    logger.info("- 用户ID: {}", user.getId());
                    logger.info("- 用户姓名: {}", user.getName());
                    logger.info("- 用户手机号: {}", user.getPhone());
                    logger.info("- 用户unionid: {}", user.getWxUnionid());
                } else {
                    logger.info("❌ 通过openid未找到用户");
                }
            } catch (Exception e) {
                logger.error("❌ 通过openid查找用户时发生异常: {}", e.getMessage());
                logger.error("异常堆栈:", e);
            }
        } else {
            logger.warn("⚠️ openid为空,跳过openid查找");
        }
        // 3. 如果通过openid没找到,尝试通过unionid查找
        if (user == null && wxLoginRequest.getWxUnionid() != null && !wxLoginRequest.getWxUnionid().trim().isEmpty()) {
            logger.info("尝试通过unionid查找用户: {}", wxLoginRequest.getWxUnionid());
            try {
                Optional<User> userOpt = userRepository.findByWxUnionid(wxLoginRequest.getWxUnionid());
                if (userOpt.isPresent()) {
                    user = userOpt.get();
                    logger.info("✅ 通过unionid找到用户");
                    logger.info("- 用户ID: {}", user.getId());
                    logger.info("- 用户姓名: {}", user.getName());
                    logger.info("- 用户手机号: {}", user.getPhone());
                    logger.info("- 用户openid: {}", user.getWxOpenid());
                    // 更新用户的openid(如果之前没有)
                    if (user.getWxOpenid() == null || user.getWxOpenid().trim().isEmpty()) {
                        logger.info("用户openid为空,需要更新");
                        user.setWxOpenid(wxLoginRequest.getWxOpenid());
                        try {
                            userRepository.save(user);
                            logger.info("✅ 成功更新用户openid,用户ID: {}", user.getId());
                        } catch (Exception e) {
                            logger.error("❌ 更新用户openid失败: {}", e.getMessage());
                            logger.error("异常堆栈:", e);
                        }
                    } else {
                        logger.info("用户openid已存在,无需更新");
                    }
                } else {
                    logger.info("❌ 通过unionid未找到用户");
                }
            } catch (Exception e) {
                logger.error("❌ 通过unionid查找用户时发生异常: {}", e.getMessage());
                logger.error("异常堆栈:", e);
            }
        } else {
            logger.warn("⚠️ unionid为空或用户已找到,跳过unionid查找");
        }
        // 4. 如果都没找到用户,不创建新用户,只记录登录信息
        if (user == null) {
            logger.info("未找到现有用户,普通访问用户不创建user记录,只记录登录信息");
            logger.info("访问用户信息:");
            logger.info("- openid: {}", wxLoginRequest.getWxOpenid());
            logger.info("- unionid: {}", wxLoginRequest.getWxUnionid());
            logger.info("- phone: {}", wxLoginRequest.getPhone());
            // 记录登录信息到t_login_record,但不创建用户
            logger.info("步骤3: 记录访问用户的登录信息");
            WxLoginRecord loginRecord = null;
            try {
                loginRecord = wxLoginRecordService.createLoginRecord(
                        wxLoginRequest.getWxOpenid(),
                        wxLoginRequest.getWxUnionid(),
                        null, // 没有用户ID
                        wxLoginRequest.getLoginIp(),
                        wxLoginRequest.getDeviceInfo(),
                        wxLoginRequest.getSessionKey(),
                        wxLoginRequest.getPhoneAuthorized()
                );
                logger.info("✅ 成功创建访问用户登录记录,记录ID: {}", loginRecord.getId());
            } catch (Exception e) {
                logger.error("❌ 创建登录记录失败: {}", e.getMessage());
                logger.error("异常堆栈:", e);
                throw new RuntimeException("创建登录记录失败: " + e.getMessage(), e);
            }
            // 返回访问用户的响应(无用户信息,无token)
            WxLoginResponse response = new WxLoginResponse();
            response.setSuccess(true);
            response.setMessage("访问成功");
            response.setSessionKey(wxLoginRequest.getSessionKey());
            response.setIsNewUser(false);
            response.setHasEmployee(false);
            response.setHasJudge(false);
            response.setHasPlayer(false);
            logger.info("=== 微信访问流程完成(无用户创建) ===");
            return response;
        }
        // 5. 记录登录信息到新表
        logger.info("步骤3: 记录登录信息");
        WxLoginRecord loginRecord = null;
        try {
            loginRecord = wxLoginRecordService.createLoginRecord(
                    wxLoginRequest.getWxOpenid(),
                    wxLoginRequest.getWxUnionid(),
                    user.getId(),
                    wxLoginRequest.getLoginIp(),
                    wxLoginRequest.getDeviceInfo(),
                    wxLoginRequest.getSessionKey(),
                    wxLoginRequest.getPhoneAuthorized()
            );
            logger.info("✅ 成功创建登录记录,记录ID: {}", loginRecord.getId());
        } catch (Exception e) {
            logger.error("❌ 创建登录记录失败: {}", e.getMessage());
            logger.error("异常堆栈:", e);
            throw new RuntimeException("创建登录记录失败: " + e.getMessage(), e);
        }
        // 6. 查找关联的员工、评委和学员信息
        logger.info("步骤4: 查找用户关联的角色信息");
        logger.info("查找用户ID: {}", user.getId());
        Optional<Employee> employeeOpt = Optional.empty();
        Optional<Judge> judgeOpt = Optional.empty();
        Optional<Player> playerOpt = Optional.empty();
        try {
            employeeOpt = employeeRepository.findByUserId(user.getId());
            logger.info("员工角色查找结果: {}", employeeOpt.isPresent() ? "找到" : "未找到");
            if (employeeOpt.isPresent()) {
                Employee employee = employeeOpt.get();
                logger.info("- 员工ID: {}", employee.getId());
                logger.info("- 员工姓名: {}", employee.getName());
                logger.info("- 员工角色ID: {}", employee.getRoleId());
            }
        } catch (Exception e) {
            logger.error("❌ 查找员工角色时发生异常: {}", e.getMessage());
            logger.error("异常堆栈:", e);
        }
        try {
            judgeOpt = judgeRepository.findByUserId(user.getId());
            logger.info("评委角色查找结果: {}", judgeOpt.isPresent() ? "找到" : "未找到");
            if (judgeOpt.isPresent()) {
                Judge judge = judgeOpt.get();
                logger.info("- 评委ID: {}", judge.getId());
                logger.info("- 评委姓名: {}", judge.getName());
                logger.info("- 评委职称: {}", judge.getTitle());
                logger.info("- 评委公司: {}", judge.getCompany());
            }
        } catch (Exception e) {
            logger.error("❌ 查找评委角色时发生异常: {}", e.getMessage());
            logger.error("异常堆栈:", e);
        }
        try {
            playerOpt = playerRepository.findByUserId(user.getId());
            logger.info("学员角色查找结果: {}", playerOpt.isPresent() ? "找到" : "未找到");
            if (playerOpt.isPresent()) {
                Player player = playerOpt.get();
                logger.info("- 学员ID: {}", player.getId());
                logger.info("- 学员姓名: {}", player.getName());
                logger.info("- 学员手机号: {}", player.getPhone());
            }
        } catch (Exception e) {
            logger.error("❌ 查找学员角色时发生异常: {}", e.getMessage());
            logger.error("异常堆栈:", e);
        }
        // 6. 生成JWT token
        logger.info("步骤5: 生成JWT token");
        String tokenIdentifier = user.getPhone() != null ? user.getPhone() : user.getWxOpenid();
        logger.info("Token标识符: {}", tokenIdentifier);
        String token = null;
        try {
            token = jwtUtil.generateToken(user.getId(), tokenIdentifier);
            logger.info("✅ 成功生成JWT token,长度: {}", token != null ? token.length() : 0);
        } catch (Exception e) {
            logger.error("❌ 生成JWT token失败: {}", e.getMessage());
            logger.error("异常堆栈:", e);
            throw new RuntimeException("生成JWT token失败: " + e.getMessage(), e);
        }
        // 7. 确定主要角色类型(优先级:employee > judge > player)
        logger.info("步骤6: 确定用户主要角色类型");
        String userType;
        if (employeeOpt.isPresent()) {
            userType = "employee";
            logger.info("用户主要角色: 员工");
        } else if (judgeOpt.isPresent()) {
            userType = "judge";
            logger.info("用户主要角色: 评委");
        } else if (playerOpt.isPresent()) {
            userType = "player";
            logger.info("用户主要角色: 学员");
        } else {
            userType = "user"; // 普通微信用户
            logger.info("用户主要角色: 普通微信用户");
        }
        // 8. 构建用户信息
        logger.info("步骤7: 构建用户信息响应");
        LoginResponse.UserInfo userInfo = null;
        try {
            userInfo = new LoginResponse.UserInfo(
                    user.getId(),
                    user.getName(),
                    user.getPhone(),
                    userType
            );
            logger.info("✅ 成功构建基础用户信息");
            logger.info("- 用户ID: {}", user.getId());
            logger.info("- 用户姓名: {}", user.getName());
            logger.info("- 用户手机号: {}", user.getPhone());
            logger.info("- 用户类型: {}", userType);
        } catch (Exception e) {
            logger.error("❌ 构建基础用户信息失败: {}", e.getMessage());
            logger.error("异常堆栈:", e);
            throw new RuntimeException("构建基础用户信息失败: " + e.getMessage(), e);
        }
        // 9. 获取并设置用户头像
        logger.info("步骤8: 获取用户头像");
        try {
            List<Media> avatarMediaList = mediaRepository.findByTargetTypeAndTargetIdAndState(
                MediaTargetType.USER_AVATAR.getValue(),
                user.getId(),
                1
            );
            if (!avatarMediaList.isEmpty()) {
                Media avatarMedia = avatarMediaList.get(0);
                String fullAvatarUrl = buildFullMediaUrl(avatarMedia.getPath());
                userInfo.setAvatarUrl(fullAvatarUrl);
                logger.info("✅ 找到用户头像: {} -> {}", avatarMedia.getPath(), fullAvatarUrl);
            } else {
                logger.info("用户{}没有找到头像", user.getId());
            }
        } catch (Exception e) {
            logger.error("❌ 获取用户头像失败: {}", e.getMessage());
            logger.error("异常堆栈:", e);
        }
        // 10. 设置所有关联的角色信息
        logger.info("步骤9: 设置角色详细信息");
        if (employeeOpt.isPresent()) {
            try {
                Employee employee = employeeOpt.get();
                userInfo.setEmployee(new LoginResponse.EmployeeInfo(
                        employee.getId(),
                        employee.getName(),
                        employee.getRoleId(),
                        employee.getDescription()
                ));
                logger.info("✅ 设置员工信息成功");
                logger.info("- 员工ID: {}", employee.getId());
                logger.info("- 员工姓名: {}", employee.getName());
                logger.info("- 员工角色ID: {}", employee.getRoleId());
                logger.info("员工微信登录成功,ID: {}, 姓名: {}", employee.getId(), employee.getName());
            } catch (Exception e) {
                logger.error("❌ 设置员工信息失败: {}", e.getMessage());
                logger.error("异常堆栈:", e);
            }
        }
        if (judgeOpt.isPresent()) {
            try {
                Judge judge = judgeOpt.get();
                userInfo.setJudge(new LoginResponse.JudgeInfo(
                        judge.getId(),
                        judge.getName(),
                        judge.getTitle(),
                        judge.getCompany(),
                        judge.getDescription()
                ));
                logger.info("✅ 设置评委信息成功");
                logger.info("- 评委ID: {}", judge.getId());
                logger.info("- 评委姓名: {}", judge.getName());
                logger.info("- 评委职称: {}", judge.getTitle());
                logger.info("- 评委公司: {}", judge.getCompany());
                if (employeeOpt.isEmpty()) {
                    logger.info("评委微信登录成功,ID: {}, 姓名: {}", judge.getId(), judge.getName());
                }
            } catch (Exception e) {
                logger.error("❌ 设置评委信息失败: {}", e.getMessage());
                logger.error("异常堆栈:", e);
            }
        }
        if (playerOpt.isPresent()) {
            try {
                Player player = playerOpt.get();
                userInfo.setPlayer(new LoginResponse.PlayerInfo(
                        player.getId(),
                        player.getName(),
                        player.getPhone(),
                        player.getDescription()
                ));
                logger.info("✅ 设置学员信息成功");
                logger.info("- 学员ID: {}", player.getId());
                logger.info("- 学员姓名: {}", player.getName());
                logger.info("- 学员手机号: {}", player.getPhone());
                if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) {
                    logger.info("学员微信登录成功,ID: {}, 姓名: {}", player.getId(), player.getName());
                }
            } catch (Exception e) {
                logger.error("❌ 设置学员信息失败: {}", e.getMessage());
                logger.error("异常堆栈:", e);
            }
        }
        logger.info("=== 微信登录流程完成 ===");
        logger.info("登录结果:");
        logger.info("- 是否新用户: {}", isNewUser);
        logger.info("- 登录记录ID: {}", loginRecord != null ? loginRecord.getId() : "null");
        logger.info("- Token长度: {}", token != null ? token.length() : 0);
        logger.info("- 用户类型: {}", userType);
        logger.info("完成时间: {}", java.time.LocalDateTime.now());
        return new WxLoginResponse(token, userInfo, isNewUser, loginRecord.getId(), wxLoginRequest.getSessionKey());
    }
    /**
     * 构建完整的媒体URL
     * @param path 媒体路径
     * @return 完整的URL
     */
    private String buildFullMediaUrl(String path) {
        if (path == null || path.trim().isEmpty()) {
            return null;
        }
        // 如果路径已经是完整URL,直接返回
        if (path.startsWith("http://") || path.startsWith("https://")) {
            return path;
        }
        // 构建完整URL
        String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
        String cleanPath = path.startsWith("/") ? path : "/" + path;
        return cleanBaseUrl + cleanPath;
    }
    /**
     * 解密微信手机号
     */
    public PhoneDecryptResponse decryptPhoneNumber(String encryptedData, String iv, String sessionKey) {
        logger.info("=== 开始解密微信手机号 ===");
        logger.info("解密时间: {}", java.time.LocalDateTime.now());
        logger.info("encryptedData长度: {}", encryptedData != null ? encryptedData.length() : 0);
        logger.info("iv长度: {}", iv != null ? iv.length() : 0);
        logger.info("sessionKey长度: {}", sessionKey != null ? sessionKey.length() : 0);
        try {
            // 1. Base64解码
            byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
            byte[] ivBytes = Base64.getDecoder().decode(iv);
            byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
            logger.info("Base64解码成功");
            logger.info("encryptedBytes长度: {}", encryptedBytes.length);
            logger.info("ivBytes长度: {}", ivBytes.length);
            logger.info("sessionKeyBytes长度: {}", sessionKeyBytes.length);
            // 2. AES解密
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec secretKeySpec = new SecretKeySpec(sessionKeyBytes, "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            String decryptedData = new String(decryptedBytes, "UTF-8");
            logger.info("AES解密成功");
            logger.info("解密后数据长度: {}", decryptedData.length());
            logger.info("解密后数据: {}", decryptedData);
            // 3. 解析JSON
            ObjectMapper objectMapper = new ObjectMapper();
            WechatPhoneInfo phoneInfo = objectMapper.readValue(decryptedData, WechatPhoneInfo.class);
            logger.info("JSON解析成功");
            logger.info("手机号: {}", phoneInfo.getPhoneNumber());
            logger.info("纯手机号: {}", phoneInfo.getPurePhoneNumber());
            logger.info("国家代码: {}", phoneInfo.getCountryCode());
            PhoneDecryptResponse response = new PhoneDecryptResponse(
                phoneInfo.getPhoneNumber(),
                phoneInfo.getPurePhoneNumber(),
                phoneInfo.getCountryCode()
            );
            logger.info("✅ 微信手机号解密成功");
            return response;
        } catch (Exception e) {
            logger.error("❌ 微信手机号解密失败", e);
            String friendlyMessage = getDecryptFriendlyErrorMessage(e);
            throw new RuntimeException(friendlyMessage, e);
        }
    }
    /**
     * 使用新版API通过code直接获取手机号
     */
    public PhoneDecryptResponse getPhoneNumberByCode(String code) {
        logger.info("=== 开始使用新版API获取手机号 ===");
        logger.info("获取时间: {}", java.time.LocalDateTime.now());
        logger.info("输入code: {}", code);
        try {
            // 调用微信API服务获取手机号
            WechatApiService.WechatPhoneInfo phoneInfo = wechatApiService.getPhoneNumberByCode(code);
            logger.info("✅ 新版API获取手机号成功");
            logger.info("手机号: {}", phoneInfo.getPhoneNumber());
            logger.info("纯手机号: {}", phoneInfo.getPurePhoneNumber());
            logger.info("国家代码: {}", phoneInfo.getCountryCode());
            PhoneDecryptResponse response = new PhoneDecryptResponse(
                phoneInfo.getPhoneNumber(),
                phoneInfo.getPurePhoneNumber(),
                phoneInfo.getCountryCode()
            );
            return response;
        } catch (Exception e) {
            logger.error("❌ 新版API获取手机号失败", e);
            // 直接传递WechatApiService的友好错误信息
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * 微信手机号信息
     */
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class WechatPhoneInfo {
        private String phoneNumber;
        private String purePhoneNumber;
        private String countryCode;
        public String getPhoneNumber() {
            return phoneNumber;
        }
        public void setPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
        }
        public String getPurePhoneNumber() {
            return purePhoneNumber;
        }
        public void setPurePhoneNumber(String purePhoneNumber) {
            this.purePhoneNumber = purePhoneNumber;
        }
        public String getCountryCode() {
            return countryCode;
        }
        public void setCountryCode(String countryCode) {
            this.countryCode = countryCode;
        }
    }
    /**
     * 根据解密异常提供友好的错误信息
     */
    private String getDecryptFriendlyErrorMessage(Exception e) {
        String errorMessage = e.getMessage();
        if (e instanceof IllegalArgumentException) {
            if (errorMessage.contains("Illegal base64 character")) {
                return "授权数据格式错误,请重新获取手机号授权";
            }
        }
        if (e instanceof javax.crypto.BadPaddingException) {
            return "授权数据已过期或无效,请重新获取手机号授权";
        }
        if (e instanceof javax.crypto.IllegalBlockSizeException) {
            return "授权数据长度错误,请重新获取手机号授权";
        }
        if (e instanceof java.security.InvalidKeyException) {
            return "会话密钥无效,请重新登录后再试";
        }
        if (e instanceof com.fasterxml.jackson.core.JsonParseException) {
            return "授权数据解析失败,请重新获取手机号授权";
        }
        if (errorMessage.contains("sessionKey")) {
            return "会话已过期,请重新登录后再试";
        }
        if (errorMessage.contains("encryptedData")) {
            return "授权数据无效,请重新获取手机号授权";
        }
        return "手机号解密失败,请重新获取手机号授权";
    }
}