技術選型: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_CORRECTION | H (30%) | 最高容錯,嵌入 Logo 後仍可識別。L(7%)、M(15%)、Q(25%)、H(30%) |
| MARGIN | 1-2 | 靜默區邊距,太小掃描困難,太大浪費空間 |
| QR_SIZE | 300-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` |
| 生成速度慢 | 單線程、大尺寸、重複編碼 | 線程池並行、適當尺寸、緩存相同內容 |
最佳實踐總結
- 容錯率設 H:嵌入 Logo 必須最高容錯
- Logo 面積 ≤ 20%:居中、等比壓縮、圓角邊框
- 靜默區保留:Margin 1-2 modules
- 解析驗證:生成後必掃描驗證,自動化測試覆蓋
- 業務隔離:不同業務用不同目錄/前綴,便於管理追蹤