Files
shz-backend/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/WeChatMiniProgramApiClient.java

182 lines
6.3 KiB
Java
Raw Normal View History

package com.ruoyi.cms.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 微信小程序服务端接口客户端
*
* <p>该客户端只负责单次远程调用不记录 appSecretaccess token 或微信响应中的敏感内容</p>
*/
@Component
public class WeChatMiniProgramApiClient
{
private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
private static final String UNLIMITED_QR_CODE_URL =
"https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=";
private static final int HTTP_TIMEOUT_MILLIS = 15000;
public AccessTokenResult getAccessToken(String appId, String appSecret)
{
Map<String, Object> params = new HashMap<>();
params.put("grant_type", "client_credential");
params.put("appid", appId);
params.put("secret", appSecret);
String body = HttpUtil.get(ACCESS_TOKEN_URL, params, HTTP_TIMEOUT_MILLIS);
JSONObject json = parseJson(body, "获取微信 access token 失败");
String accessToken = json.getString("access_token");
Integer expiresIn = json.getInteger("expires_in");
if (accessToken == null || accessToken.trim().isEmpty()) {
throw weChatError("获取微信 access token 失败", json);
}
return new AccessTokenResult(accessToken, expiresIn == null ? 7200 : expiresIn);
}
public QrCodeImage getUnlimitedQrCode(String accessToken, String scene, String page,
String envVersion)
{
JSONObject requestBody = new JSONObject();
requestBody.put("scene", scene);
requestBody.put("page", page);
// 新页面尚未随小程序版本发布时也允许先生成码;扫码进入配置指定的小程序版本。
requestBody.put("check_path", false);
requestBody.put("env_version", envVersion);
requestBody.put("width", 430);
HttpResponse response = null;
try {
response = HttpRequest.post(UNLIMITED_QR_CODE_URL + accessToken)
.timeout(HTTP_TIMEOUT_MILLIS)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(requestBody.toJSONString())
.execute();
byte[] body = response.bodyBytes();
String contentType = response.header(HttpHeaders.CONTENT_TYPE);
if (response.isOk() && body != null && body.length > 0
&& isImageResponse(contentType, body)) {
return new QrCodeImage(body, normalizeImageContentType(contentType, body));
}
String errorBody = body == null ? "" : new String(body, StandardCharsets.UTF_8);
JSONObject errorJson = parseJson(errorBody, "生成微信小程序码失败");
throw weChatError("生成微信小程序码失败", errorJson);
} finally {
if (response != null) {
response.close();
}
}
}
private boolean isImageResponse(String contentType, byte[] body)
{
return (contentType != null && contentType.toLowerCase().startsWith("image/"))
|| isPng(body) || isJpeg(body);
}
private String normalizeImageContentType(String contentType, byte[] body)
{
if (contentType != null && contentType.toLowerCase().startsWith("image/")) {
int separator = contentType.indexOf(';');
return separator > 0 ? contentType.substring(0, separator) : contentType;
}
return isJpeg(body) ? MediaType.IMAGE_JPEG_VALUE : MediaType.IMAGE_PNG_VALUE;
}
private boolean isPng(byte[] body)
{
return body.length >= 8
&& (body[0] & 0xff) == 0x89
&& body[1] == 0x50
&& body[2] == 0x4e
&& body[3] == 0x47;
}
private boolean isJpeg(byte[] body)
{
return body.length >= 3
&& (body[0] & 0xff) == 0xff
&& (body[1] & 0xff) == 0xd8
&& (body[2] & 0xff) == 0xff;
}
private JSONObject parseJson(String body, String action)
{
try {
JSONObject json = JSONObject.parseObject(body);
if (json == null) {
throw new IllegalArgumentException("empty json");
}
return json;
} catch (Exception exception) {
throw new ServiceException(action + ",微信接口返回了无法识别的响应");
}
}
private ServiceException weChatError(String action, JSONObject json)
{
Integer errorCode = json.getInteger("errcode");
String errorMessage = json.getString("errmsg");
StringBuilder message = new StringBuilder(action);
if (errorCode != null) {
message.append("(错误码:").append(errorCode).append('');
}
if (errorMessage != null && !errorMessage.trim().isEmpty()) {
message.append("").append(errorMessage);
}
return new ServiceException(message.toString());
}
public static class AccessTokenResult
{
private final String accessToken;
private final int expiresIn;
public AccessTokenResult(String accessToken, int expiresIn)
{
this.accessToken = accessToken;
this.expiresIn = expiresIn;
}
public String getAccessToken()
{
return accessToken;
}
public int getExpiresIn()
{
return expiresIn;
}
}
public static class QrCodeImage
{
private final byte[] content;
private final String contentType;
public QrCodeImage(byte[] content, String contentType)
{
this.content = content;
this.contentType = contentType;
}
public byte[] getContent()
{
return content;
}
public String getContentType()
{
return contentType;
}
}
}