首先我们可以看到官方文档上是有三种码的 获取小程序码
这里特别要注意的是第一种和第三种是有数量限制的,所以大家生成的时候记得保存,也不要一直瞎生成
还有一点要注意的是第一种和第二种是太阳码
第三种是方形码
好了直接上代码
这里要注意,它请求成功返回的是一个 base64 的图片流,但是失败返回的是JSON字符串,我这里没有处理请求失败的问题
package com.sakura.user.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sakura.common.exception.BusinessException;
import com.sakura.common.redis.RedisUtil;
import com.sakura.common.tool.HttpsRequestUtil;
import com.sakura.common.tool.StringUtil;
import com.sakura.user.param.GetAppCodeParam;
import com.sakura.user.vo.wx.WxAccessTokenVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author Sakura
* @date 2024/8/22 11:04
* 微信小程序工具类
*/
@Component
@Slf4j
public class WxAppUtil {
@Value("${wx.app.appid}")
private String appid;
@Value("${wx.app.secret}")
private String secret;
// 获取授权token
private static final String GET_TOKEN = "https://api.weixin.qq.com/cgi-bin/token";
private static final String WX_APP_ACCESS_TOKEN = "wx_app_access_token";
// 获取小程序码URL
private static final String GET_APP_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacode";
// 获取不受限制的小程序码
private static final String GET_UNLIMITED_APP_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit";
private static final String GET_APP_QR_CODE_URL = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode";
@Autowired
private RedisUtil redisUtil;
// 获取授权凭证
public String getAccessToken() {
// 优先从Redis获取,没有再调接口
// 如果Redis里面有则直接取Redis里面的
if (redisUtil.hasKey(WX_APP_ACCESS_TOKEN)) {
log.info(redisUtil.get(WX_APP_ACCESS_TOKEN).toString());
return redisUtil.get(WX_APP_ACCESS_TOKEN).toString();
}
// 拼接请求参数
StringBuilder strUrl = new StringBuilder();
strUrl.append(GET_TOKEN)
.append("?grant_type=client_credential")
.append("&appid=").append(appid)
.append("&secret=").append(secret);
try {
String resultStr = HttpsRequestUtil.sendGetRequest(strUrl.toString());
log.info("【微信小程序获取授权凭证】:返回结果:" + resultStr);
WxAccessTokenVo wxAccessTokenVo = JSON.parseObject(resultStr, WxAccessTokenVo.class);
if (StringUtil.isBlank(wxAccessTokenVo.getAccess_token())) {
throw new BusinessException("微信小程序获取授权凭证异常");
}
//官方 access_token expires_in 为 2个小时, 这里设置 100分钟
redisUtil.set(WX_APP_ACCESS_TOKEN, wxAccessTokenVo.getAccess_token(), 100 * 60);
return wxAccessTokenVo.getAccess_token();
} catch (Exception e) {
log.info("【微信小程序获取授权凭证】:获取异常:" + e.getMessage());
e.printStackTrace();
throw new BusinessException("微信小程序获取授权凭证异常");
}
}
// 获取小程序码
public byte[] getAppCode(GetAppCodeParam getAppCodeParam) {
// 先要获取access_token
String accessToken = getAccessToken();
// 拼接请求参数
StringBuilder strUrl = new StringBuilder();
strUrl.append(GET_APP_CODE_URL)
.append("?access_token=")
.append(accessToken);
// 封装请求参数到body
JSONObject jsonObject = new JSONObject();
jsonObject.put("path", getAppCodeParam.getPath());
if (StringUtil.isNotBlank(getAppCodeParam.getEnvVersion())) {
jsonObject.put("env_version", getAppCodeParam.getEnvVersion());
}
if (getAppCodeParam.getWidth() != null) {
jsonObject.put("width", getAppCodeParam.getWidth());
}
if (getAppCodeParam.getIsHyaline() != null) {
jsonObject.put("is_hyaline", getAppCodeParam.getIsHyaline());
}
try {
return HttpsRequestUtil.sendPostReturnByte(strUrl.toString(), jsonObject.toJSONString());
} catch (Exception e) {
log.info("【微信小程序获取小程序码】:获取小程序码异常:" + e.getMessage());
e.printStackTrace();
throw new BusinessException("微信小程序获取小程序码异常");
}
}
// 获取无限制的小程序码
public byte[] getUnlimitedAppCode(GetAppCodeParam getAppCodeParam) {
// 先要获取access_token
String accessToken = getAccessToken();
// 拼接请求参数
StringBuilder strUrl = new StringBuilder();
strUrl.append(GET_UNLIMITED_APP_CODE_URL)
.append("?access_token=")
.append(accessToken);
// 封装请求参数到body
JSONObject jsonObject = new JSONObject();
jsonObject.put("scene", getAppCodeParam.getScene());
if (StringUtil.isNotBlank(getAppCodeParam.getPage())) {
jsonObject.put("page", getAppCodeParam.getPage());
}
if (StringUtil.isNotBlank(getAppCodeParam.getEnvVersion())) {
jsonObject.put("env_version", getAppCodeParam.getEnvVersion());
}
if (getAppCodeParam.getWidth() != null) {
jsonObject.put("width", getAppCodeParam.getWidth());
}
if (getAppCodeParam.getIsHyaline() != null) {
jsonObject.put("is_hyaline", getAppCodeParam.getIsHyaline());
}
if (getAppCodeParam.getCheckPath() != null) {
jsonObject.put("check_path", getAppCodeParam.getCheckPath());
}
try {
return HttpsRequestUtil.sendPostReturnByte(strUrl.toString(), jsonObject.toJSONString());
} catch (Exception e) {
log.info("【微信小程序获取不限制小程序码】:获取不限制小程序码异常:" + e.getMessage());
e.printStackTrace();
throw new BusinessException("微信小程序获取不限制小程序码异常");
}
}
// 获取小程序二维码
public byte[] getAppQRCode(GetAppCodeParam getAppCodeParam) {
// 先要获取access_token
String accessToken = getAccessToken();
// 拼接请求参数
StringBuilder strUrl = new StringBuilder();
strUrl.append(GET_APP_QR_CODE_URL)
.append("?access_token=")
.append(accessToken);
// 封装请求参数到body
JSONObject jsonObject = new JSONObject();
jsonObject.put("path", getAppCodeParam.getPath());
if (getAppCodeParam.getWidth() != null) {
jsonObject.put("width", getAppCodeParam.getWidth());
}
try {
return HttpsRequestUtil.sendPostReturnByte(strUrl.toString(), jsonObject.toJSONString());
} catch (Exception e) {
log.info("【微信小程序获取小程序二维码】:获取小程序二维码异常:" + e.getMessage());
e.printStackTrace();
throw new BusinessException("微信小程序获取小程序二维码异常");
}
}
}
http 工具类
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author Sakura
* @date 2024/6/24 17:07
*/
public class HttpsRequestUtil {
public static String sendPostRequest(String urlString, String jsonInputString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法为POST
connection.setRequestMethod("POST");
// 设置请求头
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
// 启用输出流,用于发送请求数据
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
}
// 获取响应
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
return response.toString();
} finally {
// 关闭连接
connection.disconnect();
}
}
public static String sendGetRequest(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法为GET
connection.setRequestMethod("GET");
// 设置请求头
connection.setRequestProperty("Accept", "application/json");
// 获取响应
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
return response.toString();
} finally {
// 关闭连接
connection.disconnect();
}
}
public static byte[] sendPostReturnByte(String urlString, String jsonInputString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法为POST
connection.setRequestMethod("POST");
// 设置请求头
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
// 启用输出流,用于发送请求数据
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
}
// 获取响应
try (InputStream is = connection.getInputStream()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int bytesRead;
byte[] data = new byte[1024];
while ((bytesRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
} finally {
// 关闭连接
connection.disconnect();
}
}
}
请求的参数
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 获取小程序码参数
*
* @author Sakura
* @since 2024-08-26
*/
@Data
@Accessors(chain = true)
@ApiModel(value = "获取小程序码参数")
public class GetAppCodeParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("类型:1小程序码 2不受限制小程序码 3小程序二维码")
@NotNull(message = "小程序码类型不能为空")
private Integer type;
@ApiModelProperty("type为1.3时必传 扫码进入的小程序页面路径")
private String path;
@ApiModelProperty("type为1.2时可传 要打开的小程序版本。正式版为 \"release\",体验版为 \"trial\",开发版为 \"develop\"。默认是正式版。")
private String envVersion;
@ApiModelProperty("二维码宽度 默认430,二维码的宽度,单位 px,最小 280px,最大 1280px")
private Integer width;
@ApiModelProperty("type为2时必传 自定义参数最大32个可见字符 如 a=1")
private String scene;
@ApiModelProperty("type为2时可传 默认是true,检查page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但page 有数量上限(60000个)请勿滥用。")
private Boolean checkPath;
@ApiModelProperty("type为2时可传 默认是主页,页面 page,例如 pages/index/index,根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面。scancode_time为系统保留参数,不允许配置")
private String page;
@ApiModelProperty("type为1.2时可传 默认值false;是否需要透明底色,为 true 时,生成透明底色的小程序码")
private Boolean isHyaline;
}
还有因为返回的是个流,所以我们还需要把它转成图片保存起来,这里大家可以上传到云存储也可就放在服务器上,我这里就是放在服务器上的,在本地项目或者服务器 jar 包目录下 “./resources/files/appcode”
@Override
public String getAppCode(GetAppCodeParam getAppCodeParam) throws Exception {
// 根据类型获取不同二维码
byte[] bytes = null;
if (getAppCodeParam.getType() == 1) {
bytes = wxAppUtil.getAppCode(getAppCodeParam);
} else if (getAppCodeParam.getType() == 2) {
bytes = wxAppUtil.getUnlimitedAppCode(getAppCodeParam);
} else if (getAppCodeParam.getType() == 3) {
bytes = wxAppUtil.getAppQRCode(getAppCodeParam);
}
if (bytes == null || bytes.length < 1) {
throw new BusinessException("获取小程序码失败");
}
// 目标目录
String outputDir = "./resources/files/appcode";
// 创建目录(如果不存在)
File directory = new File(outputDir);
if (!directory.exists()) {
directory.mkdirs();
}
// 生成图片文件的完整路径
String outputFileName = RandomStringUtils.randomAlphanumeric(32);
String outputFilePath = outputDir + "/" + outputFileName + ".png";
// 将字节数组写入到图片文件中
try (FileOutputStream fos = new FileOutputStream(outputFilePath)) {
fos.write(bytes);
}
return "https://sakura.com/api-wx/appCode/download/" + outputFileName;
}
因为图片存在服务器上了,我们还需要下载下来
看不懂的可以看下我另一篇讲什么上传下载文件的 Java实现本地文件上传下载接口示例
@Override
public void download(HttpServletResponse response, String code) throws Exception {
File downloadFile = new File("./resources/files/appcode/" + code + ".png");
if (!downloadFile.exists() || downloadFile.length() == 0) {
throw new BusinessException(500, "文件不存在");
}
// 确定文件的Content-Type
String mimeType = Files.probeContentType(downloadFile.toPath());
if (mimeType == null) {
mimeType = "application/octet-stream"; // 默认类型
}
response.setContentType(mimeType);
response.addHeader("Content-Length", String.valueOf(downloadFile.length()));
response.addHeader("Content-Disposition", "attachment; filename=\"" + code + ".png\"");
try (InputStream is = new FileInputStream(downloadFile);
OutputStream os = response.getOutputStream()) {
IOUtils.copy(is, os);
os.flush(); // 确保数据已写入输出流
} catch (IOException e) {
log.info("下载图片发生IO异常");
e.printStackTrace();
throw new BusinessException(500, "文件下载失败");
} catch (Exception e) {
log.info("下载图片发生异常");
e.printStackTrace();
throw new BusinessException(500, "文件下载失败");
}
}
最后,小程序获取 AccessToken 是需要添加白名单的,然后第二种码在小程序没发布的时候 check_path 一定要传 false,默认是 true 会报错的