在現代軟體開發過程中,模組化管理已經是一個非常重要的概念。透過模組化,我們可以更有效地管理專案中的程式碼,同時也可以更方便地在未來擴展功能。
一開始,我們只有一個基本產生 QR Code 的功能。不過,隨著時間的推移,我們逐漸增加了許多的功能,比如前景和背景顏色的選擇,以及昨天新增的圖片尺寸縮放功能。
但是我們現在都是把所有程式碼都寫在 main.rs
當中,目前感覺程式碼的行數已經有點多了,所以今天的目標就是要把專案做一個模組化的重構。
首先,建立兩個新的資料夾,分別是 api 和 models,來分別放 API 相關的功能和模型資料結構。
$ mkdir api models
接著在剛剛新增的資料夾中,再新增兩個檔案。
$ touch ./src/api/mod.rs ./src/models/mod.rs
把 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
,不然在其他檔案會拿不到。
把 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
,測試一下是否沒問題,我們今天的模組重構的部分就完成了🎉。
通過模組化重構,我們已經為未來的發展建立了穩固的基礎。模組化使得我們的應用更為靈活,也使得維護和擴充變得更加簡單。
下一步,我們計劃繼續擴充更多功能和選項,所有這些都將基於我們現有的模組化設計,明天見👋!