qq悟空

qq悟空 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

qq悟空 回答了问题 · 2017-09-12

mongo建立replicaSet时为何无法收到其他节点回应?

刚刚我也遇到这个问题,报这个错误
{

"ok" : 0,
"errmsg" : "Quorum check failed because not enough voting nodes responded; required 2 but only the following 1 voting nodes responded: xx.xxx.xxx.xx:xxxxx; the following nodes did not respond affirmatively: yy.yyy.yyy.yy:yyyyy failed with Received heartbeat from member with the same member ID as ourself: 0, zz.zzz.zzz.zzz:zzzzz failed with Received heartbeat from member with the same member ID as ourself: 0",
"code" : 74

}

我问题出现的原因的是:在2个实例中的keyfile文件,不一样

解决方法是:使用其中一个keyfile,替换另一个keyfile文件,即可解决

关注 2 回答 1

qq悟空 赞了文章 · 2016-11-03

mongodb中的aggregate(聚合查询)

什么是aggregate

aggregate类似于pipe.拆分结果然后对结果进行分析求值然后再返回新结果..

文档

MongoDB聚合
官方API
MongoDB aggregate 运用篇 个人总结 - fycayy
案例一
案例二
案例三

案例

那么aggregate有什么作用呢?举个例子 testName文档中有如下几个集合

<!-- more -->

//集合一
{
    _id:1,
    list:[
        {name:"x",age:11,sex:"boy"},
        {name:"y",age:12,sex:"girl"},
        {name:"z",age:13,sex:"boy"},
        {name:"n",age:14,sex:"boy"},
    ]
}
//集合二
{
    _id:2,
    list:[
        {name:"q",age:15,sex:"boy"},
        {name:"w",age:16,sex:"girl"},
        {name:"e",age:17,sex:"girl"},
        {name:"r",age:18,sex:"boy"},
    ]
}
//集合三
{
     _id:3,
    list:[
        {name:"a",age:19,sex:"girl"},
        {name:"s",age:20,sex:"girl"},
        {name:"d",age:21,sex:"girl"},
        {name:"f",age:22,sex:"boy"},
    ]
}

我们想筛选出list中sex为boy的集合.理所当然我们会这样写

db.testName.find({"list":{"$elemMatch":{"sex":"boy"}}})

当然这样写是能拿到结果的.拿到的是什么结果呢?结果会返回所有的sex=boy的集合,并且sex=girl的数据也包含在内.但是我们只想要sex=boy的数据.这时候就可以用聚合查询了

聚合查询

db.testName.aggregate(
    {"$unwind":"$list"},
    {"$match":{"list.sex":{"$eq":"boy"}}},     
    {
        "$group":{
            "_id":"$_id",
            "results":{
                "$push"{"name":"$list.name","age":"$list.age","sex":"$list.sex"}
            }
        }
    }   
)

okay,不要怕.我们慢慢来分析 首先从$unwind来分析.$unwind会拆分数组元素拆分成一个一个集合那么这条参数执行后会得到什么结果呢?

{
    _id:1,
    list:{name:"x",age:11,sex:"boy"},
}
{
    _id:1,
    list:{name:"y",age:12,sex:"girl"},
}
{
    _id:1,
    list:{name:"z",age:13,sex:"boy"},
}
...

会拆分成一条条的临时集合,这个临时集合的名称就是$list,这就相当于第一道工艺程序把数组拆分开.接下来交给第二道程序{"$match":{"list.sex":{"$eq":"boy"}}} 这条语句作用是啥呢?想必大家也知道了.就是对临时的集合进行一个查询,类似于db.$list.find(),那么查询到的肯定就是sex=boy的数据了 现在返回的数据就是 不会有sex=girl的存在了

{
    _id:1,
    list:{name:"x",age:11,sex:"boy"},
}
{
    _id:1,
    list:{name:"z",age:13,sex:"boy"},
}
....

接下来就是要把这些组合在一起,也就是第三道工艺程序

{
    "$group":{
        "_id":"$_id",
        "results":{
            "$push"{"name":"$list.name","age":"$list.age","sex":"$list.sex"}
        }
    }
} 

其实也很好理解,$group就是组合的意思,把这些零散的数据以我们想要的形式组合起来.这个$_id指的就是临时集合中的_id.那么$list就是临时集合中的list,以_id为首把这些零散的list给组合成一个新的数组就是聚合.$push是添加一个对象,当然也有更简单的方式

{
    "$group":{
        "_id":"$_id",
        "results":{
            "$push":"$list"
        }
    }
} 

这样就直接把整个list对象给push到一个数组中.那么这样查询最终返回的结果就是我们想要的结果了

查看原文

赞 2 收藏 6 评论 0

qq悟空 赞了文章 · 2016-11-03

「译」 MapReduce in MongoDB

在这篇文章里面,我们会演示如何在 MongoDB 中使用 MapReduce 操作。
我们会用 dummy-json 这个包来生成一些虚假的数据,然后用 Mongojs

如果想要快速看到结果,可以到 这里 里看看。

什么是 MongoDB ?

MongoDB 是一个 NoSQL 数据库,不像 MySQL 、MSSQL 和 Oracle DB 那样,MongoDB 使用集合(collections) 来代替表(tables)。同时,它用集合中的文档(documents)来代替表中的行(rows)。还有最好的一点是,所有文档都保存成 JSON 格式!你可以到这里学更多关于 MongoDB 的知识。

你可以从 这里 下载安装 MongoDB。

如果以前没用过 MongoDB,那么你可以记住下面这些命令:

CommandResult
mongod启动 MongoDB 服务
mongo进入 MongoDB Shell
show dbs显示所有数据库列表
use <db name>进入指定的数据库
show collections进入数据库之后,显示该数据库中所有的集合
db.collectionName.find()显示该集合中所有文档
db.collectionName.findOne()显示该集合中第一个文档
db.collectionName.find().pretty()显示漂亮的 JSON 格式
db.collectionName.insert({key: value})插入一条新的记录
db.collectionName.update({ condition: value}, {$set: {key: value}}, {upsert: true})会更新指定的文档,设置指定的值。如果 upserttrue,当没有找到匹配的文档时,会创建一条新的记录
db.collectionName.remove({})移除集合中的所有文档
db.collectionName.remove({key: value})移除集合中匹配到的文档

什么是 MapReduce ?

弄清楚 MapReduce 是如何运作的是非常重要的,如果对 MapReduce 过程不了解的话,你在运行 MapReduce 时很可能得不到你想要的结果。

从 mongodb.org 上的解析:

Map-reduce 是一种数据处理范例,用于将大量的数据变成有用的聚合结果。 对于 map-reduce 操作,MongoDB 提供了 mapReduce 的数据库命令。

在这非常简单的术语里面,mapReduce 命令接受两个基本的输入:mapper 函数和 reducer 函数。

Mapper 是一个匹配数据的过程,它会在集合中查询我们想要处理的字段,然后根据我们指定的 key 去分组,再把这些 key-value 对交给 reducer 函数,由它来处理这些匹配到的数据。

我们来看看下面这些数据:

[
  { name: foo, price: 9 },
  { name: foo, price: 12 },
  { name: bar, price: 8 },
  { name: baz, price: 3 },
  { name: baz, price: 5 }
]

我们想要计算出相同名字下的所需要的价钱。我们将会用这个数据通过 Mapper 和 Reducer 去获得结果。

当我们让 Mapper 去处理上面的数据时,会生成如下的结果:

KeyValue
foo[9,12]
bar[8]
baz[3,5]

看到了吗?它用相同的 key 去分组数据。在我们的例子中,是用 name 分组。这些结果会发送到 Reducer 中。

现在,在 reducer 中,我们会得到上面表格中的第一行数据,然后迭代这些数据然后把它们加起来,这就是第一行数据的总和。然后 reducer 会对第二行数据做同样的事情,直到所有行被处理完。

最终的输出结果如下:

NameTotal
foo21
bar8
baz8

现在你明白为什么 Mapper 会叫 Mapper 了吧 ! (因为它会创建一份数据的映射)
也明白了为什么 Reducer 会叫 Reducer 了吧 ! (因为它会把 Mapper 生成的数据归纳成一个简单的形式)

如果你运行一些例子,你就会知道它是怎么工作的拉。你也可以从官方文档 中了解更多细节。

创建一个项目

正如上文所说,我们可以在 mongo shell 中直接查询和看到输出结果。但是,为了让教程更加丰富,我们会构建一个 Nodejs 项目,在里面运行我们之前的任务。

Mongojs

我们会用 mongojs 去实现我们的 MapReduce。你可以用同样的代码跑在 mongo shell 里面,会看到同样的结果。

Dummy-json

我们会用 dummy-json 去创建一些虚假的数据。你可以在 这里 找到更多的信息。然后我们会在这些虚假数据上面运行 MapReduce 命令,生成一些有意义的结果。

我们开始吧!

首先,你要安装 Nodejs,你可以看看 这里。然后你要创建一个叫 mongoDBMapReduce 的目录。我们将会创建 package.json 文件来保存项目的详细信息。

运行 npm init 然后填入你喜欢的东西,创建完 package.json 后,我们要添加项目的依赖。
运行 npm i mongojs dummy-json --save-dev ,然后等几分钟之后,我们项目的依赖就安装好了。

生成虚假数据

下一步,我们要用 dummy-json 模块来生成虚假数据。
在项目的根目录创建一个名叫 dataGen.js 的文件,我们会把数据生成的逻辑保存到一个独立的文件里面。如果以后需要添加更多的数据,你可以运行这个文件。

把下面的内容复制到 dataGen.js 里面:

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData']);
var fs = require('fs');
var dummyjson = require('dummy-json');
 
var helpers = {
  gender: function() {
    return ""+ Math.random() > 0.5 ? 'male' : 'female';
  },
  dob : function() {
    var start = new Date(1900, 0, 1),
        end = new Date();
        return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
    },
  hobbies : function () {
    var hobbysList = []; 
    hobbysList[0] = [];
    hobbysList[0][0] = ["Acrobatics", "Meditation", "Music"];
    hobbysList[0][1] = ["Acrobatics", "Photography", "Papier-Mache"];
    hobbysList[0][2] = [ "Papier-Mache"];
    return hobbysList[0][Math.floor(Math.random() * hobbysList[0].length)];
  }
};
 
console.log("Begin Parsing >>");
 
var template = fs.readFileSync('schema.hbs', {encoding: 'utf8'});
var result = dummyjson.parse(template, {helpers: helpers});
 
console.log("Begin Database Insert >>");
 
db.sourceData.remove(function (argument) {
    console.log("DB Cleanup Completd");
});
 
db.sourceData.insert(JSON.parse(result), function (err, docs) {
    console.log("DB Insert Completed");
});

第1-4行,我们引入了所有依赖。
第2行,我们创建了一个叫 mapReduceDB 的数据库。在数据库里面,创建了一个叫 sourceData 的集合。

第6-23行,是 Handlebar 的 helper。你可以到 dummy-json 中了解更多信息。

第27-28行,我们读取了 schema.hbs 文件 (我们接着会创建这个文件),然后把它解析成 JSON。

第32行,在插入新数据之前,我们要先把旧数据清除掉。如果你想保留旧数据,把这部分注释掉就好了。

第36行,把生成的数据插入数据库。

接着,我们要在项目根目录创建一个叫 schema.hbs 的文件。这里面会包括 JSON 文档的结构。把下面的内容复制到文件里面:

[
    {{#repeat 9999}}
    {
      "id": {{index}},
      "name": "{{firstName}} {{lastName}}",
      "email": "{{email}}",
      "work": "{{company}}",
      "dob" : "{{dob}}",
      "age": {{number 1 99}},
      "gender" : "{{gender}}",
      "salary" : {{number 999 99999}},
      "hobbies" : "{{hobbies}}"
    }
    {{/repeat}}
]

注意 第2行,我们会生成 9999 个文档。

打开一个新的终端,运行 mongod,启动 MongoDB 服务。然后回到原来的终端,运行 node dataGen.js

如果一切正常,会显示如下结果:

$ node dataGen.js
Begin Parsing >>
Begin Database Insert >>
DB Cleanup Completed
DB Insert Completed

然后按 ctrl + c 杀掉 Node 程序。要验证是否插入成功,我们可以打开一个新的终端,运行 mongo 命令进入 mongo shell。

> use mapReduceDB
> db.sourceData.findOne()
{
    "id": 0,
    "name": "Leanne Flinn",
    "email": "leanne.flinn@unilogic.com",
    "work": "Unilogic",
    "dob": "Sun Mar 14 1909 12:45:53 GTM+0530 (LST)",
    "age": 27,
    "gender": "male",
    "salary": 16660,
    "hobbies": "Acrobatics,Photography,Papier-Mache",
    "_id": Object("57579f702fa6c7651e504fe2")
}
> db.sourceData.count()
9999

有意义的数据

现在我们有 9999 个虚假用户的数据,让我们试着把数据变得有意义

例子1:计算男女数量

首先,在项目根目录创建一个 example1.js 的文件,我们要进行 MapReduce 操作,去计算男女的数量。

Mapper 的逻辑

我们只需要让 Mapper 以性别作为 key,把值作为 1。因为一个用户不是男就是女。所以,Mapper 的输出会是下面这样:

KeyValue
Male[1,1,1...]
Female[1,1,1,1,1...]

Reducer 的逻辑

在 Reducer 中,我们会获得上面两行数据,我们要做的是把每一行中的值求和,表示该性别的总数。最终的输出结果如下:

KeyValue
Male5031
Female4968

代码

好了,现在我们可以写代码去实现了。在 example1.js 中,我们要先引入所需要的依赖。

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData', 'example1_results']);

注意 第2行,第一个参数是数据库的名字,第二个参数表示集合的数组。example1_results 集合用来保存结果。

接下来,我们加上 mapper 和 reducer 函数:

var mapper = function () {
    emit(this.gender, 1);
};
 
var reducer = function(gender, count){
    return Array.sum(count);
};

第2行中, this 表示当前的文档,因此 this.gender 会作为 mapper 的 key,它的值要么是 male,要么是 female。而 emit() 将会把数据发送到一个临时保存数据的地方,作为 mapper 的结果。

第5行中,我们简单地把每个性别的所有值加起来。

最后,加上执行逻辑:

db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example1_results"
    }
 );
 
 db.example1_results.find(function (err, docs) {
    if(err) console.log(err);
    console.log(docs);
 });

第5行中,我们设置了输出的集合名。
第9行中,我们会从 example1_results 集合取得结果并显示它。

我们可以在终端运行试试:

$ node example1.js
[ { _id: 'female', value: 4968 }, { _id: 'male': value: 5031 } ]

我的数量可能和你的不一样,但男女总数应该是 9999 !

Mongo Shell 代码

如果你想在 mongo shell 中运行上面的例子,你可以粘贴下面这些代码到终端里面:

mapper = function () {
    emit(this.gender, 1);
};
 
reducer = function(gender, count){
    return Array.sum(count);
};
 
db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example1_results"
    }
 );
 
 db.example1_results.find()

然后你就会看到一样的结果,很简单吧!

例子2:获取每个性别中最老和最年轻的人

在项目根目录创建一个 example2.js 的文件。在这里,我们要把所有用户根据性别分组,然后分别找每个性别中最老和最年轻的用户。这个例子比前面的稍微复杂一点。

Mapper 的逻辑

在 mapper 中,我们要以性别作为 key,然后以 object 作为 value。这个 object 要包含用户的年龄和名字。年龄是用来做计算用的,而名字只是用来显示给人看的。

KeyValue
Male[{age: 9, name: 'John'}, ...]
Female[{age: 19, name: 'Rita'}, ...]

Reducer 的逻辑

我们的 reducer 会比前一个例子要复杂一点。我们要检查所有和性别相关的年龄,找到年龄最大和最小的用户。最终的输出结果是这样的:

KeyValue
Male{min: {name: 'harry', age: 1}, max: {name: 'Alex', age: 99} }
Female{min: {name: 'Loli', age: 10}, max: {name: 'Mary', age: 98} }

代码

现在打开 example2.js,粘贴下面的内容进去:

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData', 'example2_results']);
 
 
var mapper = function () {
    var x = {age : this.age, name : this.name};
    emit(this.gender, {min : x , max : x});
};
 
 
var reducer = function(key, values){
    var res = values[0];
    for (var i = 1; i < values.length; i++) {
        if(values[i].min.age < res.min.age)
            res.min = {name : values[i].min.name, age : values[i].min.age};
        if (values[i].max.age > res.max.age) 
           res.max = {name : values[i].max.name, age : values[i].max.age};
    };
    return res;
};
 
 
db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example2_results"
    }
 );
 
 db.example2_results.find(function (err, docs) {
    if(err) console.log(err);
    console.log(JSON.stringify(docs));
 });

第6行,我们构建了一个 object,把它作为 value 发送。
第13-18行,我们迭代了所有 object,检查当前的 object 的年龄是否大于或小于前一个 object 的年龄,如果是,就会更新 res.max 或者 res.min
在第第27行,我们把结果输出到 example2_results 中。

我们可以运行一下这个例子:

$ node example2.js
[ { _id: 'female', value: { min: [Object], max: [Object] } },
  { _id: 'male', value: { min: [Object], max: [Object] } } ]

例子3:计算每种兴趣爱好的人数

在我们最后的例子中,我们会看看有多少用户有相同的兴趣爱好。我们在项目根目录创建一个叫 example3.js 的文件。用户数据长这样子:

{
    "id": 0,
    "name": "Leanne Flinn",
    "email": "leanne.flinn@unilogic.com",
    "work": "Unilogic",
    "dob": "Sun Mar 14 1909 12:45:53 GTM+0530 (LST)",
    "age": 27,
    "gender": "male",
    "salary": 16660,
    "hobbies": "Acrobatics,Photography,Papier-Mache",
    "_id": Object("57579f702fa6c7651e504fe2")
}

如你所见,每个用户的兴趣爱好列表都用逗号分隔。我们会找出有多少用户有表演杂技的爱好等等。

Mapper 的逻辑

在这个场景下,我们的 mapper 会复杂一点。我们要为每个用户的兴趣爱好发送一个新的 key-value 对。这样,每个用户的每个兴趣爱好都会触发一次计算。最终我们会得到如下的结果:

KeyValue
Acrobatics[1,1,1,1,1,1,….]
Meditation[1,1,1,1,1,1,….]
Music[1,1,1,1,1,1,….]
Photography[1,1,1,1,1,1,….]
Papier-Mache[1,1,1,1,1,1,….]

Reducer 的逻辑

在这里,我们只要简单地为每种兴趣爱好求和就好了。最终我们会得到下面的结果:

KeyValue
Acrobatics6641
Meditation3338
Music3338
Photography3303
Papier-Mache6661

代码

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData', 'example3_results']);
 
 
var mapper = function () {
     var hobbys = this.hobbies.split(',');
      for (i in hobbys) {
        emit(hobbys[i], 1);
    }
};
 
var reducer = function (key, values) {
    var count = 0;
    for (index in values) {
        count += values[index];
    }
 
    return count;
};
 
 
db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example3_results"
    }
 );
 
 db.example3_results.find(function (err, docs) {
    if(err) console.log(err);
    console.log(docs);
 });

注意第7-9行,我们迭代了每个兴趣爱好,然后发送了一次记数。
第13-18行可以用 Array.sum(values) 来代替,这样是另外一种做相同事情的方式。最终我们得到的结果:

$ node example3.js
[ { _id: 'Acrobatics', value: 6641 },
  { _id: 'Meditation', value: 3338 },
  { _id: 'Music', value: 3338 },
  { _id: 'Photography', value: 6661 },
  { _id: 'Papier-Mache', value: 3303 } ]

这就是 MongoDB 中运行 MapReduce 的方法了。但要记住,有时候一个简单的查询就能完成你想要的事情的。

出处

http://scarletsky.github.io/2016/06/12/mapreduce-in-mongodb/

参考资料

MapReduce in MongoDB

查看原文

赞 2 收藏 11 评论 0

qq悟空 回答了问题 · 2015-07-23

海量数据如何进行批量查询

redis似乎很合适,可以考虑一下

关注 5 回答 3

qq悟空 赞了回答 · 2014-09-09

解决MongoDB怎样批量执行命令?

直接将所有要执行的代码写成一个 all.js 文件,然后用 mongo localhost/zip all.js 执行。

关注 0 回答 2

qq悟空 赞了回答 · 2014-06-26

解决javascript学习顺序:先系统学习jQuery再学习基本JavaScript是否会相对快捷?为什么?

首先我必须非常严肃地指出

“原生”JS和DOM和浏览器半毛钱关系也没有

我认为,搞清楚JS,jQuery,DOM三者的关系是至关重要的,搞清楚以后无论正着学倒着学跳着学都没问题

  • Javascript:最流行的原型式语言,弱类型,基于原型的面向对象,函数作为一等对象可传递可存储。是一门历史比较古老但一直在焕发第N春的语言。常见的运行时有:几乎所有浏览器/NodeJS/PhantomJS/CoughDB/MongoDB等
  • DOM:浏览器对HTML文档的抽象模型,以树形结构为基础,提供了各种属性、内容、插入移除、事件等方面的API。这些API以JS语言的形式暴露在浏览器的JS运行环境中
    - BOM:浏览器对浏览器自身的抽象模型,包括历史、当前位置、窗口、屏幕等的API,同样暴露在浏览器的JS运行环境中
  • jQuery:最流行的Javascript DOM/BOM操作类库,主要致力于消除了不同浏览器之前DOM和BOM的差异性,并提供了优雅的链式API、强大的选择器和便利的扩展结构。

搞清楚jQuery只是DOM的封装,DOM作为网页的操作接口,只是暴露成JS语言的形式。直接学jQuery可以最快上手实际项目,避开jQ学裸DOM可以在比如移动开发等极端场景游刃有余,并且可以强化对jQuery的理解,避免一些坑。而JS本身是他们两者的基础。

关注 13 回答 19

qq悟空 收藏了问题 · 2014-06-26

javascript学习顺序:先系统学习jQuery再学习基本JavaScript是否会相对快捷?为什么?

个人情况:

半年前端学习积累:

  • html,css基本熟练
  • javascript能看懂,会写简单的轮播图代码。会改大部分的js特效。

问题背景:

想要利用暑假两个月的时间系统学习javascript,目的是熟练打好js基础。咨询老师,身边的同学之后,给出的建议是先从jQuery入手,熟练jQuery之后再学习原生js。但各大论坛给出的建议均是先从js入手。

自己的体验:

  • 在线课堂分别听了jQuery基础和JS基础。都能跟上进度且理解。没有出现其他论坛大牛说的不懂JS就读不懂jQuery代码的问题。

  • 依然担心jQuery熟悉之后再学习JS会很难有更大的提高,形成粗糙的代码风格。

问题总结:

请教各位前辈,您学习js的顺序是怎样的?您觉得以我的个人情况,应该如何安排学习进度?

qq悟空 赞了回答 · 2014-06-25

解决怎么样实现MYSQL数据库分表?

分表只是为了分散存储压力,查询的话可以借鉴@Han Du 的方法,建个索引表,存储基本信息,通过索引表去统计和排列,需要具体数据的时候再join
很多开源的系统,尤其是国外的优秀商城产品,看下数据库设计会发现他们很擅长使用索引表

关注 1 回答 5

qq悟空 赞了回答 · 2014-06-25

解决怎么样实现MYSQL数据库分表?

按照用户的唯一ID去路由分表。

关注 1 回答 5

qq悟空 回答了问题 · 2014-06-25

解决论坛新回复提醒功能,如何设计数据库

从你描述的来看,应该是一个普通的站内信通知的功能了

可以简单做一个log表:帖子回复成功后,插入一个记录到log表里

表的字段类似:自增id, 帖子id, 回贴人id, 回帖时间, 帖子作者id(可选,通过帖子id得到)

应该差不多满足你的需求吧

关注 0 回答 2

认证与成就

  • 获得 2 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-01-21
个人主页被 278 人浏览