zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use lazy_static::lazy_static;

use crate::prelude2::*;

use std::collections::HashMap;
use std::str::FromStr;
// use futures_util::pin_mut;
use futures_util::stream::StreamExt;

pub mod upload_files;
pub mod upload_images;
pub mod upload_videos;

pub use upload_images::*;
pub use upload_videos::*;

lazy_static! {
    pub static ref TEMP_UPLOAD_FOLDER: String = crate::commons::upload_folder();
    pub static ref GRID_FS_DATABASE_NAME: String = String::from("mygridfs");
}

#[repr(usize)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum UploadType {
    /// 头像
    ProfilePicture,
    IdCardPicture1a,
    IdCardPicture2b,
    CertificatePicture,
    DiplomaPicture,
    SimpleUploader,
}

/// 当前支持的文件上传类型
pub static UPLOAD_TYPE_NAMES: [&str; 6] = [
    "profile_picture",     // 头像(pp)
    "idcard_picture1a",    // 身份证正面(id1a)
    "idcard_picture2a",    // 身份证反面(id2b)
    "certificate_picture", // 从业资格证书(cf)
    "diploma_picture",     // 毕业证书(dp)
    "simple_uploader",     //
];

impl UploadType {
    fn from_usize(u: usize) -> Option<UploadType> {
        match u {
            0 => Some(UploadType::ProfilePicture),
            1 => Some(UploadType::IdCardPicture1a),
            2 => Some(UploadType::IdCardPicture2b),
            3 => Some(UploadType::CertificatePicture),
            4 => Some(UploadType::DiplomaPicture),
            5 => Some(UploadType::SimpleUploader),
            _ => None,
        }
    }

    #[inline]
    pub fn max() -> UploadType {
        UploadType::ProfilePicture
    }

    pub fn as_str(&self) -> &'static str {
        UPLOAD_TYPE_NAMES[*self as usize]
    }

    pub fn iter() -> impl Iterator<Item = Self> {
        (0..7).map(|i| Self::from_usize(i).unwrap())
    }
}

#[allow(missing_copy_implementations)]
#[derive(Debug, PartialEq, Eq)]
pub struct ParseUploadError(());

fn ok_or<T, E>(t: Option<T>, e: E) -> core::result::Result<T, E> {
    match t {
        Some(t) => Ok(t),
        None => Err(e),
    }
}

impl FromStr for UploadType {
    type Err = ParseUploadError;
    fn from_str(types: &str) -> core::result::Result<UploadType, Self::Err> {
        ok_or(
            UPLOAD_TYPE_NAMES
                .iter()
                .position(|&name| name.eq_ignore_ascii_case(types))
                .map(|p| UploadType::from_usize(p).unwrap()),
            ParseUploadError(()),
        )
    }
}

// 根据文件md5查询文件
pub async fn get_file_by_md5(
    query: web::Query<HashMap<String, String>>,
    request: HttpRequest,
    ctx: web::Data<AppContext>,
) -> impl Responder {
    if let Some(md5) = query.get("md5") {
        if let Ok(Some((_id, flength))) = ctx
            .mongo()
            .get_file_by_md5(&GRID_FS_DATABASE_NAME, md5)
            .await
        {
            request.json(200, R::ok((_id, flength.to_string())))
        } else {
            request.json(200, R::ok(Vec::<String>::new()))
        }
    } else {
        request.json(200, R::ok(Vec::<String>::new()))
    }
}

// 文件上传表单
pub async fn file_upload_form(
    query: web::Query<HashMap<String, String>>,
    request: HttpRequest,
    app_state: web::Data<AppContext>,
) -> impl Responder {
    if let Some(id) = query.get("id") {
        if let Some(content_type) = app_state
            .mongo()
            .get_file_content_type(&GRID_FS_DATABASE_NAME, id)
            .await?
        {
            let mut ctx = tera::Context::new();

            if content_type == "video/mp4" {
                ctx.insert("video_id", &id);
                ctx.insert("mime_type", &content_type);
            } else if content_type == "image/jpeg"
                || content_type == "image/jpg"
                || content_type == "image/png"
                || content_type == "image/gif"
            {
                ctx.insert("image_id", &id);
                ctx.insert("mime_type", &content_type);
            } else {
                log::warn!(
                    "file_upload_form: id={}, message={}",
                    id,
                    "not support file mime type!"
                )
            }

            return request.render(200, "upload0/file_upload.html", ctx);
        }

        request.render(200, "defaults/404.html", tera::Context::new())
    } else {
        request.render(200, "upload0/file_upload.html", tera::Context::new())
    }
}

pub async fn image_viewer(
    image_id: actix_web::web::Path<String>,
    _request: HttpRequest,
    app_state: web::Data<AppContext>,
) -> actix_web::Result<HttpResponse, actix_web::Error> {
    match app_state
        .mongo()
        .get_file(&GRID_FS_DATABASE_NAME, &image_id.to_string())
        .await
    {
        Ok((mime_type, mut _stream)) => {
            let stream = async_stream::stream! {
                while let Some(chunk) = _stream.next().await {
                    yield Ok::<_, actix_web::Error>(web::Bytes::from(chunk));
                }
            };

            // pin_mut!(stream); // needed for iteration
            let mut response = HttpResponse::Ok();

            match mime_type {
                Some(mime_type) => {
                    // 设置适当的 Content-Type 头
                    response.content_type(mime_type);
                }
                None => {
                    // 假设图像是 JPEG 格式
                    response.content_type("image/jpeg");
                }
            }

            // 将异步流添加到响应中
            Ok(response.streaming(stream))
        }
        Err(e) => Ok(HttpResponse::BadRequest().json(R::failed(400, e.to_string()))),
    }
}

pub async fn download() -> actix_web::Result<HttpResponse, actix_web::Error> {
    // 创建字节流
    let chunk1: Vec<u8> = vec![65, 66, 67];
    let chunk2: Vec<u8> = vec![68, 69, 70];
    let chunk3: Vec<u8> = vec![71, 72, 73];

    // 创建一个异步流,将分块的字节数据发送给客户端
    let stream = futures_util::stream::iter(vec![chunk1, chunk2, chunk3])
        .map(|chunk| Ok::<_, actix_web::Error>(web::Bytes::from(chunk)))
        .boxed();

    // 构建响应并设置 Content-Type 头
    let content_type = "text/plain";

    let response = HttpResponse::Ok()
        .content_type(content_type)
        // .transfer_encoding("chunked") // 使用 Transfer-Encoding 头以分块传输数据
        // .no_chunking(1)
        .streaming(stream);

    Ok(response)
}

pub fn clear_files(file_list: Vec<std::path::PathBuf>) {
    for destination in file_list {
        if let Err(err) = std::fs::remove_file(destination) {
            log::error!("clear-files-error: error={:?}", err)
        }
    }
}