xml<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<binarywang.wx.version>4.6.0</binarywang.wx.version>
</properties>
<!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-miniapp -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${binarywang.wx.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-mp -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${binarywang.wx.version}</version>
</dependency>
javapackage com.xx.common.weixin;
import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import com.github.binarywang.utils.qrcode.MatrixToImageWriter;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.jeeplus.common.utils.AliyunOSSUtils;
import com.jeeplus.common.utils.StringUtils;
import com.jeeplus.config.properties.JeePlusProperites;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.mp.api.WxMpQrcodeService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
/**
* 微信工具类
*
* @author zhoudeshui
*/
@Component
public class WXUtils {
private static final Logger log = LoggerFactory.getLogger(WXUtils.class);
@Autowired
private WXBase wxBase;
@Autowired
private AliyunOSSUtils aliyunOSSUtils;
public WXBase getWxBase() {
return wxBase;
}
public void setWxBase(WXBase wxBase) {
this.wxBase = wxBase;
}
/**
* 通过js_code获取用户openid
*
* @param jsCode 小程序登录返回的临时登录凭证
* @return 用户openid
*/
public String getOpenIdByJsCode(String jsCode) {
try {
WxMaJscode2SessionResult result = wxBase.wxMaService().jsCode2SessionInfo(jsCode);
if (result != null) {
return result.getOpenid();
} else {
log.error("Failed to get session info with jsCode: {}", jsCode);
return null;
}
} catch (Exception e) {
log.error("Error getting OpenID by jsCode: {}", jsCode, e);
return null;
}
}
/**
* 通过code获取用户手机号
*
* @param code 小程序登录返回的临时登录凭证
* @return 用户手机号
*/
public String getPhoneNumber(String code) {
try {
// 使用jsCode获取用户手机号
WxMaPhoneNumberInfo phoneNumberInfo = wxBase.wxMaService().getUserService().getPhoneNoInfo(code);
if (phoneNumberInfo != null && phoneNumberInfo.getPhoneNumber() != null) {
return phoneNumberInfo.getPhoneNumber();
} else {
log.warn("Failed to get phone number for code: {}", code);
return null;
}
} catch (Exception e) {
log.error("Error getting phone number with code: {}", code, e);
return null;
}
}
/**
* 生成授权小程序二维码
*
* @param sceneParams 场景参数 携带设备号
* @return 上传到OSS后的小程序二维码图片URL
*/
public String generateAuthorizationQrCode(Map<String, Object> sceneParams) {
try {
//基本参数封装
String path = "pages/degLoginWx/degLoginWx";
boolean checkPath = false;
String envVersion = JeePlusProperites.DEVELOP;
int width = 430;
boolean autoColor = true;
WxMaCodeLineColor lineColor = null;
boolean isHyaline = false;
WxMaQrcodeService qrcodeService = wxBase.wxMaService().getQrcodeService();
// 将sceneParams转换为URL编码的字符串
StringBuilder encodedSceneParams = new StringBuilder();
for (Map.Entry<String, Object> entry : sceneParams.entrySet()) {
if (encodedSceneParams.length() > 0) {
encodedSceneParams.append("&");
}
encodedSceneParams.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name()));
encodedSceneParams.append("=");
encodedSceneParams.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8.name()));
}
String sceneParamsStr = encodedSceneParams.toString();
File qrCodeFile = qrcodeService.createWxaCodeUnlimit(sceneParamsStr, path, checkPath, envVersion, width, autoColor, lineColor, isHyaline);
MultipartFile qrCodeMultipartFile = getMultipartFile(qrCodeFile);
return aliyunOSSUtils.saveAndGetURL(qrCodeMultipartFile, "", "").toString();
} catch (Exception e) {
log.error("Error generate Authorization QrCode with code: {}", e);
return null;
}
}
public static MultipartFile getMultipartFile(File file) {
/**
* 转为PNG: MediaType.IMAGE_PNG_VALUE
*/
FileItem item = new DiskFileItemFactory().createItem("file", MediaType.IMAGE_PNG_VALUE, true, file.getName());
try (InputStream input = Files.newInputStream(file.toPath()); OutputStream os = item.getOutputStream()) {
// 流转移
IOUtils.copy(input, os);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid file: " + e, e);
}
return new CommonsMultipartFile(item);
}
/**
* 生成微信公众号JS-SDK配置参数
*
* @return 包含微信JS-SDK配置参数的Map对象
*/
public Map<String, Object> createWxJsSDK(String url) {
try {
// 获取jsapi_ticket
WxJsapiSignature wxJsapiSignature = wxBase.wxMpService().createJsapiSignature(url);
// 返回结果
Map<String, Object> result = new HashMap<>();
result.put("appId", wxJsapiSignature.getAppId());
result.put("timestamp", wxJsapiSignature.getTimestamp());
result.put("nonceStr", wxJsapiSignature.getNonceStr());
result.put("signature", wxJsapiSignature.getSignature());
return result;
} catch (Exception e) {
log.error("Error creating WxJsSDK: {}", e.getMessage());
return null;
}
}
/**
* 通过js_code获取用户openid
*
* @param jsCode 公众号登录返回的临时登录凭证
* @return 用户openid
*/
public String getOpenIdByJsCodeMp(String jsCode) {
try {
WxOAuth2AccessToken result = wxBase.wxMpService().getOAuth2Service().getAccessToken(jsCode);
if (result != null) {
return result.getOpenId();
} else {
log.error("Failed to get session info with jsCode: {}", jsCode);
return null;
}
} catch (Exception e) {
log.error("Error getting OpenID by jsCode: {}", jsCode, e);
return null;
}
}
/**
* 生成公众号消息通信二维码
*
* @param sceneParams 场景参数 携带设备号
* @return 上传到OSS后的小程序二维码图片URL
*/
public String generateMpInteractionQrCode(Map<String, Object> sceneParams) {
try {
WxMpQrcodeService qrcodeService = wxBase.wxMpService().getQrcodeService();
// 将sceneParams转换为URL编码的字符串
StringBuilder encodedSceneParams = new StringBuilder();
for (Map.Entry<String, Object> entry : sceneParams.entrySet()) {
if (encodedSceneParams.length() > 0) {
encodedSceneParams.append("&");
}
encodedSceneParams.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name()));
encodedSceneParams.append("=");
encodedSceneParams.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8.name()));
}
String sceneParamsStr = encodedSceneParams.toString();
WxMpQrCodeTicket wxMpQrCodeTicket = qrcodeService.qrCodeCreateLastTicket(sceneParamsStr);
String ticket = wxMpQrCodeTicket.getTicket();
//参数needShortUrl=true为生成短连接 可能会失效或被移除,若失效移除即可
// return qrcodeService.qrCodePictureUrl(ticket, true);
return qrcodeService.qrCodePictureUrl(ticket);
// File qrCodeFile = qrcodeService.qrCodePicture(wxMpQrCodeTicket);
// MultipartFile qrCodeMultipartFile = getMultipartFile(qrCodeFile);
// return aliyunOSSUtils.saveAndGetURL(qrCodeMultipartFile, "", "").toString();
} catch (Exception e) {
log.error("Error generate Interaction QrCode with code: {}", e);
return null;
}
}
/**
* 生成微信网页授权跳转的二维码并返回File对象的URL(OSS)
* redirectUri 回调URL
*
* @param scope 授权作用域
* state 用于防止CSRF攻击的随机字符串
* @return 生成的二维码图片文件
*/
public String generateH5AuthorizationQrCode(String scope, String param) {
try {
String appId = wxBase.wxMpService().getWxMpConfigStorage().getAppId();
String redirectUri = String.format("https://cloud.niuzongl.cn/deg_page/draeger/wechat/redirect/%s/%s", appId, param);
// 构造授权URL
String encodedRedirectUri = URLEncoder.encode(redirectUri, StandardCharsets.UTF_8.name());
String authUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?" + "appid=" + appId + "&redirect_uri=" + encodedRedirectUri + "&response_type=code" + "&scope=" + scope + "&forcePopup=" + JeePlusProperites.TRUE + "&state=" + StringUtils.createCharacter(7) + "#wechat_redirect";
// 生成二维码图片并保存到临时文件
File temporaryFile = createTemporaryQrCodeFile(authUrl);
if (temporaryFile == null) {
log.error("Failed to create temporary file for QrCode, temporaryFile is null");
return null;
}
MultipartFile qrCodeMultipartFile = getMultipartFile(temporaryFile);
return aliyunOSSUtils.saveAndGetURL(qrCodeMultipartFile, "", "").toString();
} catch (Exception e) {
log.error("Error generating Wechat Auth QrCode: {}", e.getMessage());
return null;
}
}
private String generateSceneStr(String url) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(url.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
private File createTemporaryQrCodeFile(String content) {
try {
QRCodeWriter writer = new QRCodeWriter();
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, 256, 256, hints);
File tempFile = File.createTempFile("qrcode", ".png");
tempFile.deleteOnExit(); // 确保程序退出时删除临时文件
BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
ImageIO.write(image, "png", tempFile);
return tempFile;
} catch (Exception e) {
log.error("Error creating temporary QrCode file: {}", e.getMessage());
return null;
}
}
}
javapackage com.xx.common.weixin;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author zhoudeshui
* 使用WxAppletConfigHolder后继承WxMaDefaultConfigImpl继承WxMaConfig设置默认属性
*/
@Component
@ToString
public class WXAppletConfigHolder extends WxMaDefaultConfigImpl {
@Value("${wx.applet.appId}")
private String appId;
@Value("${wx.applet.secret}")
private String secret;
// 注意这里没有构造函数传参
@PostConstruct
public void init() {
setAppid(appId);
setSecret(secret);
}
}
javapackage com.xx.common.weixin;
import lombok.ToString;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author zhoudeshui
* 使用WXMediaConfigHolder后继承WxMpDefaultConfigImpl继承WxMpConfigStorage设置默认属性
*/
@Component
@ToString
public class WXMediaConfigHolder extends WxMpDefaultConfigImpl {
@Value("${wx.media.appId}")
private String appId;
@Value("${wx.media.secret}")
private String secret;
// 注意这里没有构造函数传参
@PostConstruct
public void init() {
setAppId(appId);
setSecret(secret);
}
}
javapackage com.xx.common.weixin;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WXBase {
@Autowired
private WXAppletConfigHolder appletConfigHolder;
@Autowired
private WXMediaConfigHolder mediaConfigHolder;
//小程序
@Bean
public WxMaService wxMaService() {
// 使用配置实例化WxMaService
WxMaService wxMaService = new WxMaServiceImpl();
wxMaService.setWxMaConfig(appletConfigHolder);
return wxMaService;
}
//公众号
@Bean
public WxMpService wxMpService() {
// 使用配置实例化WxMpService
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(mediaConfigHolder);
return wxMpService;
}
}
xml<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.2</version>
<scope>compile</scope>
</dependency>
java public URL saveAndGetURL(MultipartFile file, String path, String fileName) {
logger.debug("开始向aliyun-oss保存文件: {}", fileName);
long begin = System.currentTimeMillis();
try {
// 新增:检查文件大小
long fileSize = file.getSize();
if (fileSize > 5 * 1024 * 1024) { // 5MB限制
throw new IllegalArgumentException("文件大小超过5MB限制");
}
if (StringUtils.isBlank(fileName)) {
fileName = IdUtil.fastSimpleUUID();
// 假设file.getContentType()可以获取文件的MIME类型
String contentType = file.getContentType();
if (contentType != null) {
fileName += getFileExtensionFromContentType(contentType);
} else {
// 如果无法从contentType获取,可以设置一个默认的后缀,如 unknown
fileName += ".unknown";
}
}
if (StringUtils.isBlank(path)) {
path = "/";
}
OSS client = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ObjectMetadata meta = new ObjectMetadata();
meta.setContentDisposition("attachment;filename*=utf-8''" + URLEncoder.encode(fileName, String.valueOf(StandardCharsets.UTF_8)));
String objectPath;
if (fileName.contains(".")) { // 如果文件名中已经包含点(即有后缀)
objectPath = accessoryBaseDir + path + (StringUtils.isEmpty(path) || path.endsWith("/") ? "" : "/") + fileName;
} else { // 如果没有后缀,则添加一个默认后缀
objectPath = accessoryBaseDir + path + (StringUtils.isEmpty(path) || path.endsWith("/") ? "" : "/") + fileName + ".unknown";
}
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectPath, file.getInputStream(), meta);
client.putObject(putObjectRequest);
// 生成预签名的URL
Date expireDate = DateUtils.addYears(new Date(), 25);
URL url = client.generatePresignedUrl(bucketName, putObjectRequest.getKey(), expireDate);
//http(隐藏私有特征)
URL newUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getPath());
client.shutdown();
logger.debug("originURL: {}, URL: {}", url, newUrl);
return newUrl;
} catch (IOException | OSSException | MultipartException | IllegalArgumentException e) {
e.printStackTrace();
throw new RuntimeException("保存文件到OSS或获取URL失败", e);
} finally {
logger.debug("完成向aliyun-oss保存文件: {}", System.currentTimeMillis() - begin);
}
}
java private static String getFileExtensionFromContentType(String contentType) {
switch (contentType) {
case "image/jpeg":
case "image/pjpeg":
return ".jpg";
case "image/png":
return ".png";
case "image/gif":
return ".gif";
case "image/svg+xml":
return ".svg";
case "image/tiff":
return ".tiff";
case "image/webp":
return ".webp";
case "video/mp4":
return ".mp4";
case "video/quicktime":
return ".mov";
case "video/x-msvideo":
return ".avi";
case "audio/mpeg":
return ".mp3";
case "audio/x-wav":
return ".wav";
case "audio/vnd.wav":
return ".wav";
case "audio/ogg":
return ".ogg";
case "audio/webm":
return ".weba";
case "application/pdf":
return ".pdf";
case "application/msword":
return ".doc";
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
return ".docx";
case "application/vnd.ms-excel":
return ".xls";
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
return ".xlsx";
case "application/vnd.ms-powerpoint":
return ".ppt";
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
return ".pptx";
case "application/zip":
return ".zip";
case "application/x-rar-compressed":
return ".rar";
case "text/plain":
return ".txt";
case "text/html":
return ".html";
case "text/css":
return ".css";
case "text/javascript":
return ".js";
// 添加更多针对不同MIME类型的处理...
default:
// 如果无法匹配到特定类型,返回默认或未知类型后缀
return ".unknown";
}
}
本文作者:周得水
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!