zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use actix_http::header::CONTENT_ENCODING;
use actix_http::ContentEncoding;
use lazy_static::lazy_static;
use qrcode::render::svg;
use qrcode::EcLevel;
use qrcode::QrCode;
use qrcode::Version;
use rand::Rng;

use crate::prelude2::*;

use actix_web::http::header;
use futures_util::TryStreamExt as _;
use image;
use mime::Mime;
use mime::IMAGE_GIF;
use mime::IMAGE_JPEG;
use mime::IMAGE_PNG;
use rqrr;

lazy_static! {
    static ref LIMIT_IMAGE_FILE_COUNT: usize = 1; // 支持的文件上传数量
    // static ref LIMIT_FILE_SIZE: usize = 1_048_576 * 150; // 上传文件大小限制 150MiB * n
    static ref LEGAL_IMAGE_FILETYPES: [Mime; 3] = [IMAGE_PNG, IMAGE_JPEG, IMAGE_GIF];
}

// otpauth://totp/Google:tomcat_jim1@gmail.com?
// algorithm=SHA1&
// digits=6&
// issuer=Google&
// period=30&
// secret=D******************************U
pub async fn detect_grids(
    mut payload: actix_multipart::Multipart,
    request: HttpRequest,
) -> impl Responder {
    let content_length = match request.headers().get(header::CONTENT_LENGTH) {
        Some(val) => val.to_str().unwrap_or("0").parse().unwrap(),
        None => 0,
    };

    if content_length == 0 {
        return request.json(200, R::failed(400, "上传图片为空!".to_string()));
    }

    if let Ok(Some(field)) = payload.try_next().await {
        let _field_name = field.name().to_owned();

        if let Some(file_type) = field.content_type() {
            if !LEGAL_IMAGE_FILETYPES.contains(file_type) {
                return request.json(
                    200,
                    R::failed(400, format!("不支持的图片类型: {}", &file_type)),
                );
            }
        }

        let (_file_name, destination, _) = crate::commons::save_upload(field).await?;

        let img = image::open(&destination).unwrap().to_luma8();
        let mut img = rqrr::PreparedImage::prepare(img);
        let grids = img.detect_grids();
        let (_meta, content) = grids[0].decode().unwrap();

        if let Err(err) = std::fs::remove_file(&destination) {
            log::error!(
                "clear-files-error: destination={:?}, error={:?}",
                destination,
                err
            )
        }

        request.json(200, R::ok(content))
    } else {
        request.json(200, R::failed(400, "请提供上传文件"))
    }
}

// otpauth://totp/Google:tomcat_jim1@gmail.com?
// algorithm=SHA1&
// digits=6&
// issuer=Google&
// period=30&
// secret=D******************************U
pub async fn generate_otpcode(
    query: web::Query<HashMap<String, String>>,
    _request: HttpRequest,
) -> impl Responder {
    // if query.get("name").is_none() {
    //     return request.json(400, R::failed(413, "name is required"));
    // };

    let name = query.get("name").unwrap();

    let mut rng = rand::rng();

    let secret: String = std::iter::repeat_with(|| rng.sample(rand::distr::Alphanumeric))
        .map(char::from)
        .take(32)
        .collect();

    let period = 30;
    let issuer = "zero4rs";
    let code = format!("otpauth://totp/{issuer}:{name}?algorithm=SHA1&digits=6&issuer={issuer}&period={period}&secret={secret}");

    // 版本
    let version = match code.len() {
        0..=50 => Version::Normal(1),
        51..=100 => Version::Normal(2),
        101..=200 => Version::Normal(3),
        _ => Version::Normal(4), // 最高版本
    };

    // 纠错级别
    let ec_level = match code.len() {
        0..=50 => EcLevel::L,
        51..=200 => EcLevel::M,
        _ => EcLevel::Q, // 或更高的级别
    };

    let image3 = QrCode::with_version(code, version, ec_level)
        .map_err(|e| {
            log::error!("error={:?}", e);
        })
        .unwrap()
        .render()
        .min_dimensions(200, 200)
        .dark_color(svg::Color("#000000"))
        .light_color(svg::Color("#ffffff"))
        .build();

    HttpResponse::Ok()
        .content_type("image/svg+xml")
        .append_header((CONTENT_ENCODING, ContentEncoding::Identity))
        .append_header((
            "Cache-Control",
            "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0",
        ))
        .body(image3)
}