diff --git a/.DS_Store b/.DS_Store index 2b7ce5b..f833f46 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/.DS_Store b/12-初探Rust微服务架构(gRPC+Tonic)/.DS_Store new file mode 100644 index 0000000..1d5aa8c Binary files /dev/null and b/12-初探Rust微服务架构(gRPC+Tonic)/.DS_Store differ diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/12-初探Rust微服务架构(gRPC+Tonic).pdf b/12-初探Rust微服务架构(gRPC+Tonic)/12-初探Rust微服务架构(gRPC+Tonic).pdf new file mode 100644 index 0000000..8466e82 Binary files /dev/null and b/12-初探Rust微服务架构(gRPC+Tonic)/12-初探Rust微服务架构(gRPC+Tonic).pdf differ diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/.DS_Store b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/.DS_Store new file mode 100644 index 0000000..40d2aed Binary files /dev/null and b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/.DS_Store differ diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/Cargo.toml b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/Cargo.toml new file mode 100644 index 0000000..0c9de89 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "api-service-demo" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] # 用来运行 HelloWorld gRPC 服务器的可执行文件 +name = "shortlink-server" +path = "src/server.rs" + +[dependencies] +tonic = "0.5" +prost = "0.8" +axum = { version = "0.2.3" } +sqlx = { version = "0.5.6", features = ["mysql", "runtime-tokio-rustls"] } +tokio = { version = "1.11.0", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +tower = { version = "0.4", features = ["util", "timeout"] } +tower-http = { version = "0.1", features = ["add-extension", "trace"] } +uuid = { version = "0.8.2", features = ["serde", "v4"] } +anyhow = "1.0.44" +redis = { version = "0.21", features = ["tokio-comp", "aio"] } + +[build-dependencies] +tonic-build = "0.5" \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/build.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/build.rs new file mode 100644 index 0000000..3f704b3 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/build.rs @@ -0,0 +1,5 @@ +fn main() -> Result<(), Box> { + println!("======"); + tonic_build::compile_protos("proto/shortlink.proto")?; + Ok(()) +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/proto/shortlink.proto b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/proto/shortlink.proto new file mode 100644 index 0000000..0eede90 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/proto/shortlink.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package shortlink; + +service ShortLink { + rpc GetInfo(ShortLinkRequest) returns (ShortLinkReply); +} + +message ShortLinkRequest { + int32 id = 1; +} + +message ShortLinkReply { + string url = 1; +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/controllers/mod.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/controllers/mod.rs new file mode 100644 index 0000000..f868356 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/controllers/mod.rs @@ -0,0 +1 @@ +pub mod shortlink_controller; \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/controllers/shortlink_controller.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/controllers/shortlink_controller.rs new file mode 100644 index 0000000..9fa9fb6 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/controllers/shortlink_controller.rs @@ -0,0 +1,87 @@ +use axum::{Json, extract}; +use crate::app::models::dto; +use axum::extract::Extension; +use axum::response::IntoResponse; +use axum::http::{StatusCode, HeaderMap, Request}; +use crate::app::models::shortlink; +use sqlx::{Pool, MySql}; +use axum::http::header::LOCATION; +use axum::body::Body; +use redis::{Client, AsyncCommands, RedisResult}; +use redis::aio::Connection; +use std::sync::Arc; +use tokio::sync::{RwLock, Mutex}; +use std::ops::Deref; + +pub async fn create_shortlink( + Json(req): Json, + Extension(pool): Extension> +) -> impl IntoResponse { + println!("{:#?}", req); + match shortlink::create_shortlink(&pool, &req.url).await { + Ok(_) => { + (StatusCode::OK, Json(dto::CreateUserResp { + ok: true + })) + } + Err(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, Json(dto::CreateUserResp { + ok: false + })) + } + } +} + +pub async fn delete_shortlink( + Json(req): Json, + Extension(pool): Extension>, +) -> impl IntoResponse { + println!("{:#?}", req); + match shortlink::delete_shortlink(&pool, req.id).await { + Ok(_) => { + (StatusCode::OK, Json(dto::DeleteShortLinkResp { + ok: true + })) + } + Err(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, Json(dto::DeleteShortLinkResp { + ok: false + })) + } + } +} + +pub async fn get_shortlink( + extract::Path(id): extract::Path, + req: Request +) -> impl IntoResponse { + let mut url = String::from("/api/not_found"); + //let pool = req.extensions().get::>().unwrap(); + let mut con = req.extensions().get::>>().unwrap().lock().await; + let mut redis_key = String::from("url_"); + redis_key.push_str(&*id.to_string()); + let res: RedisResult = con.get(redis_key).await; + match res { + Ok(v) => { + url = v; + } + Err(err) => { + println!("err = {:#?}", err); + } + } + // match shortlink::get_shortlink(pool, id).await { + // Ok(record) => { + // url = Box::leak(record.url.into_boxed_str()); + // } + // Err(err) => { + // println!("err = {:#?}", err); + // } + // } + let mut headers = HeaderMap::new(); + headers.insert(LOCATION, url.parse().unwrap()); + (StatusCode::FOUND, headers, ()) +} + +pub async fn not_found() -> impl IntoResponse { + (StatusCode::OK, "404 Not Found") +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/mod.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/mod.rs new file mode 100644 index 0000000..1ddf45a --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/mod.rs @@ -0,0 +1,2 @@ +pub mod controllers; +pub mod models; \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/dto.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/dto.rs new file mode 100644 index 0000000..16b4c4b --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/dto.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CreateShortLinkReq { + pub url: String +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CreateUserResp { + pub ok: bool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DeleteShortLinkReq { + pub id: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DeleteShortLinkResp { + pub ok: bool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ShortLinkInfoResp { + pub id: u32, + pub url: String +} diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/mod.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/mod.rs new file mode 100644 index 0000000..a95f352 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/mod.rs @@ -0,0 +1,2 @@ +pub mod shortlink; +pub mod dto; diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/shortlink.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/shortlink.rs new file mode 100644 index 0000000..3828e76 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/app/models/shortlink.rs @@ -0,0 +1,41 @@ +use sqlx::{Error, MySql, Pool, FromRow}; +use sqlx::mysql::MySqlQueryResult; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, FromRow, Clone)] +pub struct ShortLink { + pub id: u32, + pub url: String, +} + +pub async fn create_shortlink(pool: &Pool, url: &str) -> Result { + sqlx::query( + r#" + INSERT INTO short_links (`url`) + VALUES(?)"#, + ) + .bind(url) + .execute(pool).await +} + +pub async fn delete_shortlink(pool: &Pool, id: u64) -> Result { + sqlx::query( + r#" + DELETE FROM short_links + WHERE id = ? + "#, + ) + .bind(id) + .execute(pool).await +} + +// pub async fn get_shortlink(pool: &Pool, id: i32) -> Result { +// sqlx::query_as::<_, ShortLink>( +// r#" +// SELECT * FROM short_links +// WHERE id = ? +// "#, +// ) +// .bind(id) +// .fetch_one(pool).await +// } diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/database.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/database.rs new file mode 100644 index 0000000..c72c07d --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/database.rs @@ -0,0 +1,16 @@ +use sqlx::mysql::MySqlPoolOptions; +use sqlx::{MySql, Pool}; +use redis::Client; +use redis::aio::Connection; + +pub async fn do_connect() -> Pool { + let pool = MySqlPoolOptions::new() + .max_connections(5) + .connect("mysql://root:jkxsl12369@127.0.0.1/shorten_db").await; + pool.unwrap() +} + +pub async fn do_redis_connect() -> Connection { + let client = redis::Client::open("redis://127.0.0.1/").unwrap(); + client.get_async_connection().await.unwrap() +} diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/env.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/env.rs new file mode 100644 index 0000000..5c7b79d --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/env.rs @@ -0,0 +1,26 @@ +// use sqlx::{Pool, MySql}; +// use redis::aio::Connection; +// use std::sync::Arc; +// +// #[derive(Clone, Debug)] +// pub struct Environment { +// mysql_conn: Pool, +// redis_conn: Arc::new +// } +// +// impl Environment { +// pub async fn new(mysql_conn: Pool, redis_conn: Arc::new) -> anyhow::Result { +// Ok(Self { +// mysql_conn, +// redis_conn +// }) +// } +// +// pub fn db(self) -> Pool { +// self.mysql_conn +// } +// +// pub fn clients(self) -> Arc::new { +// self.redis_conn +// } +// } \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/mod.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/mod.rs new file mode 100644 index 0000000..36c3b87 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/mod.rs @@ -0,0 +1,3 @@ +pub mod routes; +pub mod database; +pub mod env; \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/routes.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/routes.rs new file mode 100644 index 0000000..6758d60 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/config/routes.rs @@ -0,0 +1,29 @@ +use axum::handler::{post, get}; +use axum::{Router, AddExtensionLayer}; +use axum::routing::BoxRoute; +use redis::Client; + +use crate::app::controllers::shortlink_controller; +use sqlx::{Pool, MySql}; +use redis::aio::Connection; +use std::sync::Arc; +use tokio::sync::{RwLock, Mutex}; + +pub fn app(pool: Pool, redis_client: Connection) -> Router { + Router::new() + .route("/", get(|| async { "welcome to use axum!" })) + .nest("/api", short_links()) + .layer(AddExtensionLayer::new(pool)) + .layer(AddExtensionLayer::new(Arc::new(Mutex::new(redis_client)))) + .layer(tower_http::trace::TraceLayer::new_for_http()) + .boxed() +} + +pub fn short_links() -> Router { + Router::new() + .route("/create_shortlink", post(shortlink_controller::create_shortlink)) + .route("/delete_shortlink", post(shortlink_controller::delete_shortlink)) + .route("/:id", get(shortlink_controller::get_shortlink)) + .route("/not_found", get(shortlink_controller::not_found)) + .boxed() +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/main.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/main.rs new file mode 100644 index 0000000..6adfdde --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/main.rs @@ -0,0 +1,16 @@ +use std::error::Error; +use std::net::SocketAddr; + +mod config; +mod app; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + let pool = config::database::do_connect().await; + let redis_client = config::database::do_redis_connect().await; + axum::Server::bind(&addr) + .serve(config::routes::app(pool, redis_client).into_make_service()) + .await?; + Ok(()) +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/server.rs b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/server.rs new file mode 100644 index 0000000..9ef5085 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/api-service-demo/src/server.rs @@ -0,0 +1,42 @@ +use tonic::{transport::Server, Request, Response, Status}; + +use short_link::short_link_server::{ShortLink, ShortLinkServer}; + +use short_link::{ShortLinkReply, ShortLinkRequest}; + +pub mod short_link { + tonic::include_proto!("shortlink"); +} + +#[derive(Debug, Default)] +pub struct MyShortLink {} + +#[tonic::async_trait] +impl ShortLink for MyShortLink { + async fn get_info( + &self, + request: Request, + ) -> Result, Status> { + println!("Got a request: {:?}", request); + + // todo: 需要实现根据request.id来查询数据库和读redis, 这些都是上次公开课说过的, 所以就不写了 + let reply = short_link::ShortLinkReply { + url: String::from("http://www.baidu.com"), + }; + + Ok(Response::new(reply)) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50052".parse()?; + let shortlink = MyShortLink::default(); + + Server::builder() + .add_service(ShortLinkServer::new(shortlink)) + .serve(addr) + .await?; + + Ok(()) +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/.DS_Store b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/.DS_Store new file mode 100644 index 0000000..d26de40 Binary files /dev/null and b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/.DS_Store differ diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/Cargo.toml b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/Cargo.toml new file mode 100644 index 0000000..5ebe358 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "helloworld-tonic" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] # 用来运行 HelloWorld gRPC 服务器的可执行文件 +name = "helloworld-server" +path = "src/server.rs" + +[[bin]] # 用来运行 HelloWorld gRPC 客户端的可执行文件 +name = "helloworld-client" +path = "src/client.rs" + +[dependencies] +tonic = "0.5" +prost = "0.8" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } + +[build-dependencies] +tonic-build = "0.5" \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/build.rs b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/build.rs new file mode 100644 index 0000000..0c941bb --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/helloworld.proto")?; + Ok(()) +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/proto/helloworld.proto b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/proto/helloworld.proto new file mode 100644 index 0000000..417962c --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/proto/helloworld.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package helloworld; + +service Greeter { + rpc SayHello(HelloRequest) returns (HelloReply); +} + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/client.rs b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/client.rs new file mode 100644 index 0000000..f94eeab --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/client.rs @@ -0,0 +1,20 @@ +use hello_world::greeter_client::GreeterClient; +use hello_world::HelloRequest; +pub mod hello_world { + tonic::include_proto!("helloworld"); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = GreeterClient::connect("http://[::1]:50051").await?; + + let request = tonic::Request::new(HelloRequest { + name: "Tonic".into(), + }); + + let response = client.say_hello(request).await?; + + println!("RESPONSE={:?}", response); + + Ok(()) +} \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/main.rs b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/main.rs new file mode 100644 index 0000000..454e837 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/main.rs @@ -0,0 +1,9 @@ +// fn main() -> Result<(), Box> { +// tonic_build::configure() +// .build_server(false) +// .compile( +// &["proto/helloworld/helloworld.proto"], +// &["proto/helloworld"], +// )?; +// Ok(()) +// } \ No newline at end of file diff --git a/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/server.rs b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/server.rs new file mode 100644 index 0000000..4bc1b89 --- /dev/null +++ b/12-初探Rust微服务架构(gRPC+Tonic)/helloworld-tonic/src/server.rs @@ -0,0 +1,40 @@ +use tonic::{transport::Server, Request, Response, Status}; + +use hello_world::greeter_server::{Greeter, GreeterServer}; +use hello_world::{HelloReply, HelloRequest}; + +pub mod hello_world { + tonic::include_proto!("helloworld"); +} + +#[derive(Debug, Default)] +pub struct MyGreeter {} + +#[tonic::async_trait] +impl Greeter for MyGreeter { + async fn say_hello( + &self, + request: Request, + ) -> Result, Status> { + println!("Got a request: {:?}", request); + + let reply = hello_world::HelloReply { + message: format!("Hello {}!", request.into_inner().name).into(), + }; + + Ok(Response::new(reply)) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50051".parse()?; + let greeter = MyGreeter::default(); + + Server::builder() + .add_service(GreeterServer::new(greeter)) + .serve(addr) + .await?; + + Ok(()) +} \ No newline at end of file