iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
SideProject30

30 天用 Rust 打造 QR Code 製造機系列 第 9

Day 9 - 產生地址和 Mail 的 QR code

  • 分享至 

  • xImage
  •  

接續昨天成果擴充了電話號碼的 QR code 功能後,今天我們打鐵趁熱,要進一步地新增兩種新的 QR code 類型,分別是 Mail 和地址。對於地址部分,會使用 Google 的 Geocoding API 來取得經緯度。

為什麼需要這些新功能?

在商務或社交場合,除了電話號碼外,電子郵件和實體地址也是常需要交換的資訊。透過 QR Code,這些信息交換會變得更為便捷和高效。

更新 Info 結構

首先,在 models 資料夾下的 mod.rs 中,我們再次增加新的欄位來儲存電子郵件和地址。

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

如同之前一樣的設定,我們使用 Option 來應對不同需求。

調用 Google Geocoding API

設定 Google Cloud 專案

為了將地址轉換成經緯度,我們需要調用 Geocoding API。你需要先設定一個 Google Cloud

  • 先建立一個新專案。

https://ithelp.ithome.com.tw/upload/images/20230924/20120293mloxfgbPiK.png

  • 專案名稱取一個好辨識的名稱。

https://ithelp.ithome.com.tw/upload/images/20230924/20120293zLfu6DzhLD.png

  • 點擊 API 和服務後,搜尋 Geocoding API

https://ithelp.ithome.com.tw/upload/images/20230924/20120293pIfRQuzeJO.png

  • 啟用 Geocoding API 的服務

https://ithelp.ithome.com.tw/upload/images/20230924/20120293VXSiDyLj9g.png

接下來要做一些驗證,之後我們就可以取得價值 $300 美元的 Google Cloud 免費抵免額。

需要綁定信用卡以及回答一些問題,這部分都可以按照個人情況選擇回應。

https://ithelp.ithome.com.tw/upload/images/20230924/20120293ZrpdZ7iluw.png

最後會拿到一組專屬你的 API 金鑰,要保管好。

https://ithelp.ithome.com.tw/upload/images/20230924/20120293ZXEmoSFdCn.png

反向地理編碼 (地址查詢) 要求和回應

拿到 API 金鑰之後,接下來就可以嘗試看看是否可以使用,以下是 API 的範例,記得 YOUR_API_KEY 替換成自己的 API 金鑰:

https://maps.googleapis.com/maps/api/geocode/json?address=台北市中正區衡陽路7號&key=YOUR_API_KEY

回傳的結果可以拿到我們要的經緯度:

https://ithelp.ithome.com.tw/upload/images/20230924/201202937k9k6nyMzq.png

新增 API 處理地址

在開始之前,我們需要安裝一些套件來做處理。

cargo add reqwest -F json
cargo add serde_json dotenv

reqwest 會幫助我們來發送 HTTP 請求,因為我們需要跟 Google Geocoding 去請求並回傳結果。

serde_json 則是處理送回來的 JSON,我們需要拿到經緯度的資料。

最後我們不希望把 API 金鑰會跟著程式碼一起上傳到 GitHub,所以會新增一個 .env 的環境變數來存放,這時候就需要 dotenv 來幫我們處理。

現在我們的 Cargo.toml 的 dependencies 應該會是這樣:

[dependencies]
actix-web = "4.4.0"
dotenv = "0.15.0"
image = "0.23.0"
qrcode = "0.12.0"
regex = "1.9.5"
reqwest = { version = "0.11.20", features = ["json"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"

接著引入必要的套件:

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

新增一個函式來處理地址轉換經緯度:

async fn get_coordinates(address: &str) -> Result<(f64, f64), reqwest::Error> {
    dotenv().ok();

    let api_key = env::var("GOOGLE_MAPS_API_KEY").expect("GOOGLE_MAPS_API_KEY must be set");
    let url = format!(
        "https://maps.googleapis.com/maps/api/geocode/json?address={}&key={}",
        address, api_key
    );

    let response: serde_json::Value = reqwest::get(&url).await?.json().await?;

    let lat = response["results"][0]["geometry"]["location"]["lat"]
        .as_f64()
        .unwrap();
    let lng = response["results"][0]["geometry"]["location"]["lng"]
        .as_f64()
        .unwrap();

    Ok((lat, lng))
}

最後在 generate_svg() 新增處理 emailaddress

#[post("/generate_qr_svg")]
async fn generate_svg(data: web::Json<Info>) -> HttpResponse {
    // 省略

    let code_data = match data
        .url
        .as_ref()
        .map(|url| url.as_bytes().to_vec())
        .or_else(|| {
            data.phone
                .as_ref()
                .map(|phone| format!("tel:{}", phone).as_bytes().to_vec())
        })
        .or_else(|| {
            data.email
                .as_ref()
                .map(|mail| format!("mailto:{}", mail).as_bytes().to_vec())
        })
        .or_else(|| {
            data.address
                .as_ref()
                .and_then(|address| get_coordinates(address).await.ok())
                .map(|(lat, lng)| format!("geo:{},{}", lat, lng).as_bytes().to_vec())
        }) {
        Some(data) => data,
        None => return HttpResponse::BadRequest().body("缺少有效數據"),
    };

    // 省略
}

不過,執行後會發現有問題,因為預期在處理 address 的回傳資料想要用 await 去等待資料回傳,但是 Cargo 會告訴我們,說 await 只能在 async 函式或區塊中使用。

解決 await 問題

為了解決這個問題,可以將這部分的邏輯移到一個獨立的 async 函式中,然後在主要函式中再去做 await 這個新函式。

async fn get_code_data(data: &Info) -> Option<Vec<u8>> {
    if let Some(url) = &data.url {
        return Some(url.as_bytes().to_vec());
    }
    if let Some(phone) = &data.phone {
        return Some(format!("tel:{}", phone).as_bytes().to_vec());
    }
    if let Some(email) = &data.email {
        return Some(format!("mailto:{}", email).as_bytes().to_vec());
    }
    if let Some(address) = &data.address {
        if let Ok((lat, lng)) = get_coordinates(address).await {
            return Some(format!("geo:{},{}", lat, lng).as_bytes().to_vec());
        }
    }
    None
}

#[post("/generate_qr_svg")]
async fn generate_svg(data: web::Json<Info>) -> HttpResponse {
    // 省略

    let code_data = match get_code_data(&data).await {
        Some(data) => data,
        None => return HttpResponse::BadRequest().body("缺少有效數據"),
    };

    // 省略
}

測試結果

在 Postman 測試地址的結果是 OK 的。

https://ithelp.ithome.com.tw/upload/images/20230924/20120293L1b6nRKedD.png

測試一下 Email 也是沒問題。

https://ithelp.ithome.com.tw/upload/images/20230924/20120293MK4xIHDe5K.png

結語

今天新增了兩種新的 QR Code 類型,明天我們將進一步優化和重構我們的程式,以達到更高的效能和可讀性。

我們明天見!👋


上一篇
Day 8 - 產生電話版本的 QR code
下一篇
Day 10 - 為 Rust 單元測試
系列文
30 天用 Rust 打造 QR Code 製造機30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
阿鵝
iT邦新手 1 級 ‧ 2023-09-24 13:09:53

我的手機掃完跳轉到 Apple 地圖,然後變 9 號 /images/emoticon/emoticon37.gif

Bucky iT邦新手 3 級 ‧ 2023-09-24 14:01:35 檢舉

對啊,geocoding 在轉換台灣地址會差一點XD

我要留言

立即登入留言