實戰(zhàn):從原理到批量導出)
簡介PHP二維碼在線生成工具本地版v1.0是一份基于PHP源碼的二維碼生成方案主要面向需要在自己網站空間或本地環(huán)境生成二維碼的開發(fā)者解決線上生成服務依賴外部接口、無法自定義部署的問題。程序采用當前時間與隨機數(shù)組合的方式生成PNG圖片路徑可有效避免文件重復生成的圖片保存在根目錄單張體積約1K4K圖片寬高會隨文字數(shù)量自動變化實測生成包含200個漢字的二維碼也能正常運行整體邏輯簡潔適合內網環(huán)境、臨時工具站或教學演示場景也便于按需修改樣式或擴展功能。壓縮包共7個文件以兩個PHP核心腳本為主配以txt使用說明與URL參考鏈接另有png示例圖包體僅25KB部署非常輕量上傳至PHP網站空間后通過首頁index.php即可使用子目錄部署同樣支持。目前已有280人學習下載適合具備基礎PHP環(huán)境、希望快速搭建本地二維碼生成功能的開發(fā)者參考。 做這個工具的直接原因很簡單我給客戶做小程序后臺時每天要給幾十個商品生成帶渠道參數(shù)的二維碼原來一直用在線二維碼網站但用著用著問題就來了。最讓我沒法忍的是商品編號、渠道碼這些內部數(shù)據(jù)都要先傳到別人的服務器免費版還有各種限制一不注意就彈廣告。后來我干脆自己用 PHP 寫了一個本地版二維碼在線生成工具v1.0 從開發(fā)到現(xiàn)在穩(wěn)定跑了兩個多月今天把完整實現(xiàn)思路、核心代碼和踩坑記錄都整理出來給同樣需要本地生成二維碼的朋友參考。這里的“在線”指的是瀏覽器訪問本地服務實時生成不是 SaaS 服務。1. 為什么我棄用在線二維碼網站自己寫了個本地版1.1 在線生成器的四個痛點用在線網站生成二維碼表面上省事實際用起來難受的地方不少。先說數(shù)據(jù)安全問題這是最核心的。我在給客戶做商品渠道追蹤時二維碼內容里通常帶著商品編號、內部物料編碼、渠道參數(shù)有時候還有客戶手機號這些東西一旦提交到第三方網站數(shù)據(jù)就脫離你控制了。雖然大多數(shù)網站聲稱不做存儲但你沒法驗證出了問題就是自己的鍋。第二個痛點是功能限制。免費版在線生成器基本只能改改內容、尺寸容錯級別固定想加 logo、改顏色、批量生成全部要付費。我曾經為了一個項目連續(xù)開了三個月會員一個月幾十塊錢不算多但用起來總覺得虧。第三個痛點是穩(wěn)定性不可控。在線服務哪天改版、域名變更、圖片 CDN 掛了你歷史生成的二維碼圖片就可能失效。二維碼一旦打印出去用戶掃碼直接就打不開這種問題在線工具廠家不會替你負責。第四個痛點是效率。一次要生成一兩百個二維碼時用在線工具要么手動復制粘貼到網頁要么一個個點下載效率非常低。有時還有驗證碼、登錄、等待隊列之類的東西批量場景下根本沒法用。1.2 本地版的核心價值數(shù)據(jù)安全、離線可用、可二次開發(fā)自己寫本地版之后以上四個問題基本都消失了。數(shù)據(jù)全程在本地 PHP 進程里處理不經過任何第三方服務器商品信息、渠道參數(shù)這些敏感數(shù)據(jù)可以放心用。服務跑在內網或者本機斷網也能正常生成不用看別人臉色。另外最重要的是可二次開發(fā)。我后來把生成接口接進了內部商品管理后臺商品入庫時自動生成帶渠道參數(shù)的二維碼文件名按商品編碼命名直接歸檔到服務器。在線工具根本做不到這種深度集成。1.3 v1.0 的功能邊界與技術選型v1.0 我規(guī)劃了這幾個功能單張二維碼生成、批量生成、自定義容錯級別、自定義尺寸和留白、logo 合成、前端實時預覽、批量打包下載。技術棧沒有追新用了原生 PHP jQuery phpqrcode。phpqrcode 是網上很老牌的單文件 PHP 二維碼庫不需要 Composer適合本地小工具。如果你項目里用的是 ThinkPHP 3.2.3 這類老框架也可以直接把 qrlib.php 引入改成控制器方法即可代碼思路完全一致。2. 二維碼生成原理與 PHP 庫選型2.1 黑白格子背后的數(shù)據(jù)編碼與容錯級別寫生成工具之前我建議先花十分鐘理解二維碼的基本原理不然遇到“掃不出來”的問題會很被動。二維碼本質上是一個二維矩陣把字符串按一定規(guī)則轉成二進制再通過 Reed-Solomon 糾錯算法加上冗余數(shù)據(jù)最終分布到矩陣的黑色和白色模塊里。識別端掃碼時先通過三個角上的回字定位圖形確定方向和坐標系再讀取矩陣里的數(shù)據(jù)反向還原出原始字符串。這里最需要關注的是容錯級別。二維碼分 L、M、Q、H 四個級別分別能容忍大約 7%、15%、25%、30% 的污損或遮擋。容錯級別越高二維碼圖案越密識別越穩(wěn)。給商品打碼這種場景我默認用 M 級既能保證一定的抗污損能力圖案密度也適中。如果要在二維碼中間貼 logo就得用 Q 或者 H 級不然中央遮擋區(qū)域容易導致掃碼失敗。2.2 phpqrcode 與 endroid/qr-code 怎么選PHP 生成二維碼的庫主流就是 phpqrcode 和 endroid/qr-code 兩個。我當時的選型對比是這樣的對比項phpqrcodeendroid/qr-code安裝方式手動 require 單文件Composer 安裝PHP 版本要求PHP 5.6 即可要求 PHP 7.2部分版本更高依賴擴展GD 庫GD、fileinfo 等輸出格式PNG、SVGPNG、SVG、EPS、PDF 等Logo 合成需要自己用 GD 處理官方支持自帶接口維護狀態(tài)多年未更新功能穩(wěn)定持續(xù)維護上手成本極低需要熟悉 Composer最終我選了 phpqrcode原因有三個一是本地服務器是 Windows 環(huán)境跑著老項目PHP 版本還停留在 5.6endroid 新版本直接裝不上二是這個工具不需要復雜導出格式PNG 就夠用三是 phpqrcode 單文件引入代碼邏輯一目了然出問題好排查。2.3 本地環(huán)境準備PHP 版本、GD 擴展、目錄權限開始前先確認環(huán)境。Windows 上我建議用 phpstudy 或 XAMPPLinux 直接用包管理器安裝 php-gd。打開命令行輸入php -m如果輸出列表里沒有 gd就需要安裝或開啟擴展。php -m | grep gd如果沒有輸出說明 GD 擴展沒裝好。我在 Windows 的 phpstudy 里遇到過一次原因很簡單php.ini 里沒有去掉extensiongd2前面的分號。如果你用 Docker 拉官方鏡像比如php:7.4-apache也需要自己執(zhí)行docker-php-ext-install gd安裝擴展。另外要注意 output 目錄的寫權限。批量生成二維碼時要寫文件apache 或 php-fpm 運行用戶必須對目標目錄有寫權限不然會報 “failed to open stream: Permission denied”。3. 核心實現(xiàn)單張生成、批量導出、Logo 合成、前端預覽3.1 單張二維碼接口20 行代碼搞定phpqrcode 的核心方法只有一行QRcode::png()但直接調用會遇到一個輸出問題這個方法默認把圖片數(shù)據(jù)直接打印到瀏覽器不方便作為接口返回。我用輸出緩沖把它截獲再統(tǒng)一返回圖片流。單張生成接口的完整代碼?php require_once __DIR__ . /lib/phpqrcode/qrlib.php; $data $_GET[data] ?? hello; $size max(1, min((int)($_GET[size] ?? 10), 20)); $margin (int)($_GET[margin] ?? 2); $ec in_array($_GET[ec] ?? M, [L, M, Q, H]) ? $_GET[ec] : M; ob_start(); QRcode::png($data, false, $ec, $size, $margin); $png ob_get_clean(); header(Content-Type: image/png); header(Content-Length: . strlen($png)); echo $png;這段代碼里幾個參數(shù)要注意size 是每個模塊的像素數(shù)取值范圍在 1 到 20 之間實際圖片邊長大約是size * 模塊數(shù) 2 * margin并不是說 size 越大內容越多。margin 是二維碼四周白邊的像素寬度這個值不能設成 0掃碼器需要靠白邊來確認邊界一般保持 2 以上。3.2 批量生成一次導入幾百條并打包下載批量生成是本地工具相對在線網站最明顯的優(yōu)勢。我的實現(xiàn)是從 textarea 里讀文本一行一條內容然后循環(huán)調用QRcode::png輸出到指定目錄最后用 ZipArchive 打包下載。核心代碼?php require_once __DIR__ . /lib/phpqrcode/qrlib.php; $raw $_POST[data] ?? ; $lines explode(\n, $raw); $outDir __DIR__ . /output/ . date(YmdHis); if (!is_dir($outDir)) { mkdir($outDir, 0755, true); } foreach ($lines as $i $line) { $text trim($line); if ($text ) { continue; } $filename $outDir . / . $i . _ . md5($text) . .png; QRcode::png($text, $filename, QR_ECLEVEL_M, 10, 2); } $zip new ZipArchive(); $zipFile $outDir . .zip; $zip-open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE); foreach (glob($outDir . /*.png) as $file) { $zip-addFile($file, basename($file)); } $zip-close();這里我踩過一個坑如果直接用序號$i做文件名有兩個內容不同的二維碼在循環(huán)里都會生成同樣名稱的文件后生成的文件會覆蓋先前的。后來加了md5($text)做指紋再加上序號前綴基本不會沖突。批量導出時如果一次幾萬條建議用set_time_limit(0)避免執(zhí)行超時同時每處理完一個 unset 釋放內存。3.3 Logo 與彩色二維碼掃得出來才是關鍵默認生成的二維碼是黑白的放在產品包裝上比較丑所以 v1.0 加了 logo 合成和顏色自定義。logo 合成的原理很簡單先生成二維碼 PNG再用 GD 庫把 logo 圖片縮放后貼到二維碼中央。?php $qr imagecreatefrompng($qrFile); $logo imagecreatefrompng($logoFile); $qrW imagesx($qr); $logoW (int)($qrW * 0.25); // logo邊長控制在二維碼的25% $logoH (int)(imagesy($logo) * $logoW / imagesx($logo)); $dstX (int)(($qrW - $logoW) / 2); $dstY (int)(($qrW - $logoH) / 2); imagecopyresampled($qr, $logo, $dstX, $dstY, 0, 0, $logoW, $logoH, imagesx($logo), imagesy($logo)); imagepng($qr, $qrFile);logo 大小一定要控制好邊長不要超過二維碼總尺寸的 30%。二維碼的容錯機制能容忍中央?yún)^(qū)域被遮擋但也有限度我實測過logo 如果超過 30%手機掃碼時識別率會明顯下降尤其是環(huán)境光線不好時基本就是報廢狀態(tài)。彩色二維碼我最初想直接改 phpqrcode 源碼后來發(fā)現(xiàn)不值得。phpqrcode 的QRcode::png()本身就支持$saveToFile參數(shù)但不支持自定義前景色。簡單做法是生成灰度二維碼后再用 GD 遍歷像素替換顏色但性能一般。經驗是二維碼的三個定位角盡量保持深色不要做反色處理。很多人做“反色二維碼”覺得酷把黑底白格或者用亮色當前景結果就是掃碼器識別不了日常使用千萬別這么干。3.4 前端不刷新頁面實時出碼v1.0 的前端用 jQuery 做實時預覽綁定 input 的 input 事件防抖后請求 3.1 里的接口拿回 base64 圖片直接替換 img 標簽。這里關鍵是防抖不然用戶每敲一個字符就請求一次本地服務壓力不大但瀏覽器網絡請求會亂掉。另外分享一個擴展場景有人想在背景圖上拖拽二維碼、調整大小后保存成一張新圖片。這個需求本質上已經不是生成二維碼而是圖片合成。前端用 html2canvas 可以把 DOM 截圖但高清輸出比較麻煩字號和圖片清晰度都不穩(wěn)定。我更推薦的做法是后端接收坐標、尺寸、背景圖參數(shù)用 GD 成圖這樣圖片質量可控還能保存高清版本。v1.0 我把這個功能留到 v1.1 再做成獨立模塊。4. 完整實操把 v1.0 跑起來并集成到業(yè)務系統(tǒng)4.1 目錄結構與部署步驟我最終的目錄結構是這樣的qr_tool/ ├── index.php ├── api.php ├── lib/ │ └── phpqrcode/ │ ├── qrlib.php │ └── ... ├── assets/ │ └── jquery.min.js └── output/部署步驟很簡單把整個目錄放到本地 Web 服務的站點根目錄比如 phpstudy 的 www 目錄或 Nginx 的 html 目錄然后訪問http://localhost/qr_tool/index.php。output 目錄需要提前建好并確保有寫權限。4.2 核心代碼逐段解析index.php 核心是表單和實時預覽我貼一段關鍵代碼div classform-item label二維碼內容/label input typetext idqr-data valuehttps://example.com / /div div classform-item label容錯級別/label select idqr-ec option valueLL7%/option option valueM selectedM15%/option option valueQQ25%/option option valueHH30%/option /select /div img idqr-preview src alt二維碼預覽 / script var timer null; $(#qr-data, #qr-ec, #qr-size).on(input change, function () { clearTimeout(timer); timer setTimeout(loadQr, 300); }); function loadQr() { var data $(#qr-data).val(); var ec $(#qr-ec).val(); var size $(#qr-size).val(); $(#qr-preview).attr(src, api.php?data encodeURIComponent(data) ec ec size size); } /script幾個細節(jié)內容輸入框一定要用encodeURIComponent編碼如果內容里帶了、?這樣的參數(shù)不編碼會導致請求參數(shù)斷裂。容錯級別下拉框選中后立刻觸發(fā)預覽用戶交互會比較順手。4.3 性能測試與內存調優(yōu)記錄我本地壓過一次生成一張純文本二維碼接口響應約 10ms肉眼感知就是瞬間。批量生成 100 張普通二維碼寫入文件方式總耗時大概 0.8 秒這個速度完全滿足日常需求。批量場景下內存增長比較明顯主要出現(xiàn)在 logo 合成階段因為imagecreatefrompng會把整張圖片載入內存如果大量并發(fā)內存很容易頂滿。我的處理辦法是每處理完一張就imagedestroy()釋放資源。生成大量文件時建議分批處理比如每 500 張打包一個 zip避免單個 zip 文件過大導致下載超時。5. 常見問題與排查技巧實錄5.1 掃不出來從容錯級別、顏色對比度、白邊三個方面排查這個問題的出現(xiàn)頻率最高。我整理了一個排查順序從概率高的原因開始現(xiàn)象原因解決方案時好時壞尤其在光線差時識別失敗容錯級別太低至少用 M 級有遮擋用 Q/H 級深色背景、亮色模塊或反色二維碼掃描器對反色支持差盡量采用深色模塊淺色背景二維碼四周白邊太小掃碼器無法確認邊界margin 參數(shù)加到 2 以上打印出來掃碼失敗打印分辨率不足或紙張紋路干擾提高打印分辨率至少 300dpi屏幕顯示時掃不出來屏幕亮度、反射調亮屏幕并減少環(huán)境光反射還有一個“二維碼缺口怎么調整”的問題其實是打印場景里經常遇到的。生成時數(shù)據(jù)沒錯但打印后出現(xiàn)白色斷點或黑點這不是二維碼本身的 bug而是打印機臟了或紙張受潮。可以先打一張測試頁確認打印機狀態(tài)。如果邊緣鋸齒明顯檢查打印設置里的圖片縮放不要勾選“適應頁面”否則會改變長寬比例。5.2 中文和特殊字符亂碼怎么處理PHP 生成二維碼時內容字符串必須是 UTF-8 編碼否則生成出來的東西掃碼會顯示亂碼。我在 Windows 下踩過坑PHP 文件本身有時是 GBK 編碼字符串拼接出來直接就是 GBK二維碼掃出來就是亂碼。解決辦法是編輯器統(tǒng)一把 PHP 文件保存為 UTF-8 無 BOM 格式。如果內容是 URL 地址建議生成前用urlencode處理參數(shù)部分。比如https://example.com/?uid1001fromqr直接把整段內容放進二維碼掃碼是能識別的但有些掃碼器會忽略部分參數(shù)穩(wěn)妥的做法是對參數(shù)值做 URL 編碼或者把參數(shù)內容編碼成短鏈再生成二維碼。5.3 批量生成時超時和內存耗盡批量生成幾百張還好超過 1000 張時我遇到過Maximum execution time of 30 seconds exceeded和Allowed memory size exhausted。原因和解決辦法都很明確一是 PHP 默認執(zhí)行時間太短在腳本開頭加set_time_limit(0)二是 GD 圖像資源沒有及時釋放循環(huán)里加imagedestroy($qr)。另外批量提交數(shù)據(jù)時max_input_vars默認只有 1000 個變量如果你用多個 input 字段提交一批數(shù)據(jù)很容易被這個限制截斷。我的方案是用 textarea 一次提交全部內容換行分隔完美避開這個坑。5.4 PHP 環(huán)境報錯速查表報錯信息原因解決Call to undefined function imagecreate()GD 擴展未安裝/未啟用開啟 php.ini 里的extensiongd2或重新編譯安裝QRcode::png(): Argument #4 ($size) must be ...size 參數(shù)類型不合法確保傳入整數(shù)且范圍在 1 到 20 之間mbstring is already loadedphp.ini 中重復加載模塊檢查 php.ini 和命令行配置是否重復保留一處mkdir(): Permission denied輸出目錄無寫權限給目錄配置寫入權限程序內用is_dir()檢查后創(chuàng)建最后再分享一個我自己一直在用的小技巧批量導出的文件名里加上日期、時間戳和內容哈希比如20260503_123000_1f3872a.png這樣既能避免覆蓋又方便追溯到是哪一批生成的數(shù)據(jù)。管理內部物料碼時這個習慣能省不少事。做這個工具最大的體會是本地工具真正的價值不是省那幾十塊錢會員費而是數(shù)據(jù)安全可控、邏輯隨時能改還能和內部系統(tǒng)無縫對接?,F(xiàn)在這個 v1.0 已經從工具升級成了我內部系統(tǒng)里的一個模塊商品錄入時自動調用生成完直接歸檔。后面如果有時間我打算把前端攝像頭掃碼識別也加進去讓生成和驗證形成閉環(huán)。本文還有配套的精品資源點擊獲取