| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- package com.ruoyi.common.utils.poi;
- import com.alibaba.fastjson.JSON;
- import com.google.zxing.BarcodeFormat;
- import com.google.zxing.EncodeHintType;
- import com.google.zxing.MultiFormatWriter;
- import com.google.zxing.WriterException;
- import com.google.zxing.common.BitMatrix;
- import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
- import com.ruoyi.common.utils.StringUtils;
- import com.ruoyi.framework.config.RuoYiConfig;
- import org.apache.commons.collections4.CollectionUtils;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.Units;
- import org.apache.poi.xwpf.usermodel.XWPFDocument;
- import org.apache.poi.xwpf.usermodel.XWPFParagraph;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import javax.imageio.ImageIO;
- import javax.servlet.http.HttpServletResponse;
- import java.awt.*;
- import java.awt.image.BufferedImage;
- import java.io.*;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipOutputStream;
- public class QRCodeUtil<T> {
- private static final Logger log = LoggerFactory.getLogger(QRCodeUtil.class);
- /**
- * 导出数据列表
- */
- private List<T> list;
- private String title;
- private static XWPFDocument doc;
- //CODE_WIDTH:二维码宽度,单位像素(适当提高分辨率,让文字更清晰)
- private static final int CODE_WIDTH = 400;
- //CODE_HEIGHT:二维码高度,单位像素
- private static final int CODE_HEIGHT = 400;
- //FONT_WIDTH:字体大小(稍微放大一点)
- private static final int FONT_WIDTH = 18;
- //FRONT_COLOR:二维码前景色,0x000000 表示黑色
- private static final int FRONT_COLOR = 0x000000;
- //BACKGROUND_COLOR:二维码背景色,0xFFFFFF 表示白色
- //演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白
- private static final int BACKGROUND_COLOR = 0xFFFFFF;
- /**
- * 实体对象
- */
- public Class<T> clazz;
- public QRCodeUtil(Class<T> clazz) {
- this.clazz = clazz;
- }
- private void init(List<T> list, String title) {
- if (CollectionUtils.isEmpty(list)) {
- list = new ArrayList<>();
- }
- this.list = list;
- this.title = title;
- doc = new XWPFDocument();
- }
- public void exportQRCode(HttpServletResponse response, List<T> list) {
- exportQRCode(response, list, StringUtils.EMPTY);
- }
- public void exportQRCode(HttpServletResponse response, List<T> list, String title) {
- response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
- response.setCharacterEncoding("utf-8");
- this.init(list, title);
- exportQRCode(response);
- }
- public void exportQRCode(HttpServletResponse response) {
- try {
- writeDoc();
- doc.write(response.getOutputStream());
- } catch (Exception e) {
- log.error("导出Word异常{}", e.getMessage());
- } finally {
- IOUtils.closeQuietly(doc);
- }
- }
- public static void createCodeToFile(String content, File codeImgFileSaveDir, String fileName) {
- try {
- if (StringUtils.isBlank(content) || StringUtils.isBlank(fileName)) {
- return;
- }
- content = content.trim();
- if (codeImgFileSaveDir == null || codeImgFileSaveDir.isFile()) {
- //二维码图片存在目录为空,默认放在桌面...
- codeImgFileSaveDir = new File(RuoYiConfig.getQRCodePath());
- }
- if (!codeImgFileSaveDir.exists()) {
- //二维码图片存在目录不存在,开始创建...
- codeImgFileSaveDir.mkdirs();
- }
- //核心代码-生成二维码
- BufferedImage bufferedImage = getBufferedImage(content);
- File codeImgFile = new File(codeImgFileSaveDir, fileName);
- ImageIO.write(bufferedImage, "png", codeImgFile);
- log.info("二维码图片生成成功:" + codeImgFile.getPath());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public static void createCodeToStream(String content, ByteArrayOutputStream baos, String topText, String bottomText) {
- try {
- if (StringUtils.isBlank(content)) {
- return;
- }
- content = content.trim();
- //核心代码-生成二维码
- BufferedImage qrCodeImage = getBufferedImage(content);
- // 计算总高度:顶部文字区域 + 二维码 + 底部文字区域
- int topMargin = StringUtils.isNotEmpty(topText) ? 40 : 0; // 顶部文字区域高度
- int bottomMargin = 80; // 底部文字区域高度,适当加大以容纳多行文字
- int totalHeight = CODE_HEIGHT + topMargin + bottomMargin;
- // 创建包含顶部和底部文字的组合图片
- BufferedImage combinedImage = new BufferedImage(CODE_WIDTH, totalHeight, BufferedImage.TYPE_INT_RGB);
- Graphics2D g = combinedImage.createGraphics();
- // 设置抗锯齿(图像和文字)
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
- // 设置背景颜色为白色
- g.setColor(Color.WHITE);
- g.fillRect(0, 0, CODE_WIDTH, totalHeight);
- // 绘制顶部文字
- if (StringUtils.isNotEmpty(topText)) {
- g.setColor(Color.BLACK);
- g.setFont(new Font("微软雅黑", Font.PLAIN, FONT_WIDTH));
- FontMetrics fm = g.getFontMetrics();
- int textWidth = fm.stringWidth(topText);
- int textX = (CODE_WIDTH - textWidth) / 2;
- int textY = topMargin - 10; // 文字在顶部区域垂直居中
- g.drawString(topText, textX, textY);
- }
- // 绘制二维码(向下偏移顶部文字区域的高度)
- g.drawImage(qrCodeImage, 0, topMargin, null);
- // 绘制底部文字(支持自动换行)
- if (StringUtils.isNotEmpty(bottomText)) {
- g.setColor(Color.BLACK);
- g.setFont(new Font("微软雅黑", Font.PLAIN, FONT_WIDTH));
- FontMetrics fm = g.getFontMetrics();
- int maxWidth = CODE_WIDTH - 20; // 留左右 10 像素边距
- List<String> lines = new ArrayList<>();
- StringBuilder currentLine = new StringBuilder();
- for (int i = 0; i < bottomText.length(); i++) {
- char c = bottomText.charAt(i);
- currentLine.append(c);
- if (fm.stringWidth(currentLine.toString()) > maxWidth) {
- // 超出宽度,回退一个字符作为下一行开始
- currentLine.deleteCharAt(currentLine.length() - 1);
- lines.add(currentLine.toString());
- currentLine = new StringBuilder().append(c);
- }
- }
- if (currentLine.length() > 0) {
- lines.add(currentLine.toString());
- }
- int lineHeight = fm.getHeight();
- int startY = topMargin + CODE_HEIGHT + (bottomMargin - lineHeight * lines.size()) / 2 + fm.getAscent();
- for (int i = 0; i < lines.size(); i++) {
- String line = lines.get(i);
- int textWidth = fm.stringWidth(line);
- int textX = (CODE_WIDTH - textWidth) / 2;
- int textY = startY + i * lineHeight;
- g.drawString(line, textX, textY);
- }
- }
- g.dispose();
- ImageIO.write(combinedImage, "png", baos);
- log.info("二维码图片生成到输出流成功...");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private static Font loadFont(String fontFilePath, int fontSize) throws IOException, FontFormatException {
- return Font.createFont(Font.TRUETYPE_FONT, new File(fontFilePath)).deriveFont(Font.PLAIN, fontSize);
- }
- //核心代码-生成二维码
- private static BufferedImage getBufferedImage(String content) throws WriterException {
- //com.google.zxing.EncodeHintType:编码提示类型,枚举类型
- Map<EncodeHintType, Object> hints = new HashMap<>();
- //EncodeHintType.CHARACTER_SET:设置字符编码类型
- hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
- //EncodeHintType.ERROR_CORRECTION:设置误差校正
- //ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
- //不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
- hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
- //EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近
- hints.put(EncodeHintType.MARGIN, 1);
- MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
- BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
- BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR);
- 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);
- }
- }
- return bufferedImage;
- }
- private void writeDoc() {
- XWPFParagraph paragraph = doc.createParagraph();
- list.forEach(item -> {
- try {
- log.info("==================================================" + JSON.toJSONString(item));
- //务必重写类的toString方法
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- createCodeToStream(JSON.toJSONString(item), baos, title, item.toString());
- InputStream is = new ByteArrayInputStream(baos.toByteArray());
- paragraph.createRun().addPicture(is, XWPFDocument.PICTURE_TYPE_JPEG, "image.jpg", Units.toEMU(200), Units.toEMU(200));
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- }
- /**
- * 生成二维码图片并打包成ZIP文件下载
- *
- * @param response HttpServletResponse
- * @param list 数据列表
- * @param title 标题(可选)
- */
- public void exportQRCodeAsZip(HttpServletResponse response, List<T> list, String title) {
- response.setContentType("application/zip");
- response.setCharacterEncoding("utf-8");
- response.setHeader("Content-Disposition", "attachment;");
- this.init(list, title);
- try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
- // 设置压缩级别
- zos.setLevel(ZipOutputStream.STORED);
- int index = 1;
- for (T item : list) {
- try {
- // 生成文件名
- String fileName = generateFileName(item, index) + ".png";
- index++;
- // 创建ZIP条目
- ZipEntry zipEntry = new ZipEntry(fileName);
- zos.putNextEntry(zipEntry);
- // 生成二维码图片并写入ZIP
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- String content = JSON.toJSONString(item);
- String bottomText = item.toString();
- createCodeToStream(content, baos, title, bottomText);
- // 将字节数组写入ZIP
- zos.write(baos.toByteArray());
- zos.closeEntry();
- baos.close();
- log.info("成功生成二维码图片: {}", fileName);
- } catch (Exception e) {
- log.error("生成二维码图片失败: {}", e.getMessage());
- }
- }
- zos.finish();
- log.info("ZIP文件生成成功,包含 {} 个二维码图片", list.size());
- } catch (Exception e) {
- log.error("生成ZIP文件异常: {}", e.getMessage());
- try {
- response.reset();
- response.setContentType("application/json");
- response.setCharacterEncoding("utf-8");
- response.getWriter().println("生成ZIP文件失败: " + e.getMessage());
- } catch (IOException ex) {
- log.error("发送错误信息失败: {}", ex.getMessage());
- }
- }
- }
- /**
- * 生成二维码图片并打包成ZIP文件下载(简化版)
- *
- * @param response HttpServletResponse
- * @param list 数据列表
- */
- public void exportQRCodeAsZip(HttpServletResponse response, List<T> list) {
- exportQRCodeAsZip(response, list, StringUtils.EMPTY);
- }
- /**
- * 生成文件名
- *
- * @param item 数据项
- * @param index 索引
- * @return 文件名
- */
- private String generateFileName(T item, int index) {
- // 如果对象有特定的标识字段,可以重写此方法
- // 例如:如果对象有name字段,可以返回 item.getName()
- // 默认使用索引+对象字符串表示
- String baseName = StringUtils.substring(item.toString(), 0, 20); // 限制长度
- baseName = baseName.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5]", "_"); // 替换特殊字符
- return String.format("%03d_%s", index, baseName);
- }
- }
|