iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0

在現代軟體開發過程中,模組化管理已經是一個非常重要的概念。透過模組化,我們可以更有效地管理專案中的程式碼,同時也可以更方便地在未來擴展功能。

從顏色到尺寸,漸進的功能擴展

一開始,我們只有一個基本產生 QR Code 的功能。不過,隨著時間的推移,我們逐漸增加了許多的功能,比如前景和背景顏色的選擇,以及昨天新增的圖片尺寸縮放功能。

但是我們現在都是把所有程式碼都寫在 main.rs 當中,目前感覺程式碼的行數已經有點多了,所以今天的目標就是要把專案做一個模組化的重構。

新增模組資料夾與檔案

首先,建立兩個新的資料夾,分別是 api 和 models,來分別放 API 相關的功能和模型資料結構。

$ mkdir api models

接著在剛剛新增的資料夾中,再新增兩個檔案。

$ touch ./src/api/mod.rs ./src/models/mod.rs

重構 models

main.rs 中的 Info 結構移到 models 資料夾下的 mod.rs

#[derive(serde::Deserialize)]
pub struct Info {
    pub url: String,
    pub foreground: Option<String>,
    pub background: Option<String>,
    pub dimension: Option<u32>,
}

然後要在 struct 前面標示 pub,而且在其 struct 中的每個屬性都加上 pub,不然在其他檔案會拿不到。

重構 API

main.rs 裡所有與 API 相關的程式碼 (例如 generate_svg()) 移到 api 資料夾下的 mod.rs

use actix_web::{get, post, web, HttpResponse};
use image::{DynamicImage, Luma};
use qrcode::render::svg;
use qrcode::QrCode;
use regex::Regex;

use crate::models::Info;

const MIN_DIMENSION: u32 = 100;
const MAX_DIMENSION: u32 = 2000;

fn is_valid_color(color: &str) -> bool {
    let re = Regex::new(r"^#[0-9a-fA-F]{6}$").unwrap();
    re.is_match(color)
}

#[get("/generate_qr")]
async fn index(data: web::Query<Info>) -> HttpResponse {
    let code = match QrCode::new(data.url.as_bytes()) {
        Ok(c) => c,
        Err(_) => return HttpResponse::BadRequest().body("你輸入的字串無法處理"),
    };

    let image = code.render::<Luma<u8>>().build();

    let mut buffer = Vec::new();
    match DynamicImage::ImageLuma8(image).write_to(&mut buffer, image::ImageOutputFormat::Png) {
        Ok(_) => (),
        Err(_) => return HttpResponse::InternalServerError().body("無法生成圖像"),
    }

    HttpResponse::Ok().content_type("image/png").body(buffer)
}

#[post("/generate_qr_svg")]
async fn generate_svg(data: web::Json<Info>) -> HttpResponse {
    let fg_color_str = match &data.foreground {
        Some(color) if is_valid_color(color) => color,
        _ => "#000000",
    };

    let bg_color_str = match &data.background {
        Some(color) if is_valid_color(color) => color,
        _ => "#FFFFFF",
    };

    let code = match QrCode::new(data.url.as_bytes()) {
        Ok(c) => c,
        Err(_) => return HttpResponse::BadRequest().body("你輸入的字串無法處理"),
    };

    let size = match &data.dimension {
        Some(dimension) if *dimension >= MIN_DIMENSION && *dimension <= MAX_DIMENSION => *dimension,
        _ => 200,
    };

    let image = code
        .render()
        .min_dimensions(size, size)
        .dark_color(svg::Color(fg_color_str))
        .light_color(svg::Color(bg_color_str))
        .build();

    HttpResponse::Ok().content_type("image/svg+xml").body(image)
}

然後在 main.rs 裡,引入 api 跟 models 的模組,並且整理一下程式碼:

use actix_web::{App, HttpServer};

mod models;
mod api;
use api::{generate_svg, index};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(index).service(generate_svg))
        .bind("127.0.0.1:8080")?
        .run()
        .await
}

最後執行 cargo run,測試一下是否沒問題,我們今天的模組重構的部分就完成了🎉。

結語

通過模組化重構,我們已經為未來的發展建立了穩固的基礎。模組化使得我們的應用更為靈活,也使得維護和擴充變得更加簡單。

下一步,我們計劃繼續擴充更多功能和選項,所有這些都將基於我們現有的模組化設計,明天見👋!


上一篇
Day 6 - 為 QR Code 新增圖片大小縮放功能
下一篇
Day 8 - 產生電話版本的 QR code
系列文
30 天用 Rust 打造 QR Code 製造機30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言