diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml index f8316d5..58e624c 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml @@ -40,6 +40,19 @@ 3.8.1 + + + com.google.zxing + core + 3.3.3 + + + + com.google.zxing + javase + 3.3.3 + + com.tencentcloudapi tencentcloud-sdk-java-tts diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java index 7798e39..2c7fc1e 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiPromotionController.java @@ -32,8 +32,8 @@ public class AppletApiPromotionController { @Operation(summary = "获取推广二维码", description = "获取推广二维码") @GetMapping(value = "/qrCode", produces = MediaType.IMAGE_PNG_VALUE) - public byte[] getInviteCode() { - return appletApiWaterService.getInviteCode(); + public byte[] getInviteCode(@RequestParam(required = false, defaultValue = LoginType.APPLET) String type) { + return appletApiWaterService.getInviteCode(type); } @Operation(summary = "我的团队", description = "我的团队") diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java index 820be32..358b69f 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiWaterService.java @@ -11,7 +11,7 @@ import java.util.List; public interface AppletApiWaterService { - byte[] getInviteCode(); + byte[] getInviteCode(String type); /** * 流水列表 diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java index 80d4b75..57a678c 100644 --- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiWaterServiceImpl.java @@ -11,8 +11,10 @@ import org.jeecg.common.system.util.AppletUserUtil; import org.jeecg.common.system.vo.AppletUser; import org.jeecg.common.util.RedisUtil; import org.jeecg.common.util.oss.OssBootUtil; +import org.jeecg.modules.applet.Final.LoginType; import org.jeecg.modules.applet.entity.StatisticsVo; import org.jeecg.modules.applet.service.AppletApiWaterService; +import org.jeecg.modules.applet.util.QRCodeUtil; import org.jeecg.modules.common.IdUtils; import org.jeecg.modules.common.wxUtils.WxHttpUtils; import org.jeecg.modules.demo.appletConfig.service.IAppletConfigService; @@ -67,7 +69,7 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { private final ExecutorService asyncExecutor = Executors.newFixedThreadPool(5); @Override - public byte[] getInviteCode(){ + public byte[] getInviteCode(String type){ AppletUser user = AppletUserUtil.getCurrentAppletUser(); // 获取环境配置 @@ -84,21 +86,23 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { // 获取必要的配置信息 String xcxSharePage = appletConfigService.getContentByCode("xcxSharePage"); String backgroundImageUrl = appletConfigService.getContentByCode("qr_code_bg"); - + String webUrl = appletConfigService.getContentByCode("webUrl"); + int webUrlCode = (webUrl != null && LoginType.OFFICIAL.equals(type)) ? webUrl.hashCode() : 0; + // 获取二维码位置配置参数 int qrCodeX = appletConfigService.getContentByCodeAsInt("qr_code_x"); int qrCodeY = appletConfigService.getContentByCodeAsInt("qr_code_y"); // 优化缓存策略:使用更精确的缓存key,包含所有影响因素(包括背景图片URL和二维码位置配置) - String cacheKey = String.format("inviteCode:final:%s:%s:%s:%s:%s:%s", - user.getId(), trial, xcxSharePage.hashCode(), backgroundImageUrl.hashCode(), qrCodeX, qrCodeY); +// String cacheKey = String.format("inviteCode:final:%s:%s:%s:%s:%s:%s", +// user.getId(), trial, xcxSharePage.hashCode(), backgroundImageUrl.hashCode(), qrCodeX, qrCodeY); // 移除图片数据的Redis缓存,避免类型转换错误 // 直接检查OSS中是否已存在最终图片 // 检查OSS中是否已存在最终图片 - String finalPath = String.format("invite/final/%s_%s_%s_%s_%s_%s.jpg", - user.getId(), trial, xcxSharePage.hashCode(), backgroundImageUrl.hashCode(), qrCodeX, qrCodeY); + String finalPath = String.format("invite/final/%s_%s_%s_%s_%s_%s_%s_%s.jpg", + user.getId(), trial, xcxSharePage.hashCode(), backgroundImageUrl.hashCode(), qrCodeX, qrCodeY, type, webUrlCode); try { InputStream ossFile = OssBootUtil.getOssFile(finalPath, null); if (ossFile != null) { @@ -112,7 +116,7 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { } buffer.flush(); byte[] ossImageBytes = buffer.toByteArray(); - + // 直接返回OSS中的图片,不再缓存到Redis log.info("从OSS返回邀请码图片,用户ID: {}", user.getId()); return ossImageBytes; @@ -127,10 +131,18 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { try { // 直接生成小程序码,移除Redis缓存避免类型转换错误 log.info("生成小程序码,用户ID: {}", user.getId()); - byte[] qrCodeBytes = generateWxQrCode(user, xcxSharePage, trial); +// byte[] qrCodeBytes = null; + BufferedImage qrCodeImage = null; + + if (LoginType.APPLET.equals(type)){ + qrCodeImage = ImageIO.read(new ByteArrayInputStream(generateWxQrCode(user, xcxSharePage, trial))); + }else { + qrCodeImage = QRCodeUtil.getBufferedImage(webUrl + "?inviter=" + user.getId()); + } // 生成最终合成图片,传递已获取的配置参数避免重复调用 - byte[] finalImage = this.generateAndCombineImagesFromUrl2(qrCodeBytes, backgroundImageUrl, qrCodeX, qrCodeY); + + byte[] finalImage = this.generateAndCombineImagesFromUrl2(qrCodeImage, backgroundImageUrl, qrCodeX, qrCodeY); // 异步上传到OSS(移除Redis缓存) uploadToOssAsync(finalImage, finalPath); @@ -192,7 +204,7 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { } - public byte[] generateAndCombineImagesFromUrl2(byte[] qrCodeImageByte, String backgroundUrl, int qr_code_x, int qr_code_y) { + public byte[] generateAndCombineImagesFromUrl2(BufferedImage qrCodeImage, String backgroundUrl, int qr_code_x, int qr_code_y) { File file = null; try { // 直接从URL加载背景图像,移除Redis缓存避免类型转换问题 @@ -201,7 +213,7 @@ public class AppletApiWaterServiceImpl implements AppletApiWaterService { log.debug("从URL加载背景图片: {}", backgroundUrl); // 从字节数组加载小程序码图像 - BufferedImage qrCodeImage = ImageIO.read(new ByteArrayInputStream(qrCodeImageByte)); +// BufferedImage qrCodeImage = ImageIO.read(new ByteArrayInputStream(qrCodeImageByte)); // 使用传入的配置参数,避免重复调用配置服务 diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/util/QRCodeUtil.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/util/QRCodeUtil.java new file mode 100644 index 0000000..aa77d6a --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/util/QRCodeUtil.java @@ -0,0 +1,113 @@ +package org.jeecg.modules.applet.util; + + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import lombok.extern.log4j.Log4j2; + +import javax.imageio.ImageIO; +import javax.swing.filechooser.FileSystemView; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +@Log4j2 +public class QRCodeUtil { + + //CODE_WIDTH:二维码宽度,单位像素 + private static final int CODE_WIDTH = 256; + //CODE_HEIGHT:二维码高度,单位像素 + private static final int CODE_HEIGHT = 256; + //FRONT_COLOR:二维码前景色,0x000000 表示黑色 + private static final int FRONT_COLOR = 0x000000; + //BACKGROUND_COLOR:二维码背景色,0xFFFFFF 表示白色 + //演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白 + private static final int BACKGROUND_COLOR = 0xF4FCFF;//rgb(244 252 255) + + public static void createCodeToFile(String content, File codeImgFileSaveDir, String fileName) { + try { + if (StringUtils.isBlank(content) || StringUtils.isBlank(fileName)) { + return; + } + content = content.trim(); + if (codeImgFileSaveDir==null || codeImgFileSaveDir.isFile()) { + //二维码图片存在目录为空,默认放在桌面... + codeImgFileSaveDir = FileSystemView.getFileSystemView().getHomeDirectory(); + } + if (!codeImgFileSaveDir.exists()) { + //二维码图片存在目录不存在,开始创建... + codeImgFileSaveDir.mkdirs(); + } + + //核心代码-生成二维码 + BufferedImage bufferedImage = getBufferedImage(content); + + File codeImgFile = new File(codeImgFileSaveDir, fileName); + ImageIO.write(bufferedImage, "png", codeImgFile); + + log.info("二维码图片生成成功:" + codeImgFile.getPath()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 生成二维码并输出到输出流, 通常用于输出到网页上进行显示,输出到网页与输出到磁盘上的文件中,区别在于最后一句 ImageIO.write + * write(RenderedImage im,String formatName,File output):写到文件中 + * write(RenderedImage im,String formatName,OutputStream output):输出到输出流中 + * @param content :二维码内容 + * @param outputStream :输出流,比如 HttpServletResponse 的 getOutputStream + */ + public static void createCodeToOutputStream(String content, OutputStream outputStream) { + try { + if (StringUtils.isBlank(content)) { + return; + } + content = content.trim(); + //核心代码-生成二维码 + BufferedImage bufferedImage = getBufferedImage(content); + + //区别就是这一句,输出到输出流中,如果第三个参数是 File,则输出到文件中 + ImageIO.write(bufferedImage, "png", outputStream); + + log.info("二维码图片生成到输出流成功..."); + } catch (Exception e) { + e.printStackTrace(); + } + } + + //核心代码-生成二维码 + public static BufferedImage getBufferedImage(String content) throws WriterException { + + //com.google.zxing.EncodeHintType:编码提示类型,枚举类型 + Map hints = new HashMap(); + + //EncodeHintType.CHARACTER_SET:设置字符编码类型 + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + + //EncodeHintType.ERROR_CORRECTION:设置误差校正 + //ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction + //不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + + //EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近 + hints.put(EncodeHintType.MARGIN, 1); + + MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); + BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints); + BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR); + for (int x = 0; x < CODE_WIDTH; x++) { + for (int y = 0; y < CODE_HEIGHT; y++) { + bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? FRONT_COLOR : BACKGROUND_COLOR); + } + } + return bufferedImage; + } +} diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml index 9b3bfd9..54a6a2c 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml @@ -1,5 +1,5 @@ server: - port: 8002 + port: 8003 undertow: # max-http-post-size: 10MB # 平替 tomcat server.tomcat.max-swallow-siz, undertow该值默认为-1 worker-threads: 16 # 4核CPU标准配置