技术栈
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