package com.rongyichuang.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.rongyichuang.config.WechatConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; /** * 微信API服务 */ @Service public class WechatApiService { private static final Logger logger = LoggerFactory.getLogger(WechatApiService.class); @Autowired private WechatConfig wechatConfig; @Autowired private RestTemplate restTemplate; @Autowired private ObjectMapper objectMapper; /** * 通过code获取微信用户的openid和unionid */ public WechatUserInfo getWechatUserInfo(String code) throws JsonProcessingException, JsonMappingException { logger.info("=== 开始调用微信API获取用户信息 ==="); logger.info("请求时间: {}", java.time.LocalDateTime.now()); logger.info("输入code: {}", code); logger.info("code长度: {}", code != null ? code.length() : 0); // 验证微信配置 if (wechatConfig.getAppSecret() == null || wechatConfig.getAppSecret().trim().isEmpty()) { logger.error("❌ 微信小程序AppSecret未正确配置"); logger.error("当前AppSecret值: {}", wechatConfig.getAppSecret()); logger.error("请在application.yml中配置正确的微信小程序AppSecret"); throw new RuntimeException("微信小程序AppSecret未正确配置,请检查application.yml配置"); } try { // 构建请求URL String url = String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", wechatConfig.getApi().getCode2session(), wechatConfig.getAppId(), wechatConfig.getAppSecret(), code); logger.info("微信API配置信息:"); logger.info("- API地址: {}", wechatConfig.getApi().getCode2session()); logger.info("- AppId: {}", wechatConfig.getAppId()); logger.info("- AppSecret: {}***", wechatConfig.getAppSecret() != null ? wechatConfig.getAppSecret().substring(0, Math.min(8, wechatConfig.getAppSecret().length())) : "null"); logger.info("完整请求URL: {}", url.replaceAll("secret=[^&]*", "secret=***")); logger.info("开始发送HTTP请求到微信API..."); long startTime = System.currentTimeMillis(); String response = restTemplate.getForObject(url, String.class); long endTime = System.currentTimeMillis(); logger.info("微信API响应时间: {}ms", endTime - startTime); logger.info("微信API原始响应: {}", response); if (response == null || response.trim().isEmpty()) { logger.error("❌ 微信API返回空响应"); throw new RuntimeException("微信API返回空响应"); } JsonNode jsonNode = objectMapper.readTree(response); logger.info("解析后的JSON节点: {}", jsonNode.toString()); // 检查是否有错误 if (jsonNode.has("errcode")) { int errcode = jsonNode.get("errcode").asInt(); logger.info("微信API返回errcode: {}", errcode); if (errcode != 0) { String errmsg = jsonNode.get("errmsg").asText(); logger.error("❌ 微信API调用失败"); logger.error("错误代码: {}", errcode); logger.error("错误信息: {}", errmsg); logger.error("完整响应: {}", response); throw new RuntimeException("微信API调用失败: " + errmsg + " (errcode: " + errcode + ")"); } } // 提取用户信息 String openid = jsonNode.has("openid") ? jsonNode.get("openid").asText() : null; String sessionKey = jsonNode.has("session_key") ? jsonNode.get("session_key").asText() : null; String unionid = jsonNode.has("unionid") ? jsonNode.get("unionid").asText() : null; logger.info("=== 微信API响应解析结果 ==="); logger.info("openid存在: {}", openid != null); logger.info("openid值: {}", openid); logger.info("sessionKey存在: {}", sessionKey != null); logger.info("sessionKey长度: {}", sessionKey != null ? sessionKey.length() : 0); logger.info("unionid存在: {}", unionid != null); logger.info("unionid值: {}", unionid); if (openid == null || openid.trim().isEmpty()) { logger.error("❌ 微信API响应中缺少openid字段"); logger.error("完整响应: {}", response); throw new RuntimeException("微信API响应中缺少openid字段"); } if (sessionKey == null || sessionKey.trim().isEmpty()) { logger.error("❌ 微信API响应中缺少session_key字段"); logger.error("完整响应: {}", response); throw new RuntimeException("微信API响应中缺少session_key字段"); } logger.info("✅ 成功获取微信用户信息"); logger.info("openid: {}", openid); logger.info("unionid: {}", unionid != null ? unionid : "未提供"); logger.info("sessionKey长度: {}", sessionKey.length()); return new WechatUserInfo(openid, unionid, sessionKey); } catch (Exception e) { logger.error("=== 调用微信API发生异常 ==="); logger.error("异常时间: {}", java.time.LocalDateTime.now()); logger.error("异常类型: {}", e.getClass().getSimpleName()); logger.error("异常信息: {}", e.getMessage()); logger.error("异常堆栈:", e); if (e instanceof RuntimeException) { throw e; } else { throw new RuntimeException("调用微信API失败: " + e.getMessage(), e); } } } /** * 获取微信小程序access_token */ public String getAccessToken() throws JsonProcessingException { logger.info("=== 开始获取微信小程序access_token ==="); try { // 构建请求URL String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", wechatConfig.getAppId(), wechatConfig.getAppSecret()); logger.info("请求access_token URL: {}", url.replaceAll("secret=[^&]*", "secret=***")); String response = restTemplate.getForObject(url, String.class); logger.info("access_token响应: {}", response); if (response == null || response.trim().isEmpty()) { throw new RuntimeException("获取access_token返回空响应"); } JsonNode jsonNode = objectMapper.readTree(response); // 检查是否有错误 if (jsonNode.has("errcode")) { int errcode = jsonNode.get("errcode").asInt(); if (errcode != 0) { String errmsg = jsonNode.get("errmsg").asText(); logger.error("获取access_token失败: {} (errcode: {})", errmsg, errcode); throw new RuntimeException("获取access_token失败: " + errmsg + " (errcode: " + errcode + ")"); } } String accessToken = jsonNode.get("access_token").asText(); logger.info("✅ 成功获取access_token"); return accessToken; } catch (Exception e) { logger.error("获取access_token发生异常: {}", e.getMessage(), e); throw new RuntimeException("获取access_token失败: " + e.getMessage(), e); } } /** * 使用新版API通过code直接获取手机号 */ public WechatPhoneInfo getPhoneNumberByCode(String code) throws JsonProcessingException { logger.info("=== 开始使用新版API获取手机号 ==="); logger.info("输入code: {}", code); try { // 1. 先获取access_token String accessToken = getAccessToken(); // 2. 构建请求URL String url = wechatConfig.getApi().getGetPhoneNumber() + "?access_token=" + accessToken; logger.info("请求手机号URL: {}", url); // 3. 构建请求体 String requestBody = String.format("{\"code\":\"%s\"}", code); logger.info("请求体: {}", requestBody); // 4. 发送POST请求 org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders(); headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON); org.springframework.http.HttpEntity entity = new org.springframework.http.HttpEntity<>(requestBody, headers); String response = restTemplate.postForObject(url, entity, String.class); logger.info("手机号API响应: {}", response); if (response == null || response.trim().isEmpty()) { throw new RuntimeException("获取手机号返回空响应"); } JsonNode jsonNode = objectMapper.readTree(response); // 检查是否有错误 if (jsonNode.has("errcode")) { int errcode = jsonNode.get("errcode").asInt(); if (errcode != 0) { String errmsg = jsonNode.get("errmsg").asText(); logger.error("获取手机号失败: {} (errcode: {})", errmsg, errcode); // 根据错误码提供更友好的错误信息 String friendlyMessage = getFriendlyErrorMessage(errcode, errmsg); throw new RuntimeException(friendlyMessage); } } // 解析手机号信息 JsonNode phoneInfo = jsonNode.get("phone_info"); if (phoneInfo == null) { throw new RuntimeException("响应中缺少phone_info字段"); } String phoneNumber = phoneInfo.get("phoneNumber").asText(); String purePhoneNumber = phoneInfo.get("purePhoneNumber").asText(); String countryCode = phoneInfo.get("countryCode").asText(); logger.info("✅ 成功获取手机号: {}", phoneNumber); return new WechatPhoneInfo(phoneNumber, purePhoneNumber, countryCode); } catch (Exception e) { logger.error("获取手机号发生异常: {}", e.getMessage(), e); throw new RuntimeException("获取手机号失败: " + e.getMessage(), e); } } /** * 微信用户信息 */ public static class WechatUserInfo { private String openid; private String unionid; private String sessionKey; public WechatUserInfo(String openid, String unionid, String sessionKey) { this.openid = openid; this.unionid = unionid; this.sessionKey = sessionKey; } public String getOpenid() { return openid; } public String getUnionid() { return unionid; } public String getSessionKey() { return sessionKey; } } /** * 微信手机号信息 */ public static class WechatPhoneInfo { private String phoneNumber; private String purePhoneNumber; private String countryCode; public WechatPhoneInfo(String phoneNumber, String purePhoneNumber, String countryCode) { this.phoneNumber = phoneNumber; this.purePhoneNumber = purePhoneNumber; this.countryCode = countryCode; } public String getPhoneNumber() { return phoneNumber; } public String getPurePhoneNumber() { return purePhoneNumber; } public String getCountryCode() { return countryCode; } } /** * 根据微信API错误码提供友好的错误信息 */ private String getFriendlyErrorMessage(int errcode, String errmsg) { switch (errcode) { case 40001: return "微信授权失败:access_token无效,请重新授权"; case 40003: return "微信授权失败:openid错误,请重新登录"; case 40013: return "微信配置错误:AppID无效,请联系管理员"; case 40029: return "授权码已过期或无效,请重新获取手机号授权"; case 47001: return "请求参数错误,请重试"; case 61023: return "授权码无效或已过期,请重新获取手机号授权"; case 61024: return "授权码已被使用,请重新获取手机号授权"; case 45009: return "接口调用超过限制,请稍后重试"; case 89503: return "服务器IP未在微信白名单中,请联系管理员"; default: return "获取手机号失败:" + errmsg + " (错误码: " + errcode + ")"; } } }