微信小程序二维码生成器 PHP 版
3925 字
20 分钟
微信小程序二维码生成器 PHP 版
微信小程序二维码生成器 PHP 版
简介
这是一个基于 PHP 开发的微信小程序二维码生成工具,支持通过 URL 参数灵活调用微信官方提供的三种二维码生成接口,并支持多语言返回字段(中/英文)。该工具适用于快速生成小程序码、查询剩余额度等场景,方便开发者集成到现有系统中。
功能特性
- 支持微信官方三种二维码生成接口(接口 a / b / c)
- 通过 URL 参数传递 AppID、AppSecret 及业务参数
- 支持查询接口 a+c 的剩余生成额度(10 万总量)
- 自动缓存 access_token,有效期内复用,减少接口调用
- 使用稳定版 access_token 接口(stable_token),避免 40001 错误
- 文件锁机制防止并发刷新 token
- 支持自定义二维码线条颜色(line_color)和自动配色(auto_color)
- 支持透明背景二维码生成(is_hyaline)
- 支持多语言返回字段(lg=en/cn,中英双语)
- 提供可视化的参数说明页面(缺参时自动展示)
- 支持环境版本选择(env_version:release/trial/develop)
接口说明
| 接口代号 | 官方接口名 | 数量限制 | 二维码样式 | 适用场景 |
|---|---|---|---|---|
| a | wxacode.get | 10 万(与 c 共享) | 圆形小程序码(菊花码) | 码数量较少的业务 |
| b | wxacode.getUnlimited | 无限制 | 圆形小程序码(菊花码) | 码数量极多的业务(推荐) |
| c | wxacode.createQRCode | 10 万(与 a 共享) | 普通方形二维码 | 不推荐使用 |
调用方式
1. 生成二维码
https://xxx.xx/index.php?appid={id}&appsecret={secret}&type=b&path=pages/index/index&scene=foo=bar&width=500&lg=en2. 查询剩余额度
https://xxx.xx/index.php?appid={id}&appsecret={secret}&action=query&lg=cn参数说明
基础参数(所有请求必填)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| appid | string | 必填 | — | 微信小程序 AppID |
| appsecret | string | 必填 | — | 微信小程序 AppSecret |
| action | string | 可选 | gen | gen = 生成二维码,query = 查询剩余额度 |
生成二维码参数(action=gen)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| type | string | 必填 | — | 接口类型:a / b / c(小写) • a:wxacode.get,10万限制,圆形码 • b:wxacode.getUnlimited,无限制,圆形码(推荐) • c:wxacode.createQRCode,10万限制,方形码(不推荐) |
| path | string | 必填 | — | 小程序页面路径(不含 ?),例如 pages/index/index |
| scene | string | 可选 | — | 自定义参数(最大 32 个字符)。接口 A/C 拼接到 path;接口 B 作为 scene 字段;若为空,接口 B 默认填 0 |
| width | string/number | 可选 | b (430px) | 二维码宽度:a=280px,b=430px,c=1280px,或任意 280-1280 的数字 |
| auto_color | boolean | 可选 | false | 是否自动配置线条颜色(接口 A/B) |
| line_color | JSON | 可选 | — | 线条颜色,格式:{“r”:0,“g”:0,“b”:0},RGB 值 0-255(接口 A/B) |
| is_hyaline | boolean | 可选 | false | 是否透明背景(接口 A/B)。不传时默认为白色背景 |
| env_version | string | 可选 | release | 环境版本:release(线上)/ trial(体验)/ develop(开发),仅接口 B 支持 |
| lg | string | 可选 | en | 返回字段语言:en = 英文,cn = 中文 |
查询剩余额度参数(action=query)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| action | string | 可选 | query | 设置为 query 查询剩余额度(无需 type 和 path) |
| lg | string | 可选 | en | 返回字段语言:en = 英文,cn = 中文 |
返回示例
查询额度成功
{"code": 0,"data": {"total": 100000,"used": 5,"remaining": 99995,"percent_used": "0.01%"}}查询额度成功(中文)
{"状态码": 0,"数据": {"总数": 100000,"已用": 5,"剩余": 99995,"使用百分比": "0.01%"}}生成二维码失败
{"code": -1,"msg": "生成二维码失败:invalid page path","errcode": 41030}生成二维码失败(中文)
{"状态码": -1,"消息": "生成二维码失败:invalid page path","错误码": 41030}请求示例
示例 1:生成二维码(接口 B,带 scene,宽度 500px)
https://xxx.xx/index.php?appid={id}&appsecret={secret}&type=b&path=pages/index/index&scene=foo=bar&width=500示例 2:生成二维码(接口 B,不带 scene,宽度 a=280px)
https://xxx.xx/index.php?appid={id}&appsecret={secret}&type=b&path=pages/index/index&width=a示例 3:生成二维码(接口 A,带 scene,宽度 300px)
https://xxx.xx/index.php?appid={id}&appsecret={secret}&type=a&path=pages/index/index&scene=foo=bar&width=300示例 4:生成二维码(接口 C,宽度 b=430px)
https://xxx.xx/index.php?appid={id}&appsecret={secret}&type=c&path=pages/index/index&scene=foo=bar&width=b示例 5:查询剩余额度(英文)
https://xxx.xx/index.php?appid={id}&appsecret={secret}&action=query示例 6:查询剩余额度(中文)
https://xxx.xx/index.php?appid={id}&appsecret={secret}&action=query&lg=cn完整 PHP 代码
<?php$lang = isset($_GET['lg']) ? strtolower(trim($_GET['lg'], '"')) : 'en';$lang = ($lang === 'cn') ? 'cn' : 'en';
$text = [ 'en' => [ 'title' => 'WeChat Mini Program QR Code Generator - API Parameters', 'base_params_title' => 'Base Parameters (Required for All Requests)', 'action_params_title' => 'Action: Generate QR Code (action=gen)', 'action_query_title' => 'Action: Query Remaining Quota (action=query)', 'return_fields_title' => 'Return Field Language (optional)', 'response_example_title' => 'Response Example (JSON)', 'example_title' => 'Example Requests', 'example1' => 'Generate QR Code (Interface B, with scene, custom width 500px)', 'example2' => 'Generate QR Code (Interface B, no scene, width a=280px)', 'example3' => 'Generate QR Code (Interface A, with scene, custom width 300px)', 'example4' => 'Generate QR Code (Interface C, width b=430px)', 'example5' => 'Query Remaining Quota (English)', 'example6' => 'Query Remaining Quota (Chinese)', 'interface_a_note' => 'Interface A (wxacode.get) - 100,000 limit, circular QR code', 'interface_b_note' => 'Interface B (wxacode.getUnlimited) - No limit, circular QR code, RECOMMENDED', 'interface_c_note' => 'Interface C (wxacode.createQRCode) - 100,000 limit, square QR code, NOT RECOMMENDED', 'width_note' => 'Width: a=280px, b=430px, c=1280px, or any number 280-1280', 'color_note' => 'line_color format: {"r":0,"g":0,"b":0} (RGB values 0-255)', 'env_note' => 'env_version: release (live) / trial (test) / develop (dev)', 'return_fields' => 'Return fields: code, msg, errcode, data, total, used, remaining, percent_used', 'return_fields_cn' => 'Return fields: 状态码, 消息, 错误码, 数据, 总数, 已用, 剩余, 使用百分比', 'lang_switch_en' => 'English', 'lang_switch_cn' => '中文', 'th_parameter' => 'Parameter', 'th_type' => 'Type', 'th_required' => 'Required', 'th_default' => 'Default', 'th_description' => 'Description', 'required' => 'required', 'optional' => 'optional', 'default' => 'default', 'desc_appid' => 'WeChat Mini Program AppID', 'desc_appsecret' => 'WeChat Mini Program AppSecret', 'desc_action' => 'gen = generate QR code, query = check remaining quota', 'desc_type' => 'a / b / c (lowercase)', 'desc_path' => 'Mini Program page path (without ?), e.g. pages/index/index', 'desc_scene' => 'Custom parameters (max 32 chars). For interface A: appended to path. For interface B: used as scene field. If empty, interface B defaults to 0.', 'desc_width' => 'Width: a=280px, b=430px, c=1280px, or any number 280-1280', 'desc_auto_color' => 'Auto configure line color (interface A/B)', 'desc_line_color' => 'line_color format: {"r":0,"g":0,"b":0} (RGB values 0-255) (interface A/B)', 'desc_is_hyaline' => 'Transparent background (interface A/B). Default: white (false)', 'desc_env_version' => 'env_version: release (live) / trial (test) / develop (dev) (interface B only)', 'desc_lg' => 'en = English field names, cn = Chinese field names', 'desc_action_query' => 'Set action=query to check remaining quota (no type or path needed)', 'width_out_of_range' => 'width must be between 280 and 1280 (or a/b/c)', 'width_invalid' => 'width must be a/b/c or a number between 280 and 1280', 'missing_appid' => 'appid and appsecret are required', 'invalid_action' => 'action must be gen or query', 'missing_type' => 'type (a/b/c) is required for generation mode', 'missing_path' => 'path parameter is required', 'cache_open_fail' => 'Unable to open cache file', 'token_network_fail' => 'Failed to get stable_token', 'token_fetch_fail' => 'Failed to get access_token: ', 'file_lock_fail' => 'Unable to acquire file lock', 'query_fail' => 'Failed to query quota', 'wechat_error' => 'WeChat API error: ', 'num_header_missing' => 'Num-Used header not available', 'missing_page_b' => 'page parameter is missing for interface B', 'qrcode_generate_fail' => 'Failed to generate QR code: ', 'http_request_fail' => 'Request failed, HTTP status: ', 'unknown_error' => 'unknown error', 'example_req1' => '?appid={id}&appsecret={secret}&type=b&path=pages/index/index&scene=foo=bar&width=500', 'example_req2' => '?appid={id}&appsecret={secret}&type=b&path=pages/index/index&width=a', 'example_req3' => '?appid={id}&appsecret={secret}&type=a&path=pages/index/index&scene=foo=bar&width=300', 'example_req4' => '?appid={id}&appsecret={secret}&type=c&path=pages/index/index&scene=foo=bar&width=b', 'example_req5' => '?appid={id}&appsecret={secret}&action=query', 'example_req6' => '?appid={id}&appsecret={secret}&action=query&lg=cn', ], 'cn' => [ 'title' => '微信小程序码生成器 - 参数说明', 'base_params_title' => '基础参数(所有请求必填)', 'action_params_title' => '操作:生成二维码(action=gen)', 'action_query_title' => '操作:查询剩余额度(action=query)', 'return_fields_title' => '返回字段语言(可选)', 'response_example_title' => '返回示例 (JSON)', 'example_title' => '请求示例', 'example1' => '生成二维码(接口 B,带 scene,宽度自定义为 500px)', 'example2' => '生成二维码(接口 B,不带 scene,宽度使用代号 a=280px)', 'example3' => '生成二维码(接口 A,带 scene,宽度自定义为 300px)', 'example4' => '生成二维码(接口 C,宽度使用代号 b=430px)', 'example5' => '查询剩余额度(英文)', 'example6' => '查询剩余额度(中文)', 'interface_a_note' => '接口 A(wxacode.get)- 限制 10 万个,圆形菊花码', 'interface_b_note' => '接口 B(wxacode.getUnlimited)- 无限制,圆形菊花码,推荐使用', 'interface_c_note' => '接口 C(wxacode.createQRCode)- 限制 10 万个,普通方形码,不推荐', 'width_note' => '宽度:a=280px,b=430px,c=1280px,或任意 280-1280 的数字', 'color_note' => 'line_color 格式:{"r":0,"g":0,"b":0}(RGB 值 0-255)', 'env_note' => 'env_version:release(线上)/ trial(体验)/ develop(开发)', 'return_fields' => '返回字段:code, msg, errcode, data, total, used, remaining, percent_used', 'return_fields_cn' => '返回字段:状态码, 消息, 错误码, 数据, 总数, 已用, 剩余, 使用百分比', 'lang_switch_en' => 'English', 'lang_switch_cn' => '中文', 'th_parameter' => '参数名', 'th_type' => '类型', 'th_required' => '必填', 'th_default' => '默认值', 'th_description' => '说明', 'required' => '必填', 'optional' => '可选', 'default' => '默认', 'desc_appid' => '微信小程序 AppID', 'desc_appsecret' => '微信小程序 AppSecret', 'desc_action' => 'gen = 生成二维码,query = 查询剩余额度', 'desc_type' => 'a / b / c(小写)', 'desc_path' => '小程序页面路径(不含 ?),例如 pages/index/index', 'desc_scene' => '自定义参数(最大 32 个字符)。接口 A 拼接到 path;接口 B 作为 scene 字段;若为空,接口 B 默认填 0。', 'desc_width' => '宽度:a=280px,b=430px,c=1280px,或任意 280-1280 的数字', 'desc_auto_color' => '自动配置线条颜色(接口 A/B)', 'desc_line_color' => 'line_color 格式:{"r":0,"g":0,"b":0}(RGB 值 0-255)(接口 A/B)', 'desc_is_hyaline' => '透明背景(接口 A/B)。不传时默认为白色(false)', 'desc_env_version' => 'env_version:release(线上)/ trial(体验)/ develop(开发)(仅接口 B)', 'desc_lg' => 'en = 英文字段名,cn = 中文字段名', 'desc_action_query' => '设置 action=query 查询剩余额度(无需 type 和 path)', 'width_out_of_range' => 'width 必须在 280 到 1280 之间(或使用 a/b/c)', 'width_invalid' => 'width 必须是 a/b/c 或 280~1280 之间的数字', 'missing_appid' => '必须提供 appid 和 appsecret 参数', 'invalid_action' => 'action 必须是 gen 或 query', 'missing_type' => '生成模式必须提供 type(a/b/c 小写)', 'missing_path' => '生成模式必须提供 path 参数', 'cache_open_fail' => '无法打开缓存文件', 'token_network_fail' => '获取 stable_token 网络失败', 'token_fetch_fail' => '获取 access_token 失败:', 'file_lock_fail' => '无法获取文件锁', 'query_fail' => '查询额度接口请求失败', 'wechat_error' => '微信接口错误:', 'num_header_missing' => '未获取到 Num-Used 头,可能接口暂不支持', 'missing_page_b' => '接口 B 缺少 page 参数', 'qrcode_generate_fail' => '生成二维码失败:', 'http_request_fail' => '请求失败,HTTP状态码:', 'unknown_error' => '未知错误', 'example_req1' => '?appid={id}&appsecret={secret}&type=b&path=pages/index/index&scene=foo=bar&width=500', 'example_req2' => '?appid={id}&appsecret={secret}&type=b&path=pages/index/index&width=a', 'example_req3' => '?appid={id}&appsecret={secret}&type=a&path=pages/index/index&scene=foo=bar&width=300', 'example_req4' => '?appid={id}&appsecret={secret}&type=c&path=pages/index/index&scene=foo=bar&width=b', 'example_req5' => '?appid={id}&appsecret={secret}&action=query', 'example_req6' => '?appid={id}&appsecret={secret}&action=query&lg=cn', ]];$T = $text[$lang];
$langMap = [ 'en' => [ 'code' => 'code', 'msg' => 'msg', 'errcode' => 'errcode', 'data' => 'data', 'total' => 'total', 'used' => 'used', 'remaining' => 'remaining', 'percent_used' => 'percent_used', ], 'cn' => [ 'code' => '状态码', 'msg' => '消息', 'errcode' => '错误码', 'data' => '数据', 'total' => '总数', 'used' => '已用', 'remaining' => '剩余', 'percent_used' => '使用百分比', ]];$L = $langMap[$lang];
function getRawPath() { $uri = $_SERVER['REQUEST_URI'] ?? ''; if (preg_match('/[?&]path=([^&]*)/', $uri, $matches)) { return urldecode($matches[1]); } return '';}
$appid = isset($_GET['appid']) ? trim($_GET['appid'], '"') : '';$appsecret = isset($_GET['appsecret']) ? trim($_GET['appsecret'], '"') : '';$type = isset($_GET['type']) ? strtolower(trim($_GET['type'], '"')) : '';$path = getRawPath();if (empty($path)) { $path = isset($_GET['path']) ? trim($_GET['path'], '"') : '';}$scene = isset($_GET['scene']) ? trim($_GET['scene'], '"') : '';$widthParam = isset($_GET['width']) ? trim($_GET['width'], '"') : '';$action = isset($_GET['action']) ? strtolower(trim($_GET['action'], '"')) : 'gen';
$auto_color = isset($_GET['auto_color']) ? filter_var($_GET['auto_color'], FILTER_VALIDATE_BOOLEAN) : false;$line_color = isset($_GET['line_color']) ? trim($_GET['line_color'], '"') : '';$is_hyaline = isset($_GET['is_hyaline']) ? filter_var($_GET['is_hyaline'], FILTER_VALIDATE_BOOLEAN) : false;$env_version = isset($_GET['env_version']) ? trim($_GET['env_version'], '"') : 'release';
$lineColorArray = null;if (!empty($line_color)) { $decoded = json_decode($line_color, true); if (is_array($decoded) && isset($decoded['r']) && isset($decoded['g']) && isset($decoded['b'])) { $lineColorArray = $decoded; }}
$width = 430;if (empty($widthParam)) { $width = 430;} elseif (preg_match('/^(a|b|c)$/i', $widthParam)) { $widthMap = ['a' => 280, 'b' => 430, 'c' => 1280]; $width = $widthMap[strtolower($widthParam)];} elseif (is_numeric($widthParam)) { $width = intval($widthParam); if ($width < 280 || $width > 1280) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['width_out_of_range']], JSON_UNESCAPED_UNICODE)); }} else { die(json_encode([$L['code'] => -1, $L['msg'] => $T['width_invalid']], JSON_UNESCAPED_UNICODE));}
$showHelp = false;if (empty($appid) || empty($appsecret)) { $showHelp = true;} elseif ($action !== 'gen' && $action !== 'query') { $showHelp = true;} elseif ($action === 'gen' && (empty($type) || !in_array($type, ['a', 'b', 'c']))) { $showHelp = true;} elseif ($action === 'gen' && empty($path)) { $showHelp = true;}
if ($showHelp) { header('Content-Type: text/html; charset=utf-8'); echo '<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>' . $T['title'] . '</title><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:900px;margin:30px auto;padding:0 20px;background:#f7f8fa;color:#333}h1{font-size:24px;border-bottom:3px solid #07c160;padding-bottom:12px}h2{font-size:18px;margin:28px 0 12px 0;color:#07c160}table{width:100%;border-collapse:collapse;margin:12px 0 20px 0;font-size:14px;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,0.08)}th{background:#07c160;color:#fff;padding:10px 12px;text-align:left;font-weight:600;white-space:nowrap}td{padding:10px 12px;border-bottom:1px solid #f0f0f0}tr:last-child td{border-bottom:none}.req{color:#e74c3c;font-weight:600}.opt{color:#f39c12;font-weight:600}.note{font-size:13px;color:#666;background:#f0f9f3;padding:12px 16px;border-radius:6px;margin:6px 0 16px 0;border-left:4px solid #07c160}.example{background:#1e1e1e;color:#d4d4d4;padding:12px 16px;border-radius:6px;font-family:"Courier New",monospace;font-size:13px;overflow-x:auto;margin:4px 0 12px 0;white-space:pre-wrap;word-break:break-all}.lang-switch{float:right;font-size:14px;margin-top:6px}.lang-switch a{color:#07c160;text-decoration:none;margin-left:12px;font-weight:500}.lang-switch a:hover{text-decoration:underline}@media(max-width:600px){table{font-size:12px}td,th{padding:6px 8px}.example{font-size:11px;padding:8px 10px}}</style></head><body><div class="lang-switch"><a href="?lg=en">' . $T['lang_switch_en'] . '</a> | <a href="?lg=cn">' . $T['lang_switch_cn'] . '</a></div><h1>' . $T['title'] . '</h1><h2>' . $T['base_params_title'] . '</h2><table><tr><th>' . $T['th_parameter'] . '</th><th>' . $T['th_type'] . '</th><th>' . $T['th_required'] . '</th><th>' . $T['th_default'] . '</th><th>' . $T['th_description'] . '</th></tr><tr><td><code>appid</code></td><td>string</td><td><span class="req">' . $T['required'] . '</span></td><td>—</td><td>' . $T['desc_appid'] . '</td></tr><tr><td><code>appsecret</code></td><td>string</td><td><span class="req">' . $T['required'] . '</span></td><td>—</td><td>' . $T['desc_appsecret'] . '</td></tr><tr><td><code>action</code></td><td>string</td><td><span class="opt">' . $T['optional'] . '</span></td><td><code>gen</code></td><td>' . $T['desc_action'] . '</td></tr></table><h2>' . $T['action_params_title'] . '</h2><table><tr><th>' . $T['th_parameter'] . '</th><th>' . $T['th_type'] . '</th><th>' . $T['th_required'] . '</th><th>' . $T['th_default'] . '</th><th>' . $T['th_description'] . '</th></tr><tr><td><code>type</code></td><td>string</td><td><span class="req">' . $T['required'] . '</span></td><td>—</td><td>' . $T['desc_type'] . '<br><span style="font-size:12px;color:#666;">' . $T['interface_a_note'] . '<br>' . $T['interface_b_note'] . '<br>' . $T['interface_c_note'] . '</span></td></tr><tr><td><code>path</code></td><td>string</td><td><span class="req">' . $T['required'] . '</span></td><td>—</td><td>' . $T['desc_path'] . '</td></tr><tr><td><code>scene</code></td><td>string</td><td><span class="opt">' . $T['optional'] . '</span></td><td>—</td><td>' . $T['desc_scene'] . '</td></tr><tr><td><code>width</code></td><td>string/number</td><td><span class="opt">' . $T['optional'] . '</span></td><td><code>b</code> (430px)</td><td>' . $T['desc_width'] . '</td></tr><tr><td><code>auto_color</code></td><td>boolean</td><td><span class="opt">' . $T['optional'] . '</span></td><td><code>false</code></td><td>' . $T['desc_auto_color'] . '</td></tr><tr><td><code>line_color</code></td><td>JSON</td><td><span class="opt">' . $T['optional'] . '</span></td><td>—</td><td>' . $T['desc_line_color'] . '</td></tr><tr><td><code>is_hyaline</code></td><td>boolean</td><td><span class="opt">' . $T['optional'] . '</span></td><td><code>false</code> (white)</td><td>' . $T['desc_is_hyaline'] . '</td></tr><tr><td><code>env_version</code></td><td>string</td><td><span class="opt">' . $T['optional'] . '</span></td><td><code>release</code></td><td>' . $T['desc_env_version'] . '</td></tr></table><h2>' . $T['action_query_title'] . '</h2><table><tr><th>' . $T['th_parameter'] . '</th><th>' . $T['th_type'] . '</th><th>' . $T['th_required'] . '</th><th>' . $T['th_default'] . '</th><th>' . $T['th_description'] . '</th></tr><tr><td><code>action</code></td><td>string</td><td><span class="opt">' . $T['optional'] . '</span></td><td><code>query</code></td><td>' . $T['desc_action_query'] . '</td></tr></table><h2>' . $T['return_fields_title'] . '</h2><table><tr><th>' . $T['th_parameter'] . '</th><th>' . $T['th_type'] . '</th><th>' . $T['th_required'] . '</th><th>' . $T['th_default'] . '</th><th>' . $T['th_description'] . '</th></tr><tr><td><code>lg</code></td><td>string</td><td><span class="opt">' . $T['optional'] . '</span></td><td><code>en</code></td><td>' . $T['desc_lg'] . '<br><span style="font-size:12px;color:#666;">' . $T['return_fields'] . '<br>' . $T['return_fields_cn'] . '</span></td></tr></table><h2>' . $T['example_title'] . '</h2><div class="note">' . $T['example1'] . '</div><div class="example">' . $T['example_req1'] . '</div><div class="note">' . $T['example2'] . '</div><div class="example">' . $T['example_req2'] . '</div><div class="note">' . $T['example3'] . '</div><div class="example">' . $T['example_req3'] . '</div><div class="note">' . $T['example4'] . '</div><div class="example">' . $T['example_req4'] . '</div><div class="note">' . $T['example5'] . '</div><div class="example">' . $T['example_req5'] . '</div><div class="note">' . $T['example6'] . '</div><div class="example">' . $T['example_req6'] . '</div><h2>' . $T['response_example_title'] . '</h2><div class="example">{"code":0,"data":{"total":100000,"used":5,"remaining":99995,"percent_used":"0.01%"}}</div><div style="margin-top:30px;padding-top:16px;border-top:1px solid #e0e0e0;font-size:13px;color:#999;text-align:center;">WeChat Mini Program QR Code Generator · ' . date('Y-m-d') . '</div></body></html>'; exit;}
if (empty($appid) || empty($appsecret)) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['missing_appid']], JSON_UNESCAPED_UNICODE));}if (!in_array($action, ['gen', 'query'])) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['invalid_action']], JSON_UNESCAPED_UNICODE));}
function getAccessToken($appid, $appsecret) { global $T, $L; $cacheFile = __DIR__ . '/cache/token_' . md5($appid) . '.json'; $cacheDir = dirname($cacheFile); if (!is_dir($cacheDir)) mkdir($cacheDir, 0755, true); $fp = fopen($cacheFile, 'c+'); if (!$fp) die(json_encode([$L['code'] => -1, $L['msg'] => $T['cache_open_fail']], JSON_UNESCAPED_UNICODE)); if (flock($fp, LOCK_EX)) { $data = []; if (filesize($cacheFile) > 0) { $content = fread($fp, filesize($cacheFile)); $data = json_decode($content, true); } if ($data && isset($data['expires']) && $data['expires'] > time()) { flock($fp, LOCK_UN); fclose($fp); return $data['access_token']; } $url = "https://api.weixin.qq.com/cgi-bin/stable_token"; $postData = ['grant_type' => 'client_credential', 'appid' => $appid, 'secret' => $appsecret, 'force_refresh' => false]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode != 200) { flock($fp, LOCK_UN); fclose($fp); die(json_encode([$L['code'] => -1, $L['msg'] => $T['token_network_fail']], JSON_UNESCAPED_UNICODE)); } $json = json_decode($result, true); if (!isset($json['access_token'])) { flock($fp, LOCK_UN); fclose($fp); die(json_encode([$L['code'] => -1, $L['msg'] => $T['token_fetch_fail'] . ($json['errmsg'] ?? $T['unknown_error']), $L['errcode'] => $json['errcode'] ?? 0], JSON_UNESCAPED_UNICODE)); } $cache = ['access_token' => $json['access_token'], 'expires' => time() + 7200 - 60]; ftruncate($fp, 0); rewind($fp); fwrite($fp, json_encode($cache)); fflush($fp); flock($fp, LOCK_UN); fclose($fp); return $json['access_token']; } else { fclose($fp); die(json_encode([$L['code'] => -1, $L['msg'] => $T['file_lock_fail']], JSON_UNESCAPED_UNICODE)); }}
if ($action === 'query') { $access_token = getAccessToken($appid, $appsecret); $queryPath = 'pages/index/index?query=1'; $url = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" . $access_token; $postData = ['path' => $queryPath, 'width' => 280, 'auto_color' => false, 'is_hyaline' => false]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $headers = []; curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$headers) { $len = strlen($header); $header = explode(':', $header, 2); if (count($header) < 2) return $len; $name = strtolower(trim($header[0])); $headers[$name] = trim($header[1]); return $len; }); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode != 200) die(json_encode([$L['code'] => -1, $L['msg'] => $T['query_fail']], JSON_UNESCAPED_UNICODE)); $decoded = json_decode($response, true); if ($decoded && isset($decoded['errcode']) && $decoded['errcode'] != 0) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['wechat_error'] . $decoded['errmsg'], $L['errcode'] => $decoded['errcode']], JSON_UNESCAPED_UNICODE)); } $numUsed = isset($headers['num-used']) ? intval($headers['num-used']) : -1; $total = 100000; if ($numUsed >= 0) { $remaining = $total - $numUsed; $percent = round(($numUsed / $total) * 100, 2); echo json_encode([ $L['code'] => 0, $L['data'] => [ $L['total'] => $total, $L['used'] => $numUsed, $L['remaining'] => $remaining, $L['percent_used'] => $percent . '%' ] ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); } else { echo json_encode([$L['code'] => -1, $L['msg'] => $T['num_header_missing']], JSON_UNESCAPED_UNICODE); } exit;}
if (empty($type) || !in_array($type, ['a', 'b', 'c'])) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['missing_type']], JSON_UNESCAPED_UNICODE));}if (empty($path)) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['missing_path']], JSON_UNESCAPED_UNICODE));}
$access_token = getAccessToken($appid, $appsecret);$url = '';$postData = [];
switch ($type) { case 'a': $finalPath = $path . (!empty($scene) ? '?' . $scene : ''); $url = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" . $access_token; $postData = ['path' => $finalPath, 'width' => $width, 'auto_color' => $auto_color, 'is_hyaline' => $is_hyaline]; if ($lineColorArray) $postData['line_color'] = $lineColorArray; break; case 'b': $finalScene = !empty($scene) ? $scene : '0'; $url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" . $access_token; $postData = ['scene' => $finalScene, 'page' => $path, 'width' => $width, 'auto_color' => $auto_color, 'is_hyaline' => $is_hyaline, 'env_version' => $env_version]; if ($lineColorArray) $postData['line_color'] = $lineColorArray; break; case 'c': $finalPath = $path . (!empty($scene) ? '?' . $scene : ''); $url = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=" . $access_token; $postData = ['path' => $finalPath, 'width' => $width]; break;}
$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);$response = curl_exec($ch);$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);
$decoded = json_decode($response, true);if ($decoded && isset($decoded['errcode']) && $decoded['errcode'] != 0) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['qrcode_generate_fail'] . $decoded['errmsg'], $L['errcode'] => $decoded['errcode']], JSON_UNESCAPED_UNICODE));}if ($httpCode != 200) { die(json_encode([$L['code'] => -1, $L['msg'] => $T['http_request_fail'] . $httpCode], JSON_UNESCAPED_UNICODE));}
header('Content-Type: image/png');echo $response;?>注意事项
- 页面路径必须是小程序线上已发布的页面,否则微信接口会返回错误。
- AppSecret 属于敏感信息,建议通过 URL 传递时使用 HTTPS 协议。
- 接口 a 和 c 共享 10 万个额度,接口 b 无数量限制,推荐优先使用接口 b。
- 切换二维码宽度(width)或透明背景(hyaline)等样式参数不会消耗新额度。
- cache 目录会自动创建,无需手动操作。
- 颜色参数仅支持 RGB 格式 JSON,如 {“r”:255,“g”:0,“b”:0} 表示红色。
- 透明背景仅接口 A/B 支持,且设置 is_hyaline=true 时背景色会被忽略。
- 参数缺失或错误时,会自动展示可视化的参数说明页面(HTML 格式)。
- 接口 B 的 scene 参数可为空,为空时默认传 “0”。
- 接口 B 支持 env_version 参数,可用于生成开发版或体验版二维码。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
微信小程序二维码生成器 PHP 版
https://blog.shenyouchu.cn/posts/wechat-qrcode/ 相关文章 智能推荐
1
每月仅 0.8 元服务器?
小程序 0.8元/月 IPv6 VPS + 小皮面板 + Cloudflare CDN 建站教程
2
电脑如何开机自动开启热点
电脑 电脑如何开机自动开启热点不显示黑窗口
3
开学了 已老实!
标签 开学了 已老实!
4
一分钟速通 FRP 内网穿透
内网穿透 ChmlFrp教程
5
纯IPV6服务器搭建网站教程
IPV6 每年仅36?超详细!纯IPV6服务器搭建网站教程,低成本,零代码,手把手教程。
随机文章 随机推荐