![]()
我们使用的是第二种方法 只需要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("系统接口调用失败,请稍后重试");
}
}
}