joshsulin
3 years ago
committed by
GitHub
26 changed files with 446 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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" |
@ -0,0 +1,5 @@ |
|||
fn main() -> Result<(), Box<dyn std::error::Error>> { |
|||
println!("======"); |
|||
tonic_build::compile_protos("proto/shortlink.proto")?; |
|||
Ok(()) |
|||
} |
@ -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; |
|||
} |
@ -0,0 +1 @@ |
|||
pub mod shortlink_controller; |
@ -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<dto::CreateShortLinkReq>, |
|||
Extension(pool): Extension<Pool<MySql>> |
|||
) -> 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<dto::DeleteShortLinkReq>, |
|||
Extension(pool): Extension<Pool<MySql>>, |
|||
) -> 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<i32>, |
|||
req: Request<Body> |
|||
) -> impl IntoResponse { |
|||
let mut url = String::from("/api/not_found"); |
|||
//let pool = req.extensions().get::<Pool<MySql>>().unwrap();
|
|||
let mut con = req.extensions().get::<Arc<Mutex<Connection>>>().unwrap().lock().await; |
|||
let mut redis_key = String::from("url_"); |
|||
redis_key.push_str(&*id.to_string()); |
|||
let res: RedisResult<String> = 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") |
|||
} |
@ -0,0 +1,2 @@ |
|||
pub mod controllers; |
|||
pub mod models; |
@ -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 |
|||
} |
@ -0,0 +1,2 @@ |
|||
pub mod shortlink; |
|||
pub mod dto; |
@ -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<MySql>, url: &str) -> Result<MySqlQueryResult, Error> { |
|||
sqlx::query( |
|||
r#" |
|||
INSERT INTO short_links (`url`) |
|||
VALUES(?)"#, |
|||
) |
|||
.bind(url) |
|||
.execute(pool).await |
|||
} |
|||
|
|||
pub async fn delete_shortlink(pool: &Pool<MySql>, id: u64) -> Result<MySqlQueryResult, Error> { |
|||
sqlx::query( |
|||
r#" |
|||
DELETE FROM short_links |
|||
WHERE id = ? |
|||
"#, |
|||
) |
|||
.bind(id) |
|||
.execute(pool).await |
|||
} |
|||
|
|||
// pub async fn get_shortlink(pool: &Pool<MySql>, id: i32) -> Result<ShortLink, Error> {
|
|||
// sqlx::query_as::<_, ShortLink>(
|
|||
// r#"
|
|||
// SELECT * FROM short_links
|
|||
// WHERE id = ?
|
|||
// "#,
|
|||
// )
|
|||
// .bind(id)
|
|||
// .fetch_one(pool).await
|
|||
// }
|
@ -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<MySql> { |
|||
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() |
|||
} |
@ -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<MySql>,
|
|||
// redis_conn: Arc::new<Connection>
|
|||
// }
|
|||
//
|
|||
// impl Environment {
|
|||
// pub async fn new(mysql_conn: Pool<MySql>, redis_conn: Arc::new<Connection>) -> anyhow::Result<Self> {
|
|||
// Ok(Self {
|
|||
// mysql_conn,
|
|||
// redis_conn
|
|||
// })
|
|||
// }
|
|||
//
|
|||
// pub fn db(self) -> Pool<MySql> {
|
|||
// self.mysql_conn
|
|||
// }
|
|||
//
|
|||
// pub fn clients(self) -> Arc::new<Connection> {
|
|||
// self.redis_conn
|
|||
// }
|
|||
// }
|
@ -0,0 +1,3 @@ |
|||
pub mod routes; |
|||
pub mod database; |
|||
pub mod env; |
@ -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<MySql>, redis_client: Connection) -> Router<BoxRoute> { |
|||
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<BoxRoute> { |
|||
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() |
|||
} |
@ -0,0 +1,16 @@ |
|||
use std::error::Error; |
|||
use std::net::SocketAddr; |
|||
|
|||
mod config; |
|||
mod app; |
|||
|
|||
#[tokio::main] |
|||
async fn main() -> Result<(), Box<dyn Error>> { |
|||
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(()) |
|||
} |
@ -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<ShortLinkRequest>, |
|||
) -> Result<Response<ShortLinkReply>, 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<dyn std::error::Error>> { |
|||
let addr = "[::1]:50052".parse()?; |
|||
let shortlink = MyShortLink::default(); |
|||
|
|||
Server::builder() |
|||
.add_service(ShortLinkServer::new(shortlink)) |
|||
.serve(addr) |
|||
.await?; |
|||
|
|||
Ok(()) |
|||
} |
Binary file not shown.
@ -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" |
@ -0,0 +1,4 @@ |
|||
fn main() -> Result<(), Box<dyn std::error::Error>> { |
|||
tonic_build::compile_protos("proto/helloworld.proto")?; |
|||
Ok(()) |
|||
} |
@ -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; |
|||
} |
@ -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<dyn std::error::Error>> { |
|||
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(()) |
|||
} |
@ -0,0 +1,9 @@ |
|||
// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// tonic_build::configure()
|
|||
// .build_server(false)
|
|||
// .compile(
|
|||
// &["proto/helloworld/helloworld.proto"],
|
|||
// &["proto/helloworld"],
|
|||
// )?;
|
|||
// Ok(())
|
|||
// }
|
@ -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<HelloRequest>, |
|||
) -> Result<Response<HelloReply>, 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<dyn std::error::Error>> { |
|||
let addr = "[::1]:50051".parse()?; |
|||
let greeter = MyGreeter::default(); |
|||
|
|||
Server::builder() |
|||
.add_service(GreeterServer::new(greeter)) |
|||
.serve(addr) |
|||
.await?; |
|||
|
|||
Ok(()) |
|||
} |
Loading…
Reference in new issue