feat(wx-login): 更新微信登录配置和优化access_token管理
- 修改application.yml中的激活环境从glt2到ysb2 - 在CreditMpCustomerMapper.xml中增加to_user字段的关键词搜索功能 - 删除WxLoginController中重复的TimeUnit导入 - 实现access_token失效时的自动刷新重试机制 - 使用微信稳定的stable_token接口替代原有token接口 - 添加强制刷新access_token的功能支持 - 优化缓存TTL计算逻辑,确保token在过期前及时刷新 - 改进微信API错误处理和异常信息提示
This commit is contained in:
@@ -42,7 +42,6 @@ import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_WEIXIN;
|
||||
import static com.gxwebsoft.common.core.constants.RedisConstants.ACCESS_TOKEN_KEY;
|
||||
@@ -246,27 +245,30 @@ public class WxLoginController extends BaseController {
|
||||
* @param userParam 需要传微信凭证code
|
||||
*/
|
||||
private String getPhoneByCode(UserParam userParam) {
|
||||
// 获取手机号码
|
||||
String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken();
|
||||
HashMap<String, Object> paramMap = new HashMap<>();
|
||||
if (StrUtil.isBlank(userParam.getCode())) {
|
||||
throw new BusinessException("code不能为空");
|
||||
}
|
||||
paramMap.put("code", userParam.getCode());
|
||||
// 执行post请求
|
||||
|
||||
// access_token 失效/过期时自动刷新并重试一次
|
||||
for (int attempt = 0; attempt < 2; attempt++) {
|
||||
String accessToken = (attempt == 0) ? getAccessToken(false) : getAccessToken(true);
|
||||
String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
|
||||
|
||||
String post = HttpUtil.post(apiUrl, JSON.toJSONString(paramMap));
|
||||
JSONObject json = JSON.parseObject(post);
|
||||
if (json.get("errcode").equals(0)) {
|
||||
|
||||
Integer errcode = json.getInteger("errcode");
|
||||
if (errcode != null && errcode.equals(0)) {
|
||||
JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info"));
|
||||
// 微信用户的手机号码
|
||||
final String phoneNumber = phoneInfo.getString("phoneNumber");
|
||||
// 验证手机号码
|
||||
// if (userParam.getNotVerifyPhone() == null && !Validator.isMobile(phoneNumber)) {
|
||||
// String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
|
||||
// redisTemplate.delete(key);
|
||||
// throw new BusinessException("手机号码格式不正确");
|
||||
// }
|
||||
return phoneNumber;
|
||||
return phoneInfo.getString("phoneNumber");
|
||||
}
|
||||
|
||||
if (errcode != null && (errcode == 40001 || errcode == 42001 || errcode == 40014)) {
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -285,6 +287,10 @@ public class WxLoginController extends BaseController {
|
||||
* <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
return getAccessToken(false);
|
||||
}
|
||||
|
||||
private String getAccessToken(boolean forceRefresh) {
|
||||
Integer tenantId = getTenantId();
|
||||
String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
|
||||
|
||||
@@ -294,9 +300,13 @@ public class WxLoginController extends BaseController {
|
||||
throw new BusinessException("请先配置小程序");
|
||||
}
|
||||
|
||||
if (forceRefresh) {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
// 从缓存获取access_token
|
||||
String value = redisTemplate.opsForValue().get(key);
|
||||
if (value != null) {
|
||||
if (!forceRefresh && value != null) {
|
||||
// 解析access_token
|
||||
JSONObject response = JSON.parseObject(value);
|
||||
String accessToken = response.getString("access_token");
|
||||
@@ -305,23 +315,38 @@ public class WxLoginController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
// 微信获取凭证接口
|
||||
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token";
|
||||
// 组装url参数
|
||||
String url = apiUrl.concat("?grant_type=client_credential")
|
||||
.concat("&appid=").concat(setting.getString("appId"))
|
||||
.concat("&secret=").concat(setting.getString("appSecret"));
|
||||
String appId = setting.getString("appId");
|
||||
String appSecret = setting.getString("appSecret");
|
||||
|
||||
// 执行get请求
|
||||
String result = HttpUtil.get(url);
|
||||
// 解析access_token
|
||||
// 微信稳定版获取凭证接口(避免并发刷新导致旧token失效)
|
||||
String apiUrl = "https://api.weixin.qq.com/cgi-bin/stable_token";
|
||||
JSONObject reqBody = new JSONObject();
|
||||
reqBody.put("grant_type", "client_credential");
|
||||
reqBody.put("appid", appId);
|
||||
reqBody.put("secret", appSecret);
|
||||
reqBody.put("force_refresh", forceRefresh);
|
||||
|
||||
String result = HttpRequest.post(apiUrl)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(reqBody.toJSONString())
|
||||
.execute()
|
||||
.body();
|
||||
JSONObject response = JSON.parseObject(result);
|
||||
if (response.getString("access_token") != null) {
|
||||
// 存入缓存
|
||||
redisTemplate.opsForValue().set(key, result, 7000L, TimeUnit.SECONDS);
|
||||
return response.getString("access_token");
|
||||
|
||||
Integer errcode = response.getInteger("errcode");
|
||||
if (errcode != null && errcode != 0) {
|
||||
throw new BusinessException("获取access_token失败: " + response.getString("errmsg") + " (errcode: " + errcode + ")");
|
||||
}
|
||||
throw new BusinessException("小程序配置不正确");
|
||||
|
||||
String accessToken = response.getString("access_token");
|
||||
Integer expiresIn = response.getInteger("expires_in");
|
||||
if (accessToken != null) {
|
||||
long ttlSeconds = Math.max((expiresIn != null ? expiresIn : 7200) - 300L, 60L);
|
||||
redisTemplate.opsForValue().set(key, result, ttlSeconds, TimeUnit.SECONDS);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
throw new BusinessException("获取access_token失败: " + result);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取微信openId并更新")
|
||||
@@ -576,23 +601,29 @@ public class WxLoginController extends BaseController {
|
||||
throw new IOException("小程序配置不完整,缺少 appId 或 appSecret");
|
||||
}
|
||||
|
||||
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token")
|
||||
.newBuilder()
|
||||
.addQueryParameter("grant_type", "client_credential")
|
||||
.addQueryParameter("appid", appId)
|
||||
.addQueryParameter("secret", appSecret)
|
||||
.build();
|
||||
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/stable_token").newBuilder().build();
|
||||
var root = om.createObjectNode();
|
||||
root.put("grant_type", "client_credential");
|
||||
root.put("appid", appId);
|
||||
root.put("secret", appSecret);
|
||||
root.put("force_refresh", false);
|
||||
|
||||
Request req = new Request.Builder().url(url).get().build();
|
||||
okhttp3.RequestBody reqBody = okhttp3.RequestBody.create(
|
||||
root.toString(), MediaType.parse("application/json; charset=utf-8"));
|
||||
Request req = new Request.Builder().url(url).post(reqBody).build();
|
||||
try (Response resp = http.newCall(req).execute()) {
|
||||
String body = resp.body().string();
|
||||
JsonNode json = om.readTree(body);
|
||||
if (json.has("errcode") && json.get("errcode").asInt() != 0) {
|
||||
throw new IOException("Get access_token failed: " + body);
|
||||
}
|
||||
if (json.has("access_token")) {
|
||||
String token = json.get("access_token").asText();
|
||||
long expiresIn = json.get("expires_in").asInt(7200);
|
||||
|
||||
// 缓存完整的JSON响应,与其他方法保持一致
|
||||
redisUtil.set(key, body, expiresIn, TimeUnit.SECONDS);
|
||||
long ttlSeconds = Math.max(expiresIn - 300L, 60L);
|
||||
redisUtil.set(key, body, ttlSeconds, TimeUnit.SECONDS);
|
||||
tokenExpireEpoch = now + expiresIn;
|
||||
System.out.println("获取新的access_token成功(Local),租户ID: " + tenantId);
|
||||
return token;
|
||||
@@ -789,10 +820,21 @@ public class WxLoginController extends BaseController {
|
||||
String appId = wxConfig.getString("appId");
|
||||
String appSecret = wxConfig.getString("appSecret");
|
||||
|
||||
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
|
||||
String apiUrl = "https://api.weixin.qq.com/cgi-bin/stable_token";
|
||||
System.out.println("调用微信API获取token - 租户ID: " + tenantId + ", AppID: " + (appId != null ? appId.substring(0, Math.min(8, appId.length())) + "..." : "null"));
|
||||
System.out.println("微信API请求URL: " + apiUrl.replaceAll("secret=[^&]*", "secret=***"));
|
||||
String result = HttpUtil.get(apiUrl);
|
||||
System.out.println("微信API请求URL: " + apiUrl);
|
||||
|
||||
JSONObject reqBody = new JSONObject();
|
||||
reqBody.put("grant_type", "client_credential");
|
||||
reqBody.put("appid", appId);
|
||||
reqBody.put("secret", appSecret);
|
||||
reqBody.put("force_refresh", false);
|
||||
|
||||
String result = HttpRequest.post(apiUrl)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(reqBody.toJSONString())
|
||||
.execute()
|
||||
.body();
|
||||
System.out.println("微信API响应: " + result);
|
||||
JSONObject json = JSON.parseObject(result);
|
||||
|
||||
@@ -800,8 +842,8 @@ public class WxLoginController extends BaseController {
|
||||
if (json.containsKey("errcode")) {
|
||||
Integer errcode = json.getInteger("errcode");
|
||||
String errmsg = json.getString("errmsg");
|
||||
if (errcode != null && errcode != 0) {
|
||||
System.err.println("微信API错误 - errcode: " + errcode + ", errmsg: " + errmsg);
|
||||
|
||||
if (errcode == 40125) {
|
||||
throw new RuntimeException("微信AppSecret配置错误,请检查并更新正确的AppSecret");
|
||||
} else if (errcode == 40013) {
|
||||
@@ -810,13 +852,15 @@ public class WxLoginController extends BaseController {
|
||||
throw new RuntimeException("微信API调用失败: " + errmsg + " (errcode: " + errcode + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json.containsKey("access_token")) {
|
||||
String accessToken = json.getString("access_token");
|
||||
Integer expiresIn = json.getInteger("expires_in");
|
||||
|
||||
// 缓存access_token,存储完整JSON响应(与getAccessToken方法保持一致)
|
||||
redisUtil.set(key, result, (long) (expiresIn - 300), TimeUnit.SECONDS);
|
||||
long ttlSeconds = Math.max((expiresIn != null ? expiresIn : 7200) - 300L, 60L);
|
||||
redisUtil.set(key, result, ttlSeconds, TimeUnit.SECONDS);
|
||||
|
||||
System.out.println("获取新的access_token成功,租户ID: " + tenantId);
|
||||
return accessToken;
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
</if>
|
||||
<if test="param.keywords != null">
|
||||
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
OR a.to_user LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
|
||||
@@ -4,7 +4,7 @@ server:
|
||||
# 多环境配置
|
||||
spring:
|
||||
profiles:
|
||||
active: glt2
|
||||
active: ysb2
|
||||
|
||||
application:
|
||||
name: server
|
||||
|
||||
Reference in New Issue
Block a user