当前位置: 首页 > 工具软件 > Actix > 使用案例 >

Actix Web & SQLx 搭建 Web 后端服务

丌官星渊
2023-12-01

本文代码 https://github.com/tothis/rust-record/tree/main/test/actix-web

已集成功能

  • log4rs 集成
  • SQLx 集成
  • Actix Web CRUD

Cargo.toml

[package]
name = "actix-web-record"
version = "0.1.0"
authors = ["Li Lei <this.lilei@gmail.com>"]
edition = "2021"

[dependencies]
serde = "1.0"
actix-web = "4.0"
dotenv = "0.15"
log = "0.4"
log4rs = "1.0"

[dependencies.sqlx]
version = "0.5"
features = [
    "postgres",
    "runtime-actix-rustls"
]

数据库结构

-- ----------------------------
-- Sequence structure for user_id_seq
-- ----------------------------
DROP SEQUENCE IF EXISTS "public"."user_id_seq";
CREATE SEQUENCE "public"."user_id_seq"
INCREMENT 1
MINVALUE  1
MAXVALUE 9223372036854775807
START 1
CACHE 1;
ALTER SEQUENCE "public"."user_id_seq" OWNER TO "postgres";

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS "public"."user";
CREATE TABLE "public"."user" (
  "id" int8 NOT NULL GENERATED BY DEFAULT AS IDENTITY (
INCREMENT 1
MINVALUE  1
MAXVALUE 9223372036854775807
START 1
CACHE 1
),
  "created_at" timestamp(6),
  "updated_at" timestamp(6),
  "deleted_at" timestamp(6),
  "name" varchar(255) COLLATE "pg_catalog"."default"
)
;
ALTER TABLE "public"."user" OWNER TO "postgres";

-- ----------------------------
-- Alter sequences owned by
-- ----------------------------
ALTER SEQUENCE "public"."user_id_seq"
OWNED BY "public"."user"."id";
SELECT setval('"public"."user_id_seq"', 1, false);

-- ----------------------------
-- Primary Key structure for table user
-- ----------------------------
ALTER TABLE "public"."user" ADD CONSTRAINT "user_pkey" PRIMARY KEY ("id");

.env

DATABASE_URL="postgres://postgres:123456@127.0.0.1/actix_web_record"

config/log4rs.yaml

refresh_rate: 30 seconds
appenders:
  stdout:
    kind: console
    encoder:
      pattern: "{d(%F %H:%M:%S%.f)} - {m}{n}"
  main:
    kind: file
    path: "test/actix-web/log/main.log"
    encoder:
      pattern: "{d(%F %H:%M:%S%.f)} - {m}{n}"
root:
  level: warn
  appenders:
    - stdout
loggers:
  main:
    level: info
    appenders:
      - main
      - stdout
    additive: false

src/handler/user.rs

use actix_web::{delete, get, post, Responder, web};
use actix_web::web::Data;
use serde::{Deserialize, Serialize};
use sqlx::{PgPool, query, query_as};
use web::{Json, Path};

use crate::handler::result::{none, some};

#[derive(Serialize)]
struct GetUserResult {
    id: i64,
    name: Option<String>,
}

#[derive(Deserialize)]
pub struct SaveUserQuery {
    id: Option<i64>,
    name: Option<String>,
}

#[get("user")]
pub async fn all(
    data: Data<PgPool>
) -> impl Responder {
    some(
        query_as!(
            GetUserResult,
            r#"select id, name from "user" where deleted_at is null"#
        ).fetch_all(data.get_ref()).await
    )
}

#[get("user/{id}")]
pub async fn get(
    data: Data<PgPool>,
    path: Path<i64>,
) -> impl Responder {
    let id = path.into_inner();
    some(
        query_as!(
            GetUserResult,
            r#"select id, name from "user" where deleted_at is null and id = $1"#,
            id
        ).fetch_one(data.get_ref()).await
    )
}

#[post("user")]
pub async fn post(
    data: Data<PgPool>,
    Json(v): Json<SaveUserQuery>,
) -> impl Responder {
    none(
        match v.id {
            None => query!(
                r#"insert into "user" (name, created_at) values ($1, now())"#,
                v.name
            ).execute(data.get_ref()).await,
            Some(id) => query!(
                r#"update "user" set name = $1, updated_at = now() where deleted_at is null and id = $2"#,
                v.name,
                id
            ).execute(data.get_ref()).await
        }
    )
}

#[delete("user/{id}")]
pub async fn delete(
    data: Data<PgPool>,
    path: Path<i64>,
) -> impl Responder {
    let id = path.into_inner();
    none(
        query!(
            r#"update "user" set deleted_at = now() where id = $1"#,
            id
        ).execute(data.get_ref()).await
    )
}

src/handler/mod.rs

use actix_web::http::header::CONTENT_TYPE;
use actix_web::Responder;
use actix_web::web;
use serde::{Deserialize, Serialize};

mod user;

pub const APPLICATION_JSON: &str = "application/json";

pub fn route(config: &mut web::ServiceConfig) {
    config.service(user::all);
    config.service(user::get);
    config.service(user::post);
    config.service(user::delete);
}

pub async fn default_route() -> impl Responder {
    r#"{"code":"0","message":"404 Not Found"}"#.customize().insert_header((CONTENT_TYPE, APPLICATION_JSON))
}

#[derive(Serialize, Deserialize)]
pub struct HttpResult<T> {
    code: u8,
    #[serde(skip_serializing_if = "Option::is_none")]
    data: Option<T>,
    #[serde(skip_serializing_if = "String::is_empty")]
    message: String,
}

mod result {
    use std::fmt::Display;

    use actix_web::web::Json;
    use log::error;

    use crate::handler::HttpResult;

    fn err<T, M: Display>(message: M) -> Json<HttpResult<T>> {
        let message = message.to_string();
        error!(target: "main", "{}", message);
        Json(HttpResult { code: 1, data: None, message })
    }

    /// 响应数据
    pub fn some<T, E: Display>(v: crate::Result<T, E>) -> Json<HttpResult<T>> {
        match v {
            Ok(o) => Json(HttpResult {
                code: 0,
                data: Some(o),
                message: "".into(),
            }),
            Err(e) => err(e)
        }
    }

    /// 不响应数据
    pub fn none<T, E: Display>(v: crate::Result<T, E>) -> Json<HttpResult<()>> {
        match v {
            Ok(_) => Json(HttpResult {
                code: 0,
                data: None,
                message: "".into(),
            }),
            Err(e) => err(e)
        }
    }
}

main.rs

use std::env;
use std::error::Error;
use std::result::Result as StdResult;

use actix_web::{App, HttpServer};
use actix_web::web::{Data, route};
use dotenv::dotenv;
use log4rs;
use log::info;
use sqlx::PgPool;

mod handler;

pub type Result<T = (), E = Box<dyn Error>> = StdResult<T, E>;

#[actix_web::main]
async fn main() -> Result {
    dotenv().ok();
    log4rs::init_file("config/log4rs.yaml", Default::default())?;
    let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?;

    info!(target: "main", "程序启动");
    HttpServer::new(move ||
        App::new()
            .app_data(Data::new(pool.clone()))
            .default_service(route().to(handler::default_route))
            .configure(handler::route)
    ).bind(("127.0.0.1", 8080))?.run().await?;
    Ok(())
}

 类似资料: