ZXing(Zebra Crossing)是 Java 生態最主流的二維碼/條碼處理庫。本文提供生產級完整代碼:Maven 依賴、核心工具類(支持 Logo 嵌入、壓縮、多格式輸出)、解碼工具、Servlet 直接輸出圖片流,可直接集成至 Spring Boot / 傳統 Web 項目。
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.1</version> <!-- 當前最新穩定版 -->
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.1</version>
</dependency>
功能特性:
BarcodeFormat 參數即可)。BufferedImage、文件、OutputStream 多種方式。File/路徑字符串。public class QRCodeUtil {
private static final String CHARSET = "utf-8";
private static final String FORMAT_NAME = "JPG";
private static final int QRCODE_SIZE = 800;
private static final int LOGO_WIDTH = 80;
private static final int LOGO_HEIGHT = 80;
// 核心生成邏輯
private static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 1); // 邊距
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
BufferedImage image = new BufferedImage(QRCODE_SIZE, QRCODE_SIZE, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < QRCODE_SIZE; x++) {
for (int y = 0; y < QRCODE_SIZE; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
if (logoPath != null && !logoPath.isEmpty()) {
insertImage(image, logoPath, needCompress);
}
return image;
}
// Logo 嵌入:縮放、居中、圓角邊框
private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws Exception {
File file = new File(logoPath);
if (!file.exists()) { log.warn("Logo 文件不存在: {}", logoPath); return; }
Image src = ImageIO.read(file);
int w = src.getWidth(null), h = src.getHeight(null);
if (needCompress) {
if (w > LOGO_WIDTH) w = LOGO_WIDTH;
if (h > LOGO_HEIGHT) h = LOGO_HEIGHT;
Image scaled = src.getScaledInstance(w, h, Image.SCALE_SMOOTH);
BufferedImage tag = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics(); g.drawImage(scaled, 0, 0, null); g.dispose();
src = scaled;
}
Graphics2D graph = source.createGraphics();
int x = (QRCODE_SIZE - w) / 2;
int y = (QRCODE_SIZE - h) / 2;
graph.drawImage(src, x, y, w, h, null);
// 圓角邊框
Shape shape = new RoundRectangle2D.Float(x, y, w, h, 6, 6);
graph.setStroke(new BasicStroke(3f));
graph.draw(shape);
graph.dispose();
}
// 多種輸出方式
public static void encode(String content, String destPath) throws Exception { ... }
public static void encode(String content, String logoPath, OutputStream out, boolean compress) throws Exception { ... }
public static String decode(String path) throws Exception { ... }
}
ZXing 需 LuminanceSource 讀取圖像亮度數據,繼承實現支持 BufferedImage 直接解碼,含裁剪、旋轉支持:
public class BufferedImageLuminanceSource extends LuminanceSource {
private final BufferedImage image;
// 構造器、getRow、getMatrix、crop、rotateCounterClockwise 實現...
}
@WebServlet("/qrcode")
public class QRCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String content = "https://example.com?token=" + UUID.randomUUID(); // 動態業務參數
resp.setContentType("image/jpeg");
resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
try {
QRCodeUtil.encode(content, resp.getOutputStream()); // 直接寫響應流
} catch (Exception e) {
log.error("生成二維碼失敗", e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
前端直接用 <img src="/qrcode?param=xxx" /> 即可顯示動態二維碼。
QRCodeUtil 註冊為 @Component 或工具類静態方法。ResponseEntity 或 void 寫 HttpServletResponse。| 問題 | 原因 | 解決 |
|---|---|---|
| 中文內容掃碼亂碼 | 編碼未指定 UTF-8 | hints.put(EncodeHintType.CHARACTER_SET, "utf-8") |
| Logo 遮擋導致無法識別 | Logo 過大、未設置錯誤訂正級別 H | 限制 Logo 佔比 ≤ 20%,ERROR_CORRECTION=H |
| 解碼失敗 | 圖片模糊、對比度低、格式不支持 | 輸出 PNG 無損、確保 300+ DPI、預處理銳化 |
| 高併發 OOM | 大量 BufferedImage 佔用堆外/堆內存 | 限流、對象池復用、異步生成、緩存結果 |