Nodejs实现qq空间[神奇图片]的制作原理

Nodejs实现qq空间[神奇图片]的制作原理

相信很多人都看到过qq空间转发的某条日志中有张神奇图片 会显示你的头像,个人qq等信息。很神奇的样子。我对这个有点兴趣,再加上我最近想学node,于是前天晚上开始从nodejs.org的主页hello,word开始学起。用了半天写出了这个简单的神奇图片 制作服务器。

首先是名词解释,允许我用度娘的链接HTTP_REFFER

通过referer的名词解释可以知道访问外部图片的时候会带上此次浏览的信息,那我们就去看看qq空间的referer信息有哪些?

我在这里用的IE的开发者工具分析,大多数浏览器都有这个功能。

我个人测试qq号的REFERER信息

从这幅图中的REFERER可以看到我个人的qq号。于是我就想把这个图片链接到我个人图片服务器不就行了吗。

http.createServer(function (request, response) {
            fs.readFile('default.png', 'binary', function (err, file) {
            response.writeHead(200, {'Content-Type': 'image/png'});
            response.write(file, 'binary');
            response.end();
        });
}).listen(8080, '127.0.0.1');

这是最简单的读取本地图片并显示的服务器代码。

下面开始增加对refer的判断。我通过http检测工具知道,只有在qq空间的个人中心才能获取到此人的qq号,而在其他位置,如:日志正文,手机qq等页面都是没有此referer的,所以此神奇图片只能在个人中心浏览之后,再加上缓存才能在pc端的其他页面看到。

代码如下:

var http = require('http');
var url = require('url');
var fs = require('fs');

http.createServer(function (request, response) {

// 如果没有referer就算显示默认图片 if (!request.headers.referer) { return showDefaultPng(); }

// referer不是从个人中心过来的也显示默认图片 var aQQ = url.parse(request.headers.referer).pathname.match(/\d+/); if(aQQ===null) { return showDefaultPng(); }

// 从腾讯的接口获取qq号对应的个人信息
http.get('http://base.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=' + aQQ[0], function (res) {
    var infoBuffer = [];
    res.on('data', function (data) {
        infoBuffer.push(data);
    });
    res.on('end', function () {
        var sInfo = Buffer.concat(infoBuffer);
        var aInfo = sInfo.toString().replace(/"/g, "").match(/\[(.+)\]/);
        if (!aInfo) {
            // 如果不能获取到个人信息,依然显示默认图片
            return showDefaultPng();
        }

        else {
            // 如果能正确获取信息,就需要从qq图片服务器拉取头像
            var a = aInfo[1].split(',')[0];
            var req = http.request({'hostname': url.parse(a).hostname, 'method': 'get', 'port': '80', 'path': url.parse(a).pathname}, function (res) {
                var data = [];
                res.on('data', function (chunk) {
                    data.push(chunk);
                });
                res.on('end', function () {
                    var buffer = Buffer.concat(data);
                    // 增加缓存,并发送出去
                    response.writeHead(200, {'Content-Type': 'image/png',
                    'Cache-Control': 'public,max-age=31536000'
                    });
                    response.write(buffer, 'binary');
                    response.end();
                });
            });
            req.end();
        }
    });
});

function showDefaultPng() {
    fs.readFile('default.png', 'binary', function (err, file) {
        response.writeHead(200, {'Content-Type': 'image/png'});
        response.write(file, 'binary');
        response.end();
    });
}

}).listen(8080, ‘127.0.0.1’);

console.log(‘Server running at http://127.0.0.1:8080/’);

http://base.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=你的qq号 这个链接可以查询到你的qq的基本信息。

ps: 我有两年的JS经验,两年的JAVA经验,搭过express和mongo的个人博客经验,所以也不能说是完全的node新手。


2 回复

Node.js 实现 QQ 空间“神奇图片”的制作原理

相信很多人都看到过 QQ 空间中转发的日志里有一张神奇图片,这张图片会显示你的头像、个人 QQ 等信息。这个功能看起来非常神奇。我对这个功能很感兴趣,加上最近想学习 Node.js,于是我在半天内编写了一个简单的神奇图片生成服务器。

名词解释

首先,我们来了解一下 HTTP_REFERER 这个名词。简单来说,当浏览器请求一个外部资源时,它会带上这次请求的一些信息,包括来源地址(即 HTTP_REFERER)。我们可以利用这个特性来识别用户是从哪个页面跳转过来的。

分析 QQ 空间的 REFERER 信息

为了了解 QQ 空间的 REFERER 信息,我使用了 IE 的开发者工具进行分析。大多数现代浏览器都提供了类似的工具。以下是我分析的结果:

我的 QQ 号的 REFERER 信息

从上图中可以看到,我的 QQ 号码作为 REFERER 信息的一部分被传递。因此,如果我们要实现类似的功能,只需要将图片链接到我们的图片服务器即可。

最简单的实现

首先,我们来实现一个最简单的读取本地图片并显示的服务器代码:

const http = require('http');
const fs = require('fs');

http.createServer((request, response) => {
    fs.readFile('default.png', 'binary', (err, file) => {
        response.writeHead(200, {'Content-Type': 'image/png'});
        response.write(file, 'binary');
        response.end();
    });
}).listen(8080, '127.0.0.1');

console.log('Server running at http://127.0.0.1:8080/');

这段代码创建了一个 HTTP 服务器,监听 8080 端口,并读取 default.png 文件返回给客户端。

增加对 REFERER 的判断

接下来,我们需要增加对 REFERER 的判断逻辑。只有在 QQ 空间的个人中心页面访问时,才会获取用户的 QQ 号码,并根据 QQ 号码从腾讯的接口获取用户信息,再从 QQ 图片服务器拉取头像。

const http = require('http');
const url = require('url');
const fs = require('fs');

http.createServer((request, response) => {
    // 如果没有 REFERER 信息,直接显示默认图片
    if (!request.headers.referer) {
        return showDefaultPng();
    }

    // 解析 REFERER 中的 QQ 号码
    const aQQ = url.parse(request.headers.referer).pathname.match(/\d+/);
    if (!aQQ) {
        return showDefaultPng();
    }

    // 从腾讯的接口获取 QQ 号码对应的个人信息
    http.get(`http://base.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=${aQQ[0]}`, (res) => {
        let infoBuffer = [];
        res.on('data', (data) => {
            infoBuffer.push(data);
        });
        res.on('end', () => {
            const sInfo = Buffer.concat(infoBuffer);
            const aInfo = sInfo.toString().replace(/"/g, "").match(/\[(.+)\]/);

            if (!aInfo) {
                // 如果无法获取到个人信息,依然显示默认图片
                return showDefaultPng();
            }

            // 获取头像 URL 并读取头像
            const a = aInfo[1].split(',')[0];
            http.get(a, (res) => {
                let data = [];
                res.on('data', (chunk) => {
                    data.push(chunk);
                });
                res.on('end', () => {
                    const buffer = Buffer.concat(data);
                    // 设置缓存并发送图片
                    response.writeHead(200, {
                        'Content-Type': 'image/png',
                        'Cache-Control': 'public,max-age=31536000'
                    });
                    response.write(buffer, 'binary');
                    response.end();
                });
            });
        });
    });

    function showDefaultPng() {
        fs.readFile('default.png', 'binary', (err, file) => {
            response.writeHead(200, {'Content-Type': 'image/png'});
            response.write(file, 'binary');
            response.end();
        });
    }
}).listen(8080, '127.0.0.1');

console.log('Server running at http://127.0.0.1:8080/');

这段代码增加了对 REFERER 的判断逻辑。只有在 QQ 空间的个人中心页面访问时,才会从腾讯的接口获取用户信息,并根据 QQ 号码从 QQ 图片服务器拉取头像。如果无法获取到相关信息,则显示默认图片。

总结

通过以上代码,我们实现了 QQ 空间的神奇图片效果。该效果利用了 HTTP_REFERER 特性来识别用户来源,并根据 QQ 号码从腾讯的接口获取用户信息,从而实现动态展示用户头像等功能。


要在Node.js中实现一个模拟QQ空间“神奇图片”的服务,我们需要解析请求中的Referer头来判断是否是从QQ空间的个人中心页面来的请求。如果是,则根据Referer中的信息(例如用户的QQ号)从腾讯的接口获取用户头像,否则返回默认图片。

以下是实现这一功能的代码示例:

const http = require('http');
const url = require('url');
const fs = require('fs');

http.createServer((req, res) => {
    // 如果没有Referer信息或者Referer不是来自QQ空间个人中心页面,则显示默认图片
    if (!req.headers.referer || !/space\.qq\.com/.test(req.headers.referer)) {
        return showDefaultPng(res);
    }

    const match = req.headers.referer.match(/uin=(\d+)/);
    if (!match) {
        return showDefaultPng(res);
    }

    const qqNumber = match[1];

    // 从腾讯接口获取用户的头像URL
    http.get(`http://base.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=${qqNumber}`, (portraitRes) => {
        let data = '';
        portraitRes.on('data', (chunk) => {
            data += chunk;
        });
        portraitRes.on('end', () => {
            const portraitUrl = data.match(/"(\S+)"/)[1];
            if (!portraitUrl) {
                return showDefaultPng(res);
            }

            // 请求用户的头像
            http.get(portraitUrl, (avatarRes) => {
                res.writeHead(200, {'Content-Type': 'image/png'});
                avatarRes.pipe(res);
            }).on('error', () => {
                showDefaultPng(res);
            });
        });
    }).on('error', () => {
        showDefaultPng(res);
    });

    function showDefaultPng(res) {
        fs.readFile('default.png', 'binary', (err, file) => {
            if (err) {
                res.writeHead(500);
                res.end('Internal Server Error');
                return;
            }
            res.writeHead(200, {'Content-Type': 'image/png'});
            res.write(file, 'binary');
            res.end();
        });
    }
}).listen(8080, '127.0.0.1');

console.log('Server running at http://127.0.0.1:8080/');

在这个例子中:

  1. 首先检查Referer是否存在以及是否来自QQ空间个人中心页面。
  2. 如果是,从腾讯的接口获取用户头像的URL。
  3. 请求该URL获取头像并返回给客户端。
  4. 如果无法获取头像信息,则返回默认图片。

请注意,这个示例中的代码未考虑安全性问题,实际应用时需要增加相应的安全措施。

回到顶部