Explorar el Código

feat(wechat): 完善微信登录与手机号获取功能

- 禁用 WeChat Mock 模式并更新正式 appid 和 secret
- 增强 code2Session 接口日志记录与错误处理
- 优化 getUserPhoneByCode 方法的健壮性与调试信息
- 添加详细的微信接口响应解析与异常提示
- 统一处理 HTTP 请求中的 code 参数校验
- 增加对微信返回错误码的分类日志输出
- 补充 access_token 获取失败时的错误记录
- 提升手机号获取逻辑中对返回结构的兼容性判断
李思佳 hace 1 mes
padre
commit
cab8dc880b

+ 121 - 15
service/login/src/main/java/com/zhentao/service/impl/WeChatServiceImpl.java

@@ -42,14 +42,22 @@ public class WeChatServiceImpl implements WeChatService {
 
     @Override
     public Map<String, Object> code2Session(String code) {
+        log.info("=== 开始调用微信 code2Session ===");
+        log.info("接收到的 code: {}", code);
+        log.info("code 长度: {}", code != null ? code.length() : 0);
+        log.info("Mock 模式: {}", mockEnabled);
+        
         Map<String, Object> result = new HashMap<>();
         if (code == null || code.trim().isEmpty()) {
+            log.error("❌ code 为空");
             result.put("errcode", 400);
             result.put("errmsg", "code 不能为空");
             return result;
         }
+        
         // 开发环境 Mock:无需真实 appid/secret 与外网请求
         if (mockEnabled) {
+            log.info("✅ 使用 Mock 模式");
             Map<String, Object> mock = new HashMap<>();
             mock.put("errcode", 0);
             mock.put("openid", "mock_openid_" + code.trim());
@@ -57,29 +65,74 @@ public class WeChatServiceImpl implements WeChatService {
             mock.put("unionid", null);
             return mock;
         }
+        
         if (appId == null || appId.isEmpty() || secret == null || secret.isEmpty()) {
+            log.error("❌ 未配置 appid 或 secret");
+            log.error("appId: {}", appId != null ? (appId.isEmpty() ? "空字符串" : "已配置") : "null");
+            log.error("secret: {}", secret != null ? (secret.isEmpty() ? "空字符串" : "已配置") : "null");
             result.put("errcode", 500);
             result.put("errmsg", "未配置 wechat.appid/secret");
             return result;
         }
+        
+        log.info("✅ 配置检查通过");
+        log.info("appId: {}", appId);
+        log.info("secret: {}...", secret.substring(0, Math.min(8, secret.length())));
+        
         String url = String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
-                jscode2sessionUrl, appId, secret, code);
+                jscode2sessionUrl, appId, secret, code.trim());
+        log.info("请求URL: {}", url.replace(secret, "***"));
+        
         try (CloseableHttpClient client = HttpClients.createDefault()) {
             HttpGet get = new HttpGet(url);
+            log.info("发送HTTP请求到微信服务器...");
+            
             try (CloseableHttpResponse resp = client.execute(get)) {
+                int statusCode = resp.getStatusLine().getStatusCode();
+                log.info("HTTP状态码: {}", statusCode);
+                
                 String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
+                log.info("微信返回原始响应: {}", body);
+                
                 if (body == null || body.isEmpty()) {
+                    log.error("❌ 微信返回空响应");
                     Map<String, Object> err = new HashMap<>();
                     err.put("errcode", 500);
                     err.put("errmsg", "微信返回空响应");
                     return err;
                 }
+                
                 ObjectMapper mapper = new ObjectMapper();
                 Map<String, Object> parsed = mapper.readValue(body, new TypeReference<Map<String, Object>>(){});
+                
+                // 检查是否有错误
+                if (parsed.containsKey("errcode")) {
+                    Integer errcode = (Integer) parsed.get("errcode");
+                    if (errcode != 0) {
+                        String errmsg = (String) parsed.get("errmsg");
+                        log.error("❌ 微信接口返回错误");
+                        log.error("错误码: {}", errcode);
+                        log.error("错误信息: {}", errmsg);
+                        
+                        // 针对常见错误给出提示
+                        if (errcode == 40029) {
+                            log.error("提示: code 无效,可能原因:");
+                            log.error("  1. code 已被使用过(每个 code 只能使用一次)");
+                            log.error("  2. code 已过期(5分钟有效期)");
+                            log.error("  3. code 与 appid 不匹配");
+                        } else if (errcode == 40163) {
+                            log.error("提示: code 已被使用");
+                        }
+                    } else {
+                        log.info("✅ 微信接口调用成功");
+                        log.info("openid: {}", parsed.get("openid"));
+                    }
+                }
+                
                 return parsed != null ? parsed : new HashMap<>();
             }
         } catch (Exception e) {
-            log.error("调用 jscode2session 失败", e);
+            log.error("❌ 调用 jscode2session 异常", e);
             Map<String, Object> err = new HashMap<>();
             err.put("errcode", 500);
             err.put("errmsg", "调用 jscode2session 异常:" + e.getMessage());
@@ -122,38 +175,91 @@ public class WeChatServiceImpl implements WeChatService {
 
     @Override
     public String getUserPhoneByCode(String code) {
+        log.info("=== 开始获取微信手机号 ===");
+        log.info("接收到的 phoneCode: {}", code);
+        log.info("Mock 模式: {}", mockEnabled);
+        
         // 开发环境 Mock:返回演示手机号
         if (mockEnabled) {
+            log.info("✅ 使用 Mock 模式,返回测试手机号");
             return "13800138000";
         }
-        if (code == null || code.trim().isEmpty()) return null;
+        
+        if (code == null || code.trim().isEmpty()) {
+            log.error("❌ phoneCode 为空");
+            return null;
+        }
+        
+        log.info("获取 access_token...");
         String accessToken = getAccessToken();
-        if (accessToken == null) return null;
+        if (accessToken == null) {
+            log.error("❌ 获取 access_token 失败");
+            return null;
+        }
+        log.info("✅ access_token 获取成功: {}...", accessToken.substring(0, Math.min(20, accessToken.length())));
+        
         String url = String.format("%s?access_token=%s", getPhoneUrl, accessToken);
+        log.info("请求URL: {}", url.replace(accessToken, "***"));
+        
         try (CloseableHttpClient client = HttpClients.createDefault()) {
             // 微信该接口要求 POST JSON:{"code":"xxx"}
             org.apache.http.client.methods.HttpPost post = new org.apache.http.client.methods.HttpPost(url);
             post.setHeader("Content-Type", "application/json;charset=UTF-8");
-            String payload = "{\"code\":\"" + code + "\"}";
+            String payload = "{\"code\":\"" + code.trim() + "\"}";
+            log.info("请求体: {}", payload);
             post.setEntity(new org.apache.http.entity.StringEntity(payload, StandardCharsets.UTF_8));
+            
+            log.info("发送HTTP POST请求到微信服务器...");
             try (CloseableHttpResponse resp = client.execute(post)) {
+                int statusCode = resp.getStatusLine().getStatusCode();
+                log.info("HTTP状态码: {}", statusCode);
+                
                 String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
-                if (body == null || body.isEmpty()) return null;
+                log.info("微信返回原始响应: {}", body);
+                
+                if (body == null || body.isEmpty()) {
+                    log.error("❌ 微信返回空响应");
+                    return null;
+                }
+                
                 ObjectMapper mapper = new ObjectMapper();
                 Map<String, Object> parsed = mapper.readValue(body, new TypeReference<Map<String, Object>>(){});
-                // 期望结构:{"code":0,"data":{"phoneNumber":"xxxx","purePhoneNumber":"xxxx"}}
-                Object codeField = parsed.get("code");
-                if (codeField instanceof Number && ((Number) codeField).intValue() == 0) {
-                    Object data = parsed.get("data");
-                    if (data instanceof Map) {
-                        Object phone = ((Map) data).get("phoneNumber");
-                        if (phone == null) phone = ((Map) data).get("purePhoneNumber");
-                        return phone != null ? String.valueOf(phone) : null;
+                
+                // 检查错误码
+                Object errcode = parsed.get("errcode");
+                if (errcode != null) {
+                    int errcodeInt = ((Number) errcode).intValue();
+                    if (errcodeInt != 0) {
+                        String errmsg = parsed.get("errmsg") != null ? String.valueOf(parsed.get("errmsg")) : "未知错误";
+                        log.error("❌ 微信接口返回错误");
+                        log.error("错误码: {}", errcodeInt);
+                        log.error("错误信息: {}", errmsg);
+                        return null;
+                    }
+                }
+                
+                // 微信返回结构:{"errcode":0,"errmsg":"ok","phone_info":{"phoneNumber":"xxxx","purePhoneNumber":"xxxx","countryCode":"86","watermark":{...}}}
+                Object phoneInfo = parsed.get("phone_info");
+                if (phoneInfo instanceof Map) {
+                    Map<String, Object> phoneMap = (Map<String, Object>) phoneInfo;
+                    Object phone = phoneMap.get("phoneNumber");
+                    if (phone == null) phone = phoneMap.get("purePhoneNumber");
+                    
+                    if (phone != null) {
+                        String phoneStr = String.valueOf(phone);
+                        log.info("✅ 成功获取手机号: {}", phoneStr);
+                        return phoneStr;
+                    } else {
+                        log.error("❌ phone_info 中没有 phoneNumber 或 purePhoneNumber");
+                        log.error("phone_info 内容: {}", phoneMap);
                     }
+                } else {
+                    log.error("❌ 响应中没有 phone_info 字段或格式错误");
+                    log.error("响应内容: {}", parsed);
                 }
             }
         } catch (Exception e) {
-            log.error("通过 code 获取手机号失败", e);
+            log.error("❌ 通过 code 获取手机号异常", e);
         }
         return null;
     }

+ 3 - 3
service/login/src/main/resources/application.yml

@@ -62,9 +62,9 @@ mybatis-plus:
 # --- WeChat 集成配置 ---
 wechat:
   mock:
-    enabled: true   # 暂时启用 Mock,避免未提供 secret 导致 502
-  appid: wx8454588818fa7b30
-  secret: ${WECHAT_SECRET:}  # 出于安全,默认从环境变量注入,未提供则为空
+    enabled: false   # 暂时启用 Mock,避免未提供 secret 导致 502
+  appid: wx3e90d662a801266e
+  secret: d82ce405f04a47de14382bef4180239d  # 出于安全,默认从环境变量注入,未提供则为空
 
 # --- MinIO 配置 ---
 minio: