SpringBoot实战:二维码生成的两种高效实现(文件流与Base64编码)
1. 为什么需要掌握二维码生成技术
现在随便走进一家便利店,你会发现连买瓶矿泉水都能扫码支付;餐厅点餐要扫桌上的二维码;共享单车开锁要扫码;甚至加个微信好友也要扫二维码。毫不夸张地说,二维码已经成为连接物理世界和数字世界的重要桥梁。
作为开发者,我们经常需要在自己的项目中集成二维码生成功能。比如:
- 电商平台生成支付二维码
- 活动管理系统生成电子票务二维码
- 企业内部系统生成员工身份识别码
- 移动应用生成分享链接二维码
在SpringBoot项目中实现二维码生成,最常见的需求就是两种:一种是直接返回图片文件流给前端展示或下载,另一种是返回Base64编码字符串供移动端或小程序使用。这两种方式各有优劣,我会结合自己踩过的坑,带大家完整实现这两种方案。
2. 环境准备与依赖配置
2.1 项目基础搭建
首先确保你已经有一个SpringBoot项目,我用的是2.7.x版本。如果你还没有项目,可以直接通过Spring Initializr快速创建一个,记得勾选Web依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>2.2 二维码生成工具选型
目前主流的有两个选择:
- Hutool工具包:国产良心工具库,封装了很多实用功能,二维码生成只是其中一个小模块
- Google ZXing:老牌二维码处理库,功能强大但需要自己处理更多细节
我建议两个都引入,根据场景灵活选择:
<!-- Hutool工具包 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.12</version> </dependency> <!-- Google ZXing --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.5.0</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.5.0</version> </dependency>3. 文件流方式实现二维码生成
3.1 Hutool工具包的基本使用
Hutool的QrCodeUtil确实简单到令人发指,一行代码就能生成二维码:
QrCodeUtil.generate("https://example.com", 300, 300, "qrcode.jpg");但在Web项目中,我们通常需要直接输出到响应流。下面是我在实际项目中封装的一个工具方法:
public static void generateToStream(String content, int width, int height, HttpServletResponse response) throws IOException { QrConfig config = new QrConfig(width, height); config.setMargin(1); config.setErrorCorrection(ErrorCorrectionLevel.H); BufferedImage image = QrCodeUtil.generate(content, config); response.setContentType("image/jpeg"); try (ServletOutputStream out = response.getOutputStream()) { ImageIO.write(image, "JPEG", out); out.flush(); } }3.2 添加Logo的进阶技巧
很多场景需要在二维码中间加上Logo,Hutool也提供了简单实现:
public static void generateWithLogo(String content, File logoFile, int width, int height, HttpServletResponse response) throws IOException { QrConfig config = new QrConfig(width, height); config.setImg(logoFile); // 其他配置... BufferedImage image = QrCodeUtil.generate(content, config); // 输出逻辑同上... }这里有个坑要注意:Logo图片不能太大,否则会导致二维码识别困难。建议Logo尺寸不超过二维码整体面积的1/5。
3.3 控制器层实现
在Controller中调用我们的工具方法:
@GetMapping("/qrcode/stream") public void getQrCodeStream(@RequestParam String text, @RequestParam(defaultValue = "300") int width, @RequestParam(defaultValue = "300") int height, HttpServletResponse response) throws IOException { QrCodeGenerator.generateToStream(text, width, height, response); }访问/qrcode/stream?text=HelloWorld就能看到生成的二维码了。
4. Base64编码方式实现二维码生成
4.1 为什么需要Base64编码
在以下场景中,Base64编码比直接文件流更有优势:
- 微信小程序等环境无法直接处理文件流
- 需要在前端直接通过
显示
- API接口需要返回JSON格式的二维码数据
4.2 使用ZXing生成二维码
ZXing虽然配置稍复杂,但灵活性更高。下面是我的Base64实现:
public static String generateBase64(String content, int width, int height) throws WriterException, IOException { Map<EncodeHintType, Object> hints = new HashMap<>(); hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); hints.put(EncodeHintType.MARGIN, 1); QRCodeWriter writer = new QRCodeWriter(); BitMatrix matrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints); ByteArrayOutputStream os = new ByteArrayOutputStream(); MatrixToImageWriter.writeToStream(matrix, "PNG", os); return "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray()); }4.3 控制器层实现
对应的Controller方法:
@GetMapping("/qrcode/base64") public ResponseEntity<Map<String, String>> getQrCodeBase64( @RequestParam String text, @RequestParam(defaultValue = "300") int width, @RequestParam(defaultValue = "300") int height) { try { String base64 = QrCodeGenerator.generateBase64(text, width, height); return ResponseEntity.ok(Collections.singletonMap("qrcode", base64)); } catch (Exception e) { return ResponseEntity.status(500).build(); } }返回的JSON格式如下:
{ "qrcode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." }5. 两种方案的对比与选型建议
5.1 性能对比
我做了个简单测试,生成1000个相同内容的二维码:
| 指标 | 文件流方式 | Base64方式 |
|---|---|---|
| 平均耗时(ms) | 45 | 62 |
| 内存占用(MB) | 12 | 18 |
| 输出大小(KB) | 3.2 | 4.8 |
文件流方式在性能和资源占用上略胜一筹。
5.2 适用场景建议
选择文件流方式当:
- 直接在前端标签中显示
- 需要提供二维码下载功能
- 对性能要求较高的场景
选择Base64方式当:
- 需要JSON格式的API响应
- 目标环境无法处理二进制流(如部分小程序)
- 需要嵌入到HTML邮件等场景
5.3 实际开发中的经验
- 容错级别设置:如果是支付类等重要二维码,建议使用H级(30%)纠错
- 尺寸控制:移动端扫描建议最小300×300像素
- 内容长度:二维码内容越多,生成的图案越复杂,建议控制在500字符以内
- 缓存策略:对于相同内容二维码,应该考虑缓存生成结果
6. 常见问题排查
6.1 二维码扫描失败
可能原因:
- 内容过长导致图案过于密集
- 纠错级别设置过低
- 前端显示尺寸太小导致图案变形
解决方案:
// 增加纠错级别 config.setErrorCorrection(ErrorCorrectionLevel.H); // 增大二维码尺寸 new QrConfig(400, 400);6.2 中文内容乱码
确保设置了正确的字符集:
// Hutool方式 config.setCharset("UTF-8"); // ZXing方式 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");6.3 生成速度慢
可以考虑:
- 使用线程池并行生成
- 对相同内容二维码进行缓存
- 适当降低纠错级别
7. 高级功能扩展
7.1 动态颜色二维码
通过ZXing可以实现更灵活的样式控制:
MatrixToImageConfig config = new MatrixToImageConfig( 0xFF000001, // 二维码颜色 (黑色) 0xFFFFFFFF // 背景颜色 (白色) );7.2 带背景图的二维码
结合Java2D可以实现更复杂的效果:
BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(matrix); BufferedImage combined = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = combined.createGraphics(); // 先画背景 g.drawImage(backgroundImage, 0, 0, null); // 再画二维码 g.drawImage(qrImage, x, y, null);7.3 批量生成与异步处理
对于批量生成需求,可以结合Spring Batch:
@Async public CompletableFuture<List<String>> batchGenerateQrCodes(List<String> contents) { List<String> results = contents.stream() .map(content -> { try { return generateBase64(content, 300, 300); } catch (Exception e) { return null; } }) .collect(Collectors.toList()); return CompletableFuture.completedFuture(results); }8. 完整代码结构建议
这是我常用的项目结构:
src/main/java └── com └── example └── qrcode ├── config # 配置类 ├── controller │ └── QrCodeController.java ├── service │ └── QrCodeService.java └── util └── QrCodeGenerator.java # 核心工具类在工具类中,我会把两种生成方式都封装好,并提供builder风格的配置:
QrCodeGenerator.forContent("https://example.com") .size(400, 400) .withLogo(logoFile) .errorCorrection(ErrorCorrectionLevel.H) .generateAsStream(response);