简介PHP二维码在线生成工具本地版v1.0是一套基于PHP开发的轻量级二维码生成源码面向需要在自有网站、子目录或虚拟主机环境中快速生成二维码的开发者与站点管理员。程序通过当前时间与随机数组合生成PNG图片路径从源头避免文件重复生成的二维码图片保存在根目录单张大小约1K4K长宽随文字数量自适应实测200个汉字可正常运行上传至PHP空间即可使用部署门槛低无需数据库或第三方扩展。压缩包共7个文件体积仅25KB包含2个PHP文件负责首页展示与核心生成逻辑、2个TXT说明文档使用说明与备注文本、2个URL快捷方式指向脚本之家等辅助资源以及1张PNG示例图目录结构非常精简便于快速理解调用关系并投入应用。该工具可作为小型二维码工具集成、学习PHP文件生成与图像输出的实战范例若在表单提交、链接分享等场景中需要临时生成文字二维码也能通过修改参数直接复用。目前已有280人学习/下载适合PHP初级开发者收藏备查。1. PHP 二维码在线生成工具本地版断网也能出图的 PHP 服务做内部系统的人应该都有这种感觉临时要给设备贴个二维码、给会议室做个扫码签到、给物料打个批次标签第一反应是打开网页版的二维码生成器。但真要量产几百上千个码的时候网页版就不够用了——内容敏感不能过第三方服务器一次生成太多要手动排队内网环境干脆连外网都不通。这个标题做的就是把「在线生成」这件事搬回本地用 PHP 起一个服务输入内容、选参数、出图、下载全程数据不出内网离线也能跑源码在自己手里随便改。v1.0 这个版本号很实在它不需要做成多复杂的系统核心功能就三件事生成、参数可调、能落地成图片。适合谁读用 PHP 做内部系统的开发、需要批量生成二维码的运维或实施人员以及手里只有一套老 PHP 环境、不愿意为了一个二维码功能去引 Node 或 Python 的人。这篇文章会把原理、实现、踩坑一次讲完。2. 二维码底层逻辑与本地版选型纠错级别、空白区和轮子怎么选2.1 QR 码不是一堆黑点版本、掩码与纠错纠错级别的关系很多人把二维码当成随机黑点调参数全靠试。其实 QR 码的结构是固定的它由 21×21 个「模块」也就是最小黑白点起步每升一个版本宽高各加 4 个模块最高到版本 40。每个二维码里除了数据本身还有定位图案、格式信息以及最重要的一部分——纠错码。QR 用的是 Reed-Solomon 纠错分 L、M、Q、H 四个级别含义是内容被遮挡或污损后最多能找回多少比例的信息。纠错级别可恢复比例典型场景L约 7%显示环境干净比如屏幕上的码M约 15%大多数常规场景默认推荐Q约 25%印刷品、可能被部分遮挡H约 30%要叠 logo、要打印在粗糙材质上另一个不能省的配置是「空白区」英文叫 quiet zone指二维码图案四周至少留 4 个模块宽的空白。这个值经常被忽略很多人生成的二维码贴到墙上扫不出来用脚趾头想都知道是贴太满扫码器找不到码的边界。库里的 margin 参数就是干这个的默认给 4一般别低于 3。还有掩码mask机制QR 生成时会对数据做 8 种掩码图案的尝试选惩罚分数最低的一种确保黑白分布均匀方便扫码器识别。这部分算法工作完全由库完成使用者不需要干预但理解它存在有助于解释一个问题为什么同一内容的码不同库生成出来的图案不一样因为掩码选择和编码细节实现有差异不影响扫码结果。我们做本地版第一件事就是把「生成」这个黑匣子交给成熟实现而不是自己从头写位流编码。但理解 L/M/Q/H 和空白区能决定你调参时是瞎试还是有的放矢。2.2 本地版为什么能打赢在线 API延迟、隐私与批量做这个方向之前我们团队先盘点过「为什么不直接用现成的在线接口」。最直接的原因是数据边界内网系统里要生成的二维码内容经常是http://192.168.x.x/...#/page?token...这种带敏感参数的内网地址这玩意儿发到外网服务器上等于把内网拓扑和账号体系送出去。另外在线接口普遍有频率限制免费版生成 500 个码基本就要排队或触发验证码更不要说断网的时候整条流程直接瘫痪。本地版的优势可以归纳成四点。第一离线可用第二内容自控存在你自己生成的图片里不出服务器第三批量自由脚本里 for 循环生成一万个都没人限你第四可以做缓存同样内容的码生成一次存下来后续请求直接读文件响应时间从秒级降到毫秒级。对比一下两种方案对比点网页版在线工具本地版 PHP 服务网络依赖必须联网完全离线内容隐私经过第三方服务器不出内网批量生成受限无限制样式定制只给固定样式全参数可控维护成本零一台能跑 PHP 的机器本地版不能简单理解成「一个网络工具装到本地」它的价值是把你从「依赖别人服务」变成「自己掌握生成链路」。落到实现上就是一个 PHP 入口 一个二维码库 一个缓存目录也就是后面要写的 v1.0 闭环。2.3 轮子怎么选老牌类库、Composer 包还是自己写选库之前先泼盆冷水不要自己写 QR 编码器。因为除了把数据塞进矩阵还要做 Reed-Solomon 纠错码计算、掩码选择和惩罚分评估标准文本有几十页业余实现大概率在「自己能扫出来」和「别人扫不出来」之间反复横跳。做工具的人库就是用来省的。PHP 生态里最常被提到的两个方向。一个是老牌的 phpqrcode单文件类库没有依赖一个QRcode::png()就能出图特点是老、简单、遍地都是教程。但它是 PHP 5 时代的代码风格在 PHP 8 环境下容易冒出弃用告警后面避坑章会展开。另一个是 Composer 上更现代的实现API 更规范支持 PNG / SVG / EPS 输出能直接把 logo 叠进去推荐给 PHP 8 环境。其余还有不少个人写的类库API 设计各异没经过大量生产验证的不建议生产环境用。我的选型建议就一句话能用 Composer 就用现代库环境老旧到装不了 Composer 才退回老牌单文件类库。两个方案后面都给出可直接抄的最小实现。选型没有绝对正确但「能用就上新库、不能上新库就用老库、永远不自己写」这个原则能帮你少走弯路。3. 用 PHP 把本地生成服务跑起来从单文件到带缓存的接口3.1 环境准备与最小闭环一个文件直接出图先确认环境。任意 PHP 环境都可以只要开了 GD 扩展用来画图就能跑。Windows 上常见的一键环境包默认就带着Linux 上缺 GD 的话装一下扩展再重启服务即可这是唯一的前置条件。工具本身不需要数据库不需要 Composer 之外的任何依赖。接着是装库。以 Composer 方式为例在你准备放工具的目录里执行# 初始化一个 Composer 项目包名可以自己定这里是示意 composer init --namelocal/qr-tool --no-interaction # 安装二维码生成库装完会出现 vendor/ 目录 composer require phpqrcode/phpqrcode装完后写一个最小入口能访问就说明链路通了。这里先用经典老库的写法因为它 API 十几年来几乎没变过按步骤抄不会因为版本差异翻车?php // qr.php —— 极简版二维码生成接口 require_once __DIR__ . /vendor/autoload.php; // 从 URL 参数里取内容 $text $_GET[text] ?? ; if ($text ) { http_response_code(400); exit(text 参数不能为空); } // 参数顺序内容、输出文件(false 表示直接输出到浏览器)、纠错级别、每个模块像素、空白区 \QRcode::png($text, false, L, 8, 4);这段代码的逻辑不复杂拿到 text 参数非空就调用库出图输出一张 PNG 给浏览器。false是关键参数——它让库把图片数据直接写进输出流不需要落盘如果给一个文件路径库就会把图片写到文件里适合批量场景。参数速查表是这个服务最核心的调参依据参数取值说明纠错级别L / M / Q / H值越大抗遮挡越强内容容量越小模块像素1-10每个黑点占几个像素输出图片的基本粒度空白区0-20四周留白宽度按模块数算建议 4浏览器访问qr.php?texthttps://example.comlevelH如果页面里出现一张能扫的二维码最小闭环就成了。这里有个新手常犯的直觉错误调「模块像素」不等于「放大图片」它是让二维码的每个点变得更粗不会产生插值模糊所以生成大图要用它而不是后面用 CSS 或编辑器强行拉伸。3.2 带缓存和参数控制的接口版v1.0 的完整形态极简版只能验证链路真正要给别人用得加参数控制和缓存。先想清楚一个事每次请求都重新算一张图是浪费的因为有大量场景是同一内容反复请求比如扫码签到的二维码几百人扫的是同一张码。缓存的做法是把参数拼成字符串做一次哈希用哈希做文件名图片落地缓存目录下次请求直接读文件。这里给出一个带完整控制的接口实现?php // api/qr.php —— 本地版二维码生成接口带缓存 require __DIR__ . /../vendor/autoload.php; use Endroid\QrCode\QrCode; use Endroid\QrCode\Writer\PngWriter; // 参数收敛只允许这 4 个避免被乱传参数 $text $_GET[text] ?? ; $level strtoupper($_GET[level] ?? M); $size (int) ($_GET[size] ?? 600); $margin (int) ($_GET[margin] ?? 20); // 输入校验内容必填长度限 2048级别限 L/M/Q/H if ($text || mb_strlen($text) 2048) { http_response_code(400); exit(text 参数为空或超长); } if (!in_array($level, [L, M, Q, H], true)) { http_response_code(400); exit(level 参数非法); } $cacheDir __DIR__ . /../cache; if (!is_dir($cacheDir)) { mkdir($cacheDir, 0775, true); } // 缓存键内容 全部参数任何一项变了就生成新图 $key md5($text . | . $level . | . $size . | . $margin); $cacheFile $cacheDir . / . $key . .png; if (!file_exists($cacheFile)) { $qrCode QrCode::create($text) -setSize($size) -setMargin($margin) -setErrorCorrectionLevel($level); $writer new PngWriter(); $result $writer-write($qrCode); file_put_contents($cacheFile, $result-getString()); } // 输出图片并让浏览器/CDN 缓存一天 header(Content-Type: image/png); header(Cache-Control: public, max-age86400); readfile($cacheFile);逻辑上比极简版多做了四件事参数白名单化、输入长度校验、文件缓存、HTTP 缓存头。其中缓存键的设计值得留意——它把所有参与生成图的参数都放进了哈希意味着任何参数变化都会生成新图而相同参数永远落到同一个文件不会出现「改了尺寸却拿到了旧图」的尴尬。关于这段代码里QrCode::create()的用法不同小版本 API 略有差异有的版本用构造器new QrCode($text)有的用静态方法QrCode::create($text)装好库后翻一下 vendor 目录里对应类的公开方法再确认即可这不影响整体架构。PngWriter同理SVG 输出换成SvgWriterEPS 输出换成对应的 Writer接口结构一样。有了接口再给一个简单的前台页面方便不懂命令行的人用浏览器操作。页面就一个输入框加几个选项前端把参数拼接成 URL 塞给图片标签!doctype html html langzh-CN head meta charsetUTF-8 title本地二维码生成器/title /head body h3本地二维码生成器 v1.0/h3 textarea idtext rows4 cols50 placeholder输入内容或链接/textarea brbr 纠错级别 select idlevel option valueLL7%/option option valueM selectedM15%/option option valueQQ25%/option option valueHH30%/option /select 尺寸 input typenumber idsize value600 min200 max1200 brbr button idbtn生成/button brbr img idqr alt二维码预览 styledisplay:none; border:1px solid #ccc; script const btn document.getElementById(btn); const img document.getElementById(qr); btn.addEventListener(click, function () { const text document.getElementById(text).value; const level document.getElementById(level).value; const size document.getElementById(size).value; // 这里必须 encodeURIComponent否则内容里的 和 # 会截断 URL const url /api/qr.php?text encodeURIComponent(text) level level size size; img.src url; img.style.display inline; }); /script /body /html这个页面本身不实现任何生成逻辑只负责拼接 URL 和刷新图片。核心是在 JS 里用了encodeURIComponent这是做二维码工具最容易忽略的一步——用户输入的链接里带或#是常态不编码的话参数会被截断生成出来的码内容是错的。如果需要被前端跨域调用接口侧按需加 CORS 头即可内网部署一般不需要考虑这层。3.3 批量生成命令行脚本处理 5000 个物料码做物料标签和管理场景的人用页面一个个生成根本不现实。批量场景我一般会写一个 CLI 脚本从 CSV 里读数据循环调用同一个生成库直接落盘文件。这样不受浏览器超时限制也不会因为一张大图把 PHP-FPM 进程拖住。?php // cli/batch.php —— 命令行批量生成 // 用法php cli/batch.php input.csv ./output require __DIR__ . /../vendor/autoload.php; $csvFile $argv[1]; $outputDir rtrim($argv[2], /) . /; if (!is_dir($outputDir)) { mkdir($outputDir, 0775, true); } $handle fopen($csvFile, r); // CSV 第一行是表头内容,编号,纠错级别 fgetcsv($handle); while (($row fgetcsv($handle)) ! false) { // 传进来的可能是 UTF-8 也可能带 BOM先清洗再处理 $text trim($row[0]); $code trim($row[1]); $level trim($row[2]) ?: M; if ($text || $code ) { continue; } // 文件名用业务编号避免只凭内容哈希难回溯 $file $outputDir . $code . .png; // 每次生成内容不一样不需要缓存直接输出到文件 $qrCode QrCode::create($text) -setSize(600) -setMargin(20) -setErrorCorrectionLevel($level); $writer new PngWriter(); $writer-write($qrCode)-saveToFile($file); echo generated: {$code}\n; } fclose($handle); echo done.\n;批量脚本和接口版的差别在于不落缓存、文件名用业务编码、用saveToFile()直接写盘。注意这里生成 600px 的 PNG 是给打印准备的如果只是屏幕显示300px 足够文件体积小很多。批量跑之前先拿 3-5 条数据试跑确认文件名规则和输出路径再放开全量。整套脚本跑完物料码就有了甚至不需要任何界面。4. 本地版上线前要排的 5 个坑中文乱码、模糊、PHP 8 与接口滥用4.1 中文内容扫出来乱码或直接扫不出来现象生成的二维码内容里包含中文手机扫码后显示的是乱码或者干脆提示「无法识别」。这问题在从表单提交内容时出现得最频繁。原因有两层。第一层是编码不一致页面或 URL 参数不是 UTF-8库拿到的是错误编码的字节流直接把字节塞进二维码扫码器按 UTF-8 解出来就是乱码。第二层是特殊字符问题内容里带了或#URL 没有编码参数被截断二维码内容变成了半截文本。解决全链路固定 UTF-8——页面声明 UTF-8PHP 侧用mb_internal_encoding(UTF-8)兜底前端拼 URL 时用encodeURIComponent。内容里有换行、有 query 参数都不要想着手工替换统一交给 URL 编码。从数据库或文件读出来生成时清洗一遍 BOM 头再交给库。这是二维码生成里最典型的「看着没问题扫出来一团糟」的问题血泪经验。4.2 放大后模糊、打印后扫不动问题在生成参数而不是图片格式现象300×300 的 PNG 拉大到 600px 显示没问题打印成标签后扫不出来或者用截图工具放大四倍边缘全是锯齿和灰块。原因位图拉大是插值补点QR 码的黑白边界被插值搞成灰色渐变扫码器按二值化处理时判定不了边界。二维码不是普通图片它是网格边界必须锐利。解决生成时就按目标尺寸生成不要事后放大。屏幕用 300-600px打印建议直接生成 600px 以上或输出 SVG 矢量格式打印分辨率按 300dpi 计算一个 25mm 见方的标签对应约 295px直接生成这个尺寸别让打印机去缩放位图。同时确认 margin 给足打印后周边留白不够也会扫不出。验证方法是生成后截图放大 4 倍看边缘如果边缘有过渡色或锯齿就调生成参数别调图片尺寸。这个习惯能挡住九成的「打印翻车」。4.3 PHP 8 环境下老库疯狂报警告甚至输出损坏的图片现象在 PHP 8 环境跑老牌类库页面顶部出现一串 deprecation 提示更糟的情况是图片输出前多了几行文字浏览器显示图片失败。原因老库写于 PHP 5 时代用了当时允许、后来废弃的语法习惯比如动态创建属性、某些旧函数在 PHP 8.0 和 8.2 里分别被标为弃用和移除。而 PHP 的输出缓冲是连续的警告文本混进图片二进制流图片就坏了。解决首选换现代库这是最省心的路。确实换不了时可以在引入老库前关掉 deprecation 提示?php // 兜底方案屏蔽弃用提示避免污染输出流 error_reporting(E_ALL ~E_DEPRECATED); require_once phpqrcode.php;但这只是把症状盖住老库在更严格的 PHP 版本下可能不只是告警建议在生产环境把display_errors关掉错误记到日志里而不是输出到页面。另一个相关的坑是本地集成环境默认开着错误输出调试时能看见提示是好事上线前一定要确认关掉否则不光二维码工具任何输出图片的接口都会被污染。4.4 开放接口被滥用磁盘被塞满、被当免费生成器现象接口跑了一阵磁盘空间被几百张异常巨大的 PNG 占满或者有人用这个接口批量生成内容拿去当广告二维码。原因接口只做了最小校验text无长度限制、size无上限同一个接口还被反复请求每生成一张图都落盘磁盘自然撑爆。原因分析完解决要分三步text长度限制在 2048 以内因为二维码在低纠错级别下实际容量有限超出这个长度生成的码本来就难扫没有正当用途size上限限制在 1200px单图文件控制在合理范围内同参数走缓存已经生成过的内容不再重新计算内容里拒绝script等标签防止参数回显到页面时带上 XSS 语义。对内网工具再简单加一层 IP 白名单或 Basic Auth 就能挡住大部分滥用。不要觉得「内网而已无所谓」内网里跑着扫描器的人比你想的多。接口参数不校验就是黑匣子出问题只能从头翻日志。4.5 贴了 logo 或者改了配色扫码率直线下降现象在二维码中间贴了个 1/3 面积的 logo自己手机扫没问题同事的某品牌手机扫不出做成渐变色、圆角样式后打印出来更难扫。原因二维码的纠错能力不是无限的中心位置恰好覆盖了大量纠错码模块遮挡面积超过纠错上限就废了。配色问题同理浅色前景 深色背景会降低扫码器的识别对比度甚至有些扫码器直接忽略低对比度图案。解决加 logo 前把纠错级别拉到 Hlogo 面积控制在码面积大约 1/5 以内别追求「越大越好看」。配色遵循一个原则深色前景、浅色背景、黑白为底线。样式转换是加分项但生产物料永远备一份纯黑白版本出问题就换回去。验证方式别只用一台手机至少用两个不同品牌的扫码 App 各扫一次再用解码工具校验英文原始内容——「我自己能扫出来」不是标准主流设备都能扫才是标准。5. 从 v1.0 到能上线动态二维码、离线验证与删功能5.1 动态二维码码不变、内容后台改二维码生成工具做到 v1.0 之后最值得加的方向是「动态二维码」。静态二维码的内容写死在码里印出去就改不了了动态二维码生成的是一段短链或固定地址码面上只有这个地址真正的内容由服务端在扫码后返回。这时候二维码图案不变后台改目标地址旧的码还能用。设备巡检、资产标签、会议签到这类场景贴出去之前不知道内容会不会变用动态方案能省下重新印码的成本。本地版天然适合做这个——生成服务就在自己手里加一张落地页路由表就行不需要依赖别人的短链服务。5.2 生成之后的验证三步别等贴墙上才发现废了我有个习惯任何参数改动后都会做一遍「生成 - 验证 - 落盘」三步。生成后先扫一次确认内容和输入一致然后把图片放大四倍看边缘是否锐利最后打印一张小样扫一下确认打印机没有把网点打糊。这三步五分钟能做完但能挡掉九成的「贴完才发现扫不出」。尤其是打印物料它跟你屏幕显示的呈现逻辑完全不一样屏幕能扫不等于打印能扫。做这个工具最深的教训是功能是做加法做死的。当年为了「完整」加了十几种样式模板、颜色选择器、批量下载压缩包结果真正被人天天用的是「粘贴链接、选纠错级别、出图」这三个动作。剩下的功能只是让界面变拥挤。下一版如果要做动态二维码我会先把落地页、扫码统计和内容管理这三件事做扎实形式上的花样等到使用的人真提需求再说。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?