11
This commit is contained in:
95
src-tauri/src/api_client.rs
Normal file
95
src-tauri/src/api_client.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
//! 经 Rust 发起的 `/api/v1` HTTP 请求(可选 AES 加解密)。
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::api_crypto::{self, ENCRYPTED_HEADER};
|
||||
use crate::app_config;
|
||||
|
||||
pub async fn request_json(
|
||||
method: &str,
|
||||
path: &str,
|
||||
body: Option<&str>,
|
||||
extra_headers: HashMap<String, String>,
|
||||
) -> Result<(u16, Value), String> {
|
||||
let path = if path.starts_with('/') {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("/{path}")
|
||||
};
|
||||
let base = app_config::api_base().trim_end_matches('/').to_string();
|
||||
let url = format!("{base}/api/v1{path}");
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(60))
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let method = reqwest::Method::from_bytes(method.to_uppercase().as_bytes())
|
||||
.map_err(|e| format!("无效 HTTP 方法: {e}"))?;
|
||||
|
||||
let aes_key = api_crypto::aes_key_from_env();
|
||||
let mut req = client.request(method, &url);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
HeaderName::from_static("content-type"),
|
||||
HeaderValue::from_static("application/json"),
|
||||
);
|
||||
for (k, v) in extra_headers {
|
||||
if let (Ok(name), Ok(val)) = (
|
||||
HeaderName::from_bytes(k.as_bytes()),
|
||||
HeaderValue::from_str(&v),
|
||||
) {
|
||||
headers.insert(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(key) = aes_key {
|
||||
headers.insert(
|
||||
HeaderName::from_static("x-aiclient-encrypted"),
|
||||
HeaderValue::from_static("1"),
|
||||
);
|
||||
if let Some(raw) = body.filter(|s| !s.is_empty()) {
|
||||
let wire = api_crypto::wrap_encrypted(raw.as_bytes(), &key)?;
|
||||
req = req.body(wire);
|
||||
}
|
||||
} else if let Some(raw) = body {
|
||||
req = req.body(raw.to_string());
|
||||
}
|
||||
|
||||
req = req.headers(headers);
|
||||
|
||||
let res = req.send().await.map_err(|e| {
|
||||
if e.is_connect() {
|
||||
"无法连接服务器,请确认后端已启动".to_string()
|
||||
} else {
|
||||
e.to_string()
|
||||
}
|
||||
})?;
|
||||
|
||||
let status = res.status().as_u16();
|
||||
let encrypted = res
|
||||
.headers()
|
||||
.get(ENCRYPTED_HEADER)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
== Some("1");
|
||||
|
||||
let text = res.text().await.map_err(|e| e.to_string())?;
|
||||
|
||||
let json_text = if encrypted {
|
||||
let key = aes_key.ok_or_else(|| "响应已加密但未配置 AICLIENT_API_AES_KEY".to_string())?;
|
||||
let plain = api_crypto::unwrap_encrypted(&text, &key)?;
|
||||
String::from_utf8(plain).map_err(|e| format!("响应 UTF-8 无效: {e}"))?
|
||||
} else {
|
||||
text
|
||||
};
|
||||
|
||||
let value: Value =
|
||||
serde_json::from_str(&json_text).unwrap_or(Value::String(json_text.clone()));
|
||||
|
||||
Ok((status, value))
|
||||
}
|
||||
Reference in New Issue
Block a user