Just Be Unreserved!

学无止境。


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

Linux挂载磁盘教程

发表于 Oct 31 2013   |   分类于 Linux   |  

###挂载前查看各分区基本情况:

1
2
3
4
[root@localhost ~]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/xvda2 36G 2.3G 32G 7% /
tmpfs 15G 0 15G 0% /dev/shm

###查看各磁盘基本情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost ~]# fdisk -l 
Disk /dev/xvda: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders

Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00014faa

Device Boot Start End Blocks Id System
/dev/xvda1 1 523 4194304 82 Linux swap / Solaris
Partition 1 does not end on cylinder boundary.
/dev/xvda2 * 523 5222 37747712 83 Linux

Disk /dev/xvde: 493.9 GB, 493921239040 bytes
255 heads, 63 sectors/track, 60049 cylinders

Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

###挂载磁盘/dev/xvde的具体步骤:

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
[root@localhost ~]# fdisk /dev/xvde 
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0xb28f7207.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)
WARNING: DOS-compatible mode is deprecated. It's strongly recommended to
switch off the mode (command 'c') and change display units to
sectors (command 'u').
Command (m for help): p^H
Disk /dev/xvde: 493.9 GB, 493921239040 bytes
255 heads, 63 sectors/track, 60049 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xb28f7207
Device Boot Start End Blocks Id System
Command (m for help): m
Command action
a toggle a bootable flag
b edit bsd disklabel
c toggle the dos compatibility flag
d delete a partition
l list known partition types
m print this menu
n add a new partition
o create a new empty DOS partition table
p print the partition table
q quit without saving changes
s create a new empty Sun disklabel
t change a partition's system id
u change display/entry units
v verify the partition table
w write table to disk and exit
x extra functionality (experts only)
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-60049, default 1):
Using default value 1
Last cylinder, +cylinders or +size{K,M,G} (1-60049, default 60049):
Using default value 60049
Command (m for help): w
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.

###在不重启的情况下识别出来刚才创建的分区:

1
2
3
[root@localhost ~]# partprobe 
Warning: WARNING: the kernel failed to re-read the partition table on /dev/xvda (Device or resource busy). As a result, it may not reflect all of your changes until after reboot.
Warning: Unable to open /dev/sr0 read-write (Read-only file system). /dev/sr0 has been opened read-only.

###再次查看各磁盘基本情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# fdisk -l 
Disk /dev/xvda: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders

Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00014faa

Device Boot Start End Blocks Id System
/dev/xvda1 1 523 4194304 82 Linux swap / Solaris
Partition 1 does not end on cylinder boundary.
/dev/xvda2 * 523 5222 37747712 83 Linux

Disk /dev/xvde: 493.9 GB, 493921239040 bytes
255 heads, 63 sectors/track, 60049 cylinders

Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xb28f7207

Device Boot Start End Blocks Id System
/dev/xvde1 1 60049 482343561 83 Linux /*系统已接入磁盘*/

###以ext3的格式格式化磁盘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@localhost ~]# mkfs.ext3 /dev/xvde1 
mke2fs 1.41.12 (17-May-2010)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
30146560 inodes, 120585890 blocks
6029294 blocks (5.00%) reserved for the super user

First data block=0
Maximum filesystem blocks=4294967296
3680 block groups

32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
102400000
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 25 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.

###编辑磁盘挂载配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost ~]# vim /etc/fstab 
#
# /etc/fstab
# Created by anaconda on Tue Mar 26 14:54:14 2013
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
UUID=5056bbc2-cf00-4f7c-95e1-35be323fc523 / ext3 defaults 1 1
UUID=c2a7b4a1-c806-46e5-a97d-b9a964fc9e05 swap swap defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
/dev/xvde1 /opt/ ext3 defaults 0 0 /*增加这一行内容*/

###执行挂载:

1
[root@localhost ~]# mount -a

###查看各分区基本情况:

1
2
3
4
5
[root@localhost ~]# df -h 
Filesystem Size Used Avail Use% Mounted on
/dev/xvda2 36G 2.3G 32G 7% /
tmpfs 15G 0 15G 0% /dev/shm
/dev/xvde1 453G 199M 430G 1% /opt /*/opt分区已挂载上/dev/xvde1这个磁盘*/

环回文件与挂载

发表于 Oct 29 2013   |   分类于 Linux   |  

##一、什么是环回文件系统?

环回(loopback)的概念,在很多地方我们都可以看到,出现频率最多的可能就是网络相关的地方了。这里只讨论linux下的环回文件系统。文件系统,这个大家应该都了解,通常,要使用一个文件系统,我们首先要在硬件设备创建文件系统,然后将文件系统挂载在被称为挂载点(mount point)的目录上。那么什么是环回文件系统呢?环回文件系统就是指那些在文件中而非物理设备中创建的文件系统。比如我们可以创建一个文件,然后把这个文件格式化为我们常见ntfs、exfat或者ext4等文件系统格式,然后把它挂载在一个目录上使用。

##二、创建一个特定大小的大文件

①在创建环回文件之前,我们要先要了解怎样去创建一个特定大小的大文件。先看下面这串命令:

1
dd if=/dev/zero of=first.file bs=1M count=1

执行结果如下:

(A)

这个命令会创建一个1MB大小的文件。dd命令是克隆输入的内容,并将副本写入到输出;if(input file)代表输入,stdin、设备文件、普通文件都可以作为输入,这个例子就是用设备文件作为输入;of(output file)代表输出,stdout、设备文件、普通文件等也可作为输出,这个例子是用first.file这个普通文件作为输出;bs代表以字节为单位的块大小(block size,BS),count代表被复制的块数。

②接下来查看下我们刚才创建的文件:

(B)

其实创建的文件占用空间并不是1MB,比1MB要多,这是为什么呢?主要还是涉及到BS这个概念,具体的原因也比较复杂,真说起来可能又是一片长博客了,Linux文件系统十问,这篇文章阐释非常清晰。

③上面的命令里有个/dev/zero,这是一个字符设备,它会不断地返回0值;如果不指定if的值,dd默认会从stdin读取输入,同样,如果不指定of的值,dd会将stdout作为默认输出。上面dd命令的输出结果,我们还看到了这样一条数据:

1
1048576 bytes (1.0 MB) copied, 0.00157623 s, 665 MB/s

意思也很容易都看出来,由此,我们还可以用dd命令传输大量的数据,来测试硬盘的读写速度,上面的命令里”if=/dev/zero”不产生IO,所以可以用来测纯写速度;同样,用”of=/dev/null”可以测试纯读速度。当然,前提都是要传输大量的数据。

##三、创建环回文件系统

①创建一个1GB大小的文件:

(C)

②格式化这个1GB的文件:

1
mkfs.ext4 file.img

(D)

③将文件格式化为ext4文件系统,然后我们可以检查下文件系统:

(E)

##四、挂载环回文件

到了挂载这一步就和我们平常挂载设备文件基本上一样了,但挂载也分几种的:

①下面是一种比较偷懒的挂载方法,我们并没有连接到哪个设备上,在内部,这个环回文件会连接到/dev/loop1或者/dev/loop2的设备上,这时候就需要手动连接设备了。

1
2
3
4
mkdir /mnt/loop   
mount -o loop file.img /mnt/loop
losetup /dev/loop1 file.img
mount /dev/loop1 /mnt/loop

②上面的方法并不适用于所有的场合,比如我们创建了一个硬盘文件,然后对这个硬盘文件分区并要挂载其中的某个分区,就不能使用mount -o loop了,需要使用下面的方法了。

1
2
losetup /dev/loop1 file.img   
fdisk /dev/loop1

在file.img中创建分区并挂载第一个分区:

1
losetup -o 32256 /dev/loop2 file.img

这里,/dev/loop2就是第一个分区了。其中-o表示偏移量,32256字节是针对DOS分区方案的一个设置,第一个分区自硬盘第32256字节之后开始。同样,我们可以这样设置第二个分区。要卸载的话,就是umount mount_point.

莫忘初心

发表于 Oct 26 2013   |   分类于 感悟   |  

图片来自ongsixiao

不要在迷失的道路上停留太久,不要让自己对未来的迷惘变成习惯;不能与优秀同行,也要时常低头,看看脚下的路。莫忘初心。

自从越来越多地使用微博、微信,真的是已经好久好久没再写过长文了。如今,经常地突然脑子里感觉有很多很多的东西想要记录,打开博客,刚一点开编辑器,却怎么也不知道该怎么开始。现在想想以前每天写博客的日子,感觉好像是上个世纪的事情。如今,也真的是好久没有静下心来看过一本完整的书了,一直在买书,从来没看过。可能懒惰真就是人的本能,完全不用学习就可以把懒惰的特质发挥到完美到极致。

越来越多地接触这个社会,遇到越来越多的人,关于自己,却从来没有一个交待。每次看到别人类似的自我反省的文章,心里总是略显不屑地呵呵一笑:俗不可耐!却从来没想到过自己还远不如人家,连自省的勇气甚至觉悟都没有。身处这个浮躁的社会里,想要努力地沉淀自己从来都不是一种奢求,而是一种勇气;又有谁愿意承认自己是一个懦弱到害怕直面自己的人?从来都没人阻止自己去追求内心的宁静,除了自己的懦弱和不屑。

在某些方面,我自认为是一个追求完美的人,有一些心灵上的洁癖;也许每一个拥有“追求完美”这种特质的人,都会不自觉地有种孤芳自赏的清高。这“清高”本就是那么的完美,像一块无瑕美玉,让人不舍得伸手去触碰,生怕给美玉留下污点;然而这“清高”终究是一种只存在于自我陶醉的世界里,终有一天必须要看到自己之外的世界。经常看到有人自我反省说:“是不是跑得太快了,以至于忘记了当初的那份纯粹?”再看看自己,恐怕自己连跑都没有跑吧!

这么久以来,自己经历了很多的困难,越来越感觉到生存的不易,一次次地被欺骗,被愚弄,每次总要到跌得遍体鳞伤的时候,就开始无休止地抱怨。一直都是在毫无意义的抱怨,从来没想过是否自己的初衷就是错的,从来都没有想过为什么总是自己,从来没想过自己是不是已经忘记低头看看所处的位置了。一直以来,都仿佛下了一个很大的赌注一般,凭着年少轻狂的那股倔强,毫无目的也毫无顾忌地往前奔,早已经忘记了自己真正想要的到底是什么,甚至已经在与自己的初心完全背道而驰的了。

如今,周围全都是优秀的人,看到别人的优雅,欣赏别人的优雅,多么渴望自己也能优雅起来!意识到迷失了,还是要尽快走出来继续上路,要继续走下去,还要一直走下去。这很难,可这是自己的初心。

JavaScript实现简单的表单验证

发表于 Sep 29 2013   |   分类于 其他   |  

最近开始学习一些前端的知识了,主要有HTML、CSS、JavaScript、DOM、XML等,东西都不难,只是这茫茫多的标签、属性、对象、方法…真的是好烦好烦啊~每写一小段都要翻半天W3的帮助文档,因为不熟悉,所以还不一定能找得到,不过总算是能自己写一些简单的小程序出来。稍微有一点逻辑性的东西主要集中在JS了,对这些新东西,还是有必要记录一下。于是就写了一个简单的表单验证,一方面巩固一下学习的东西,一方面以后要用到表单验证,应该能直接拿来用了。

##用HTML绘出表单##

既然是表单验证,首先肯定是要创建表单,这部分与JS也没什么关系,主要就是HTML标签的堆砌,主要就考察对标签及其属性的熟练程度了。当然如果对CSS很精通的话,可以做出来非常好看,效果很炫的表单,我压根也就没用CSS,单纯地实现了功能。代码如下:

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
<html>
<head>
<title>FormConfirm</title>
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="表单验证">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<form action="#" method="get" onsubmit="return check()" name="form">
<table border="1px" align="center" style="width:700px" cellspacing='0'>
<tr>
<th style="width:200px;height:35px" colspan="2">用户注册</th>
</tr>
<tr>
<th style="width:200px;height:35px">用户名</th>
<td>
<input type="text" style="width:200px" name="userName" />
</td>
</tr>
<tr>
<th style="width:200px;height:35px">密码</th>
<td>
<input type="text" style="width:200px" name = "password" />
</td>
</tr>
<tr>
<th style="width:200px;height:35px">年龄</th>
<td>
<input type="text" style="width:200px" name = "age" />
</td>
</tr>
<tr>
<th style="width:200px;height:35px">性别</th>
<td>
<input type="radio" name = "sex" value="0" checked/>男
<input type="radio" name = "sex" value="1"/>女
</td>
</tr>
<tr>
<th style="width:200px;height:35px">爱好</th>
<td>
<input type="checkbox" name="hobby" value = "0" >阅读
<input type="checkbox" name="hobby" value = "1" >旅行
<input type="checkbox" name="hobby" value = "2" >体育
<input type="checkbox" name="hobby" value = "3" >音乐
<input type="checkbox" name="hobby" value = "4" >电影
</td>
</tr>
<tr>
<th style="width:200px;height:35px">学历</th>
<td>
<select name="education">
<option value="" selected>- 请选择 -</option>
<option value="0" >大专</option>
<option value="1" >本科</option>
<option value="2" >硕士</option>
</select>
</td>
</tr>
<tr>
<th style="width:200px;height:35px" colspan="2">
<input type="submit" value="注册"/>
</th>
</tr>
</table>
</form>
</body>
</html>

<script type="text/javascript">

</script>

画出了个模子,接下来就是重头戏了,用JS实现表单的验证。JS代码可以放在html结束标签后面,也可以通过外部引入的方式引入,我这里是直接写在了html结束标签的后面。

##验证之前的分析##

在做验证写代码之前,我们先要做一些准备工作,理清思路,然后根据思路去一步步实现。

  • 首先,我们要明确这表单验证需要验证哪些内容,很明显,这里需要验证“用户名”“密码”“年龄”“性别”“爱好”“学历”,一共是这六项内容;
  • 然后,明确验证失败后我们得到怎样的提示;
  • 为了减少工作量,我们可以分析一下这么多的验证里,在实现上某几项有什么共性,如果有共性,怎么把这些共性抽取出来。

在这里我希望得到的失败提示是在各项的后面出现红色字体的错误提示,其中“爱好”比较特殊,因为是复选框,若失败直接alert弹出错误提示即可;还有“用户名”“密码”“年龄”这三项在鼠标移出输入框就进行判断,如果与期望值不符,就提示错误。明确前面的东西,就很容易想到他们的共性,在“用户名”“密码”“年龄”“学历”验证失败后,都需要在右侧出现红色的错误提示。

##实现被验证项的共性##

可以定义一个方法实现错误提示的共性,共性的错误提示中不同之处分别是错误提示出现的位置和错误提示的内容,因此可以将出现的位置(where)和提示内容(what)作为方法的两个参数传进去。红色错误提示即红色文本,可以创建一个font标签再设置其颜色属性来实现。当然,当用户输入正确信息后,要删除这些错误信息,删除错误信息只需要明确错误信息出现的位置(where)即可。明确这些,这个方法就容易写了。

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
//验证错误,添加错误信息
function addError(where,what){
//获取通过父节点的font子节点数组,如果font数组的长度为0,则创建font标签
if(!where.parentNode.getElementsByTagName("font").length){
//创建font标签 并添加提示信息
var font = document.createElement("font");
//设置font标签的颜色
font.setAttribute("color","red");
//设置font标签的内容
font.innerHTML = what;
//将font标签添加到td上
where.parentNode.appendChild(font);
}
}


//验证正确后,删除错误信息
function removeError(where){
//存在错误信息,获取父节点的子节点数组,此数组只有一个元素,因此font标签为arr[0]
var font = where.parentNode.getElementsByTagName("font")[0];
//如果设置错误信息的font标签存在,就删除这个font标签
if(font){
font.parentNode.removeChild(font);
}
}

OK,到这里,就实现了添加删除错误信息的方法,接下来验证“用户名”“密码”“年龄”“学历”的时候,就可以直接调用这两个方法了。

##验证用户名##

验证用户名,其实就是拿用户输入的信息与期望用户输入的信息进行比较,因此首先需要有一个预定义的正则表达式,规定用户必须按照这个正则来输入信息。如果校验通过,就返回true,如果有错误信息,就删除错误信息;如果校验不通过,就返回false,并添加上错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//验证用户名
function checkUserName(){
//1 根据验证规则创建正则表达式
var nameReg = /^[a-zA-Z][a-zA-Z0-9_]{5,14}$/g;
//2 获得用户填写的用户名,这里可以直接使用document.form来获取表单对象
var userName = document.form.userName.value;
//3 校验
if(nameReg.test(userName)){
//调用removeError方法,传入where方法
removeError(document.form.userName);
return true;
}else{
//调用addError方法,传入where和what对应的实际参数
addError(document.form.userName,"用户名首字母必须为英文,长度必须在6~15之间");
return false;
}
}

//当鼠标移出输入框,即失去输入框焦点时验证输入,调用checkUserName方法
document.form.userName.onblur =function(){
checkUserName();
}

##验证密码、年龄和学历##

实现了用户名的校验,那密码、年龄和学历这三项的验证就简单多了,除了参数跟验证用户名的不一样,其他的基本一样。

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
//验证密码
function checkPassWord(){
//验证密码的正则表达式
var pwdReg = /[a-zA-Z0-9_]{6,15}/g;
var password = document.form.password.value;
if(pwdReg.test(password)){
removeError(document.form.password);
return true;
}else{
addError(document.form.password,"密码长度必须在6~15之间");
return false;
}
}
document.form.password.onblur = function(){
checkPassWord();
}

//验证年龄
function checkAge(){
var age=document.form.age.value;
if(+age >= 18 && age <= 120){
removeError(document.form.age);
return true;
}else{
addError(document.form.age,"输入的年龄必须在18~120之间");
return false;
}
}
document.form.age.onblur = function(){
checkAge();
}

//验证学历
function checkEdu(){
var index=document.form.education.value;
if(index){
removeError(document.form.education);
return true;
}else{
addError(document.form.education,"请选择学历");
return false;
}
}

验证学历就不需要再考虑失去焦点时验证的问题了,只需要提交时验证即可。

##验证爱好##

只剩下最后一项验证爱好,同样是提交时后验证。由于“爱好”是复选框,有多个name属性,因此无法使用document.form.attribution来获取属性,这时就可以使用JavaScript警告提示框来输出错误信息了,当然获取habit属性也只能使用document的getElementsByName方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//验证爱好
function checkHabit(){
var habits=document.getElementsByName("habit");
//创建一个计数器
var count = 0;
//遍历habits数组
for ( var i = 0; i < habits.length; i++) {
//当复选框被选中一个,计数器就加1
if (habits[i].checked) {
count++;
}
}
//如果计数器大于等于2,就返回true,否则就使用js警告框输出警告信息
if (count>=2) {
return true;
}else {
alert("请至少选择两个爱好");
return false;
}
}

##返回验证结果##

最后一步就是返回验证结果了。调用之前定义的验证各项的方法,任何一项验证不通过都要返回false,即不提交表单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function check(){
var flag=true;
if(!checkUserName()){
flag=false;
}
if(!checkPassWord()){
flag=false;
}
if(!checkAge()){
flag=false;
}
if(!checkEdu()){
flag=false;
}
if(!checkHabit()){
flag=false;
}
return flag;
}

Android添加桌面widget插件

发表于 Sep 23 2013   |   分类于 Android   |  

##Widget的概述
可以添加桌面Widget是Android的一大特色,AppWidget是在HomeScreen上显示的小部件,提供直观的交互操作,有许多的表现形式,如视频、音乐、地图、新闻、游戏等等,它的根本思想来源于代码的复用。通过在HomeScreen长按,在弹出的对话框中选择Widget部件进行构建,长按部件并拖动到垃圾箱可以删除。

Android中的Widget主要设计以下几个类:

  • AppWidgerProvider
  • AppWidgetManager
  • RemoteViews

Widget的核心是AppWidgetProvider,属于广播接收者,是四大组件之一,在AppWidgetProvider这个类中,一共有五个方法,其中有一个onReceive(Context, Intent)方法,分别调用了其他四个空实现的方法onEnabled(Context context),onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds),onDeleted(Context context, int[] appWidgetIds),onDisabled(Context context),而被调用的这四个方法刚好就是一个Widget的生命周期。

RemoteView是用来描述一个跨进程显示的View,也就是说这个View是在另外一个进程中显示的,它可以获取layout的布局文件,并且提供了可以修改View内容的一些简答操作。AppWidgetProvider提供Widget的生命周期,真正显示界面的是AppWidgetHostView(这是在Launcher中实现的),这中间就是通过RemoteView来沟通。通过RemoteView来告诉Launcher你想要的AppWidget长什么样。

##如何创建一个Widget

###配置清单文件
要创建一个Widget,第一步就是要在清单文件中配置:

1
2
3
4
5
6
7
<receiver android:name="ExampleAppWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />

</receiver>

###定义Widget的生命周期
然后要写一个类继承AppWidgetProvider,并重写AppWidgetProvider的代表生命周期的四个空方法:

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
package com.example.widget1;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class ExampleAppWidgetProvider extends AppWidgetProvider {

private static final String TAG = "ExampleAppWidgetProvider";

// 可用
@Override
public void onEnabled(Context context) {
// TODO Auto-generated method stub
super.onEnabled(context);
Log.i(TAG, "onEnabled");
// 启动服务
context.startService(new Intent(context, MyService.class));
}

// 更新
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds)
{

// TODO Auto-generated method stub
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.i(TAG, "onUpdate");
}

// 删除
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// TODO Auto-generated method stub
super.onDeleted(context, appWidgetIds);
Log.i(TAG, "onDeleted");
}

// 不可用
@Override
public void onDisabled(Context context) {
// TODO Auto-generated method stub
super.onDisabled(context);
Log.i(TAG, "onDisabled");
// 停止服务
context.stopService(new Intent(context, MyService.class));

}
}

###定义Widget的基本属性
然后呢,就需要提供一个XML文件来定义Widget的基本属性(位于res/xml/..)目录下,右键新建Android资源文件,选择类型AppWidgetProvider即可。

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<!-- android:minWidth="294dp" 宽
android:minHeight="72dp"高
android:initialLayout="@layout/example_appwidget" widget的布局
-->

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:initialLayout="@layout/example_appwidget"
>

</appwidget-provider>

###定义Widget的布局
接着需要提供一个Widget界面布局的XML文件(位于res/layout/…),需要注意的是该XML使用的组件必须是RemoteViews所支持的,目前原生API支持的组件如下:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • AnalogClock
  • Button
  • Chronmeter
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView

如果使用这些组件之外的组件,在Widget创建的时候,会出现一个android.view.InflateException的异常。

因此这也就导致一些功能或者样式无法实现,如很基本的List或文本编辑都是无法直接实现的,如果想自定义Widget中的View的话只能通过修改Framework来提供相应组件的支持。

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
android:id="@+id/tv_time"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="这是widget桌面插件"
android:background="#f00"
android:textColor="@android:color/black"
android:gravity="right"/>

<Button
android:id="@+id/bt_open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="复制号码到拨号盘"/>

</RelativeLayout>

###Widget动态实例

接下来,可以实例实现一个Widget显示系统时间和复制号码到拨号盘。

一般Widget所显示的内容都是需要动态变化的,因此需要开启服务在后台操作。AppWidgetManager就是负责更新Widget的内容,AppWidgetManager类中提供一个获取实例的方法,getInstance(Context context),AppWidgetManager也提供了三个更新Widget的方法,由于AppWidgetProvider是一个广播接收者,很显然,需要传递一个组件进去,所以在这里我需要选择updateAppWidget(ComponentName provider, RemoteViews views)这个方法来对AppWidget进行更新操作。在上面继承的AppWidgetProvider类,在Widget的生命周期方法中,已经做了启动和停止服务的操作,然后我只要写一个MyService类继承Service即可,当然,后台服务需要开启线程,要重写run()方法。

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
package com.example.widget1;

import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.widget.RemoteViews;

public class MyService extends Service {

// 定时器
Timer timer = null;
TimerTask task = new TimerTask() {

@Override
public void run() {
// TODO Auto-generated method stub
// 更新app widget
// 使用AppWidgetManager 更新
// App Widget管理器
AppWidgetManager awm = AppWidgetManager
.getInstance(getApplicationContext());
// 组件
ComponentName provider = new ComponentName(getApplicationContext(),
ExampleAppWidgetProvider.class);
// 远端布局(不在activity上的布局)
RemoteViews views = new RemoteViews(getPackageName(),
R.layout.example_appwidget);
// 获取系统当前的时间
long millis = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat(
"yyyy-MM-dd hh:mm:ss");
// 格式化系统时间
String text = format.format(millis);
// 设置显示内容
views.setTextViewText(R.id.tv_time, text);

// 复制号码到拨号盘
Intent intent = new Intent();
intent.setAction(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:8888"));
PendingIntent pendingIntent = PendingIntent.getActivity(
getApplicationContext(), 100, intent, 0);
// 事件
views.setOnClickPendingIntent(R.id.bt_open, pendingIntent);

// 更新
awm.updateAppWidget(provider, views);
}
};

@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
timer = new Timer();
// task任务 delay延时执行 period 周期
timer.schedule(task, 0, 500);

}

@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}

@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
// 取消任务
timer.cancel();
}

}

这样一个简单的桌面插件显示系统时间和复制号码到拨号盘的小程序就完成了。

怎样拒绝邻居想要蹭WiFi的请求

发表于 Sep 19 2013   |   分类于 随笔   |  

刚把wifi密码告诉了斜对面的哥们儿。现在来回答下这个问题。

在郑州被深深地伤害之后,彻底对郑州失望,就只身来南京了,7月份过来的。以前没来过南京,当初只是因为想赶紧逃离,比较讨厌北京,又不想去上海,广州也玩儿够了,觉得无趣,最后一冲动就跑来南京了。等到来了南京,发现原来南京还有个同学,着实帮我了不少;不过工作确定之后,我们就分开了,距离非常远。我来到了一个人也不认识的地方。租房的问题解决之后,感觉一切都蛮好,比想象中的顺利。只是觉得,有点孤单。

跟人合租的房子,房子挺大,不过是五个小居室,住的人挺多,一个客厅,房租很便宜(这是我最看重的)。平常早上上班离开家,锁上门;下班回到家,再把们一关,谁也不认识谁。一个人躺在床上,盯着天花板,说不出的孤独;刚去的日子,晚上还给家人朋友打电话或者打一盘DotA,后来慢慢的就不想再打了。想找个人说话,却没有。

直到一天早上,去洗漱,碰见斜对门的兄弟,突然觉得不说点什么,好像很尴尬。就异常别扭地挤出来个“早”。从那以后,貌似碰见的次数也越来越多了。斜对门的大哥跟老婆一起住,跟我一样,都是去年毕业,俩人大学谈恋爱四年,毕业之后立马就领证了。我刚来那段时间,兄弟在为工作的事忙活,现在是已经确定了要自己创业。刚毕业、刚结婚的小青年儿夫妻俩相濡以沫,日子过的不富裕,不过倒是有说不出的温馨。

一天晚上,下班之后去买了西瓜和方便面,前一天特意从易迅上买了个电热水器(不得不说易迅的物流实在是太给力了,上午下单,下午两点多就送到了),准备回家烧水吃泡面。回到家,面对一个大西瓜,突然发现,原来自己没有刀,没法切啊…怎么办?最后硬着头皮,敲小夫妻的门,借来了刀,切了西瓜,想把西瓜送给小夫妻俩,捧着切好的西瓜站在他们门口半天,不敢敲门。突然,门开了,看见小嫂子端着饺子…场面真有趣。最后,我用西瓜“换了”饺子。心里说不出的温暖。从那之后,我们就经常相互送好吃的;都是属于面对陌生人那种有点闷的人,所以也就是敲门,把东西往对方手里一送,转身就撤;不说话的。

昨天晚上,我给家里人打电话,说是中秋放假三天不回去了,本来打算十一回去,结果十一要加班,也就没法回去了。刚打完电话,有人敲门,开门一看,是斜对面的大哥。“兄弟,我看你这过节好像也是一个人,我叫了几个朋友,明天过来一起过节,你明天也跟我们一起过吧!”突然,我也不知道怎么回答,愣了有十几秒,尴尬地笑着,憋出来一句,“那就太谢谢了啊……”霎那间,暖流直窜脑门!今天夫妻俩做了菜,非常丰盛哦,有八个菜,我们几个吃吃玩玩,愉快地度过了这一天。整个席间,我都被一股股暖流冲击着!

刚收了摊子。去兄弟屋里坐了一会儿,聊天,发现他们屋里没有路由器,是网线直接插电脑上用的。他们夫妻俩都是教育行业的,对IT相关的基本一窍不通,我正对门的是俩小姑娘,由于我们住的屋子里在我去之前只有一条4M的宽带,网口在俩小姑娘屋里。我来了之后,又从外面拉了一条10M的宽带。两个小姑娘告诉小夫妻俩不能在他们屋里放路由器,而且不能从外面拉网线,夫妻俩就信了,还跟她们共同分担宽带费用,我瞬间明白了一切。不过没给他们夫妻俩说,我把自己的WiFi密码告诉了他们,然后把他们的笔记本设置了一下,做了个无线热点。

呀!写到这里,突然想起来,在一起这么长时间,还不知道他们名字和其他的任何联系方式呢…

一个人在外面,真心是不容易。每个人都是那么地平凡,自己的故事也同样发生在这个城市其他一百万人身上,但是与这么多善良的人萍水相逢,即使明天就要别离,孤单和伤感也会因为这暖心默默远去。谢谢你们,每一个人。一个WiFi密码,换来的也许是更多无法用金钱衡量的东西。

原文链接:不熟悉的邻居来敲门问我的 Wi-Fi 密码,说只是平时用下上网,我该怎么回绝?

Java1.5新特性

发表于 Aug 14 2013   |   分类于 Java   |  

“JDK1.5”(开发代号猛虎)的一个重要主题就是通过新增一些特性来简化开发,这些特性包括泛型,for-each 循环,自动装包/拆包,枚举,可变参数,静态导入.使用这些特性有助于我们编写更加清晰,精悍,安全的代码.

下面我们简单介绍一下这些新特性.

##泛型(Generic)##

C++通过模板技术可以指定集合的元素类型,而Java在1.5之前一直没有相对应的功能.一个集合可以放任何类型的对象,相应地从集合里面拿对象的时候我们也不得不对他们进行强制类型转换.”猛虎”引入了泛型,它允许指定集合里元素的类型,这样你可以得到强类型在编译时刻进行类型检查的好处.

1
2
Collection<String> c = new ArrayList();
c.add(new Date());

编译器会给出报错.

##For-Each循环##
For-Each循环得加入简化了集合的遍历.假设我们要遍历一个集合对其中的元素进行一些处理.典型的代码为:

1
2
3
4
5
6
void processAll(Collection c){
for(Iterator i=c.iterator(); i.hasNext();){
MyClass myObject = (MyClass)i.next();
myObject.process();
}
}

使用For-Each循环,我们可以把代码改写成:

1
2
3
4
void processAll(Collection<MyClass> c){
for (MyClass myObject :c)
myObject.process();
}

这段代码要比上面清晰许多,并且避免了强制类型转换.

##自动装箱/拆箱(Autoboxing/unboxing)##

自动装箱/拆箱大大方便了基本类型数据和它们包装类地使用.

  1. 自动装箱:基本类型自动转为包装类.(int >> Integer)
  2. 自动拆箱:包装类自动转为基本类型.(Integer >> int)

在JDK1.5之前,我们总是对集合不能存放基本类型而耿耿于怀,现在自动转换机制解决了我们的问题.

1
2
3
4
5
int a = 3;
Collection c = new ArrayList();
c.add(a);//自动装箱,int转换成Integer.
Integer b = new Integer(2);
c.add(b + 2); //自动拆箱,Integer转换成int

这里Integer先自动转换为int进行加法运算,然后int再次转换为Integer.

##枚举(Enums)##

JDK1.5加入了一个全新类型的“类”-枚举类型.为此JDK1.5引入了一个新关键字enmu.我们可以这样来定义一个枚举类型.

1
2
3
4
5
public enum Color{
Red,
White,
Blue
}

然后可以这样来使用Color myColor = Color.Red.

枚举类型还提供了两个有用的静态方法values()和valueOf(). 我们可以很方便地使用它们,例如

1
2
3
for (Color c : Color.values()){
System.out.println(c);

}

##可变参数(Varargs)##

可变参数使程序员可以声明一个接受可变数目参数的方法.注意,可变参数必须是方法声明中的最后一个参数.假设我们要写一个简单的方法打印一些对象:

1
2
3
util.write(obj1);
util.write(obj1,obj2);
util.write(obj1,obj2,obj3);

在JDK1.5之前,我们可以用重载来实现,但是这样就需要写很多的重载方法,显得不是很有效.如果使用可变参数的话我们只需要一个方法就行了

1
2
3
4
5
public void write(Object... objs) {
for (Object obj: objs){
System.out.println(obj);
}
}

在引入可变参数以后,Java的反射包也更加方便使用了。对于c.getMethod(“test”, new Object[0]).invoke(c.newInstance(), new Object[0])),现在我们可以这样写了c.getMethod(“test”).invoke(c.newInstance()),这样的代码比原来清楚了很多.

##静态导入(Static Imports)##

要使用用静态成员(方法和变量)我们必须给出提供这个方法的类.使用静态导入可以使被导入类的所有静态变量和静态方法在当前类直接可见,使用这些静态成员无需再给出他们的类名.

1
2
3
import static java.lang.Math.*;
……
r = sin(PI * 2); //无需再写r = Math.sin(Math.PI);

不过,过度使用这个特性也会一定程度上降低代码地可读性.

使用cwRsync同步静态资源

发表于 Jun 7 2013   |   分类于 Server   |  

最近一直在加班,加班,加班……前几天某个风雨交加的夜里,辗转反侧实在睡不着,粗略算了一下,四月份休息了两天半,五月份休息了三天半,每天晚上基本上也都是九点之后才到家,有时候还会通宵加班。一直以来我都没细致地去想过自己现在为什么这么拼命,现在想来,突然发现自己不知不觉地捡起来了曾经戒掉的理想,发现自己有意愿好像也有能力为曾经的不甘心扬眉吐气,发现自己为了证明自己的价值给人看对自己是如此地重要!因为如此,我才愿意在自己不是太在意金钱的年纪里忍受如此的落差,虽然看上去很大程度上是在为别人活,但这样我挺满足的。

虽说是这样,可心里却并不快乐。今天有同事问我是不是觉得很充实,我想了一下,迸出来一句:我觉得很累……然后就真的好像虚了很多,也确实想放松放松了……扯了这么多,偏题严重了,写博客的频繁程度,其实很大程度上是与自己的懒惰程度成反比的,平时说自己比较忙,没时间写,其实是自欺欺人。昨天刚做了个静态资源的同步,今天先记录一下。

cwRsync是一个跨平台的开源文件同步(备份)软件,简单好用。我的环境,Server端和Client端都是Windows 2008 Server R2,服务端IP是10.0.0.4,客户端IP是10.0.0.2。

服务端和客户端软件在官网都可以下载到,直接双击也就安装完成了,非常简单。需要指出的一点是安装Server端的时候,有一个创建用户的对话框,因为我的环境是2008R2,所以设置的密码复杂度一定要符合2008R2的标准,否则服务安装不上,而且没有任何提示。我创建的用户名是FileSync,密码是1Qaz2Wsx.

Server:

先说服务端的配置,主要就是rsyncd.conf这个配置文件,内容很容易理解,也就不一一解释了,我要同步的是D盘的image和Upload两个文件夹;

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
usechroot =false

strict modes =false

hosts allow = *

log file = rsyncd.log

pid file = rsyncd.pid

port =800

max connections =4

UID=0

GID=0

# Module definitions

# Remember cygwin naming conventions : c:\work becomes /cygwin/c/work

#

#[test]

#path = /cygdrive/c/work

#read only =false

#transfer logging = yes

[image]

path = /cygdrive/d/image

read only =false

transfer logging = yes

lock file = rsyncd.lock

auth users =FileSync

secrets file = etc/rsyncd.secrets

[Upload]

path = /cygdrive/d/Upload

read only =false

transfer logging = yes

lock file = rsyncd.lock

auth users =FileSync

secrets file = etc/rsyncd.secrets

2、创建密码文件rsyncd.secrets并放入etc文件夹,格式为“user:passwd”,如“FileSync:1Qaz2Wsx”;

3、修改防火墙配置,将配置文件里配置的800端口打开;

4、修改要同步的D盘的image和Upload两个文件夹的权限,添加用户FileSync对两个文件夹为完全控制;

至此,服务端配置完毕。

Client: 客户端的配置更简单,一个脚本就可以完成全部的同步任务。

1、新建脚本rsync.bat,我的脚本内容如下:

1
2
3
4
5
6
7
8
9
10
@echo off
echo.
echo 开始同步数据,请稍等...
echo.
cd C:\Program Files (x86)\cwRsync\bin
rsync -vzrtopg --port=800 --progress --delete FileSync@10.0.0.4::image /cygdrive/d/image < passwd.txt
rsync -vzrtopg --port=800 --progress --delete FileSync@10.0.0.4::Upload /cygdrive/d/Upload < passwd.txt
echo.
echo 数据同步完成
echo.

2、新建密码文件passwd.txt,输入密码1Qaz2Wsx,将rsync.bat和passwd.txt放置于同一目录下;

3、开启服务端的RsyncServer服务,执行脚本就可以完成同步任务,如果需要定时同步,添加Windows计划任务即可。

Linux下安装配置nginx+php+mysql环境

发表于 Apr 29 2013   |   分类于 Linux   |  

由于最近比较忙,写的比较简略,步骤不够详细,不过基本按着这步骤顺下来,也不会出什么问题,先记录下来,以后有空再丰富下内容。

1
yum -y update

yum install gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel //安装依赖包

/usr/sbin/groupadd nginx

/usr/sbin/useradd -g nginx -M nginx

mkdir -p /var/tmp/nginx/client //创建用户

yum -y install php mysql mysql-server mysql-devel php-mysql php-cgi php-mbstring php-gd php-fastcgi //安装php和mysql

rpm -ivh http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

//启用redhat附件包

yum install nginx //安装nginx

chkconfig nginx on //设置开机启动

yum install spawn-fcgi //安装spawn-fcgi来运行php-cgi

wget http://bash.cyberciti.biz/dl/419.sh.zip

unzip 419.sh.zip

mv 419.sh /etc/init.d/php_cgi

chmod +x /etc/init.d/php_cgi // 获取spawn-fcgi 的启动脚本

service php_cgi start //启动php_cgi

location ~ \.php$ {

root /var/www; //网站根目录

fastcgi_pass 127.0.0.1:9000;

fastcgi_index index.php;

fastcgi_param SCRIPT_FILENAME /var/www$fastcgi_script_name;

include fastcgi_params;

} //配置nginx.conf

(root指定网站根目录;fastcgi_param指定放置PHP动态程序的主目录,即$fastcgi_script_name前面的路径)

yum install phpmyadmin //安装phpmyadmin

chmod +x /var/lib/php/session //修改/var/lib/php/session和php_cgi权限一致

chown -R nginx.nginx /var/lib/php/session

将网站目录放在/var/www目录下,访问http://IP/网站目录即可。

Linux下拷贝MySql数据库文件备份数据库

发表于 Apr 11 2013   |   分类于 DataBase   |  

大致上MySql数据库备份可以采用两种方式:一种就是直接导出sql语句或者易于导入的其他格式的sql存储文件,使用sql语句或者一些可视化客户端导出,这种方法非常简单,无需赘述;另一种方法就是拷贝出数据库文件,再将数据库文件转换成sql文件,这篇文章就介绍一下这种方法。

①查找数据库文件存放位置,MySql文件的后缀有三种形式.MYD、.MYI、*.frm;

1
find / -name *.MYD

②进入数据库存储文件目录;

1
cd /opt/lanmp/mysql-5.1.63/win/data/

③使用copy命令把所有数据拷出去;

1
copy mysql /home

④此时只需将这些数据库文件转换成sql文件,这也是这篇文章里我要讲的重点,如果需要的时候再将sql文件用命令导入即可;

⑤新建一个数据库命名为mysqlbak,将刚才拷出来的文件夹拷入新数据库的data文件夹;

1
2
3
find / -name mysqlbak
cd /home/mysql
copy * /www/wdlinux/mysql-5.1.63/var/mysqlbak/

⑥此时就已经可以看到新数据库mysqlbak里的数据了,跟原数据库mysql里的数据是一样的;

⑦使用命令导出数据库文件即可。

1
mysqldump -uroot -p mysqlbak /home/mysql.sql

⑧这home文件夹下的文件mysql.sql文件就是易导入的数据库文件。需要注意的是所有的操作都必须在操作系统下用命令完成,而不提倡使用工具。因为在实际操作过程中,我们经常会遇到数据库里的数据库文件版本和数据库版本不一致,或者新旧数据库版本不一致的问题,再或者数据服务器版本与客户端工具不兼容等意想不到的问题,如果是这种情况,使用工具导出一般会报“1577:Cannot proceed because system tables used by Event Scheduler were found damaged at server start”的错误。说到这里,其实本文也提供了一种打开mysql数据库文件的方法,如果现在只有一堆.MYD、.MYI、*.frm文件,执行步骤⑤到步骤⑧即可。

1…91011…15
Ailurus

Ailurus

Android Developer

145 日志
13 分类
215 标签
RSS
Github Twitter Facebook Weibo
Links
  • 明哥
  • veaer
  • 程序亦非猿
  • 阿布
  • 区长
  • 卡老师
© 2020 Ailurus
由 Hexo 强力驱动
主题 - NexT.Pisces