diff --git a/.github/workflows/pr-chatbot-review.yml b/.github/workflows/pr-chatbot-review.yml index 6e279dac..b41116d3 100644 --- a/.github/workflows/pr-chatbot-review.yml +++ b/.github/workflows/pr-chatbot-review.yml @@ -83,10 +83,8 @@ jobs: const OpenAI = require('openai'); const openai = new OpenAI({ - baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", - defaultHeaders: { - 'Authorization': `Bearer ${process.env.DASHSCOPE_API_KEY}` - } + apiKey: process.env.DASHSCOPE_API_KEY, + baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1" }); const diff = process.env.DIFF; @@ -118,31 +116,15 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `## PR 代码分析 + body: ` + PR 代码分析 ${analysis} - --- *这是由通义千问 AI 自动生成的 PR 分析,仅供参考。*` }); } catch (error) { - console.error('API 调用失败:', error); - core.setFailed(`PR 分析失败: ${error.message}`); - - // 在 PR 中添加错误信息 - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `## PR 代码分析失败 - - 很抱歉,在分析过程中遇到了错误:${error.message} - - 请检查 Action 日志以获取更多详细信息。 - - --- - *这是自动生成的错误通知。*` - }); + core.setFailed(`分析失败: ${error.message}`); } - name: Skip Analysis Comment diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index e77e8018..907b1bd6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1012,6 +1012,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" +[[package]] +name = "data-encoding" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" + [[package]] name = "data-url" version = "0.3.1" @@ -2238,6 +2244,7 @@ dependencies = [ "tauri-plugin-sql", "tauri-plugin-updater", "tauri-plugin-upload", + "tungstenite", ] [[package]] @@ -6471,6 +6478,24 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "native-tls", + "rand 0.9.0", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0c3e20b4..4d420653 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -47,6 +47,7 @@ tauri-plugin-updater = "2.6.1" tauri-plugin-sql = { version = "2", features = ["sqlite"] } tauri-plugin-single-instance = "2.2.2" tauri-plugin-notification = "2.2.2" +tungstenite = { version = "0.26.2", features = ["native-tls"] } [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] tauri-plugin-autostart = "2.2.0" lazy_static = "1.4" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 76212455..a901703b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,7 +17,8 @@ use desktops::tray; #[cfg(desktop)] use init::CustomInit; #[cfg(desktop)] -use proxy::test_proxy; +use proxy::test_api_proxy; +use proxy::test_ws_proxy; // 移动端依赖 #[cfg(mobile)] @@ -54,7 +55,8 @@ fn setup_desktop() { audio, set_height, set_badge_count, - test_proxy + test_api_proxy, + test_ws_proxy ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/proxy.rs b/src-tauri/src/proxy.rs index 87a08890..7cad4bb4 100644 --- a/src-tauri/src/proxy.rs +++ b/src-tauri/src/proxy.rs @@ -1,21 +1,49 @@ use reqwest::{Client, Proxy}; use std::time::Duration; +use tungstenite::client::connect_with_config; +use tungstenite::http::Uri; +use tungstenite::protocol::WebSocketConfig; // 定义测试用的URL列表 -const TEST_URLS: [&str; 3] = [ - "https://www.baidu.com", - "https://www.bing.com", - "https://www.qq.com", +const TEST_API_URLS: [&str; 1] = [ + "/system/config/init", ]; #[tauri::command] -pub async fn test_proxy(proxy_type: String, proxy_host: String, proxy_port: u16) -> Result { +pub async fn test_ws_proxy(proxy_type: String, proxy_host: String, proxy_port: u16, proxy_suffix: String) -> Result { + if proxy_type.is_empty() { + return Ok(true); // 不使用代理时直接返回成功 + } + + let proxy_url = match proxy_type.as_str() { + "ws" => format!("ws://{}:{}/{}", proxy_host, proxy_port, proxy_suffix), + "wss" => format!("wss://{}:{}/{}", proxy_host, proxy_port, proxy_suffix), + _ => return Err("不支持的代理类型".to_string()), + }; + let uri = Uri::from_maybe_shared(format!("{}?clientId=test", proxy_url)).unwrap(); + let config = if proxy_type == "wss" { Some(WebSocketConfig::default()) } else { None }; + let mut result = match connect_with_config(uri, config, 3) { + Ok(ws) => { + ws + } + Err(e) => { + println!("{:?}", e); + return Err("代理无法连接,请检查代理设置".to_string()) + } + }; + let _ = result.0.close(None).map_err(|_| {}); + Ok(true) +} + +#[tauri::command] +pub async fn test_api_proxy(proxy_type: String, proxy_host: String, proxy_port: u16, proxy_suffix: String) -> Result { if proxy_type.is_empty() { return Ok(true); // 不使用代理时直接返回成功 } let proxy_url = match proxy_type.as_str() { "http" => format!("http://{}:{}", proxy_host, proxy_port), + "https" => format!("https://{}:{}", proxy_host, proxy_port), "socks5" => format!("socks5://{}:{}", proxy_host, proxy_port), _ => return Err("不支持的代理类型".to_string()), }; @@ -28,8 +56,8 @@ pub async fn test_proxy(proxy_type: String, proxy_host: String, proxy_port: u16) .map_err(|e| format!("创建客户端失败: {}", e))?; // 依次测试多个URL,直到有一个成功 - for url in TEST_URLS.iter() { - match test_url(&client, url).await { + for url in TEST_API_URLS.iter() { + match test_url(&client, format!("{}/{}{}",proxy_url.as_str(), proxy_suffix.as_str(), url).as_str()).await { Ok(_) => return Ok(true), Err(e) => { println!("测试 {} 失败: {}", url, e); diff --git a/src/services/urls.ts b/src/services/urls.ts index 3212e087..75b3a2ee 100644 --- a/src/services/urls.ts +++ b/src/services/urls.ts @@ -1,6 +1,20 @@ import { URLEnum } from '@/enums' -const { VITE_SERVICE_URL } = import.meta.env +let { VITE_SERVICE_URL } = import.meta.env +const savedProxy = localStorage.getItem('proxySettings') +if (savedProxy) { + const settings = JSON.parse(savedProxy) + const suffix = settings.apiIp + ':' + settings.apiPort + '/' + settings.apiSuffix + switch (settings.apiType) { + case 'http': + case 'https': + case 'socket5': + VITE_SERVICE_URL = settings.apiType + '://' + suffix + break + default: + break + } +} const prefix = VITE_SERVICE_URL export default { diff --git a/src/services/webSocket.ts b/src/services/webSocket.ts index 96294b22..39853f65 100644 --- a/src/services/webSocket.ts +++ b/src/services/webSocket.ts @@ -83,9 +83,18 @@ class WS { localStorage.removeItem('user') } const clientId = await getEnhancedFingerprint() + const savedProxy = localStorage.getItem('proxySettings') + let serverUrl = import.meta.env.VITE_WEBSOCKET_URL + if (savedProxy) { + const settings = JSON.parse(savedProxy) + const suffix = settings.wsIp + ':' + settings.wsPort + '/' + settings.wsSuffix + if (settings.wsType === 'ws' || settings.wsType === 'wss') { + serverUrl = settings.wsType + '://' + suffix + } + } // 初始化 ws worker.postMessage( - `{"type":"initWS","value":{"token":${token ? `"${token}"` : null},"clientId":${clientId ? `"${clientId}"` : null}}}` + `{"type":"initWS","value":{"token":${token ? `"${token}"` : null},"clientId":${clientId ? `"${clientId}", "serverUrl":"${serverUrl}"` : null}}}` ) } diff --git a/src/views/loginWindow/Proxy.vue b/src/views/loginWindow/Proxy.vue index 176158d0..09ebf13c 100644 --- a/src/views/loginWindow/Proxy.vue +++ b/src/views/loginWindow/Proxy.vue @@ -3,30 +3,78 @@ - + 网络代理设置 - -

类型

- - - -

IP地址

- + + + + +

类型

+ -

端口号

- + + +

IP或域名

+ + +

端口

+ +

后缀

+ +
+
+
+
+ + + +

类型

+ -

网络代理测试

-
-
+ + +

IP或域名

+ + +

端口

+ +

后缀

+ +
+
+
+
+ + +

测试

保存

返回

@@ -38,7 +86,7 @@ import { lightTheme } from 'naive-ui' import router from '@/router' import { invoke } from '@tauri-apps/api/core' -const options = [ +const apiOptions = [ { label: '不使用代理', value: '' @@ -47,45 +95,104 @@ const options = [ label: 'HTTP代理', value: 'http' }, + { + label: 'HTTPS代理', + value: 'https' + }, { label: 'SOCKS5代理', value: 'socks5' } ] -const proxyType = ref(options[0].value) -const IP = ref('') -const port = ref('') +const wsOptions = [ + { + label: '不使用代理', + value: '' + }, + { + label: 'WS代理', + value: 'ws' + }, + { + label: 'WSS代理', + value: 'wss' + } +] + +const proxy = ref('api') +const savedProxy = reactive({ + apiType: '', + apiIp: '', + apiPort: '', + apiSuffix: '', + wsType: '', + wsIp: '', + wsPort: '', + wsSuffix: '' +}) // 从本地存储加载代理设置 onMounted(() => { - const savedProxy = localStorage.getItem('proxySettings') - if (savedProxy) { - const settings = JSON.parse(savedProxy) - proxyType.value = settings.type - IP.value = settings.ip - port.value = settings.port + let proxySettings = localStorage.getItem('proxySettings') + if (proxySettings) { + const parse = JSON.parse(proxySettings) + savedProxy.apiType = parse.apiType + savedProxy.apiIp = parse.apiIp + savedProxy.apiPort = parse.apiPort + savedProxy.apiSuffix = parse.apiSuffix + savedProxy.wsType = parse.wsType + savedProxy.wsIp = parse.wsIp + savedProxy.wsPort = parse.wsPort + savedProxy.wsSuffix = parse.wsSuffix } }) +const handleTab = async (tab: string) => { + if (tab === 'api') { + proxy.value = 'api' + } else { + proxy.value = 'ws' + } +} + // 保存代理设置 const handleSave = async () => { try { - if (proxyType.value && (!IP.value || !port.value)) { + if ( + (savedProxy.apiType && (!savedProxy.apiIp || !savedProxy.apiPort)) || + (savedProxy.wsType && (!savedProxy.wsIp || !savedProxy.wsPort)) + ) { window.$message.warning('请填写完整的代理信息') return } - - // 保存到本地存储 - const proxySettings = { - type: proxyType.value, - ip: IP.value, - port: port.value + let proxySettings + if (proxy.value === 'api') { + // 保存到本地存储 + proxySettings = { + ...savedProxy, + apiType: savedProxy.apiType, + apiIp: savedProxy.apiIp, + apiPort: savedProxy.apiPort, + apiSuffix: savedProxy.apiSuffix + } + } else { + // 保存到本地存储 + proxySettings = { + ...savedProxy, + wsType: savedProxy.wsType, + wsIp: savedProxy.wsIp, + wsPort: savedProxy.wsPort, + wsSuffix: savedProxy.wsSuffix + } } - localStorage.setItem('proxySettings', JSON.stringify(proxySettings)) + const settings = JSON.stringify(proxySettings) + localStorage.setItem('proxySettings', settings) window.$message.success('代理设置保存成功') - router.push('/login') + setTimeout(() => { + location.reload() + }, 1000) } catch (error) { window.$message.error('代理设置失败:' + error) } @@ -93,7 +200,10 @@ const handleSave = async () => { // 测试代理连接 const proxyTest = async () => { - if (proxyType.value && (!IP.value || !port.value)) { + if ( + (savedProxy.apiType && (!savedProxy.apiIp || !savedProxy.apiPort)) || + (savedProxy.wsType && (!savedProxy.wsIp || !savedProxy.wsPort)) + ) { window.$message.warning('请填写完整的代理信息') return } @@ -103,10 +213,11 @@ const proxyTest = async () => { duration: 0 }) - const result = await invoke('test_proxy', { - proxyType: proxyType.value, - proxyHost: IP.value, - proxyPort: Number(port.value) + const result = await invoke(proxy.value === 'api' ? 'test_api_proxy' : 'test_ws_proxy', { + proxyType: proxy.value === 'api' ? savedProxy.apiType : savedProxy.wsType, + proxyHost: proxy.value === 'api' ? savedProxy.apiIp : savedProxy.wsIp, + proxyPort: Number(proxy.value === 'api' ? savedProxy.apiPort : savedProxy.wsPort), + proxySuffix: proxy.value === 'api' ? savedProxy.apiSuffix : savedProxy.wsSuffix }) if (result) { @@ -125,6 +236,7 @@ const proxyTest = async () => { diff --git a/src/workers/webSocket.worker.ts b/src/workers/webSocket.worker.ts index cfb66ac4..2df638a6 100644 --- a/src/workers/webSocket.worker.ts +++ b/src/workers/webSocket.worker.ts @@ -19,6 +19,8 @@ let token: null | string = null let clientId: null | string = null +let serverUrl: null | string = null + // 往 ws 发消息 const connectionSend = (value: object) => { connection?.send(JSON.stringify(value)) @@ -129,9 +131,7 @@ const initConnection = () => { // 建立链接 // 本地配置到 .env 里面修改。生产配置在 .env.production 里面 if (!connection) { - connection = new WebSocket( - `${import.meta.env.VITE_WEBSOCKET_URL}?clientId=${clientId}${token ? `&token=${token}` : ''}` - ) + connection = new WebSocket(`${serverUrl}?clientId=${clientId}${token ? `&token=${token}` : ''}`) } // 收到消息 connection.addEventListener('message', onConnectMsg) @@ -158,6 +158,7 @@ self.onmessage = (e: MessageEvent) => { reconnectCount = 0 token = value['token'] clientId = value['clientId'] + serverUrl = value['serverUrl'] initConnection() break }