先上效果图
项目源码: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({// 请求URLurl: "/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({// 请求URLurl: "/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") // 映射请求路径为/indexpublic 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 对象,用于生成不同格式的条形码和二维码。// 生成二维码的 BitMatrixBitMatrix 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 中。// 添加 logoif (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 对象,用于生成不同格式的条形码和二维码。// 生成二维码的 BitMatrixBitMatrix 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 中。// 添加 logoif (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 转换为 LuminanceSourceBufferedImageLuminanceSource 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 转换为 LuminanceSourceBufferedImageLuminanceSource 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") // 映射请求路径为/hellopublic String hello(){ // 方法名,返回一个字符串return "/index"; // 返回值,指示重定向到/index路径}/*** 处理HTTP GET请求,当访问"/index"路径时触发。** @return 返回字符串"cs.html",表示转发到名为"cs.html"的视图。*/@RequestMapping("/index") // 映射请求路径为/indexpublic String index(){ // 方法名,返回一个字符串return "cs.html"; // 返回值,指示转发到名为cs.html的视图}}
总结
深入了解了二维码的生成和解析原理,以及如何通过代码实现这些功能。 掌握了图像处理的基本方法,包括调整图像大小、绘制图像等操作。
学会了如何使用输入输出流进行数据的传输和处理,以及如何将生成的二维码以不同的方式返回给客户端。
熟悉了 Spring框架的基本使用,包括如何定义 RESTful API 接口、处理参数和响应等。
提高了代码的错误处理和参数校验能力,确保程序的稳定性和可靠性。
总之,这个二维码项目是一个很好的学习示例,涵盖了多个方面的技术要点,可以帮助大家深入理解和掌握 Java 编程中的各种技术。