2024-07-02
技术分享
00
请注意,本文编写于 204 天前,最后修改于 194 天前,其中某些信息可能已经过时。

目录

本工具基于WxJava MiniApp/Mp 框架封装
Maven pom依赖
WXUtils
WXAppletConfigHolder(小程序)
WXMediaConfigHolder(公众号)
WXBase
Maven pom依赖
AliyunOSS Utils function
getFileExtensionFromContentType function

本工具基于WxJava MiniApp/Mp 框架封装

Maven pom依赖

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>

WXUtils

java
package 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; } } }

WXAppletConfigHolder(小程序)

java
package 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); } }

WXMediaConfigHolder(公众号)

java
package 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); } }

WXBase

java
package 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; } }

Maven pom依赖

xml
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.2</version> <scope>compile</scope> </dependency>

AliyunOSS Utils function

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); } }

getFileExtensionFromContentType function

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 许可协议。转载请注明出处!