后端生成带文字的二维码

182 阅读4分钟

需求:要求后端生成如下图的二维码(图中信息为随意添加),前端调接口直接下载图片或压缩包

1648451121070.jpg

思路:因要求生成的二维码样式较多(未展示出来),所以我这把这张图片分成了三个部分,标题,二维码和详情,拼接到一张图片上,代码如下

import com.alibaba.fastjson.JSONObject;
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 com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import javax.swing.filechooser.FileSystemView;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
public class QrCodeUtils {
    private BufferedImage image;
    /**
     * 图片的宽度
     */
    private static final int IMAGE_WIDTH = 380;
    /**
     * 图片的高度
     */
    private static final int IMAGE_HEIGHT = 210;
    /**
     * 二维码颜色 默认是黑色
     */
    private static final int QR_COLOR = 0xFF000000;
    /**
     * 二维码宽
     */
    private static final int WIDTH = 100;
    /**
     * 二维码高
     */
    private static final int HEIGHT = 100;
    /**
     * 单次生成的二维码数量
     */
    private static final int BATCH_NUM = 50;
    /**
     * 文字颜色
     */
    private static final Color WORD_COLOR = Color.black;
    /**
     * 背景颜色
     */
    private static final Color BG_COLOR = Color.white;
    /**
     * 线条颜色
     */
    private static final Color LINE_COLOR = new Color(205, 205, 180);
    /**
     * 背景图形颜色
     */
    private static final Color BG_IMAGE_COLOR = new Color(246, 246, 246);
    /**
     * 标题文字样式
     */
    private static final Font TITLE_FONT = new Font("黑体", Font.BOLD, 16);
    /**
     * 内容文字样式
     */
    private static final Font FONT = new Font("宋体", Font.PLAIN, 14);

    /**
     * 用于设置QR二维码参数
     */
    private static final Map<EncodeHintType, Object> HINTS = new HashMap<EncodeHintType, Object>() {
        private static final long serialVersionUID = 1L;
        {
            // 设置QR二维码的纠错级别(H为最高级别)具体级别信息
            put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            // 设置编码方式
            put(EncodeHintType.CHARACTER_SET, "utf-8");
            put(EncodeHintType.MARGIN, 0);
        }
    };

    /**
     * 生成图片文件到本地
     *
     * @param fileLocation fileLocation
     */
    public void outputImage(String fileLocation) {
        BufferedOutputStream bos = null;
        if (image != null) {
            try {
                FileOutputStream fos = new FileOutputStream(fileLocation + ".jpg");
                bos = new BufferedOutputStream(fos);
                JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bos);
                encoder.encode(image);
                bos.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //关闭输出流
                if (bos != null) {
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
/**
 * 下载图片
 * @param response response
 */
private void downloadImg(HttpServletResponse response) {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            //写入流中
            ImageIO.write(image, "png", baos);
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] bytes = baos.toByteArray();
        response.reset();
        OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
        response.setContentType("application/octet-stream");
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Content-Disposition", "attachment;filename=" + System.currentTimeMillis() + ".png");
        toClient.write(bytes);
        toClient.flush();
        toClient.close();
    }
    catch (IOException e) {
       e.printStackTrace();
    }
}

/**
 * 输出base64图片
 * @return String
 */
private String outputBase64() {
    //io流
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        //写入流中
        ImageIO.write(image, "png", baos);
    } catch (IOException e) {
        e.printStackTrace();
    }
    //转换成字节
    byte[] bytes = baos.toByteArray();
    BASE64Encoder encoder64 = new BASE64Encoder();
    //转换成base64串
    String pngBase64 = encoder64.encodeBuffer(bytes).trim();
    //删除
    pngBase64 = pngBase64.replaceAll("\n", "").replaceAll("\r", "");
    return "data:image/jpg;base64," + pngBase64;
}

/**
 * 临时路径
 * @return String
 */
public static String getTemplatePath() {
    String realPath = QrCodeUtils.class.getClassLoader().getResource("").getFile();
    File file = new File(realPath);
    realPath = file.getAbsolutePath();
    try {
        realPath = java.net.URLDecoder.decode(realPath, "utf-8");
    } catch (Exception e) {
        e.printStackTrace();
    }
    return realPath;
}

/**
 * 输出到压缩包
 * @param zipFile zipFile
 * @param list list
 */
public void zipFiles(File zipFile, List<JSONObject> list) {
    // 判断压缩后的文件存在不,不存在则创建
    if (!zipFile.exists()) {
        try {
            zipFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 创建 FileOutputStream 对象
    FileOutputStream fileOutputStream ;
    // 创建 ZipOutputStream
    ZipOutputStream zipOutputStream ;
    // 创建 FileInputStream 对象
    BufferedInputStream bis = null;
    InputStream inputStream = null;

    try {
        // 实例化 FileOutputStream 对象
        fileOutputStream = new FileOutputStream(zipFile);
        // 实例化 ZipOutputStream 对象
        zipOutputStream = new ZipOutputStream(fileOutputStream);
        // 遍历源文件数组
        for (JSONObject jsonObject : list) {
            long timestamp = System.currentTimeMillis();
            // 二维码
            BufferedImage qrImage = drawQRCode(jsonObject.getString("url"));

            // 总数量
            int totalNum = jsonObject.getInteger("num");
            // 已完成数量
            int num = 0;
            double times = 1;
            // 如果数量大于50,分页生成
            if (jsonObject.getInteger("num") > BATCH_NUM) {
                times = (int)Math.ceil((double)totalNum/BATCH_NUM);
            }
            for (int i = 0; i < times; i++) {
                int tampNum = BATCH_NUM;
                if (num + tampNum > totalNum) {
                    tampNum = totalNum - num;
                }
                drawImg(jsonObject.getString("title"), jsonObject.getString("words") + timestamp,
                        qrImage, tampNum, num, jsonObject.getString("type"));
                num = num + tampNum;
                //io流
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    //写入流中
                    ImageIO.write(image, "jpg", baos);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //转换成字节
                byte[] bytes = baos.toByteArray();
                // 实例化 ZipEntry 对象,源文件数组中的当前文件
                zipOutputStream.putNextEntry(new ZipEntry(System.currentTimeMillis() + ".jpg"));
                zipOutputStream.write(bytes, 0, bytes.length);
            }
        }
        zipOutputStream.closeEntry();
        zipOutputStream.close();
        fileOutputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (bis != null) {
                bis.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public void drawImg(String title, String words, BufferedImage qrImage, Integer num, Integer startNum, String type) {
    //创建主模板图片
    image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT * num, BufferedImage.TYPE_INT_RGB);

    for (int a = 0; a < num; a++) {
        Graphics2D main = image.createGraphics();
        //设置图片的背景色
        main.setColor(new Color(255, 250, 240));
        main.fillRect(0, a * IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_HEIGHT * (a + 1));
        switch (type) {
            default:
                drawImg01(title, words, qrImage, a, startNum);
                break;
        }

    }
}


public void drawImg01(String title, String words, BufferedImage qrImage, int a, int startNum) {
    // 标题区域宽度、高度
    int titleAreaWidth = IMAGE_WIDTH;
    int titleAreaHeight = 60;
    // 二维码区域宽度、高度
    int codeAreaWidth = 140;
    int codeAreaHeight = IMAGE_HEIGHT - titleAreaHeight - 10;
    // 文字区域宽度、高度
    int fontAreaWidth = IMAGE_WIDTH - codeAreaWidth;
    int fontAreaHeight = IMAGE_HEIGHT - titleAreaHeight - 10;

    // ***********************页面头部文字****************
    Graphics2D titleRight = image.createGraphics();
    titleRight.setColor(BG_COLOR);
    titleRight.fillRect(0, a * IMAGE_HEIGHT, titleAreaWidth, titleAreaHeight);
    titleRight.drawImage(drawTitle(title), 125, a * IMAGE_HEIGHT + (titleAreaHeight - 35), null);

    titleRight.setColor(LINE_COLOR);
    titleRight.drawLine(20, a * IMAGE_HEIGHT + (titleAreaHeight - 5), 380, a * IMAGE_HEIGHT + (titleAreaHeight - 5));

    // 左上
    titleRight.drawLine(5, a * IMAGE_HEIGHT + 5, 15, a * IMAGE_HEIGHT + 5);
    titleRight.drawLine(5, a * IMAGE_HEIGHT + 5, 5, a * IMAGE_HEIGHT + 15);
    // 右上
    titleRight.drawLine(IMAGE_WIDTH - 15, a * IMAGE_HEIGHT + 5, IMAGE_WIDTH - 5, a * IMAGE_HEIGHT + 5);
    titleRight.drawLine(IMAGE_WIDTH - 5, a * IMAGE_HEIGHT + 5, IMAGE_WIDTH - 5, a * IMAGE_HEIGHT + 15);

    // ***********************二维码****************
    Graphics2D leftBg = image.createGraphics();
    leftBg.setColor(BG_COLOR);
    leftBg.fillRect(0, a * IMAGE_HEIGHT + titleAreaHeight, codeAreaWidth, IMAGE_HEIGHT - 70);
    leftBg.drawImage(qrImage, 35, a * IMAGE_HEIGHT + 70, qrImage.getWidth(), qrImage.getHeight(), null);
    // 左下
    leftBg.setColor(LINE_COLOR);
    leftBg.drawLine(5, (a + 1) * IMAGE_HEIGHT - 25, 5, (a + 1) * IMAGE_HEIGHT - 15);
    leftBg.drawLine(5, (a + 1) * IMAGE_HEIGHT - 15, 15, (a + 1) * IMAGE_HEIGHT - 15);

    //**********************中间文字部分*********
    Graphics2D centerWord = image.createGraphics();
    centerWord.setColor(BG_COLOR);
    centerWord.fillRect(codeAreaWidth, a * IMAGE_HEIGHT + titleAreaHeight, fontAreaWidth, fontAreaHeight);
    centerWord.drawImage(drawInfo(words, startNum + a, 30), codeAreaWidth + 20, a * IMAGE_HEIGHT + titleAreaHeight + 15, null);
    // 右下
    centerWord.setColor(LINE_COLOR);
    centerWord.drawLine(IMAGE_WIDTH - 15, (a + 1) * IMAGE_HEIGHT - 15, IMAGE_WIDTH - 5, (a + 1) * IMAGE_HEIGHT - 15);
    centerWord.drawLine(IMAGE_WIDTH - 5, (a + 1) * IMAGE_HEIGHT - 15, IMAGE_WIDTH - 5, (a + 1) * IMAGE_HEIGHT - 25);

}

/**
 * @param qrUrl 二维码内容
 * @return 二维码图片
 */
public static BufferedImage drawQRCode(String qrUrl) {
    try {
        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        // 参数顺序分别为:编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
        BitMatrix bm = multiFormatWriter.encode(qrUrl, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, HINTS);
        BufferedImage qrImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);

        // 边框
        Graphics2D border = qrImage.createGraphics();
        border.setColor(LINE_COLOR);
        // 上
        border.drawLine(0, 0, WIDTH, 0);
        // 左
        border.drawLine(0, 0, 0, HEIGHT);
        // 下
        border.drawLine(0, HEIGHT - 2, WIDTH - 2, HEIGHT - 2);
        // 右
        border.drawLine(WIDTH - 2, 0, WIDTH - 2, HEIGHT - 2);

        // 开始利用二维码数据创建Bitmap图片,分别设为黑(0xFFFFFFFF)白(0xFF000000)两色
        for (int x = 1; x < WIDTH - 4; x++) {
            for (int y = 1; y < HEIGHT - 4; y++) {
                qrImage.setRGB(x, y, bm.get(x, y) ? QR_COLOR : 0);
            }
        }
        return qrImage;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

/**
     * 标题
     * @param title 标题内容
     * @return BufferedImage
     */
    public static BufferedImage drawTitle(String title) {
        BufferedImage titleImage = new BufferedImage(200, 20, BufferedImage.TYPE_INT_ARGB);
        Graphics2D titleInfo = titleImage.createGraphics();

        // 设置字体颜色
        titleInfo.setColor(WORD_COLOR);
        titleInfo.setFont(TITLE_FONT);
        titleInfo.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
        titleInfo.drawString(title, 0, 15);
        return titleImage;
    }

    /**
     * 内容
     * @param words 内容
     * @param num 第num个
     * @param margin 文字行间距
     * @return BufferedImage
     */
    public static BufferedImage drawInfo(String words, int num, int margin) {
        BufferedImage infoImage = new BufferedImage(200, 80, BufferedImage.TYPE_INT_ARGB);
        Graphics2D centerWord = infoImage.createGraphics();
        //设置字体颜色
        centerWord.setColor(WORD_COLOR);
        centerWord.setFont(FONT);
        centerWord.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
        String[] info = words.split("&");
        centerWord.drawString(info[0], 0, 15);
        centerWord.drawString(info[1], 0, 15 + margin);
        String tamp = "0000";
        int len = String.valueOf(num).length();
        tamp = tamp.substring(0, tamp.length() - len) + num;
        centerWord.drawString(info[2] + tamp, 0, 15 + 2 * margin);

        return infoImage;
    }

    /**
     * 下载二维码图片
     * @param list list
     * @param response response
     */
    public static void getCodeImg(List<JSONObject> list, HttpServletResponse response) {
        QrCodeUtils cg = new QrCodeUtils();

        for (JSONObject jsonObject : list) {
            // 二维码
            BufferedImage image = drawQRCode(jsonObject.getString("url"));
            cg.drawImg(jsonObject.getString("title"), jsonObject.getString("words"),
                    image, 2, 0, jsonObject.getString("type"));
            cg.downloadImg(response);
        }
    }

    /**
     * 下载zip文件
     * @param list 二维码上的内容,包含标题,产地,产品信息,二维码信息等
     * @param response response
     */
    public static void getCodeZip(List<JSONObject> list, HttpServletResponse response) {
        QrCodeUtils cg = new QrCodeUtils();

        String name ="qrCodeImg.zip";
        File filePath = new File(getTemplatePath() + File.separator + name);
        cg.zipFiles(filePath, list);

        String filename = System.currentTimeMillis() + "_" + name;
        //设置文件路径
        if (filePath.exists()) {
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            try {
                response.setContentType("application/octet-stream");
                response.setHeader("Content-disposition", "attachment; filename=" + new String(filename.getBytes("utf-8"), "ISO8859-1"));
                byte[] buffer = new byte[4096];
                fis = new FileInputStream(filePath);
                bis = new BufferedInputStream(fis);
                OutputStream os = response.getOutputStream();
                int i = bis.read(buffer);
                while (i != -1) {
                    os.write(buffer, 0, i);
                    i = bis.read(buffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (bis != null) {
                    try {
                        bis.close();
                        // 删除临时文件
                        filePath.delete();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        QrCodeUtils cg = new QrCodeUtils();
        // 保存图片的地址
        FileSystemView fsv = FileSystemView.getFileSystemView();
        File path = fsv.getHomeDirectory();
        String outUrl = path.getAbsolutePath() + "\imgs\";
        String info = "产品名称:信阳毛尖&产地:河南信阳&溯源编码:";
        // 二维码内容
        String url = "https://baike.baidu.com/item/%E4%BF%A1%E9%98%B3%E6%AF%9B%E5%B0%96/548774?fr=aladdin";
        String type = "1";
        // 二维码
        BufferedImage image = drawQRCode(url);
        try {
            // 画图(标题,详情,二维码,一张图片上的二维码数量,起始数量,二维码样式)
            cg.drawImg("信阳特产,信阳毛尖", info, image, 1, 0, type);
            // 输出图片
            cg.outputImage(outUrl + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}