先上效果图
项目源码:https://download.csdn.net/download/qq_43055855/89891285
源码地址
手机扫描二维码跳转到指定网页
概述
这个项目是一个基于 Java 的二维码生成与解析工具,主要由 QRCodeUtil 和 QRCodeController 两个类组成。它利用了 Google ZXing 库来实现二维码的生成和解析功能,并通过 Spring 框架进行整合,提供了 RESTful API 接口用于生成带有 logo 的二维码。
主要功能
生成带有 logo 的二维码:
- 用户可以传入二维码内容和 logo 文件路径(或字节数组),生成带有指定 logo 的二维码图像。通过设置编码提示类型,包括字符集、错误纠正级别和边距等,确保二维码的可读性和容错性。
- 计算 logo 在二维码中的合适大小,并将其居中绘制在二维码图像上,使生成的二维码更加美观和具有辨识度。
- 保存二维码到文件或输出流:
- 可以将生成的二维码保存到指定的文件目录中,若目录不存在则自动创建。如果未指定文件名,则随机生成一个以当前时间戳命名的 png 格式文件。
- 也可以将二维码生成到输出流中,并以 Base64 编码的形式返回给客户端,方便在网页等环境中直接使用。
- 解析二维码内容:提供了两种方式解析二维码内容,分别是解析本地二维码图片文件和解析网络二维码图片地址。通过将图像转换为亮度源,再创建二进制位图,最后使用解码提示类型进行解码,获取二维码中的文本内容。
- RESTful API 接口:QRCodeController 类提供了一个 POST 请求的接口 /qrCode,接受二维码内容和可选的 logo 文件上传。根据上传的 logo 文件情况,生成带有或不带 logo 的二维码,并将其写入 HttpServletResponse 的输出流中返回给客户端。
新建maven项目构建springboot
技术要点
Google ZXing 库的使用:
- 熟练掌握了
Google ZXing
库中各种类的使用,如MultiFormatWriter
用于生成二维码的位矩阵,BitMatrix
表示二维码的位数据,BufferedImageLuminanceSource
和 HybridBinarizer用于二维码的解析等。图像处理: - 利用 Java 的
BufferedImage
和Graphics2D
类进行图像处理,包括调整 logo 图像的大小、在二维码图像上绘制logo
等操作。 - 文件操作和输入输出流:使用
ImageIO
进行图像文件的读取和写入操作,以及处理文件的存在性检查、目录创建等。同时,熟练运用OutputStream
和ByteArrayOutputStream
等输入输出流进行数据的传输和处理。 - Spring 框架整合:
通过 @RestController
和 @Slf4j
等注解,将该工具整合到 Spring 框架中,方便进行日志记录和提供RESTful
API 服务。
参数校验和错误处理:
在接口方法中对传入的参数进行校验,如检查二维码内容是否为空、上传的文件是否为有效的图片文件等。同时,对可能出现的异常进行了捕获和处理,记录错误日志并返回合适的响应。
pom.xml文件代码
<!-- 定义这是一个springboot项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.18.RELEASE</version>
</parent>
<!-- 定义这是一个web应用 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- zxing生成二维码 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
我们需要lombock插件thymeleaf魔板引擎,还有两个核心二维码库
zing的core和javase
在templates下定义yml文件映射
spring:
thymeleaf:
prefix: classpath:/templates/
在静态资源文件resources
下的templates
里边新建一个html文件,注意需自己建文件夹
<!DOCTYPE html>
<!-- 声明文档类型 -->
<html lang="en">
<!-- HTML文档头部 -->
<head>
<!-- 设置字符集为UTF-8 -->
<meta charset="UTF-8">
<!-- 页面标题 -->
<title>二维码生成器</title>
<!-- 内联样式 -->
<style type="text/css">
/* 设置文本区域的字体大小、宽度和高度 */
textarea {
font-size: 30px;
width: 400px;
height: 200px;
}
/* 设置提示信息的样式,包括颜色和默认不显示 */
.hint {
color: red;
display: none;
}
/* 设置二维码容器的样式,包括居中、最小高度等 */
.qrCodeContainer {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100vh;
position: relative;
border: 2px solid sandybrown;
padding: 20px;
box-sizing: border-box;
}
</style>
<!-- 引入jQuery库 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<!-- JavaScript代码 -->
<script type="text/javascript">
// 当DOM准备就绪时执行
$(function () {
// 当按钮被点击时触发
$("button").click(function () {
// 获取文本区域的值
var codeContent = $("textarea").val();
// 获取文件输入框的DOM元素
var logoInput = $("#logoInput")[0];
// 获取用户选择的文件
var logoFile = logoInput.files[0];
// 检查二维码内容是否为空
if (codeContent.trim() === "") {
// 显示提示信息
$(".hint").text("二维码内容不能为空").fadeIn(500);
} else {
// 清除提示信息
$(".hint").text("").fadeOut(500);
// 创建FormData对象存储数据
var formData = new FormData();
// 添加二维码内容到表单数据
formData.append("codeContent", codeContent);
// 如果用户选择了logo文件,则将其添加到表单数据
if (logoFile) {
formData.append("logoFile", logoFile);
}
// 发送AJAX请求
$.ajax({
// 请求URL
url: "/qrCode",
// 请求类型
type: "POST",
// 请求数据
data: formData,
// 不对数据进行序列化处理
processData: false,
// 不设置content-type头
contentType: false,
// 成功回调
success: function (data) {
// 检查返回的数据是否符合base64图片格式
if (/^data:image\/png;base64,[A-Za-z0-9+/]{42,}={0,2}$/.test(data)) {
console.log('Received valid base64 data:', data);
// 获取画布元素
var canvas = document.getElementById('qrCanvas');
// 获取画布上下文
var ctx = canvas.getContext('2d');
// 创建图片对象
var img = new Image();
// 图片加载完成时绘制到画布
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// document.body.appendChild(canvas); // 可选操作:将画布添加到body
};
// 设置图片源为返回的base64数据
img.src = data;
} else {
console.error('Invalid base64 data received:', data);
}
},
// 错误回调
error: function (error) {
console.error(error);
}
});
}
});
});
</script>
</head>
<!-- HTML文档主体 -->
<body>
<!-- 输入框,用户在此输入要生成二维码的内容 -->
<textarea placeholder="请输入要生成二维码内容(网址)..."></textarea><br>
<!-- 文件输入框,用户可以选择logo图片 -->
<input type="file" id="logoInput"><br>
<!-- 按钮,用户点击生成二维码 -->
<button>点击我生成二维码</button>
<!-- 提示信息 -->
<span class="hint"></span>
<!-- 二维码容器,包含画布用于显示生成的二维码 -->
<div class="qrCodeContainer">
<canvas id="qrCanvas"></canvas>
</div>
</body>
</html>
这个html文件主要是前端页面的展示,包括样式的设置向后端发送ajax请求数据,调用接口成功回调后的数据渲染。 核心代码:
// 发送AJAX请求
$.ajax({
// 请求URL
url: "/qrCode",
// 请求类型
type: "POST",
// 请求数据
data: formData,
// 不对数据进行序列化处理
processData: false,
// 不设置content-type头
contentType: false,
// 成功回调
success: function (data) {
// 检查返回的数据是否符合base64图片格式
if (/^data:image\/png;base64,[A-Za-z0-9+/]{42,}={0,2}$/.test(data)) {
console.log('Received valid base64 data:', data);
// 获取画布元素
var canvas = document.getElementById('qrCanvas');
// 获取画布上下文
var ctx = canvas.getContext('2d');
// 创建图片对象
var img = new Image();
// 图片加载完成时绘制到画布
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// document.body.appendChild(canvas); // 可选操作:将画布添加到body
};
// 设置图片源为返回的base64数据
img.src = data;
} else {
console.error('Invalid base64 data received:', data);
}
},
// 错误回调
error: function (error) {
console.error(error);
}
});
在控制层中Controller调用静态资源文件下的html`在这里插入代码片
/**
* 处理HTTP GET请求,当访问"/index"路径时触发。
*
* @return 返回字符串"cs.html",表示转发到名为"cs.html"的视图。
*/
@RequestMapping("/index") // 映射请求路径为/index
public String index(){ // 方法名,返回一个字符串
return "cs.html"; // 返回值,指示转发到名为cs.html的视图
}
新建二维码工具类QRCodeUtil
定义二维码的基本尺寸和参数:
// 定义二维码的基本尺寸和颜色
private static final int CODE_WIDTH = 500; // 二维码宽度,单位像素。
private static final int CODE_HEIGHT = 500; // 二维码高度,单位像素。
private static final int FRONT_COLOR = 0x000000; // 二维码前景色,0x000000 表示黑色。
private static final int BACKGROUND_COLOR = 0xFFFFFF; // 二维码背景色,0xFFFFFF 表示白色。
新建一个BufferedImage类型的方法用于存储图像
private static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
// 创建一个新的 BufferedImage 对象,用于存储调整大小后的图像,设置宽度、高度和类型(TYPE_INT_RGB,表示 24 位 RGB 颜色)。
Graphics2D g = resizedImage.createGraphics();
// 在新的 BufferedImage 对象上创建一个 Graphics2D 对象,用于绘制调整后的图像。
g.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
// 在新的 BufferedImage 对象上绘制原始图像,指定目标宽度和高度,使其调整大小。
g.dispose();
// 释放 Graphics2D 对象占用的资源。
return resizedImage;
// 返回调整大小后的 BufferedImage 对象。
}
接着就是创建二维码并保存文件
/**
* 创建二维码并保存到文件。
*
* @param codeContent 二维码内容
* @param codeImgFileSaveDir 二维码图片保存的目录
* @param fileName 二维码图片文件名称
*/
public static void createCodeToFile(String codeContent, File codeImgFileSaveDir, String fileName, String logoPath) {
try {
if (codeContent == null || "".equals(codeContent)) {
log.info("二维码内容为空,不进行操作...");
return;
}
codeContent = codeContent.trim();
// 如果二维码内容为空,记录日志并返回,不进行后续操作。否则,去除二维码内容两端的空白字符。
if (codeImgFileSaveDir == null || codeImgFileSaveDir.isFile()) {
codeImgFileSaveDir = FileSystemView.getFileSystemView().getHomeDirectory();
log.info("二维码图片存在目录为空,默认放在桌面...");
}
// 如果二维码图片保存目录为空或为文件(而不是目录),将保存目录设置为桌面目录,并记录日志。
if (!codeImgFileSaveDir.exists()) {
codeImgFileSaveDir.mkdirs();
log.info("二维码图片存在目录不存在,开始创建...");
}
// 如果保存目录不存在,创建该目录,并记录日志。
if (fileName == null || "".equals(fileName)) {
fileName = System.currentTimeMillis() + ".png";
log.info("二维码图片文件名为空,随机生成 png 格式图片...");
}
// 如果二维码图片文件名为空,使用当前时间戳作为文件名,并设置为 png 格式,记录日志。
// 使用新方法生成带有 logo 的二维码图像
BufferedImage bufferedImage = getBufferedImageWithLogo(codeContent, logoPath);
File codeImgFile = new File(codeImgFileSaveDir, fileName);
// 创建一个 File 对象,表示二维码图片文件,使用保存目录和文件名。
ImageIO.write(bufferedImage, "png", codeImgFile);
// 使用 ImageIO 将带有 logo 的二维码图像写入文件,指定文件格式为 png。
log.info("二维码图片生成成功:" + codeImgFile.getPath());
// 记录日志,打印二维码图片生成成功的消息和文件路径。
} catch (Exception e) {
e.printStackTrace();
// 如果在生成二维码图片过程中发生异常,打印异常堆栈信息。
}
}
创建二维码输出流的方法
public static void createCodeToOutputStream(String codeContent, OutputStream outputStream, byte[] logoBytes) {
try {
if (codeContent == null || "".equals(codeContent.trim())) {
log.info("二维码内容为空,不进行操作...");
return;
}
codeContent = codeContent.trim();
// 如果二维码内容为空,记录日志并返回,不进行后续操作。否则,去除二维码内容两端的空白字符。
BufferedImage bufferedImage = getBufferedImageWithLogo(codeContent, logoBytes);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(bufferedImage, "png", baos);
// 将带有 logo 的二维码图像写入 ByteArrayOutputStream,指定文件格式为 png。
byte[] imageBytes = baos.toByteArray();
// 获取 ByteArrayOutputStream 中的字节数组,表示二维码图像数据。
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
// 使用 Base64 编码器将二维码图像数据编码为字符串。
outputStream.write(("data:image/png;base64," + base64Image).getBytes());
// 将包含 Base64 编码的二维码图像数据写入输出流,格式为"data:image/png;base64,"加上编码后的字符串。
log.info("二维码图片生成到输出流成功...");
// 记录日志,打印二维码图片生成到输出流成功的消息。
} catch (IOException e) {
log.error("输出流写入错误: {}", e.getMessage());
// 如果在写入输出流过程中发生 IOException,记录错误日志,打印错误消息。
}
} catch (Exception e) {
e.printStackTrace();
log.error("发生错误: {}!", e.getMessage());
// 如果在生成二维码图片过程中发生其他异常,打印异常堆栈信息,并记录错误日志,打印错误消息。
}
}
这是图像的尺寸
/**
*
* @param originalImage // 原始图像
* @param targetWidth 目标宽度
* @param targetHeight 目标高度
* @return
*/
private static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
// 创建一个新的 BufferedImage 对象,用于存储调整大小后的图像,设置宽度、高度和类型(TYPE_INT_RGB,表示 24 位 RGB 颜色)。
Graphics2D g = resizedImage.createGraphics();
// 在新的 BufferedImage 对象上创建一个 Graphics2D 对象,用于绘制调整后的图像。
g.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
// 在新的 BufferedImage 对象上绘制原始图像,指定目标宽度和高度,使其调整大小。
g.dispose();
// 释放 Graphics2D 对象占用的资源。
return resizedImage;
// 返回调整大小后的 BufferedImage 对象。
}
获取图片logo
public static BufferedImage getBufferedImageWithLogo(String codeContent, byte[] logoBytes) {
try {
log.info("生成带有 logo 的二维码,内容:{},logo 字节数组长度:{}", codeContent, logoBytes!= null? logoBytes.length : 0);
// 记录日志,表明正在生成带有 logo 的二维码,并打印二维码内容和 logo 字节数组的长度(如果不为 null)。
// 设置编码提示类型
Map<EncodeHintType, Object> hints = new HashMap<>();
// 创建一个 HashMap 来存储编码提示类型和对应的值。
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 设置编码字符集为 UTF-8,确保二维码中的文本可以正确编码和解码。
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
// 设置二维码的错误纠正级别为 M(中等),提高二维码的容错能力。
hints.put(EncodeHintType.MARGIN, 1);
// 设置二维码的边距为 1,控制二维码周围的空白区域大小。
// 创建多格式写入器实例
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
// 创建一个 MultiFormatWriter 对象,用于生成不同格式的条形码和二维码。
// 生成二维码的 BitMatrix
BitMatrix bitMatrix = multiFormatWriter.encode(codeContent, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
// 使用 MultiFormatWriter 的 encode 方法生成二维码的位矩阵,传入二维码内容、格式(QR_CODE)、宽度、高度和编码提示类型。
// 创建二维码图像
BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR);
// 创建一个 BufferedImage 对象,用于存储二维码图像,设置图像的宽度、高度和类型(TYPE_INT_BGR,表示 24 位 RGB 颜色)。
// 根据 BitMatrix 填充图像
for (int x = 0; x < CODE_WIDTH; x++) {
for (int y = 0; y < CODE_HEIGHT; y++) {
bufferedImage.setRGB(x, y, bitMatrix.get(x, y)? FRONT_COLOR : BACKGROUND_COLOR);
}
}
// 遍历二维码的位矩阵,根据位矩阵中的值设置图像的像素颜色,将前景色(黑色)或背景色(白色)设置到 BufferedImage 中。
// 添加 logo
if (logoBytes!= null && logoBytes.length > 0) {
log.info("添加 logo,字节数组长度:{}", logoBytes.length);
// 如果 logo 字节数组不为 null 且长度大于 0,记录日志并打印 logo 字节数组的长度。
BufferedImage logoImage = ImageIO.read(new ByteArrayInputStream(logoBytes));
// 使用 ImageIO 从字节数组输入流中读取 logo 图像,创建一个 BufferedImage 对象表示 logo 图像。
int logoWidth = bufferedImage.getWidth() / 4;
int logoHeight = bufferedImage.getHeight() / 4;
// 计算 logo 图像在二维码中的大小,这里将 logo 的宽度和高度设置为二维码宽度和高度的四分之一。
BufferedImage resizedLogoImage = resizeImage(logoImage, logoWidth, logoHeight);
// 调用 resizeImage 方法,将 logo 图像调整为计算出的大小,得到调整后的 BufferedImage 对象。
Graphics2D g = bufferedImage.createGraphics();
// 在二维码图像上创建一个 Graphics2D 对象,用于绘制 logo。
int x = (bufferedImage.getWidth() - logoWidth) / 2;
int y = (bufferedImage.getHeight() - logoHeight) / 2;
// 计算 logo 在二维码图像中的位置,使其居中显示。
g.drawImage(resizedLogoImage, x, y, null);
// 在二维码图像上绘制调整后的 logo 图像,指定位置为计算出的坐标。
g.dispose();
// 释放 Graphics2D 对象占用的资源。
}
return bufferedImage;
// 返回带有 logo 的二维码图像。
} catch (WriterException | IOException e) {
e.printStackTrace();
log.error("生成带有 logo 的二维码时发生错误:{}", e.getMessage());
// 如果在生成二维码过程中发生异常,打印异常堆栈信息,并记录错误日志,打印错误消息。
// 可以根据需要返回一个默认的图像或者抛出一个自定义异常
return null;
// 在发生错误时,返回 null,表示生成二维码失败。
}
}
获取二维码上面的logo内容
/**
*
* @param codeContent 二维码的内容
* @param logoPath logo的路径
* @return
*/
public static BufferedImage getBufferedImageWithLogo(String codeContent, String logoPath) {
try {
log.info("生成带有 logo 的二维码,内容:{},logo 路径:{}", codeContent, logoPath);
// 记录日志,表明正在生成带有 logo 的二维码,并打印二维码内容和 logo 路径。
// 设置编码提示类型
Map<EncodeHintType, Object> hints = new HashMap<>();
// 创建一个 HashMap 来存储编码提示类型和对应的值。
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 设置编码字符集为 UTF-8,确保二维码中的文本可以正确编码和解码。
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
// 设置二维码的错误纠正级别为 M(中等),提高二维码的容错能力。
hints.put(EncodeHintType.MARGIN, 1);
// 设置二维码的边距为 1,控制二维码周围的空白区域大小。
// 创建多格式写入器实例
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
// 创建一个 MultiFormatWriter 对象,用于生成不同格式的条形码和二维码。
// 生成二维码的 BitMatrix
BitMatrix bitMatrix = multiFormatWriter.encode(codeContent, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
// 使用 MultiFormatWriter 的 encode 方法生成二维码的位矩阵,传入二维码内容、格式(QR_CODE)、宽度、高度和编码提示类型。
// 创建二维码图像
BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR);
// 创建一个 BufferedImage 对象,用于存储二维码图像,设置图像的宽度、高度和类型(TYPE_INT_BGR,表示 24 位 RGB 颜色)。
// 根据 BitMatrix 填充图像
for (int x = 0; x < CODE_WIDTH; x++) {
for (int y = 0; y < CODE_HEIGHT; y++) {
bufferedImage.setRGB(x, y, bitMatrix.get(x, y)? FRONT_COLOR : BACKGROUND_COLOR);
}
}
// 遍历二维码的位矩阵,根据位矩阵中的值设置图像的像素颜色,将前景色(黑色)或背景色(白色)设置到 BufferedImage 中。
// 添加 logo
if (logoPath!= null &&!"".equals(logoPath)) {
log.info("添加 logo,路径:{}", logoPath);
// 如果 logo 路径不为空且不为空字符串,记录日志并打印 logo 路径。
BufferedImage logoImage = ImageIO.read(new File(logoPath));
// 使用 ImageIO 读取 logo 图像文件,创建一个 BufferedImage 对象表示 logo 图像。
int logoWidth = bufferedImage.getWidth() / 5;
int logoHeight = bufferedImage.getHeight() / 5;
// 计算 logo 图像在二维码中的大小,这里将 logo 的宽度和高度设置为二维码宽度和高度的五分之一。
BufferedImage resizedLogoImage = resizeImage(logoImage, logoWidth, logoHeight);
// 调用 resizeImage 方法,将 logo 图像调整为计算出的大小,得到调整后的 BufferedImage 对象。
Graphics2D g = bufferedImage.createGraphics();
// 在二维码图像上创建一个 Graphics2D 对象,用于绘制 logo。
int x = (bufferedImage.getWidth() - logoWidth) / 2;
int y = (bufferedImage.getHeight() - logoHeight) / 2;
// 计算 logo 在二维码图像中的位置,使其居中显示。
g.drawImage(resizedLogoImage, x, y, null);
// 在二维码图像上绘制调整后的 logo 图像,指定位置为计算出的坐标。
g.dispose();
// 释放 Graphics2D 对象占用的资源。
}
return bufferedImage;
// 返回带有 logo 的二维码图像。
} catch (WriterException | IOException e) {
e.printStackTrace();
log.error("生成带有 logo 的二维码时发生错误:{}", e.getMessage());
// 如果在生成二维码过程中发生异常,打印异常堆栈信息,并记录错误日志,打印错误消息。
// 可以根据需要返回一个默认的图像或者抛出一个自定义异常
return null;
// 在发生错误时,返回 null,表示生成二维码失败。
}
}
解析网络二维码图片内容和本地二维码图片内容
/**
* 解析本地二维码图片内容。
*
* @param file 本地二维码图片文件
* @return 二维码内容
* @throws Exception 如果解析失败
*/
public static String parseQRCodeByFile(File file) {
String resultStr = null;
// 初始化结果字符串为 null,表示如果解析失败,将返回 null。
if (file == null || file.isDirectory() ||!file.exists()) { // 检查文件是否有效
return resultStr;
}
// 如果文件为空、是目录或不存在,直接返回 null,表示无法解析。
try {
// 读取本地图片文件
BufferedImage bufferedImage = ImageIO.read(file);
// 使用 ImageIO 读取本地二维码图片文件,创建一个 BufferedImage 对象表示图片。
// 将 BufferedImage 转换为 LuminanceSource
BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
// 创建一个 BufferedImageLuminanceSource 对象,将读取的 BufferedImage 转换为亮度源,用于二维码解析。
// 创建二进制位图
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
// 创建一个 BinaryBitmap 对象,使用 HybridBinarizer 对亮度源进行二值化处理,得到二进制位图,用于二维码解析。
// 设置解码提示类型
Hashtable hints = new Hashtable<>();
// 创建一个 Hashtable 来存储解码提示类型和对应的值。
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
// 设置解码字符集为 UTF-8,确保二维码中的文本可以正确解码。
// 解码二维码
Result result = new MultiFormatReader().decode(bitmap, hints);
// 使用 MultiFormatReader 对二进制位图进行解码,传入解码提示类型,得到 Result 对象表示解码结果。
resultStr = result.getText();
// 从解码结果中获取二维码的文本内容,并赋值给 resultStr。
} catch (IOException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
log.error("图片非二维码图片, 路径是: {}!", file.getPath());
// 如果找不到二维码或者图片不是二维码,打印错误堆栈信息,并记录错误日志,打印图片路径。
}
return resultStr;
// 返回解析得到的二维码内容,如果解析失败,返回 null。
}
/**
* 解析网络二维码图片内容。
*
* @param url 二维码图片网络地址
* @return 二维码内容
* @throws Exception 如果解析失败
*/
public static String parseQRCodeByUrl(URL url) {
String resultStr = null;
// 初始化结果字符串为 null,表示如果解析失败,将返回 null。
if (url == null) { // 检查 URL 是否有效
return resultStr;
}
// 如果 URL 为空,直接返回 null,表示无法解析。
try {
// 读取网络图片文件
BufferedImage bufferedImage = ImageIO.read(url);
// 使用 ImageIO 读取网络二维码图片文件,创建一个 BufferedImage 对象表示图片。
// 将 BufferedImage 转换为 LuminanceSource
BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
// 创建一个 BufferedImageLuminanceSource 对象,将读取的 BufferedImage 转换为亮度源,用于二维码解析。
// 创建二进制位图
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
// 创建一个 BinaryBitmap 对象,使用 HybridBinarizer 对亮度源进行二值化处理,得到二进制位图,用于二维码解析。
// 设置解码提示类型
Hashtable hints = new Hashtable<>();
// 创建一个 Hashtable 来存储解码提示类型和对应的值。
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
// 设置解码字符集为 UTF-8,确保二维码中的文本可以正确解码。
// 解码二维码
Result result = new MultiFormatReader().decode(bitmap, hints);
// 使用 MultiFormatReader 对二进制位图进行解码,传入解码提示类型,得到 Result 对象表示解码结果。
resultStr = result.getText();
// 从解码结果中获取二维码的文本内容,并赋值给 resultStr。
} catch (IOException e) {
e.printStackTrace();
log.error("二维码图片地址错误, 地址是: {}!", url);
// 如果读取网络图片时发生 IOException,打印错误堆栈信息,并记录错误日志,打印图片地址。
} catch (NotFoundException e) {
e.printStackTrace();
log.error("图片非二维码图片, 地址是: {}!", url);
// 如果找不到二维码或者图片不是二维码,打印错误堆栈信息,并记录错误日志,打印图片地址。
}
return resultStr;
// 返回解析得到的二维码内容,如果解析失败,返回 null。
}
前端调用控制层接口代码
package com.huae.MainApplication.controller;
import com.huae.MainApplication.controller.QRCodeUtil;
// 导入本项目中 QRCodeUtil 工具类。
import lombok.extern.slf4j.Slf4j;
// 使用 Lombok 生成日志对象。
import org.springframework.http.MediaType;
// 导入 Spring 中的 MediaType,用于指定请求的媒体类型。
import org.springframework.web.bind.annotation.PostMapping;
// 导入 Spring 的注解,用于定义 POST 请求的处理方法。
import org.springframework.web.bind.annotation.RequestParam;
// 导入 Spring 的注解,用于从请求中获取参数。
import org.springframework.web.bind.annotation.RestController;
// 导入 Spring 的注解,标记该类为 RESTful Web 服务控制器。
import org.springframework.web.multipart.MultipartFile;
// 导入 Spring 的类,用于处理文件上传。
import javax.imageio.ImageIO;
// 导入 Java 的图像输入输出工具类。
import javax.servlet.http.HttpServletResponse;
// 导入 Servlet 的响应对象。
import java.awt.image.BufferedImage;
// 导入 Java 的缓冲图像类。
import java.io.File;
// 导入 Java 的文件类。
import java.io.IOException;
// 导入 Java 的输入输出异常类。
import java.io.OutputStream;
// 导入 Java 的输出流类。
import static com.huae.MainApplication.controller.QRCodeUtil.getBufferedImageWithLogo;
// 导入静态方法,用于生成带有 logo 的二维码图像。
@RestController
@Slf4j
public class QRCodeController {
@PostMapping(value = "qrCode", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
// 定义一个 POST 请求的处理方法,请求路径为"qrCode",接受的请求内容类型为 multipart/form-data。
public void createQRCodeWithLogo(@RequestParam("codeContent") String codeContent,
// 从请求中获取名为"codeContent"的参数,类型为字符串。
@RequestParam(value = "logoFile", required = false) MultipartFile logoFile,
// 从请求中获取名为"logoFile"的参数,类型为 MultipartFile,表示上传的文件,该参数可选。
HttpServletResponse response) {
try {
if (codeContent == null || "".equals(codeContent.trim())) {
log.info("二维码内容为空,不进行操作...");
// 如果二维码内容为空,记录日志并返回,不进行后续操作。
return;
}
codeContent = codeContent.trim();
// 如果二维码内容不为空,去除两端的空白字符。
OutputStream outputStream = response.getOutputStream();
// 获取 HttpServletResponse 的输出流,用于向客户端发送数据。
if (logoFile != null) {
// 如果上传的文件不为空。
// 检查上传的文件是否是有效的图片文件
if (!isValidImageFile(logoFile)) {
log.error("上传的文件不是有效的图片文件");
// 如果文件不是有效的图片文件,记录错误日志并返回。
return;
}
// 直接将 logo 文件的内容读取到内存中,而不是保存到临时文件
byte[] logoBytes = logoFile.getBytes();
// 将上传的文件内容读取为字节数组。
QRCodeUtil.createCodeToOutputStream(codeContent, outputStream, logoBytes);
// 调用 QRCodeUtil 的 createCodeToOutputStream 方法,生成带有 logo 的二维码并写入输出流。
BufferedImage bufferedImage = getBufferedImageWithLogo(codeContent, logoBytes);
// 使用静态导入的方法生成带有 logo 的二维码图像。
ImageIO.write(bufferedImage, "png", new File("C:\\temp\\debug_qr.png"));
// 将生成的二维码图像写入指定的文件,用于调试。
} else {
QRCodeUtil.createCodeToOutputStream(codeContent, outputStream, null);
// 如果没有上传 logo 文件,调用 QRCodeUtil 的 createCodeToOutputStream 方法,生成不带 logo 的二维码并写入输出流。
}
log.info("成功生成二维码!");
// 记录日志,表示成功生成二维码。
} catch (IOException e) {
log.error("发生错误, 错误信息是:{}!", e.getMessage());
// 如果在处理过程中发生 IOException,记录错误日志并打印错误消息。
}
}
private boolean isValidImageFile(MultipartFile file) {
try {
// 检查文件扩展名是否为常见的图片扩展名
String fileName = file.getOriginalFilename();
// 获取上传文件的原始文件名。
if (fileName != null && (fileName.endsWith(".png") || fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) {
// 如果文件名不为空且扩展名是.png、.jpg 或.jpeg。
// 尝试读取文件内容作为图像
BufferedImage image = ImageIO.read(file.getInputStream());
// 使用 ImageIO 从文件的输入流中读取图像。
return image != null;
// 如果成功读取到图像,返回 true,表示文件是有效的图片文件。
}
return false;
// 如果文件名不满足条件或读取图像失败,返回 false,表示文件不是有效的图片文件。
} catch (IOException e) {
return false;
// 如果在读取文件过程中发生 IOException,返回 false,表示文件不是有效的图片文件。
}
}
}
最后controller层主页面代码
package com.huae.MainApplication.controller;
import org.springframework.stereotype.Controller; // 导入Spring的@Controller注解,用于标记这是一个控制器类
import org.springframework.web.bind.annotation.RequestMapping; // 导入Spring的@RequestMapping注解,用于映射请求URL
import org.springframework.web.bind.annotation.ResponseBody; // 导入Spring的@ResponseBody注解,用于直接返回对象到视图层
/**
* 控制器类,处理HTTP请求。
*/
@Controller // 标记此类为Spring MVC控制器
public class HelloController {
/**
* 处理HTTP GET请求,当访问"/hello"路径时触发。
*
* @return 返回字符串"/index",表示重定向到"/index"路径。
*/
@ResponseBody // 标记此方法返回的内容将直接作为HTTP响应体返回给客户端
@RequestMapping("/hello") // 映射请求路径为/hello
public String hello(){ // 方法名,返回一个字符串
return "/index"; // 返回值,指示重定向到/index路径
}
/**
* 处理HTTP GET请求,当访问"/index"路径时触发。
*
* @return 返回字符串"cs.html",表示转发到名为"cs.html"的视图。
*/
@RequestMapping("/index") // 映射请求路径为/index
public String index(){ // 方法名,返回一个字符串
return "cs.html"; // 返回值,指示转发到名为cs.html的视图
}
}
总结
深入了解了二维码的生成和解析原理,以及如何通过代码实现这些功能。 掌握了图像处理的基本方法,包括调整图像大小、绘制图像等操作。
学会了如何使用输入输出流进行数据的传输和处理,以及如何将生成的二维码以不同的方式返回给客户端。
熟悉了 Spring框架的基本使用,包括如何定义 RESTful API 接口、处理参数和响应等。
提高了代码的错误处理和参数校验能力,确保程序的稳定性和可靠性。
总之,这个二维码项目是一个很好的学习示例,涵盖了多个方面的技术要点,可以帮助大家深入理解和掌握 Java 编程中的各种技术。