本文代码 https://github.com/tothis/rust-record/tree/main/test/actix-web
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(())
}