使用 hutool 工具类生成和识别二维码图片
实际工作中生成二维码,通过手机进行扫码查看的需求比较多。Hutool 工具包中有很多实用的工具类,其中就包含二维码生成和识别的 QrCodeUtil 工具类,默认生成白底黑色的二维码,也可以自定义二维码的样式,比如设置背景色、前景色、设置边距以及在二维码中心显示自定义的 logo 等等,使用起来非常方便。
本篇博客通过代码的方式,介绍常用的二维码操作方法,比如:生成二维码、识别二维码的方法,请求网站接口下载二维码、展示二维码的方法,识别二维码提取信息的方法。在博客的最后会提供源代码的下载。
一、搭建工程
搭建一个 SpringBoot 工程,其结构如下所示:
QrController 中主要编写了一些 http 接口,用于演示如何下载二维码以及在页面中展示二维码
test 包下的 QrCodeTest 编写了一些方法,用于演示如何生成二维码以及识别二维码
resources 下的 logo 目录存放了一些图片,用于展示在所生成的二维码图片中心
resources 下的 img 目录下存放了一张二维码图片,用于演示识别二维码,提取二维码信息
先看一下 pom 文件中的依赖包:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jobs</groupId>
<artifactId>springboot_qrcode</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入码生成和识别依赖包-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.4.1</version>
</dependency>
<!--里面有很多非常实用的工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
</plugins>
</build>
</project>
主要引入 hutool-all 依赖和 com.google.zxing 的 core 依赖。com.google.zxing 的 core 依赖是生成和识别二维码的核心依赖,hutool 里面提供了 QrCodeUtil 工具类,对其进行了封装,大大简化了二维码的生成和识别的代码实现。
二、代码实现
由于代码很简单,注释也很详细,这里就不详细介绍,首先看一下 QrCodeTest 类的代码:
package com.jobs;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Base64Utils;
import org.springframework.util.ResourceUtils;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
@SpringBootTest
public class QrCodeTest {
//直接根据url生成二维码
@Test
void test1() {
QrCodeUtil.generate("https://www.cnblogs.com/studyjobs",
300, 300, FileUtil.file("D:\\qrcode1.jpg"));
}
//设置二维码前景色、背景色、中间的logo
@Test
void test2() throws FileNotFoundException {
QrConfig config = new QrConfig(300, 300);
// 设置边距,既二维码和背景之间的边距
config.setMargin(1);
// 设置前景色,二维码颜色(绿色)
config.setForeColor(new Color(0, 139, 69));
// 设置背景色(淡黄色)
config.setBackColor(new Color(255, 255, 224));
// 设置中间的 logo 图片
String logoPath = ResourceUtils.getFile("classpath:logo/logo1.png").getPath();
config.setImg(logoPath);
// 设置容错级别
config.setErrorCorrection(ErrorCorrectionLevel.H);
// 生成二维码到文件,也可以到流
QrCodeUtil.generate("https://www.cnblogs.com/studyjobs",
config, FileUtil.file("D:/qrcode2.jpg"));
}
//生成二维码的base64字符串
@Test
void test3() {
byte[] pngArr = QrCodeUtil.generatePng("https://www.cnblogs.com/studyjobs",
300, 300);
String base64 = Base64Utils.encodeToString(pngArr);
System.out.println(base64);
//将 base64 字符串,放入 html 的 image 标签中,打开 html 就可以看到二维码图片
//格式为:<img src="data:image/png;base64,你的图片的base64写在这里"/>
//我们可以让 http 接口返回图片的 base64 字符串到前端,相当于提供了二维码图片下载接口
}
//生成二维码的base64字符串
@Test
void test4() throws FileNotFoundException {
QrConfig config = new QrConfig(300, 300);
// 设置边距,既二维码和背景之间的边距
config.setMargin(1);
// 设置前景色,二维码颜色(绿色)
config.setForeColor(new Color(0, 139, 69));
// 设置背景色(淡黄色)
config.setBackColor(new Color(255, 255, 224));
// 设置中间的 logo 图片
String logoPath = ResourceUtils.getFile("classpath:logo/logo2.png").getPath();
config.setImg(logoPath);
// 设置容错级别
config.setErrorCorrection(ErrorCorrectionLevel.H);
byte[] pngArr = QrCodeUtil.generatePng("https://www.cnblogs.com/studyjobs", config);
String base64 = Base64Utils.encodeToString(pngArr);
System.out.println(base64);
//将 base64 字符串,放入 html 的 image 标签中,打开 html 就可以看到二维码图片
//格式为:<img src="data:image/png;base64,你的图片的base64写在这里"/>
//我们可以让 http 接口返回图片的 base64 字符串到前端,相当于提供了二维码图片下载接口
}
//识别二维码中的 url 信息
@Test
void test5() throws FileNotFoundException {
File qrFile = ResourceUtils.getFile("classpath:img/QrCode.png");
String decode = QrCodeUtil.decode(qrFile);
System.out.println(decode);
}
//识别 base64 图片中的 url 信息
@Test
void test6() {
String base64 = "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";
byte[] bytes = Base64Utils.decodeFromString(base64);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
String decode = QrCodeUtil.decode(bis);
System.out.println(decode);
}
}
然后再看一下 QrController 的代码内容:
package com.jobs.controller;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.springframework.util.Base64Utils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
@RequestMapping("/qrcode")
@RestController
public class QrController {
//下载二维码的 base64 字符串
@GetMapping("/download1")
public String download1(HttpServletResponse response) throws Exception {
//生成默认的白底黑码,中间无logo的二维码
byte[] pngArr = QrCodeUtil.generatePng("https://www.cnblogs.com/studyjobs",
300,300);
String base64 = Base64Utils.encodeToString(pngArr);
return base64;
}
//下载二维码图片
@GetMapping("/download2")
public void download2(HttpServletResponse response) throws Exception {
QrConfig config = new QrConfig(300, 300);
// 设置边距,既二维码和背景之间的边距
config.setMargin(1);
// 设置前景色,二维码颜色(绿色)
config.setForeColor(new Color(0, 139, 69));
// 设置背景色(淡黄色)
config.setBackColor(new Color(255, 255, 224));
// 设置中间的 logo 图片
String logoPath = ResourceUtils.getFile("classpath:logo/logo3.png").getPath();
config.setImg(logoPath);
// 设置容错级别
config.setErrorCorrection(ErrorCorrectionLevel.H);
byte[] pngArr = QrCodeUtil.generatePng("https://www.cnblogs.com/studyjobs", config);
//下载文件的响应类型,这里统一设置成了文件流
//你可以根据自己所提供下载的文件类型,使用不同的响应 mime 类型
response.setContentType("application/octet-stream;charset=utf-8");
//设置下载弹出框中默认显示的文件名称,如果指定中文名称的话,需要转成 iso8859-1 编码,解决乱码问题
String fileName = new String("二维码图片.png".getBytes(), "iso8859-1");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
response.getOutputStream().write(pngArr, 0, pngArr.length);
}
//浏览器直接显示二维码图片
@GetMapping("/download3")
public void download3(HttpServletResponse response) throws Exception {
QrConfig config = new QrConfig(300, 300);
// 设置边距,既二维码和背景之间的边距
config.setMargin(1);
// 设置前景色,二维码颜色(绿色)
config.setForeColor(new Color(0, 139, 69));
// 设置背景色(淡黄色)
config.setBackColor(new Color(255, 255, 224));
// 设置中间的 logo 图片
String logoPath = ResourceUtils.getFile("classpath:logo/logo2.png").getPath();
config.setImg(logoPath);
// 设置容错级别
config.setErrorCorrection(ErrorCorrectionLevel.H);
byte[] pngArr = QrCodeUtil.generatePng("https://www.cnblogs.com/studyjobs", config);
response.setContentType("image/png");
response.getOutputStream().write(pngArr, 0, pngArr.length);
}
}
下面列出代码执行过程中,自定义生成的一张二维码图片,扫描后跳转到博客园中我的博客首页:
以上就是本篇博客内容的全部,非常简略,具体细节可以下载源代码运行和验证。
本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_qrcode.zip