简介:面向电商订单、库存标签、物流单据等场景的PHP开发者,这是一份完整的条形码生成示例,支持在网页中直接输出可扫描的条形码图像。压缩包约209KB,共32个文件,其中29个为PHP脚本,覆盖条码编码、绘制、颜色与字体设置等核心逻辑,另含1个TTF字体文件、条形码配置文件和说明文档,目录结构一目了然。示例基于BCG类库实现,内置Code 128、Code 39、EAN/UPC、ISBN等多种常见编码格式,并提供可立即运行的演示页面;开发者可通过修改参数调整条形码类型、尺寸和颜色,也可以与数据库记录结合,实现订单或商品的动态条码生成与批量验证。同时,说明文档和测试脚本能帮助梳理条码数据的输入、渲染与输出流程,降低上手门槛。目前已有377人学习该资源,对于需要快速集成条码能力或了解条形码生成原理的开发者,是一份兼具实用性与参考价值的示例。 条形码这事儿,做业务系统的朋友应该都不陌生。无论是超市小票、图书编号还是快递面单,背后都是一串黑白条纹在默默工作。我在好几个项目里都遇到过“生成条形码”的需求,需求本身不难,但要是没摸清门路,光在环境准备和编码格式上就能卡半天。这次我从零写了一个 PHP 生成条形码的完整 demo,把开发过程中遇到的关键点、选型思路和实际踩过的坑都整理出来,希望能给正打算做类似功能的朋友一个能直接“抄作业”的参考。
先交代一下背景:这个 demo 的使用场景是图书管理系统的商品标签打印,需要根据数据库里存储的图书编号实时生成对应条形码,并在前端页面上展示、支持下载。开发环境是老牌的 LNMP 架构,PHP 版本 7.4,服务器是宝塔面板,底层依赖 GD 库处理图像。整体需求不算复杂,但麻雀虽小五脏俱全,从类库选型、环境配置、代码封装到前端联调,每个环节都有值得说道的细节。
1. 需求分析与实现方案选型
1.1 demo 要解决的核心问题
在拿到需求的第一时间,我先做了拆解。表面上看,需求就是“根据编号生成一张条形码图片”,但往深处想,至少有四个问题需要提前回答:
第一,条形码图片的格式是什么?直接输出 PNG 流给前端,还是把图片文件保存到服务器?这个决定了后面的接口设计和存储方案。
第二,条形码的码制选哪一种?Code128、Code39、EAN-13 各有各的适用场景。图书编号这种场景,通常用 Code128,因为它支持全部 ASCII 字符,编码密度高,同样的内容长度下生成的图片更短,打印机出错的概率也低。
第三,生成效率如何?如果只是偶尔生成一张,随便怎么实现都行;但如果要批量打印库存里几千条数据,就必须考虑循环生成的耗时和资源占用。
第四,图片质量能否满足打印要求?屏幕上看不清楚的条纹,打印出来往往是灾难。条形码的宽度、高度、边距(quiet zone)都需要可配置。
带着这四个问题,我进入选型阶段。
1.2 主流 PHP 条形码方案对比
PHP 生态里生成条形码的方案不算少,我梳理了三个主流方向,各有优劣。
第一种是用纯 PHP 类库直接生成图片,典型代表是 Picqer 出品的 php-barcode-generator。这类方案不依赖系统层面的扩展,通过 GD 库逐像素绘制条形码,安装简单、兼容性好,支持 Code39、Code128、EAN-13 等常见码制。优点是部署方便,composer 一条命令搞定;缺点是涉及复杂二维码(比如 QR Code)时力不从心,只能专注条形码。对这个 demo 来说,它是首选。
第二种是使用图形库扩展,比如 Zend\Barcode 组件(现在归到 Laminas 名下)。它的优点是代码组织规范,和框架结合紧密,提供了一整套渲染和验证机制;缺点是依赖较重,如果项目本身不用 Laminas 全家桶,单拉一个组件进来多少有点“杀鸡用牛刀”。
第三种是调用第三方 HTTP API 生成条形码。这种方式的好处是完全不占用本地资源,前端把编号传给接口,接口返回图片 URL 就行。但缺点也很明显:每次生成都是一次网络往返,内网环境或离线环境直接抓瞎;而且数据安全是个潜在大坑,商品编号、图书编号这类业务数据经过第三方服务,等于把自己的家底交给别人保管。
综合评估,我选了 Picqer 的 php-barcode-generator。理由很直白:依赖最少、在 GD 库基础上工作、对 PHP 版本要求友好(5.6 以上都能跑)、社区使用面广、遇到问题能快速搜到解决方案。这个选择后来被验证是正确的,整个开发过程几乎没有被类库本身的 bug 卡住过。
2. 环境准备与依赖安装
2.1 运行环境检查
在动手写代码之前,先确认服务器环境是否满足要求。这个步骤看起来多余,但真有人装完依赖才发现 GD 库缺失,白折腾半小时。
我用下面这段命令快速检查 PHP 版本和已启用的扩展:
php -v php -m | grep gd正常输出里应该能看到gd字样。如果没看到,说明 GD 扩展没有启用。在宝塔面板里操作很简单:打开“软件商店”->“PHP 7.4”->“设置”->“安装扩展”,找到 GD 扩展点击安装即可。命令行环境的朋友则需要根据系统包管理器安装,比如 Ubuntu 下的apt-get install php7.4-gd,装完记得重启 PHP-FPM。
另外还有一种情况需要注意,就是 GD 库虽然安装了,但可能缺少 FreeType 字体支持。这个特性主要用于在图片上绘制文字,如果我们后续需要在条形码下方显示可读的编号文字,就离不开 FreeType。检查命令是:
php -r "var_dump(gd_info());"输出数组里如果有FreeType Support => bool(true)就说明没问题。
2.2 安装 php-barcode-generator 类库
环境就绪之后,用 Composer 安装类库就是一句命令的事:
composer require picqer/php-barcode-generator安装完成后,vendor 目录里会出现picqer/php-barcode-generator。如果你用的是 ThinkPHP、Laravel 这类框架,Composer 自动加载机制会把命名空间注册好,直接在业务代码里use Picqer\Barcode\BarcodeGeneratorPNG;就能用。没有框架的原生 PHP 项目,需要手动引入vendor/autoload.php。
这一步几乎不会遇到问题,唯一可能出现的情况是 PHP 版本过低导致 Composer 版本不兼容,但只要是 PHP 5.6 以上基本都稳。
2.3 存储目录与权限预设
类库安装只是第一步,真正到业务层面还需要考虑图片存哪里。我在 demo 里规划了一个/public/barcodes目录,专门存放生成好的条形码图片,并保证 Web 服务器有写入权限。
为什么单独建目录而不是直接放在临时目录?因为业务场景要求条形码可以重复展示,图书编号“9787121418174”对应的图片每次打开详情页都应该一致,重复生成是浪费资源。把生成结果缓存到指定目录,下次访问时先判断文件是否存在,存在就直接返回,这样页面响应速度能快上一个数量级,同时也方便后续做打印批量导出。
权限设置上,目录所有者设为运行 PHP 的用户(宝塔环境下通常是www),权限值设为755或775均可。千万别图省事直接777,会有安全隐患。
3. 核心代码实现与开发实录
3.1 基础版:三行代码生成条形码
先把最基本的调用方式展示出来。使用BarcodeGeneratorPNG类,只需要指定内容、码制和宽高因子,就能生成一张 PNG 格式的条形码图片:
<?php require 'vendor/autoload.php'; use Picqer\Barcode\BarcodeGeneratorPNG; $generator = new BarcodeGeneratorPNG(); $barcode = $generator->getBarcode('9787121418174', $generator::TYPE_CODE_128, 2, 50); file_put_contents('public/barcodes/9787121418174.png', $barcode);这段代码做的事情很直观:调用getBarcode()方法,传入三个参数,其中2是像素放大倍数,50是条形码高度(单位也是像素)。生成的二进制字符串通过file_put_contents写入文件,就是一张可直接访问的 PNG 图片。
3.2 参数深度解析:为什么这样调优
getBarcode()方法的参数看着简单,实际调优空间很大,四个核心参数的理解直接决定了成图质量。
第一个参数是条形码内容。这里要注意,Code128 支持的数字和字母范围很广,但如果我们用 EAN-13 码制,内容必须恰好是 12 位或 13 位数字,多一位少一位都会抛异常。业务系统里商品编号往往有多种格式,所以推荐统一使用 Code128,兼容性最好。
第二个参数是码制常量。php-barcode-generator 提供了TYPE_CODE_39、TYPE_CODE_128、TYPE_EAN_13、TYPE_UPC_A等多种选择。码制的差异直接影响条形码能否被扫码枪识别,这一点非常关键。我见过有同事生成了一堆 Code39 的图片,结果仓库扫码枪根本认不出来,排查半天才发现是码制的问题。对于现代扫码设备,首选 Code128,几乎不会出兼容性问题。
第三个参数是宽度因子(Width Factor)。这个参数控制条纹的基本宽度。值越小,条形码整体越窄,但过小会导致扫描器难以识别;值越大,条形码越宽,占用的标签面积也就越大。经验值在 2 到 3 之间比较合适。我用 2 做屏幕预览,打印时改用 3。
第四个参数是高度(Height)。条形码高度至少要保证在 30 像素以上,太矮的条形码扫起来很费劲,尤其是手持扫码枪对不齐角度的时候。我习惯设置 50,如果标签纸空间紧张,也会压到 40,但低于这个值扫码成功率会有肉眼可见的下降。
3.3 进阶:生成带文字说明的条形码
很多业务场景要求在条形码下方显示对应的编号文字,方便人工核对。PNG 生成器本身支持在图片底部附加文字,设置方法如下:
<?php $generator = new BarcodeGeneratorPNG(); $generator->setColor(0, 0, 0); // 条形码颜色,默认黑色 $barcode = $generator->getBarcode($code, $generator::TYPE_CODE_128, 2, 50);但getBarcode()生成的图片默认不包含文字。如果需要文字,可以在生成后调用 GD 函数自行绘制,或者使用更高阶的 SVG 生成器配合 CSS 控制文字。考虑到 demo 要展示完整能力,我封装了一个函数,合并“生成条形码”和“绘制文字”两个步骤:
<?php require 'vendor/autoload.php'; use Picqer\Barcode\BarcodeGeneratorPNG; /** * 生成带文字的条形码图片 * * @param string $code 条形码内容 * @param int $width 宽度因子 * @param int $height 条形码高度 * @return string 二进制PNG图片数据 */ function generateBarcodeWithText(string $code, int $width = 2, int $height = 50): string { $generator = new BarcodeGeneratorPNG(); $barcodeImageData = $generator->getBarcode($code, $generator::TYPE_CODE_128, $width, $height); $image = imagecreatefromstring($barcodeImageData); $imageWidth = imagesx($image); $imageHeight = imagesy($image); // 底部预留20像素高度用于显示文字 $newHeight = $imageHeight + 20; $newImage = imagecreatetruecolor($imageWidth, $newHeight); // 填充白色背景 $white = imagecolorallocate($newImage, 255, 255, 255); $black = imagecolorallocate($newImage, 0, 0, 0); imagefill($newImage, 0, 0, $white); // 将原条形码拷贝到新画布的顶部 imagecopy($newImage, $image, 0, 0, 0, 0, $imageWidth, $imageHeight); // 在底部中央绘制文字 $fontPath = __DIR__ . '/assets/arial.ttf'; $fontSize = 12; $textBox = imagettfbbox($fontSize, 0, $fontPath, $code); $textWidth = $textBox[2] - $textBox[0]; $textX = intval(($imageWidth - $textWidth) / 2); imagettftext($newImage, $fontSize, 0, $textX, $imageHeight + 16, $black, $fontPath, $code); ob_start(); imagepng($newImage); $data = ob_get_clean(); imagedestroy($image); imagedestroy($newImage); return $data; }这里用到了几个 GD 库的基础函数,简单说一下意图:先读取原条形码图片的宽高,然后新建一个比原来高 20 像素的画布,白色背景打底,把原条形码原样拷贝到顶部,再用imagettftext()在底部居中的位置绘制编号文字。字体文件我用的是arial.ttf,放到了项目的 assets 目录下,你也可以换成自己喜欢的开源字体,但注意字体文件路径要写对,否则imagettfbbox()会直接抛异常。
3.4 完整 demo:从数据库读取编号并生成条形码
既然场景是图书管理系统,那 demo 就必须有数据库交互。我建了一张简单的图书表,包含id、book_name、isbn三个字段。核心逻辑是:接收前端传入的图书 ID,从数据库查出对应 ISBN,调用上面封装好的函数生成条形码图片,最后返回给前端。
<?php require 'vendor/autoload.php'; use Picqer\Barcode\BarcodeGeneratorPNG; $pdo = new PDO('mysql:host=127.0.0.1;dbname=library', 'root', 'your_password'); $id = intval($_GET['id'] ?? 0); if ($id <= 0) { http_response_code(400); exit('invalid id'); } $stmt = $pdo->prepare('SELECT isbn FROM books WHERE id = ?'); $stmt->execute([$id]); $book = $stmt->fetch(PDO::FETCH_ASSOC); if (!$book) { http_response_code(404); exit('book not found'); } $isbn = trim($book['isbn']); $barcodeFile = __DIR__ . '/public/barcodes/' . $isbn . '.png'; // 文件存在则直接返回,避免重复生成 if (!file_exists($barcodeFile)) { $imageData = generateBarcodeWithText($isbn); file_put_contents($barcodeFile, $imageData); } header('Content-Type: image/png'); readfile($barcodeFile);这段代码里有几个细节值得多说一句。第一,我用PDO预处理语句防止 SQL 注入,虽然这只是一个 demo,但这种安全习惯应当从一开始就养成。第二,用intval($_GET['id'])强制类型转换,把非法参数挡在门外。第三,文件缓存逻辑让同一 ISBN 的条形码只生成一次,减少 I/O 开销。
4. 前端展示与下载集成
4.1 页面展示方案
后端接口已经能够输出 PNG 图片,前端展示就很简单了。最直接的方式是把接口地址放在<img>标签的src属性里:
<img src="/barcode.php?id=1" alt="条形码">到这一步,用户访问商品详情页就能看到条形码。但我还做了一点小增强:提供一张独立的展示页,用户输入编号即可预览条形码,方便测试人员校验生成效果。这个页面用了一个简单的 HTML 表单:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>条形码生成Demo</title> <style> body { font-family: sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; } input[type=text] { width: 300px; padding: 8px; font-size: 16px; } button { padding: 8px 24px; font-size: 16px; cursor: pointer; } .result { margin-top: 30px; padding: 20px; border: 1px solid #eee; text-align: center; } </style> </head> <body> <h2>条形码生成Demo</h2> <form onsubmit="event.preventDefault(); loadBarcode();"> <input type="text" id="code" placeholder="请输入编号,如 9787121418174"> <button type="submit">生成条形码</button> </form> <div class="result"> <img id="barcodeImg" alt="条形码预览"> </div> <script> function loadBarcode() { const code = document.getElementById('code').value.trim(); if (!code) { alert('编号不能为空'); return; } document.getElementById('barcodeImg').src = '/barcode.php?code=' + encodeURIComponent(code); } </script> </body> </html>这里要注意,因为我在 JS 里用了encodeURIComponent()对参数进行编码,后端接收时也应该用$_GET['code']获取并做基本校验,避免特殊字符破坏 URL 结构。
4.2 点击下载与打印适配
展示只是第一步,用户最终要把条形码贴到商品上,所以下载和打印是刚需。我在展示页里加了两个按钮:一个触发下载,一个触发打印。
下载功能实现思路很简单,加一个 HTTP 响应头Content-Disposition: attachment即可。我新增了一个download.php文件,几乎和barcode.php逻辑一致,只是响应头不同:
header('Content-Type: image/png'); header('Content-Disposition: attachment; filename="' . $isbn . '.png"'); readfile($barcodeFile);打印功能则利用了浏览器的能力,把条形码图片放进去,调用window.print()。这里值得注意的一点是,打印样式中需要把页面多余的边距、按钮、输入框隐藏掉,只留下条形码区域,否则打印出来会浪费一整张纸。我在 CSS 里加了媒体查询:
@media print { body * { visibility: hidden; } #printArea, #printArea * { visibility: visible; } #printArea { position: absolute; left: 0; top: 0; } }这套“隐藏所有元素再显示目标区域”的做法,是前端打印的经典套路,用起来非常稳。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
开发过程中,我整理了一份高频问题清单,按出现概率排序,方便遇到相似情况快速定位。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 生成图片为空白或纯色块 | 服务器的 GD 库未启用或版本过低 | 检查php -m,安装或升级 GD 扩展 |
调用getBarcode()抛出“错误码长度”异常 | 使用 EAN-13 等定长码制,但内容位数不符 | 改用 Code128 码制,或补齐内容位数 |
| 生成的条形码扫码无法识别 | 宽度因子太小,或图片被拉伸变形 | 宽度因子至少设为 2;前端展示不要随意拉伸图片 |
| 图片下方文字乱码 | 字体文件路径错误,或字体不支持中文 | 检查 TTF 字体路径,改用支持中文的字体(如思源黑体) |
| 大量生成时内存爆掉 | 循环里创建的图像资源未释放 | 每次生成后调用imagedestroy()释放资源 |
| 本地正常但服务器上不显示图片 | 未安装 PHP 的 fileinfo 扩展或图片目录无写权限 | 安装 fileinfo 扩展;检查目录权限 |
5.2 独家避坑心得
第一个坑是字体路径。几乎所有用过imagettftext()的开发者都会被字体路径坑过。绝对路径虽然一劳永逸,但代码一旦迁移到别的服务器就得改。我的习惯是把字体文件放进项目目录,用__DIR__拼出完整路径,这样项目打包到哪里都不会丢字体。
第二个坑是文件锁。如果有多个人同时首次访问某个条码,可能出现并发请求同时检测到文件不存在、同时生成图片的情况。虽然业务上影响不大(只是多生成几次),但在高并发场景下会增加服务器压力。解决办法是使用文件锁:
$lockFile = $barcodeFile . '.lock'; $fp = fopen($lockFile, 'w'); flock($fp, LOCK_EX); if (!file_exists($barcodeFile)) { file_put_contents($barcodeFile, generateBarcodeWithText($isbn)); } flock($fp, LOCK_UN); fclose($fp);第三个坑是打印清晰度。如果发现打印出的条形码扫描不通过,优先检查图片的分辨率,而不是怀疑生成逻辑。浏览器显示时如果页面 CSS 把图片缩放得太小,或者打印选项里设置了“适应页面”导致图片被压缩,都会让条纹糊成一团。建议在打印样式里给条形码图片设置固定宽度,比如width: 200px;,保证原始像素不被过度缩小。
第四个坑比较隐蔽,和 PHP 的内存限制有关。默认memory_limit是 128M,生成几十张条形码图片通常没事,但如果你在循环里把每一张图片都用imagecreatefromstring()解析一遍,再拼到一张大图上批量打印,内存很容易飙到 200M+。遇到这种情况,要么在循环里实时释放资源,要么临时调大内存限制ini_set('memory_limit', '256M');,我个人更推荐前者,代码更稳健。
6. 从 demo 到生产环境的扩展思路
demo 跑通只是第一步,真要接到生产环境,还有几件事值得提前考虑。
第一,引入 Redis 或文件缓存机制。条形码内容通常是数据库里的标识字段,短时间内不会变化。我的 demo 里已经做了文件缓存,但如果系统是分布式部署,多台服务器共享文件目录还要考虑文件同步问题。更稳妥的方案是把图片转成 Base64 字符串存入 Redis,设置一个合理的过期时间,前端通过接口直接取 Base64 data URI 展示,彻底绕开静态文件分发。
第二,考虑接入条形码打印中间件。如果你所在的公司用的是斑马(Zebra)这类工业条码打印机,服务器端生成 PNG 图片只是万里长征第一步。真正的生产链路往往是:后端生成条码数据 -> 传给打印服务 -> 打印服务控制打印机。好在 php-barcode-generator 支持输出 SVG 格式,SVG 矢量图在打印时不会失真,非常适合这个方向。
use Picqer\Barcode\BarcodeGeneratorSVG; $generator = new BarcodeGeneratorSVG(); $svg = $generator->getBarcode($isbn, $generator::TYPE_CODE_128, 2, 50);第三,统一封装成 service 类。如果你是一个喜欢代码整洁的人,强烈建议把条形码生成、缓存、验证封装成一个独立的服务类,和控制器解耦。这样换类库、换存储方案都只需要改一处,不用全局搜索散落的file_put_contents。
我在实际项目中用的就是类似下面这个简化的服务接口:
interface BarcodeServiceInterface { public function generate(string $code): string; // 返回PNG的Base64编码 public function getPath(string $code): string; // 返回图片缓存路径 public function exists(string $code): bool; // 判断缓存是否存在 }一开始觉得麻烦,但后来需求从“生成一张图书条码”扩展到“生成各种物料编码条码”的时候,这套抽象帮我省了大力气。
最后再分享一个小技巧:调试阶段可以把生成结果直接输出成 Base64 字符串,在浏览器地址栏粘贴查看,省去不断写文件、删文件的麻烦:
header('Content-Type: text/plain'); $data = (new BarcodeGeneratorPNG())->getBarcode('demo123', BarcodeGeneratorPNG::TYPE_CODE_128); echo base64_encode($data);把输出贴到浏览器里,前面加上data:image/png;base64,前缀就能直接预览。这个法子特别适合排查“图片能不能生成”和“生成内容对不对”这两个基本问题。条形码这功能吧,看着不起眼,但真到扫码枪怼上的时候出了问题,比程序报错还让人头大。希望这份实测记录能帮你少走几个我当时走过的弯路。
本文还有配套的精品资源,点击获取