注意事项
- 如果调用成功,会直接返回图片二进制内容,如果请求失败,会返回 JSON 格式的数据。
- POST 参数需要转成 JSON 字符串,不支持 form 表单提交。
- 调用分钟频率受限(5000次/分钟),如需大量小程序码,建议预生成
获取 scene 值
- scene 字段的值会作为 query 参数传递给小程序/小游戏。用户扫描该码进入小程序/小游戏后,开发者可以获取到二维码中的 scene 值,再做处理逻辑。
- 调试阶段可以使用开发工具的条件编译自定义参数 scene=xxxx 进行模拟,开发工具模拟时的 scene 的参数值需要进行 encodeURIComponent
配置文件
envVersion为要打开的小程序版本。正式版为 “release”,体验版为 “trial”,开发版为 “develop”。默认是正式版。
wx:
appId: wxfdf3xxxxx
appSecret: 758ac1cxxxxxx
envVersion: release
生成微信不限制的小程序码(拉新二维码)
- 获取小程序码之前需要获取小程序的accessToken
- scene: 和前端规定需要接收的参数是什么,假设此时我的需求为生成拉新二维码,此处我的scene为"shareId=1",代表分享者的id是1,用户扫码时前端能拿到这个参数从而判断这个二维码是由谁分享的,从而给对应用户奖励
- 当然这里也可以传其他的参数,读者可以自定义只要符合下面的规则即可
- 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#amp;'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
- env_version: 要打开的小程序版本。正式版为 “release”,体验版为 “trial”,开发版为 “develop”。默认是正式版。
- access_token和scene是必传的,其他字段可以传,此处这里我只传了以上三个参数,有需要的可以自己传需要的参数
- RedisUtil可以参考这个springboot整合redis多数据源(附带RedisUtil)
- fileService:为阿里云oss上传方法,将微信返回的byte[]上传到阿里云oss返回url.
- 读者也可以自己处理微信返回的byte[]
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.hss.common.core.domain.R;
import com.hss.common.exception.ServiceException;
import com.hss.common.utils.RedisUtil;
import com.hss.common.utils.uuid.UUID;
import com.hss.system.service.FileService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author zr 2024/10/14
*/
@Service
public class WxService {
private static final Logger log = LoggerFactory.getLogger(LoginService.class);
@Value("${wx.appId}")
private String appId;
@Value("${wx.appSecret}")
private String appSecret;
@Value("${wx.envVersion}")
private String envVersion;
@Autowired
private FileService fileService;
private final String TOKEN_KEY = "applets-token:";
private final String GET_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token";
/**
* 获取accessToken
* @return
*/
public R<String> getAccessToken() {
String accessToken = RedisUtil.StringOps.get(TOKEN_KEY);
if (StringUtils.isNotEmpty(accessToken)) {
return R.ok(accessToken);
} else {
HashMap<String, Object> parameter = new HashMap();
parameter.put("appid", this.appId);
parameter.put("secret", this.appSecret);
parameter.put("grant_type", "client_credential");
HttpResponse response = this.httpGet(GET_ACCESS_TOKEN, (Map) null, parameter);
String res = response.body();
JSONObject jsonObject = JSON.parseObject(res);
Integer errcode = jsonObject.getInteger("errcode");
if (ObjectUtils.isEmpty(errcode)) {
accessToken = jsonObject.getString("access_token");
Integer expires = jsonObject.getInteger("expires_in");
RedisUtil.StringOps.setEx(TOKEN_KEY, accessToken, (long) (expires - 200), TimeUnit.SECONDS);
return R.ok(accessToken);
} else {
return R.fail("微信错误:" + jsonObject.getString("errmsg"));
}
}
}
public HttpResponse httpGet(String url, Map<String, String> headers, Map<String, Object> parameter) {
HttpRequest httpRequest = (HttpRequest) HttpUtil.createGet(url).form(parameter).addHeaders(headers);
HttpResponse response = httpRequest.execute();
log.info("Url: " + httpRequest.getUrl());
log.info("head: " + JSON.toJSONString(httpRequest.headers()));
log.info("parame: " + JSON.toJSONString(httpRequest.form()));
log.info("res: " + response.body());
return response;
}
public String httpPost(String url, String body) {
String res = HttpUtil.post(url, body);
log.info("Url: " + url);
log.info("body: " + body);
log.info("res: " + res);
return res;
}
/**
* 获取不限制的小程序码
* @param scene
* @return
*/
//https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/getUnlimitedQRCode.html
public R<String> getUnlimitedQRCode(String scene) {
try {
// 定义请求URL
String url = String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", this.getAccessToken().getData());
// 构建请求参数
Map<String, Object> params = new HashMap<>();
params.put("scene", scene);
params.put("page", "pages/index/index");
// params.put("check_path", checkPath);
params.put("env_version", envVersion);//要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
// params.put("width", width);
// params.put("auto_color", autoColor);
// if (!autoColor && lineColor != null) {
// params.put("line_color", lineColor);
// }
// params.put("is_hyaline", isHyaline);
// 将参数转换为 JSON 字符串
String jsonParams = JSON.toJSONString(params);
// 设置请求头,将请求体发送为 JSON
HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json") // 确保请求是 JSON 格式
.body(jsonParams) // 将 JSON 请求体发送出去
.execute();
byte[] bytes = response.bodyBytes();
String upload = fileService.upload(bytes, UUID.fastUUID() + ".jpg");
return R.ok(upload);
} catch (Exception e) {
log.error("获取小程序码失败: {}", e.getMessage());
throw new ServiceException("获取小程序码失败");
}
}
}
FileService(阿里云OSS服务)
- 配置文件
aliyun:
oss:
end-point: https://oss-cn-beijing.aliyuncs.com
access-key-id: LTAIxxxxxxxxxxx
access-key-secret: qoNx6xxxxxxxxxxxx
bucket-name: xxxx
expiration: 7 #临时url有效期(天)
- 配置类
package com.hss.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author zr 2024/2/29
*/
@ConfigurationProperties(prefix = "aliyun.oss")
@Configuration
@Data
public class OSSConfig {
private String endPoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
private Integer expiration;
}
- FileServiceImpl,接口我就不写了
package com.hss.system.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.hss.common.config.OSSConfig;
import com.hss.common.utils.RedisUtil;
import com.hss.system.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 文件上传业务类
*
* @author zr 2024/2/29
*/
@Service
@Slf4j
public class FileServiceImpl implements FileService {
@Autowired
private OSSConfig ossConfig;
/**
* 阿里云OSS文件上传byte[]
* @param qrCodeData
* @param fileName
* @return
*/
@Override
public String upload(byte[] qrCodeData, String fileName) {
//获取相关配置
String bucketName = ossConfig.getBucketName();
String endPoint = ossConfig.getEndPoint();
String accessKeyId = ossConfig.getAccessKeyId();
String accessKeySecret = ossConfig.getAccessKeySecret();
//创建OSS对象
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
try {
// 将二维码图片上传到阿里云OSS
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(qrCodeData);
ossClient.putObject(new PutObjectRequest(bucketName, fileName, byteArrayInputStream));
// 生成文件的访问URL
String fileUrl = getTemporaryUrl(fileName);
return fileUrl; // 返回文件URL
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("上传文件到OSS失败");
} finally {
// 关闭OSSClient
if (ossClient != null) {
ossClient.shutdown();
}
}
}
/**
* 阿里云OSS文件上传file
*
* @param file
*/
@Override
public String upload(MultipartFile file) {
//获取相关配置
String bucketName = ossConfig.getBucketName();
String endPoint = ossConfig.getEndPoint();
String accessKeyId = ossConfig.getAccessKeyId();
String accessKeySecret = ossConfig.getAccessKeySecret();
//创建OSS对象
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
//获取原生文件名
String originalFilename = file.getOriginalFilename();
//JDK8的日期格式
LocalDateTime time = LocalDateTime.now();
DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM");
//拼装OSS上存储的路径
String folder = dft.format(time);
String fileName = generateUUID();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
//在OSS上bucket下的文件名
String uploadFileName = "applets-head-portrait/" + folder + "/" + fileName + extension;
String temporaryUrl = null;
try {
PutObjectResult result = ossClient.putObject(bucketName, uploadFileName, file.getInputStream());
//拼装返回路径
if (result != null) {
// 原路径
// return "https://"+bucketName+"."+endPoint+"/"+uploadFileName;
temporaryUrl = getTemporaryUrl(uploadFileName) + "," + getTemporaryCompressionUrl(uploadFileName);
}
} catch (Exception e) {
log.error("文件上传失败:{}", e.getMessage());
} finally {
//OSS关闭服务,不然会造成OOM
ossClient.shutdown();
}
return temporaryUrl;
}
/**
* 阿里云OSS文件上传url
*
* @param url
*/
@Override
public String upload(String url) {
// 1. 获取本地文件
File file = new File(url);
if (!file.exists()) {
throw new IllegalArgumentException("文件不存在: " + url);
}
//获取相关配置
String bucketName = ossConfig.getBucketName();
String endPoint = ossConfig.getEndPoint();
String accessKeyId = ossConfig.getAccessKeyId();
String accessKeySecret = ossConfig.getAccessKeySecret();
//创建OSS对象
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
//获取原生文件名
String originalFilename = file.getName();
//JDK8的日期格式
LocalDateTime time = LocalDateTime.now();
DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM");
//拼装OSS上存储的路径
String folder = dft.format(time);
String fileName = generateUUID();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
//在OSS上bucket下的文件名
String uploadFileName = "applets-image/" + folder + "/" + fileName + extension;
String temporaryUrl = null;
try {
InputStream InputStream = new FileInputStream(file);
PutObjectResult result = ossClient.putObject(bucketName, uploadFileName, InputStream);
//拼装返回路径
if (result != null) {
// 原路径
// return "https://"+bucketName+"."+endPoint+"/"+uploadFileName;
temporaryUrl = getTemporaryUrl(uploadFileName) + "," + getTemporaryCompressionUrl(uploadFileName);
}
} catch (Exception e) {
log.error("文件上传失败:{}", e.getMessage());
} finally {
//OSS关闭服务,不然会造成OOM
ossClient.shutdown();
}
return temporaryUrl;
}
/**
* 获取临时url
*
* @return
*/
@Override
public String getTemporaryUrl(String uploadFileName) {
String value = RedisUtil.StringOps.get(uploadFileName);
if (ObjectUtils.isNotEmpty(value)) {
return value;
}
//获取相关配置
String bucketName = ossConfig.getBucketName();
String endPoint = ossConfig.getEndPoint();
String accessKeyId = ossConfig.getAccessKeyId();
String accessKeySecret = ossConfig.getAccessKeySecret();
Integer expirationDay = ossConfig.getExpiration();
//创建OSS对象
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
// 设置过期时间
Date expiration = new Date(System.currentTimeMillis() + expirationDay * 24 * 3600 * 1000); // 1 小时后过期
// 生成临时访问 URL
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, uploadFileName);
request.setExpiration(expiration);
URL signedUrl = ossClient.generatePresignedUrl(request);
String urlString = signedUrl.toString();
//缓存过期时间比oss过期时间少一天
RedisUtil.StringOps.setEx(uploadFileName, urlString, expirationDay - 1, TimeUnit.DAYS);
// 关闭 OSS 客户端
ossClient.shutdown();
return urlString;
}
/**
* 获取临时压缩url
*
* @return
*/
@Override
public String getTemporaryCompressionUrl(String uploadFileName) {
//获取相关配置
String bucketName = ossConfig.getBucketName();
String endPoint = ossConfig.getEndPoint();
String accessKeyId = ossConfig.getAccessKeyId();
String accessKeySecret = ossConfig.getAccessKeySecret();
Integer expirationDay = ossConfig.getExpiration();
//创建OSS对象
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
// 设置过期时间
Date expiration = new Date(System.currentTimeMillis() + expirationDay * 24 * 3600 * 1000); // 1 小时后过期
// 生成临时访问 URL
// 压缩百分之50。
String style = "image/resize,p_50";
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, uploadFileName);
request.setExpiration(expiration);
request.setProcess(style);
URL signedUrl = ossClient.generatePresignedUrl(request);
String urlString = signedUrl.toString();
//缓存过期时间比oss过期时间少一天
// RedisUtil.StringOps.setEx(uploadFileName, urlString, expirationDay - 1, TimeUnit.DAYS);
// 关闭 OSS 客户端
ossClient.shutdown();
return urlString;
}
/**
* 获取随机字符串
*
* @return
*/
private String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
}