我是一个前端,但是我选择用 rust 搭建博客系统

8 阅读2分钟

技术栈

actix-web + sqlx + pgsql + redis + docker

使用 actix-web

创建一个服务

0.0.0.0:4000 不然 在docker打包的时候会有坑


let app = move || {

App::new()

.app_data(shared_data.clone())

.wrap(Logger::default())

.wrap(cors())

.wrap(app::drivers::middleware::auth::Authentication)

.configure(app::drivers::routes::api)

};

HttpServer::new(app).bind("0.0.0.0:4000")?.run().await

配置跨域


use actix_cors::Cors;

Cors::default().allow_any_origin()

路由


cfg.service(web::scope("/healthcheck").service(healthcheck))

.service(

web::scope("/blog")

.service(get_all_page)

)

中间件-检查token是否有效


fn call(&self, req: ServiceRequest) -> Self::Future {

let is_verified = if should_skip_auth(&req) {

true

} else {

let head = req.headers().get("Authorization");

if let Some(head) = head {

let token = head.to_str().unwrap().to_string();

let state = req

.app_data::<Data<AppState>>()

.expect("Cannot get app state.");

  


let mut con = state.redis.get_connection().unwrap();

// throw away the result, just make sure it does not fail

let result: Result<String, redis::RedisError> = con.get("token");

  


if let Ok(result) = result {

result == token

} else {

false

}

} else {

return Box::pin(async move {

let (req, _res) = req.into_parts();

let res = HttpResponse::Unauthorized().finish().map_into_right_body();

let srv = ServiceResponse::new(req, res);

Ok(srv)

});

}

};

  


if is_verified {

let fut = self.service.call(req);

Box::pin(async move {

let res = fut.await?.map_into_left_body();

Ok(res)

})

} else {

Box::pin(async move {

let (req, _res) = req.into_parts();

let res = HttpResponse::Unauthorized().finish().map_into_right_body();

let srv = ServiceResponse::new(req, res);

Ok(srv)

})

}

}

}

具体接口使用


use actix_web::{delete, get, post, put, web, HttpResponse, Responder};

use uuid::Uuid;

  


#[get("/list")]

pub async fn get_page(state: web::Data<AppState>, info: web::Query<PageQuery>) -> HttpResponse {

  


let mut con = state.redis.get_connection().unwrap();

let result: String = con.get("token").unwrap();

let result = get_blog_page(&state.db, Uuid::try_parse(&result).unwrap(), info.size, info.current)

.await

.unwrap();

HttpResponse::Ok().json(result)

}

依赖


actix-cors="0.7.0"

actix-web = "4.5.1"

actix="0.13.3"

使用sqlx的过程

首先新建数据库连接池


// DATABASE_URL=postgres://name:password@ip:port/database

dotenv().ok();

let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");

PgPoolOptions::new().connect(&database_url).await.unwrap()

在actix-web 新建服务的时候 注入进全局的data里面

注入的时候:


let shared_data = web::Data::new(AppState { db,redis });

let app = move || {

App::new()

.app_data(shared_data.clone())

.wrap(Logger::default())

.wrap(cors())

.wrap(app::drivers::middleware::auth::Authentication)

.configure(app::drivers::routes::api)

};

使用的时候:


#[get("/list")]

pub async fn get_page(state: web::Data<AppState>, info: web::Query<PageQuery>) -> HttpResponse {

  


let mut con = state.redis.get_connection().unwrap(); // 拿到数据库连接池

let result: String = con.get("token").unwrap();

let result = get_blog_page(&state.db, Uuid::try_parse(&result).unwrap(), info.size, info.current)

.await

.unwrap();

HttpResponse::Ok().json(result)

}

  


let rows: Vec<Blog> = sqlx::query_as!(

Blog,

r#"select * from blog where user_id = $1 ORDER BY update_time desc LIMIT $2 OFFSET $3"#,

current_user_id,

size,

(current - 1) * size

)

.fetch_all(pool) //使用

.await?;

  


支持自动序列化

可以结合上方的 query_as 去看,还是比较方便使用的,当然这是单表,多表请看下方


#[derive(Serialize, Debug, Clone, sqlx:: FromRow)]

pub struct Blog {

pub id: Uuid,

pub title: Option<String>,

  


pub description: Option<String>,

pub body: Option<String>,

  


pub update_time: Option<DateTime<Utc>>,

  


pub user_id: Uuid,

  


pub img: Option<String>

}

多表

也没有很复杂


let rows: Vec<CommentWithUser> = sqlx::query_as!(

CommentWithUser,

r#"select c.*, u.name as user_name from comment c left join "user" u on c.user_id = u.id where c.blog_id = $1 ORDER BY c.create_time desc"#,

blog_id

)

.fetch_all(pool)

.await?;

  


#[derive(Serialize, Debug, Clone, sqlx:: FromRow)]

pub struct CommentWithUser {

pub id: Uuid,

pub content: String,

pub parent_id: Option<Uuid>,

pub create_time: DateTime<Utc>,

pub user_id: Uuid,

pub blog_id: Uuid,

pub user_name: Option<String>,

}

最后是sqlx的依赖


sqlx = { version = "0.7.3", features = [

"postgres",

"runtime-tokio-rustls",

"macros",

"chrono",

"uuid",

] }

使用单点redis

创建

也是通过全局注入的


redis = "0.25.3"

  


let redis = redis::Client::open("redis://ip:6379/").unwrap();

设置


let mut con = redis.get_connection().unwrap();

// con.set("token", user.id.to_string());

let _: () = con.set("token", user.id.to_string()).unwrap();

获取


let mut con = state.redis.get_connection().unwrap();

// throw away the result, just make sure it does not fail

let result: String = con.get("token").unwrap();

删除


let mut con = state.redis.get_connection().unwrap();

let _: () = con.del("token").unwrap();

DockerFile


FROM rust:latest@sha256:371ae5168b632596c4b32fb97f63bcf85c26cb84cf5e775da4e34e42c1651626

  


WORKDIR /usr/src/app

  


COPY . .

  


RUN cargo build --release

  


CMD ["/usr/src/app/target/release/blog"]

联系方式

有问题可以互相沟通 邮箱 ancaisj@126.com