feat: 后端支持配置代理

This commit is contained in:
lanyeeee
2025-08-02 05:12:22 +08:00
parent 5fd8d7363b
commit b63be91f39
4 changed files with 77 additions and 10 deletions
+47 -4
View File
@@ -16,7 +16,8 @@ use tauri::{
use tokio::task::JoinSet;
use crate::{
extensions::AppHandleExt,
config::ProxyMode,
extensions::{AnyhowErrorToStringChain, AppHandleExt},
protobuf::DmSegMobileReply,
types::{
bangumi_info::BangumiInfo, bangumi_media_url::BangumiMediaUrl, cheese_info::CheeseInfo,
@@ -59,6 +60,15 @@ impl BiliClient {
}
}
pub fn reload_client(&self) {
let api_client = create_api_client(&self.app);
*self.api_client.write() = api_client;
let media_client = create_media_client(&self.app);
*self.media_client.write() = media_client;
let content_length_client = create_content_length_client(&self.app);
*self.content_length_client.write() = content_length_client;
}
pub async fn generate_qrcode(&self) -> anyhow::Result<QrcodeData> {
// 发送生成二维码请求
let request = self
@@ -800,7 +810,7 @@ impl BiliClient {
}
}
fn create_api_client(_app: &AppHandle) -> ClientWithMiddleware {
fn create_api_client(app: &AppHandle) -> ClientWithMiddleware {
let retry_policy = ExponentialBackoff::builder()
.base(1)
.jitter(Jitter::Bounded)
@@ -811,6 +821,7 @@ fn create_api_client(_app: &AppHandle) -> ClientWithMiddleware {
headers.insert("referer", HeaderValue::from_static(REFERRER));
let client = reqwest::ClientBuilder::new()
.set_proxy(app, "api_client")
.timeout(Duration::from_secs(3))
.default_headers(headers)
.build()
@@ -821,7 +832,7 @@ fn create_api_client(_app: &AppHandle) -> ClientWithMiddleware {
.build()
}
fn create_media_client(_app: &AppHandle) -> ClientWithMiddleware {
fn create_media_client(app: &AppHandle) -> ClientWithMiddleware {
let retry_policy = ExponentialBackoff::builder()
.base(1)
.jitter(Jitter::Bounded)
@@ -832,6 +843,7 @@ fn create_media_client(_app: &AppHandle) -> ClientWithMiddleware {
headers.insert("referer", HeaderValue::from_static(REFERRER));
let client = reqwest::ClientBuilder::new()
.set_proxy(app, "media_client")
.default_headers(headers)
.build()
.unwrap();
@@ -841,18 +853,49 @@ fn create_media_client(_app: &AppHandle) -> ClientWithMiddleware {
.build()
}
fn create_content_length_client(_app: &AppHandle) -> Client {
fn create_content_length_client(app: &AppHandle) -> Client {
let mut headers = HeaderMap::new();
headers.insert("user-agent", HeaderValue::from_static(USER_AGENT));
headers.insert("referer", HeaderValue::from_static(REFERRER));
reqwest::ClientBuilder::new()
.set_proxy(app, "content_length_client")
.timeout(Duration::from_secs(5))
.default_headers(headers)
.build()
.unwrap()
}
trait ClientBuilderExt {
fn set_proxy(self, app: &AppHandle, client_name: &str) -> Self;
}
impl ClientBuilderExt for reqwest::ClientBuilder {
fn set_proxy(self, app: &AppHandle, client_name: &str) -> reqwest::ClientBuilder {
let proxy_mode = app.get_config().read().proxy_mode;
match proxy_mode {
ProxyMode::NoProxy => self.no_proxy(),
ProxyMode::System => self,
ProxyMode::Custom => {
let config = app.get_config().inner().read();
let proxy_host = &config.proxy_host;
let proxy_port = &config.proxy_port;
let proxy_url = format!("http://{proxy_host}:{proxy_port}");
match reqwest::Proxy::all(&proxy_url).map_err(anyhow::Error::from) {
Ok(proxy) => self.proxy(proxy),
Err(err) => {
let err_title = format!("{client_name}将`{proxy_url}`设为代理失败,将直连");
let string_chain = err.to_string_chain();
tracing::error!(err_title, message = string_chain);
self.no_proxy()
}
}
}
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct BiliResp {
pub code: i64,
+14 -5
View File
@@ -52,13 +52,18 @@ pub fn get_config(config: tauri::State<RwLock<Config>>) -> Config {
#[specta::specta]
#[allow(clippy::needless_pass_by_value)]
pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> {
let bili_client = app.get_bili_client();
let config_state = app.get_config();
let proxy_changed = {
let config_state = config_state.read();
config_state.proxy_mode != config.proxy_mode
|| config_state.proxy_host != config.proxy_host
|| config_state.proxy_port != config.proxy_port
};
let enable_file_logger = config.enable_file_logger;
let enable_file_logger_changed = config_state
.read()
.enable_file_logger
.ne(&enable_file_logger);
let file_logger_changed = config_state.read().enable_file_logger != enable_file_logger;
{
// 包裹在大括号中,以便自动释放写锁
@@ -70,7 +75,11 @@ pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> {
tracing::debug!("保存配置成功");
}
if enable_file_logger_changed {
if proxy_changed {
bili_client.reload_client();
}
if file_logger_changed {
if enable_file_logger {
logger::reload_file_logger()
.map_err(|err| CommandError::from("重新加载文件日志失败", err))?;
+14
View File
@@ -30,6 +30,9 @@ pub struct Config {
pub dir_fmt: String,
pub dir_fmt_for_part: String,
pub time_fmt: String,
pub proxy_mode: ProxyMode,
pub proxy_host: String,
pub proxy_port: u16,
pub task_concurrency: usize,
pub task_download_interval_sec: u64,
pub chunk_concurrency: usize,
@@ -111,6 +114,9 @@ impl Config {
dir_fmt: "{collection_title}/{episode_title}".to_string(),
dir_fmt_for_part: DEFAULT_FMT_FOR_PART.to_string(),
time_fmt: "%Y-%m-%d_%H-%M-%S".to_string(),
proxy_mode: ProxyMode::NoProxy,
proxy_host: "127.0.0.1".to_string(),
proxy_port: 7890,
task_concurrency: 3,
task_download_interval_sec: 0,
chunk_concurrency: 16,
@@ -215,3 +221,11 @@ pub enum PreferAudioQuality {
#[serde(rename = "HiRes")]
AudioHiRes = 30251,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Type)]
pub enum ProxyMode {
#[default]
NoProxy,
System,
Custom,
}
+2 -1
View File
@@ -258,7 +258,7 @@ export type CntInfo = { collect: number; play: number; thumb_up: number; share:
export type CntInfoInMedia = { collect: number; play: number; danmaku: number; vt: number; play_switch: number; reply: number; view_text_1: string }
export type CodecType = "Unknown" | "Audio" | "AVC" | "HEVC" | "AV1"
export type CommandError = { err_title: string; err_message: string }
export type Config = { download_dir: string; enable_file_logger: boolean; sessdata: string; prefer_video_quality: PreferVideoQuality; prefer_codec_type: PreferCodecType; prefer_audio_quality: PreferAudioQuality; download_video: boolean; download_audio: boolean; auto_merge: boolean; download_xml_danmaku: boolean; download_ass_danmaku: boolean; download_json_danmaku: boolean; download_subtitle: boolean; download_cover: boolean; download_nfo: boolean; download_json: boolean; dir_fmt: string; dir_fmt_for_part: string; time_fmt: string; task_concurrency: number; task_download_interval_sec: number; chunk_concurrency: number; chunk_download_interval_sec: number; danmaku_config: CanvasConfig }
export type Config = { download_dir: string; enable_file_logger: boolean; sessdata: string; prefer_video_quality: PreferVideoQuality; prefer_codec_type: PreferCodecType; prefer_audio_quality: PreferAudioQuality; download_video: boolean; download_audio: boolean; auto_merge: boolean; download_xml_danmaku: boolean; download_ass_danmaku: boolean; download_json_danmaku: boolean; download_subtitle: boolean; download_cover: boolean; download_nfo: boolean; download_json: boolean; dir_fmt: string; dir_fmt_for_part: string; time_fmt: string; proxy_mode: ProxyMode; proxy_host: string; proxy_port: number; task_concurrency: number; task_download_interval_sec: number; chunk_concurrency: number; chunk_download_interval_sec: number; danmaku_config: CanvasConfig }
export type Consulting = { consulting_flag: boolean; consulting_url: string }
export type ContentList = { bold: boolean; content: string; number: string }
export type Cooperation = { link: string }
@@ -361,6 +361,7 @@ export type PreferAudioQuality = "Best" | "64K" | "132K" | "192K" | "Dolby" | "H
export type PreferCodecType = "Unknown" | "AVC" | "HEVC" | "AV1"
export type PreferVideoQuality = "Best" | "240P" | "360P" | "480P" | "720P" | "720P60" | "1080P" | "AiRepair" | "1080P+" | "1080P60" | "4K" | "HDR" | "Dolby" | "8K"
export type PreviewedPurchaseNote = { long_watch_text: string; pay_text: string; price_format: string; watch_text: string; watching_text: string }
export type ProxyMode = "NoProxy" | "System" | "Custom"
export type Publish = { is_finish: number; is_started: number; pub_time: string; pub_time_show: string; unknow_pub_date: number; weekday: number }
export type PurchaseFormatNote = { content_list: ContentList[]; link: string; title: string }
export type PurchaseNote = { content: string; link: string; title: string }