This commit is contained in:
lychang
2024-01-17 00:56:21 +08:00
commit a24c63024f
8 changed files with 2511 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/.idea

39
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,39 @@
stages:
- generate_image
- deploy
- notify
variables:
BUILD_NAME: "rust/storage"
SERVICE_NAME: "storage"
IMAGE_TAG: "dev"
generate_image_dev:
stage: generate_image
image: docker:20.10.2
script:
- docker build -t $BUILD_NAME:$IMAGE_TAG .
tags:
- '001'
only:
- dev
except:
- triggers
deploy_dev:
stage: deploy
image: docker:20.10.2
script:
- CONTAINER_NAME=$(docker ps -aq --filter name=$SERVICE_NAME)
- echo $CONTAINER_NAME
- if [[ -n "$CONTAINER_NAME" ]]; then
docker rm -f $CONTAINER_NAME;
fi
- docker run -d --name $SERVICE_NAME $BUILD_NAME:$IMAGE_TAG
tags:
- '001'
only:
- dev
except:
- triggers

2198
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "web"
version = "1.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytes = "1.5.0"
actix-web = "4.4.1"
actix-cors = "0.7.0"
rust-s3 ={version = "0.33.0" }
tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] }
serde = "1.0.195"

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM rust:1.75 as builder
WORKDIR /app
RUN USER=root cargo new storage
WORKDIR /app/storage
RUN mkdir -p .cargo
COPY config.toml .cargo/
COPY Cargo.toml Cargo.lock ./
COPY ./src src
RUN cargo install --path . --color always
# Copy the app to an base Docker image, here we use distroless
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y ca-certificates
RUN update-ca-certificates
RUN mkdir -p /cache
COPY --from=builder /usr/local/cargo/bin/storage /storage
USER 1000
ENTRYPOINT ["/storage"]

14
config.toml Normal file
View File

@@ -0,0 +1,14 @@
[http]
check-revoke = false
[source.crates-io]
replace-with='rsproxy'
[source.rsproxy]
registry="https://rsproxy.cn/crates.io-index"
[registries.rsproxy]
index = "https://rsproxy.cn/crates.io-index"
[net]
git-fetch-with-cli = true

135
src/main.rs Normal file
View File

@@ -0,0 +1,135 @@
use std::env;
use actix_cors::Cors;
use actix_web::{App, HttpServer, Responder, get, put, post, delete, web, CustomizeResponder};
use actix_web::http::StatusCode;
use actix_web::web::Json;
use bytes::Bytes;
use serde::Serialize;
mod mods;
use mods::MinioPrivate;
use crate::mods::FileManager;
#[derive(Serialize)]
struct Response {
error_code: u16,
error_message: String,
}
fn json_response(status_code: u16) -> CustomizeResponder<Json<Response>> {
let message = match status_code {
200 => { "Success!" }
201 => { "Create success!" }
204 => { "Delete success!" }
205 => { "Update success!" }
404 => { "Not exists!" }
409 => { "Resource conflict!" }
500 => { "Server error!" }
_ => { "Server error!" }
};
let resp = Response { error_code: status_code, error_message: message.to_string() };
Json(resp)
.customize()
.with_status(StatusCode::from_u16(status_code).unwrap())
.insert_header(("content-type", "application/json"))
}
fn bytes_response(file_content: Bytes, mut file_type: String) -> CustomizeResponder<Bytes> {
if file_type == "" {
file_type = "application/octet-stream".to_string();
}
return if file_content.is_empty() {
Bytes::from_static(b"{\"error_code\": 404,\"error_message\": \"Not exists!\"}")
.customize()
.with_status(StatusCode::from_u16(404).unwrap())
.insert_header(("content-type", "application/json"))
} else {
file_content
.customize()
.with_status(StatusCode::from_u16(200).unwrap())
.insert_header(("content-type", file_type))
};
}
async fn get_minio() -> MinioPrivate {
let region = env::var("MINIO_REGION").unwrap_or("".to_string());
let endpoint = env::var("MINIO_ENDPOINT").unwrap_or("http://192.168.1.100:9000".to_string());
let access_key = env::var("MINIO_ACCESS_KEY").unwrap_or("sTkzoRR9lQUmI5Hp".to_string());
let secret_key = env::var("MINIO_SECRET_KEY").unwrap_or("oDUG9GRlkNv3V76bVGjovp9228bP25WC".to_string());
return MinioPrivate {
region,
endpoint,
access_key,
secret_key,
};
}
#[get("/healthy")]
async fn healthy() -> impl Responder {
"Ok!".to_string()
}
#[post("/{bucket_name}/{file_path:.*}")]
async fn create_file(path: web::Path<(String, String)>, body: web::Payload) -> impl Responder {
let minio = get_minio().await;
let path_parameters = path.into_inner();
let work_dir = path_parameters.0;
let path_name = path_parameters.1;
let file_content = body.to_bytes().await.unwrap();
let status_code = minio.create(work_dir.as_str(), path_name.as_str(), file_content).await;
json_response(status_code)
}
#[put("/{bucket_name}/{file_path:.*}")]
async fn update_file(path: web::Path<(String, String)>, body: web::Payload) -> impl Responder {
let minio = get_minio().await;
let path_parameters = path.into_inner();
let work_dir = path_parameters.0;
let path_name = path_parameters.1;
let file_content = body.to_bytes().await.unwrap();
let status_code = minio.update(work_dir.as_str(), path_name.as_str(), file_content).await;
json_response(status_code)
}
#[get("/{bucket_name}/{file_path:.*}")]
async fn get_file(path: web::Path<(String, String)>) -> impl Responder {
let minio = get_minio().await;
let path_parameters = path.into_inner();
let work_dir = path_parameters.0;
let path_name = path_parameters.1;
let result = minio.get(work_dir.as_str(), path_name.as_str()).await;
bytes_response(result, "".to_string())
}
#[delete("/{bucket_name}/{file_path:.*}")]
async fn delete_file(path: web::Path<(String, String)>) -> impl Responder {
let minio = get_minio().await;
let path_parameters = path.into_inner();
let work_dir = path_parameters.0;
let path_name = path_parameters.1;
let status_code = minio.delete(work_dir.as_str(), path_name.as_str()).await;
json_response(status_code)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let cors = Cors::permissive();
App::new()
.wrap(cors)
.service(healthy)
.service(get_file)
.service(create_file)
.service(update_file)
.service(delete_file)
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}

86
src/mods.rs Normal file
View File

@@ -0,0 +1,86 @@
use bytes::Bytes;
use s3::bucket::Bucket;
use s3::region::Region;
use s3::creds::Credentials;
use core::time::Duration;
pub trait FileManager {
async fn exists(&self, work_dir: &str, file_path: &str) -> u16;
async fn get(&self, work_dir: &str, file_path: &str) -> Bytes;
async fn create(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16;
async fn update(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16;
async fn delete(&self, work_dir: &str, file_path: &str) -> u16;
async fn _bucket(&self, bucket_name: &str) -> Bucket;
}
pub struct MinioPrivate {
pub region: String,
pub endpoint: String,
pub access_key:String,
pub secret_key: String,
}
impl FileManager for MinioPrivate {
async fn exists(&self, work_dir: &str, file_path: &str) -> u16 {
let bucket = self._bucket(work_dir).await;
return match bucket.head_object(file_path).await {
Ok(_) => { 200 }
Err(_) => { 404 }
};
}
async fn get(&self, work_dir: &str, file_path: &str) -> Bytes {
let bucket = self._bucket(work_dir).await;
return match bucket.get_object(file_path).await {
Ok(value) => {
Bytes::from(value.to_vec())
}
Err(_) => {
Bytes::new()
}
};
}
async fn create(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16 {
if self.exists(work_dir, file_path).await == 200 {
return 409;
}
let bucket = self._bucket(work_dir).await;
return match bucket.put_object(file_path, file_content.as_ref()).await {
Ok(_) => { 201 }
Err(_) => { 500 }
};
}
async fn update(&self, work_dir: &str, file_path: &str, file_content: Bytes) -> u16 {
if self.exists(work_dir, file_path).await == 404 {
return 404;
}
let bucket = self._bucket(work_dir).await;
return match bucket.put_object(file_path, file_content.as_ref()).await {
Ok(_) => { 205 }
Err(_) => { 500 }
};
}
async fn delete(&self, work_dir: &str, file_path: &str) -> u16 {
if self.exists(work_dir, file_path).await == 404 {
return 404;
}
let bucket = self._bucket(work_dir).await;
return match bucket.delete_object(file_path).await {
Ok(_) => { 204 }
Err(_) => { 500 }
};
}
async fn _bucket(&self, bucket_name: &str) -> Bucket {
let region = Region::Custom { region: self.region.clone().into(), endpoint: self.endpoint.clone().into() };
let credentials = Credentials::new(Some(self.access_key.as_str()), Some(self.secret_key.as_str()), None, None, None).unwrap();
let mut bucket = Bucket::new(bucket_name, region, credentials).unwrap().with_path_style();
bucket.set_request_timeout(Option::from(Duration::new(60, 0)));
return bucket;
}
}