小东子的个人技术专栏

重点关注Android、Java、智能硬件、JavaEE、react-native,Swift,微信小程序


  • 首页

  • 归档

  • 标签
小东子的个人技术专栏

微信小程序-蓝牙BLE

发表于 2017-05-21   |   字数统计: 1,233(字)   |   阅读时长: 7(分)

在前面已经写了两篇关于Android蓝牙和ios 蓝牙开发的文章,今天带来的是微信小程序蓝牙实现。

  • Android蓝牙
  • ios蓝牙(Swift)

有一段时间没有。没有写关于小程序的文章了。3月28日,微信的api又一次新的更新。期待已久的蓝牙api更新。就开始撸一番。

源码地址

1.简述

  • 蓝牙适配器接口是基础库版本 1.1.0 开始支持。
  • iOS 微信客户端 6.5.6 版本开始支持,Android 客户端暂不支持
  • 蓝牙总共增加了18个api接口。

2.Api分类

  • 搜索类
  • 连接类
  • 通信类

3.API的具体使用

详细见官网:

https://mp.weixin.qq.com/debug/wxadoc/dev/api/bluetooth.html#wxgetconnectedbluethoothdevicesobject

4. 案例实现

4.1 搜索蓝牙设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* 搜索设备界面
*/
Page({
data: {
logs: [],
list:[],
},
onLoad: function () {
console.log('onLoad')
var that = this;
// const SDKVersion = wx.getSystemInfoSync().SDKVersion || '1.0.0'
// const [MAJOR, MINOR, PATCH] = SDKVersion.split('.').map(Number)
// console.log(SDKVersion);
// console.log(MAJOR);
// console.log(MINOR);
// console.log(PATCH);
// const canIUse = apiName => {
// if (apiName === 'showModal.cancel') {
// return MAJOR >= 1 && MINOR >= 1
// }
// return true
// }
// wx.showModal({
// success: function(res) {
// if (canIUse('showModal.cancel')) {
// console.log(res.cancel)
// }
// }
// })
//获取适配器
wx.openBluetoothAdapter({
success: function(res){
// success
console.log("-----success----------");
console.log(res);
//开始搜索
wx.startBluetoothDevicesDiscovery({
services: [],
success: function(res){
// success
console.log("-----startBluetoothDevicesDiscovery--success----------");
console.log(res);
},
fail: function(res) {
// fail
console.log(res);
},
complete: function(res) {
// complete
console.log(res);
}
})
},
fail: function(res) {
console.log("-----fail----------");
// fail
console.log(res);
},
complete: function(res) {
// complete
console.log("-----complete----------");
console.log(res);
}
})
wx.getBluetoothDevices({
success: function(res){
// success
//{devices: Array[11], errMsg: "getBluetoothDevices:ok"}
console.log("getBluetoothDevices");
console.log(res);
that.setData({
list:res.devices
});
console.log(that.data.list);
},
fail: function(res) {
// fail
},
complete: function(res) {
// complete
}
})
},
onShow:function(){
},
//点击事件处理
bindViewTap: function(e) {
console.log(e.currentTarget.dataset.title);
console.log(e.currentTarget.dataset.name);
console.log(e.currentTarget.dataset.advertisData);
var title = e.currentTarget.dataset.title;
var name = e.currentTarget.dataset.name;
wx.redirectTo({
url: '../conn/conn?deviceId='+title+'&name='+name,
success: function(res){
// success
},
fail: function(res) {
// fail
},
complete: function(res) {
// complete
}
})
},
})

4.2连接 获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/**
* 连接设备。获取数据
*/
Page({
data: {
motto: 'Hello World',
userInfo: {},
deviceId: '',
name: '',
serviceId: '',
services: [],
cd20: '',
cd01: '',
cd02: '',
cd03: '',
cd04: '',
characteristics20: null,
characteristics01: null,
characteristics02: null,
characteristics03: null,
characteristics04: null,
result,
},
onLoad: function (opt) {
var that = this;
console.log("onLoad");
console.log('deviceId=' + opt.deviceId);
console.log('name=' + opt.name);
that.setData({ deviceId: opt.deviceId });
/**
* 监听设备的连接状态
*/
wx.onBLEConnectionStateChanged(function (res) {
console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`)
})
/**
* 连接设备
*/
wx.createBLEConnection({
deviceId: that.data.deviceId,
success: function (res) {
// success
console.log(res);
/**
* 连接成功,后开始获取设备的服务列表
*/
wx.getBLEDeviceServices({
// 这里的 deviceId 需要在上面的 getBluetoothDevices中获取
deviceId: that.data.deviceId,
success: function (res) {
console.log('device services:', res.services)
that.setData({ services: res.services });
console.log('device services:', that.data.services[1].uuid);
that.setData({ serviceId: that.data.services[1].uuid });
console.log('--------------------------------------');
console.log('device设备的id:', that.data.deviceId);
console.log('device设备的服务id:', that.data.serviceId);
/**
* 延迟3秒,根据服务获取特征
*/
setTimeout(function () {
wx.getBLEDeviceCharacteristics({
// 这里的 deviceId 需要在上面的 getBluetoothDevices
deviceId: that.data.deviceId,
// 这里的 serviceId 需要在上面的 getBLEDeviceServices 接口中获取
serviceId: that.data.serviceId,
success: function (res) {
console.log('000000000000' + that.data.serviceId);
console.log('device getBLEDeviceCharacteristics:', res.characteristics)
for (var i = 0; i < 5; i++) {
if (res.characteristics[i].uuid.indexOf("cd20") != -1) {
that.setData({
cd20: res.characteristics[i].uuid,
characteristics20: res.characteristics[i]
});
}
if (res.characteristics[i].uuid.indexOf("cd01") != -1) {
that.setData({
cd01: res.characteristics[i].uuid,
characteristics01: res.characteristics[i]
});
}
if (res.characteristics[i].uuid.indexOf("cd02") != -1) {
that.setData({
cd02: res.characteristics[i].uuid,
characteristics02: res.characteristics[i]
});
} if (res.characteristics[i].uuid.indexOf("cd03") != -1) {
that.setData({
cd03: res.characteristics[i].uuid,
characteristics03: res.characteristics[i]
});
}
if (res.characteristics[i].uuid.indexOf("cd04") != -1) {
that.setData({
cd04: res.characteristics[i].uuid,
characteristics04: res.characteristics[i]
});
}
}
console.log('cd01= ' + that.data.cd01 + 'cd02= ' + that.data.cd02 + 'cd03= ' + that.data.cd03 + 'cd04= ' + that.data.cd04 + 'cd20= ' + that.data.cd20);
/**
* 回调获取 设备发过来的数据
*/
wx.onBLECharacteristicValueChange(function (characteristic) {
console.log('characteristic value comed:', characteristic.value)
//{value: ArrayBuffer, deviceId: "D8:00:D2:4F:24:17", serviceId: "ba11f08c-5f14-0b0d-1080-007cbe238851-0x600000460240", characteristicId: "0000cd04-0000-1000-8000-00805f9b34fb-0x60800069fb80"}
/**
* 监听cd04cd04中的结果
*/
if (characteristic.characteristicId.indexOf("cd01") != -1) {
const result = characteristic.value;
const hex = that.buf2hex(result);
console.log(hex);
}
if (characteristic.characteristicId.indexOf("cd04") != -1) {
const result = characteristic.value;
const hex = that.buf2hex(result);
console.log(hex);
that.setData({ result: hex });
}
})
/**
* 顺序开发设备特征notifiy
*/
wx.notifyBLECharacteristicValueChanged({
deviceId: that.data.deviceId,
serviceId: that.data.serviceId,
characteristicId: that.data.cd01,
state: true,
success: function (res) {
// success
console.log('notifyBLECharacteristicValueChanged success', res);
},
fail: function (res) {
// fail
},
complete: function (res) {
// complete
}
})
wx.notifyBLECharacteristicValueChanged({
deviceId: that.data.deviceId,
serviceId: that.data.serviceId,
characteristicId: that.data.cd02,
state: true,
success: function (res) {
// success
console.log('notifyBLECharacteristicValueChanged success', res);
},
fail: function (res) {
// fail
},
complete: function (res) {
// complete
}
})
wx.notifyBLECharacteristicValueChanged({
deviceId: that.data.deviceId,
serviceId: that.data.serviceId,
characteristicId: that.data.cd03,
state: true,
success: function (res) {
// success
console.log('notifyBLECharacteristicValueChanged success', res);
},
fail: function (res) {
// fail
},
complete: function (res) {
// complete
}
})
wx.notifyBLECharacteristicValueChanged({
// 启用 notify 功能
// 这里的 deviceId 需要在上面的 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: that.data.deviceId,
serviceId: that.data.serviceId,
characteristicId: that.data.cd04,
state: true,
success: function (res) {
console.log('notifyBLECharacteristicValueChanged success', res)
}
})
}, fail: function (res) {
console.log(res);
}
})
}
, 1500);
}
})
},
fail: function (res) {
// fail
},
complete: function (res) {
// complete
}
})
},
/**
* 发送 数据到设备中
*/
bindViewTap: function () {
var that = this;
var hex = 'AA5504B10000B5'
var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
}))
console.log(typedArray)
console.log([0xAA, 0x55, 0x04, 0xB1, 0x00, 0x00, 0xB5])
var buffer1 = typedArray.buffer
console.log(buffer1)
wx.writeBLECharacteristicValue({
deviceId: that.data.deviceId,
serviceId: that.data.serviceId,
characteristicId: that.data.cd20,
value: buffer1,
success: function (res) {
// success
console.log("success 指令发送成功");
console.log(res);
},
fail: function (res) {
// fail
console.log(res);
},
complete: function (res) {
// complete
}
})
},
/**
* ArrayBuffer 转换为 Hex
*/
buf2hex: function (buffer) { // buffer is an ArrayBuffer
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
})

5.效果展示

这里写图片描述

发送校验指令。获取结果

这里写图片描述

小东子的个人技术专栏

centos7安装zookeeper并配置集群

发表于 2017-05-10   |   字数统计: 862(字)   |   阅读时长: 5(分)

在 CentOS7 上安装 zookeeper-3.4.9 服务

1、创建 /usr/local/services/zookeeper 文件夹:

1
mkdir -p /usr/local/services/zookeeper

2、进入到 /usr/local/services/zookeeper 目录中:

1
cd /usr/local/services/zookeeper

3、下载 zookeeper-3.4.9.tar.gz:

1
wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.9/zookeeper-3.4.9.tar.gz

4、解压缩 zookeeper-3.4.9.tar.gz:

1
tar -zxvf zookeeper-3.4.9.tar.gz

5、进入到 /usr/local/services/zookeeper/zookeeper-3.4.9/conf 目录中:

1
cd zookeeper-3.4.9/conf/

6、复制 zoo_sample.cfg 文件的并命名为为 zoo.cfg:

1
cp zoo_sample.cfg zoo.cfg

7、用 vim 打开 zoo.cfg 文件并修改其内容为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# The number of milliseconds of each tick
# zookeeper 定义的基准时间间隔,单位:毫秒
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# dataDir=/tmp/zookeeper
# 数据文件夹
dataDir=/usr/local/services/zookeeper/zookeeper-3.4.9/data
# 日志文件夹
dataLogDir=/usr/local/services/zookeeper/zookeeper-3.4.9/logs
# the port at which the clients will connect
# 客户端访问 zookeeper 的端口号
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

8、保存并关闭 zoo.cfg 文件:

9、进入到 /usr/local/services/zookeeper/zookeeper-3.4.9/bin 目录中:

1
cd ../bin/

10、用 vim 打开 /etc/ 目录下的配置文件 profile:

1
2
3
4
5
6
7
8
9
10
vim /etc/profile
并在其尾部追加如下内容:
# idea - zookeeper-3.4.9 config start - 2016-09-08
export ZOOKEEPER_HOME=/usr/local/services/zookeeper/zookeeper-3.4.9/
export PATH=$ZOOKEEPER_HOME/bin:$PATH
export PATH
# idea - zookeeper-3.4.9 config start - 2016-09-08

11、使 /etc/ 目录下的 profile 文件即可生效:

1
source /etc/profile

12、启动 zookeeper 服务:

1
2
3
4
5
zkServer.sh start
如打印如下信息则表明启动成功:
ZooKeeper JMX enabled by default
Using config: /usr/local/services/zookeeper/zookeeper-3.4.9/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

13、查询 zookeeper 状态:

1
zkServer.sh status

14、关闭 zookeeper 服务:

1
2
3
4
5
zkServer.sh stop
如打印如下信息则表明成功关闭:
ZooKeeper JMX enabled by default
Using config: /usr/local/services/zookeeper/zookeeper-3.4.9/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED

15、重启 zookeeper 服务:

1
2
3
4
5
6
7
8
9
10
zkServer.sh restart
如打印如下信息则表明重启成功:
ZooKeeper JMX enabled by default
Using config: /usr/local/services/zookeeper/zookeeper-3.4.9/bin/../conf/zoo.cfg
ZooKeeper JMX enabled by default
Using config: /usr/local/services/zookeeper/zookeeper-3.4.9/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
ZooKeeper JMX enabled by default
Using config: /usr/local/services/zookeeper/zookeeper-3.4.9/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

ZooKeeper学习总结

http://www.linuxidc.com/Linux/2016-07/133179.htm

Ubuntu 14.04安装分布式存储Sheepdog+ZooKeeper http://www.linuxidc.com/Linux/2014-12/110352.htm

CentOS 6安装sheepdog 虚拟机分布式储存 http://www.linuxidc.com/Linux/2013-08/89109.htm

ZooKeeper集群配置 http://www.linuxidc.com/Linux/2013-06/86348.htm

使用ZooKeeper实现分布式共享锁 http://www.linuxidc.com/Linux/2013-06/85550.htm

分布式服务框架 ZooKeeper – 管理分布式环境中的数据 http://www.linuxidc.com/Linux/2013-06/85549.htm

ZooKeeper集群环境搭建实践 http://www.linuxidc.com/Linux/2013-04/83562.htm

ZooKeeper服务器集群环境配置实测 http://www.linuxidc.com/Linux/2013-04/83559.htm

ZooKeeper集群安装 http://www.linuxidc.com/Linux/2012-10/72906.htm

Zookeeper3.4.6的安装 http://www.linuxidc.com/Linux/2015-05/117697.htm

zookeeper 没有到主机的路由 (Host unreachable)

http://www.w2bc.com/article/213344

myid文件缺失导致zookeeper无法启动(myid file is missing)

http://blog.csdn.net/u010842515/article/details/51147016

小东子的个人技术专栏

centos7安装MYSQL5.7并配置

发表于 2017-05-10   |   字数统计: 847(字)   |   阅读时长: 4(分)

安装环境:CentOS7 64位 MINI版,安装MySQL5.7

1、配置YUM源

在MySQL官网中下载YUM源rpm安装包:http://dev.mysql.com/downloads/repo/yum/

下载mysql源安装包

1
shell> wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm

安装mysql源

1
shell> yum localinstall mysql57-community-release-el7-8.noarch.rpm

检查mysql源是否安装成功

1
shell> yum repolist enabled | grep "mysql.*-community.*"

看到上图所示表示安装成功

2、安装MySQL


1
shell> yum install mysql-community-server

3、启动MySQL服务


1
shell> systemctl start mysqld

查看MySQL的启动状态

1
2
3
4
5
6
7
8
9
10
shell> systemctl status mysqld
● mysqld.service - MySQL Server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; disabled; vendor preset: disabled)
Active: active (running) since 五 2016-06-24 04:37:37 CST; 35min ago
Main PID: 2888 (mysqld)
CGroup: /system.slice/mysqld.service
└─2888 /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid
6月 24 04:37:36 localhost.localdomain systemd[1]: Starting MySQL Server...
6月 24 04:37:37 localhost.localdomain systemd[1]: Started MySQL Server.

4、开机启动


1
2
shell> systemctl enable mysqld
shell> systemctl daemon-reload

5、修改root默认密码

mysql安装完成之后,在/var/log/mysqld.log文件中给root生成了一个默认密码。通过下面的方式找到root默认密码,然后登录mysql进行修改:

1
2
3
shell> grep 'temporary password' /var/log/mysqld.log
shell> mysql -uroot -p
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!';

或者

1
mysql> set password for 'root'@'localhost'=password('MyNewPass4!');

注意:mysql5.7默认安装了密码安全检查插件(validate_password),默认密码检查策略要求密码必须包含:大小写字母、数字和特殊符号,并且长度不能少于8位。否则会提示ERROR 1819 (HY000): Your password does not satisfy the current policy requirements错误,如下图所示: 通过msyql环境变量可以查看密码策略的相关信息:

1
mysql> show variables like '%password%';
1
2
3
4
5
6
7
8
validate_password_policy:密码策略,默认为MEDIUM策略
validate_password_dictionary_file:密码策略文件,策略为STRONG才需要
validate_password_length:密码最少长度
validate_password_mixed_case_count:大小写字符长度,至少1个
validate_password_number_count :数字至少1个
validate_password_special_char_count:特殊字符至少1个

上述参数是默认策略MEDIUM的密码检查规则。

共有以下几种密码策略:

策略 检查规则 0 or LOW Length 1 or MEDIUM Length; numeric, lowercase/uppercase, and special characters 2 or STRONG Length; numeric, lowercase/uppercase, and special characters; dictionary file MySQL官网密码策略详细说明:http://dev.mysql.com/doc/refman/5.7/en/validate-password-options-variables.html#sysvar_validate_password_policy

修改密码策略

在/etc/my.cnf文件添加validate_password_policy配置,指定密码策略

选择0(LOW),1(MEDIUM),2(STRONG)其中一种,选择2需要提供密码字典文件

validate_password_policy=0 如果不需要密码策略,添加my.cnf文件中添加如下配置禁用即可:

1
validate_password = off

重新启动mysql服务使配置生效:

systemctl restart mysqld

6、添加远程登录用户


默认只允许root帐户在本地登录,如果要在其它机器上连接mysql,必须修改root允许远程连接,或者添加一个允许远程连接的帐户,为了安全起见,我添加一个新的帐户:

mysql> GRANT ALL PRIVILEGES ON . TO ‘yangxin’@’%’ IDENTIFIED BY ‘Yangxin0917!’ WITH GRANT OPTION;

7、配置默认编码为utf8


修改/etc/my.cnf配置文件,在[mysqld]下添加编码配置,如下所示:

[mysqld] character_set_server=utf8 init_connect=’SET NAMES utf8’ 重新启动mysql服务,查看数据库默认编码如下所示:

默认配置文件路径: 配置文件:/etc/my.cnf 日志文件:/var/log//var/log/mysqld.log 服务启动脚本:/usr/lib/systemd/system/mysqld.service socket文件:/var/run/mysqld/mysqld.pid

小东子的个人技术专栏

Mongodb基本的命令语法

发表于 2017-05-10   |   字数统计: 1,992(字)   |   阅读时长: 9(分)

一、MONGODB基本命令用法

成功启动MongoDB后,再打开一个命令行窗口输入mongo,就可以进行数据库的一些操作。

输入help可以看到基本操作命令:

show dbs:显示数据库列表
show collections:显示当前数据库中的集合(类似关系数据库中的表)
show users:显示用户

use :切换当前数据库,这和MS-SQL里面的意思一样
db.help():显示数据库操作命令,里面有很多的命令
db.foo.help():显示集合操作命令,同样有很多的命令,foo指的是当前数据库下,一个叫foo的集合,并非真正意义上的命令
db.foo.find():对于当前数据库中的foo集合进行数据查找(由于没有条件,会列出所有数据)
db.foo.find( { a : 1 } ):对于当前数据库中的foo集合进行查找,条件是数据中有一个属性叫a,且a的值为1

MongoDB没有创建数据库的命令,但有类似的命令。

如:如果你想创建一个“myTest”的数据库,先运行use myTest命令,之后就做一些操作(如:db.createCollection(‘user’)),这样就可以创建一个名叫“myTest”的数据库。

二、数据库常用命令

1、Help查看命令提示

1
2
3
4
5
6
7
8
9
help
db.help();
db.yourColl.help();
db.youColl.find().help();
rs.help();

2、切换/创建数据库

1
use yourDB;

当创建一个集合(table)的时候会自动创建当前数据库

3、查询所有数据库

1
show dbs;

4、删除当前使用数据库

1
db.dropDatabase();

5、从指定主机上克隆数据库

1
db.cloneDatabase(“127.0.0.1”); //将指定机器上的数据库的数据克隆到当前数据库

6、从指定的机器上复制指定数据库数据到某个数据库

1
db.copyDatabase("mydb", "temp", "127.0.0.1");//将本机的mydb的数据复制到temp数据库中

7、修复当前数据库

1
db.repairDatabase();

8、查看当前使用的数据库

1
db.getName();

db; db和getName方法是一样的效果,都可以查询当前使用的数据库

9、显示当前db状态

1
db.stats();

10、当前db版本

1
db.version();

11、查看当前db的链接机器地址

1
db.getMongo();

三、Collection聚集集合

1、创建一个聚集集合(table)

1
db.createCollection(“collName”, {size: 20, capped: 5, max: 100});

2、得到指定名称的聚集集合(table)

1
db.getCollection("account");

3、得到当前db的所有聚集集合

1
db.getCollectionNames();

4、显示当前db所有聚集索引的状态

1
db.printCollectionStats();

四、用户相关

1、添加一个用户

1
2
3
db.addUser("name");
db.addUser("userName", "pwd123", true);

添加用户、设置密码、是否只读

2、数据库认证、安全模式

1
db.auth("userName", "123123");

3、显示当前所有用户

1
show users;

4、删除用户

1
db.removeUser("userName");

五.其他

1、查询之前的错误信息

1
db.getPrevError();

2、清除错误记录

1
db.resetError();

六.查看聚集集合基本信息

1、查看帮助

1
db.yourColl.help();

2、查询当前集合的数据条数

1
db.yourColl.count();

3、查看数据空间大小

1
db.userInfo.dataSize();

4、得到当前聚集集合所在的数据库

1
db db.userInfo.getDB();

5、得到当前聚集的状态

1
db.userInfo.stats();

6、得到聚集集合总大小

1
db.userInfo.totalSize();

7、聚集集合储存空间大小

1
db.userInfo.storageSize();

8、Shard版本信息

1
db.userInfo.getShardVersion()

9、聚集集合重命名

1
db.userInfo.renameCollection("users");

将userInfo重命名为users

10、删除当前聚集集合

1
db.userInfo.drop();

七、聚集集合查询

1、查询所有记录

1
db.userInfo.find();

相当于:select* from userInfo;

默认每页显示20条记录,当显示不下的情况下,可以用it迭代命令查询下一页数据。注意:键入it命令不能带“;”

但是你可以设置每页显示数据的大小,用DBQuery.shellBatchSize= 50;这样每页就显示50条记录了。

2、查询去掉后的当前聚集集合中的某列的重复数据

1
db.userInfo.distinct("name");

会过滤掉name中的相同数据

相当于:select distict name from userInfo;

3、查询age = 22的记录

1
db.userInfo.find({"age": 22});

相当于: select * from userInfo where age = 22;

4、查询age > 22的记录

1
db.userInfo.find({age: {$gt: 22}});

相当于:select * from userInfo where age >22;

5、查询age < 22的记录

1
db.userInfo.find({age: {$lt: 22}});

相当于:select * from userInfo where age <22;

6、查询age >= 25的记录

1
db.userInfo.find({age: {$gte: 25}});

相当于:select * from userInfo where age >= 25;

7、查询age <= 25的记录

1
db.userInfo.find({age: {$lte: 25}});

8、查询age >= 23 并且 age <= 26

1
db.userInfo.find({age: {$gte: 23, $lte: 26}});

9、查询name中包含 mongo的数据

1
db.userInfo.find({name: /mongo/});

//相当于%%

select * from userInfo where name like ‘%mongo%’;

10、查询name中以mongo开头的

1
db.userInfo.find({name: /^mongo/});

select * from userInfo where name like ‘mongo%’;

11、查询指定列name、age数据

1
db.userInfo.find({}, {name: 1, age: 1});

相当于:select name, age from userInfo;

当然name也可以用true或false,当用ture的情况下河name:1效果一样,如果用false就是排除name,显示name以外的列信息。

12、查询指定列name、age数据, age > 25

1
db.userInfo.find({age: {$gt: 25}}, {name: 1, age: 1});

相当于:select name, age from userInfo where age >25;

13、按照年龄排序

1
2
3
升序:db.userInfo.find().sort({age: 1});
降序:db.userInfo.find().sort({age: -1});

14、查询name = zhangsan, age = 22的数据

1
db.userInfo.find({name: 'zhangsan', age: 22});

相当于:select * from userInfo where name = ‘zhangsan’ and age = ‘22’;

15、查询前5条数据

1
db.userInfo.find().limit(5);

相当于:selecttop 5 * from userInfo;

16、查询10条以后的数据

1
db.userInfo.find().skip(10);

相当于:select * from userInfo where id not in (

selecttop 10 * from userInfo

);

17、查询在5-10之间的数据

1
db.userInfo.find().limit(10).skip(5);

可用于分页,limit是pageSize,skip是第几页*pageSize

18、or与 查询

1
db.userInfo.find({$or: [{age: 22}, {age: 25}]});

相当于:select * from userInfo where age = 22 or age = 25;

19、查询第一条数据

1
db.userInfo.findOne();

相当于:selecttop 1 * from userInfo;

1
db.userInfo.find().limit(1);

20、查询某个结果集的记录条数

1
db.userInfo.find({age: {$gte: 25}}).count();

相当于:select count(*) from userInfo where age >= 20;

如果要返回限制之后的记录数量,要使用count(true)或者count(非0)

1
db.users.find().skip(10).limit(5).count(true);

21、按照某列进行排序

1
db.userInfo.find({sex: {$exists: true}}).count();

相当于:select count(sex) from userInfo;

八.索引

1、创建索引

1
2
3
db.userInfo.ensureIndex({name: 1});
db.userInfo.ensureIndex({name: 1, ts: -1});

2、查询当前聚集集合所有索引

1
db.userInfo.getIndexes();

3、查看总索引记录大小

1
db.userInfo.totalIndexSize();

4、读取当前集合的所有index信息

1
db.users.reIndex();

5、删除指定索引

1
db.users.dropIndex("name_1");

6、删除所有索引索引

1
db.users.dropIndexes();

9.修改、添加、删除集合数据

1、添加

1
db.users.save({name: ‘zhangsan’, age: 25, sex: true});

添加的数据的数据列,没有固定,根据添加的数据为准

2、修改

1
db.collection.update(criteria, objNew, upsert, multi )

criteria:update的查询条件,类似sql update查询内where后面的

objNew:update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的。

upsert : 如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。

multi : mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

1
db.users.update({age: 25}, {$set: {name: 'changeName'}}, false, true);

相当于:update users set name = ‘changeName’ where age = 25;

1
db.users.update({name: 'Lisi'}, {$inc: {age: 50}}, false, true);

相当于:update users set age = age + 50 where name = ‘Lisi’;

1
db.users.update({name: 'Lisi'}, {$inc: {age: 50}, $set: {name: 'hoho'}}, false, true);

相当于:update users set age = age + 50, name = ‘hoho’ where name = ‘Lisi’;

3、删除

1
db.users.remove({age: 132});

4、查询修改删除

1
2
3
4
5
6
7
8
9
10
11
db.users.findAndModify({
query: {age: {$gte: 25}},
sort: {age: -1},
update: {$set: {name: 'a2'}, $inc: {age: 2}},
remove: true
});
1
2
3
4
5
6
7
8
9
10
11
db.runCommand({ findandmodify : "users",
query: {age: {$gte: 25}},
sort: {age: -1},
update: {$set: {name: 'a2'}, $inc: {age: 2}},
remove: true
});
小东子的个人技术专栏

Consul 简介、安装、常用命令的使用

发表于 2017-03-14   |   字数统计: 3,338(字)   |   阅读时长: 17(分)

1 Consul简介

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。使用起来也较 为简单。Consul使用Go语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合 。

2 Consul安装

安装环境:
mac:64bit(查看mac位数:打开终端–>”uname -a”)
consul_0.6.4_darwin_amd64.zip和consul_0.6.4_web_ui.zip,从consul官网https://www.consul.io/downloads.html进行下载就好(选择好OS和位数)

1、解压consul_0.6.4_darwin_amd64.zip
2、将解压后的二进制文件consul(上边画红框的部分拷贝到/usr/local/bin下)

1
sudo scp consul /usr/local/bin/

说明:使用sudo是因为权限问题。
3、查看是否安装成功,

直接在家目录下执行consul命令即可。出现如下结果,表示安装成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
lidongdeMacBook-Pro:bin lidong$ consul
usage: consul [--version] [--help] <command> [<args>]
Available commands are:
agent Runs a Consul agent
configtest Validate config file
event Fire a new event
exec Executes a command on Consul nodes
force-leave Forces a member of the cluster to enter the "left" state
info Provides debugging information for operators
join Tell Consul agent to join cluster
keygen Generates a new encryption key
keyring Manages gossip layer encryption keys
kv Interact with the key-value store
leave Gracefully leaves the Consul cluster and shuts down
lock Execute a command holding a lock
maint Controls node or service maintenance mode
members Lists the members of a Consul cluster
monitor Stream logs from a Consul agent
operator Provides cluster-level tools for Consul operators
reload Triggers the agent to reload configuration files
rtt Estimates network round trip time between nodes
snapshot Saves, restores and inspects snapshots of Consul server state
version Prints the Consul version
watch Watch for changes in Consul

3 Consul 启动

1、执行命令

1
./consul agent -dev # -dev表示开发模式运行,另外还有-server表示服务模式运行

查看显示结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
lidongdeMacBook-Pro:bin lidong$ consul agent -dev
==> Starting Consul agent...
==> Starting Consul agent RPC...
==> Consul agent running!
Version: 'v0.7.5'
Node ID: 'c67a8d03-deac-35b8-8f68-06ff7b687215'
Node name: 'lidongdeMacBook-Pro.local'
Datacenter: 'dc1'
Server: true (bootstrap: false)
Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
Atlas: <disabled>
==> Log data will now stream in as it occurs:
2017/03/13 12:44:31 [DEBUG] Using unique ID "c67a8d03-deac-35b8-8f68-06ff7b687215" from host as node ID
2017/03/13 12:44:31 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:127.0.0.1:8300 Address:127.0.0.1:8300}]
2017/03/13 12:44:31 [INFO] raft: Node at 127.0.0.1:8300 [Follower] entering Follower state (Leader: "")
2017/03/13 12:44:31 [INFO] serf: EventMemberJoin: lidongdeMacBook-Pro.local 127.0.0.1
2017/03/13 12:44:31 [INFO] consul: Adding LAN server lidongdeMacBook-Pro.local (Addr: tcp/127.0.0.1:8300) (DC: dc1)
2017/03/13 12:44:31 [INFO] serf: EventMemberJoin: lidongdeMacBook-Pro.local.dc1 127.0.0.1
2017/03/13 12:44:31 [INFO] consul: Adding WAN server lidongdeMacBook-Pro.local.dc1 (Addr: tcp/127.0.0.1:8300) (DC: dc1)
2017/03/13 12:44:36 [WARN] raft: Heartbeat timeout from "" reached, starting election
2017/03/13 12:44:36 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering Candidate state in term 2
2017/03/13 12:44:36 [DEBUG] raft: Votes needed: 1
2017/03/13 12:44:36 [DEBUG] raft: Vote granted from 127.0.0.1:8300 in term 2. Tally: 1
2017/03/13 12:44:36 [INFO] raft: Election won. Tally: 1
2017/03/13 12:44:36 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Leader state
2017/03/13 12:44:36 [INFO] consul: cluster leadership acquired
2017/03/13 12:44:36 [INFO] consul: New leader elected: lidongdeMacBook-Pro.local
2017/03/13 12:44:36 [DEBUG] consul: reset tombstone GC to index 3
2017/03/13 12:44:36 [INFO] consul: member 'lidongdeMacBook-Pro.local' joined, marking health alive
2017/03/13 12:44:37 [INFO] agent: Synced service 'consul'
2017/03/13 12:44:37 [DEBUG] agent: Node info in sync
2017/03/13 12:44:39 [INFO] agent.rpc: Accepted client: 127.0.0.1:54095
2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/catalog/datacenters (580.862µs) from=127.0.0.1:54114
2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/catalog/datacenters (34.955µs) from=127.0.0.1:54114
2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/internal/ui/nodes?dc=dc1&token=<hidden> (1.024476ms) from=127.0.0.1:54112
2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/coordinate/nodes?dc=dc1&token=<hidden> (361.25µs) from=127.0.0.1:54111
2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/internal/ui/services?dc=dc1&token=<hidden> (315.405µs) from=127.0.0.1:54111
2017/03/13 12:44:53 [DEBUG] http: Request GET /v1/internal/ui/nodes?dc=dc1&token=<hidden> (140.095µs) from=127.0.0.1:54111
2017/03/13 12:44:54 [DEBUG] http: Request GET /v1/kv/?keys&seperator=/&dc=dc1&token=<hidden> (1.368486ms) from=127.0.0.1:54111
2017/03/13 12:44:55 [DEBUG] http: Request GET /v1/acl/list?dc=dc1&token=<hidden> (9.253µs) from=127.0.0.1:54111
2017/03/13 12:44:57 [DEBUG] http: Request GET /v1/internal/ui/services?dc=dc1&token=<hidden> (109.196µs) from=127.0.0.1:54111
2017/03/13 12:44:59 [DEBUG] http: Request GET /v1/internal/ui/services?dc=dc1&token=<hidden> (98.48µs) from=127.0.0.1:54111
2017/03/13 12:45:58 [DEBUG] agent: Service 'consul' in sync
2017/03/13 12:45:58 [DEBUG] agent: Node info in sync
2017/03/13 12:47:55 [DEBUG] agent: Service 'consul' in sync
2017/03/13 12:47:55 [DEBUG] agent: Node info in sync

说明:

-dev(该节点的启动不能用于生产环境,因为该模式下不会持久化任何状态),该启动模式仅仅是为了快速便捷的启动单节点consul
该节点处于server模式
该节点是leader
该节点是一个健康节点
2、查看consul cluster中的每一个consul节点的信息

1
2
3
lidongdeMacBook-Pro:~ lidong$ consul members
Node Address Status Type Build Protocol DC
lidongdeMacBook-Pro.local 127.0.0.1:8301 alive server 0.7.5 2 dc1

说明:

  • Address:节点地址
  • Status:alive表示节点健康
  • Type:server运行状态是server状态
  • DC:dc1表示该节点属于DataCenter1
    注意:

members命令的输出是基于gossip协议的,并且是最终一致的(也就是说,某一个时刻你去运用该命令查到的consul节点的状态信息可能是有误的)

输入http://127.0.0.1:8500/ui/ 访问Consul,可查看到如下界面:
这里写图片描述

4 停止服务(优雅退出)

命令:CTRL+C

1
2
3
4
5
6
7
8
^C==> Caught signal: interrupt
2017/03/13 12:50:42 [DEBUG] http: Shutting down http server (127.0.0.1:8500)
2017/03/13 12:50:42 [INFO] agent: requesting shutdown
2017/03/13 12:50:42 [INFO] consul: shutting down server
2017/03/13 12:50:42 [WARN] serf: Shutdown without a Leave
2017/03/13 12:50:42 [WARN] serf: Shutdown without a Leave
2017/03/13 12:50:42 [ERR] dns: error starting tcp server: accept tcp 127.0.0.1:8600: use of closed network connection
2017/03/13 12:50:42 [INFO] agent: shutdown complete

说明:

  • 该节点离开后,会通知cluster中的其他节点

注意:

  • 安装部分参考自:https://www.consul.io/intro/getting-started/install.html
  • 启动和停止服务部分参考自:https://www.consul.io/intro/getting-started/agent.html

5 Consul常用命令

命令 解释 示例
agent 运行一个consul agent consul agent -dev
join 将agent加入到consul集群 consul join IP
members 列出consul cluster集群中的members consul members
leave 将节点移除所在集群 consul leave

consul agent 命令详解

输入consul agent --help ,可以看到consul agent 的选项,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
-advertise=addr Sets the advertise address to use
-advertise-wan=addr Sets address to advertise on wan instead of advertise addr
-atlas=org/name Sets the Atlas infrastructure name, enables SCADA.
-atlas-join Enables auto-joining the Atlas cluster
-atlas-token=token Provides the Atlas API token
-atlas-endpoint=1.2.3.4 The address of the endpoint for Atlas integration.
-bootstrap Sets server to bootstrap mode
-bind=0.0.0.0 Sets the bind address for cluster communication
-http-port=8500 Sets the HTTP API port to listen on
-bootstrap-expect=0 Sets server to expect bootstrap mode.
-client=127.0.0.1 Sets the address to bind for client access.
This includes RPC, DNS, HTTP and HTTPS (if configured)
-config-file=foo Path to a JSON file to read configuration from.
This can be specified multiple times.
-config-dir=foo Path to a directory to read configuration files
from. This will read every file ending in ".json"
as configuration in this directory in alphabetical
order. This can be specified multiple times.
-data-dir=path Path to a data directory to store agent state
-dev Starts the agent in development mode.
-recursor=1.2.3.4 Address of an upstream DNS server.
Can be specified multiple times.
-dc=east-aws Datacenter of the agent (deprecated: use 'datacenter' instead).
-datacenter=east-aws Datacenter of the agent.
-encrypt=key Provides the gossip encryption key
-join=1.2.3.4 Address of an agent to join at start time.
Can be specified multiple times.
-join-wan=1.2.3.4 Address of an agent to join -wan at start time.
Can be specified multiple times.
-retry-join=1.2.3.4 Address of an agent to join at start time with
retries enabled. Can be specified multiple times.
-retry-interval=30s Time to wait between join attempts.
-retry-max=0 Maximum number of join attempts. Defaults to 0, which
will retry indefinitely.
-retry-join-wan=1.2.3.4 Address of an agent to join -wan at start time with
retries enabled. Can be specified multiple times.
-retry-interval-wan=30s Time to wait between join -wan attempts.
-retry-max-wan=0 Maximum number of join -wan attempts. Defaults to 0, which
will retry indefinitely.
-log-level=info Log level of the agent.
-node=hostname Name of this node. Must be unique in the cluster
-protocol=N Sets the protocol version. Defaults to latest.
-rejoin Ignores a previous leave and attempts to rejoin the cluster.
-server Switches agent to server mode.
-syslog Enables logging to syslog
-ui Enables the built-in static web UI server
-ui-dir=path Path to directory containing the Web UI resources
-pid-file=path Path to file to store agent PID

consul agent 命令的常用选项,如下:

  • -data-dir
    • 作用:指定agent储存状态的数据目录
    • 这是所有agent都必须的
    • 对于server尤其重要,因为他们必须持久化集群的状态
  • -config-dir
    • 作用:指定service的配置文件和检查定义所在的位置
    • 通常会指定为”某一个路径/consul.d”(通常情况下,.d表示一系列配置文件存放的目录)
  • -config-file
    • 作用:指定一个要装载的配置文件
    • 该选项可以配置多次,进而配置多个配置文件(后边的会合并前边的,相同的值覆盖)
  • -dev
    • 作用:创建一个开发环境下的server节点
    • 该参数配置下,不会有任何持久化操作,即不会有任何数据写入到磁盘
    • 这种模式不能用于生产环境(因为第二条)
  • -bootstrap-expect
    • 作用:该命令通知consul server我们现在准备加入的server节点个数,该参数是为了延迟日志复制的启动直到我们指定数量的server节点成功的加入后启动。
  • -node
    • 作用:指定节点在集群中的名称
    • 该名称在集群中必须是唯一的(默认采用机器的host)
    • 推荐:直接采用机器的IP
  • -bind
    • 作用:指明节点的IP地址
    • 有时候不指定绑定IP,会报Failed to get advertise address: Multiple private IPs found. Please configure one. 的异常
  • -server
    • 作用:指定节点为server
    • 每个数据中心(DC)的server数推荐至少为1,至多为5
    • 所有的server都采用raft一致性算法来确保事务的一致性和线性化,事务修改了集群的状态,且集群的状态保存在每一台server上保证可用性
    • server也是与其他DC交互的门面(gateway)
  • -client
    • 作用:指定节点为client,指定客户端接口的绑定地址,包括:HTTP、DNS、RPC
    • 默认是127.0.0.1,只允许回环接口访问
    • 若不指定为-server,其实就是-client
  • -join
    • 作用:将节点加入到集群
  • -datacenter(老版本叫-dc,-dc已经失效)
    • 作用:指定机器加入到哪一个数据中心中

使用-client 参数可指定允许客户端使用什么ip去访问,例如-client 192.168.11.143 表示可以使用http://192.168.11.143:8500/ui 去访问。

我们尝试一下:

1
consul agent -dev -client 192.168.11.143

发现果然可以使用http://192.168.11.143:8500/ui 访问了。

6 Consul 的高可用

Consul Cluster集群架构图如下:
这里写图片描述

这边准备了三台CentOS 7的虚拟机,主机规划如下,供参考:

主机名称 IP 作用 是否允许远程访问
node0 192.168.11.143 consul server 是
node1 192.168.11.144 consul client 否
node2 192.168.11.145 consul client 是

6.1 搭建步骤:

  • 启动node0机器上的Consul(node0机器上执行):
1
consul agent -data-dir /tmp/node0 -node=node0 -bind=192.168.11.143 -datacenter=dc1 -ui -client=192.168.11.143 -server -bootstrap-expect 1
  • 启动node1机器上的Consul(node1机器上执行):
1
consul agent -data-dir /tmp/node1 -node=node1 -bind=192.168.11.144 -datacenter=dc1 -ui
  • 启动node2机器上的Consul(node2机器上执行):
1
consul agent -data-dir /tmp/node2 -node=node2 -bind=192.168.11.145 -datacenter=dc1 -ui -client=192.168.11.145
  • 将node1节点加入到node0上(node1机器上执行):
1
consul join 192.168.11.143
  • 将node2节点加入到node0上(node2机器上执行):
1
consul join -rpc-addr=192.168.11.145:8400 192.168.11.143
  • 这样一个简单的Consul集群就搭建完成了,在node1上查看当前集群节点:
1
consul members -rpc-addr=192.168.11.143:8400

结果如下:

1
2
3
4
Node Address Status Type Build Protocol DC
node0 192.168.11.143:8301 alive server 0.7.0 2 dc1
node1 192.168.11.144:8301 alive client 0.7.0 2 dc1
node2 192.168.11.145:8301 alive client 0.7.0 2 dc1

说明集群已经搭建成功了。

我们分析一下,为什么第5步和第6步需要加-rpc-addr 选项,而第4步不需要加任何选项呢?原因是-client 指定了客户端接口的绑定地址,包括:HTTP、DNS、RPC,而consul join 、consul members 都是通过RPC与Consul交互的。

6.2 访问集群

这里写图片描述
如上,我们三个节点都加了-ui 参数启动了内建的界面。我们可以通过:http://192.168.11.143:8500/ui/ 或者http://192.168.11.145:8500/ui/进行访问,也可以在node1机器上通过http://127.0.0.1:8500/ui/ 进行访问,原因是node1没有开启远程访问 ,三种访问方式结果是一致的,如下:

consul cluster

7 参考文档:

  1. Consul官方文档:https://www.consul.io/intro/getting-started/install.html

  2. Consul 系列博文:http://www.cnblogs.com/java-zhao/archive/2016/04/13/5387105.html

  3. 使用consul实现分布式服务注册和发现:http://www.tuicool.com/articles/M3QFven

12…10
李东

李东

细节决定成败,点滴铸就辉煌

46 文章
18 标签
© 2017 李东
由 Hexo 强力驱动
主题 - NexT.Pisces