【详尽-实战篇】使用Springboot生成自带logo或者图片的二维码-扫描二维码可以跳转到指定的页面-Zing-core
先上效果图
项目源码: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 编程中的各种技术。
相关文章:

【详尽-实战篇】使用Springboot生成自带logo或者图片的二维码-扫描二维码可以跳转到指定的页面-Zing-core
先上效果图 项目源码:https://download.csdn.net/download/qq_43055855/89891285 源码地址 手机扫描二维码跳转到指定网页 概述 这个项目是一个基于 Java 的二维码生成与解析工具,主要由 QRCodeUtil 和 QRCodeController 两个类组成。它利用了 Google…...

vue跨标签页通信(或跨窗口)详细教程
在 Vue 应用中,跨标签页(或跨窗口)的通信通常涉及到两个或多个浏览器标签页之间的信息共享。由于每个标签页或窗口都是独立的 JavaScript 执行环境,它们不能直接通过 Vue 或其他 JavaScript 库来直接相互通信。但是,有一些方法可以实现这种跨标签页的通信,主要依靠浏览器…...

【VUE】Vue3通过数组下标更改数组视图为什么会更新?
在 Vue 3 中,使用 Proxy 来实现了对数组的响应式监听,相比于 Vue 2 使用的 Object.defineProperty(),Proxy 更加高效和灵活。 因此,在 Vue 3 中,通过数组下标直接更改数组中某一项的值,也能够被 Vue 正确监…...

前端转换double数据,保留两位小数
Number Number(1.00) 1 Number(1.10) 1.1 Number(1.101) 1.101 要想前端展示页面按 1.00展示1,1.10 展示1.1 需要套一个number() 1.1 保留两位小数,并三位一个分隔符 indexView.value[key] formatNumber(indexView.value[key].toFixed(2))//格式…...

【实战案例】JSR303统一校验与SpringBoot项目的整合
前后端分离项目中,当前前端请求后端接口的时候通常需要传输参数,对于参数的校验应该在哪一步进行校验?Controller中还是Service中?答案是都需要校验,只不过负责的板块不一样,Controller中通常校验请求参数的…...

忘记了系统root密码,如何重置root密码?
重置root密码(CentOS7) 文章目录 重置root密码(CentOS7)[toc] 1.开启系统时,在引导界面按下字母e。 2.进入到内核界面,找到Linux开头字样一行,然后在最末尾输入参数rd.break,然后按住…...

7-基于国产化FT-M6678+JFM7K325T的6U CPCI信号处理卡
一、板卡概述 本板卡系我公司自主研发,基于6U CPCI的通用高性能信号处理平台。板卡采用一片国产8核DSP FT-C6678和一片国产FPGA JFM7K325T-2FFG900作为主处理器。为您提供了丰富的运算资源。如下图所示: 二、设计参考标准 ● PCIMG 2.0 R3.0 CompactP…...

计算机毕业设计 | SSM超市进销存管理系统(附源码)
1,绪论 1.1 开发背景 世界上第一个购物中心诞生于美国纽约,外国人迈克尔库伦开设了第一家合作商店,为了更好地吸引大量客流量,迈克尔库伦精心设计了低价策略,通过大量进货把商品价格压低,通过商店一次性集…...

手撕数据结构 —— 堆(C语言讲解)
目录 1.堆的认识 什么是堆 堆的性质 2.堆的存储 3.堆的实现 Heap.h中接口总览 具体实现 堆结构的定义 初始化堆 销毁堆 堆的插入 堆的向上调整算法 堆的插入的实现 堆的删除 堆的向下调整算法 堆的删除的实现 使用数组初始化堆 获取堆顶元素 获取堆中的数据…...

TS和JS中,string与String的区别
1. string string 是 TypeScript 的基本类型,用于表示简单的字符串值,同时它是一个原始类型,可直接表示文本数据。 2. String String 是 JavaScript 中的一个全局对象(类),用于创建字符串对象࿰…...

jna调用c++动态库linux测试
1、 编译代码和运行指令 javac -cp .:jna-5.7.0.jar:jna-platform-5.7.0.jar JnaTest.java VideoAiLibrary.java java -cp .:jna-5.7.0.jar:jna-platform-5.7.0.jar JnaTest javac -cp .:jna-5.7.0.jar:jna-platform-5.7.0.jar JnaTest.java VideoAiLibrary.java -cp 指定c…...

智诊小助手TF卡记录文件导出
若想将TF卡中记录的数据文件导出可按以下的流程进行配置: 点击主界面中的导出选项即可进入到下图中TF卡应用界面点击TF卡应用界面中“查看记录文件”的选项,进入导出文件界面。点击“选择”进入勾选文件的界面 点击“导出”后,点击“确定”即…...

Jetpack-ViewModel+LiveData+DataBinding
1.ViewModel 解决问题: 瞬态数据丢失异步调用内存泄漏类膨胀提高维护难度和测试难度 作用: 介于View视图和Model数据模型之间桥梁使视图和数据能够分离,也能保持通信 public class MainActivity extends AppCompatActivity {private Tex…...

Servlet[springmvc]的Servlet.init()引发异常
报错: 原因之一: web.xml配置文件中监听器导入依赖项错误...

总结:SQL查询变慢,常见原因分析!
文章目录 引言SQL查询慢原因索引失效特殊情况-执行计划中,key有值,还是很慢怎么办? 多表JOIN为什么互联网公司都不建议使用多表join? 索引基数太小不合理查询字段太多表中数据量太大数据库连接数不够为什么乐观锁还会导致大量的锁…...

基于webrtc实现音视频通信
与传统通信方式不同,p2p通信的实现过程不依赖于中间服务器的信息收发,直接通过信令等完成通信过程的建立; 通过websocket实现信令服务器的建立,而通过信令来确定通信双方; webrtc通过 sdp协议来完善通信双方间协议的…...

【多版本并发控制(MVCC)】
并发事务问题: MySQL隔离级别-未提交读,提交读,可重复读,序列化 隔离级别对于并发事务的解决情况 隔离级别脏读不可重复读幻读未提交读不可不可不可读已提交可不可不可可重复读 (默认)可可不可串行化&…...

常见漏洞及webshell工具的流量特征
常见攻击的流量特征 信息泄露 请求/路径中,包含 特殊文件 或 路径;响应包中,包含敏感信息(如,数据结构,用户信息,网络结构等) 弱口令爆破 非常规流量:短时间内大量数据…...

python学习-怎么在Pycharm写代码
打开Pycharm,点击文件-新建项目 2.选择pure python-点击箭头 展开 3.选择 Existing interpreter 如果 Existing interpreter 下没有相关环境 (1)点击**…** (2)选择python的安装路径 4.可修改文件名称-点击创建 …...

牛客周赛63(C++实现)
🌈个人主页:Yui_ 🌈Linux专栏:Linux 🌈C语言笔记专栏:C语言笔记 🌈数据结构专栏:数据结构 🌈C专栏:C 文章目录 1.小红的好数1.1 题目描述1.2 思路1.3 代码 2.…...

高级英语1第四版教材全解pdf课后答案+课文翻译张汉熙
《高级英语1》是张汉熙教授编著的一本英语教材,广泛用于国内高校英语专业高年级学生的教学。这本书以提高学生的英语综合能力为目标,注重语言知识的系统性和实用性,同时强调跨文化交际能力的培养。书中选材丰富,涵盖了文学、历史、…...

视频去水印软件3款推荐:好用的去水印软件分享!
在处理视频素材时,水印往往是一个令人头疼的问题。幸运的是,市面上有许多优秀的视频编辑软件能够帮助我们快速、有效地去除水印。今天,我将为大家推荐三款功能强大的视频去水印软件:影忆、Final Cut Pro X以及Adobe Premiere Pro&…...

perl文件测试操作符及其意义
perl文件测试操作符及其意义 文件测试操作符意义-r文件或目录,对目前(有效的)用户或组来说是可读的-w文件或目录,对目前(有效的)用户或组来说是可写的-x文件或目录,对目前(有效的&a…...

NC 单据模板自定义项 设置参照(自定义参照)
NC 单据模板自定义项 设置参照(自定义参照) 如图下图,NC 单据模板自定义项 设置参照: 1、选择需要设置参照的自定义字段,选择高级属性页签,在类型设置中,数据类型选择参照信息,即bd…...

Element-ui官方示例(Popover 弹出框)
Element-ui官方示例(Popover 弹出框),好用的弹出框。 使用 vue-cli3 我们为新版的 vue-cli 准备了相应的Element 插件,你可以用它们快速地搭建一个基于 Element 的项目。 使用 Starter Kit 我们提供了通用的项目模版&#…...

Bootstrap 5 练习 - 显示工具提示
文章目录 引言准备工作创建HTML文件导入Bootstrap 5框架编写页面代码编写JavaScript脚本浏览网页注意事项结束语 引言 大家好,今天我们将一起学习如何在Bootstrap 5中创建一个简单的工具提示(Tooltip)。工具提示是一个非常实用的用户界面元素…...

【p2p、分布式,区块链笔记 Torrent】: WebTorrent GitTorrent bittorrent-dht
bittorrent-dht模块 BitTorrent DHT 通过 DHT 网络广播值,允许其他用户通过 DHT 来发现和获取这些数据。 1. 导入依赖 var DHT require(bittorrent-dht)2. 创建实例 var dht new DHT({bootstrap: config.dht.bootstrap }) dht.listen(config.dht.listen)new D…...

【Next.js 项目实战系列】05-删除 Issue
原文链接 CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的库点个star,关注一下吧 上一篇【Next.js 项目实战系列】04-修改 Issue 删除 Issue 添加删除 Button 本节代码链接 这里我们主要关注布局…...

Springboot api http并发测试请求
pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> 线程发起请求 package com.example.demo;import org.springframework.http.HttpEntity; import org…...

Qt的websocket客户端和服务器测试(非安全版本)
测试内容: 客户端: 1 连接服务器 2 发送数据 3 处理错误信号 4 监听断开信号 5 接收服务器的数据 服务器: 1 监听等待客户端连接 2 向指定的客户端发送数据 4 监听断开信号 5 接收客户端的数据 测试界面 工程文件.pro添加的内容:…...