xdd 1 dienu atpakaļ
vecāks
revīzija
a04f579f04

+ 181 - 175
fs-user-app/src/main/java/com/fs/app/controller/store/UserScrmController.java

@@ -30,6 +30,11 @@ import com.fs.hisStore.vo.FsUserTuiVO;
 import com.fs.utils.FileCacheService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -40,7 +45,11 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.imageio.IIOImage;
 import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.FileImageOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import java.awt.*;
@@ -51,7 +60,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.math.BigDecimal;
 import java.net.URL;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 
 
 @Api("个人中心")
@@ -129,27 +143,29 @@ public class UserScrmController extends AppBaseController {
     @GetMapping("/getTuiImg")
     public R getTuiImg(HttpServletRequest request){
         log.info("获取推荐海报 headers: {}", ServletUtil.getHeaderMap(request));
-        log.info("开始获取推荐海报");
-
-        InputStream templateStream = null;
-        InputStream fontStream = null;
 
         try {
             String userId = getUserId();
             log.info("获取用户ID: {}", userId);
-
             if (StringUtils.isEmpty(userId)) {
                 log.error("用户ID为空");
                 return R.error("用户ID不能为空");
             }
-
+            // 检查是否已存在海报,避免重复生成
+            String resultUrl = "profile/tui/tui-" + userId + ".jpg";
+            File existingPoster = new File(fsConfig.getTuiImgPath() + "/tui-" + userId + ".jpg");
+            if (existingPoster.exists() && existingPoster.lastModified() > System.currentTimeMillis() - 24 * 60 * 60 * 1000) {
+                log.info("使用已存在的海报: {}", resultUrl);
+                return R.ok().put("url", resultUrl);
+            }
             FsUserScrm user = userService.selectFsUserById(Long.parseLong(userId));
             if (user == null) {
                 log.error("未找到用户信息,用户ID: {}", userId);
                 return R.error("用户信息不存在");
             }
-
             log.info("查询到用户信息: 用户ID={}, 昵称={}", userId, user.getNickname());
+
+            // 确保用户有邀请码
             if(StringUtils.isEmpty(user.getUserCode())){
                 log.info("用户邀请码为空,开始生成新的邀请码");
                 FsUserScrm userMap = new FsUserScrm();
@@ -159,213 +175,203 @@ public class UserScrmController extends AppBaseController {
                 user.setUserCode(userMap.getUserCode());
                 log.info("成功生成并更新用户邀请码: {}", userMap.getUserCode());
             }
-
-            String tempDir = System.getProperty("java.io.tmpdir") + File.separator + "poster_temp";
-            File tempDirFile = new File(tempDir);
-            if (!tempDirFile.exists()) {
-                tempDirFile.mkdirs();
-            }
-
-            File tempTemplateFile = new File(tempDir, "template_" + userId + "_" + System.currentTimeMillis() + ".jpg");
-            File tempFontFile = new File(tempDir, "font_" + userId + "_" + System.currentTimeMillis() + ".ttf");
-
-            try {
-                log.info("开始加载海报模板图片");
-                templateStream = fileCacheService.getTemplateStream();
-                FileUtils.copyInputStreamToFile(templateStream, tempTemplateFile);
-                log.info("海报模板图片加载成功: {}", tempTemplateFile.getAbsolutePath());
-                if (!isValidImageFile(tempTemplateFile)) {
-                    log.error("模板图片文件无效或损坏");
-                    return R.error("模板图片文件无效");
-                }
-                log.info("开始加载字体文件");
-                fontStream = fileCacheService.getFontStream();
-                FileUtils.copyInputStreamToFile(fontStream, tempFontFile);
-                log.info("字体文件加载成功: {}", tempFontFile.getAbsolutePath());
-
-            } catch (IOException e) {
-                log.error("加载资源文件失败: {}", e.getMessage(), e);
-                return R.error("加载资源文件失败: " + e.getMessage());
-            }
-            return generatePoster(user, tempTemplateFile, tempFontFile, userId);
-
+            return generatePosterOptimized(user, userId);
         } catch (NumberFormatException e) {
             log.error("用户ID格式错误: {}", e.getMessage(), e);
             return R.error("用户ID格式错误");
         } catch (Exception e){
             log.error("获取推荐海报失败: {}", e.getMessage(), e);
             return R.error("操作异常: " + e.getMessage());
-        } finally {
-            closeQuietly(templateStream);
-            closeQuietly(fontStream);
         }
     }
 
     /**
-     * 生成海报的核心方法
+     * 优化的海报生成方法 - 在内存中完成所有图片操作
      */
-    private R generatePoster(FsUserScrm user, File templateFile, File fontFile, String userId) {
-        File outputFile = null;
-        File qrFile = null;
+    private R generatePosterOptimized(FsUserScrm user, String userId) {
+        log.info("开始生成用户海报,用户ID: {}", userId);
 
         try {
+            // 确保输出目录存在
             String outputDir = fsConfig.getTuiImgPath();
             File outputDirFile = new File(outputDir);
             if (!outputDirFile.exists()) {
                 outputDirFile.mkdirs();
             }
-
-            String outputPath = outputDir + "/tui-" + userId + ".jpg";
-            outputFile = new File(outputPath);
-            log.info("开始生成用户海报,输出路径: {}", outputPath);
-            Font font;
-            try {
-                font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
-                font = font.deriveFont(Font.PLAIN, 50f);
-                log.info("字体加载成功");
-            } catch (Exception e) {
-                log.error("字体加载失败: {}", e.getMessage(), e);
-                font = new Font(Font.SANS_SERIF, Font.PLAIN, 50);
-                log.info("使用系统默认字体作为备选");
-            }
-            String nickname = user.getNickname();
-            if (StringUtils.isEmpty(nickname)) {
-                nickname = "用户";
-            }
-
-            log.info("开始向海报添加用户昵称文本: {}", nickname);
-            try {
-                FileUtils.copyFile(templateFile, outputFile);
-
-                BufferedImage templateImage = ImageIO.read(templateFile);
-                if (templateImage == null) {
-                    log.error("无法读取模板图片");
-                    return R.error("模板图片读取失败");
-                }
-
-                BufferedImage resultImage = new BufferedImage(
-                        templateImage.getWidth(),
-                        templateImage.getHeight(),
-                        BufferedImage.TYPE_INT_RGB
-                );
-
-                Graphics2D g2d = resultImage.createGraphics();
+            // 并行加载资源
+            CompletableFuture<BufferedImage> templateFuture = CompletableFuture.supplyAsync(() -> {
                 try {
-                    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-                    g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
-                    g2d.drawImage(templateImage, 0, 0, null);
-
-                    g2d.setFont(font);
-                    g2d.setColor(Color.BLACK);
-
-                    String nicknameText = nickname + "邀您加入";
-                    FontMetrics fm = g2d.getFontMetrics();
-                    int textWidth = fm.stringWidth(nicknameText);
-                    int x = (templateImage.getWidth() - textWidth) / 2;
-                    int y = 900;
-
-                    g2d.drawString(nicknameText, x, y);
-
-                    String inviteText = "邀请码:" + user.getUserCode();
-                    int inviteTextWidth = fm.stringWidth(inviteText);
-                    int inviteX = (templateImage.getWidth() - inviteTextWidth) / 2;
-                    int inviteY = 1000;
-
-                    g2d.drawString(inviteText, inviteX, inviteY);
-
-                } finally {
-                    g2d.dispose();
+                    log.info("开始加载海报模板图片");
+                    try (InputStream templateStream = fileCacheService.getTemplateStream()) {
+                        BufferedImage image = ImageIO.read(templateStream);
+                        log.info("海报模板图片加载成功");
+                        return image;
+                    }
+                } catch (Exception e) {
+                    log.error("加载模板图片失败: {}", e.getMessage(), e);
+                    throw new RuntimeException("加载模板图片失败", e);
                 }
-
-                ImageIO.write(resultImage, "jpg", outputFile);
-                log.info("文本添加完成");
-
-            } catch (Exception e) {
-                log.error("添加文本时出现异常: {}", e.getMessage(), e);
-                return R.error("添加文本失败: " + e.getMessage());
-            }
-            String qrPath = fsConfig.getTuiImgPath() + "/qr-" + userId + ".png";
-            qrFile = new File(qrPath);
-            log.info("开始生成二维码图片: {}", qrPath);
-            try {
-                String qrContent = fsConfig.getUrl() + "/distribution?userCode=" + user.getUserCode();
-                log.info("生成二维码内容: {}", qrContent);
-                QrCodeUtil.generate(qrContent, 300, 300, qrFile);
-                log.info("二维码生成成功");
-            } catch (Exception e) {
-                log.error("生成二维码失败: {}", e.getMessage(), e);
-                return R.error("生成二维码失败: " + e.getMessage());
-            }
-            try {
-                log.info("开始将二维码图片合并到海报");
-                BufferedImage posterImage = ImageIO.read(outputFile);
-                BufferedImage qrImage = ImageIO.read(qrFile);
-
-                if (posterImage == null || qrImage == null) {
-                    log.error("读取图片失败,无法合并二维码");
-                    return R.error("图片读取失败");
+            });
+            CompletableFuture<Font> fontFuture = CompletableFuture.supplyAsync(() -> {
+                try {
+                    log.info("开始加载字体文件");
+                    try (InputStream fontStream = fileCacheService.getFontStream()) {
+                        Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
+                        font = font.deriveFont(Font.PLAIN, 50f);
+                        log.info("字体文件加载成功");
+                        return font;
+                    }
+                } catch (Exception e) {
+                    log.error("字体加载失败,使用默认字体: {}", e.getMessage());
+                    return new Font(Font.SANS_SERIF, Font.PLAIN, 50);
                 }
-
-                Graphics2D g2d = posterImage.createGraphics();
+            });
+            CompletableFuture<BufferedImage> qrFuture = CompletableFuture.supplyAsync(() -> {
                 try {
-                    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-
-                    int qrX = posterImage.getWidth() - qrImage.getWidth() - 50;
-                    int qrY = 900;
+                    log.info("开始生成二维码");
+                    String qrContent = fsConfig.getUrl() + "/distribution?userCode=" + user.getUserCode();
+                    log.info("生成二维码内容: {}", qrContent);
 
-                    g2d.drawImage(qrImage, qrX, qrY, null);
-                } finally {
-                    g2d.dispose();
+                    // 直接生成BufferedImage,不保存临时文件
+                    return QrCodeUtil.generate(qrContent, 300, 300);
+                } catch (Exception e) {
+                    log.error("生成二维码失败: {}", e.getMessage(), e);
+                    throw new RuntimeException("生成二维码失败", e);
                 }
+            });
+            // 等待所有资源加载完成
+            BufferedImage templateImage = templateFuture.get(10, TimeUnit.SECONDS);
+            Font font = fontFuture.get(10, TimeUnit.SECONDS);
+            BufferedImage qrImage = qrFuture.get(10, TimeUnit.SECONDS);
+            if (templateImage == null) {
+                log.error("模板图片加载失败");
+                return R.error("模板图片加载失败");
+            }
+            // 在内存中完成所有图片合成操作
+            BufferedImage finalImage = createFinalPoster(templateImage, font, qrImage, user);
+            // 只进行一次文件写入
+            String outputPath = outputDir + "/tui-" + userId + ".jpg";
+            File outputFile = new File(outputPath);
 
-                ImageIO.write(posterImage, "jpg", outputFile);
-                log.info("二维码合并完成");
+            // 使用高质量的JPEG编码
+            writeHighQualityJPEG(finalImage, outputFile);
 
-            } catch (Exception e) {
-                log.error("合并二维码时出现异常: {}", e.getMessage(), e);
-                return R.error("合并二维码失败: " + e.getMessage());
-            }
             String resultUrl = "profile/tui/tui-" + userId + ".jpg";
             log.info("海报生成完成,返回URL: {}", resultUrl);
             return R.ok().put("url", resultUrl);
-
         } catch (Exception e) {
             log.error("生成海报过程出现异常: {}", e.getMessage(), e);
             return R.error("生成海报失败: " + e.getMessage());
-        } finally {
-            cleanupTempFiles(qrFile);
         }
     }
-    private boolean isValidImageFile(File imageFile) {
+
+    /**
+     * 在内存中完成所有图片合成操作
+     */
+    private BufferedImage createFinalPoster(BufferedImage templateImage, Font font,
+                                            BufferedImage qrImage, FsUserScrm user) {
+        log.info("开始在内存中合成最终海报");
+
+        // 创建最终图片
+        BufferedImage finalImage = new BufferedImage(
+                templateImage.getWidth(),
+                templateImage.getHeight(),
+                BufferedImage.TYPE_INT_RGB
+        );
+
+        Graphics2D g2d = finalImage.createGraphics();
         try {
-            BufferedImage image = ImageIO.read(imageFile);
-            return image != null && image.getWidth() > 0 && image.getHeight() > 0;
-        } catch (Exception e) {
-            log.error("验证图片文件失败: {}", e.getMessage());
-            return false;
+            // 设置高质量渲染
+            setupHighQualityRendering(g2d);
+
+            // 绘制模板图片
+            g2d.drawImage(templateImage, 0, 0, null);
+
+            // 绘制文本
+            drawUserText(g2d, font, user, templateImage.getWidth());
+
+            // 绘制二维码
+            drawQRCode(g2d, qrImage, templateImage.getWidth());
+
+            log.info("海报合成完成");
+            return finalImage;
+
+        } finally {
+            g2d.dispose();
         }
     }
-    private void closeQuietly(InputStream stream) {
-        if (stream != null) {
-            try {
-                stream.close();
-            } catch (IOException e) {
-                log.warn("关闭流时出现异常: {}", e.getMessage());
-            }
-        }
+
+    /**
+     * 设置高质量渲染参数
+     */
+    private void setupHighQualityRendering(Graphics2D g2d) {
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
     }
-    private void cleanupTempFiles(File... files) {
-        for (File file : files) {
-            if (file != null && file.exists()) {
-                try {
-                    file.delete();
-                    log.debug("删除临时文件: {}", file.getAbsolutePath());
-                } catch (Exception e) {
-                    log.warn("删除临时文件失败: {}", e.getMessage());
-                }
-            }
+
+    /**
+     * 绘制用户文本信息
+     */
+    private void drawUserText(Graphics2D g2d, Font font, FsUserScrm user, int imageWidth) {
+        g2d.setFont(font);
+        g2d.setColor(Color.BLACK);
+
+        String nickname = StringUtils.isEmpty(user.getNickname()) ? "用户" : user.getNickname();
+        FontMetrics fm = g2d.getFontMetrics();
+
+        // 绘制邀请文本
+        String nicknameText = nickname + "邀您加入";
+        int textWidth = fm.stringWidth(nicknameText);
+        int x = (imageWidth - textWidth) / 2;
+        int y = 900;
+        g2d.drawString(nicknameText, x, y);
+
+        // 绘制邀请码
+        String inviteText = "邀请码:" + user.getUserCode();
+        int inviteTextWidth = fm.stringWidth(inviteText);
+        int inviteX = (imageWidth - inviteTextWidth) / 2;
+        int inviteY = 1000;
+        g2d.drawString(inviteText, inviteX, inviteY);
+
+        log.info("用户文本绘制完成");
+    }
+
+    /**
+     * 绘制二维码
+     */
+    private void drawQRCode(Graphics2D g2d, BufferedImage qrImage, int imageWidth) {
+        int qrX = imageWidth - qrImage.getWidth() - 50;
+        int qrY = 900;
+        g2d.drawImage(qrImage, qrX, qrY, null);
+        log.info("二维码绘制完成");
+    }
+    /**
+     * 高质量JPEG图片写入
+     */
+    private void writeHighQualityJPEG(BufferedImage image, File outputFile) throws IOException {
+        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
+        if (!writers.hasNext()) {
+            throw new IllegalStateException("No JPEG writers found");
+        }
+
+        ImageWriter writer = writers.next();
+        ImageWriteParam param = writer.getDefaultWriteParam();
+
+        if (param.canWriteCompressed()) {
+            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            param.setCompressionQuality(0.9f); // 高质量压缩
         }
+
+        try (FileImageOutputStream output = new FileImageOutputStream(outputFile)) {
+            writer.setOutput(output);
+            writer.write(null, new IIOImage(image, null, null), param);
+        } finally {
+            writer.dispose();
+        }
+
+        log.info("高质量JPEG图片写入完成: {}", outputFile.getAbsolutePath());
     }
 
     @Login

+ 43 - 0
fs-user-app/src/main/java/com/fs/app/utils/QrCodeUtil.java

@@ -0,0 +1,43 @@
+package com.fs.app.utils;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+     * 扩展二维码工具类,支持直接返回BufferedImage
+     */
+    public class QrCodeUtil {
+
+        public static BufferedImage generateImage(String content, int width, int height) throws Exception {
+            Map<EncodeHintType, Object> hints = new HashMap<>();
+            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+            hints.put(EncodeHintType.MARGIN, 1);
+
+            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
+
+            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+            for (int x = 0; x < width; x++) {
+                for (int y = 0; y < height; y++) {
+                    image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
+                }
+            }
+
+            return image;
+        }
+
+        // 保持原有的文件生成方法兼容性
+        public static void generate(String content, int width, int height, File file) throws Exception {
+            BufferedImage image = generateImage(content, width, height);
+            ImageIO.write(image, "PNG", file);
+        }
+    }