Java在线生成二维码

一、前言

说到二维码,我相信大家每天都会用到,尤其是在手机支付的场景,使用频率极广。在实际的业务开发过程中,二维码的使用场景开发也会经常出现在我们开发人员的面前,我们应该如何去处理呢,今天就带着大家一起深入的了解一下它的技术实现过程。

二、代码实践

在 Java 生态体系里面,操作二维码的开源项目很多,如 SwetakeQRCode、BarCode4j、Zxing 等等。

今天我们介绍下简单易用的 google 公司的 zxing,zxing 不仅使用方便,而且可以还操作条形码或者二维码等,不仅有 java 版本,还有 Android 版。

开源库地址:

GitHub 开源地址:https://github.com/zxing/zxing
zxing 二进制包下载地址:http://repo1.maven.org/maven2/com/google/zxing
zxing Maven 仓库地址:https://mvnrepository.com/artifact/com.google.zxing

通过 Maven 仓库,我们可以很轻松的将其依赖包添加到自己的项目。

2.1 添加依赖

        <!-- 如果是非 web 应用则导入 core 包即可,如果是 web 应用,则 core 与 javase 一起导入。-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.4.0</version>
        </dependency>

2.2 生成普通二维码

/**
     * CODE_WIDTH:二维码宽度,单位像素
     * CODE_HEIGHT:二维码高度,单位像素
     * FRONT_COLOR:二维码前景色,0x000000 表示黑色
     * BACKGROUND_COLOR:二维码背景色,0xFFFFFF 表示白色
     * 演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白
     */
    private static final int CODE_WIDTH = 512;
    private static final int CODE_HEIGHT = 512;
    private static final int FRONT_COLOR = 0x000000;
    private static final int BACKGROUND_COLOR = 0xFFFFFF;
    private static MultiFormatWriter mutiWriter = new MultiFormatWriter();

    /**
     * 生成二维码 并 保存为图片
     *
     * @param codeContent
     */
    public static void createCodeToFile(String codeContent) {
        try {
            //获取系统目录
            String filePathDir = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath();
            //随机生成 png 格式图片
            String fileName = new Date().getTime() + ".png";

            /**com.google.zxing.EncodeHintType:编码提示类型,枚举类型
             * EncodeHintType.CHARACTER_SET:设置字符编码类型
             * EncodeHintType.ERROR_CORRECTION:设置误差校正
             *      ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
             *      不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
             * EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近
             * */
            Map<EncodeHintType, Object> hints = new HashMap();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            hints.put(EncodeHintType.MARGIN, 1);

            /**
             * MultiFormatWriter:多格式写入,这是一个工厂类,里面重载了两个 encode 方法,用于写入条形码或二维码
             *      encode(String contents,BarcodeFormat format,int width, int height,Map<EncodeHintType,?> hints)
             *      contents:条形码/二维码内容
             *      format:编码类型,如 条形码,二维码 等
             *      width:码的宽度
             *      height:码的高度
             *      hints:码内容的编码类型
             * BarcodeFormat:枚举该程序包已知的条形码格式,即创建何种码,如 1 维的条形码,2 维的二维码 等
             * BitMatrix:位(比特)矩阵或叫2D矩阵,也就是需要的二维码
             */
            BitMatrix bitMatrix = mutiWriter.encode(codeContent, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);

            /**java.awt.image.BufferedImage:具有图像数据的可访问缓冲图像,实现了 RenderedImage 接口
             * BitMatrix 的 get(int x, int y) 获取比特矩阵内容,指定位置有值,则返回true,将其设置为前景色,否则设置为背景色
             * BufferedImage 的 setRGB(int x, int y, int rgb) 方法设置图像像素
             *      x:像素位置的横坐标,即列
             *      y:像素位置的纵坐标,即行
             *      rgb:像素的值,采用 16 进制,如 0xFFFFFF 白色
             */
            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);
                }
            }

            /**javax.imageio.ImageIO java 扩展的图像IO
             * write(RenderedImage im,String formatName,File output)
             *      im:待写入的图像
             *      formatName:图像写入的格式
             *      output:写入的图像文件,文件不存在时会自动创建
             *
             * 即将保存的二维码图片文件*/
            File codeImgFile = new File(filePathDir, fileName);
            ImageIO.write(bufferedImage, "png", codeImgFile);
            System.out.println("二维码图片生成成功:" + codeImgFile.getPath());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2.3 生成彩色带图标二维码

  /**
     * CODE_WIDTH:二维码宽度,单位像素
     * CODE_HEIGHT:二维码高度,单位像素
     * FRONT_COLOR:二维码前景色,0x000000 表示黑色
     * BACKGROUND_COLOR:二维码背景色,0xFFFFFF 表示白色
     * IMAGE_WIDTH:二维码中间图标内框宽度
     * IMAGE_HEIGHT: 二维码中间图标内框高度
     * 演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白
     */
    private static final int CODE_WIDTH = 512;
    private static final int CODE_HEIGHT = 512;
    private static final int IMAGE_WIDTH = 100;
    private static final int IMAGE_HEIGHT = 100;
    private static final int IMAGE_HALF_WIDTH = IMAGE_WIDTH / 2;
    private static final int FRAME_WIDTH = 2;
    private static MultiFormatWriter mutiWriter = new MultiFormatWriter();

    /**
     * 生成彩色二维码 并 保存为图片
     *
     * @param content
     * @param srcImagePath
     */
    public static void createColorCodeToFile(String content, String srcImagePath) {
        try {
            //获取系统目录
            String filePathDir = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath();
            //随机生成 png 格式图片
            String fileName = new Date().getTime() + ".png";
            BufferedImage scaleImage = scale(srcImagePath, IMAGE_WIDTH, IMAGE_HEIGHT, true);
            int[][] srcPixels = new int[IMAGE_WIDTH][IMAGE_HEIGHT];
            for (int i = 0; i < scaleImage.getWidth(); i++) {
                for (int j = 0; j < scaleImage.getHeight(); j++) {
                    srcPixels[i][j] = scaleImage.getRGB(i, j);
                }
            }
            Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            hints.put(EncodeHintType.MARGIN, 1);
            // 生成二维码
            BitMatrix matrix = mutiWriter.encode(content, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);
            // 二维矩阵转为一维像素数组
            int halfW = matrix.getWidth() / 2;
            int halfH = matrix.getHeight() / 2;
            int[] pixels = new int[CODE_WIDTH * CODE_HEIGHT];
            for (int y = 0; y < matrix.getHeight(); y++) {
                for (int x = 0; x < matrix.getWidth(); x++) {
                    // 左上角颜色,根据自己需要调整颜色范围和颜色
                    if (x > 0 && x < 170 && y > 0 && y < 170) {
                        Color color = new Color(231, 144, 56);
                        int colorInt = color.getRGB();
                        pixels[y * CODE_WIDTH + x] = matrix.get(x, y) ? colorInt : 16777215;
                    }
                    // 读取图片
                    else if (x > halfW - IMAGE_HALF_WIDTH
                            && x < halfW + IMAGE_HALF_WIDTH
                            && y > halfH - IMAGE_HALF_WIDTH
                            && y < halfH + IMAGE_HALF_WIDTH) {
                        pixels[y * CODE_WIDTH + x] = srcPixels[x - halfW + IMAGE_HALF_WIDTH][y - halfH + IMAGE_HALF_WIDTH];
                    } else if ((x > halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH
                            && x < halfW - IMAGE_HALF_WIDTH + FRAME_WIDTH
                            && y > halfH - IMAGE_HALF_WIDTH - FRAME_WIDTH && y < halfH + IMAGE_HALF_WIDTH + FRAME_WIDTH)
                            || (x > halfW + IMAGE_HALF_WIDTH - FRAME_WIDTH
                            && x < halfW + IMAGE_HALF_WIDTH + FRAME_WIDTH
                            && y > halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH && y < halfH + IMAGE_HALF_WIDTH + FRAME_WIDTH)
                            || (x > halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH
                            && x < halfW + IMAGE_HALF_WIDTH + FRAME_WIDTH
                            && y > halfH - IMAGE_HALF_WIDTH - FRAME_WIDTH && y < halfH - IMAGE_HALF_WIDTH + FRAME_WIDTH)
                            || (x > halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH
                            && x < halfW + IMAGE_HALF_WIDTH + FRAME_WIDTH
                            && y > halfH + IMAGE_HALF_WIDTH - FRAME_WIDTH && y < halfH + IMAGE_HALF_WIDTH + FRAME_WIDTH)) {
                        pixels[y * CODE_WIDTH + x] = 0xfffffff;
                        // 在图片四周形成边框
                    } else {
                        // 二维码颜色
                        int num1 = (int) (50 - (50.0 - 13.0) / matrix.getHeight() * (y + 1));
                        int num2 = (int) (165 - (165.0 - 72.0) / matrix.getHeight() * (y + 1));
                        int num3 = (int) (162 - (162.0 - 107.0) / matrix.getHeight() * (y + 1));
                        Color color = new Color(num1, num2, num3);
                        int colorInt = color.getRGB();
                        // 此处可以修改二维码的颜色,可以分别制定二维码和背景的颜色;
                        pixels[y * CODE_WIDTH + x] = matrix.get(x, y) ? colorInt : 16777215;
                        // 0x000000:0xffffff
                    }
                }
            }
            BufferedImage image = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_RGB);
            image.getRaster().setDataElements(0, 0, CODE_WIDTH, CODE_HEIGHT, pixels);
            File codeImgFile = new File(filePathDir, fileName);
            ImageIO.write(image, "jpg", codeImgFile);
            System.out.println("二维码图片生成成功:" + codeImgFile.getPath());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (WriterException e) {
            e.printStackTrace();
        }
    }


    private static BufferedImage scale(String srcImageFile, int height, int width, boolean hasFiller) throws IOException {
        double ratio = 0.0; // 缩放比例
        File file = new File(srcImageFile);
        BufferedImage srcImage = ImageIO.read(file);
        Image destImage = srcImage.getScaledInstance(width, height,
                BufferedImage.SCALE_SMOOTH);
        // 计算比例
        if ((srcImage.getHeight() > height) || (srcImage.getWidth() > width)) {
            if (srcImage.getHeight() > srcImage.getWidth()) {
                ratio = (new Integer(height)).doubleValue()
                        / srcImage.getHeight();
            } else {
                ratio = (new Integer(width)).doubleValue()
                        / srcImage.getWidth();
            }
            AffineTransformOp op = new AffineTransformOp(
                    AffineTransform.getScaleInstance(ratio, ratio), null);
            destImage = op.filter(srcImage, null);
        }
        if (hasFiller) {
            // 补白
            BufferedImage image = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D graphic = image.createGraphics();
            graphic.setColor(Color.white);
            graphic.fillRect(0, 0, width, height);
            if (width == destImage.getWidth(null)) {
                graphic.drawImage(destImage, 0,
                        (height - destImage.getHeight(null)) / 2,
                        destImage.getWidth(null), destImage.getHeight(null),
                        Color.white, null);
            } else {
                graphic.drawImage(destImage,
                        (width - destImage.getWidth(null)) / 2, 0,
                        destImage.getWidth(null), destImage.getHeight(null),
                        Color.white, null);
                graphic.dispose();
                destImage = image;
            }
        }
        return (BufferedImage) destImage;
    }

2.4 条形码生成

/**
     * CODE_WIDTH:条形码宽度,单位像素
     * IMAGE_HEIGHT: 条形码高度
     * 演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白
     */
    private static final int CODE_WIDTH = 512;
    private static final int IMAGE_HEIGHT = 100;
    private static MultiFormatWriter mutiWriter = new MultiFormatWriter();

    // 生成条形码的方法
    public static void generateBarcode(String data) {
       //获取系统目录
        String filePathDir = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath();
      //随机生成 png 格式图片
        String fileName = new Date().getTime() + ".png";
        try {
            Map<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 设置字符编码

            BitMatrix bitMatrix = mutiWriter.encode(data, BarcodeFormat.CODE_128, CODE_WIDTH, IMAGE_HEIGHT, hints);

            // 创建BufferedImage对象来表示条形码
            BufferedImage image = new BufferedImage(CODE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
            for (int x = 0; x < CODE_WIDTH; x++) {
                for (int y = 0; y < IMAGE_HEIGHT; y++) {
                    image.setRGB(x, y, bitMatrix.get(x, y) ? 0 : 0xFFFFFF); // 生成黑色条和白色背景的条形码
                }
            }

            // 将条形码保存到文件
            File barcodeFile = new File(filePathDir,fileName);
            ImageIO.write(image, "png", barcodeFile);

            System.out.println("条形码已生成并保存到: " + barcodeFile.getAbsolutePath());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2.5 二维码读取

/**
     * 解析二维码内容(文件)
     * @param file
     * @return
     * @throws IOException
     */
    public static String parseQRCodeByFile(File file) throws IOException {
        BufferedImage bufferedImage = ImageIO.read(file);
        return parseQRCode(bufferedImage);
    }

    /**
     * 解析二维码内容(网络链接)
     * @param url
     * @return
     * @throws IOException
     */
    public static String parseQRCodeByUrl(URL url) throws IOException {
        BufferedImage bufferedImage = ImageIO.read(url);
        return parseQRCode(bufferedImage);
    }

    private static String parseQRCode(BufferedImage bufferedImage){
        try {
            /**
             * com.google.zxing.client.j2se.BufferedImageLuminanceSource:缓冲图像亮度源
             * 将 java.awt.image.BufferedImage 转为 zxing 的 缓冲图像亮度源
             * 关键就是下面这几句:HybridBinarizer 用于读取二维码图像数据,BinaryBitmap 二进制位图
             */
            LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            Map<DecodeHintType, Object> hints = new HashMap<>();
            hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");

            /**
             * 如果图片不是二维码图片,则 decode 抛异常:com.google.zxing.NotFoundException
             * MultiFormatWriter 的 encode 用于对内容进行编码成 2D 矩阵
             * MultiFormatReader 的 decode 用于读取二进制位图数据
             */
            Result result = new MultiFormatReader().decode(bitmap, hints);
            return result.getText();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("-----解析二维码内容失败-----");
        }
        return null;
    }

2.6 测试

2.7.1 生成

  public static void main(String[] args) {
        String codeContent1 = "Hello World";
        createCodeToFile(codeContent1);
        String codeContent2 = "https://www.baidu.com/";
        createCodeToFile(codeContent2);
        createColorCodeToFile("http://www.baidu.com/", "C:\\Users\\wno704\\Desktop\\favicon.jpg");
    }

结果如下:

二维码图片生成成功:C:\Users\wno704\Desktop\1634476878102.png 二维码图片生成成功:C:\Users\wno704\Desktop\1634476878245.png 二维码图片生成成功:C:\Users\wno704\Desktop\1634476878284.png

具体生成的文件

2.7.2 读取

读取上面生成的二维码

public static void main(String[] args) throws IOException {
        File localFile = new File("C:\\Users\\wno704\\Desktop\\1634476878102.png");
        String content1 = parseQRCodeByFile(localFile);
        System.out.println(localFile + " 二维码内容:" + content1);

        localFile = new File("C:\\Users\\wno704\\Desktop\\1634476878245.png");
        content1 = parseQRCodeByFile(localFile);
        System.out.println(localFile + " 二维码内容:" + content1);

        localFile = new File("C:\\Users\\wno704\\Desktop\\1634476878284.png");
        content1 = parseQRCodeByFile(localFile);
        System.out.println(localFile + " 二维码内容:" + content1);

        URL url = new URL("http://cdn.pzblog.cn/1951b6c4b40fd81630903bf6f7037156.png");
        String content2 = parseQRCodeByUrl(url);
        System.out.println(url + " 二维码内容:" + content2);
    }

执行结果:

C:\Users\wno704\Desktop\1634476878102.png 二维码内容:Hello World C:\Users\wno704\Desktop\1634476878245.png 二维码内容:https://wno704.top C:\Users\wno704\Desktop\1634476878284.png 二维码内容:https://wno704.top http://cdn.pzblog.cn/1951b6c4b40fd81630903bf6f7037156.png 二维码内容:https://www.baidu.com/

2.8 web二维码交互

在实际的项目开发过程中,很多时候二维码都是根据参数实时输出到网页上进行显示的,它的实现原理类似验证码,例如下图,它们都是后台先生成内存图像BufferedImage,然后使用ImageIO.write写出来。

在线生成二维码的功能,其实也类似于此!

2.8.1 前端关键代码

<img src="http://xxxx/qrCode" alt="验证码,点击刷新!" onclick="this.src=this.src+'?temp='+Math.random();" class="content-code fl-r" />

2.8.2 后端关键代码

@Controller
public class SystemController {

    @GetMapping("qrCode")
    public void getQRCode(HttpServletResponse response) {
        String content = "Hello World";
        try {
            /**
             * 调用工具类生成二维码并输出到输出流中
             */
            QRCodeUtil.createCodeToOutputStream(content, response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

其中createCodeToOutputStream方法,源码如下:

/**
     * 生成二维码 并 写入流
     *
     * @param codeContent
     */
    public static void createCodeToOutputStream(String codeContent, OutputStream outputStream) {
        try {

            /**com.google.zxing.EncodeHintType:编码提示类型,枚举类型
             * EncodeHintType.CHARACTER_SET:设置字符编码类型
             * EncodeHintType.ERROR_CORRECTION:设置误差校正
             *      ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
             *      不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
             * EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近
             * */
            Map<EncodeHintType, Object> hints = new HashMap();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            hints.put(EncodeHintType.MARGIN, 1);

            /**
             * MultiFormatWriter:多格式写入,这是一个工厂类,里面重载了两个 encode 方法,用于写入条形码或二维码
             *      encode(String contents,BarcodeFormat format,int width, int height,Map<EncodeHintType,?> hints)
             *      contents:条形码/二维码内容
             *      format:编码类型,如 条形码,二维码 等
             *      width:码的宽度
             *      height:码的高度
             *      hints:码内容的编码类型
             * BarcodeFormat:枚举该程序包已知的条形码格式,即创建何种码,如 1 维的条形码,2 维的二维码 等
             * BitMatrix:位(比特)矩阵或叫2D矩阵,也就是需要的二维码
             */
            BitMatrix bitMatrix = mutiWriter.encode(codeContent, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints);

            /**java.awt.image.BufferedImage:具有图像数据的可访问缓冲图像,实现了 RenderedImage 接口
             * BitMatrix 的 get(int x, int y) 获取比特矩阵内容,指定位置有值,则返回true,将其设置为前景色,否则设置为背景色
             * BufferedImage 的 setRGB(int x, int y, int rgb) 方法设置图像像素
             *      x:像素位置的横坐标,即列
             *      y:像素位置的纵坐标,即行
             *      rgb:像素的值,采用 16 进制,如 0xFFFFFF 白色
             */
            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);
                }
            }

            ImageIO.write(bufferedImage, "png", outputStream);
            System.out.println("二维码图片生成成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这种方式,如果是单体应用,其实没太大问题,在微服务开发的环境下有局限性。

因此我们还有另外一种玩法,那就是将生成的图片流转成base64的格式,然后返回给前端进行展示。

关键代码改造过程如下:

//定义字节输出流,将bufferedImage写入
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", out);
//将输出流转换成base64
String str64 = Base64.getEncoder().encodeToString(out.toByteArray());

最后,把base64内容以json的形式返回给前端,进行展示!

posted on 2026-08-10 15:37  爱河  阅读(2)  评论(0)    收藏  举报

导航