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标准配置