解决 QQ 老接口中文昵称乱码:使用 GB18030 转 UTF-8
调用 QQ 老接口时中文昵称出现乱码,通常是自动编码检测误判所致。本文介绍如何在 PHP 中使用 mb_convert_encoding,将 GB18030 编码稳定转换为 UTF-8,并说明转换时的注意事项。
最近在使用QQ空间头像接口获取用户信息时,遇到了一个比较典型的中文乱码问题
接口地址类似:
https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=22741441
英文昵称返回正常:
portraitCallBack({"756048855":["http://qlogo4.store.qq.com/qzone/756048855/756048855/100",7323,-1,0,0,0,"Forever",0]})
但中文昵称会变成乱码:
portraitCallBack({"22741441":["http://qlogo2.store.qq.com/qzone/22741441/22741441/100",1982,-1,0,0,0,"����",0]})
问题原因
一开始很容易以为这是PHP输出编码问题,于是尝试:
mb_convert_encoding($response, 'UTF-8', 'GBK');
但如果接口原始返回里已经是 ���� 或 �,后端再转码也无法恢复
关键点在于:普通GET请求时,QQ接口返回的中文昵称已经在服务端或网关层被错误转换了。此时拿到的不是GBK原始字节,而是已经损坏的替换字符。
后来发现,只要请求时加上:
Content-Type: multipart/form-data
并且让GET请求携带一个非空body,就可以触发QQ接口返回GBK/GB18030原始中文字节
也就是说,真正起作用的不是PHP本身,而是这个请求特征改变了QQ接口的服务端处理分支:
GET + multipart/form-data + 非空body
拿到GBK原始字节后,再用PHP转成UTF-8,中文昵称就正常了
示例一:GuzzleHTTP 版本
安装GuzzleHTTP
composer require guzzlehttp/guzzle
完整代码:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
header('Content-Type: application/json; charset=utf-8');
$qq = $_GET['qq'] ?? '756048855';
try {
if (!preg_match('/^[1-9][0-9]{4,11}$/', $qq)) {
throw new RuntimeException('Invalid QQ number', 400);
}
$result = [
'code' => 200,
'qq' => $qq,
'data' => getQqUserInfo($qq),
'time' => date('Y-m-d H:i:s'),
];
} catch (Throwable $e) {
$status = ($e->getCode() >= 400 && $e->getCode() < 600) ? $e->getCode() : 500;
http_response_code($status);
$result = [
'code' => $status,
'message' => $e->getMessage(),
'time' => date('Y-m-d H:i:s'),
];
}
echo json_encode(
$result,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT
);
/**
* 获取QQ用户基础信息
* @param string $qq QQ号码
* @return array
* @throws JsonException
* @throws RuntimeException
*/
function getQqUserInfo(string $qq): array
{
$response = fetchQqPortrait($qq);
$response = mb_convert_encoding($response, 'UTF-8', 'GB18030');
$json = unwrapJsonp($response);
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$item = $data[$qq] ?? [];
return [
'name' => $item[6] ?? '',
'mail' => "{$qq}@qq.com",
'avatar' => isset($item[0]) ? str_replace('http://', 'https://', $item[0]) : '',
'qzone' => "https://user.qzone.qq.com/{$qq}",
'imgurl' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=40",
'imgurl1' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=100",
'imgurl2' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=140",
'imgurl3' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=640",
];
}
/**
* 使用GuzzleHTTP请求QQ空间头像接口
* @param string $qq QQ号码
* @return string
*
* @throws RuntimeException
*/
function fetchQqPortrait(string $qq): string
{
$client = new Client([
'timeout' => 10,
'connect_timeout' => 5,
'http_errors' => false,
'allow_redirects' => ['max' => 3],
'headers' => [
'User-Agent' => 'Mozilla/5.0',
'Referer' => 'https://user.qzone.qq.com/',
],
]);
$url = "https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins={$qq}";
$response = $client->request('GET', $url, [
'version' => '1.1',
RequestOptions::BODY => 'x',
RequestOptions::HEADERS => [
'Content-Type' => 'multipart/form-data; boundary=----qq-portrait-boundary',
],
]);
$statusCode = $response->getStatusCode();
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException("Qzone API returned HTTP {$statusCode}", 502);
}
return (string) $response->getBody();
}
/**
* 去除QQ接口返回值外层的JSONP回调函数
* @param string $response QQ接口返回的JSONP字符串
* @return string
*
* @throws RuntimeException
*/
function unwrapJsonp(string $response): string
{
if (!preg_match('/^[^(]+\((.*)\)\s*;?$/s', trim($response), $matches)) {
throw new RuntimeException('Invalid JSONP response', 502);
}
return $matches[1];
}
示例二:PHP cURL 版本
如果项目里不想引入GuzzleHTTP,也可以直接使用PHP原生cURL
完整代码:
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
$qq = $_GET['qq'] ?? '756048855';
try {
if (!preg_match('/^[1-9][0-9]{4,11}$/', $qq)) {
throw new RuntimeException('Invalid QQ number', 400);
}
$result = [
'code' => 200,
'qq' => $qq,
'data' => getQqUserInfoByCurl($qq),
'time' => date('Y-m-d H:i:s'),
];
} catch (Throwable $e) {
$status = ($e->getCode() >= 400 && $e->getCode() < 600) ? $e->getCode() : 500;
http_response_code($status);
$result = [
'code' => $status,
'message' => $e->getMessage(),
'time' => date('Y-m-d H:i:s'),
];
}
echo json_encode(
$result,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT
);
/**
* 通过PHP cURL获取QQ用户基础信息
* @param string $qq QQ号码
* @return array
* @throws JsonException
* @throws RuntimeException
*/
function getQqUserInfoByCurl(string $qq): array
{
$response = fetchQqPortraitByCurl($qq);
$response = mb_convert_encoding($response, 'UTF-8', 'GB18030');
$json = unwrapJsonp($response);
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$item = $data[$qq] ?? [];
return [
'name' => $item[6] ?? '',
'mail' => "{$qq}@qq.com",
'avatar' => isset($item[0]) ? str_replace('http://', 'https://', $item[0]) : '',
'qzone' => "https://user.qzone.qq.com/{$qq}",
'imgurl' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=40",
'imgurl1' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=100",
'imgurl2' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=140",
'imgurl3' => "https://q1.qlogo.cn/g?b=qq&nk={$qq}&s=640",
];
}
/**
* 使用PHP cURL请求QQ空间头像接口
* @param string $qq QQ号码
* @return string
*
* @throws RuntimeException
*/
function fetchQqPortraitByCurl(string $qq): string
{
$url = "https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins={$qq}";
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_CONNECTTIMEOUT => 5,
在调用 QQ 老接口时,如果返回结果中包含中文昵称,直接输出可能会出现乱码。常见原因是程序依赖自动编码检测,但这种方式并不稳定。
接口响应通常会同时包含英文、URL、数字以及少量中文字符。当中文内容占比较低时,编码检测函数很容易产生误判,进而使用错误的字符集完成转换。
## 解决方法
当接口已成功触发正确的响应分支时,可以明确按照 **GBK/GB18030** 处理中文昵称,再统一转换为 UTF-8:
```php
$response = mb_convert_encoding($response, 'UTF-8', 'GB18030');
相比依赖自动检测,显式指定源编码更加稳定。GB18030 兼容 GBK,并覆盖更多中文字符,因此更适合处理这类旧接口返回的数据。
使用建议
- 先确认接口请求成功,并检查返回内容是否符合预期。
- 仅对确认采用 GBK 或 GB18030 编码的响应执行转换。
- 转换后再进行 JSON 解析、正则提取或页面输出。
- 如果接口本身已经返回 UTF-8,则不要重复转换,以免产生新的乱码。
综上,对于 QQ 老接口中的中文昵称乱码问题,建议不要依赖自动编码检测,而是根据接口实际编码显式使用 GB18030 转换为 UTF-8。
评论0





暂无评论,期待您的发言...