是的,我回来了。停更了 4 个月之后,再次开始更新这个写了 14 年的博客。

停更主要有两个原因:

  1. 太忙(lan)
  2. Hexo 太慢

原因并不重要,直接看迁移的过程吧!

1. 为什么要迁移到 Hugo

我曾经在 2014 年 博客静态化工作 时写了一个工具 wpcmd 用来实现 Wordpress 的本地发布。3 年后的 2017 年,我花了一些时间把 WordPress 迁移到了 Hexo 1/2。现在,我要从 Hexo 迁移到 Hugo。

转到 Hexo 的这两年时间,我用得并不顺畅。主要原因有下面三个:

  1. 我的博客现在有接近 900 篇文章,在我的 MacBook Pro (Retina, 13-inch, Early 2015, 3.1 GHz Intel Core i7, 16 GB 1867 MHz DDR3)上,每次使用 Hexo 编译都超过 2 分钟。如果在我的 1C2G 服务器上编译,时间将会更长。这个时长是无法忍受的。
  2. Hexo 的文档不全,在需要对其进行扩展的时候,经常需要去读源码。对于一个工具来说,这降低了使用效率。虽然我仍然是一个程序员,但我不认为在这个工具上花时间是值得的。
  3. Node.js 的工具链我一直不太喜欢。安装慢,依赖复杂,而且容易出错。

所以当我看到 Hugo 这个号称 The world’s fastest framework for building websites 的静态化工具时,不动心是很难的。部署容易,使用简单,还能顺便学一下 Go 语言,简直是为我量身打造的工具了。

事实证明,切换到 Hugo 之后,构建我的整个博客,只需要 10 秒多点,那叫一个快!

                   |  EN   
+------------------+------+
  Pages            | 1431  
  Paginator pages  |  268  
  Non-page files   |    0  
  Static files     | 1009  
  Processed images |    0  
  Aliases          | 1133  
  Sitemaps         |    1  
  Cleaned          |    0  

Total in 10549 ms

下面就按照时间顺序来叙述。

2. themes

maupassant 简洁美观,目前我使用这款。后面可能会换成 capsule

3. URL 对齐

网站迁移的过程,最重要的就是不能影响之前的 SEO。在从 WordPress 迁移到 Hexo 的时候,由于要切换域名,我在 Nginx 上做了一些工作来保证 SEO 正常。这一次,要做的工作也不少。我依然采用了和 WordPress 相同的结构来处理博客。

3.1 POST 和 PAGE

  1. 使用 /post/{\d+}.html 作为博客文章的 URL,对应 WordPress 中的 post,例如: https://blog.zengrong.net/post/2635.html
  2. 使用 /{\w+}/ 作为博客页面的 URL,对应 WordPress 中的 page,例如: https://blog.zengrong.net/wpcmd/

对于 post,我将每篇文章的 Markdown 文件放在 /content/post/ 路径下,文件名保持原来的 {\d+}.md 不变。他们的 type 都是 post,对 slugaliases 的处理保证了这个页面的老地址也能被访问。

这样处理之后,该页面的地址变成了: https://blog.zengrong.net/post/wordpress-to-hexo1/,但是之前的页面地址 https://blog.zengrong.net/post/2635.html 依然可以访问,并会自动跳转到新的地址。这个转向并不是在服务端做的,而是在客户端生成了一个转向界面完成的。搜索引擎将识别这个跳转,在今后漫长的时间里,把 SEO 转到新的地址。

下面是 /content/post/2635.md 这个页面的 Front Matter 内容:

+++
title = "WordPress to Hexo(1)"
postid = 2635
date = 2017-05-24T23:08:36+08:00
isCJKLanguage = true
toc = true
type = "post"
slug = "wordpress-to-hexo1"
aliases = [ "/post/2635.html",]
category = [ "web",]
tag = [ "wordpress", "master", "hexo", "staticize",]
lastmod = 2017-05-24T23:08:36+08:00
+++

对于 page,我将每篇页面的 Markdown 文件放在了 /content/page/ 路径下,文件名和原来的一致。它们的 type 都是 page,对 slugurl 进行了处理。

根据 Hugo 的约定,/content/page/ 这个 Section 的文件会生成在 /page/{\w+}/ URL 下面,我通过制定 url 来让页面被直接生成到根目录下。

下面是 /content/page/wpcmd.md 这个页面的 Front Matter 内容。

+++
title = "WPCMD"
postid = 2321
date = 2015-06-12T09:40:22+08:00
isCJKLanguage = true
toc = true
type = "page"
slug = "wpcmd"
url = "/wpcmd/"
+++

当然,手工来做这件事愚蠢且耗时,我写了一个 Python 脚本来完成这些自动转换: hexo2hugo.py

3.2 Category 和 Tag

我之前使用的 Category 和 Tag 的单数形式,因此我的标签页地址为: https://blog.zengrong.net/tag/study/ 。而在 Hugo 中采用了复数模式,默认的标签页地址是: {domain}/tags/xxx/

要解决这个问题,可以在 config.toml 中进行如下配置:

[taxonomies]
  category = "category"
  tag = "tag"

但是,配置完成后,文章中的分类和标签都无法显示了。

这是因为在 theme 中写死了对变量 .Params.Categories.Params.Tags 的引用。将模版中这样的变量都改为单数形式即可。

通过修改了下面几个模版中,解决了上面的问题:

  1. maupassant/layouts/index.html
  2. maupassant/layouts/_default/single.html
  3. maupassant/layouts/_default/taxonomy.html
  4. maupassant/layouts/partials/categories.html
  5. maupassant/layouts/partials/tags.html

4. 嵌入标签处理

在 Hexo 中,我定义了几个嵌入式标签:

  • flash 用于嵌入 flash 动画
  • download 用于支持下载链接管理
  • label Hexo 主题中自带,用于实现带颜色的 label 效果

在 Hugo 中可以使用 Shortcodes 来实现它们。我写了 3 个 Shortcodes 来实现这些功能。

当然,需要批量转换 Hexo 格式的嵌入标签到 Hugo 格式的 Shortcode 。这不难,用 Python 正则替换一下就好。这部分替换脚本也在 hexo2hugo.py 中有提供。

5. 留言服务

上次将博客迁移到 Hexo 的时候,我选择的评论服务是国内的畅言。因为当时多说濒临倒闭,Disqus 在国内又被墙。现在来看这个选择挺糟糕的,畅言的乱七八糟广告特别多,接入后我的博客就变成了牛皮癣广告墙,这里一块那里一块。

我在 WordPress to Hexo(2) 中提到过,希望使用基于 Github 的 Issue 系统的评论系统来完成评论。这样的系统现在还不少的:

使用它们存在一个问题,博客现有的三千多条评论就没法转换过去了。

因此,我还是选择了需要自己部署的产品 Isso。Python 实现,自己也可以随便折腾。

5.1 uwsgi.ini

Isso 的架设过程并不复杂,因为对 uWSGI 已经很熟悉了,我自然是选择 uWSGI 作为生产环境的部署,下面是我的 uwsgi.ini 配置文件。

[uwsgi]                                                                                   
;socket = %d%n.sock                                                                       
http = 127.0.0.1:1314                                                                     
master-fifo = %d%n.fifo                                                                   
pidfile = %d%n.pid                                                                        
                                                                                          
master = true                                                                             
processes = 2                                                                             
threads = 2                                                                               
max-requests = 600                                                                        
chmod-socket = 666                                                                        
thunder-lock = true                                                                       
harakiri-verbose = true                                                                   
harakiri = 10                                                                             
buffer-size = 32768                                                                       
                                                                                          
ignore-sigpipe = true                                                                     
ignore-write-errors = true                                                                
disable-write-exception = true                                                            
                                                                                          
req-logger = file:%dlogs/req.log                                                          
logger = file:%dlogs/%n.log                                                               
daemonize = %dlogs/%n.log                                                                 
log-master = true                                                                         
threaded-logger = true                                                                    
                                                                                          
env = ISSO_SETTINGS=%disso.cfg                                                            
env = ISSO_CORS_ORIGIN=*.zengrong.net                                                     
spooler = %dspooler                                                                       
wsgi = isso.run                                                                           
venv = %dvenv                                                                             
chdir = %d                                                                                
uid = app                                                                                 
gid = app

5.2 isso.cfg

isso.cfg 配置文件的内容如下:

[general]                                                                                 
name = blog                                                                               
dbpath = /xxx/comment.db                                                        
host = https://blog.zengrong.net/                                                         
log-file = /xxx/isso.log                                                    
notify = smtp                                                                             
reply-notifications = true                                                                
gravatar = true                                                                           
                                                                                          
[smtp]                                                                                    
username = zrong
password = xxxxxx
host = mail.xxx
port = 111
security = ssl                                                                            
to = i@zengrong.net                                                                       
from = i@zengrong.net
timeout = 5                                                                               
                                                                                          
[moderation]                                                                              
enabled = true                                                                            
                                                                                          
[hash]                                                                                    
salt = xxxxxxxxxxxxxxxxxxx

5.3 CROS Bugs of isso

isso 让人很迷的有几点:

  1. 环境变量中配置的 ISSO_CORS_ORIGIN 无效;
  2. 配置文件中配置的 host 用来指定跨域信息的域名也无效;
  3. 配置文件中配置的 log-file 无效,看不到 isso 的日志。

我在跨域的问题上纠结了许久,最后还是决定把 isso 部署到博客所在的 URL 子域下以规避跨域问题。这应该是 isso 的 bug,等有闲工夫了,我再去修改 isso 的源码来解决。

5.4 迁移畅言的历史评论到 isso

在畅言后台,可以把历史评论记录导出为 json 文件,我用 Python 写了一个脚本: changyan2isso.py 将导出的数据转换成为 isso 可以导入的标准 json 格式。然后使用 isso import 命令将其导入到 isso 的数据库中。

isso 也可以导入 WordPress 和 Disqus 的 XML 格式数据。

isso 导入 json 文件的时候,会忽略留言数据的 parent 属性,这个属性用来指引留言的回复关系。我曾经修改 changyan2isso 使其支持 parent 属性,但回复关系依赖被回复的留言必须优先存在。而我在处理历史数据的时候并没有考虑这一点。折腾了一阵子之后,我不打算在十几年的老回复上再浪费时间,就此作罢。

有兴趣的同学,可以根据 这段代码,配合 isso 中的 import 包来解决这个不支持 parent 的问题。当然,你也可以把历史回复导出成 WordPress 或者 Disqus 格式然后再导入。

我不太喜欢 XML,所以,就酱紫了。

全文完