qiniu_cdn_manager/
lib.rs

1use std::fmt::Debug;
2
3use chrono::Local;
4use config::Config;
5
6pub mod analysis;
7pub mod config;
8pub mod domain;
9pub mod log;
10pub mod prefetch;
11pub mod refresh;
12pub mod traffic;
13pub mod utils;
14
15use anyhow::anyhow;
16use reqwest::header::{HeaderMap, HeaderValue};
17use serde::{de::DeserializeOwned, Deserialize, Serialize};
18use utils::token::{ManageTokenGenerator, SignMethod};
19
20// 找不到数据错误提示
21const NOT_FOUND_MSG: &str = "未查询到数据!";
22
23// 查询中提示
24pub const QUERYING: &str = "查询中,请稍候🔎...";
25
26// 功能分类
27#[derive(Debug, PartialEq, PartialOrd)]
28pub enum SubFunctionEnum {
29    // 计费流量
30    Traffic,
31    // 刷新缓存
32    Refresh,
33    // 日志
34    Log,
35    // 域名相关
36    Domain,
37    // 文件预取
38    Prefetch,
39    // TOp
40    AnalysisTop,
41    // 状态码
42    AnalysisStatus,
43    // ISP
44    AnalysisIsp,
45    // 命中率
46    AnalysisHitmiss,
47    // 请求次数
48    AnalysisCount,
49}
50
51impl SubFunctionEnum {
52    pub fn get_sign_method(&self) -> SignMethod {
53        if [Self::Log, Self::Refresh, Self::Prefetch].contains(self) {
54            return SignMethod::Method2;
55        }
56        SignMethod::Method1
57    }
58
59    pub fn get_host(&self) -> &str {
60        if *self == Self::Domain {
61            return "api.qiniu.com";
62        }
63        "fusion.qiniuapi.com"
64    }
65}
66
67#[derive(Debug, Clone)]
68pub struct Client {
69    config: Config,
70    host: String,
71    sign_method: SignMethod,
72}
73
74#[derive(Debug, Deserialize)]
75pub struct BaseResponse {
76    pub code: i32,
77    pub error: String,
78}
79
80impl Client {
81    pub fn new(config: &Config, sub_func: SubFunctionEnum) -> Self {
82        Self {
83            config: config.to_owned().clone(),
84            host: sub_func.get_host().to_owned(),
85            sign_method: sub_func.get_sign_method(),
86        }
87    }
88    /// do request
89    #[allow(clippy::too_many_arguments)]
90    async fn do_request<T: DeserializeOwned + Debug, D: ?Sized + Serialize + std::marker::Sync>(
91        &self,
92        method: &str,
93        url: &str,
94        headers: Option<&reqwest::header::HeaderMap>,
95        content_type: Option<&str>,
96        data: Option<&D>,
97    ) -> Result<T, anyhow::Error> {
98        let client = reqwest::Client::new();
99        let mut body = "".to_string();
100        let body_bytes = match data {
101            Some(d) => {
102                body = serde_json::to_string(d).unwrap();
103                Some(body.as_bytes())
104            }
105            None => None,
106        };
107        let mut header = HeaderMap::new();
108        if headers.is_some() {
109            header = headers.unwrap().clone();
110        }
111        // 指定content_type
112        if !header.contains_key("Content-Type") {
113            if let Some(content_type) = content_type {
114                header.insert("Content-Type", HeaderValue::from_str(content_type).unwrap());
115            } else {
116                header.insert(
117                    "Content-Type",
118                    HeaderValue::from_str("application/json").unwrap(),
119                );
120            }
121        }
122        let config = self.config.clone();
123        let token_generator =
124            ManageTokenGenerator::new(config.cdn.access_key.clone(), config.cdn.secret_key.clone());
125        let sign = match self.sign_method {
126            SignMethod::Method1 => token_generator.generate_v1(url, content_type, body_bytes)?,
127            SignMethod::Method2 => {
128                token_generator.generate_v2(method, url, Some(&header), content_type, body_bytes)?
129            }
130        };
131        let authorization = match self.sign_method {
132            SignMethod::Method1 => format!("QBox {}", sign),
133            SignMethod::Method2 => format!("Qiniu {}", sign),
134        };
135        header.insert(
136            "Authorization",
137            HeaderValue::from_str(&authorization).unwrap(),
138        );
139        let mut builder;
140        match method.to_uppercase().as_str() {
141            "GET" => builder = client.get(url),
142            "POST" => builder = client.post(url),
143            "PUT" => builder = client.put(url),
144            _ => return Err(anyhow!("不支持该方法: {:?}", method)),
145        }
146        let mut start = 0;
147        if config.debug.unwrap_or(false) {
148            println!(
149                "[DEBUG] qiniu request: {} {}\n{:#?}\nbody: {}",
150                method, url, header, body,
151            );
152            start = Local::now().timestamp_millis();
153        }
154        if !body.is_empty() {
155            builder = builder.body(body);
156        }
157        let response = builder.headers(header).send().await?;
158        let status = response.status();
159        if config.debug.unwrap_or(false) {
160            let end = Local::now().timestamp_millis();
161            let elapsed = end - start;
162            let text = response.text().await;
163            if text.is_ok() {
164                let text = text.unwrap();
165                println!("[DEBUG] qiniu response, {elapsed}ms elapsed: \n{}", text);
166                if !status.is_success() {
167                    if let Ok(res) = serde_json::from_str::<BaseResponse>(&text) {
168                        return Err(anyhow!(
169                            "[{}]七牛响应异常,code: {}, error: {}",
170                            status.as_u16(),
171                            res.code,
172                            res.error,
173                        ));
174                    }
175                    return Err(anyhow!("[{}]七牛响应异常", status.as_u16()));
176                }
177                let r: T = serde_json::from_str(&text).unwrap();
178                Ok(r)
179            } else {
180                Err(anyhow!("七牛响应为空"))
181            }
182        } else {
183            if !status.is_success() {
184                if let Ok(res) = response.json::<BaseResponse>().await {
185                    return Err(anyhow!(
186                        "[{}]七牛响应异常,code: {}, error: {}",
187                        status.as_u16(),
188                        res.code,
189                        res.error,
190                    ));
191                }
192                return Err(anyhow!("[{}]七牛响应异常", status.as_u16()));
193            }
194            Ok(response.json().await?)
195        }
196    }
197}