前言

Hexo是一个基于nodejs的轻量级博客平台,由于它的简单、主题丰富等特点,收获了大量的拥趸。本博客也是基于Hexo,但是在使用Hexo部署和运营博客的过程中,发现了一些小问题。

问题

1、大部分使用Hexo的博主,都是把代码提交到github仓库。而且为了在不同环境都能方便编写博客,通常会把博客的源码(即博客配置文件,主题配置文件,样式布局文件等)和Hexo生成的代码(即public文件下的代码),同时提交到github仓库的不同分支上,如此在新环境下只要把博客源码check out下来即可。在这种情况下,我们博客配置文件的一些敏感信息,例如第三方插件需要的token,都会明码提交到公共仓库上,有一定的安全风险; 2、在做百度SEO的过程中,最有效的链接提交方式是主动提交,虽然Hexo有相关的插件,但是因为插件是注册到Hexo中的,耦合太重,使用不太灵活。 3、一些个性化的需求,例如本博客同时部署在了七牛云,有时候想看一下代码有没有正确同步,需要查看本地的文件数量和大小与七牛云上的是否相同。

解决

作为程序猿,问题有了,剩下的就是写代码解决了。由于Hexo是nodejs实现的,所以使用nodejs来解决这些问题应该是最符合的,可是nodejs并不在本人的技术栈中(- -)。人生苦短,我用Ptyhon,那么就使用简单的脚本语言Python来解决吧。

— talk is cheap show me the code —

配置文件加解密

这里使用rc4加密算法。

 1def rc4(p_key, key_len, pin, d_len):
 2    n = 65536
 3    s = list(range(n))
 4    j = 0
 5    for i in range(n):
 6        j = (j + s[i] + p_key[i % key_len]) % n  # type: int
 7        temp = s[i]
 8        s[i] = s[j]
 9        s[j] = temp
10    i = j = 0
11    out = b''
12    for x in range(d_len):
13        i = i + 1
14        j = (j + s[i]) % n  # type: int
15        temp = s[i]
16        s[i] = s[j]
17        s[j] = temp
18        out += struct.pack('H', pin[x] ^ s[(s[i] + s[j]) % n])
19    return out
20
21def coding(data):
22    if len(data) % 2:
23        data += b'\0'
24    d_len = len(data) // 2
25    return struct.unpack(str(d_len) + 'H', data)
26
27
28def un_coding(data):
29    d = b''
30    for i in range(len(data)):
31        d += struct.pack('H', data[i])
32    return d
33
34
35def create_key(_key):
36    pl = len(_key)
37    key = b''
38    r = 0
39    for i in range(32):
40        k = (_key[r % pl] + i) % 256
41        key += struct.pack('B', k)
42        r += 1
43    return key
44
45
46def update_key(_key):
47    key = un_coding(_key)
48    # 循环左移
49    key = key[1:] + struct.pack('B', key[0])
50    tem = 0
51    # 求和
52    for i in range(len(key)):
53        tem += key[i]
54    key_o = b''
55    # Xor
56    for i in range(len(key)):
57        key_o += struct.pack('B', (key[i] ^ tem) % 256)
58        tem += key_o[i] >> 3
59        tem = tem % 256
60    return coding(key_o)

有了加密算法,接下来就是对文件的内容进行加解密,首先需要定义一个公共的密码用于生成密钥:

1cipher = "随便写"

下面的方法使用到colorama,为了在控制台输出颜色,可以自己取舍是否需要。 加密文件:

 1def encrypt_file(file):
 2    a = file
 3    fin = open(a, 'rb')
 4    fout = open(a + "-encrypt", 'wb')
 5    shutil.copy(a, a + ".bak")
 6    key = coding(create_key(cipher.encode()))
 7    key = update_key(key)
 8    fin.seek(0, 2)
 9    filelen = fin.tell()
10    print(Fore.GREEN + 'encrypt ' + file + ', length: ' + str(filelen) + '\n......')
11    fin.seek(0, 0)
12    fout.write(struct.pack('I', filelen))
13    while 1:
14        dd = fin.read(65534)
15        if not dd:
16            break
17        srl = len(dd)
18        if srl % 2:
19            srl += 1
20            dd += b'\0'
21        crc = struct.pack('I', binascii.crc32(dd))
22        dd = coding(dd)
23        x = rc4(key, len(key), dd, len(dd))
24        key = update_key(key)
25        fout.write(struct.pack('H', srl))
26        fout.write(x)
27        fout.write(crc)
28    fin.close()
29    fout.close()
30    shutil.copy(a + "-encrypt", a)
31    os.remove(a + "-encrypt")
32    print(Fore.GREEN + 'OK!\n')

解密文件:

 1def decrypt_file(file):
 2    a = file
 3    fin = open(a, 'rb')
 4    fout = open(a + "-decrypt", 'wb')
 5    key = coding(create_key(cipher.encode()))
 6    key = update_key(key)
 7    filelen = struct.unpack('I', fin.read(4))[0]
 8    print(Fore.GREEN + 'decrypt ' + file + ', length: ' + str(filelen) + '\n......')
 9    while 1:
10        ps = fin.read(2)
11        if not ps:
12            break
13        packsize = struct.unpack('H', ps)[0]
14        dd = fin.read(packsize)
15        dd = coding(dd)
16        x = rc4(key, len(key), dd, len(dd))
17        key = update_key(key)
18        crc = struct.unpack('I', fin.read(4))[0]
19        if binascii.crc32(x) != crc:
20            print('CRC32校验错误!', crc, binascii.crc32(x))
21            sys.exit()
22        fout.write(x)
23    fout.truncate(filelen)
24    fin.close()
25    fout.close()
26    shutil.copy(a + "-decrypt", a)
27    os.remove(a + "-decrypt")
28    print(Fore.GREEN + 'OK!\n')

主动推送百度链接

需要用到的变量:

1# 文章路径
2posts = "source" + os.path.sep + "_posts"
3# 百度中注册的域名
4host = "http://www.ciphermagic.cn/"
5# 百度主动推送接口url
6baidu_url = "http://data.zz.baidu.com/urls?site=你的域名&token=你的token"

原理是获取文章的路径,拼接成url,这里要根据自己的博客配置作相应的修改:

1def submit_baidu_all():
2    urls = ""
3    for file in os.listdir(posts):
4		# 本博客的文章地址是:域名 + 文章名 + html后缀
5        url = host + os.path.split(file)[-1][:-3] + ".html"
6        urls += url + "\n"
7    print(urls)
8    r = requests.post(baidu_url, data=urls)
9    print(r.text)

这里是推送所有的文章,在最后的完整代码中会有推送指定文章的方法,因为需要接收命令行的参数,在最后一起展示,便于阅读。

查看文件数量和大小

 1# 获取文件夹大小
 2def get_file_size(path):
 3    if not os.path.exists(path) or not os.path.isdir(path):
 4        print(path + " is not existed or is not folder")
 5        sys.exit()
 6    total_size = 0
 7    total = 0
 8    try:
 9        filename = os.walk(path)
10        for root, dirs, files in filename:
11            for fle in files:
12                current_path = os.path.join(root, fle)
13                size = os.path.getsize(current_path)
14                total_size += size
15                total = total + 1
16        print(Fore.GREEN + "total files: %d" % total)
17        print(Fore.GREEN + "total size: " + format_size(total_size))
18    except Exception as err:
19        print(err)
20
21
22# 字节bytes转化kb\m\g
23def format_size(_bytes):
24    try:
25        _bytes = float(_bytes)
26        kb = _bytes / 1024
27    except Exception as err:
28        return err
29    if kb >= 1024:
30        m = kb / 1024
31        if m >= 1024:
32            g = m / 1024
33            return "%.2fG" % g
34        else:
35            return "%.2fM" % m
36    else:
37        return "%.2fkb" % kb

完整代码

我们在写博客的时候,通常都是通过Hexo的命令来做一系列的操作,为了保持体验的一致性,我们的python助手也使用命令行操作。操作方式是:

1python hexo-helper.py -c <command> -p <path> [-d]
  • -c 操作命令,例如加密、解密、推送链接、查看文件夹
  • -p 指定文件路径,需要加解密的文件、需要推送链接的文件、需要查看大小的文件夹
  • -d 以默认方式执行,加解密默认操作的文件是网站配置和主题配置文件,链接推送默认是推送所有,查看文件默认是public文件夹

完整代码:

  1import sys
  2import getopt
  3import struct
  4import os
  5import binascii
  6import shutil
  7import requests
  8from colorama import init, Fore
  9
 10# 提示信息
 11help_msg = "Usage:hexo.py -c <command> -p <path> [-d]"
 12# 密码
 13cipher = "你的加密密码"
 14# 网站配置文件
 15config = "_config.yml"
 16# 主题配置文件
 17theme_config = "themes" + os.path.sep + "next_my" + os.path.sep + "_config.yml"
 18# 文章路径
 19posts = "source" + os.path.sep + "_posts"
 20# 域名
 21host = "http://www.ciphermagic.cn/"
 22# 百度主动推送接口url
 23baidu_url = "http://data.zz.baidu.com/urls?site=你的域名&token=你的token"
 24
 25
 26def get_file_num_size(options):
 27    for o, a in options:
 28        if o in "-p":
 29            get_file_size(a)
 30        elif o in "-d":
 31            # 默认获取public文件夹
 32            get_file_size("public")
 33
 34
 35# 获取文件夹大小
 36def get_file_size(path):
 37    if not os.path.exists(path) or not os.path.isdir(path):
 38        print(path + " is not existed or is not folder")
 39        sys.exit()
 40    total_size = 0
 41    total = 0
 42    try:
 43        filename = os.walk(path)
 44        for root, dirs, files in filename:
 45            for fle in files:
 46                current_path = os.path.join(root, fle)
 47                size = os.path.getsize(current_path)
 48                total_size += size
 49                total = total + 1
 50        print(Fore.GREEN + "total files: %d" % total)
 51        print(Fore.GREEN + "total size: " + format_size(total_size))
 52    except Exception as err:
 53        print(err)
 54
 55
 56# 字节bytes转化kb\m\g
 57def format_size(_bytes):
 58    try:
 59        _bytes = float(_bytes)
 60        kb = _bytes / 1024
 61    except Exception as err:
 62        return err
 63    if kb >= 1024:
 64        m = kb / 1024
 65        if m >= 1024:
 66            g = m / 1024
 67            return "%.2fG" % g
 68        else:
 69            return "%.2fM" % m
 70    else:
 71        return "%.2fkb" % kb
 72
 73
 74def submit_baidu(options):
 75    for o, a in options:
 76        if o in "-p":
 77            url = host + os.path.split(a)[-1][:-3] + ".html"
 78            print(url)
 79            r = requests.post(baidu_url, data=url)
 80            print(r.text)
 81        elif o in "-d":
 82            # 默认推送所有链接
 83            submit_baidu_all()
 84
 85
 86def submit_baidu_all():
 87    urls = ""
 88    for file in os.listdir(posts):
 89        url = host + os.path.split(file)[-1][:-3] + ".html"
 90        urls += url + "\n"
 91    print(urls)
 92    r = requests.post(baidu_url, data=urls)
 93    print(r.text)
 94
 95
 96def encrypt(options):
 97    for o, a in options:
 98        if o in "-p":
 99            encrypt_file(a)
100        elif o in "-d":
101            # 默认加密网站配置文件和主题配置文件
102            encrypt_file(config)
103            encrypt_file(theme_config)
104
105
106def decrypt(options):
107    for o, a in options:
108        if o in "-p":
109            decrypt_file(a)
110        elif o in "-d":
111            # 默认解密网站配置文件和主题配置文件
112            decrypt_file(config)
113            decrypt_file(theme_config)
114
115
116def decrypt_file(file):
117    a = file
118    fin = open(a, 'rb')
119    fout = open(a + "-decrypt", 'wb')
120    key = coding(create_key(cipher.encode()))
121    key = update_key(key)
122    filelen = struct.unpack('I', fin.read(4))[0]
123    print(Fore.GREEN + 'decrypt ' + file + ', length: ' + str(filelen) + '\n......')
124    while 1:
125        ps = fin.read(2)
126        if not ps:
127            break
128        packsize = struct.unpack('H', ps)[0]
129        dd = fin.read(packsize)
130        dd = coding(dd)
131        x = rc4(key, len(key), dd, len(dd))
132        key = update_key(key)
133        crc = struct.unpack('I', fin.read(4))[0]
134        if binascii.crc32(x) != crc:
135            print('CRC32校验错误!', crc, binascii.crc32(x))
136            sys.exit()
137        fout.write(x)
138    fout.truncate(filelen)
139    fin.close()
140    fout.close()
141    shutil.copy(a + "-decrypt", a)
142    os.remove(a + "-decrypt")
143    print(Fore.GREEN + 'OK!\n')
144
145
146def encrypt_file(file):
147    a = file
148    fin = open(a, 'rb')
149    fout = open(a + "-encrypt", 'wb')
150    shutil.copy(a, a + ".bak")
151    key = coding(create_key(cipher.encode()))
152    key = update_key(key)
153    fin.seek(0, 2)
154    filelen = fin.tell()
155    print(Fore.GREEN + 'encrypt ' + file + ', length: ' + str(filelen) + '\n......')
156    fin.seek(0, 0)
157    fout.write(struct.pack('I', filelen))
158    while 1:
159        dd = fin.read(65534)
160        if not dd:
161            break
162        srl = len(dd)
163        if srl % 2:
164            srl += 1
165            dd += b'\0'
166        crc = struct.pack('I', binascii.crc32(dd))
167        dd = coding(dd)
168        x = rc4(key, len(key), dd, len(dd))
169        key = update_key(key)
170        fout.write(struct.pack('H', srl))
171        fout.write(x)
172        fout.write(crc)
173    fin.close()
174    fout.close()
175    shutil.copy(a + "-encrypt", a)
176    os.remove(a + "-encrypt")
177    print(Fore.GREEN + 'OK!\n')
178
179
180def rc4(p_key, key_len, pin, d_len):
181    n = 65536
182    s = list(range(n))
183    j = 0
184    for i in range(n):
185        j = (j + s[i] + p_key[i % key_len]) % n  # type: int
186        temp = s[i]
187        s[i] = s[j]
188        s[j] = temp
189    i = j = 0
190    out = b''
191    for x in range(d_len):
192        i = i + 1
193        j = (j + s[i]) % n  # type: int
194        temp = s[i]
195        s[i] = s[j]
196        s[j] = temp
197        out += struct.pack('H', pin[x] ^ s[(s[i] + s[j]) % n])
198    return out
199
200
201def coding(data):
202    if len(data) % 2:
203        data += b'\0'
204    d_len = len(data) // 2
205    return struct.unpack(str(d_len) + 'H', data)
206
207
208def un_coding(data):
209    d = b''
210    for i in range(len(data)):
211        d += struct.pack('H', data[i])
212    return d
213
214
215def create_key(_key):
216    pl = len(_key)
217    key = b''
218    r = 0
219    for i in range(32):
220        k = (_key[r % pl] + i) % 256
221        key += struct.pack('B', k)
222        r += 1
223    return key
224
225
226def update_key(_key):
227    key = un_coding(_key)
228    # 循环左移
229    key = key[1:] + struct.pack('B', key[0])
230    tem = 0
231    # 求和
232    for i in range(len(key)):
233        tem += key[i]
234    key_o = b''
235    # Xor
236    for i in range(len(key)):
237        key_o += struct.pack('B', (key[i] ^ tem) % 256)
238        tem += key_o[i] >> 3
239        tem = tem % 256
240    return coding(key_o)
241
242
243if __name__ == "__main__":
244    init(autoreset=True)
245    try:
246        opts, args = getopt.getopt(sys.argv[1:], "c:p:d")
247    except getopt.GetoptError:
248        print(help_msg)
249        sys.exit()
250    if len(opts) == 0:
251        print(help_msg)
252        sys.exit()
253    for opt, arg in opts:
254        if opt in "-c":
255            if arg == "en":
256                encrypt(opts)
257            elif arg == "de":
258                decrypt(opts)
259            elif arg == "baidu":
260                submit_baidu(opts)
261            elif arg == "file":
262                get_file_num_size(opts)
263            else:
264                print(help_msg)
265                sys.exit()

文件保存为hexo-helper.py,放到博客的根目录下,使用方法:

  • 加密配置文件 python hexo-helper.py -c en -d
  • 解密配置文件 python hexo-helper.py -c de -d
  • 推送百度链接 python hexo-helper.py -c baidu -d
  • 查看文件夹大小 python hexo-helper.py -c file -d

最后

python作为解析型语言,语法简洁,第三方包丰富,可以快速的满足我们的需求。日后再有新的需求,只需要再添加新的操作符和新的方法即可。最后的最后,本人接触python的时间不是很长,代码写的很丑,如有错误,请不吝指正。