1. 简介
- 原由
需要一个工具类,可以将数据存储在二维码中,通过扫一扫可以获取到信息,条形码不能接受中文字符 收藏和转载以及自己应用时拓展,具体实现和底层原理还不会,有时间我会去了解或者忘记
- 借鉴博客
项目依赖
主要的jar包依赖:
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
代码实现和原理分析
import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.lang3.StringUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 描述:生成二维码的工具类
*
* @author hehe ~_~
* @date 2021-05-14 14:45
*/
public class QrCodeUtils {
private static final String CHARSET = "utf-8";
private static final String FORMAT_NAME = "JPG";
/**
* 二维码的尺寸
*/
private static final int QRCODE_SIZE = 300;
/**
* logo的宽和高
*/
private static final int WIDTH = 60;
private static final int HEIGHT = 60;
/**
* 生成二维码图片
*
* @param content 二维码内容
* @param imagePath 二维码内部图片路径
* @param needCompress 二维码内部图片是否需要压缩标识
* @return 二维码图片
* @throws WriterException 异常
*/
private static BufferedImage createQrCode(String content,String imagePath,boolean needCompress) throws WriterException, IOException {
/**
* EncodeHintType 编码提示类型 枚举类型:
* CHARACTER_SET: 设置字符编码类型 "UTF-8" QR_CODE类型默认的编码格式 "ISO-8859-1"
* MARGIN: 设置二维码边距,单位像素,值越小,二维码距离四周越近
* ERROR_CORRECTION: 设置误差校正
* ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction
* 不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的
*/
Map<EncodeHintType, Object> hints = new ConcurrentHashMap<>(3);
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 1);
//加密生成二维码的矩阵对象 二维的0 1 然后根据特定的值选择属性
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
//设置二维码的黑色0xFF000000 和白色0xFFFFFFFF
image.setRGB(i, j, bitMatrix.get(i, j) ? 0xFF000000 : 0xFFFFFFFF);
}
}
if (StringUtils.isBlank(imagePath)) {
return image;
}
//插入小图标 logo
QrCodeUtils.insertImage(image, imagePath, needCompress);
return image;
}
/**
* 创建线性的条形码
*
* @param content 条形码内容
* @return 生成的条形码
* @throws WriterException 异常
*/
private static BufferedImage createLinearCode(String content) throws WriterException {
Map<EncodeHintType, Object> hints = new ConcurrentHashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.ITF, QRCODE_SIZE, 200, hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
image.setRGB(i, j, bitMatrix.get(i, j) ? 0xFF000000 : 0xFFFFFFFF);
}
}
return image;
}
/**
* 将logo插入二维码中间
*
* @param source 生成的二位码存储对象
* @param imagePath 二维码内部图片的路径
* 一般的业务场景会把插入的小logo放到oss生成url
* @param needCompress 二维码内部图片是否需要压缩
* @throws IOException 异常
*/
private static void insertImage(BufferedImage source, String imagePath, boolean needCompress) throws IOException {
File file = new File(imagePath);
if (!file.exists()) {
//可以自定义抛出异常
System.out.println("文件路径不存在:" + imagePath);
return;
}
//这里修改ImageIO.read() 内可以接受Url InputStream File ImageInputStream
Image src = ImageIO.read(file);
int width = src.getWidth(null);
int height = src.getHeight(null);
//是否压缩
if (needCompress) {
if (width > WIDTH) {
width = WIDTH;
}
if (height > HEIGHT) {
height = HEIGHT;
}
Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
src = image;
}
Graphics2D graphics = source.createGraphics();
int x = (QRCODE_SIZE - width) / 2;
int y = (QRCODE_SIZE - height) / 2;
graphics.drawImage(src,x,y,width,height,null);
Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
graphics.setStroke(new BasicStroke(3f));
graphics.draw(shape);
graphics.dispose();
}
/**
* 生成带logo二维码,并保存到磁盘
*
* @param content 二维码的内容
* @param imgPath 二维码内部插入的图片路径
* @param destPath 整个二维码存储的路径
* @param needCompress 二维码内部插入的图片是否需要压缩的标识
* @throws Exception 异常
*/
public static void encode(String content, String imgPath, String destPath, boolean needCompress) throws Exception {
BufferedImage image = QrCodeUtils.createQrCode(content, imgPath, needCompress);
mkdirs(destPath);
//生成随机文件名
String file = UUID.randomUUID() + ".jpg";
ImageIO.write(image, FORMAT_NAME, new File(destPath + "/" + file));
}
/**
* 生成条形码
*
* @param content 内容 不能有中文
* @param destPath 文件存放路径
* @throws Exception
*/
public static void encode1(String content,String destPath) throws Exception {
BufferedImage image = QrCodeUtils.createLinearCode(content);
mkdirs(destPath);
//生成随机文件名
String file = UUID.randomUUID() + ".jpg";
ImageIO.write(image, FORMAT_NAME, new File(destPath + "/" + file));
}
public static void mkdirs(String destPath) {
File file = new File(destPath);
// 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir。(mkdir如果父目录不存在则会抛出异常)
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
}
}
public static void encode(String content, String imgPath, OutputStream output, boolean needCompress)
throws Exception {
BufferedImage image = QrCodeUtils.createQrCode(content, imgPath, needCompress);
ImageIO.write(image, FORMAT_NAME, output);
}
//我主要用的是这个方法,其他方法我没有用
public static void encode(String content, OutputStream output) throws Exception {
QrCodeUtils.encode(content, null, output, false);
}
public static String decode(String path) throws Exception {
return QrCodeUtils.decode(new File(path));
}
}
这里我拓展一下,我使用的是临时文件上传到oss生成url返回给前端,我直接将url存放在本地磁盘的
public static void main(String[] args) throws Exception {
Long companyId = 0L;
Long userId = 520L;
String fileName = new StringBuilder().append(System.currentTimeMillis()).append(companyId).append(userId).append(".jpg").toString();
/**
* setSizeThreshold: 设置内存缓冲区的大小 默认大小 10240 == 10K 这里默认为1 1024 1k的缓冲区
* setRepository: 指定临时文件存放目录
*/
DiskFileItemFactory fileItemFactory = new DiskFileItemFactory(1,null);
FileItem fileItem = fileItemFactory.createItem("file", "multipart/form-data", true, fileName);
OutputStream outputStream = null;
String url = "";
try {
outputStream = fileItem.getOutputStream();
//生成二维码将二维码的数据存放在流中上传到oss中
QrCodeUtils.encode("12345", outputStream);
//生成二维码
//虽然没有指定路径 但是在机器中存放的路径是fileItem文件 /var/folders/d4/q_9n50d57gjd4hzngg1lr_8c0000gn/T/ 这是我的
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
//上传到oss中
//url = oss.uploadQrCode(multipartFile, fileName);
} catch (Exception e){
throw new Exception("创建二维码异常");
} finally {
//临时文件记得最后一定需要执行删除操作,占用磁盘空间
fileItem.delete();
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
System.out.println(e.toString());
}
}
}
}
关于实现二维码和条形码的枚举类
public enum BarcodeFormat {
/** Aztec 2D barcode format. */
AZTEC,
/** CODABAR 1D format. */
CODABAR,
/** Code 39 1D format. */
CODE_39,
/** Code 93 1D format. */
CODE_93,
/** Code 128 1D format. */
CODE_128,
/** Data Matrix 2D barcode format. */
DATA_MATRIX,
/** EAN-8 1D format. */
EAN_8,//这个我测试的时候只支持8位数字或字母 超出就会报错
/** EAN-13 1D format. */
EAN_13,//13位数字或字母
/** ITF (Interleaved Two of Five) 1D format. */
ITF,
/** MaxiCode 2D barcode format. */
MAXICODE,
/** PDF417 format. */
PDF_417,
/** QR Code 2D barcode format. */
QR_CODE,//这个指定的是二维码的类型 其他的没有测试
/** RSS 14 */
RSS_14,
/** RSS EXPANDED */
RSS_EXPANDED,
/** UPC-A 1D format. */
UPC_A,
/** UPC-E 1D format. */
UPC_E,
/** UPC/EAN extension format. Not a stand-alone format. */
UPC_EAN_EXTENSION
}
标有1D的应该是条形码,2D是二维码,至于区别后面自己挖掘