伪静态的博客系统

事由

在编辑(第3代,基于October的)博客的友情链接的时候,发现OctoberCMS的content类不能够解析代码,也就是只能写纯的HTML,太屑了。而且October的2FA/UFA组件还要钱,所以干脆自己写一个完全的博客好了。

博客设计

起初的设想

  • 个人页面
  • 博客系统
    • Markdown支持:编辑器、解析器
    • 年份+slug构成的动态链接
    • 博客分类

后来发现这个前端静态是超级好写的,但是动态的博客需要造后端,太麻烦了,怎么办呢?

转变方向

如果用SQL,SQL的数据要怎么同步呢?(hexo因为是客户端生成页面的,可以直接把源文件用git同步);如果不用SQL,用$O(n)$的方法去查找文章又太慢了。

也想不出什么好方法,就只能上床躺尸……正好那天容老司机在外地的卖裆佬孤苦伶仃过夜,结果聊天的时候突然想到:可以把写的文章放在文件夹里,交给服务器去预处理,塞到数据库,然后用户访问的时候直接用SQL找到对应的文章返回。

容酱老司机想到了个更狠辣的方案:预先处理一张数据表文件,客户端访问的时候用脚本去下载这个数据表然后一一匹配,找到了就再去服务器上访问对应的文件,显示博客内容。(就是相当于把hexo的生成页面那一步也省略了,全部都交给访客的浏览器去处理。)

最终方案

这个想法实现起来也还算容易……只需要我写文章,然后按照一定的格式存放,服务器从某个地方拉取数据后对文章进行处理,把Markdown parse成HTML存放到数据库,这样用户访问就可以直接拉取数据库里的内容了。

这个方法还有一个好,那就是我完全不需要进行后端的制作了。我可以把服务器完全用一个库来同步,文件和身份验证全都交给这个同步来进行。在服务器端设定好每隔几分钟从远程拉取数据,那么本地只需要写文章+上传就可以了。

实现方式

文件结构

为了方便管理,我把每个博客文章设置一个文件夹,包括以下内容

+ 2019-01-01-test-post
|--- config.json
|--- content.md
|--- asset1.jpg
|--- asset2.jpg
\--- ...

我非常自豪的感到这种文件结构实在是比hexo高明太多了,原因 好处如下:

  • JSON比YAML好写多了,不容易出现语法错误。
  • JSON和MD分开管理,可以分开修改,更加方便读入。
  • 图片文件直接放在同一个文件夹内,那么只需要![caption](asset.jpg)就可以引用了(然而这样的话本地一时爽,服务器处理就出毛病了)。

然后写完博客后服务器端通过PHP对所有的文章内容进行预处理,就可以通过网页访问了。

Prepare contents

文本处理

首先把config.jsoncontent.md源文件读入,对于设置文件用json_decode()函数处理,就可以直接访问各种设置的值。内容文件用Parsedown编译成HTML,然后和设置的值一起塞到SQL数据库里。

Post::truncate();
foreach ($postFolders as $postFolder) {
  $configRaw = $storage->get($postFolder . '/config.json');
  $contentRaw = $storage->get($postFolder . '/content.md');

  $config = json_decode($configRaw);
  $content = $this->parser->text($contentRaw);

  Post::create([
      'xconfig' => $config->xconfig, // omitted
      'content' => $content,
  ])->save();
}

图像处理

下面的内容其实应该是src后面直接跟着个双引号的……看完你就知道为什么这里改成这样了……

doge

Parsedown编译的图像是<img src=\"asset.jpg' alt="caption"/>,那么只需要对<img src=\"这个字符串进行替换(对,没错,之前这个文章里的也被替换了(笑)……),把图片重定向到制定的存放位置就可以了。

首先将图片地址重定向,

$content = str_replace('src=\"', 'src=\"/storage/posts/' . $config->slug . '/', $content);

然后把图片类型的文件移动到公开访问的对应的文件夹内(也就是排除.json.md):

foreach ($files as $file) {
  if (in_array(\File::extension($file), ['jpg', 'jpeg', 'png'])) {
      $content = $storage->get($file);
      $newFile = 'posts/' . $postConfig->slug . '/' . \File::basename($file);
      $storage->put($newFile, $content);
  }
}

就可以看到效果了。

Image display

然而之后还发现友链里有两个人头像挂了……分析后发现这两张头像的扩展名是.JPG,而Linux只认小写的.jpg

额外功能

标签

文章的标签实现的很水,就是用空格区分不同的标签,然后用explode()函数来分句,给每一个标签一个单独的搜索链接,用SELECT * WHERE tags LIKE "%tag%"就可以通过SQL搜索出所有同样标签的文章了。

RSS(Atom) Feed

因为我时常会写一些乱七八糟的不适合放到JB网上的内容,所以我在设置里增加了一个是否通过RSS共享的设定。如果设定为true(默认),则会通过RSS分享,否则RSS里就没有这篇文章。

然而RSS始终通不过检测,找了好久才发现原因是没有在HTML部分套一个<![CDATA[ ... ]]>的标签……

文章的加密

在文章的设置中还有两个变量,一个是文章的访问密码,另一个是保持锁定的时间。这两个变量的组合如下:

  • 如果有密码,无时间,则必须输入密码才可以访问;
  • 如果有密码和时间,那么在指定时间之前可以输入密码访问,之后就可以公开访问;
  • 如果只有时间,那么在指定时间之前不可访问;
  • 否则这篇文章公开。

在用户访问的时候,服务器会根据是否有密码这个变量来决定是否给用户放行。如果没有密码,则会给用户需要输密码或者不能访问的提示:这个时候点击输入密码就会弹出一个form,通过POST方式提交。

Unlock Prompt

如果密码不正确,在提示的后面会有加粗的“密码不正确”,

Unlock Failed

密码正确则内容变为文章的内容。

Unlock Succeed

当然,不可能直接访问这个链接就能看到内容了。

405 Not Allowed

兼容性

理论上来说,这套加密设定是只基于bootstrap4的,所有引入了这个库的网页都可以通过看到“文章锁定”的提示,点击“输入密码”后会弹出一个输入框。

JB RSS Feed

然而实测由于之前JB Online中发现了可能通过RSS注入脚本的bug,所以对所有的文章内容进行净化,这些骚操作当然被搞掉了……在博客聚合里只能看到普通的文本提示,点击链接后会跳转到博客的文章页面。

发布于2019-01-18 15:18

笔记 / Laravel