简介二维码技术从应用场景上可划分为面向人机交互的QR码与面向工业追溯的Data MatrixDM码。DM码取消了QR的回字形定位图形依靠连续实边与断续虚边确定边界因此在相同面积下信息密度更高更耐受金属、塑料、曲面等复杂载体成为电子元器件、PCB板、医疗器械等小面积单件标识的主流方案。工程中要实现DM码的批量生成ZXing是Java生态中成熟的选择通过core模块完成ECC200编码javase模块输出图片。批量生成并非简单循环需考虑静区设置、整数倍缩放、GS1格式、字符集统一等细节否则极易出现扫码失败、内容乱码、打印发虚等问题。从技术选型到参数优化本文结合产线实践汇总了DM码批量生成的关键步骤和踩坑记录为追溯贴标、医疗UDI等场景提供可直接落地的参考。1. DM二维码自动生成软件在什么场景才用得上先分清DM码和普通二维码“DM二维码自动生成软件”这个叫法第一次是从产线主管嘴里听到的。当时要做的不是扫码加好友而是给一包尺寸不到 3 厘米的元器件贴追溯码。普通 QR 码的定位图形太占地方印上去有效内容所剩无几。换成 Data MatrixDM之后才明白这类码天生就是给工业追溯用的。这篇笔记按落地顺序来写先讲 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.NetPython 也能做但专门支持 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 groupIdcom.google.zxing/groupId artifactIdcore/artifactId version3.5.2/version /dependency dependency groupIdcom.google.zxing/groupId artifactIdjavase/artifactId version3.5.2/version /dependency /dependenciescore 里真正干活的是 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; MapEncodeHintType, 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逐条写文件。下面这段是去掉数据源读取之后的骨架。ListString 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×5mm10 位以内12×12约 6mm×6mm16 位以内14×14约 7mm×7mm24 位以内16×16约 8mm×8mm44 位以内20×20约 10mm×10mm124 位以内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 字符串里写作 \u0001String 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 用 GBKLinux 用 UTF-8。代码里没有显式指定 CHARACTER_SETZXing 在不同环境下走了不同的默认编码。解决每次 encode 之前把 hints 里的 CHARACTER_SET 强制设成 UTF-8并且用常量写死不要依赖环境变量。如果历史生成过一批 GBK 码别想着修复直接按统一标准重新生成。5.3 96DPI的图直接打印印出来边缘发虚现象图片在电脑上看非常清晰打印到标签上之后边缘像被水泡过一样扫码识别率断崖式下降。原因生成的源 PNG 只有 24×24 像素左右打印软件为了填满标签区域自动做了非整数倍拉伸模块边缘产生灰阶过渡DM 的边框定位失效。解决生成时直接按目标 DPI 做整数倍缩放300DPI 下每个模块至少 6 像素。标签打印软件里设成原始尺寸不要让软件二次缩放。如果打印后还是发虚用放大镜看单个模块的边界应该是一条锐利的直线而不是渐变带。5.4 Excel导入序列号变成科学计数法内容被截断现象CSV 里写着 123456789012用 Excel 打开变成 1.23457E11生成到 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); ListString lines Files.readAllLines(csv, StandardCharsets.UTF_8); int ok 0; ListString 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也保持同样的习惯生成后先抽查再交付。我在这个方向上最后悔的一次是省了打印前的抽样验证整批贴完才发现模块宽度设小了一档最后全部撕掉重贴。从那以后无论多熟的批量任务我都保留抽检这一步。它不是什么玄学就是给自己留一颗后悔药。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
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:51:29
5V输入多串锂电池升压充电管理IP2342设计与调试实战 /* 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:51:23
透传解耦实践:HeliosEquipment 0.3如何把机台从业务代码中剥离 /* 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:51:23
华为USG防火墙双向NAT配置:地址重叠与NAT回流实战 前阵子有个朋友在群里发来一张截图,说他们分公司和总部的网段都是192.168.1.0/24,两边用加密隧道把内网连起来之后,两边的主机互相ping不通,总部访问分公司的一台服务器始终没反应。我一看配置就明白了——这是个非常典型的“地址… · 2026/9/26 9:24:44
TG个人发卡机器人实战:二开自动发货与双语言支持 简介:一套基于某发卡系统二次开发的Telegram个人发卡机器人源码,支持中英双语,面向需对接Telegram机器人自动发卡的个人开发者或中小团队,适用于电商、游戏等虚拟卡管理与交易处理场景。运行要求Linux或Windows服务器,… · 2026/9/26 9:24:44
勒索病毒应急处置全流程:从断网隔离到数据恢复的标准化操作指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 9:24:38
DBeaver实战指南:后端开发者高效连接与管理MySQL /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 9:24:38
仿生双足机器人变刚度驱动器设计全记录:从弹簧参数到台架实验 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 9:24:38
Agentic Lake:全模态数据湖如何支撑智能体原生架构 1. 项目概述:这不是一次普通的技术升级,而是一次数据范式的迁移“云栖2026 | 阿里云 OpenLake 迈向 Agentic Lake,一份全模态数据驱动智能体就绪”——这个标题里没有一个虚词。它不是发布会PPT上飘过的概念,而是阿里云在数据基础… · 2026/9/26 9:24:38
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46