【详尽-实战篇】使用Springboot生成自带logo或者图片的二维码-扫描二维码可以跳转到指定的页面-Zing-core

先上效果图
项目源码:https://download.csdn.net/download/qq_43055855/89891285
源码地址
在这里插入图片描述

手机扫描二维码跳转到指定网页

在这里插入图片描述

概述

这个项目是一个基于 Java 的二维码生成与解析工具,主要由 QRCodeUtil 和 QRCodeController 两个类组成。它利用了 Google ZXing 库来实现二维码的生成和解析功能,并通过 Spring 框架进行整合,提供了 RESTful API 接口用于生成带有 logo 的二维码。

主要功能

生成带有 logo 的二维码:

  1. 用户可以传入二维码内容和 logo 文件路径(或字节数组),生成带有指定 logo 的二维码图像。通过设置编码提示类型,包括字符集、错误纠正级别和边距等,确保二维码的可读性和容错性。
  2. 计算 logo 在二维码中的合适大小,并将其居中绘制在二维码图像上,使生成的二维码更加美观和具有辨识度。
  3. 保存二维码到文件或输出流:
  4. 可以将生成的二维码保存到指定的文件目录中,若目录不存在则自动创建。如果未指定文件名,则随机生成一个以当前时间戳命名的 png 格式文件。
  5. 也可以将二维码生成到输出流中,并以 Base64 编码的形式返回给客户端,方便在网页等环境中直接使用。
  6. 解析二维码内容:提供了两种方式解析二维码内容,分别是解析本地二维码图片文件和解析网络二维码图片地址。通过将图像转换为亮度源,再创建二进制位图,最后使用解码提示类型进行解码,获取二维码中的文本内容。
  7. RESTful API 接口:QRCodeController 类提供了一个 POST 请求的接口 /qrCode,接受二维码内容和可选的 logo 文件上传。根据上传的 logo 文件情况,生成带有或不带 logo 的二维码,并将其写入 HttpServletResponse 的输出流中返回给客户端。

新建maven项目构建springboot

在这里插入图片描述

技术要点

Google ZXing 库的使用:

  • 熟练掌握了 Google ZXing 库中各种类的使用,如 MultiFormatWriter 用于生成二维码的位矩阵,BitMatrix表示二维码的位数据,BufferedImageLuminanceSource 和 HybridBinarizer用于二维码的解析等。图像处理:
  • 利用 Java 的 BufferedImageGraphics2D 类进行图像处理,包括调整 logo 图像的大小、在二维码图像上绘制 logo 等操作。
  • 文件操作和输入输出流:使用 ImageIO 进行图像文件的读取和写入操作,以及处理文件的存在性检查、目录创建等。同时,熟练运用 OutputStreamByteArrayOutputStream 等输入输出流进行数据的传输和处理。
  • Spring 框架整合:
    通过 @RestController 和 @Slf4j 等注解,将该工具整合到 Spring 框架中,方便进行日志记录和提供 RESTful API 服务。
    参数校验和错误处理:
    在接口方法中对传入的参数进行校验,如检查二维码内容是否为空、上传的文件是否为有效的图片文件等。同时,对可能出现的异常进行了捕获和处理,记录错误日志并返回合适的响应。

pom.xml文件代码


    <!-- 定义这是一个springboot项目 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.18.RELEASE</version>
    </parent>

    <!-- 定义这是一个web应用 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <!-- zxing生成二维码 -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
    </dependencies>

我们需要lombock插件thymeleaf魔板引擎,还有两个核心二维码库
zing的core和javase

在templates下定义yml文件映射

spring:
  thymeleaf:
    prefix: classpath:/templates/

在静态资源文件resources下的templates里边新建一个html文件,注意需自己建文件夹

<!DOCTYPE html>
<!-- 声明文档类型 -->
<html lang="en">
<!-- HTML文档头部 -->
<head>
    <!-- 设置字符集为UTF-8 -->
    <meta charset="UTF-8">
    <!-- 页面标题 -->
    <title>二维码生成器</title>
    <!-- 内联样式 -->
    <style type="text/css">
        /* 设置文本区域的字体大小、宽度和高度 */
        textarea {
            font-size: 30px;
            width: 400px;
            height: 200px;
        }
        /* 设置提示信息的样式,包括颜色和默认不显示 */
        .hint {
            color: red;
            display: none;
        }
        /* 设置二维码容器的样式,包括居中、最小高度等 */
        .qrCodeContainer {
            display: flex;
            justify-content: center;
            align-items: center;
            width: 100%;
            min-height: 100vh;
            position: relative;
            border: 2px solid sandybrown;
            padding: 20px;
            box-sizing: border-box;
        }
    </style>
    <!-- 引入jQuery库 -->
    <script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
    <!-- JavaScript代码 -->
    <script type="text/javascript">
        // 当DOM准备就绪时执行
        $(function () {
            // 当按钮被点击时触发
            $("button").click(function () {
                // 获取文本区域的值
                var codeContent = $("textarea").val();
                // 获取文件输入框的DOM元素
                var logoInput = $("#logoInput")[0];
                // 获取用户选择的文件
                var logoFile = logoInput.files[0];
                // 检查二维码内容是否为空
                if (codeContent.trim() === "") {
                    // 显示提示信息
                    $(".hint").text("二维码内容不能为空").fadeIn(500);
                } else {
                    // 清除提示信息
                    $(".hint").text("").fadeOut(500);
                    // 创建FormData对象存储数据
                    var formData = new FormData();
                    // 添加二维码内容到表单数据
                    formData.append("codeContent", codeContent);
                    // 如果用户选择了logo文件,则将其添加到表单数据
                    if (logoFile) {
                        formData.append("logoFile", logoFile);
                    }
                    // 发送AJAX请求
                    $.ajax({
                        // 请求URL
                        url: "/qrCode",
                        // 请求类型
                        type: "POST",
                        // 请求数据
                        data: formData,
                        // 不对数据进行序列化处理
                        processData: false,
                        // 不设置content-type头
                        contentType: false,
                        // 成功回调
                        success: function (data) {
                            // 检查返回的数据是否符合base64图片格式
                            if (/^data:image\/png;base64,[A-Za-z0-9+/]{42,}={0,2}$/.test(data)) {
                                console.log('Received valid base64 data:', data);
                                // 获取画布元素
                                var canvas = document.getElementById('qrCanvas');
                                // 获取画布上下文
                                var ctx = canvas.getContext('2d');
                                // 创建图片对象
                                var img = new Image();
                                // 图片加载完成时绘制到画布
                                img.onload = function () {
                                    canvas.width = img.width;
                                    canvas.height = img.height;
                                    ctx.drawImage(img, 0, 0);
                                    // document.body.appendChild(canvas); // 可选操作:将画布添加到body
                                };
                                // 设置图片源为返回的base64数据
                                img.src = data;
                            } else {
                                console.error('Invalid base64 data received:', data);
                            }
                        },
                        // 错误回调
                        error: function (error) {
                            console.error(error);
                        }
                    });
                }
            });
        });
    </script>
</head>
<!-- HTML文档主体 -->
<body>
<!-- 输入框,用户在此输入要生成二维码的内容 -->
<textarea placeholder="请输入要生成二维码内容(网址)..."></textarea><br>
<!-- 文件输入框,用户可以选择logo图片 -->
<input type="file" id="logoInput"><br>
<!-- 按钮,用户点击生成二维码 -->
<button>点击我生成二维码</button>
<!-- 提示信息 -->
<span class="hint"></span>
<!-- 二维码容器,包含画布用于显示生成的二维码 -->
<div class="qrCodeContainer">
    <canvas id="qrCanvas"></canvas>
</div>
</body>
</html>

这个html文件主要是前端页面的展示,包括样式的设置向后端发送ajax请求数据,调用接口成功回调后的数据渲染。 核心代码:

 // 发送AJAX请求
                    $.ajax({
                        // 请求URL
                        url: "/qrCode",
                        // 请求类型
                        type: "POST",
                        // 请求数据
                        data: formData,
                        // 不对数据进行序列化处理
                        processData: false,
                        // 不设置content-type头
                        contentType: false,
                        // 成功回调
                        success: function (data) {
                            // 检查返回的数据是否符合base64图片格式
                            if (/^data:image\/png;base64,[A-Za-z0-9+/]{42,}={0,2}$/.test(data)) {
                                console.log('Received valid base64 data:', data);
                                // 获取画布元素
                                var canvas = document.getElementById('qrCanvas');
                                // 获取画布上下文
                                var ctx = canvas.getContext('2d');
                                // 创建图片对象
                                var img = new Image();
                                // 图片加载完成时绘制到画布
                                img.onload = function () {
                                    canvas.width = img.width;
                                    canvas.height = img.height;
                                    ctx.drawImage(img, 0, 0);
                                    // document.body.appendChild(canvas); // 可选操作:将画布添加到body
                                };
                                // 设置图片源为返回的base64数据
                                img.src = data;
                            } else {
                                console.error('Invalid base64 data received:', data);
                            }
                        },
                        // 错误回调
                        error: function (error) {
                            console.error(error);
                        }
                    });

在控制层中Controller调用静态资源文件下的html`在这里插入代码片

/**
     * 处理HTTP GET请求,当访问"/index"路径时触发。
     *
     * @return 返回字符串"cs.html",表示转发到名为"cs.html"的视图。
     */
    @RequestMapping("/index") // 映射请求路径为/index
    public String index(){ // 方法名,返回一个字符串
        return "cs.html"; // 返回值,指示转发到名为cs.html的视图
    }

新建二维码工具类QRCodeUtil

定义二维码的基本尺寸和参数:

// 定义二维码的基本尺寸和颜色
    private static final int CODE_WIDTH = 500; // 二维码宽度,单位像素。
    private static final int CODE_HEIGHT = 500; // 二维码高度,单位像素。
    private static final int FRONT_COLOR = 0x000000; // 二维码前景色,0x000000 表示黑色。
    private static final int BACKGROUND_COLOR = 0xFFFFFF; // 二维码背景色,0xFFFFFF 表示白色。

新建一个BufferedImage类型的方法用于存储图像

private static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
        BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
        // 创建一个新的 BufferedImage 对象,用于存储调整大小后的图像,设置宽度、高度和类型(TYPE_INT_RGB,表示 24 位 RGB 颜色)。

        Graphics2D g = resizedImage.createGraphics();
        // 在新的 BufferedImage 对象上创建一个 Graphics2D 对象,用于绘制调整后的图像。

        g.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
        // 在新的 BufferedImage 对象上绘制原始图像,指定目标宽度和高度,使其调整大小。

        g.dispose();
        // 释放 Graphics2D 对象占用的资源。

        return resizedImage;
        // 返回调整大小后的 BufferedImage 对象。
    }

接着就是创建二维码并保存文件

  /**
     * 创建二维码并保存到文件。
     *
     * @param codeContent        二维码内容
     * @param codeImgFileSaveDir 二维码图片保存的目录
     * @param fileName           二维码图片文件名称
     */
    public static void createCodeToFile(String codeContent, File codeImgFileSaveDir, String fileName, String logoPath) {
        try {
            if (codeContent == null || "".equals(codeContent)) {
                log.info("二维码内容为空,不进行操作...");
                return;
            }
            codeContent = codeContent.trim();
            // 如果二维码内容为空,记录日志并返回,不进行后续操作。否则,去除二维码内容两端的空白字符。

            if (codeImgFileSaveDir == null || codeImgFileSaveDir.isFile()) {
                codeImgFileSaveDir = FileSystemView.getFileSystemView().getHomeDirectory();
                log.info("二维码图片存在目录为空,默认放在桌面...");
            }
            // 如果二维码图片保存目录为空或为文件(而不是目录),将保存目录设置为桌面目录,并记录日志。

            if (!codeImgFileSaveDir.exists()) {
                codeImgFileSaveDir.mkdirs();
                log.info("二维码图片存在目录不存在,开始创建...");
            }
            // 如果保存目录不存在,创建该目录,并记录日志。

            if (fileName == null || "".equals(fileName)) {
                fileName = System.currentTimeMillis() + ".png";
                log.info("二维码图片文件名为空,随机生成 png 格式图片...");
            }
            // 如果二维码图片文件名为空,使用当前时间戳作为文件名,并设置为 png 格式,记录日志。

            // 使用新方法生成带有 logo 的二维码图像
            BufferedImage bufferedImage = getBufferedImageWithLogo(codeContent, logoPath);

            File codeImgFile = new File(codeImgFileSaveDir, fileName);
            // 创建一个 File 对象,表示二维码图片文件,使用保存目录和文件名。

            ImageIO.write(bufferedImage, "png", codeImgFile);
            // 使用 ImageIO 将带有 logo 的二维码图像写入文件,指定文件格式为 png。

            log.info("二维码图片生成成功:" + codeImgFile.getPath());
            // 记录日志,打印二维码图片生成成功的消息和文件路径。
        } catch (Exception e) {
            e.printStackTrace();
            // 如果在生成二维码图片过程中发生异常,打印异常堆栈信息。
        }
    }

创建二维码输出流的方法


    public static void createCodeToOutputStream(String codeContent, OutputStream outputStream, byte[] logoBytes) {
        try {
            if (codeContent == null || "".equals(codeContent.trim())) {
                log.info("二维码内容为空,不进行操作...");
                return;
            }
            codeContent = codeContent.trim();
            // 如果二维码内容为空,记录日志并返回,不进行后续操作。否则,去除二维码内容两端的空白字符。

            BufferedImage bufferedImage = getBufferedImageWithLogo(codeContent, logoBytes);

            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                ImageIO.write(bufferedImage, "png", baos);
                // 将带有 logo 的二维码图像写入 ByteArrayOutputStream,指定文件格式为 png。

                byte[] imageBytes = baos.toByteArray();
                // 获取 ByteArrayOutputStream 中的字节数组,表示二维码图像数据。

                String base64Image = Base64.getEncoder().encodeToString(imageBytes);
                // 使用 Base64 编码器将二维码图像数据编码为字符串。

                outputStream.write(("data:image/png;base64," + base64Image).getBytes());
                // 将包含 Base64 编码的二维码图像数据写入输出流,格式为"data:image/png;base64,"加上编码后的字符串。

                log.info("二维码图片生成到输出流成功...");
                // 记录日志,打印二维码图片生成到输出流成功的消息。
            } catch (IOException e) {
                log.error("输出流写入错误: {}", e.getMessage());
                // 如果在写入输出流过程中发生 IOException,记录错误日志,打印错误消息。
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("发生错误: {}!", e.getMessage());
            // 如果在生成二维码图片过程中发生其他异常,打印异常堆栈信息,并记录错误日志,打印错误消息。
        }
    }

这是图像的尺寸

/**
     *
     * @param originalImage  // 原始图像
     * @param targetWidth   目标宽度
     * @param targetHeight  目标高度
     * @return
     */
    private static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
        BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
        // 创建一个新的 BufferedImage 对象,用于存储调整大小后的图像,设置宽度、高度和类型(TYPE_INT_RGB,表示 24 位 RGB 颜色)。

        Graphics2D g = resizedImage.createGraphics();
        // 在新的 BufferedImage 对象上创建一个 Graphics2D 对象,用于绘制调整后的图像。

        g.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
        // 在新的 BufferedImage 对象上绘制原始图像,指定目标宽度和高度,使其调整大小。

        g.dispose();
        // 释放 Graphics2D 对象占用的资源。

        return resizedImage;
        // 返回调整大小后的 BufferedImage 对象。
    }

获取图片logo

 public static BufferedImage getBufferedImageWithLogo(String codeContent, byte[] logoBytes) {
        try {
            log.info("生成带有 logo 的二维码,内容:{},logo 字节数组长度:{}", codeContent, logoBytes!= null? logoBytes.length : 0);
            // 记录日志,表明正在生成带有 logo 的二维码,并打印二维码内容和 logo 字节数组的长度(如果不为 null)。

            // 设置编码提示类型
            Map<EncodeHintType, Object> hints = new HashMap<>();
            // 创建一个 HashMap 来存储编码提示类型和对应的值。

            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            // 设置编码字符集为 UTF-8,确保二维码中的文本可以正确编码和解码。

            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            // 设置二维码的错误纠正级别为 M(中等),提高二维码的容错能力。

            hints.put(EncodeHintType.MARGIN, 1);
            // 设置二维码的边距为 1,控制二维码周围的空白区域大小。

            // 创建多格式写入器实例
            MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
            // 创建一个 MultiFormatWriter 对象,用于生成不同格式的条形码和二维码。

            // 生成二维码的 BitMatrix
            BitMatrix bitMatrix = multiFormatWriter.encode(codeContent, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
            // 使用 MultiFormatWriter 的 encode 方法生成二维码的位矩阵,传入二维码内容、格式(QR_CODE)、宽度、高度和编码提示类型。

            // 创建二维码图像
            BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR);
            // 创建一个 BufferedImage 对象,用于存储二维码图像,设置图像的宽度、高度和类型(TYPE_INT_BGR,表示 24 位 RGB 颜色)。

            // 根据 BitMatrix 填充图像
            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);
                }
            }
            // 遍历二维码的位矩阵,根据位矩阵中的值设置图像的像素颜色,将前景色(黑色)或背景色(白色)设置到 BufferedImage 中。

            // 添加 logo
            if (logoBytes!= null && logoBytes.length > 0) {
                log.info("添加 logo,字节数组长度:{}", logoBytes.length);
                // 如果 logo 字节数组不为 null 且长度大于 0,记录日志并打印 logo 字节数组的长度。

                BufferedImage logoImage = ImageIO.read(new ByteArrayInputStream(logoBytes));
                // 使用 ImageIO 从字节数组输入流中读取 logo 图像,创建一个 BufferedImage 对象表示 logo 图像。

                int logoWidth = bufferedImage.getWidth() / 4;
                int logoHeight = bufferedImage.getHeight() / 4;
                // 计算 logo 图像在二维码中的大小,这里将 logo 的宽度和高度设置为二维码宽度和高度的四分之一。

                BufferedImage resizedLogoImage = resizeImage(logoImage, logoWidth, logoHeight);
                // 调用 resizeImage 方法,将 logo 图像调整为计算出的大小,得到调整后的 BufferedImage 对象。

                Graphics2D g = bufferedImage.createGraphics();
                // 在二维码图像上创建一个 Graphics2D 对象,用于绘制 logo。

                int x = (bufferedImage.getWidth() - logoWidth) / 2;
                int y = (bufferedImage.getHeight() - logoHeight) / 2;
                // 计算 logo 在二维码图像中的位置,使其居中显示。

                g.drawImage(resizedLogoImage, x, y, null);
                // 在二维码图像上绘制调整后的 logo 图像,指定位置为计算出的坐标。

                g.dispose();
                // 释放 Graphics2D 对象占用的资源。
            }

            return bufferedImage;
            // 返回带有 logo 的二维码图像。
        } catch (WriterException | IOException e) {
            e.printStackTrace();
            log.error("生成带有 logo 的二维码时发生错误:{}", e.getMessage());
            // 如果在生成二维码过程中发生异常,打印异常堆栈信息,并记录错误日志,打印错误消息。

            // 可以根据需要返回一个默认的图像或者抛出一个自定义异常
            return null;
            // 在发生错误时,返回 null,表示生成二维码失败。
        }
    }

获取二维码上面的logo内容

  /**
     *
     * @param codeContent  二维码的内容
     * @param logoPath    logo的路径
     * @return
     */
    public static BufferedImage getBufferedImageWithLogo(String codeContent, String logoPath) {
        try {
            log.info("生成带有 logo 的二维码,内容:{},logo 路径:{}", codeContent, logoPath);
            // 记录日志,表明正在生成带有 logo 的二维码,并打印二维码内容和 logo 路径。

            // 设置编码提示类型
            Map<EncodeHintType, Object> hints = new HashMap<>();
            // 创建一个 HashMap 来存储编码提示类型和对应的值。

            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            // 设置编码字符集为 UTF-8,确保二维码中的文本可以正确编码和解码。

            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            // 设置二维码的错误纠正级别为 M(中等),提高二维码的容错能力。

            hints.put(EncodeHintType.MARGIN, 1);
            // 设置二维码的边距为 1,控制二维码周围的空白区域大小。

            // 创建多格式写入器实例
            MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
            // 创建一个 MultiFormatWriter 对象,用于生成不同格式的条形码和二维码。

            // 生成二维码的 BitMatrix
            BitMatrix bitMatrix = multiFormatWriter.encode(codeContent, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
            // 使用 MultiFormatWriter 的 encode 方法生成二维码的位矩阵,传入二维码内容、格式(QR_CODE)、宽度、高度和编码提示类型。

            // 创建二维码图像
            BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR);
            // 创建一个 BufferedImage 对象,用于存储二维码图像,设置图像的宽度、高度和类型(TYPE_INT_BGR,表示 24 位 RGB 颜色)。

            // 根据 BitMatrix 填充图像
            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);
                }
            }
            // 遍历二维码的位矩阵,根据位矩阵中的值设置图像的像素颜色,将前景色(黑色)或背景色(白色)设置到 BufferedImage 中。

            // 添加 logo
            if (logoPath!= null &&!"".equals(logoPath)) {
                log.info("添加 logo,路径:{}", logoPath);
                // 如果 logo 路径不为空且不为空字符串,记录日志并打印 logo 路径。

                BufferedImage logoImage = ImageIO.read(new File(logoPath));
                // 使用 ImageIO 读取 logo 图像文件,创建一个 BufferedImage 对象表示 logo 图像。

                int logoWidth = bufferedImage.getWidth() / 5;
                int logoHeight = bufferedImage.getHeight() / 5;
                // 计算 logo 图像在二维码中的大小,这里将 logo 的宽度和高度设置为二维码宽度和高度的五分之一。

                BufferedImage resizedLogoImage = resizeImage(logoImage, logoWidth, logoHeight);
                // 调用 resizeImage 方法,将 logo 图像调整为计算出的大小,得到调整后的 BufferedImage 对象。

                Graphics2D g = bufferedImage.createGraphics();
                // 在二维码图像上创建一个 Graphics2D 对象,用于绘制 logo。

                int x = (bufferedImage.getWidth() - logoWidth) / 2;
                int y = (bufferedImage.getHeight() - logoHeight) / 2;
                // 计算 logo 在二维码图像中的位置,使其居中显示。

                g.drawImage(resizedLogoImage, x, y, null);
                // 在二维码图像上绘制调整后的 logo 图像,指定位置为计算出的坐标。

                g.dispose();
                // 释放 Graphics2D 对象占用的资源。
            }

            return bufferedImage;
            // 返回带有 logo 的二维码图像。
        } catch (WriterException | IOException e) {
            e.printStackTrace();
            log.error("生成带有 logo 的二维码时发生错误:{}", e.getMessage());
            // 如果在生成二维码过程中发生异常,打印异常堆栈信息,并记录错误日志,打印错误消息。

            // 可以根据需要返回一个默认的图像或者抛出一个自定义异常
            return null;
            // 在发生错误时,返回 null,表示生成二维码失败。
        }
    }

解析网络二维码图片内容和本地二维码图片内容

 /**
     * 解析本地二维码图片内容。
     *
     * @param file 本地二维码图片文件
     * @return 二维码内容
     * @throws Exception 如果解析失败
     */
    public static String parseQRCodeByFile(File file) {
        String resultStr = null;
        // 初始化结果字符串为 null,表示如果解析失败,将返回 null。

        if (file == null || file.isDirectory() ||!file.exists()) { // 检查文件是否有效
            return resultStr;
        }
        // 如果文件为空、是目录或不存在,直接返回 null,表示无法解析。

        try {
            // 读取本地图片文件
            BufferedImage bufferedImage = ImageIO.read(file);
            // 使用 ImageIO 读取本地二维码图片文件,创建一个 BufferedImage 对象表示图片。

            // 将 BufferedImage 转换为 LuminanceSource
            BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
            // 创建一个 BufferedImageLuminanceSource 对象,将读取的 BufferedImage 转换为亮度源,用于二维码解析。

            // 创建二进制位图
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            // 创建一个 BinaryBitmap 对象,使用 HybridBinarizer 对亮度源进行二值化处理,得到二进制位图,用于二维码解析。

            // 设置解码提示类型
            Hashtable hints = new Hashtable<>();
            // 创建一个 Hashtable 来存储解码提示类型和对应的值。

            hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
            // 设置解码字符集为 UTF-8,确保二维码中的文本可以正确解码。

            // 解码二维码
            Result result = new MultiFormatReader().decode(bitmap, hints);
            // 使用 MultiFormatReader 对二进制位图进行解码,传入解码提示类型,得到 Result 对象表示解码结果。

            resultStr = result.getText();
            // 从解码结果中获取二维码的文本内容,并赋值给 resultStr。
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            e.printStackTrace();
            log.error("图片非二维码图片, 路径是: {}!", file.getPath());
            // 如果找不到二维码或者图片不是二维码,打印错误堆栈信息,并记录错误日志,打印图片路径。
        }
        return resultStr;
        // 返回解析得到的二维码内容,如果解析失败,返回 null。
    }

    /**
     * 解析网络二维码图片内容。
     *
     * @param url 二维码图片网络地址
     * @return 二维码内容
     * @throws Exception 如果解析失败
     */
    public static String parseQRCodeByUrl(URL url) {
        String resultStr = null;
        // 初始化结果字符串为 null,表示如果解析失败,将返回 null。

        if (url == null) { // 检查 URL 是否有效
            return resultStr;
        }
        // 如果 URL 为空,直接返回 null,表示无法解析。

        try {
            // 读取网络图片文件
            BufferedImage bufferedImage = ImageIO.read(url);
            // 使用 ImageIO 读取网络二维码图片文件,创建一个 BufferedImage 对象表示图片。

            // 将 BufferedImage 转换为 LuminanceSource
            BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
            // 创建一个 BufferedImageLuminanceSource 对象,将读取的 BufferedImage 转换为亮度源,用于二维码解析。

            // 创建二进制位图
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            // 创建一个 BinaryBitmap 对象,使用 HybridBinarizer 对亮度源进行二值化处理,得到二进制位图,用于二维码解析。

            // 设置解码提示类型
            Hashtable hints = new Hashtable<>();
            // 创建一个 Hashtable 来存储解码提示类型和对应的值。

            hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
            // 设置解码字符集为 UTF-8,确保二维码中的文本可以正确解码。

            // 解码二维码
            Result result = new MultiFormatReader().decode(bitmap, hints);
            // 使用 MultiFormatReader 对二进制位图进行解码,传入解码提示类型,得到 Result 对象表示解码结果。

            resultStr = result.getText();
            // 从解码结果中获取二维码的文本内容,并赋值给 resultStr。
        } catch (IOException e) {
            e.printStackTrace();
            log.error("二维码图片地址错误, 地址是: {}!", url);
            // 如果读取网络图片时发生 IOException,打印错误堆栈信息,并记录错误日志,打印图片地址。
        } catch (NotFoundException e) {
            e.printStackTrace();
            log.error("图片非二维码图片, 地址是: {}!", url);
            // 如果找不到二维码或者图片不是二维码,打印错误堆栈信息,并记录错误日志,打印图片地址。
        }
        return resultStr;
        // 返回解析得到的二维码内容,如果解析失败,返回 null。
    }

前端调用控制层接口代码

package com.huae.MainApplication.controller;

import com.huae.MainApplication.controller.QRCodeUtil;
// 导入本项目中 QRCodeUtil 工具类。

import lombok.extern.slf4j.Slf4j;
// 使用 Lombok 生成日志对象。

import org.springframework.http.MediaType;
// 导入 Spring 中的 MediaType,用于指定请求的媒体类型。

import org.springframework.web.bind.annotation.PostMapping;
// 导入 Spring 的注解,用于定义 POST 请求的处理方法。

import org.springframework.web.bind.annotation.RequestParam;
// 导入 Spring 的注解,用于从请求中获取参数。

import org.springframework.web.bind.annotation.RestController;
// 导入 Spring 的注解,标记该类为 RESTful Web 服务控制器。

import org.springframework.web.multipart.MultipartFile;
// 导入 Spring 的类,用于处理文件上传。

import javax.imageio.ImageIO;
// 导入 Java 的图像输入输出工具类。

import javax.servlet.http.HttpServletResponse;
// 导入 Servlet 的响应对象。

import java.awt.image.BufferedImage;
// 导入 Java 的缓冲图像类。

import java.io.File;
// 导入 Java 的文件类。

import java.io.IOException;
// 导入 Java 的输入输出异常类。

import java.io.OutputStream;
// 导入 Java 的输出流类。

import static com.huae.MainApplication.controller.QRCodeUtil.getBufferedImageWithLogo;
// 导入静态方法,用于生成带有 logo 的二维码图像。

@RestController
@Slf4j
public class QRCodeController {
    @PostMapping(value = "qrCode", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    // 定义一个 POST 请求的处理方法,请求路径为"qrCode",接受的请求内容类型为 multipart/form-data。
    public void createQRCodeWithLogo(@RequestParam("codeContent") String codeContent,
                                     // 从请求中获取名为"codeContent"的参数,类型为字符串。
                                     @RequestParam(value = "logoFile", required = false) MultipartFile logoFile,
                                     // 从请求中获取名为"logoFile"的参数,类型为 MultipartFile,表示上传的文件,该参数可选。
                                     HttpServletResponse response) {
        try {
            if (codeContent == null || "".equals(codeContent.trim())) {
                log.info("二维码内容为空,不进行操作...");
                // 如果二维码内容为空,记录日志并返回,不进行后续操作。
                return;
            }
            codeContent = codeContent.trim();
            // 如果二维码内容不为空,去除两端的空白字符。

            OutputStream outputStream = response.getOutputStream();
            // 获取 HttpServletResponse 的输出流,用于向客户端发送数据。

            if (logoFile != null) {
                // 如果上传的文件不为空。

                // 检查上传的文件是否是有效的图片文件
                if (!isValidImageFile(logoFile)) {
                    log.error("上传的文件不是有效的图片文件");
                    // 如果文件不是有效的图片文件,记录错误日志并返回。
                    return;
                }
                // 直接将 logo 文件的内容读取到内存中,而不是保存到临时文件
                byte[] logoBytes = logoFile.getBytes();
                // 将上传的文件内容读取为字节数组。

                QRCodeUtil.createCodeToOutputStream(codeContent, outputStream, logoBytes);
                // 调用 QRCodeUtil 的 createCodeToOutputStream 方法,生成带有 logo 的二维码并写入输出流。

                BufferedImage bufferedImage = getBufferedImageWithLogo(codeContent, logoBytes);
                // 使用静态导入的方法生成带有 logo 的二维码图像。

                ImageIO.write(bufferedImage, "png", new File("C:\\temp\\debug_qr.png"));
                // 将生成的二维码图像写入指定的文件,用于调试。
            } else {
                QRCodeUtil.createCodeToOutputStream(codeContent, outputStream, null);
                // 如果没有上传 logo 文件,调用 QRCodeUtil 的 createCodeToOutputStream 方法,生成不带 logo 的二维码并写入输出流。
            }
            log.info("成功生成二维码!");
            // 记录日志,表示成功生成二维码。
        } catch (IOException e) {
            log.error("发生错误, 错误信息是:{}!", e.getMessage());
            // 如果在处理过程中发生 IOException,记录错误日志并打印错误消息。
        }
    }

    private boolean isValidImageFile(MultipartFile file) {
        try {
            // 检查文件扩展名是否为常见的图片扩展名
            String fileName = file.getOriginalFilename();
            // 获取上传文件的原始文件名。

            if (fileName != null && (fileName.endsWith(".png") || fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) {
                // 如果文件名不为空且扩展名是.png、.jpg 或.jpeg。

                // 尝试读取文件内容作为图像
                BufferedImage image = ImageIO.read(file.getInputStream());
                // 使用 ImageIO 从文件的输入流中读取图像。

                return image != null;
                // 如果成功读取到图像,返回 true,表示文件是有效的图片文件。
            }
            return false;
            // 如果文件名不满足条件或读取图像失败,返回 false,表示文件不是有效的图片文件。
        } catch (IOException e) {
            return false;
            // 如果在读取文件过程中发生 IOException,返回 false,表示文件不是有效的图片文件。
        }
    }

}

最后controller层主页面代码

package com.huae.MainApplication.controller;

import org.springframework.stereotype.Controller; // 导入Spring的@Controller注解,用于标记这是一个控制器类
import org.springframework.web.bind.annotation.RequestMapping; // 导入Spring的@RequestMapping注解,用于映射请求URL
import org.springframework.web.bind.annotation.ResponseBody; // 导入Spring的@ResponseBody注解,用于直接返回对象到视图层

/**
 * 控制器类,处理HTTP请求。
 */
@Controller // 标记此类为Spring MVC控制器
public class HelloController {

    /**
     * 处理HTTP GET请求,当访问"/hello"路径时触发。
     *
     * @return 返回字符串"/index",表示重定向到"/index"路径。
     */
    @ResponseBody // 标记此方法返回的内容将直接作为HTTP响应体返回给客户端
    @RequestMapping("/hello") // 映射请求路径为/hello
    public String hello(){ // 方法名,返回一个字符串
        return "/index"; // 返回值,指示重定向到/index路径
    }

    /**
     * 处理HTTP GET请求,当访问"/index"路径时触发。
     *
     * @return 返回字符串"cs.html",表示转发到名为"cs.html"的视图。
     */
    @RequestMapping("/index") // 映射请求路径为/index
    public String index(){ // 方法名,返回一个字符串
        return "cs.html"; // 返回值,指示转发到名为cs.html的视图
    }



}

总结

深入了解了二维码的生成和解析原理,以及如何通过代码实现这些功能。 掌握了图像处理的基本方法,包括调整图像大小、绘制图像等操作。
学会了如何使用输入输出流进行数据的传输和处理,以及如何将生成的二维码以不同的方式返回给客户端。

熟悉了 Spring框架的基本使用,包括如何定义 RESTful API 接口、处理参数和响应等。

提高了代码的错误处理和参数校验能力,确保程序的稳定性和可靠性。

总之,这个二维码项目是一个很好的学习示例,涵盖了多个方面的技术要点,可以帮助大家深入理解和掌握 Java 编程中的各种技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/892912.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python 爬虫 入门 一、基础工具

目录 一&#xff0c;网页开发者工具的使用 二、通过python发送请求 &#xff08;一&#xff09;、get &#xff08;二&#xff09;、带参数的get &#xff08;三&#xff09;、post 后续&#xff1a;数据解析 一&#xff0c;网页开发者工具的使用 我们可以用 requests 库…

人脸识别-特征算法

文章目录 一、LBPH算法1.基本原理2.实现步骤3.代码实现 二、Eigenfaces算法1.特点2.代码实习 三、FisherFaces算法1.算法原理2.算法特点3.代码实现 四、总结 人脸识别特征识别器是数字信息发展中的一种生物特征识别技术&#xff0c;其核心在于通过特定的算法和技术手段&#xf…

leader必备技能——编写高质量测试计划

前言 作为一个想成为leader(不论是整个测试部门还是小项目组的leader&#xff09;的人&#xff0c;测试计划编写是必备技能。 接下来我们先了解一下测试计划的一些基础知识再进一步了解。 什么是测试计划&#xff1f; 测试计划是对测试过程的整体设计&#xff0c;测试计划确…

Spring Boot知识管理:智能搜索与分析

3系统分析 3.1可行性分析 通过对本知识管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本知识管理系统采用JAVA作为开发语言&#xff0c;Spring Boot框…

c#中多态的实例应用说明

在C#中&#xff0c;多态性是通过继承和实现接口来实现的&#xff0c;允许编写可以使用基类型的代码&#xff0c;然后使用派生类型的特定行为。 一.实例界面显示 二.源码界面显示 //定义的基类abstract class Shape{public abstract int Area();//基类中的抽象方法}//定义矩形的…

【前端】如何制作一个自己的网页(6)

接上文 网络中的图片 我们也可以在百度等网站搜索自己喜欢的图片。 此时对图片点击右键&#xff0c;选择【复制图片地址】&#xff0c;即可获得该图片的网络地址。 其实在HTML中&#xff0c;除了图片以外&#xff0c;我们还可以利用地址找到另一个网页。 如右图所示&#…

第一次排查 Java 内存泄漏,别人觉得惊险为什么我觉得脸红害羞呢

今天前端一直在群里说&#xff0c;服务是不是又挂了&#xff1f;一直返回 503。我一听这不对劲&#xff0c;赶紧看了一眼 K8S 的 pod 状态&#xff0c;居然重启了4次。测试环境只有一个副本&#xff0c;所以赶紧把副本数给上调到了3个。 堵住前端的嘴&#xff0c;免得破坏我在…

【C语言】一维数组应用Fibonacci数列

Fibonacci数&#xff08;斐波那契数列&#xff09; 前两项为1&#xff0c;从第三项开始&#xff0c;每一项为前两项的和。可以知道连续三项的关系&#xff1a;f[i]f[i-1]f[i-2] 使用数组进行存储&#xff0c;十分方便。可以知道前n项的fibonacci数。 #include <stdio.h>…

数据治理(2)-数据标准

前言 在建模前规划制定数据标准&#xff0c;或在建模使用过程中根据业务情况沉淀企业业务的数据标准。通过规范约束标准代码、度量单位、字段标准、命名词典&#xff0c;来保障数据处理的一致性&#xff0c;从源头上保障数据的标准化生产&#xff0c;节约后续数据应用和处理的…

什么是 C/2023 A3(紫金山-阿特拉斯)彗星?让我们用 Python 来绘制它的路径

彗星的基本概念 彗星&#xff08;Comet&#xff09;&#xff0c;是指进入太阳系内亮度和形状会随日距变化而变化的绕日运动的天体&#xff0c;呈云雾状的独特外貌&#xff0c;也是中国神话传说的扫帚星&#xff08;星官名&#xff09;。彗星分为彗核、彗发、彗尾三部分。彗核由…

一起体验AI动手实验,OceanBase 2024 年度发布会精彩预告

2024年OceanBase年度发布会将于10月23日在北京望京凯悦酒店举行。此次大会围绕“不止于记录”的主题&#xff0c;共同探讨当前数据库领域的前沿话题&#xff0c;包含主论坛、分论坛、AI 动手实训营、开源技术交流会等多个环节&#xff0c;诚邀全国各地的企业和开发者共同参与&a…

一个月学会Java 第18天 容器与泛型(有容器的原码解读)

Day18 容器与泛型 我们来简单讲讲容器是什么&#xff0c;顾名思义&#xff0c;是存东西的器皿&#xff0c;就叫做容器&#xff0c;那在我们计算机中需要存的是什么呢&#xff0c;是不是就是数据啊&#xff0c;所以我们的java是有提供一系列数据容器的&#xff0c;容器我们也叫做…

Redis:分布式 - 集群

Redis&#xff1a;分布式 - 集群 集群数据分片哈希求余一致性哈希算法哈希槽分区算法 Docker搭建集群集群操作重定向故障转移集群扩容 集群 在主从复制与哨兵模式中&#xff0c;数据库的数据对于每一台主机来说&#xff0c;都是全量保存的。这就会导致&#xff0c;就算引入再多…

Unity网络开发基础 —— 实践小项目

概述 接Unity网络开发基础 导入基础知识中的代码 需求分析 手动写Handler类 手动书写消息池 using GamePlayer; using System; using System.Collections; using System.Collections.Generic; using UnityEngine;/// <summary> /// 消息池中 主要是用于 注册 ID和消息类…

ps提示不能使用移动工具,因为目标通道被隐藏的解决办法

解决&#xff1a;按F7&#xff0c;或者从窗口把图层打开 按图示找到快速蒙版图层。它可能被隐藏或以特殊图标显示。右键删除或者拖到右下角垃圾桶里

岩石分类检测数据集 4700张 岩石检测 带标注 voc yolo 9类

岩石分类检测数据集 4700张 岩石检测 带标注 voc yolo 9类 岩石分类检测数据集 (Rock Classification and Detection Dataset) 描述: 本数据集旨在支持对不同类型的岩石进行自动分类和检测&#xff0c;特别适用于地质勘探、矿物识别、环境监测等领域。通过使用该数据集训练的模…

智慧云党建”主题网站设计与实现(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

5G NR UE初始接入信令流程

文章目录 5G NR UE初始接入信令流程 5G NR UE初始接入信令流程 用户设备向gNB-DU发送RRCSetupRequest消息。gNB-DU 包含 RRC 消息&#xff0c;如果 UE 被接纳&#xff0c;则在 INITIAL UL RRC MESSAGE TRANSFER 消息中包括为 UE 分配的低层配置&#xff0c;并将其传输到 gNB-CU…

Sounding Reference Signal (SRS)

文章目录 探测参考信号&#xff08;SRS&#xff09;Sounding Reference Signals 探测参考信号的生成基序列 探测参考信号&#xff08;SRS&#xff09; 探测参考信号&#xff08;SRS&#xff09;在上行链路中传输&#xff0c;使网络能够估计不同频率下的信道质量。 Sounding R…

2024双十一值得购买的好物有哪些?看完这五款好物让你不后悔!

随着一年一度的双十一购物狂欢节即将拉开帷幕&#xff0c;作为一名热衷于分享购物心得的博主&#xff0c;我今天特别想在这里为大家详细介绍五款我个人非常期待入手的好物。这些产品都是经过我精心挑选和试用的&#xff0c;我相信它们不仅能够满足我的需求&#xff0c;同样也能…