Skip to main content

qweather_sdk/
client.rs

1use crate::{api::APIResponse, WEATHER_API_URL, WEATHER_DEV_API_URL};
2use log::trace;
3use md5::{Digest, Md5};
4use reqwest::{Client, ClientBuilder};
5use serde_json::Value;
6use std::collections::BTreeMap;
7
8/// 天气API客户端
9pub struct QWeatherClient {
10    api_host: String,
11    /// 客户端
12    client: Client,
13    /// 基础查询参数
14    base_params: BTreeMap<String, String>,
15    /// 客户端配置
16    client_config: ClientConfig,
17}
18
19/// api 客户端配置
20pub struct ClientConfig {
21    /// 公钥
22    pub public_id: String,
23    /// 私钥
24    pub private_key: String,
25    /// 是否订阅
26    pub subscription: bool,
27    /// 多语言设置,请阅读[多语言](https://dev.qweather.com/docs/resource/language/)文档,了解我们的多语言是如何工作、如何设置以及数据是否支持多语言。
28    pub lang: Option<String>,
29    /// 数据单位设置,可选值包括unit=m(公制单位,默认)和unit=i(英制单位)。更多选项和说明参考度[量衡单位](https://dev.qweather.com/docs/resource/unit)。
30    pub unit: Option<String>,
31}
32
33impl ClientConfig {
34    /// 创建新的配置
35    pub fn new(public_id: impl ToString, private_key: impl ToString) -> Self {
36        ClientConfig {
37            public_id: public_id.to_string(),
38            private_key: private_key.to_string(),
39            subscription: false,
40            lang: Some("zh".to_string()),
41            unit: None,
42        }
43    }
44}
45
46impl QWeatherClient {
47    /// 使用配置创建新的客户端
48    pub fn with_config(client_config: ClientConfig) -> Self {
49        let api_host = if client_config.subscription {
50            WEATHER_API_URL.to_string()
51        } else {
52            WEATHER_DEV_API_URL.to_string()
53        };
54
55        let client = ClientBuilder::new()
56            .gzip(true)
57            .build()
58            .expect("Failed to create reqwest client");
59
60        let mut base_params = BTreeMap::new();
61        base_params.insert("publicid".to_string(), client_config.public_id.to_string());
62        if let Some(lang) = &client_config.lang {
63            base_params.insert("lang".to_string(), lang.to_string());
64        }
65
66        QWeatherClient {
67            api_host,
68            client,
69            base_params,
70            client_config,
71        }
72    }
73
74    /// 创建新的客户端
75    pub fn new(
76        public_id: impl ToString,
77        private_key: impl ToString,
78        subscription: bool,
79        lang: impl ToString,
80        unit: impl ToString,
81    ) -> Self {
82        let api_host = if subscription {
83            WEATHER_API_URL.to_string()
84        } else {
85            WEATHER_DEV_API_URL.to_string()
86        };
87
88        let client = ClientBuilder::new()
89            .gzip(true)
90            .build()
91            .expect("Failed to create reqwest client");
92
93        let mut base_params = BTreeMap::new();
94        base_params.insert("publicid".to_string(), public_id.to_string());
95
96        QWeatherClient {
97            api_host,
98            client,
99            base_params,
100            client_config: ClientConfig {
101                public_id: public_id.to_string(),
102                private_key: private_key.to_string(),
103                subscription,
104                lang: Some(lang.to_string()),
105                unit: Some(unit.to_string()),
106            },
107        }
108    }
109
110    /// 获取API Host
111    pub fn get_api_host(&self) -> &str {
112        &self.api_host
113    }
114
115    /// 请求API
116    pub async fn request_api<T>(
117        &self,
118        url: String,
119        mut params: BTreeMap<String, String>,
120    ) -> Result<APIResponse<T>, reqwest::Error>
121    where
122        T: serde::de::DeserializeOwned,
123    {
124        // 合并参数
125        params.extend(self.base_params.clone());
126        params.insert(
127            "t".to_string(),
128            chrono::Local::now().timestamp().to_string(),
129        );
130        let sign = self.sign_params(&params);
131        params.insert("sign".to_string(), sign);
132        match self.client.get(&url).query(&params).send().await {
133            Ok(response) => {
134                let body: Value = response.json().await?;
135                trace!("Response: {:?}", body);
136                match body["code"].as_str() {
137                    Some("200") | None => match serde_json::from_value::<T>(body) {
138                        Ok(response) => Ok(APIResponse::Success(response)),
139                        Err(e) => {
140                            log::error!("Failed to parse response: {}", e);
141                            Ok(APIResponse::Error("Failed to parse response".to_string()))
142                        }
143                    },
144                    // v1 error
145                    Some(code) => Ok(APIResponse::Error(code.to_string())),
146                }
147            }
148            Err(error) => Ok(APIResponse::Error(error.to_string())),
149        }
150    }
151
152    /// 签名参数
153    fn sign_params(&self, params: &BTreeMap<String, String>) -> String {
154        let mut sign = String::new();
155
156        for (key, value) in params {
157            if key.to_lowercase() == "sign" || key.to_lowercase() == "key" {
158                continue;
159            }
160            if value.is_empty() {
161                continue;
162            }
163            sign.push_str(&format!("{}={}&", key, value));
164        }
165        sign.pop();
166        sign.push_str(&self.client_config.private_key);
167        let mut hasher = Md5::new();
168        hasher.update(&sign);
169        let sign = format!("{:x}", hasher.finalize());
170
171        sign
172    }
173}