java对接微信小程序登录

我们使用的是第二种方法 只需要code和phoneCode就可以

前端传过来的入参

后端定义实体类接收入参

package com.boboboom.pro.api.system.user.param;

import lombok.Data;

import java.io.Serializable;

@Data
public class LoginMobileParam implements Serializable {

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 用户昵称
     */
    private String nickName;

    /**
     * 用户头像
     */
    private String avatar;

    /**
     * 用户手机号
     */
    private String phone;


    private String phoneCode;

    /**
     * 微信小程序端获取的openid
     */
    private String openid;

    /**
     * 微信登录临时凭证code,前端调用wx.login()获取
     * 用于调用微信接口换取session_key和openid
     * 有效期5分钟,且只能使用一次
     */
    private String code;

    /**
     * 加密的用户手机号数据
     * 前端调用wx.getPhoneNumber()获取
     * 需要使用session_key和iv进行解密
     */
    private String encryptedData;

    /**
     * 加密算法的初始向量
     * 与encryptedData一起由前端wx.getPhoneNumber()返回
     * 用于解密encryptedData
     */
    private String iv;

    private String clientIp; // 客户端ip

    private String registerCode; //推荐码人邀请码

    private String token; //退出登录时的token



}

定义HttpUtils工具类

package com.boboboom.pro.module.system.sysuser.util;

import com.alibaba.fastjson2.JSONObject;
import com.boboboom.pro.common.core.exception.ServerException;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class HttpUtils {

    /**
     * 发送HTTP GET请求
     */
    public static JSONObject sendGetRequest(String url) {
        try {
            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);

            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                throw new ServerException("GET请求失败,状态码: " + conn.getResponseCode());
            }

            // 读取响应
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                return JSONObject.parseObject(response.toString());
            }
        } catch (Exception e) {
            throw new ServerException("网络请求失败,请稍后重试");
        }
    }

    /**
     * 发送HTTP POST请求(用于手机号API)
     */
    public static JSONObject sendPostRequest(String url, String requestBody) {
        try {
            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            conn.setDoOutput(true);
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);

            // 写入请求体
            try (OutputStream os = conn.getOutputStream()) {
                byte[] data = requestBody.getBytes(StandardCharsets.UTF_8);
                os.write(data, 0, data.length);
            }

            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                throw new ServerException("POST请求失败,状态码: " + conn.getResponseCode());
            }

            // 读取响应
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                return JSONObject.parseObject(response.toString());
            }
        } catch (Exception e) {
            throw new ServerException("网络请求失败,请稍后重试");
        }
    }
}

Controller省略 直接到Service

package com.boboboom.system.test;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.boboboom.pro.api.system.user.param.LoginMobileParam;
import com.boboboom.pro.api.system.user.result.LoginMobileResult;
import com.boboboom.pro.common.core.constant.RedisKeyConstant;
import com.boboboom.pro.common.core.exception.ServerException;
import com.boboboom.pro.common.core.util.RedisKeyUtil;
import com.boboboom.pro.common.model.JwtUserInfo;
import com.boboboom.pro.common.model.UserSourceEnum;
import com.boboboom.pro.common.redis.util.RedisUtil;
import com.boboboom.pro.module.system.constant.MobileConsent;
import com.boboboom.pro.module.system.sysuser.util.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

import java.util.concurrent.TimeUnit;

@Slf4j
public class Test1 {
    // 获取微信接口调用凭证access_token
    private static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    // 通过code获取openid
    private static final String JSCODE2SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";
    // 通过手机号授权phoneCode获取明文手机号
    private static final String GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
   //每次获取token会导致上一个token失效(弃用)
   // private static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";

    // 获取稳定版接口调用凭据不会强制刷新token(推荐使用)
    private static final String POST_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token";
    @Value("${wechat.appid}")
    private String appId;

    @Value("${wechat.secret}")
    private String appSecret;


    public LoginMobileResult loginForMobile(LoginMobileParam param) {
        log.info("小程序登录入参: {}", JSONUtil.toJsonStr(param));
        log.info("开始处理移动端登录,code:{}", param.getCode());

        // 1. 获取微信openid
        String openid = getOpenidByCode(param.getCode());
        param.setOpenid(openid);
        log.info("获取到openid:{}", openid);


        // 2. 获取手机号
        String phone = getPhoneNumberByCode(param.getPhoneCode());
        param.setPhone(phone);
        log.info("获取到手机号:{}", phone);

        // 3. 通过phone查询用户是否存在 如果存在生成登录结果(含JWT令牌生成和Redis存储)
//        LoginMobileResult existingResult = getLoginResultByPhone(phone);
//        if (existingResult != null) {
//            return existingResult;
//        }

        // 4. 处理新用户
//        Long userId = handleUserLogin(param);
//        LoginMobileResult result = generateLoginResult(userId, phone, param.getNickName());
//        log.info("用户登录处理完成,userId:{}", userId);
        return null;
    }


    // ------------------------------ 私有工具方法 ------------------------------


    private LoginMobileResult generateLoginResult(Long userId, String userName, String nickName) {
        // 生成JWT
        long expireTime = System.currentTimeMillis() + MobileConsent.TOKEN_EXPIRETIME_VERIFIED;
        JwtUserInfo jwtUserInfo = new JwtUserInfo();
        jwtUserInfo.setUserId(userId);
        jwtUserInfo.setUserName(userName);
        jwtUserInfo.setNickName(nickName);
        jwtUserInfo.setSource(UserSourceEnum.MOBILE.getCode());
        //通过JWT工具生成令牌
//        String token = jwtTokenUtil.generateToken(jwtUserInfo, expireTime);
        String token = "";
        // 存储到Redis
        RedisUtil.setEx(RedisKeyUtil.getMobileTokenKey(token), jwtUserInfo,
                MobileConsent.TOKEN_EXPIRETIME_VERIFIED, TimeUnit.MILLISECONDS);

        // 构建结果
        LoginMobileResult result = new LoginMobileResult();
        result.setUserId(userId);
        result.setToken(token);
        return result;
    }


    /**
     * 通过code获取openid
     */
    private String getOpenidByCode(String code) {
        String param = String.format("appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
                appId, appSecret, code);
        JSONObject result = HttpUtils.sendGetRequest(JSCODE2SESSION_URL + "?" + param);
        log.info("获取openid响应:{}", result);

        if (result.containsKey("errcode") && result.getIntValue("errcode") != 0) {
            throw new ServerException("登录失败: " + result.getString("errmsg"));
        }
        return result.getString("openid");
    }


    /**
     * 通过手机号授权phoneCode获取明文手机号
     */
    private String getPhoneNumberByCode(String phoneCode) {
        try {
            String accessToken = getAccessToken();
            String url = GET_PHONE_NUMBER_URL + "?access_token=" + accessToken;
            String requestBody = "{\"code\":\"" + phoneCode + "\"}";

            JSONObject result = HttpUtils.sendPostRequest(url, requestBody);
            log.info("获取手机号响应:{}", result);

            int errCode = result.getIntValue("errcode");
            if (errCode != 0) {
                log.error("手机号获取失败: errcode={}, errmsg={}", errCode, result.getString("errmsg"));
                throw new ServerException("获取手机号失败(" + result.getString("errmsg") + ")");
            }

            JSONObject phoneInfo = result.getJSONObject("phone_info");
            return phoneInfo.getString("purePhoneNumber");
        } catch (Exception e) {
            log.error("获取手机号异常", e);
            throw new ServerException("获取手机号失败,请重试");
        }
    }


  /**
     * 获取微信接口调用凭证access_token
     * access_token默认有效时间为2小时 (2小时后会自动失效,需要重新获取)
     * 微信官方参考文档:https://developers.weixin.qq.com/doc/service/api/
     *
     */
    private String getAccessToken() {
//        // 生成带 appId 的缓存键(格式:MOBILE:WECHAT:TOKEN:wx123456)
//        String redisKey = RedisKeyConstant.MOBILE_WECHAT_TOKEN_KEY + ":" + appId;
//        // 优先从缓存获取
//        Object cachedToken = RedisUtil.get(redisKey);
//        if (cachedToken != null) {
//            return cachedToken.toString();
//        }

        try {
            // 稳定版接口使用POST方式,参数放在请求体
            String requestBody = "{\"grant_type\":\"client_credential\"," +
                    "\"appid\":\"" + appId + "\"," +
                    "\"secret\":\"" + appSecret + "\"}";
            JSONObject result = HttpUtils.sendPostRequest(POST_ACCESS_TOKEN_URL, requestBody);
            log.info("获取access_token响应:{}", result);

            //获取接口调用凭据旧版本 如果测试环境和生产用的是同一个appId 会导致上一个token失效 (弃用 如果使用这个url来获取token需要自己缓存到redis 设置提前200秒过期) 
//            String url = GET_ACCESS_TOKEN_URL + "?grant_type=client_credential" +
//                    "&appid=" + appId + "&secret=" + appSecret;
//            JSONObject result = HttpUtils.sendGetRequest(url);
//            log.info("获取access_token响应:{}", result);

            int errCode = result.getIntValue("errcode");
            if (errCode != 0) {
                throw new ServerException("获取access_token失败: " + result.getString("errmsg"));
            }
            String accessToken = result.getString("access_token");
            int expiresIn = result.getIntValue("expires_in");
            log.info("access_token获取成功{},有效期{}秒", accessToken, expiresIn);

//            String accessToken = result.getString("access_token");
//            int expiresIn = result.getIntValue("expires_in") - 200;
//            int expiresIn = result.getIntValue("expires_in") - 200; // 获取到的token提前200秒过期
//            RedisUtil.setEx(redisKey, accessToken, expiresIn);

            log.info("access_token获取成功,有效期{}秒", expiresIn);
            return accessToken;
        } catch (Exception e) {
            log.error("获取access_token异常", e);
            throw new ServerException("系统接口调用失败,请稍后重试");
        }
    }


}

posted @ 2025-06-19 20:12  难忘是想起  阅读(0)  评论(0)    收藏  举报  来源