news 2026/9/25 1:51:35

ZXing批量生成DM二维码:工业追溯场景的实现与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ZXing批量生成DM二维码:工业追溯场景的实现与避坑指南

简介:二维码技术从应用场景上可划分为面向人机交互的QR码与面向工业追溯的Data Matrix(DM码)。DM码取消了QR的回字形定位图形,依靠连续实边与断续虚边确定边界,因此在相同面积下信息密度更高,更耐受金属、塑料、曲面等复杂载体,成为电子元器件、PCB板、医疗器械等小面积单件标识的主流方案。工程中要实现DM码的批量生成,ZXing是Java生态中成熟的选择,通过core模块完成ECC200编码,javase模块输出图片。批量生成并非简单循环,需考虑静区设置、整数倍缩放、GS1格式、字符集统一等细节,否则极易出现扫码失败、内容乱码、打印发虚等问题。从技术选型到参数优化,本文结合产线实践汇总了DM码批量生成的关键步骤和踩坑记录,为追溯贴标、医疗UDI等场景提供可直接落地的参考。

1. DM二维码自动生成软件在什么场景才用得上:先分清DM码和普通二维码

“DM二维码自动生成软件”这个叫法,第一次是从产线主管嘴里听到的。当时要做的不是扫码加好友,而是给一包尺寸不到 3 厘米的元器件贴追溯码。普通 QR 码的定位图形太占地方,印上去有效内容所剩无几。换成 Data Matrix(DM)之后才明白,这类码天生就是给工业追溯用的。这篇笔记按落地顺序来写:先讲 DM 码与 QR 码的差别,再给一套能用 ZXing 跑通的批量生成代码,然后是我调过的参数和几个值得写进 SOP 的坑。适合正在做追溯贴标、医疗器械唯一标识、PCB 板单件追溯的人参考。

2. 为什么选DM而不是QR:编码原理决定适用场景,也决定选型

2.1 从贴标场景看DM码的定位

Data Matrix 是一种矩阵式二维条码,最明显的特征是边框。它的左边和下边是连续实线,上边和右边是断续虚线,扫描器靠这两组边判断条码的边界和变形程度。因为没有 QR 那种三个回字形定位图形,同样面积下 DM 能容纳更多模块,信息密度更高。

这个特性决定了它的主要使用场景:电子元器件、PCB 板、试剂瓶、医疗器械、食品药品包装这类需要在小面积上做单件标识的地方。QR 码更擅长的是“手机相册扫码、跳转链接”,处理的是人跟内容的交互;DM 码处理的是机器跟产品的交互,扫码枪要快、要稳、要能适应金属、塑料、曲面上的激光打标或点阵打印。

回到自动生成这件事上,DM 和 QR 在生成逻辑上没有本质区别,都是“内容进,图像出”。但选型时不能只图能出图,还要看它能不能批量、能不能控制静区、能不能输出适合打印的格式。这里面的差别,比大多数人想象的大。

2.2 在线生成器与本地代码生成的取舍

先说结论:如果只是偶尔生成一个两个 DM 码,在线生成器够用。但你要是准备把它接进生产环境,我一般不建议走在线方案。

在线生成器的问题集中在三处。第一,很多生成器根本不支持 DM 格式,只给 QR 选项。第二,批量场景下你需要按序列号循环生成几百上千张,在线工具要么限制数量,要么需要手工逐个下载,没人愿意干这种活。第三,产品序列号、批号、生产日期属于内部数据,把内容提交到第三方网站,心理上过不去,实际上也不合规。

本地生成是更可控的路线。方向上常见的有三种:Java 生态用 ZXing 或 Barcode4J;.NET 产线机台用 ZXing.Net;Python 也能做,但专门支持 DM 的库不如 Java 生态成熟。如果你的需求里已经包含“导入 Excel 批量生成”“JasperReports 报表套打”“与后台管理系统集成”,Java 基本上是绕不开的一站。

2.3 技术栈选择:ZXing、Barcode4J与Jaspersoft Studio的适用边界

场景推荐方案理由
Java 后端批处理ZXing core + javaseAPI 简洁,直接输出 BitMatrix 再转 PNG,批量循环好写
JasperReports 模板套打Jasper 自带条码组件 / Barcode4JJaspersoft Studio 里拖组件就能生成 DM,适合报表打印
Excel 批量导出EasyPOI / POI + ZXing生成图片单元格的同时,把序列号字段设成文本格式
.NET 产线机台ZXing.Net与上位机集成方便,依赖少,和 Java 版 API 几乎一致

具体到日常项目,我自己的判断标准是:如果只是写个批处理脚本,ZXing 足够,不要再引报表框架;如果公司已经在用 Jaspersoft Studio 做标签模板,就别自己拼图了,直接在模板里加 BarCode 组件,数据字段拖进去,导出 PDF 时自动渲染。

这里提醒一句,Jaspersoft Studio 里创建二维码的 BarCode 组件默认类型不一定是 DataMatrix,需要在下拉框里手动选。选完之后,还要确认数据表达式返回的是字符串而不是数值,否则长序列号可能被截断。

3. 用ZXing把批量DM生成跑通:从最小工程到上千张出图

3.1 建立Maven工程与依赖

在 Java 项目里引入 ZXing 只需要两个坐标,一个是 core,负责把字符串编码成 BitMatrix;一个是 javase,负责把 BitMatrix 输出成 PNG、JPEG 这类图片格式。我一般会单独建一个 maven 模块,不往业务代码里混。

<dependencies> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.5.2</version> </dependency> </dependencies>

core 里真正干活的是 com.google.zxing.datamatrix.DataMatrixWriter,内部实现了 ECC200 编码。javase 包里提供 MatrixToImageWriter,负责按 BitMatrix 生成 BufferedImage 或直接写文件。分开引的好处是,如果你的项目只需要编码不需要输出图片,可以只依赖 core,减少无用的类进 classpath。

3.2 最小代码:一个main方法生成单张DM

先跑通单张,再谈批量。下面这段是最小可运行代码,不需要 Spring,不需要配置文件。

package dm.generator; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.BitMatrix; import com.google.zxing.client.j2se.MatrixToImageWriter; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class SingleDm { public static void main(String[] args) throws Exception { String content = "SN:20240701001;PC:SHANGHAI"; Map<EncodeHintType, Object> hints = new HashMap<>(); hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); hints.put(EncodeHintType.MARGIN, 1); BitMatrix matrix = new MultiFormatWriter().encode( content, BarcodeFormat.DATA_MATRIX, 0, 0, hints ); MatrixToImageWriter.writeToPath( matrix, "PNG", Paths.get("output/single.png") ); } }

几个参数说清楚。encode 里宽和高必须传 0,这不是偷懒,是 ZXing 对 DataMatrix 的处理逻辑决定的:DM 没有“指定像素宽高”的概念,符号尺寸由内容和纠错算法自动算出,你传一个具体宽度反而会让它走到 QR 的分支逻辑。MARGIN 表示静区宽度,单位是模块数,默认值是 4,这里设成 1 是测试用的,实际打印我建议至少 2,后文避坑部分会细说。CHARACTER_SET 设成 UTF-8,防止内容里有中文时编码出错。

运行之后,output 目录下会多出一个 single.png。如果打开发现码周围白边太窄或太宽,调整 MARGIN 就行。

3.3 批量循环:从List到文件命名与输出目录

单张跑通之后,批量只是加一个循环。常见做法是读一个 CSV 或者数据库列表,逐条 encode,逐条写文件。下面这段是去掉数据源读取之后的骨架。

List<String> snList = loadSerialNumbers("serial.csv"); int fileCount = 0; for (String sn : snList) { String fileName = sanitize(sn) + ".png"; MatrixToImageWriter.writeToPath( new MultiFormatWriter().encode( sn, BarcodeFormat.DATA_MATRIX, 0, 0, hints ), "PNG", Paths.get("output", fileName) ); fileCount++; if (fileCount % 100 == 0) { System.out.println("已生成 " + fileCount + " 张"); } }

sanitize 方法很重要,目的是把序列号里的斜杠、冒号、括号等文件系统不友好的字符替换成下划线,避免生成路径时出错或者不小心覆盖到别的目录。打印进度看起来土,但跑几千张时能直观判断是不是卡住了。ZXing 的 DM 编码是纯内存计算,单张耗时基本是毫秒级,瓶颈在写文件,SSD 上 5000 张 PNG 全程大概几秒到十几秒,不需要引入线程池。

3.4 从像素输出到打印分辨率

直接生成的 PNG,默认是 1 像素对应 1 个模块。一个 16×16 的 DM 码,加静区后图片可能只有 24×24 像素。屏幕上放大看没问题,直接扔给打印机,出来的码边缘全是锯齿,扫码枪大概率不认。

正确做法是整数倍缩放。每个模块放大成 6×6 或 8×8 像素,再用 Graphics2D 画到白底画布上。

BufferedImage src = MatrixToImageWriter.toBufferedImage(matrix); int scale = 6; BufferedImage out = new BufferedImage( src.getWidth() * scale, src.getHeight() * scale, BufferedImage.TYPE_INT_RGB ); Graphics2D g = out.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, out.getWidth(), out.getHeight()); g.drawImage(src, 0, 0, out.getWidth(), out.getHeight(), null); g.dispose(); ImageIO.write(out, "PNG", new File("output/scaled.png"));

画的时候要保证整数倍,不要用 drawImage 随意拉伸到非整数尺寸。非整数倍会产生灰色过渡像素,DM 的边框定位依赖清晰的明暗跳变,灰度边缘会让扫描器在找边框阶段就放弃。scale 取多少,取决于打印机的 DPI。300 DPI 下每个模块至少要有 3 个像素,稳妥一点取 6,这样颜色更实。

4. DM参数不是玄学:尺寸、对比度、容错与内容格式都要显式设置

4.1 物理尺寸由模块数决定,不是由像素决定

很多人第一次生成 DM,会到处找“设置尺寸”的入口。ZXing 里没有这个参数,因为模块数由内容长度决定,内容越长,符号规格越大。下面这张表是常见规格和容量对照,纯数字内容为例,大致够用:

内容长度(纯数字)模块数0.5mm 模块宽时物理尺寸
6 位以内10×10约 5mm×5mm
10 位以内12×12约 6mm×6mm
16 位以内14×14约 7mm×7mm
24 位以内16×16约 8mm×8mm
44 位以内20×20约 10mm×10mm
124 位以内32×32约 16mm×16mm

实际物理尺寸 = 模块数 × 单个模块宽度。0.5mm 的模块宽度在标签打印机上是常用值,激光打标机可以做到更细,但太细会导致扫描距离变短。反过来,如果贴标区域只有 8mm×8mm,那就必须控制内容长度,让 ZXing 自动选择的模块数不超过 16×16。不要试图靠缩放图片来“压小”码,模块宽度低于 0.25mm 之后,普通扫码枪基本就不稳定了。

4.2 打印对比度:白底黑码不是唯一答案

DM 的识别靠边缘明暗跳变,所以对比度是第一位的。常规喷墨、激光打印机用白底黑码没问题。但在金属、塑料、PCB 上做激光打标,往往是黑底白码或者深色底浅色码,这时需要在生成图片时做颜色反转。

判断对比度够不够,不需要上昂贵的仪器。用 ImageIO 读像素,统计深色像素占比,能快速筛掉明显不合格的图。下面这段是我常在验证脚本里放的:

int dark = 0; for (int y = 0; y < src.getHeight(); y++) { for (int x = 0; x < src.getWidth(); x++) { if ((src.getRGB(x, y) & 0xFF) < 128) { dark++; } } } double ratio = dark / (double) (src.getWidth() * src.getHeight());

深色像素占比在 35% 到 65% 之间一般算正常。低于 20%,要么内容太少导致空区太多,要么打印颜色太浅;高于 80%,可能是黑色噪声太多或者内容过密,这两头都需要警惕。

4.3 容错与GS1格式:括号不是想写就能写

DM 默认使用 ECC200 纠错,这是标准强制要求的,不需要像 QR 码那样手动选择 L/M/Q/H。生成工具会自动处理,出错后的恢复能力也比较稳定。

但有一个细节非常容易翻车:如果你做的是 GS1 码,数据格式长这样:(01)06901234567890(17)260101。千万不要直接把括号当普通字符编码进去,标准做法是把括号替换成 FNC1 功能字符,在 Java 字符串里写作 \u0001:

String gs1Content = "\u0001" + "01906901234567890" + "17260101";

这样生成的 DM,扫码枪读出来才会被解析成 GS1 格式,ERP 才能正确拆出 GTIN 和有效期。如果你把括号原样写进去,扫码枪会把括号当成普通字符,后续系统解析时直接懵掉。

4.4 字符集:统一UTF-8,别让扫描枪读出一堆问号

当内容里只有数字、英文字母时,字符集问题不会暴露。一旦出现中文厂名、中文物料描述,就必须在 EncodeHintType.CHARACTER_SET 里显式指定 UTF-8。ZXing 的 DataMatrixWriter 并不是所有版本都默认 UTF-8,我在旧项目里就遇到过默认 ISO-8859-1 的情况,中文内容编码后成了乱码。

扫描枪那边也要同步确认。很多工业扫码枪出厂默认是 GBK 或系统本地编码,读 UTF-8 内容会显示 �。产线经验是:DM 内容里尽量不要放中文,用物料编码代替物料名,中文说明放在数据库里,扫码后回查。如果必须放,约好两边的字符集,最好用固定长度的物料编码,减少因字符集不一致导致的对不齐问题。

5. DM生成避坑指南:五个值得写进SOP的踩坑记录

5.1 静区被裁切,贴标机死活扫不出

现象:生成的码在电脑屏幕上用微信扫一扫能识别,打印成标签后上贴标机,扫码枪一直报警。

原因:ZXing 的 MARGIN 设成了 1 模块,打印机的页面边距又太小,码的边缘被裁掉了一部分。DM 码没有 QR 那种回字形定位图形,静区被破坏后,扫描器找不到正确的明暗边界,直接判定无效。

解决:把 MARGIN 改成 2 或 3,同时在输出图片时额外加一圈白边。加白边的代码很简单,在放大后的 BufferedImage 上再扩展一圈 2 个模块宽度的白底即可。标签排版时也留出固定边距。

5.2 中文内容变成乱码,扫码枪读出一串问号

现象:同样的内容,在 Windows 上生成的码能读,换到 Linux 服务器生成后就乱码。

原因:两台机器的默认字符集不一样,Windows 用 GBK,Linux 用 UTF-8。代码里没有显式指定 CHARACTER_SET,ZXing 在不同环境下走了不同的默认编码。

解决:每次 encode 之前,把 hints 里的 CHARACTER_SET 强制设成 UTF-8,并且用常量写死,不要依赖环境变量。如果历史生成过一批 GBK 码,别想着修复,直接按统一标准重新生成。

5.3 96DPI的图直接打印,印出来边缘发虚

现象:图片在电脑上看非常清晰,打印到标签上之后,边缘像被水泡过一样,扫码识别率断崖式下降。

原因:生成的源 PNG 只有 24×24 像素左右,打印软件为了填满标签区域,自动做了非整数倍拉伸,模块边缘产生灰阶过渡,DM 的边框定位失效。

解决:生成时直接按目标 DPI 做整数倍缩放,300DPI 下每个模块至少 6 像素。标签打印软件里设成原始尺寸,不要让软件二次缩放。如果打印后还是发虚,用放大镜看单个模块的边界,应该是一条锐利的直线,而不是渐变带。

5.4 Excel导入序列号变成科学计数法,内容被截断

现象:CSV 里写着 123456789012,用 Excel 打开变成 1.23457E+11,生成到 DM 里的内容是截断后的数值。

原因:Excel 默认把长数字识别成数值类型,精度不够时自动转科学计数法,后几位被抹成 0。这条坑跟 DM 生成本身无关,但批量生成时几乎一定会遇到。

解决:生成 CSV 时,在超长数字字段前加 tab 前缀,或者给字段加双引号,强制 Excel 按文本读取。如果你用 EasyPOI 或 POI 直接导出 xlsx,记得把单元格类型设成字符串,不要用 setCellValue 传数值。

5.5 内容超长,码的物理面积翻倍超了贴标区域

现象:一个码想塞进货批次、物料编码、生产日期、有效期、序列号、产地,生成出来发现 DM 码大得超出标签范围。

原因:DM 容量和模块数是强相关的,内容越多,自动选型的模块数越大。100 字符到 200 字符,符号可能从 48×48 跳到 64×64,物理面积直接扩大一倍。

解决:生成前做一个内容长度校验,纯数字建议控制在 100 字符以内,超过就报错。产线场景的正确做法是码里只放“标识 ID”,其余信息存数据库,扫码后通过 ID 回查。不要试图把 DM 码当成数据库用。

6. 进阶:把生成脚本接进贴标流程,批量出图后记得先验证再放行

日常接入贴标系统时,我习惯把生成任务写成一个独立的小作业:读取 CSV,逐条生成 PNG,同时输出一份结果日志。这样既方便排错,也能给产线留一个可回溯的记录。下面是一个简化版本:

Path csv = Paths.get("input.csv"); List<String> lines = Files.readAllLines(csv, StandardCharsets.UTF_8); int ok = 0; List<String> failed = new ArrayList<>(); for (String line : lines) { if (line.isBlank()) continue; String sn = line.trim(); if (sn.length() > 100) { failed.add(sn + " -> 内容超长"); continue; } try { BitMatrix matrix = new MultiFormatWriter().encode( sn, BarcodeFormat.DATA_MATRIX, 0, 0, hints); MatrixToImageWriter.writeToPath(matrix, "PNG", Paths.get("output", sn + ".png")); ok++; } catch (Exception e) { failed.add(sn + " -> " + e.getMessage()); } } Files.write(Paths.get("result_log.txt"), List.of("成功: " + ok, "失败: " + failed.size()), StandardCharsets.UTF_8);

每一条失败记录都写进日志,而不是中间抛异常打断整个批次。产线上跑批处理,最怕的就是生成到一半停下来,你根本不知道哪一批还没生成完。还有一个验证步骤不能省:批量跑完后,抽三张不同批次的码,用扫码枪实际扫一遍,确认能读到完整内容,再放行去贴标。这个动作虽然慢,但一次漏检造成的返工成本,远远大于这一分钟的验证时间。如果系统里接的是 Jaspersoft Studio 报表或 EasyPOI 导出 Excel,也保持同样的习惯,生成后先抽查再交付。

我在这个方向上最后悔的一次,是省了打印前的抽样验证,整批贴完才发现模块宽度设小了一档,最后全部撕掉重贴。从那以后,无论多熟的批量任务,我都保留抽检这一步。它不是什么玄学,就是给自己留一颗后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 1:51:24

ROCm多流调度实战:hipMemcpyAsync异步陷阱与拷贝计算重叠

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:50:31

网心云OES Plus刷Armbian后系统迁移至SATA硬盘扩容实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:49:37

RabbitMQ测试工具实战:从Docker部署到命令行判活与消息收发自测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:48:03

Windows 11下Java调试环境搭建与Debug常见问题全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:46:48

MS1030超声波水表设计实战:从15ps时差测量到系统标定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华