
GS1 标签做错的方式通常很隐蔽。校验位差一位,扫描器还是能读出字符串;FNC1 少了一个,批次号会把序列号一起吞掉;在 DataBar 上多挂一个 AI,编码器直接拒绝或者生成一个解出来和预期不同的符号。这些问题的共同点是“结果看起来正常”。在线 GS1 条码生成器的思路是:把校验规则前置到生成阶段,并且把“符合规范的扫描器应该返回什么”一并算出来,让一张标签图成为一个可核对的测试用例。
在线演示
在线 GS1 条码生成器(英文名 Online GS1 Barcode Generator):打开就能用,不需要注册,也不需要安装;元素串拼装、校验位计算和符号渲染都在浏览器内完成。
配合本文验证时可以这样走一遍:挑一个业务场景生成标签,先把右侧「扫描器应该返回什么」的对照表读一遍,再把导出的标签 PNG 拿到 GS1 扫描器里读回来,逐项比对元素串、AI 表和校验位判定。
关键要点
- 唯一的第三方依赖是
bwip-js@4.11.4,没有识别 SDK、没有许可证、没有上传;生成器本身就是校验器。 - 覆盖 12 种符号:DataBar 全向 / 截断 / 堆叠 / 堆叠全向 / 受限、DataBar Expanded、DataBar Expanded Stacked、GS1 DataMatrix、GS1 QR Code、GS1-128、ITF-14、EAN-13。
- AI 表含 39 个条目,按用途分组(零售、追溯、日期、计量、物流、参与方、资产、内部),并以数据而非代码的方式生成
310–394计量家族与391x/393x货币家族。 - 校验位自动计算:从校验位左边一位起按 3、1 交替加权(最右侧数据位权重 3),取
(10 − sum mod 10) mod 10。 - 每条诊断信息都带一键修正:
+ 修正校验位、+ 改用 GS1 DataMatrix、+ 添加 AI 01(GTIN),从不静默改动输入。 - 11 个真实场景在两轮随机化载荷下均通过 11/11 往返验证(生成 → 打印/导出 → 扫描 → 解析比对)。
从场景开始,而不是从 AI 开始
页面的第一个控件是场景下拉框,第二个是符号类型,两者联动。11 个场景与默认符号、默认 AI 组合如下:
| 场景 | 默认符号 | 默认 AI |
|---|---|---|
| 零售单品——只带 GTIN | GS1 DataBar 全向 | 01 |
| 生鲜计重——GTIN + 重量 + 价格 | DataBar Expanded Stacked | 01, 3103, 3922 |
| 医药单元——GTIN + 有效期 + 批次 + 序列号 | GS1 DataMatrix | 01, 17, 10, 21 |
| 物流集货箱——SSCC | GS1-128 | 00 |
| ITF-14 纸箱——只带 GTIN | ITF-14 | 01 |
| 订单配送——GTIN、批次、订单、收货方 | GS1-128 | 01, 10, 400, 410, 422 |
| 组合装——GTIN + 件数 + 保质期 | DataBar Expanded | 01, 30, 15 |
| 周转资产——GRAI | GS1 DataMatrix | 8003 |
| 标价包装——GTIN + 重量 + 欧元标价 | DataBar Expanded | 01, 3103, 3932 |
| 面向消费者码——GTIN + 批次 + 序列号 | GS1 QR Code | 01, 10, 21 |
| 自定义——自己拼 | GS1 DataMatrix | 01, 17, 10 |
注意医药单元与面向消费者码的 AI 组合完全相同,只是符号不同:前者用 DataMatrix(尺寸小、适合单支药盒),后者用 QR Code(普通手机相机可读)。这正说明“选哪种符号”往往取决于使用场景而不只是数据。
符号与数据的兼容规则
12 种符号里有 7 种是只能装 GTIN 的:DataBar 全向 / 截断 / 堆叠 / 堆叠全向 / 受限,以及 ITF-14 和 EAN-13。在上面挂第二个 AI 会直接报错:
DataBar、ITF-14 或 EAN-13 符号只能承载 GTIN,没有别的内容。多出的 1 个元素无法编码。
报错的同时会给出修复按钮“改用 GS1 DataBar Expanded Stacked”之类,而不只是告诉用户错了。DataBar Limited 还有一条额外约束:GTIN 必须以 0 或 1 开头(受限变体容量最小),不满足时提示改回全向变体。
DataBar Expanded 与 Expanded Stacked 不在“只能装 GTIN”之列,因为它们本身就能承载多个 AI。
编码器输入的两处特殊处理
对 10 种带 AI 语法的符号,交给 bwip-js 的 text 是带括号的人眼可读串 (01)…(10)…,由 BWIPP 自己决定 FNC1 的位置:
var options = {
bcid: encoder.bcid, // 例如 'gs1datamatrix'、'gs1_128'、'databarexpandedstacked'
text: encoder.text, // 带括号的 HRI 串
scale: scale,
includetext: false,
paddingwidth: 4,
paddingheight: 4
};
if (render.height) options.height = render.height;
EAN-13 与 ITF-14 是例外(aiSyntax: false)。EAN-13 接收 12 位数字让 BWIPP 算第 13 位校验;ITF-14 必须收到完整的 13 或 14 位 GTIN,绝不裁掉:
// 裁成 13 位会让 BWIPP 对一段已经含校验位的数据再算一次校验位,
// 符号解出来的数字就和载荷声明的不一致了。
这是“测试素材生成器绝对不能出的那类错”——图上写着 A,扫出来是 B。
AI 长度与配对约束
每个 AI 在表里都声明了类型与长度,生成器的校验就建立在这上面:
| 类型 | 规则 |
|---|---|
gtin |
14 位数字 + 校验位 |
sscc |
18 位数字 + 校验位 |
gln |
13 位数字 + 校验位 |
date |
6 位数字,月份 01–12,日 00–31(00 表示当月最后一天) |
decimal |
定长数字,小数位数由 AI 第 4 位决定 |
country |
3 位数字 |
text / number |
不超过各自上限,且不含控制字符 |
除了逐项长度,还有两条配对约束,这两条是最容易被忽略、也最容易让扫描器报错的:
var PAIRING_RULES = [
{
match: function (ai) { return ai === '21'; },
satisfiedBy: function (ais) {
return ais.indexOf('01') !== -1 || ais.indexOf('03') !== -1 || ais.indexOf('8006') !== -1;
},
why: 'AI 21(序列号)标识的是某一个具体单品,因此必须带它所归属的贸易项目:AI 01、03 或 8006。',
add: '01'
}
];
规则里的 why 会直接显示给用户,add 则变成“添加 AI 01(GTIN)”按钮。第二条规则针对 391x/393x 这类带计价含义的 AI:它们必须和一个计件或计量 AI(30 或 31nn/32nn/35nn/36nn)同时出现,否则价格没有分母。
FNC1 只加在该加的位置
元素串的显示形式用 | 标出 FNC1 应该出现的位置,而这个位置不是每个元素后面都有:
function toDecodedText(rows) {
var out = '';
rows.forEach(function (item, index) {
var entry = AI_BY_CODE[item.ai];
out += item.ai + item.value;
var isLast = index === rows.length - 1;
if (!isLast && entry && !entry.fixed) out += FNC1; // FNC1 = String.fromCharCode(29)
});
return out;
}
规则是:只有变长元素、且它后面还有元素时才需要分隔符。定长元素的长度是已知的,不需要分隔符;最后一个元素也不需要——读到结尾自然结束。页面上的说明写得很直接:“元素串里的 | 标出一个必须出现 FNC1 字节(0x1D)的位置,只有变长元素需要终止时才出现。”这是扫描器返回乱码最常见的单一原因,值得在生成阶段就讲明白。
“扫描器应该返回什么”对照表
每生成一个符号,右侧同步给出四组结果:
- 元素串(原始解码输出)——FNC1 位置以
|标出。 - 人眼可读串(HRI)——
(01)09506000123457(17)250430(10)LOT-4221这种形式。 - 应用标识符表——四列:
AI、数据元、扫描器读到的值、校验位。变长字段会标注“(变长)”,校验位列显示“通过”“失败”或“—”(只有 GTIN、SSCC、GLN 参与校验,其它显示“—”)。 - GS1 Digital Link——以
01或8006作主键,10、21、22、235、400–403进路径,其余转查询参数;没有主键时明确写“没有 GTIN 或 ITIP,无法构造 Digital Link”。
日期在表里会换算成 ISO 形式(年窗口 yy <= 49 为 20xx,50–99 为 19xx,日 00 取当月最后一天),计量 AI 会补上单位或货币名称(内置 13 种货币代码)。
渲染:宁可重编码,也不缩小
符号宽度超出标签画框时,生成器用更小的整数倍重新编码,而不是把渲染结果等比缩小:
// 重新编码、而不是把成品图缩小——这才是重点。
// 按比例缩小线性符号会模糊模块边缘……
配套的还有画框留白:paddingwidth/paddingheight 设为 4 个模块。静区是规范的一部分而不是美观问题——符号贴着自己的边框,解码难度会明显上升。
如果为了塞进画框已经把模块尺寸压到 1 px,状态栏会给出明确警告:“模块尺寸已降到 1 px,请缩短载荷,给出一个相机能舒服读到的测试图。”另一个警告是符号被降采样的情况。所有这些状态都只影响预览与下载,不会让页面上的元素串和图片不一致——载荷有错时预览直接作废并标灰,而不是显示一张和载荷不匹配的图。
推荐的符号类型
页面内置了一套推荐逻辑,用 AI 组合反推符号:
| 条件 | 推荐符号 |
|---|---|
| 含 AI 00(SSCC) | GS1-128 |
| 只有 GTIN,且是 13 位 | EAN-13 |
| 只有 GTIN,其它情况 | DataBar 全向 |
AI 少于 4 个,且不含 3103/3922/30 |
DataBar Expanded |
含 3103/3922/30 |
DataBar Expanded Stacked |
| AI 不少于 4 个 | GS1 DataMatrix |
| 其它 | GS1-128 |
当前选择与推荐不一致时,提示区会同时给出推荐项和理由,但不自动切换——符号选择是有业务含义的决定,工具只负责把取舍讲清楚。
用生成器验证扫描器
这两个工具是成对使用的:生成器按场景产出标签 PNG 与对照表,GS1 条码扫描器把图片读回来解析。11 个场景在两轮随机化载荷下都完成了往返验证,验证的是“生成器声明的结果”和“扫描器实际返回的结果”逐项一致——包括校验位判定和日期换算。
需要说清楚的是:生成的符号不构成真实性证明。GS1 载荷是明文,能被正确解析只说明数据格式合法。这些工具用于构造测试素材,不用于伪造或冒充真实商品标签。
常见问题
为什么生成器要同时给出“扫描器应该返回什么”?
因为一张标签图本身不是一个测试用例。把图片和“合规扫描器应返回的元素串、人眼可读串、AI 表、校验位判定”放在一起,才构成一个可核对、可回归的用例;否则你只能凭肉眼判断这次识别是不是对的。
DataBar 和 GS1-128 该选哪个?
只装 GTIN 的零售单元用 DataBar(全向、截断、堆叠、受限等变体);一个以上 AI 时用 GS1-128、GS1 DataMatrix 或 GS1 QR Code。生成器内置了推荐逻辑,选错符号时会给出“改用 XX”的一键修正,而不是只报一句错。
为什么代码里没有 GS1 Composite?
因为编码器 bwip-js 不提供 Composite 编码,而且 Composite 本质上是两个需要被读取器关联起来的符号,单张标签图无法完整表达它。生成器在说明里明确写出了这个缺口,而不是悄悄省略。
校验位是生成器算还是我自己填?
生成器自动计算并追加。GTIN-14、SSCC-18、GLN-13、GSRN-18 都按同一套 Mod-10 算法从右往左按 3、1 交替加权,你只需要填前面 12/17/12/17 位数字。
生成的条码能被哪些扫描器读取?
任何符合 GS1 规范的读取器都可以。数据本身是明文,所以“能被正确解析”只证明数据格式合法,不证明商品或标签真实——这个工具用于构造测试素材。
相关阅读
- 如何编写一个在线二维码生成器——二维码编码的基础流程。
- 如何生成带矢量条码的 PDF 并读码——需要直接打印标签时,矢量输出比位图更可靠。
- 如何读取条码中的二进制数据——理解识别器返回的原始字节,有助于排查分隔符问题。
- 条码扫描,使用 ZXing、ML Kit 和 Dynamsoft——不同引擎对 GS1 数据的处理差异。