java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot ZXing二维码生成与读取

SpringBoot集成ZXing实现二维码的生成与读取功能

作者:Amour恋空

本教程将详细讲解如何在 Spring Boot 项目中集成 ZXing 库实现二维码的生成(返回 Base64 编码)和读取(解析图片的二维码)功能,并覆盖常见异常处理、参数优化等实战要点,适合 Java 开发新手快速上手,需要的朋友可以参考下

一、概述

本教程将详细讲解如何在 Spring Boot 项目中集成 ZXing 库实现二维码的生成(返回 Base64 编码)和读取(解析图片的二维码)功能,并覆盖常见异常处理、参数优化等实战要点,适合 Java 开发新手快速上手。

二、环境准备

2.1 技术栈

2.2 依赖引入

在 pom.xml 中添加 ZXing 核心依赖:

<!-- SpringBoot启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- SpringBoot的web启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- ZXing 二维码核心依赖 -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.5.2</version> <!-- 推荐使用最新稳定版 -->
        </dependency>
        <!-- ZXing JavaSE 扩展(处理图片) -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.5.2</version>
        </dependency>

三、工具类封装

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
//注意JDK11以上javax包名需要修改为jakarta
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


/**
 * 二维码工具类(生成+读取)
 * 包含异常处理、参数优化、Base64 转换等功能
 */
public class QRCodeUtil {
    // 默认二维码宽度/高度
    private static final int DEFAULT_SIZE = 300;
    // 默认字符编码
    private static final String DEFAULT_CHARSET = "UTF-8";
    // 二维码颜色(黑色)
    private static final int QR_CODE_COLOR = 0xFF000000;
    // 二维码背景色(白色)
    private static final int QR_CODE_BACKGROUND = 0xFFFFFFFF;

    /**
     * 生成二维码 BufferedImage 对象
     * @param content 二维码内容(必填)
     * @return BufferedImage 二维码图片
     * @throws WriterException 生成失败异常
     */
    public static BufferedImage createQRCode(String content) throws WriterException {
        return createQRCode(content, DEFAULT_SIZE, DEFAULT_SIZE);
    }

    /**
     * 自定义尺寸生成二维码
     * @param content 二维码内容
     * @param width 宽度
     * @param height 高度
     * @return BufferedImage
     * @throws WriterException 内容为空/尺寸非法时抛出
     */
    public static BufferedImage createQRCode(String content, int width, int height) throws WriterException {
        // 前置校验
        if (content == null || content.isEmpty()) {
            throw new WriterException("二维码内容不能为空");
        }
        if (width <= 0 || height <= 0) {
            throw new WriterException("二维码尺寸必须大于0");
        }

        // 配置二维码参数(关键:解决中文乱码、提升容错率)
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, DEFAULT_CHARSET); // 字符编码
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 最高容错率(30%)
        hints.put(EncodeHintType.MARGIN, 1); // 边距(0=无白边)

        // 生成二维码矩阵
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);

        // 转换为 BufferedImage(自定义颜色)
        MatrixToImageConfig config = new MatrixToImageConfig(QR_CODE_COLOR, QR_CODE_BACKGROUND);
        return MatrixToImageWriter.toBufferedImage(bitMatrix, config);
    }

    /**
     * 将 BufferedImage 转换为字节数组
     * @param image 二维码图片
     * @return 字节数组
     * @throws IOException 图片转换失败
     */
    public static byte[] imageToBytes(BufferedImage image) throws IOException {
        if (image == null) {
            throw new IOException("图片对象不能为空");
        }
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "PNG", outputStream);
        return outputStream.toByteArray();
    }

    /**
     * 将字节数组转换为 Base64 编码字符串(纯编码,无前缀)
     * @param bytes 图片字节数组
     * @return Base64 字符串
     */
    public static String imageToBase64(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            throw new IllegalArgumentException("字节数组不能为空");
        }
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 读取图片流中的二维码内容
     * @param inputStream 图片输入流(如文件流、网络流)
     * @return 二维码内容
     * @throws NotFoundException 未识别到二维码
     * @throws IOException 图片读取失败
     * @throws ChecksumException 二维码数据校验失败
     * @throws FormatException 二维码格式错误
     */
    public static String readQRCode(InputStream inputStream) throws NotFoundException, IOException, ChecksumException, FormatException {
        if (inputStream == null) {
            throw new IOException("图片输入流不能为空");
        }
        BufferedImage image = ImageIO.read(inputStream);
        if (image == null) {
            throw new IOException("无法解析图片,请检查文件格式");
        }

        // 配置解析参数(提升识别率)
        Map<DecodeHintType, Object> hints = new HashMap<>();
        hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); // 尝试更高精度解析
        hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); // 仅解析二维码
        hints.put(DecodeHintType.CHARACTER_SET, DEFAULT_CHARSET); // 字符编码

        // 解析二维码
        BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(image)));
        QRCodeReader qrCodeReader = new QRCodeReader();
        Result result = qrCodeReader.decode(binaryBitmap, hints);
        return result.getText();
    }

    /**
     * 读取 Base64 编码中的二维码内容
     * @param base64Str Base64 字符串(支持带/不带 data:image/png;base64, 前缀)
     * @return 二维码内容
     * @throws Exception 解析失败
     */
    public static String readQRCodeFromBase64(String base64Str) throws Exception {
        if (base64Str == null || base64Str.isEmpty()) {
            throw new IllegalArgumentException("Base64 字符串不能为空");
        }
        // 移除 Base64 前缀(如果有)
        String pureBase64 = base64Str.replace("data:image/png;base64,", "");
        // 解码为字节数组并转换为输入流
        byte[] bytes = Base64.getDecoder().decode(pureBase64);
        try (InputStream inputStream = new java.io.ByteArrayInputStream(bytes)) {
            return readQRCode(inputStream);
        }
    }
}

四、接口实现(生成 + 读取)

4.1 二维码生成接口(支持显示图片/Base64)

import com.google.zxing.NotFoundException;
import com.google.zxing.WriterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
//注意JDK11以上javax包名需要修改为jakarta
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;

/**
 * 二维码生成接口
 */
@RestController
@RequestMapping("/qrcode")
public class QRCodeGenerateController {


    /**
     * 生成二维码(直接在浏览器显示图片)
     * @param content 二维码内容(必填)
     * @param width 宽度(默认300)
     * @param height 高度(默认300)
     */
    @GetMapping("/img")
    public void generate( @RequestParam(required = true) String content,
                          @RequestParam(defaultValue = "300") int width,
                          @RequestParam(defaultValue = "300") int height, HttpServletResponse response) throws Exception {
        try {
            response.setContentType("image/png");
            BufferedImage image = QRCodeUtil.createQRCode(content, width, height);
            com.example.demo.d1.QRCodeUtil.imageToBytes(image);
            ImageIO.write(image, "png", response.getOutputStream());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 生成二维码并返回 Base64 编码(前端可直接用于 img 标签)
     * @param content 二维码内容(必填)
     * @param width 宽度(默认300)
     * @param height 高度(默认300)
     * @return 结构化响应
     */
    @GetMapping("/generate")
    public Map<String, Object> generateQRCode(
            @RequestParam(required = true) String content,
            @RequestParam(defaultValue = "300") int width,
            @RequestParam(defaultValue = "300") int height) {

        Map<String, Object> result = new HashMap<>();
        try {
            // 生成二维码图片
            BufferedImage qrImage = QRCodeUtil.createQRCode(content, width, height);
            // 转换为字节数组
            byte[] qrBytes = QRCodeUtil.imageToBytes(qrImage);
            // 转换为 Base64(带前端可直接使用的前缀)
            String base64 = "data:image/png;base64," + QRCodeUtil.imageToBase64(qrBytes);
            // 响应结果
            result.put("code", 200);
            result.put("msg", "二维码生成成功");
            result.put("data", base64);
        } catch (WriterException e) {
            // 处理生成失败异常(内容为空/尺寸非法等)
            result.put("code", 400);
            result.put("msg", "二维码生成失败:" + e.getMessage());
            result.put("data", null);
        } catch (Exception e) {
            // 处理其他未知异常
            result.put("code", 500);
            result.put("msg", "服务器异常:" + e.getMessage());
            result.put("data", null);
        }
        return result;
    }
}

4.2 二维码读取接口(支持文件 / Base64)

import com.google.zxing.NotFoundException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.Map;

/**
 * 二维码读取接口
 */
@RestController
@RequestMapping("/qrcode")
public class QRCodeReadController {

    /**
     * 解析上传的图片文件中的二维码
     * @param file 图片文件(支持 png/jpg/jpeg)
     * @return 解析结果
     */
    @PostMapping("/file")
    public Map<String, Object> readQRCodeFromFile(@RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            // 前置校验
            if (file.isEmpty()) {
                result.put("code", 400);
                result.put("msg", "上传的文件不能为空");
                return result;
            }
            String contentType = file.getContentType();
            if (contentType == null || !contentType.startsWith("image/")) {
                result.put("code", 400);
                result.put("msg", "请上传图片文件(png/jpg/jpeg)");
                return result;
            }

            // 解析二维码
            String content = QRCodeUtil.readQRCode(file.getInputStream());
            result.put("code", 200);
            result.put("msg", "二维码解析成功");
            result.put("data", content);
        } catch (NotFoundException e) {
            // 核心异常:未识别到二维码
            result.put("code", 400);
            result.put("msg", "未识别到二维码:图片中无有效二维码或二维码模糊/破损");
            result.put("data", null);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("msg", "解析失败:" + e.getMessage());
            result.put("data", null);
        }
        return result;
    }

    /**
     * 解析 Base64 编码中的二维码
     * @param base64Str Base64 字符串(支持带/不带前缀)
     * @return 解析结果
     */
    @PostMapping("/base64")
    public Map<String, Object> readQRCodeFromBase64(@RequestParam("base64") String base64Str) {
        Map<String, Object> result = new HashMap<>();
        try {
            String content = QRCodeUtil.readQRCodeFromBase64(base64Str);
            result.put("code", 200);
            result.put("msg", "解析成功");
            result.put("data", content);
        } catch (NotFoundException e) {
            result.put("code", 400);
            result.put("msg", "未识别到二维码");
        } catch (IllegalArgumentException e) {
            result.put("code", 400);
            result.put("msg", "Base64 格式错误:" + e.getMessage());
        } catch (Exception e) {
            result.put("code", 500);
            result.put("msg", "解析失败:" + e.getMessage());
        }
        return result;
    }
}

五、常见异常与解决方案

5.1 核心异常列表

异常类型异常说明解决方案
com.google.zxing.NotFoundException未识别到二维码1. 检查图片是否包含有效二维码
2. 确保二维码清晰、无遮挡、分辨率≥200px
3. 解析时启用 TRY_HARDER 参数4. 避免二维码角度倾斜过大
com.google.zxing.WriterException二维码生成失败1. 检查内容是否为空
2. 确保尺寸参数大于 0
3. 内容过长时缩短(或提升容错级别)
java.io.IOException图片读取 / 转换失败1. 检查文件格式是否为 png/jpg
2. 确保输入流未提前关闭
3. 验证 Base64 编码是否完整
IllegalArgumentException参数非法1. 校验 Base64 字符串是否为空
2. 检查文件是否为空
3. 验证尺寸 / 编码参数合法性

5.2 通用优化建议

  1. 提升生成容错率:使用 ErrorCorrectionLevel.H(最高级别),即使二维码被遮挡 30% 仍可识别;
  2. 解决中文乱码:生成 / 解析时统一设置 CHARACTER_SET 为 UTF-8;
  3. 优化解析成功率
    • 解析时启用 TRY_HARDER 参数;
    • 对图片进行预处理(灰度化、缩放至合适尺寸);
    • 使用 MultiFormatReader 替代 QRCodeReader(支持多码制解析)
  4. Base64 兼容性:返回时拼接 data:image/png;base64, 前缀,前端可直接用于 ;
  5. 参数校验:所有接口必须校验入参(内容、文件、尺寸),避免空指针 / 非法参数异常。

六、测试

可以直接使用如下测试页面进行测试,如修改请求url请根据修改内容同步修改页面测试地址

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>二维码识别&生成工具</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        body {
            padding: 30px;
            font-size: 16px;
        }
        .container {
            max-width: 500px;
            margin: 0 auto;
        }
        h2 {
            margin-bottom: 20px;
            text-align: center;
        }
        /* 新增:生成和识别模块的分隔 */
        .module {
            margin-bottom: 40px;
            padding-bottom: 20px;
            border-bottom: 1px solid #eee;
        }
        .module:last-child {
            border-bottom: none;
        }
        .upload-box {
            border: 2px dashed #ccc;
            padding: 40px;
            text-align: center;
            margin-bottom: 20px;
            cursor: pointer;
        }
        .upload-box:hover {
            border-color: #409eff;
        }
        #preview {
            max-width: 100%;
            max-height: 300px;
            margin: 20px 0;
            display: none;
        }
        /* 新增:生成二维码的预览样式 */
        #qrcode-preview {
            max-width: 200px;
            max-height: 200px;
            margin: 20px auto;
            display: none;
        }
        #result {
            margin-top: 20px;
            padding: 15px;
            background: #f5f5f5;
            border-radius: 6px;
            white-space: pre-wrap;
        }
        button {
            padding: 10px 20px;
            background: #409eff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:disabled {
            background: #ccc;
        }
        /* 新增:生成二维码的输入框样式 */
        .generate-input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            margin: 15px 0;
            resize: vertical;
            min-height: 80px;
        }
    </style>
</head>
<body>
<div class="container">
    <!-- 新增:二维码生成模块 -->
    <div class="module">
        <h2>二维码生成</h2>
        <textarea class="generate-input" id="qrcode-text" placeholder="请输入要生成二维码的文本内容(如网址、文字、手机号等)"></textarea>
        <div style="text-align: center; margin: 10px 0;">
            <button id="generateBtn">生成二维码</button>
        </div>
        <!-- 生成的二维码预览 -->
        <div style="text-align: center;">
            <img id="qrcode-preview" alt="生成的二维码">
        </div>
    </div>
    <!-- 原有:二维码识别模块 -->
    <div class="module">
        <h2>二维码图片识别</h2>
        <div class="upload-box" onclick="document.getElementById('file').click()">
            点击或拖拽上传二维码图片
        </div>
        <input type="file" id="file" accept="image/*" style="display: none;">
        <img id="preview" alt="预览图">
        <div style="text-align: center; margin: 10px 0;">
            <button id="recognizeBtn" disabled>开始识别</button>
        </div>
        <div id="result"></div>
    </div>
</div>
<script>
    // ========== 原有:二维码识别功能 ==========
    const fileInput = document.getElementById('file');
    const preview = document.getElementById('preview');
    const recognizeBtn = document.getElementById('recognizeBtn');
    const result = document.getElementById('result');
    // 选择图片
    fileInput.onchange = function (e) {
        const file = e.target.files[0];
        if (!file) return;
        // 预览
        const url = URL.createObjectURL(file);
        preview.src = url;
        preview.style.display = 'block';
        recognizeBtn.disabled = false;
        // 识别
        recognizeBtn.onclick = async function () {
            result.innerText = "识别中...";
            recognizeBtn.disabled = true;
            const formData = new FormData();
            formData.append("file", file);
            try {
                const res = await fetch("http://localhost:8080/qrcode/read", {
                    method: "POST",
                    body: formData
                });
                const jsonData = await res.json();
                result.innerText = "识别结果:\n" + jsonData.data;
            } catch (err) {
                result.innerText = "识别失败:" + err.message;
            } finally {
                recognizeBtn.disabled = false;
            }
        };
    }
    // ==========二维码生成功能 ==========
    const generateBtn = document.getElementById('generateBtn');
    const qrcodeText = document.getElementById('qrcode-text');
    const qrcodePreview = document.getElementById('qrcode-preview');
    // 生成二维码按钮点击事件
    generateBtn.onclick = async function () {
        const text = qrcodeText.value.trim();
        if (!text) {
            alert("请输入要生成二维码的内容!");
            return;
        }
        generateBtn.disabled = true;
        generateBtn.innerText = "生成中...";
        qrcodePreview.style.display = 'none';
        try {
            // 调用后端生成二维码接口(需后端配合实现/qrcode/generate接口)
            const res = await fetch("http://localhost:8080/qrcode/generate?content="+text, {
                method: "GET",
                headers: {
                    "Content-Type": "application/json"
                }
            });
            if (!res.ok) throw new Error("生成失败");
            const jsonData = await res.json();
            // 将后端返回的二维码图片转为URL显示
            // const blob = await res.blob();
            // const qrUrl = URL.createObjectURL(blob);
             qrcodePreview.src = jsonData.data;
             qrcodePreview.style.display = 'block';
        } catch (err) {
            alert("二维码生成失败:" + err.message);
        } finally {
            generateBtn.disabled = false;
            generateBtn.innerText = "生成二维码";
        }
    }
</script>
</body>
</html>

以上就是SpringBoot集成ZXing实现二维码的生成与读取功能的详细内容,更多关于SpringBoot ZXing二维码生成与读取的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文