需求背景
一个朋友买了一个云主机(就是300元3年的那种)
云主机配置是:
- CPU:vCPU 2
- 内存:2G
- 硬盘:40G
- 带宽:1M
- 公网IP:1个
- 预装的系统是:Windows 2008 R2
这台机器上,跑了一些他自己的东西,虽然机器整体性能不咋的,但是总觉得还可以再“挖掘”一些性能(挤一挤总是会有的),所以还在跑一个自己的博客。
技术选型
最初他问我的时候,我就让他用 WAMP + WordPress,方便、省事儿,而且WP的插件资源一大推,教程也很多。
他说他尝试过,装上了以后,机器莫名卡得厉害(不知道原因)。
把他之前跑的东西都弄慢了,后来导致重装了一次系统。。
好的,这个方案干掉。
要省资源,可以用 Github 的 Pages,托管免费,还可以绑定域名。
试运行了几天以后,朋友反馈,觉得访问速度时快时慢,用户体验不能保证。
(我心想,你博客有啥用户?)
EN,那么再换一个吧。。
那么用静态网站生成器吧,这样,速度和资源,都有保证了。
于是,扔给他了一个链接:Static Site Generators - Top Open Source SSGs | Jamstack
说这里收集了应该是市面上最全的,什么口味儿的都有,包君满意!
链接扔过去了以后,朋友几天没有联系了。
有一天,右下角,他的头像又开始一闪一闪的。。。
有点点不好的预感。
- 朋友:我看了几种,后来看得眼睛都花了,最后反而不知道选啥了。
- 我:那你可以选比较流行的 Hugo,教程也多
- 朋友:嗯,看了。
- 我:如何?
- 朋友:Windows 用习惯了,看到文字性的配置,就头疼
- 我:那我这儿暂时还想不到有啥其它的方案了。
- 朋友:那么
- 我:啥?
- 朋友:我们自己撸一个呢?
- 我:啥?(为什么是我们?)
- 朋友:最近看了些CSS,想自己试试。(朋友做设计的)
- 朋友:之前看的那些框架、静态网站,都好复杂,想自己从 0 开始。慢慢摸索。
- 我:(嗯,你学 CSS 从 0 开始,起点是不错的!但是除了 CSS,还有其它一大坨呢。)
正想怎么回复,回头看到一本 Rust 教程,就给朋友说:
我来做后端和前端交互。前端HTML 和 CSS 由你来吧。
最后的方案就是:
后端:Warp + Sqlite + Sled
前端:Yew + 朋友的 HTML 和 CSS
选择的理由?
Rust
我是 2017 年的时候了解到 Rust 的,那时已经发布了 2 年了。
看了它的设计理念、语言设计以及官方教程,我的内心就告诉我,这个就是我需要的语言。
然后,经历了从入门到纠结,再到放弃,再入门到放弃,期望借这个机会再准备入门。
Warp
Rust 不乏优秀的 web 框架,我尝试过 Actix-web -> Tide -> Warp。
Warp 是目前用起最顺手的。
Sqlite
就朋友那点流量,Sqlite 足以应付了
Sled
博客是写少,读多的。用 Sled 来充当一个缓存还是不错的
Yew
用于构建前端的,很方便的将朋友写的 HTML 嵌入到我的代码里。
我就专注于前端与后端交互逻辑了
动工
在那边还在边学边整HTML CSS 的时候,我这里先开始后端的工作。
数据库,有 3 张表
博客内容
CREATE TABLE "blog" (
"id" INTEGER NOT NULL,
"title" TEXT(64) NOT NULL,
"markdown_content" TEXT(20480) NOT NULL,
"parsed_content" TEXT(65535) NOT NULL,
"tags" TEXT(256) NOT NULL,
"created_at" INTEGER NOT NULL,
PRIMARY KEY ("id" ASC)
);
标签数据
CREATE TABLE "blog_tag" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" TEXT(32) NOT NULL,
CONSTRAINT "name" UNIQUE ("name" ASC)
);
用户信息(只有朋友一个用户数据,离线初始化进去)
CREATE TABLE "user" (
"id" INTEGER NOT NULL,
"username" TEXT(32) NOT NULL,
"password" TEXT(256) NOT NULL,
PRIMARY KEY ("id")
);
Rust工程
先初始化一个 git 目录,然后创建 workspace,包含 3 个目录
- backend 博客后端
- frontend 博客前端
- common 公共类
进入 backend 目录,创建一个 Rust bin 项目
在Cargo.toml
引入依赖项
[package]
name = "blog-backend"
version = "0.1.0"
authors = ["Songday <songday@yeah.net>"]
edition = "2018"
[lib]
name = "blog_backend"
[dependencies]
blog-common = { path = "../common" }
ahash = "0.4"
base64 = "0.12"
bytes = "0.5"
chrono = { version = "0.4", features = ["serde"] }
comrak = "0.8"
futures = "0.3"
hyper = "0.13"
image = { version = "0.23", features = ["jpeg", "png", "gif"] }
lazy_static = "1.4"
lazy-static-include = "3.0"
parking_lot = "0.11"
once_cell = "1.4"
rand = "0.7"
v_htmlescape = "0.10"
subtle = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sled = "0.34"
sqlx = { version = "0.4.0-beta.1", default-features = false, features = [ "runtime-tokio", "macros", "sqlite"], optional = false }
scrypt = "0.4"
tokio = { version = "0.2", features = ["fs", "io-util", "macros", "rt-core", "rt-threaded", "signal", "time"] }
uuid = { version = "0.8", features = ["v5"] }
urlencoding = "1.1"
warp = "0.2"
然后,定义一下个人博客的路由。
Warp 最自以为豪的就是它的Filter
概念(在定义路由的时候就到的)
Thanks to its Filter system, warp provides these out of the box:
Path routing and parameter extraction
Header requirements and extraction
Query string deserialization
JSON and Form bodies
Multipart form data
Static Files and Directories
Websockets
Access logging
Gzip, Deflate, and Brotli compression
我定义的路由如下:
// 用户登录
let user_login = warp::post()
.and(warp::path("user"))
.and(warp::path("login"))
.and(warp::path::end())
.and(warp::cookie::optional(var::AUTH_HEADER_NAME))
.and(warp::body::json::<LoginParams>())
.and_then(controller::user_login);
// 用户登出
let user_logout = warp::get()
.and(warp::path("user"))
.and(warp::path("logout"))
.and(warp::path::end())
.and(warp::cookie::optional(var::AUTH_HEADER_NAME))
.and_then(controller::user_logout);
// 博客列表
let blog_list = warp::get()
.and(warp::path("blog"))
.and(warp::path("list"))
.and(warp::path::param::<u8>())
.and(warp::path::end())
.and_then(controller::blog_list);
// 博客标签列表
let blog_tags = warp::get()
.and(warp::path("blog"))
.and(warp::path("tags"))
.and(warp::path::end())
.and_then(controller::blog_tags);
// 根据博客标签展示博客列表
let blog_list_by_tag = warp::get()
.and(warp::path("blog"))
.and(warp::path("tag"))
.and(warp::path::param::<String>())
.and(warp::path::param::<u8>())
.and(warp::path::end())
.and_then(controller::blog_list_by_tag);
// 保存博客数据
let blog_save = warp::post()
.and(warp::path("blog"))
.and(warp::path("save"))
.and(warp::path::end())
.and(auth())
.and(warp::body::json::<NewBlog>())
.and_then(controller::blog_save);
// 展示某一篇博客文章
let blog_show = warp::get()
.and(warp::path("blog"))
.and(warp::path("show"))
.and(warp::path::param::<u64>())
.and(warp::path::end())
.and_then(controller::blog_show);
刚刚定义好了路由,朋友发来了登录页面框架。
我打开看了,还有登录验证码。于是就准备查一下如何让Rust读取字体再在画布上画出来。
这时朋友又发了一个zip,说里面是用于显示验证码的图片。
好吧,省得我去查资料了。
于是,先新增一个显示图片的接口
let verify_image = warp::get()
.and(warp::path("tool"))
.and(warp::path("verify-image"))
.and(warp::path::end())
.and(warp::cookie::optional(var::AUTH_HEADER_NAME))
.and_then(controller::verify_image);
画验证图,我用的是image
crate,效果如下:
好了,后端框架搭建得差不多了。
下一篇,我们一起用Yew
来搭建前端。