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

Java QR Code 生成與解析全攻略:ZXing、QRGen、Google Chart API 選型與實戰

     編輯:Java 相關知識

主流庫對比與選型建議

庫Maven 坐標特點適用場景
ZXing (Zebra Crossing)com.google.zxing:core:3.5.1
com.google.zxing:javase:3.5.1
Google 維護、功能最全、支持 1D/2D 多種碼制、可讀性強、活躍社區首選:生產環境生成/解析、Android、多格式需求
QRGennet.glxn:qrgen:2.6.0ZXing 封裝、API 極簡 (鏈式調用)、內建圖片輸出快速原型、簡單生成需求、不想依賴 javase
Swetake QRCodejp.sourceforge.qrcode:qrcode:1.0日系老牌、純 Java、無額外依賴、維護停滯舊項目維護、極簡依賴環境
Google Chart API (已停止)HTTP API無需本地庫、URL 即生成已棄用,**嚴禁新項目使用**

ZXing 核心實戰代碼 (生產級封裝)

1. 生成器 (支持 Logo、顏色、邊距、錯誤糾錯級別)

public final class QrCodeGenerator {
    private static final Map DEFAULT_HINTS = Map.of(
        EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M,  // L(7%) M(15%) Q(25%) H(30%)
        EncodeHintType.CHARACTER_SET, "UTF-8",
        EncodeHintType.MARGIN, 2  // 靜默區模塊數
    );

    private QrCodeGenerator() {}

    /**
     * 生成帶 Logo 的二維碼
     * @param content 內容
     * @param width 寬度 (px)
     * @param height 高度 (px)
     * @param logoPath Logo 文件路徑 (可選)
     * @param logoRatio Logo 佔比 (0.15-0.3 建議)
     * @return BufferedImage
     */
    public static BufferedImage generate(String content, int width, int height, 
                                         String logoPath, double logoRatio) throws WriterException, IOException {
        BitMatrix matrix = new MultiFormatWriter()
                .encode(content, BarcodeFormat.QR_CODE, width, height, DEFAULT_HINTS);
        
        BufferedImage image = MatrixToImageWriter.toBufferedImage(matrix);
        
        if (StringUtils.isNotBlank(logoPath)) {
            embedLogo(image, logoPath, logoRatio);
        }
        return image;
    }

    private static void embedLogo(BufferedImage qrImage, String logoPath, double ratio) throws IOException {
        BufferedImage logo = ImageIO.read(new File(logoPath));
        int qrWidth = qrImage.getWidth();
        int qrHeight = qrImage.getHeight();
        int logoWidth = (int) (qrWidth * ratio);
        int logoHeight = (int) (qrHeight * ratio);
        int x = (qrWidth - logoWidth) / 2;
        int y = (qrHeight - logoHeight) / 2;

        Graphics2D g = qrImage.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(logo, x, y, logoWidth, logoHeight, null);
        // 繪製白色圓角矩形背景,提高識別率
        g.setColor(Color.WHITE);
        g.fillRoundRect(x - 2, y - 2, logoWidth + 4, logoHeight + 4, 8, 8);
        g.drawImage(logo, x, y, logoWidth, logoHeight, null);
        g.dispose();
    }

    /** 寫入文件 */
    public static void writeToFile(BufferedImage image, String filePath, String format) throws IOException {
        ImageIO.write(image, format, new File(filePath));
    }

    /** 寫入 OutputStream (Web 直接輸出) */
    public static void writeToStream(BufferedImage image, OutputStream out, String format) throws IOException {
        ImageIO.write(image, format, out);
    }

    /** 生成 Base64 Data URI (前端直接 ) */
    public static String toBase64DataUri(BufferedImage image, String format) throws IOException {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            ImageIO.write(image, format, baos);
            return "data:image/" + format + ";base64," + Base64.getEncoder().encodeToString(baos.toByteArray());
        }
    }
}

2. 解析器 (支持文件、流、Base64、旋轉校正)

public final class QrCodeParser {
    private static final Map DEFAULT_HINTS = Map.of(
        DecodeHintType.CHARACTER_SET, "UTF-8",
        DecodeHintType.TRY_HARDER, Boolean.TRUE,        // 更積極嘗試
        DecodeHintType.PURE_BARCODE, Boolean.FALSE,     // 非純碼圖片
        DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE)
    );

    private QrCodeParser() {}

    public static String parse(File imageFile) throws NotFoundException, IOException {
        return parse(ImageIO.read(imageFile));
    }

    public static String parse(InputStream inputStream) throws NotFoundException, IOException {
        return parse(ImageIO.read(inputStream));
    }

    public static String parse(byte[] imageBytes) throws NotFoundException, IOException {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes)) {
            return parse(ImageIO.read(bis));
        }
    }

    public static String parse(Base64String base64) throws NotFoundException, IOException {
        byte[] bytes = Base64.getDecoder().decode(base64.getValue());
        return parse(bytes);
    }

    private static String parse(BufferedImage image) throws NotFoundException {
        // 嘗試多種旋轉角度 (0, 90, 180, 270) 提高識別率
        for (int angle : new int[]{0, 90, 180, 270}) {
            BufferedImage rotated = angle == 0 ? image : rotateImage(image, angle);
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(rotated)));
            try {
                Result result = new MultiFormatReader().decode(bitmap, DEFAULT_HINTS);
                return result.getText();
            } catch (NotFoundException ignored) {}
        }
        throw new NotFoundException("所有角度均無法識別");
    }

    private static BufferedImage rotateImage(BufferedImage src, int angle) {
        double rad = Math.toRadians(angle);
        int w = src.getWidth(), h = src.getHeight();
        int newW = (int) Math.round(Math.abs(w * Math.cos(rad)) + Math.abs(h * Math.sin(rad)));
        int newH = (int) Math.round(Math.abs(w * Math.sin(rad)) + Math.abs(h * Math.cos(rad)));
        BufferedImage dest = new BufferedImage(newW, newH, src.getType());
        Graphics2D g = dest.createGraphics();
        g.translate(newW / 2, newH / 2);
        g.rotate(rad);
        g.translate(-w / 2, -h / 2);
        g.drawImage(src, 0, 0, null);
        g.dispose();
        return dest;
    }
}

3. Spring Boot Controller 示例

@RestController
@RequestMapping("/api/qrcode")
@RequiredArgsConstructor
public class QrCodeController {

    @GetMapping("/generate")
    public void generate(@RequestParam String content,
                         @RequestParam(defaultValue = "300") int size,
                         @RequestParam(required = false) String logo,
                         HttpServletResponse response) throws Exception {
        BufferedImage image = QrCodeGenerator.generate(content, size, size, logo, 0.2);
        response.setContentType("image/png");
        QrCodeGenerator.writeToStream(image, response.getOutputStream(), "PNG");
    }

    @PostMapping("/parse")
    public Result parse(@RequestParam("file") MultipartFile file) {
        try {
            String text = QrCodeParser.parse(file.getInputStream());
            return Result.success(text);
        } catch (Exception e) {
            return Result.error("解析失敗: " + e.getMessage());
        }
    }

    @PostMapping("/parse-base64")
    public Result parseBase64(@RequestBody Map body) {
        try {
            String text = QrCodeParser.parse(new Base64String(body.get("base64")));
            return Result.success(text);
        } catch (Exception e) {
            return Result.error("解析失敗: " + e.getMessage());
        }
    }
}

錯誤糾錯級別選擇指南

  • L (7%):空間最小、適合乾淨環境 (如屏幕展示、打印質量高)。
  • M (15%) 默認推薦:平衡空間與魯棒性,適合大多數業務場景。
  • Q (25%):較髒環境 (如戶外廣告、易磨損介質)。
  • H (30%):極端環境 (如工業標籤、長期戶外),生成圖片最大。

嵌入 Logo 會佔用糾錯容量,建議級別至少 M,Logo 面積 ≤ 20%,並留白邊緣。

常見問題排查

現象原因解決
生成圖片模糊/掃不出尺寸過小、邊距為 0、錯誤級別過低最小 200x200px、MARGIN≥2、級別 M+
中文內容亂碼未指定 CHARACTER_SET=UTF-8Hints 必須設置 EncodeHintType.CHARACTER_SET
解析失敗 (NotFoundException)圖片噪點大、旋轉、光照不均、Logo 遮擋過多TRY_HARDER=true、多角度嘗試、預處理(銳化/二值化)
ZXing 依賴衝突傳遞依賴引入舊版 coremvn dependency:tree 排查、統一版本、排除舊傳遞

總結

ZXing 是 Java 二維碼領域的事實標準。**封裝統一生成/解析入口、預設合理 Hints、支持 Logo 嵌入與多角度識別、提供 Base64/流/文件多種輸出**,即可滿足 99% 業務需求。選型時堅持:新項目只用 ZXing,舊項目遷移 ZXing,拒絕已停維的 Swetake/Google Chart API。

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