package com.rongyichuang.auth.service; 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; import com.rongyichuang.judge.repository.JudgeRepository; import com.rongyichuang.player.entity.Player; import com.rongyichuang.player.repository.PlayerRepository; import com.rongyichuang.user.entity.User; import com.rongyichuang.user.repository.UserRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; 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; /** * 认证服务类 */ @Service public class AuthService { private static final Logger logger = LoggerFactory.getLogger(AuthService.class); @Autowired private UserRepository userRepository; @Autowired private EmployeeRepository employeeRepository; @Autowired private JudgeRepository judgeRepository; @Autowired private PlayerRepository playerRepository; @Autowired private PasswordEncoder passwordEncoder; @Autowired private JwtUtil jwtUtil; @Autowired private WxLoginRecordService wxLoginRecordService; @Autowired private WechatApiService wechatApiService; /** * 用户登录 */ public LoginResponse login(LoginRequest loginRequest) { logger.info("用户登录尝试,手机号: {}", loginRequest.getPhone()); // 1. 通过手机号查询用户 Optional userOpt = userRepository.findByPhone(loginRequest.getPhone()); if (userOpt.isEmpty()) { logger.warn("用户不存在,手机号: {}", loginRequest.getPhone()); throw new BadCredentialsException("用户不存在"); } User user = userOpt.get(); // 2. 检查密码是否为空 if (user.getPassword() == null || user.getPassword().trim().isEmpty()) { logger.warn("用户密码为空,手机号: {}", loginRequest.getPhone()); throw new BadCredentialsException("密码不正确"); } // 3. 验证密码 if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { logger.warn("密码验证失败,手机号: {}", loginRequest.getPhone()); throw new BadCredentialsException("密码不正确"); } // 4. 查找关联的员工、评委和学员信息 Optional employeeOpt = employeeRepository.findByUserId(user.getId()); Optional judgeOpt = judgeRepository.findByUserId(user.getId()); Optional playerOpt = playerRepository.findByUserId(user.getId()); // 5. 检查是否有权限(必须关联员工、评委或学员中的至少一个) // 注意:Web登录暂时不支持学员角色,只允许员工和评委登录 if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) { logger.warn("用户没有权限,未关联员工或评委,手机号: {}", loginRequest.getPhone()); throw new BadCredentialsException("没有权限"); } // 7. 生成JWT token String token = jwtUtil.generateToken(user.getId(), user.getPhone()); // 8. 确定主要角色类型(优先级:employee > judge > player) String userType; if (employeeOpt.isPresent()) { userType = "employee"; } else if (judgeOpt.isPresent()) { userType = "judge"; } else { userType = "player"; } // 9. 构建用户信息 LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo( user.getId(), user.getName(), user.getPhone(), userType ); // 10. 设置所有关联的角色信息 if (employeeOpt.isPresent()) { Employee employee = employeeOpt.get(); userInfo.setEmployee(new LoginResponse.EmployeeInfo( employee.getId(), employee.getName(), employee.getRoleId(), employee.getDescription() )); logger.info("员工登录成功,ID: {}, 姓名: {}", employee.getId(), employee.getName()); } if (judgeOpt.isPresent()) { Judge judge = judgeOpt.get(); userInfo.setJudge(new LoginResponse.JudgeInfo( judge.getId(), judge.getName(), judge.getTitle(), judge.getCompany(), judge.getDescription() )); if (employeeOpt.isEmpty()) { logger.info("评委登录成功,ID: {}, 姓名: {}", judge.getId(), judge.getName()); } } if (playerOpt.isPresent()) { Player player = playerOpt.get(); userInfo.setPlayer(new LoginResponse.PlayerInfo( player.getId(), player.getName(), player.getPhone(), player.getDescription() )); if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) { logger.info("学员登录成功,ID: {}, 姓名: {}", player.getId(), player.getName()); } } 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 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 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("未找到现有用户,开始创建新用户"); logger.info("新用户信息:"); logger.info("- openid: {}", wxLoginRequest.getWxOpenid()); logger.info("- unionid: {}", wxLoginRequest.getWxUnionid()); logger.info("- phone: {}", wxLoginRequest.getPhone()); try { user = new User(); user.setWxOpenid(wxLoginRequest.getWxOpenid()); user.setWxUnionid(wxLoginRequest.getWxUnionid()); user.setName("微信用户"); // 默认名称,后续可以更新 user.setPhone(wxLoginRequest.getPhone()); // 如果有授权手机号 user = userRepository.save(user); isNewUser = true; logger.info("✅ 成功创建新的微信用户"); logger.info("- 新用户ID: {}", user.getId()); logger.info("- 新用户姓名: {}", user.getName()); logger.info("- 新用户手机号: {}", user.getPhone()); } catch (Exception e) { logger.error("❌ 创建新用户失败"); logger.error("异常信息: {}", e.getMessage()); logger.error("异常堆栈:", e); throw new RuntimeException("创建新用户失败: " + e.getMessage(), e); } } // 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 employeeOpt = Optional.empty(); Optional judgeOpt = Optional.empty(); Optional 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: 设置角色详细信息"); 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()); } /** * 解密微信手机号 */ 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 "手机号解密失败,请重新获取手机号授权"; } }