diff --git a/pom.xml b/pom.xml index f420f3cd4..df0ca4ad9 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,8 @@ 1.16.7 2.7.0 + + 3.5.3 2.28.22 @@ -320,6 +322,12 @@ ${ip2region.version} + + com.google.zxing + core + ${barcode.version} + + commons-io commons-io diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 2930fd0b0..f65d2a055 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -34,6 +34,7 @@ ruoyi-common-tenant ruoyi-common-websocket ruoyi-common-sse + ruoyi-common-barcode ruoyi-common diff --git a/ruoyi-common/ruoyi-common-barcode/pom.xml b/ruoyi-common/ruoyi-common-barcode/pom.xml new file mode 100644 index 000000000..932b44dc1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-barcode/pom.xml @@ -0,0 +1,32 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-barcode + + + ruoyi-common-barcode 条形码模块 + + + + + org.dromara + ruoyi-common-core + + + + + com.google.zxing + core + + + + + diff --git a/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/constant/BarcodeConstant.java b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/constant/BarcodeConstant.java new file mode 100644 index 000000000..f895c01cb --- /dev/null +++ b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/constant/BarcodeConstant.java @@ -0,0 +1,29 @@ +package org.dromara.common.barcode.constant; + +/** + * 条形码常量 + * + * @author AprilWind + */ +public interface BarcodeConstant { + + /** + * 宽度,单位像素 + */ + Integer CODE_WIDTH = 300; + + /** + * 高度,单位像素 + */ + Integer CODE_HEIGHT = 150; + + /** + * 前景色,0x000000 表示黑色 + */ + Integer FRONT_COLOR = 0x000000; + + /** + * 背景色,0xFFFFFF 表示白色 + */ + Integer BACKGROUND_COLOR = 0xFFFFFF; +} diff --git a/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/enums/BarcodeType.java b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/enums/BarcodeType.java new file mode 100644 index 000000000..b8dc970c9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/enums/BarcodeType.java @@ -0,0 +1,155 @@ +package org.dromara.common.barcode.enums; + +import com.google.zxing.BarcodeFormat; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 条形码的种类 + * + * @author AprilWind + */ +@Getter +@AllArgsConstructor +public enum BarcodeType { + + /** + * Aztec 2D 条形码格式 + * 适用于高信息密度的编码,类似 QR 码 + */ + AZTEC("Aztec", Integer.MAX_VALUE, BarcodeFormat.AZTEC), + + /** + * CODABAR 1D 条形码格式 + * 主要用于物流标签和医疗行业 + * 最大长度:16字符 + */ + CODABAR("Codabar", 16, BarcodeFormat.CODABAR), + + /** + * Code 39 1D 条形码格式 + * 广泛用于工业和物流领域 + * 最大长度:43字符 + */ + CODE_39("Code 39", 43, BarcodeFormat.CODE_39), + + /** + * Code 93 1D 条形码格式 + * 改进的 Code 39,存储密度更高 + * 最大长度:48字符 + */ + CODE_93("Code 93", 48, BarcodeFormat.CODE_93), + + /** + * Code 128 1D 条形码格式 + * 支持整个 ASCII 字符集,适用于物流、快递和仓库管理 + * 最大长度:48字符 + */ + CODE_128("Code 128", 48, BarcodeFormat.CODE_128), + + /** + * Data Matrix 2D 条形码格式 + * 适用于小尺寸高信息密度的编码 + */ + DATA_MATRIX("Data Matrix", Integer.MAX_VALUE, BarcodeFormat.DATA_MATRIX), + + /** + * EAN-8 1D 条形码格式 + * 适用于小型商品包装 + * 最大长度:8字符 + */ + EAN_8("EAN-8", 8, BarcodeFormat.EAN_8), + + /** + * EAN-13 1D 条形码格式 + * 国际商品条形码标准 + * 最大长度:13字符 + */ + EAN_13("EAN-13", 13, BarcodeFormat.EAN_13), + + /** + * ITF (Interleaved Two of Five) 1D 条形码格式 + * 适用于物流和仓库 + * 最大长度:14字符 + */ + ITF("ITF", 14, BarcodeFormat.ITF), + + /** + * MaxiCode 2D 条形码格式 + * 美国 UPS 物流专用 + */ + MAXICODE("MaxiCode", Integer.MAX_VALUE, BarcodeFormat.MAXICODE), + + /** + * PDF417 2D 条形码格式 + * 适用于身份信息存储,如身份证、机票等 + */ + PDF_417("PDF417", Integer.MAX_VALUE, BarcodeFormat.PDF_417), + + /** + * QR Code 2D 条形码格式 + * 适用于网址、名片、支付等广泛场景 + */ + QR_CODE("QR Code", Integer.MAX_VALUE, BarcodeFormat.QR_CODE), + + /** + * RSS-14 1D 条形码格式 + * 常用于食品和药品追溯 + * 最大长度:14字符 + */ + RSS_14("RSS-14", 14, BarcodeFormat.RSS_14), + + /** + * RSS Expanded 1D 条形码格式 + * 支持更多信息存储 + */ + RSS_EXPANDED("RSS Expanded", Integer.MAX_VALUE, BarcodeFormat.RSS_EXPANDED), + + /** + * UPC-A 1D 条形码格式 + * 美国零售商品标准条形码 + * 最大长度:12字符 + */ + UPC_A("UPC-A", 12, BarcodeFormat.UPC_A), + + /** + * UPC-E 1D 条形码格式 + * UPC-A 的简化版本,适用于小商品 + * 最大长度:8字符 + */ + UPC_E("UPC-E", 8, BarcodeFormat.UPC_E), + + /** + * UPC/EAN 扩展格式 + * 通常作为附加条码使用 + * 最大长度:2字符 + */ + UPC_EAN_EXTENSION("UPC/EAN Extension", 2, BarcodeFormat.UPC_EAN_EXTENSION); + + /** + * 条形码类型的名称 + */ + private final String typeName; + + /** + * 每种条形码类型的最大长度限制 + */ + private final int maxLength; + + /** + * ZXing 对应的条形码格式 + */ + private final BarcodeFormat zxingFormat; + + /** + * 校验条形码内容是否符合长度要求 + * + * @param content 条形码内容 + * @return 是否符合长度限制 + */ + public boolean isLengthValid(String content) { + return content.length() <= this.maxLength; + } + +} + diff --git a/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/enums/ImageFormat.java b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/enums/ImageFormat.java new file mode 100644 index 000000000..592716aac --- /dev/null +++ b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/enums/ImageFormat.java @@ -0,0 +1,52 @@ +package org.dromara.common.barcode.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 图片枚举 + * + * @author AprilWind + */ +@Getter +@AllArgsConstructor +public enum ImageFormat { + + /** + * PNG格式:无损压缩,适用于需要高质量图像的场景,通常用于条形码和二维码等 + */ + PNG("PNG"), + + /** + * JPEG格式:有损压缩,适用于存储较大的彩色图像,常用于照片和Web图像 + */ + JPEG("JPEG"), + + /** + * GIF格式:支持动画和透明背景,但只支持256种颜色。适用于简单图像或小动画 + */ + GIF("GIF"), + + /** + * BMP格式:未压缩格式,适用于高质量图像存储,但文件较大,通常不适合Web使用 + */ + BMP("BMP"), + + /** + * TIFF格式:无损压缩,通常用于扫描和打印高质量图像,适合需要高精度的图像存储 + */ + TIFF("TIFF"), + + /** + * WEBP格式:由Google开发,支持有损和无损压缩,文件较小,适用于Web,特别适合移动端使用 + */ + WEBP("WEBP"), + + /** + * JPG格式:是JPEG格式的简称,常用于照片和图像压缩。与JPEG等效,可以接受`.jpg`和`.jpeg`两种扩展名 + */ + JPG("JPG"); + + private final String format; + +} diff --git a/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/utils/BarcodeUtil.java b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/utils/BarcodeUtil.java new file mode 100644 index 000000000..12aec702a --- /dev/null +++ b/ruoyi-common/ruoyi-common-barcode/src/main/java/org/dromara/common/barcode/utils/BarcodeUtil.java @@ -0,0 +1,256 @@ +package org.dromara.common.barcode.utils; + +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.barcode.constant.BarcodeConstant; +import org.dromara.common.barcode.enums.BarcodeType; +import org.dromara.common.barcode.enums.ImageFormat; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * 条形码工具类 + * + * @author AprilWind + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BarcodeUtil { + + /** + * 生成条形码并保存为指定文件 + * + * @param content 条形码内容 + * @param barcodeType 条形码种类 + * @param format 图像格式 + * @param file 保存条形码图像的文件 + * @throws Exception 可能的异常 + */ + public static void generateBarcodeToFile(String content, BarcodeType barcodeType, ImageFormat format, File file) throws Exception { + generateBarcodeToFile(content, barcodeType, BarcodeConstant.CODE_WIDTH, BarcodeConstant.CODE_HEIGHT, format, file); + } + + /** + * 生成条形码并保存为指定文件 + * + * @param content 条形码内容 + * @param barcodeType 条形码种类 + * @param width 图像宽度 + * @param height 图像高度 + * @param format 图像格式 + * @param file 保存条形码图像的文件 + * @throws Exception 可能的异常 + */ + public static void generateBarcodeToFile(String content, BarcodeType barcodeType, int width, int height, ImageFormat format, File file) throws Exception { + // 生成条形码的 BufferedImage 图像 + BufferedImage image = generateBarcodeImage(content, width, height, barcodeType); + // 将图像保存为指定格式的文件 + ImageIO.write(image, format.getFormat(), file); + } + + /** + * 生成条形码并将图像写入指定的输出流 + * + * @param content 条形码内容 + * @param barcodeType 条形码种类 + * @param format 图像格式 + * @param outputStream 输出流,用于写入条形码图像 + * @throws Exception 可能的异常 + */ + public static void generateBarcodeToOutputStream(String content, BarcodeType barcodeType, ImageFormat format, OutputStream outputStream) throws Exception { + generateBarcodeToOutputStream(content, barcodeType, BarcodeConstant.CODE_WIDTH, BarcodeConstant.CODE_HEIGHT, format, outputStream); + } + + /** + * 生成条形码并将图像写入指定的输出流 + * + * @param content 条形码内容 + * @param barcodeType 条形码种类 + * @param width 图像宽度 + * @param height 图像高度 + * @param format 图像格式 + * @param outputStream 输出流,用于写入条形码图像 + * @throws Exception 可能的异常 + */ + public static void generateBarcodeToOutputStream(String content, BarcodeType barcodeType, int width, int height, ImageFormat format, OutputStream outputStream) throws Exception { + // 生成条形码的 BufferedImage 图像 + BufferedImage image = generateBarcodeImage(content, width, height, barcodeType); + + // 将图像写入输出流,使用指定的图像格式 + ImageIO.write(image, format.getFormat(), outputStream); + } + + /** + * 生成条形码并返回指定格式的字节数组 + * + * @param content 条形码内容 + * @param barcodeType 条形码种类 + * @param format 图像格式 + * @return 生成的条形码图像字节数组 + * @throws Exception 可能的异常 + */ + public static byte[] generateBarcodeByte(String content, BarcodeType barcodeType, ImageFormat format) throws Exception { + return generateBarcodeByte(content, barcodeType, BarcodeConstant.CODE_WIDTH, BarcodeConstant.CODE_HEIGHT, format); + } + + /** + * 生成条形码并返回指定格式的字节数组 + * + * @param content 条形码内容 + * @param barcodeType 条形码种类 + * @param width 图像宽度 + * @param height 图像高度 + * @param format 图像格式 + * @return 生成的条形码图像字节数组 + * @throws Exception 可能的异常 + */ + public static byte[] generateBarcodeByte(String content, BarcodeType barcodeType, int width, int height, ImageFormat format) throws Exception { + BufferedImage image = generateBarcodeImage(content, width, height, barcodeType); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, format.getFormat(), baos); + return baos.toByteArray(); + } + + /** + * 生成条形码并返回 BufferedImage + * + * @param content 条形码内容 + * @param barcodeType 条形码的种类 + * @return 生成的 BufferedImage + * @throws WriterException 生成失败异常 + */ + public static BufferedImage generateBarcodeImage(String content, BarcodeType barcodeType) throws WriterException { + return generateBarcodeImage(content, BarcodeConstant.CODE_WIDTH, BarcodeConstant.CODE_HEIGHT, barcodeType); + } + + /** + * 生成条形码并返回 BufferedImage + * + * @param content 条形码内容 + * @param width 宽度 + * @param height 高度 + * @param barcodeType 条形码的种类 + * @return 生成的 BufferedImage + * @throws WriterException 生成失败异常 + */ + public static BufferedImage generateBarcodeImage(String content, int width, int height, BarcodeType barcodeType) throws WriterException { + //校验条形码内容是否符合长度要求 + if (!barcodeType.isLengthValid(content)) { + log.error("条形码内容长度超出限制: 最大长度为 {},实际长度为 {}", barcodeType.getMaxLength(), content.length()); + throw new IllegalArgumentException("条形码内容长度超出限制"); + } + + log.debug("开始生成条形码,内容:{}", content); + log.debug("条形码尺寸:{}x{}", width, height); + + // 生成 BitMatrix(二维码的黑白数据矩阵) + MultiFormatWriter writer = new MultiFormatWriter(); + BitMatrix bitMatrix = writer.encode(content, barcodeType.getZxingFormat(), width, height); + + // 获取矩阵大小 + int matrixWidth = bitMatrix.getWidth(); + int matrixHeight = bitMatrix.getHeight(); + + // 创建 BufferedImage,使用 RGB 颜色模式 + BufferedImage image = new BufferedImage(matrixWidth, matrixHeight, BufferedImage.TYPE_INT_RGB); + + // 根据图片大小选择合适的填充方式 + if (matrixWidth * matrixHeight < 1000 * 1000) { + // 适用于小图的填充方式(批量 setRGB) + fillImageWithSetRGB(image, bitMatrix); + } else { + // 适用于大图的填充方式(直接操作 WritableRaster) + fillImageWithWritableRaster(image, bitMatrix); + } + + log.debug("条形码图像生成完毕"); + return image; + } + + /** + * 方案 1:小尺寸(小于 1000x1000 像素)条形码填充,使用 setRGB 批量写入 + * + * @param image 目标 BufferedImage + * @param bitMatrix 二维码矩阵 + */ + private static void fillImageWithSetRGB(BufferedImage image, BitMatrix bitMatrix) { + int width = bitMatrix.getWidth(); + int height = bitMatrix.getHeight(); + + // 创建图形上下文 + Graphics2D graphics = image.createGraphics(); + try { + // 先填充白色背景 + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, width, height); + } finally { + graphics.dispose(); + } + + // 创建像素数组 + int[] pixels = new int[width * height]; + int white = Color.WHITE.getRGB(); + int black = Color.BLACK.getRGB(); + + // **先填充白色背景** + Arrays.fill(pixels, white); + + // **再填充黑色条形码** + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (bitMatrix.get(x, y)) { + pixels[y * width + x] = black; + } + } + } + + // **将数据写入 BufferedImage** + image.setRGB(0, 0, width, height, pixels, 0, width); + } + + /** + * 方案 2:大尺寸(大于等于 1000x1000 像素)条形码填充,使用 WritableRaster 直接写入 + * + * @param image 目标 BufferedImage + * @param bitMatrix 二维码矩阵 + */ + private static void fillImageWithWritableRaster(BufferedImage image, BitMatrix bitMatrix) { + int width = bitMatrix.getWidth(); + int height = bitMatrix.getHeight(); + + // 创建图形上下文 + Graphics2D graphics = image.createGraphics(); + try { + // 先填充白色背景 + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, width, height); + } finally { + graphics.dispose(); + } + // 获取 WritableRaster(直接操作像素数据) + WritableRaster raster = image.getRaster(); + // **遍历 bitMatrix,将黑色部分填充** + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (bitMatrix.get(x, y)) { + // 设置 RGB 三个通道都为 0(黑色) + raster.setSample(x, y, 0, 0);// 红色通道 + raster.setSample(x, y, 1, 0);// 绿色通道 + raster.setSample(x, y, 2, 0);// 蓝色通道 + } + } + } + } + +} diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index 24acb086d..478b9e993 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -179,6 +179,13 @@ ${revision} + + + org.dromara + ruoyi-common-barcode + ${revision} + +