二维码生成器
这是一个基于Java实现的二维码生成器项目,使用SpringBoot框架和HTML页面构建。核心功能包括:通过ZXing库生成二维码、支持自定义尺寸和添加Logo、提供文件下载功能。项目采用JDK1.8开发,使用Thymeleaf进行页面渲染,代码结构清晰,包含控制层处理页面渲染、文件上传和二维码生成逻辑。主要特性是能够根据用户输入的文本内容生成带Logo的可下载二维码图片文件。
一、效果展示
二、代码说明
- jdk1.8
- 使用springBoot框架+html
- 使用Thymeleaf实现页面渲染
- 使用zxing实现二维码的生成渲染
三、目录结构
四、代码展示
- QrCodeController:控制层,实现页面渲染、文件上传、二维码生成
package com.qrcode.controller;import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;@Controller
public class QrCodeController {@Value("${qrcode.save.path}")private String qrCodeSavePath;/*** 加载index.html页面*/@GetMapping("/")public String index(Model model) {model.addAttribute("message", "欢迎使用二维码生成器");return "index.html";}/*** 生成二维码*/@PostMapping("/generate")public String generateQrCode(@RequestParam("content") String content,@RequestParam(value = "width", defaultValue = "300") int width,@RequestParam(value = "height", defaultValue = "300") int height,@RequestParam(value = "logo", required = false) MultipartFile logo,Model model) {try {// 验证输入if (content == null || content.trim().isEmpty()) {model.addAttribute("message", "请输入二维码内容");return "index.html";}// 创建保存目录File saveDir = new File(qrCodeSavePath);if (!saveDir.exists()) {boolean created = saveDir.mkdirs();if (!created) {model.addAttribute("message", "无法创建保存目录");return "index.html";}}// 生成文件名String fileName = "qrcode_" + System.currentTimeMillis() + ".png";File qrCodeFile = new File(saveDir, fileName);// 生成二维码Map<EncodeHintType, Object> hints = new HashMap<>();hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");hints.put(EncodeHintType.MARGIN, 1);BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);// 将二维码写入文件MatrixToImageWriter.writeToPath(matrix, "PNG", qrCodeFile.toPath());// 如果上传了Logo,添加Logo到二维码if (logo != null && !logo.isEmpty()) {addLogoToQRCode(qrCodeFile, logo);}model.addAttribute("message", "二维码生成成功!");model.addAttribute("qrCodeUrl", "/download/" + fileName);} catch (Exception e) {model.addAttribute("message", "二维码生成失败: " + e.getMessage());e.printStackTrace();}return "index.html";}/*** 文件上传*/@GetMapping("/download/{fileName:.+}")public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {try {Path filePath = Paths.get(qrCodeSavePath).resolve(fileName).normalize();Resource resource = new UrlResource(filePath.toUri());if (resource.exists() && resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\"" + resource.getFilename() + "\"").contentType(MediaType.IMAGE_PNG).body(resource);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {return ResponseEntity.internalServerError().build();}}/*** 添加logo*/private void addLogoToQRCode(File qrCodeFile, MultipartFile logo) throws IOException {try {// 读取原始二维码BufferedImage qrImage = ImageIO.read(qrCodeFile);if (qrImage == null) {throw new IOException("无法读取二维码文件");}// 读取LogoBufferedImage logoImage = ImageIO.read(new ByteArrayInputStream(logo.getBytes()));if (logoImage == null) {throw new IOException("无法读取Logo文件");}// 计算Logo尺寸(二维码的1/5)int logoWidth = Math.min(qrImage.getWidth() / 5, logoImage.getWidth());int logoHeight = Math.min(qrImage.getHeight() / 5, logoImage.getHeight());// 调整Logo大小BufferedImage scaledLogo = new BufferedImage(logoWidth, logoHeight, BufferedImage.TYPE_INT_ARGB);Graphics2D g = scaledLogo.createGraphics();g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);g.drawImage(logoImage, 0, 0, logoWidth, logoHeight, null);g.dispose();// 计算Logo位置(居中)int x = (qrImage.getWidth() - logoWidth) / 2;int y = (qrImage.getHeight() - logoHeight) / 2;// 将Logo绘制到二维码上Graphics2D graphics = qrImage.createGraphics();graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);graphics.drawImage(scaledLogo, x, y, null);graphics.dispose();// 保存带Logo的二维码boolean success = ImageIO.write(qrImage, "PNG", qrCodeFile);if (!success) {throw new IOException("无法保存带Logo的二维码");}} catch (Exception e) {throw new IOException("添加Logo失败: " + e.getMessage(), e);}}
}
- BrowserLauncher:实现运行成功后自动打开http://localhost:8080/路径
package com.qrcode.utils;import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;import java.awt.*;
import java.net.URI;@Component
public class BrowserLauncher {@Value("${server.port}")private String PORT;@EventListener(ApplicationReadyEvent.class)public void launchBrowser() {try {Thread.sleep(1000);// 增强的环境检测if (isGraphicalEnvironmentAvailable()) {Desktop desktop = Desktop.getDesktop();if (desktop.isSupported(Desktop.Action.BROWSE)) {desktop.browse(new URI("http://localhost:" + PORT));System.out.println("浏览器已自动打开");return;}}// 如果桌面环境不支持,尝试命令行方式openBrowserWithCommand();} catch (Exception e) {System.out.println("无法自动打开浏览器: " + e.getMessage());}}private boolean isGraphicalEnvironmentAvailable() {try {// 检查是否有显示器if (System.getenv("DISPLAY") == null && !System.getProperty("os.name").toLowerCase().contains("win")) {return false;}// 检查Desktop类是否可用return Desktop.isDesktopSupported();} catch (Exception e) {return false;}}private void openBrowserWithCommand() {try {String os = System.getProperty("os.name").toLowerCase();Runtime runtime = Runtime.getRuntime();if (os.contains("win")) {// Windowsruntime.exec("cmd /c start http://localhost:" + PORT);} else if (os.contains("mac")) {// macOSruntime.exec(new String[]{"open", "http://localhost:" + PORT});} else if (os.contains("nix") || os.contains("nux")) {// Linux/Unixruntime.exec(new String[]{"xdg-open", "http://localhost:" + PORT});}System.out.println("已尝试通过命令行打开浏览器");} catch (Exception e) {System.out.println("命令行打开浏览器也失败了");}}
}
- index.html:前端页面
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>二维码生成器</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css"><style>body {background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%);min-height: 100vh;padding: 20px 0;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}.card {border: none;border-radius: 12px;overflow: hidden;box-shadow: 0 8px 25px rgba(0, 0, 0, 0.09);}.card-header {padding: 20px;border-bottom: 1px solid rgba(0, 0, 0, 0.05);}.card-body {padding: 30px;}.form-control, .form-control:focus {border-radius: 8px;padding: 12px 15px;box-shadow: none;border: 1px solid #dce1e8;transition: all 0.3s;}.form-control:focus {border-color: #4b6cb7;box-shadow: 0 0 0 3px rgba(75, 108, 183, 0.15);}.form-label {font-weight: 500;margin-bottom: 8px;color: #3a4a6b;}.btn-primary {background: #4b6cb7;border: none;padding: 12px;border-radius: 8px;font-weight: 600;transition: all 0.3s;}.btn-primary:hover {background: #3a5aa0;transform: translateY(-2px);}.btn-success {background: #2ecc71;border: none;border-radius: 8px;padding: 10px 20px;transition: all 0.3s;}.btn-success:hover {background: #27ae60;transform: translateY(-2px);}.preview-area {background: #f8f9fa;border-radius: 12px;padding: 25px;transition: all 0.3s;}.header-icon {font-size: 24px;margin-right: 10px;}.feature-list {font-size: 0.9rem;color: #6c757d;text-align: center;margin-bottom: 20px;}.upload-info {font-size: 0.85rem;color: #6c757d;margin-top: 5px;}.btn {height: 50px;}</style>
</head>
<body>
<div class="container mt-4"><div class="row justify-content-center"><div class="col-lg-8"><div class="card shadow"><div class="card-header bg-primary text-white"><h2 class="text-center mb-0"><i class="bi bi-qr-code header-icon"></i>二维码生成器</h2></div><div class="card-body"><!-- 消息显示 --><div th:if="${message}" class="alert alert-info alert-dismissible fade show" th:text="${message}"><button type="button" class="btn-close" data-bs-dismiss="alert"></button></div><!-- 功能特点 --><p class="feature-list">快速生成二维码 • 自定义尺寸 • 支持添加Logo</p><!-- 生成表单 --><form method="post" action="/generate" enctype="multipart/form-data"><div class="mb-4"><label class="form-label">二维码内容 <span class="text-danger">*</span></label><input type="text" class="form-control" name="content" requiredplaceholder="输入网址或文本,例如:https://www.example.com"></div><div class="row"><div class="col-md-6 mb-4"><label class="form-label">宽度 (像素)</label><input type="number" class="form-control" name="width" value="300" min="100" max="1000"></div><div class="col-md-6 mb-4"><label class="form-label">高度 (像素)</label><input type="number" class="form-control" name="height" value="300" min="100" max="1000"></div></div><div class="mb-4"><label class="form-label">Logo (可选)</label><input type="file" class="form-control" name="logo" accept="image/*"><div class="upload-info">建议使用正方形透明背景的PNG图片</div></div><button type="submit" class="btn btn-primary w-100 py-2"><i class="bi bi-qr-code me-2"></i>生成二维码</button></form><!-- 预览区域 --><div th:if="${qrCodeUrl}" class="mt-5 preview-area"><h5 class="text-center mb-4"><i class="bi bi-image me-2"></i>生成的二维码</h5><div class="d-flex justify-content-center"><img th:src="${qrCodeUrl}" alt="生成的二维码" class="img-fluid mb-4" style="max-width: 300px; border-radius: 8px;"></div><div class="text-center"><a th:href="${qrCodeUrl}" download class="btn btn-success"><i class="bi bi-download me-2"></i>下载二维码</a></div></div></div></div><!-- 页脚 --><div class="text-center mt-4 text-muted"><small>© Demo侠 二维码生成工具</small></div></div></div>
</div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
- application.properties:配置文件
# 端口
server.port=8080
server.servlet.context-path=/# 文件上传
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.file-size-threshold=2MB# Thymeleaf模版设置
spring.thymeleaf.prefix=classpath:/static/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.cache=false# 文件上传路径
qrcode.save.path=./qrcodes# 日志
logging.level.com.example.qrgenerator=DEBUG
logging.level.org.springframework.web=INFO