先看一下生成效果
普通二维码
普通带文本二维码
带logo二维码
带logo带文本二维码
直接上代码
这里主要是用的第三方工具生成二维码的,所以我们需要先引入 jar 包
<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.4.1</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.4.1</version></dependency>
虽然二维码生成依靠的是第三方工具,但是 logo 和文本还是需要我们自己添加进去的
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.qrcode.decoder.ErrorCorrectionLevel;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author Sakura* @date 2024/9/30 14:11*/
public class QRCodeGenerator {/*** @description: 生成二维码* @param text 二维码内容* @param remark 二维码描述* @param path 二维码保存路径* @param logoFile 二维码logo文件* @param width 二维码宽度* @param height 二维码高度*/public static void generateQRCode(String text, String remark, String path, File logoFile, int width, int height) throws Exception {// 设置二维码参数Map<EncodeHintType, Object> hints = new ConcurrentHashMap<>();hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 高纠错等级hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");hints.put(EncodeHintType.MARGIN, 1); // 边框// 创建BitMatrix对象BitMatrix matrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);// 动态计算增加的高度用于备注文本int textHeight = (remark != null && !remark.isEmpty()) ? 30 : 0;// 创建带有二维码和备注文本的 BufferedImageBufferedImage qrImage = new BufferedImage(width, height + textHeight, BufferedImage.TYPE_INT_RGB);Graphics2D g = qrImage.createGraphics();g.setColor(Color.WHITE);g.fillRect(0, 0, width, height + textHeight); // 设置背景为白色// 绘制二维码到 BufferedImagefor (int x = 0; x < width; x++) {for (int y = 0; y < height; y++) {qrImage.setRGB(x, y, matrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);}}// 添加Logo到二维码if (logoFile != null && logoFile.exists()) {addLogoToQRCode(qrImage, logoFile, width, height);}// 添加备注文本if (remark != null && !remark.isEmpty()) {addTextToQRCode(qrImage, remark, width, height, textHeight);}// 保存二维码图片到指定路径Path filePath = Paths.get(path);Files.createDirectories(filePath.getParent()); // 确保目录存在ImageIO.write(qrImage, "PNG", new File(path));}// 添加Logo到二维码private static void addLogoToQRCode(BufferedImage qrImage, File logoFile, int qrWidth, int qrHeight) throws Exception {// 读取Logo图片BufferedImage logoImage = ImageIO.read(logoFile);// 计算Logo的缩放比例和新宽高int logoWidth = Math.min(logoImage.getWidth(), qrWidth / 5); // 缩小至二维码宽度的1/5int logoHeight = Math.min(logoImage.getHeight(), qrHeight / 5);// 计算Logo绘制的左上角位置,使其居中int x = (qrWidth - logoWidth) / 2;int y = (qrHeight - logoHeight) / 2;// 绘制白色边框背景Graphics2D g2 = qrImage.createGraphics();g2.setColor(Color.WHITE);g2.fillRoundRect(x - 5, y - 5, logoWidth + 10, logoHeight + 10, 10, 10);// 绘制Logo到二维码中心g2.drawImage(logoImage.getScaledInstance(logoWidth, logoHeight, Image.SCALE_SMOOTH), x, y, null);g2.dispose();}// 在二维码底部添加文本private static void addTextToQRCode(BufferedImage image, String text, int width, int qrHeight, int textHeight) {Graphics2D g2 = image.createGraphics();g2.setColor(Color.BLACK);g2.setFont(new Font("Arial", Font.PLAIN, 20)); // 设置字体// 获取文本的宽度以便居中对齐FontMetrics fm = g2.getFontMetrics();int textWidth = fm.stringWidth(text);int x = (width - textWidth) / 2;// 调整 y 坐标位置,将文本稍微上移int padding = 10; // 增加一个 padding 值,让文本上移一点,避免贴得太近int y = qrHeight + (textHeight - fm.getHeight()) / 2 + fm.getAscent() - padding;// 绘制文本g2.drawString(text, x, y);g2.dispose(); // 释放资源}
}
然后就可以在我们的业务里面调用了
@RestController
@RequestMapping("/qrcode")
@Module("base")
@Api(value = "二维码api", tags = {"二维码管理"})
public class QrcodeController {@Autowiredprivate QrcodeService qrcodeService;@PostMapping("/generate")@ApiOperation(value = "生成二维码", response = ApiResult.class)public ApiResult<String> generate(@RequestParam(value = "text") String text,@RequestParam(value = "remark", required = false) String remark,@RequestParam(value = "width", required = false, defaultValue = "300") Integer width,@RequestParam(value = "height", required = false, defaultValue = "300") Integer height) throws Exception {String path = qrcodeService.generate(text, remark, width, height);return ApiResult.ok(path);}@PostMapping("/generateWithLogo")@ApiOperation(value = "生成带logo二维码", response = ApiResult.class)public ApiResult<String> generateWithLogo(@RequestPart("file") MultipartFile file,@RequestParam(value = "text") String text,@RequestParam(value = "remark", required = false) String remark,@RequestParam(value = "width", required = false, defaultValue = "300") Integer width,@RequestParam(value = "height", required = false, defaultValue = "300") Integer height) throws Exception {String path = qrcodeService.generateWithLogo(file, text, remark, width, height);return ApiResult.ok(path);}/*** 下载文件*/@GetMapping("/{code}")@ApiOperation(value = "下载")public void download(HttpServletResponse response, @PathVariable("code") String code) throws Exception {qrcodeService.download(response, code);}}
@Service
@Log
public class QrcodeServiceImpl implements QrcodeService {@Value("${local.host}")String LOCAL_HOST;// 文件存放路径跟jar包同目录下/resources/files/private static final String LOCAL_FILE_PATH = "./resources/qrcode/";@Overridepublic String generate(String text, String remark, Integer width, Integer height) throws Exception {// 随机生成一个文件名String code = RandomStringUtils.randomAlphanumeric(32);QRCodeGenerator.generateQRCode(text, remark, LOCAL_FILE_PATH + code + ".png", null, width, height);return LOCAL_HOST + "qrcode/" + code;}@Overridepublic String generateWithLogo(MultipartFile file, String text, String remark, Integer width, Integer height) throws Exception {// 随机生成一个文件名String code = RandomStringUtils.randomAlphanumeric(32);// 创建临时文件,确保文件路径正确File convFile = File.createTempFile("logo_", "_" + file.getOriginalFilename());file.transferTo(convFile);// 调用生成二维码的方法QRCodeGenerator.generateQRCode(text, remark, LOCAL_FILE_PATH + code + ".png", convFile, width, height);// 删除临时文件convFile.delete();return LOCAL_HOST + "qrcode/" + code;}@Overridepublic void download(HttpServletResponse response, String code) throws Exception {File downloadFile = new File(LOCAL_FILE_PATH + code + ".png");if (!downloadFile.exists() || downloadFile.length() == 0) {throw new BusinessException(500, "文件不存在");}// 确定文件的Content-TypeString mimeType = Files.probeContentType(downloadFile.toPath());if (mimeType == null) {mimeType = "application/octet-stream"; // 默认类型}response.setContentType(mimeType);response.addHeader("Content-Length", String.valueOf(downloadFile.length()));response.addHeader("Content-Disposition", "attachment; filename=\"" + code + ".png\"");try (InputStream is = new FileInputStream(downloadFile);OutputStream os = response.getOutputStream()) {IOUtils.copy(is, os);os.flush(); // 确保数据已写入输出流} catch (IOException e) {log.info("下载图片发生IO异常");e.printStackTrace();throw new BusinessException(500, "文件下载失败");} catch (Exception e) {log.info("下载图片发生异常");e.printStackTrace();throw new BusinessException(500, "文件下载失败");}}
}
注意我这里是将二维码保存到了本地 ./resources/qrcode/ 路径下,然后通过 http://localhost:1000/api-base/qrcode/m1BGhAAj29N9eIeKbyEBvocXXboW1RGc 调用自己的下载接口下载的,大家可以直接就返回生成的二维码文件即可