Skip to main content

bpi_rs/
request.rs

1use crate::{ BpiError, response::BpiResponse };
2use reqwest::RequestBuilder;
3use serde::de::DeserializeOwned;
4use tokio::time::Instant;
5use tracing;
6
7pub trait BilibiliRequest {
8    fn with_bilibili_headers(self) -> Self;
9    fn with_user_agent(self) -> Self;
10
11    fn send_request(
12        self,
13        operation_name: &str
14    ) -> impl std::future::Future<Output = Result<bytes::Bytes, BpiError>> + Send;
15
16    fn send_bpi<T>(
17        self,
18        operation_name: &str
19    )
20        -> impl std::future::Future<Output = Result<BpiResponse<T>, BpiError>> + Send
21        where Self: Sized + Send, T: DeserializeOwned;
22
23    fn log_url(self, operation_name: &str) -> Self;
24}
25
26impl BilibiliRequest for RequestBuilder {
27    /// UserAgent + Referer + Origin
28    fn with_bilibili_headers(self) -> Self {
29        self.with_user_agent()
30            .header("Referer", "https://www.bilibili.com/")
31            .header("Origin", "https://www.bilibili.com")
32    }
33
34    fn with_user_agent(self) -> Self {
35        self.header(
36            "User-Agent",
37            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
38        )
39    }
40
41    async fn send_request(self, operation_name: &str) -> Result<bytes::Bytes, BpiError> {
42        // 发送请求
43        let response = self.send().await.map_err(|e| {
44            tracing::error!("{} 请求失败: {}", operation_name, e);
45            BpiError::from(e) // 使用 From trait 自动转换
46        })?;
47
48        // 检查响应状态
49        let status = response.status();
50        if !status.is_success() {
51            let err = BpiError::http(status.as_u16());
52            tracing::error!("{} HTTP错误: {}", operation_name, err);
53            return Err(err);
54        }
55
56        // 获取响应体
57        response.bytes().await.map_err(|e| {
58            tracing::error!("{} 获取响应体失败: {}", operation_name, e);
59            BpiError::network(format!("获取响应体失败: {}", e))
60        })
61    }
62
63    async fn send_bpi<T>(self, operation_name: &str) -> Result<BpiResponse<T>, BpiError>
64        where T: DeserializeOwned
65    {
66        // 开始计时
67        let start = Instant::now();
68        // 请求拿到响应 bytes
69        let bytes = self.log_url(operation_name).send_request(operation_name).await?;
70
71        // 解析JSON响应
72        let result: BpiResponse<T> = serde_json::from_slice(&bytes).map_err(|e| {
73            #[cfg(any(test, debug_assertions))]
74            {
75                let json_str = String::from_utf8_lossy(&bytes);
76                let error_pos = e.column().saturating_sub(1);
77                let start = error_pos.saturating_sub(25);
78                let end = (error_pos + 25).min(json_str.len());
79                let context = &json_str[start..end];
80
81                tracing::error!(
82                    "{} JSON解析失败 (行:{} 列:{}): {}",
83                    operation_name,
84                    e.line(),
85                    e.column(),
86                    e
87                );
88                tracing::error!(
89                    "错误位置: ...{}... ({}^)",
90                    context,
91                    " ".repeat(error_pos.saturating_sub(start))
92                );
93            }
94            #[cfg(not(any(test, debug_assertions)))]
95            {
96                tracing::error!("{} JSON解析失败: {}", operation_name, e);
97            }
98            BpiError::from(e)
99        })?;
100
101        // 处理API业务错误
102        if result.code != 0 {
103            let err = if result.message.is_empty() || result.message == "0" {
104                BpiError::from_code(result.code)
105            } else {
106                BpiError::from_code_message(result.code, result.message.clone())
107            };
108
109            tracing::error!("{} API错误: {}", operation_name, err);
110            return Err(err);
111        }
112
113        let duration = start.elapsed();
114        tracing::info!("{} 请求成功,耗时: {:.2?}", operation_name, duration);
115        Ok(result)
116    }
117
118    fn log_url(self, operation_name: &str) -> Self {
119        let url = self
120            .try_clone() // 注意:这里用不到也行,直接 build 也可以
121            .and_then(|rb| rb.build().ok())
122            .map(|req| req.url().to_string())
123            .unwrap_or_else(|| "未知URL".to_string());
124
125        tracing::info!("开始请求 {}: {}", operation_name, url);
126
127        self
128    }
129}