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

Java 二維碼生成完整實現:Logo 嵌入、容錯率、解析、ZXing 最佳實踐

     編輯:Java 相關知識

技術選型:ZXing (Zebra Crossing)

Google 開源、Java 生態標準、支持 QR Code / Data Matrix / Aztec / 一維碼、活躍維護。


<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.4.1</version>
</dependency>

核心工具類:QRCodeUtil


public class QRCodeUtil {
    
    private static final String CHARSET = "UTF-8";
    private static final String FORMAT = "JPG";
    private static final int QR_SIZE = 300;      // 二維碼尺寸
    private static final int LOGO_WIDTH = 60;    // Logo 最大寬
    private static final int LOGO_HEIGHT = 60;   // Logo 最大高
    
    // 1. 核心生成邏輯
    private static BufferedImage createImage(String content, String logoPath, boolean compressLogo) 
            throws Exception {
        
        // 編碼提示
        Map hints = new EnumMap<>(EncodeHintType.class);
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 最高容錯 30%
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1); // 邊距
        
        // 生成位矩陣
        BitMatrix bitMatrix = new MultiFormatWriter()
            .encode(content, BarcodeFormat.QR_CODE, QR_SIZE, QR_SIZE, hints);
        
        // 位矩陣轉 BufferedImage
        BufferedImage image = new BufferedImage(QR_SIZE, QR_SIZE, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < QR_SIZE; x++) {
            for (int y = 0; y < QR_SIZE; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        
        // 嵌入 Logo
        if (logoPath != null && !logoPath.isEmpty()) {
            insertLogo(image, logoPath, compressLogo);
        }
        return image;
    }
    
    // 2. Logo 嵌入 (關鍵:居中、圓角邊框、壓縮)
    private static void insertLogo(BufferedImage qrImage, String logoPath, boolean compress) 
            throws Exception {
        File logoFile = new File(logoPath);
        if (!logoFile.exists()) {
            System.err.println("Logo 文件不存在: " + logoPath);
            return;
        }
        
        Image logoImg = ImageIO.read(logoFile);
        int width = logoImg.getWidth(null);
        int height = logoImg.getHeight(null);
        
        // 壓縮 Logo
        if (compress) {
            if (width > LOGO_WIDTH) width = LOGO_WIDTH;
            if (height > LOGO_HEIGHT) height = LOGO_HEIGHT;
            Image scaled = logoImg.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = tag.getGraphics();
            g.drawImage(scaled, 0, 0, null);
            g.dispose();
            logoImg = scaled;
        }
        
        // 繪制到二維碼中心
        Graphics2D g2 = qrImage.createGraphics();
        int x = (QR_SIZE - width) / 2;
        int y = (QR_SIZE - height) / 2;
        g2.drawImage(logoImg, x, y, width, height, null);
        
        // 圓角矩形邊框 (美觀+區分)
        RoundRectangle2D.Float roundRect = new RoundRectangle2D.Float(x, y, width, height, 6, 6);
        g2.setStroke(new BasicStroke(3f));
        g2.draw(roundRect);
        g2.dispose();
    }
    
    // 3. 對外暴露方法鏈
    public static void encode(String content, String logoPath, String destDir, boolean compress) 
            throws Exception {
        BufferedImage image = createImage(content, logoPath, compress);
        mkdirs(destDir);
        String fileName = System.currentTimeMillis() + "_" + new Random().nextInt(10000) + ".jpg";
        ImageIO.write(image, FORMAT, new File(destDir + "/" + fileName));
    }
    
    public static void encode(String content, String logoPath, String destDir) throws Exception {
        encode(content, logoPath, destDir, false);
    }
    
    public static void encode(String content, String destDir, boolean compress) throws Exception {
        encode(content, null, destDir, compress);
    }
    
    public static void encode(String content, String destDir) throws Exception {
        encode(content, null, destDir, false);
    }
    
    public static void encode(String content, String logoPath, OutputStream os, boolean compress) 
            throws Exception {
        BufferedImage image = createImage(content, logoPath, compress);
        ImageIO.write(image, FORMAT, os);
    }
    
    public static void encode(String content, OutputStream os) throws Exception {
        encode(content, null, os, false);
    }
    
    // 4. 解析二維碼
    public static String decode(File file) throws Exception {
        BufferedImage image = ImageIO.read(file);
        if (image == null) return null;
        
        BinaryBitmap bitmap = new BinaryBitmap(
            new HybridBinarizer(new BufferedImageLuminanceSource(image)));
        
        Map hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
        
        Result result = new MultiFormatReader().decode(bitmap, hints);
        return result.getText();
    }
    
    public static String decode(String path) throws Exception {
        return decode(new File(path));
    }
    
    private static void mkdirs(String dir) {
        File f = new File(dir);
        if (!f.exists() && !f.isDirectory()) f.mkdirs();
    }
}

關鍵參數深度解析

參數推薦值說明
ERROR_CORRECTIONH (30%)最高容錯,嵌入 Logo 後仍可識別。L(7%)、M(15%)、Q(25%)、H(30%)
MARGIN1-2靜默區邊距,太小掃描困難,太大浪費空間
QR_SIZE300-500px打印建議 ≥300dpi,手機顯示 300px 足夠
LOGO_SIZE≤ 1/5 QR_SIZE過大破壞定位點/數據區,建議 ≤ 20% 面積
圓角邊框半徑 6-10px視覺區分 Logo 與碼體,防止遮擋定位點

使用示例


public static void main(String[] args) throws Exception {
    // 1. 純二維碼
    QRCodeUtil.encode("https://www.example.com", "D:/qrcode");
    
    // 2. 嵌入 Logo (自動壓縮)
    QRCodeUtil.encode("https://www.example.com", "D:/logo.png", "D:/qrcode", true);
    
    // 3. 輸出流 (Web 直接輸出)
    @GetMapping("/qrcode")
    public void qrcode(HttpServletResponse response) throws Exception {
        response.setContentType("image/jpeg");
        QRCodeUtil.encode("動態業務內容", "logo.png", response.getOutputStream(), true);
    }
    
    // 4. 解析驗證
    String content = QRCodeUtil.decode("D:/qrcode/xxx.jpg");
    System.out.println("解析內容: " + content);
}

進階場景與優化

1. 彩色二維碼


// 自定義顏色
int color = 0xFF0066CC; // 藍色前景
int bgColor = 0xFFFFFFFF; // 白背景
image.setRGB(x, y, bitMatrix.get(x, y) ? color : bgColor);

2. 批量生成優化


// 重用 Hint Map、BitMatrix 緩存、線程池並行
ExecutorService executor = Executors.newFixedThreadPool(8);
List> futures = contents.stream()
    .map(c -> CompletableFuture.runAsync(() -> QRCodeUtil.encode(c, ...), executor))
    .collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

3. 微信/支付寶參數碼


// 微信掃碼支付 product_id
String wxContent = "weixin://wxpay/bizpayurl?pr=xxxxx";
// 支付寶面付
String aliContent = "https://qr.alipay.com/xxxxxx";

常見問題排查

現象原因解決
掃不出/識別率低容錯率低、Logo 太大遮擋定位點、邊距太小ErrorCorrectionLevel.H、Logo ≤ 20%、Margin ≥ 1
中文亂碼CHARSET 不一致統一 UTF-8,`EncodeHintType.CHARACTER_SET`
Logo 顯示不全圖片透明通道丟失、壓縮比例錯誤使用 `TYPE_INT_ARGB`、等比縮放 `SCALE_SMOOTH`
生成速度慢單線程、大尺寸、重複編碼線程池並行、適當尺寸、緩存相同內容

最佳實踐總結

  1. 容錯率設 H:嵌入 Logo 必須最高容錯
  2. Logo 面積 ≤ 20%:居中、等比壓縮、圓角邊框
  3. 靜默區保留:Margin 1-2 modules
  4. 解析驗證:生成後必掃描驗證,自動化測試覆蓋
  5. 業務隔離:不同業務用不同目錄/前綴,便於管理追蹤
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved