微信小程序二维码生成器 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)

接口说明#

接口代号官方接口名数量限制二维码样式适用场景
awxacode.get10 万(与 c 共享)圆形小程序码(菊花码)码数量较少的业务
bwxacode.getUnlimited无限制圆形小程序码(菊花码)码数量极多的业务(推荐)
cwxacode.createQRCode10 万(与 a 共享)普通方形二维码不推荐使用

调用方式#

1. 生成二维码#

https://xxx.xx/index.php?appid={id}&appsecret={secret}&type=b&path=pages/index/index&scene=foo=bar&width=500&lg=en

2. 查询剩余额度#

https://xxx.xx/index.php?appid={id}&appsecret={secret}&action=query&lg=cn

参数说明#

基础参数(所有请求必填)#

参数名类型必填默认值说明
appidstring必填微信小程序 AppID
appsecretstring必填微信小程序 AppSecret
actionstring可选gengen = 生成二维码,query = 查询剩余额度

生成二维码参数(action=gen)#

参数名类型必填默认值说明
typestring必填接口类型:a / b / c(小写)
• a:wxacode.get,10万限制,圆形码
• b:wxacode.getUnlimited,无限制,圆形码(推荐)
• c:wxacode.createQRCode,10万限制,方形码(不推荐)
pathstring必填小程序页面路径(不含 ?),例如 pages/index/index
scenestring可选自定义参数(最大 32 个字符)。接口 A/C 拼接到 path;接口 B 作为 scene 字段;若为空,接口 B 默认填 0
widthstring/number可选b (430px)二维码宽度:a=280px,b=430px,c=1280px,或任意 280-1280 的数字
auto_colorboolean可选false是否自动配置线条颜色(接口 A/B)
line_colorJSON可选线条颜色,格式:{“r”:0,“g”:0,“b”:0},RGB 值 0-255(接口 A/B)
is_hyalineboolean可选false是否透明背景(接口 A/B)。不传时默认为白色背景
env_versionstring可选release环境版本:release(线上)/ trial(体验)/ develop(开发),仅接口 B 支持
lgstring可选en返回字段语言:en = 英文,cn = 中文

查询剩余额度参数(action=query)#

参数名类型必填默认值说明
actionstring可选query设置为 query 查询剩余额度(无需 type 和 path)
lgstring可选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;
?>

注意事项#

  1. 页面路径必须是小程序线上已发布的页面,否则微信接口会返回错误。
  2. AppSecret 属于敏感信息,建议通过 URL 传递时使用 HTTPS 协议。
  3. 接口 a 和 c 共享 10 万个额度,接口 b 无数量限制,推荐优先使用接口 b。
  4. 切换二维码宽度(width)或透明背景(hyaline)等样式参数不会消耗新额度。
  5. cache 目录会自动创建,无需手动操作。
  6. 颜色参数仅支持 RGB 格式 JSON,如 {“r”:255,“g”:0,“b”:0} 表示红色。
  7. 透明背景仅接口 A/B 支持,且设置 is_hyaline=true 时背景色会被忽略。
  8. 参数缺失或错误时,会自动展示可视化的参数说明页面(HTML 格式)。
  9. 接口 B 的 scene 参数可为空,为空时默认传 “0”。
  10. 接口 B 支持 env_version 参数,可用于生成开发版或体验版二维码。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
微信小程序二维码生成器 PHP 版
https://blog.shenyouchu.cn/posts/wechat-qrcode/
作者
沈幼楚
发布于
2026-08-28
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Shen youchu
Hello, I'm Shen youchu.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
18
分类
14
标签
20
总字数
11,704
运行时长
0
最后活动
0 天前

文章目录