首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java ZXing 實戰:生成帶 Logo 的二維碼與條碼完整指南

     編輯:Java 綜合教學

前言:二維碼生成的標準選擇

ZXing (Zebra Crossing) 是 Java 生態最成熟的條碼/二維碼處理庫。本文提供生產級封裝代碼,涵蓋:二維碼生成、Logo 嵌入、圖片輸出流/文件、解析識別、Servlet 輸出,可直接集成至項目。

依賴坐標 (Maven)

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.5.1</version> <!-- 最新版建議 3.5.x -->
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.5.1</version>
</dependency>

核心工具類:QRCodeUtil

package com.example.qrcode;

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Random;

public class QRCodeUtil {

    // ========== 配置常量 ==========
    private static final String CHARSET = "UTF-8";
    private static final String FORMAT_NAME = "JPG";
    private static final int QRCODE_SIZE = 300;      // 二維碼尺寸 (px)
    private static final int LOGO_WIDTH = 60;        // Logo 寬度
    private static final int LOGO_HEIGHT = 60;       // Logo 高度
    private static final float ERROR_CORRECTION = ErrorCorrectionLevel.H; // 容錯率 H (最高 30%)

    // ========== 對外暴露的靜態方法 ==========

    /** 生成二維碼到文件 (無 Logo) */
    public static void encode(String content, String destPath) throws Exception {
        encode(content, null, destPath, false);
    }

    /** 生成二維碼到文件 (帶 Logo) */
    public static void encode(String content, String logoPath, String destPath, boolean needCompress) throws Exception {
        BufferedImage image = createImage(content, logoPath, needCompress);
        mkdirs(destPath);
        String fileName = new Random().nextInt(99999999) + "." + FORMAT_NAME.toLowerCase();
        ImageIO.write(image, FORMAT_NAME, new File(destPath + File.separator + fileName));
    }

    /** 生成二維碼到輸出流 (無 Logo) - 適用於 Web 直接輸出 */
    public static void encode(String content, OutputStream output) throws Exception {
        encode(content, null, output, false);
    }

    /** 生成二維碼到輸出流 (帶 Logo) */
    public static void encode(String content, String logoPath, OutputStream output, boolean needCompress) throws Exception {
        BufferedImage image = createImage(content, logoPath, needCompress);
        ImageIO.write(image, FORMAT_NAME, output);
    }

    /** 解析二維碼文件 */
    public static String decode(String path) throws Exception {
        return decode(new File(path));
    }

    // ========== 內部核心邏輯 ==========

    private static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {
        Hashtable hints = new Hashtable<>();
        hints.put(EncodeHintType.ERROR_CORRECTION, ERROR_CORRECTION);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1); // 邊距

        // 1. 生成位矩陣
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();

        // 2. 繪製基礎二維碼圖片
        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); // 黑白
            }
        }

        // 3. 插入 Logo (可選)
        if (logoPath != null && !logoPath.isEmpty()) {
            insertImage(image, logoPath, needCompress);
        }
        return image;
    }

    private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws Exception {
        File file = new File(logoPath);
        if (!file.exists()) {
            System.err.println("Logo 文件不存在: " + logoPath);
            return;
        }

        Image src = ImageIO.read(file);
        int width = src.getWidth(null);
        int height = src.getHeight(null);

        // 壓縮 Logo
        if (needCompress) {
            if (width > LOGO_WIDTH) width = LOGO_WIDTH;
            if (height > LOGO_HEIGHT) height = LOGO_HEIGHT;
            Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = tag.getGraphics();
            g.drawImage(image, 0, 0, null);
            g.dispose();
            src = image;
        }

        // 繪製 Logo 到中心
        Graphics2D graph = source.createGraphics();
        int x = (QRCODE_SIZE - width) / 2;
        int y = (QRCODE_SIZE - height) / 2;
        graph.drawImage(src, x, y, width, height, null);

        // 可選:Logo 周圓角邊框
        Shape shape = new RoundRectangle2D.Float(x, y, width, height, 6, 6);
        graph.setStroke(new BasicStroke(3f));
        graph.draw(shape);
        graph.dispose();
    }

    private static boolean mkdirs(String destPath) {
        File file = new File(destPath);
        if (!file.exists() && !file.isDirectory()) {
            return file.mkdirs();
        }
        return false;
    }

    private static String decode(File file) throws Exception {
        BufferedImage image = ImageIO.read(file);
        if (image == null) return null;
        
        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        Hashtable hints = new Hashtable<>();
        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
        
        Result result = new MultiFormatReader().decode(bitmap, hints);
        return result.getText();
    }
}

關鍵輔助類:BufferedImageLuminanceSource

ZXing 解析需要 LuminanceSource,Javase 模組已內置 BufferedImageLuminanceSource,無需自行複製。若使用舊版本需自行實現,參考源碼即可。

Web 端輸出:QRCodeServlet

package com.example.qrcode.web;

import com.example.qrcode.QRCodeUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;

import java.io.IOException;

@WebServlet("/qrcode")
public class QRCodeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // 動態內容:帶時間戳防緩存
            String content = "https://example.com/api?timestamp=" + System.currentTimeMillis();
            
            // 設置響應頭
            resp.setContentType("image/jpeg");
            resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
            resp.setHeader("Pragma", "no-cache");
            
            // 直接輸出到響應流
            QRCodeUtil.encode(content, resp.getOutputStream());
        } catch (Exception e) {
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "二維碼生成失敗");
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

生成條碼 (Code-128 / EAN-13 等)

// 僅需修改 BarcodeFormat 即可
BitMatrix bitMatrix = new MultiFormatWriter().encode(
    "1234567890123",           // 內容
    BarcodeFormat.CODE_128,    // 或 EAN_13, CODE_39, ITF 等
    300, 100,                  // 寬、高
    hints                      // 提示參數
);

生產環境優化建議

  1. 容錯率選擇:L(7%)、M(15%)、Q(25%)、H(30%)。帶 Logo 必須用 H 級
  2. 尺寸自適應:根據內容長度動態計算 QRCODE_SIZE,避免過疏/過密
  3. 緩存機制:相同內容 + 同樣參數 → 緩存圖片 (Redis/本地),避免重複計算
  4. 異步生成:高併發場景下,提交到線程池異步生成,返回任務 ID 輪詢/推送
  5. 參數校驗:內容非空、長度限制、Logo 文件類型/大小校驗
  6. 錯誤處理:捕獲 WriterException、IOException、NotFoundException,返回友好提示

常見問題排查

現象原因解決
掃碼識別失敗Logo 過大遮擋關鍵區域 / 容錯率過低縮小 Logo、升級到 H 級、增加邊距 MARGIN=2
生成圖片模糊尺寸過小 / 圖片壓縮質量低增大 QRCODE_SIZE、使用 PNG 格式、ImageIO.write 無壓縮參數
中文內容亂碼編碼不一致統一 CHARSET=UTF-8、確保源文件 UTF-8 編碼、URL 內容需 URLEncoder.encode
Servlet 輸出空白流未關閉 / 異常被吞 / 內容為空try-with-resources 管理流、統一異常處理、檢查 content 非空

總結

ZXing 提供了強大而靈活的二維碼/條碼生成解析能力。通過封裝 QRCodeUtil 統一參數配置、異常處理、輸出方式,配合 Servlet 實現 Web 端動態輸出,即可滿足營銷活動、設備綁定、登錄授權、溯源查詢等多元業務場景。記住核心三要素:高容錯 (H級)、適中尺寸、Logo 不遮關鍵區。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved