解决 QQ 老接口中文昵称乱码:使用 GB18030 转 UTF-8

Admin
96阅读
0评论
0点赞

调用 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,并覆盖更多中文字符,因此更适合处理这类旧接口返回的数据。

使用建议

  1. 先确认接口请求成功,并检查返回内容是否符合预期。
  2. 仅对确认采用 GBK 或 GB18030 编码的响应执行转换。
  3. 转换后再进行 JSON 解析、正则提取或页面输出。
  4. 如果接口本身已经返回 UTF-8,则不要重复转换,以免产生新的乱码。

综上,对于 QQ 老接口中的中文昵称乱码问题,建议不要依赖自动编码检测,而是根据接口实际编码显式使用 GB18030 转换为 UTF-8。

上一篇Redis 缓存击穿的三种解法下一篇基于Webman的多平台广告管理系统
评论0

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

发表评论