ZXing (Zebra Crossing) 是 Java 生態最成熟的條碼/二維碼處理庫。本文提供生產級封裝代碼,涵蓋:二維碼生成、Logo 嵌入、圖片輸出流/文件、解析識別、Servlet 輸出,可直接集成至項目。
<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>
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();
}
}
ZXing 解析需要 LuminanceSource,Javase 模組已內置 BufferedImageLuminanceSource,無需自行複製。若使用舊版本需自行實現,參考源碼即可。
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);
}
}
// 僅需修改 BarcodeFormat 即可
BitMatrix bitMatrix = new MultiFormatWriter().encode(
"1234567890123", // 內容
BarcodeFormat.CODE_128, // 或 EAN_13, CODE_39, ITF 等
300, 100, // 寬、高
hints // 提示參數
);
QRCODE_SIZE,避免過疏/過密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 不遮關鍵區。