├── .editorconfig
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .vscode
└── extensions.json
├── LICENSE
├── README.md
├── index.html
├── package.json
├── preview
├── 1.png
└── 2.png
├── public
└── doay.png
├── release.md
├── rustfmt.toml
├── src-tauri
├── .gitignore
├── Cargo.toml
├── build.rs
├── capabilities
│ ├── default.json
│ └── desktop.json
├── icons
│ ├── 64x64.png
│ ├── icon.icns
│ ├── icon.ico
│ └── icon.png
├── info.plist
├── logger
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
├── src
│ ├── args.rs
│ ├── cleanup.rs
│ ├── config.rs
│ ├── dirs.rs
│ ├── fs.rs
│ ├── http.rs
│ ├── lib.rs
│ ├── log.rs
│ ├── main.rs
│ ├── network
│ │ ├── linux.rs
│ │ ├── macos.rs
│ │ ├── mod.rs
│ │ └── windows.rs
│ ├── ray.rs
│ ├── scan_ports.rs
│ ├── setting.rs
│ ├── setup.rs
│ ├── sys_info.rs
│ └── web.rs
└── tauri.conf.json
├── src
├── App.css
├── App.tsx
├── component
│ ├── AutoCompleteField.tsx
│ ├── CodeViewer.tsx
│ ├── PageHeader.tsx
│ ├── PasswordInput.tsx
│ ├── SelectField.tsx
│ ├── SpeedGauge.tsx
│ ├── useAlert.tsx
│ ├── useAlertDialog.tsx
│ ├── useAppBar.tsx
│ ├── useCard.tsx
│ ├── useChip.tsx
│ ├── useDialog.tsx
│ ├── useServerImport.tsx
│ └── useSnackbar.tsx
├── context
│ └── ThemeProvider.tsx
├── hook
│ ├── useDebounce.ts
│ ├── useFullHeight.ts
│ ├── useInitLogLevel.ts
│ ├── useNoBackspaceNav.ts
│ ├── useVisibility.ts
│ └── useWindowFocused.ts
├── main.tsx
├── util
│ ├── concurrency.ts
│ ├── config.ts
│ ├── crypto.ts
│ ├── dns.ts
│ ├── highlightLog.ts
│ ├── invoke.ts
│ ├── network.ts
│ ├── proxy.ts
│ ├── ray.ts
│ ├── rule.ts
│ ├── server.ts
│ ├── serverConf.ts
│ ├── serverOption.ts
│ ├── serverSpeed.ts
│ ├── subscription.ts
│ ├── tauri.ts
│ ├── util.ts
│ └── validate.ts
├── view
│ ├── Dns.tsx
│ ├── DnsModeEditor.tsx
│ ├── DnsTable.tsx
│ ├── Home.tsx
│ ├── HomeBase.tsx
│ ├── HomeRay.tsx
│ ├── HttpTest.tsx
│ ├── Log.tsx
│ ├── LogDetail.tsx
│ ├── Process.tsx
│ ├── Rule.tsx
│ ├── RuleAdvanced.tsx
│ ├── RuleModeEditor.tsx
│ ├── ScanPorts.tsx
│ ├── Server.tsx
│ ├── ServerCreate.tsx
│ ├── ServerExport.tsx
│ ├── ServerImport.tsx
│ ├── ServerUpdate.tsx
│ ├── Setting.tsx
│ ├── SettingBase.tsx
│ ├── SettingProxy.tsx
│ ├── SettingRay.tsx
│ ├── SettingWeb.tsx
│ ├── SpeedTest.tsx
│ ├── Subscription.tsx
│ ├── SysInfo.tsx
│ ├── TerminalCmd.tsx
│ ├── Tool.tsx
│ └── server
│ │ ├── SsForm.tsx
│ │ ├── TrojanForm.tsx
│ │ ├── VlessForm.tsx
│ │ └── VmessForm.tsx
└── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | max_line_length = 180
5 |
6 | indent_style = space
7 | indent_size = 4
8 |
9 | charset = utf-8
10 | end_of_line = lf
11 |
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
15 | [*.json]
16 | indent_size = 2
17 |
18 | [*.md]
19 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | src-tauri/target
27 | test*
28 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "tauri-apps.tauri-vscode",
4 | "rust-lang.rust-analyzer"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Doay
2 |
3 | 基于 Rust + TypeScript 开发的跨平台 Xray GUI 客户端,采用现代化架构设计,现已支持 MacOS、Windows 和 Linux,未来计划移植至
4 | iOS 和 Android 平台
5 |
6 | ## 支持协议
7 |
8 | - VLESS / VMESS / Shadowsocks / Trojan / SOCKS / HTTP / HTTPS
9 |
10 | ## 界面预览
11 |
12 | | 设置 | 工具 |
13 | |--------------------|--------------------|
14 | |  |  |
15 |
16 | ## 名字由来
17 |
18 | - dog + day = Doay
19 | - Doay 是一只每天都爱笑的小狗,时间久了,眯眼眯到看不清世界,最后连毛都笑绿了
20 | 
21 |
22 | ## 🍎 macOS 安装温馨提示
23 |
24 | 由于 Doay 未进行开发者签名,macOS 在首次安装时可能会提示:
25 |
26 | > “无法验证开发者” 或 “应用已损坏”
27 |
28 | 这是 macOS 的安全机制导致的,**并不代表 Doay 有任何风险**。请按照以下任一方法解决该问题。
29 |
30 | ### ✅ 方法一:通过系统设置放行
31 |
32 | 1. 打开 **系统设置 > 隐私与安全**
33 | 2. 在底部找到 Doay 的提示
34 | 3. 点击 **“仍然打开”**
35 | 4. 再次尝试启动 Doay 应用
36 |
37 | ### ✅ 方法二:使用终端解除隐私限制
38 |
39 | 如果你熟悉终端,可执行以下命令来移除 macOS 的安全限制:
40 |
41 | ```
42 | xattr -r -d com.apple.quarantine /Applications/Doay.app
43 | ```
44 |
45 | 如果上面命令失败,或权限不足,请使用管理员权限执行:
46 |
47 | ```
48 | sudo xattr -rc /Applications/Doay.app
49 | ```
50 |
51 | ### 🔐 安全说明
52 |
53 | 这些命令仅用于**解除 macOS 自动加上的“隔离标记”(quarantine)**,不会改动应用内容或系统设置。Doay 完全离线,无后门,不含任何恶意行为,**请放心使用**。
54 |
55 | ## 设计宗旨
56 |
57 | - 极简设计:界面简洁直观,操作路径最短化
58 | - 零学习成本:新手用户无需教程即可上手使用
59 | - 轻量高效:核心功能突出,避免功能臃肿
60 | - 跨平台设计:考虑移植成本,简化程序架构设计
61 |
62 | ## 架构优势
63 |
64 | - 跨平台:支持 MacOS、Windows 和 Linux,未来计划移植至 iOS 和 Android 平台
65 | - 极致性能:基于 Rust 构建的高性能后端,资源占用低,执行效率媲美 C/C++
66 | - 内存优化:相比 Swift、Golang、Java、C# 等语言,Rust 内存占用更低,性能更优
67 | - 代码健壮:强类型语言 Rust + TypeScript,显著降低 Bug 发生率
68 | - 维护便捷:现代化架构设计,TypeScript 构建前端,依托 Web 生态,保证高效开发和便捷维护
69 | - 灵活扩展:低耦合架构设计,可轻松移植 Flutter、Electron 等技术栈,可快速扩展至 iOS、Android 等平台
70 |
71 | ## 特色功能
72 |
73 | - 启动极快:Doay 可在 1 秒内完成启动,从点击到界面显示几乎无延迟,提升使用体验
74 | - 安装包小:安装包仅 20 MB 左右,节省下载时间
75 | - 易用规则:访问(路由)规则配置简单灵活,小白用户易上手,高阶用户也能自由配置,可简可繁
76 | - DNS 设置:DNS 规则直观强大,普通用户可轻松使用,进阶用户可按需细调
77 | - 多种导入:支持手动添加、剪切板导入、二维码识别(摄像头、截图、图片文件)
78 | - 多种主题:内建浅色与深色主题,可自动适配系统外观,也支持手动切换,贴合用户使用习惯
79 | - 贴心订阅:不仅支持 JSON 接口链接,还支持 HTML 页面订阅,更方便获取免费机场节点
80 | - 日志查看:独创彩色日志查看器,支持 GB 级大日志的流畅浏览,智能高亮关键信息,大幅提升问题排查效率
81 | - 系统监控:实时监测 CPU、内存、磁盘、温度与网络资源,百分比展示,清晰掌握系统运行状况
82 | - 任务管理:查看并管理系统进程,支持结束任务和资源监控,更高效定位异常程序
83 | - 请求测试:便捷发送 HTTP 请求,支持查看请求头与 HTML 源码,方便连接验证代理服务器和接口调试
84 | - 网速测试:内置公网 IP 查看、Ping、抖动和上下行网速测试,真实反映代理服务器的网络带宽与连接质量
85 | - 端口扫描:高效扫描本机或远程主机的开放端口,支持设置扫描范围、线程数与超时时间,助力网络安全检测
86 | - 流量统计:实时监控网络流量,分类展示上传/下载情况,辅助识别异常网络行为
87 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Doay
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "doay",
3 | "private": true,
4 | "version": "1.0.9",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview",
11 | "tauri": "tauri"
12 | },
13 | "dependencies": {
14 | "@codemirror/lang-html": "^6.4.9",
15 | "@codemirror/lang-json": "^6.0.1",
16 | "@emotion/react": "^11.14.0",
17 | "@emotion/styled": "^11.14.0",
18 | "@mui/icons-material": "^7",
19 | "@mui/material": "^7",
20 | "@mui/x-charts": "^8.1.0",
21 | "@tauri-apps/api": "^2",
22 | "@tauri-apps/plugin-autostart": "~2",
23 | "@tauri-apps/plugin-clipboard-manager": "~2",
24 | "@tauri-apps/plugin-dialog": "~2",
25 | "@tauri-apps/plugin-opener": "^2",
26 | "@uiw/react-codemirror": "^4.23.12",
27 | "html5-qrcode": "^2.3.8",
28 | "qrcode.react": "^4.2.0",
29 | "react": "^19",
30 | "react-dom": "^19",
31 | "react-router-dom": "^7",
32 | "react-window": "^1.8.11"
33 | },
34 | "devDependencies": {
35 | "@eslint/js": "^9.23.0",
36 | "@tauri-apps/cli": "^2",
37 | "@types/react": "^19",
38 | "@types/react-dom": "^19",
39 | "@types/react-window": "^1.8.8",
40 | "@vitejs/plugin-react-swc": "^3",
41 | "eslint": "^9",
42 | "eslint-plugin-react-hooks": "^5.1.0",
43 | "eslint-plugin-react-refresh": "^0.4.19",
44 | "globals": "^16.0.0",
45 | "typescript": "~5.8.2",
46 | "vite": "^6.2.4"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/preview/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doayapp/doay/11691e55d669108eb762fdd33610d5d062112b53/preview/1.png
--------------------------------------------------------------------------------
/preview/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doayapp/doay/11691e55d669108eb762fdd33610d5d062112b53/preview/2.png
--------------------------------------------------------------------------------
/public/doay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doayapp/doay/11691e55d669108eb762fdd33610d5d062112b53/public/doay.png
--------------------------------------------------------------------------------
/release.md:
--------------------------------------------------------------------------------
1 | ## 界面预览
2 |
3 | | 设置 | 工具 |
4 | |--------------------|--------------------|
5 | |  |  |
6 |
7 | ## 🍎 macOS 安装温馨提示
8 |
9 | 由于 Doay 未进行开发者签名,macOS 在首次安装时可能会提示:
10 |
11 | > “无法验证开发者” 或 “应用已损坏”
12 |
13 | 这是 macOS 的安全机制导致的,**并不代表 Doay 有任何风险**。请按照以下任一方法解决该问题。
14 |
15 | ### ✅ 方法一:通过系统设置放行
16 |
17 | 1. 打开 **系统设置 > 隐私与安全**
18 | 2. 在底部找到 Doay 的提示
19 | 3. 点击 **“仍然打开”**
20 | 4. 再次尝试启动 Doay 应用
21 |
22 | ### ✅ 方法二:使用终端解除隐私限制
23 |
24 | 如果你熟悉终端,可执行以下命令来移除 macOS 的安全限制:
25 |
26 | ```
27 | xattr -r -d com.apple.quarantine /Applications/Doay.app
28 | ```
29 |
30 | 如果上面命令失败,或权限不足,请使用管理员权限执行:
31 |
32 | ```
33 | sudo xattr -rc /Applications/Doay.app
34 | ```
35 |
36 | ### 🔐 安全说明
37 |
38 | 这些命令仅用于**解除 macOS 自动加上的“隔离标记”(quarantine)**,不会改动应用内容或系统设置。Doay 完全离线,无后门,不含任何恶意行为,**请放心使用**。
39 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | newline_style = "Unix"
2 | use_small_heuristics = "Default"
3 | reorder_imports = true
4 | reorder_modules = true
5 | remove_nested_parens = true
6 | edition = "2021"
7 | merge_derives = true
8 | use_try_shorthand = false
9 | use_field_init_shorthand = false
10 | force_explicit_abi = true
11 |
12 | # normalize_comments = true
13 | # wrap_comments = true
14 |
15 | max_width = 160
16 | hard_tabs = false
17 | tab_spaces = 4
18 |
--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Generated by Tauri
6 | # will have schema files for capabilities auto-completion
7 | /gen/schemas
8 |
--------------------------------------------------------------------------------
/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "doay"
3 | version = "1.0.9"
4 | description = "A Doay App"
5 | authors = ["Doay"]
6 | edition = "2021"
7 |
8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9 |
10 | [lib]
11 | # The `_lib` suffix may seem redundant, but it is necessary
12 | # to make the lib name unique and wouldn't conflict with the bin name.
13 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
14 | name = "doay_lib"
15 | crate-type = ["staticlib", "cdylib", "rlib"]
16 |
17 | [build-dependencies]
18 | tauri-build = { version = "2", features = [] }
19 |
20 | [dependencies]
21 | logger = { path = "./logger" }
22 |
23 | log = "0.4"
24 | env_logger = "0.11"
25 |
26 | dirs = "6"
27 | chrono = "0.4"
28 | sysinfo = "0.35"
29 | once_cell = "1"
30 | regex = "1.11"
31 | reqwest = { version = "0.12", features = ["socks", "stream", "gzip", "brotli"] }
32 | futures-util = "0.3"
33 |
34 | #nix = { version = "0.29", features = ["fs"] }
35 | #rustc_version = "0.4"
36 | #tokio = { version = "1", features = ["full"] }
37 | tokio = { version = "1", features = ["net", "fs", "io-util", "sync", "time", "rt-multi-thread"] }
38 |
39 | actix-files = "0.6"
40 | actix-web = "4"
41 |
42 | serde = { version = "1", features = ["derive"] }
43 | serde_json = "1"
44 |
45 | tauri = { version = "2", features = ["tray-icon", "image-png"] }
46 | tauri-plugin-opener = "2"
47 | tauri-plugin-clipboard-manager = "2"
48 | tauri-plugin-dialog = "2"
49 |
50 | [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
51 | tauri-plugin-autostart = "2"
52 | tauri-plugin-single-instance = "2"
53 |
54 | [target.'cfg(windows)'.dependencies]
55 | winapi = { version = "0.3.9", features = ["wininet"] }
56 |
57 | [profile.release]
58 | #opt-level = "z" # 优化体积(0 1 2 3 "s" 或 "z")
59 | #lto = true # 启用 Link Time Optimization
60 | #codegen-units = 1 # 降低并发编译数量,提高优化质量
61 | #strip = true # 去除调试符号
62 |
--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | use std::process::Command;
2 |
3 | fn main() {
4 | let output = Command::new("rustc").arg("-V").output().expect("Failed to execute rustc");
5 | let version = String::from_utf8_lossy(&output.stdout);
6 | println!("cargo:rustc-env=RUSTC_VERSION={}", version.trim());
7 |
8 | tauri_build::build()
9 | }
10 |
--------------------------------------------------------------------------------
/src-tauri/capabilities/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../gen/schemas/desktop-schema.json",
3 | "identifier": "default",
4 | "description": "Capability for the main window",
5 | "windows": [
6 | "main"
7 | ],
8 | "permissions": [
9 | "autostart:default",
10 | "core:default",
11 | "core:window:default",
12 | "core:window:allow-hide",
13 | "core:window:allow-show",
14 | "core:window:allow-set-title",
15 | "core:window:allow-set-focus",
16 | "core:window:allow-set-theme",
17 | "core:window:allow-set-always-on-top",
18 | "core:webview:default",
19 | "clipboard-manager:default",
20 | "clipboard-manager:allow-write-text",
21 | "clipboard-manager:allow-read-text",
22 | "clipboard-manager:allow-write-image",
23 | "clipboard-manager:allow-read-image",
24 | "dialog:default",
25 | "opener:default"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src-tauri/capabilities/desktop.json:
--------------------------------------------------------------------------------
1 | {
2 | "identifier": "desktop-capability",
3 | "platforms": [
4 | "macOS",
5 | "windows",
6 | "linux"
7 | ],
8 | "windows": [
9 | "main"
10 | ],
11 | "permissions": []
12 | }
13 |
--------------------------------------------------------------------------------
/src-tauri/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doayapp/doay/11691e55d669108eb762fdd33610d5d062112b53/src-tauri/icons/64x64.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doayapp/doay/11691e55d669108eb762fdd33610d5d062112b53/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doayapp/doay/11691e55d669108eb762fdd33610d5d062112b53/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doayapp/doay/11691e55d669108eb762fdd33610d5d062112b53/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/src-tauri/info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSCameraUsageDescription
6 | 允许访问摄像头权限
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src-tauri/logger/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "logger"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | chrono = "0.4.40"
8 | once_cell = "1.21.0"
9 |
--------------------------------------------------------------------------------
/src-tauri/logger/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! 这是一个日志记录模块,提供了日志记录功能
2 | //! 支持不同级别的日志(如 None, Error, Warn, Info, Debug, Trace)
3 | //! 可以将日志输出到控制台和文件,并支持日志文件的自动轮转
4 |
5 | /**
6 | 用例:
7 | [dependencies]
8 | logger = { path = "./logger" }
9 |
10 | use logger::{debug, error, info, trace, warn};
11 |
12 | fn main() {
13 | logger::set_log_level("info");
14 | logger::set_log_filepath("logs/main.log").unwrap_or_else(|e| {
15 | eprintln!("设置日志文件路径失败: {}", e);
16 | });
17 | logger::set_log_max_size(2 * 1024 * 1024);
18 |
19 | println!("{:?}", logger::get_log_config());
20 | println!("{:?}", logger::get_log_writer());
21 |
22 | // 测试性能的循环
23 | for i in 0..3 {
24 | error!("这是一个日志,{}", i);
25 | warn!("这是一个日志,{}", i);
26 | info!("这是一个日志,{}", i);
27 | debug!("这是一个日志,{}", i);
28 | trace!("这是一个日志,{}", i);
29 | }
30 | }
31 | */
32 | use chrono::Local;
33 | use once_cell::sync::Lazy;
34 | use std::fs::{self, File};
35 | use std::io::{self, BufWriter, Write};
36 | use std::path::PathBuf;
37 | use std::sync::{Mutex, MutexGuard};
38 |
39 | // 日志级别
40 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41 | pub enum LogLevel {
42 | None,
43 | Error,
44 | Warn,
45 | Info,
46 | Debug,
47 | Trace,
48 | }
49 |
50 | // 日志配置
51 | #[derive(Debug)]
52 | pub struct LogConfig {
53 | pub log_level: LogLevel,
54 | pub log_filepath: Option,
55 | pub log_max_size: u64,
56 | }
57 |
58 | static LOG_CONFIG: Lazy> = Lazy::new(|| {
59 | Mutex::new(LogConfig {
60 | log_level: LogLevel::Trace,
61 | log_filepath: None,
62 | log_max_size: 5 * 1024 * 1024, // 设置默认值为 5MB
63 | })
64 | });
65 |
66 | static LOG_WRITER: Lazy>>> = Lazy::new(|| Mutex::new(None));
67 |
68 | // 获取全局配置
69 | pub fn get_log_config() -> MutexGuard<'static, LogConfig> {
70 | LOG_CONFIG.lock().unwrap()
71 | }
72 |
73 | // 初始化 BufWriter
74 | fn init_log_writer(filepath: &PathBuf) -> io::Result<()> {
75 | let mut writer = LOG_WRITER.lock().unwrap();
76 | if writer.is_none() {
77 | let file = File::options().append(true).create(true).open(filepath)?;
78 | *writer = Some(BufWriter::new(file));
79 | }
80 | Ok(())
81 | }
82 |
83 | // 设置 BufWriter
84 | fn set_log_writer(filepath: &PathBuf) -> io::Result<()> {
85 | let mut writer = LOG_WRITER.lock().unwrap();
86 | let file = File::options().append(true).create(true).open(filepath)?;
87 | *writer = Some(BufWriter::new(file));
88 | Ok(())
89 | }
90 |
91 | // 获取 BufWriter
92 | pub fn get_log_writer() -> MutexGuard<'static, Option>> {
93 | LOG_WRITER.lock().unwrap()
94 | }
95 |
96 | // 日志级别字符串
97 | pub fn level_str(level: LogLevel) -> &'static str {
98 | match level {
99 | LogLevel::None => "none",
100 | LogLevel::Error => "error",
101 | LogLevel::Warn => "warn",
102 | LogLevel::Info => "info",
103 | LogLevel::Debug => "debug",
104 | LogLevel::Trace => "trace",
105 | }
106 | }
107 |
108 | pub fn log(level: LogLevel, message: &str) -> Result<(), io::Error> {
109 | let config = get_log_config();
110 | // 如果当前日志级别低于设置的级别,则不记录
111 | if level > config.log_level || config.log_level == LogLevel::None {
112 | // println!("{:?} 大于 {:?}", level, config.log_level);
113 | return Ok(());
114 | }
115 |
116 | let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
117 | let log_message = format!("{} [{}] {}\n", timestamp, level_str(level), message);
118 |
119 | // 输出到控制台
120 | match level {
121 | LogLevel::None => (),
122 | LogLevel::Error => println!("\x1b[31m{}\x1b[0m", log_message.trim_end()),
123 | LogLevel::Warn => println!("\x1b[33m{}\x1b[0m", log_message.trim_end()),
124 | LogLevel::Info => println!("\x1b[32m{}\x1b[0m", log_message.trim_end()),
125 | LogLevel::Debug => println!("\x1b[34m{}\x1b[0m", log_message.trim_end()),
126 | LogLevel::Trace => println!("\x1b[35m{}\x1b[0m", log_message.trim_end()),
127 | }
128 |
129 | if let Some(log_filepath) = &config.log_filepath {
130 | init_log_writer(log_filepath)?; // 初始化 BufWriter
131 | if let Some(writer) = get_log_writer().as_mut() {
132 | writer.write_all(log_message.as_bytes())?; // 写入日志,性能比 writeln! 高
133 | writer.flush()?;
134 | }
135 |
136 | // 判断文件大小是否超过最大值
137 | let metadata = fs::metadata(log_filepath)?;
138 | if metadata.len() > config.log_max_size {
139 | let file_stem = log_filepath.file_stem().and_then(|stem| stem.to_str()).unwrap_or("log");
140 | let extension = log_filepath.extension().and_then(|ext| ext.to_str()).unwrap_or("log");
141 | let timestamp = Local::now().format("%Y%m%d_%H%M%S_%3f");
142 | let bak_filepath = log_filepath.with_file_name(format!("{}.{}.{}", file_stem, timestamp, extension));
143 | fs::rename(log_filepath, bak_filepath)?;
144 | set_log_writer(log_filepath)?;
145 | }
146 | }
147 |
148 | Ok(())
149 | }
150 |
151 | // 设置日志级别
152 | pub fn set_log_level(level_str: &str) {
153 | let mut config = get_log_config();
154 | config.log_level = match level_str.to_lowercase().as_str() {
155 | "error" => LogLevel::Error,
156 | "warn" => LogLevel::Warn,
157 | "info" => LogLevel::Info,
158 | "debug" => LogLevel::Debug,
159 | "trace" => LogLevel::Trace,
160 | _ => LogLevel::None, // 匹配不上的情况设置为 None
161 | };
162 | }
163 |
164 | // 设置日志文件路径
165 | pub fn set_log_filepath(filepath: &str) -> io::Result<()> {
166 | let mut config = get_log_config();
167 | config.log_filepath = Some(PathBuf::from(filepath));
168 | if let Some(log_filepath) = config.log_filepath.as_ref() {
169 | // 如果有值,创建父目录
170 | if let Some(parent_dir) = log_filepath.parent() {
171 | if !parent_dir.exists() {
172 | fs::create_dir_all(parent_dir)?;
173 | }
174 | }
175 | }
176 | set_log_writer(&PathBuf::from(filepath))?;
177 | Ok(())
178 | }
179 |
180 | // 设置日志文件最大大小(单位:字节)
181 | pub fn set_log_max_size(size: u64) {
182 | let mut config = get_log_config();
183 | config.log_max_size = size
184 | }
185 |
186 | // 定义日志宏
187 | #[macro_export]
188 | macro_rules! error {
189 | ($($arg:tt)*) => {
190 | if let Err(e) = $crate::log($crate::LogLevel::Error, &format!($($arg)*)) {
191 | eprintln!("Failed to log: {}", e);
192 | }
193 | };
194 | }
195 |
196 | #[macro_export]
197 | macro_rules! warn {
198 | ($($arg:tt)*) => {
199 | if let Err(e) = $crate::log($crate::LogLevel::Warn, &format!($($arg)*)) {
200 | eprintln!("Failed to log: {}", e);
201 | }
202 | };
203 | }
204 |
205 | #[macro_export]
206 | macro_rules! info {
207 | ($($arg:tt)*) => {
208 | if let Err(e) = $crate::log($crate::LogLevel::Info, &format!($($arg)*)) {
209 | eprintln!("Failed to log: {}", e);
210 | }
211 | };
212 | }
213 |
214 | #[macro_export]
215 | macro_rules! debug {
216 | ($($arg:tt)*) => {
217 | if let Err(e) = $crate::log($crate::LogLevel::Debug, &format!($($arg)*)) {
218 | eprintln!("Failed to log: {}", e);
219 | }
220 | };
221 | }
222 |
223 | #[macro_export]
224 | macro_rules! trace {
225 | ($($arg:tt)*) => {
226 | if let Err(e) = $crate::log($crate::LogLevel::Trace, &format!($($arg)*)) {
227 | eprintln!("Failed to log: {}", e);
228 | }
229 | };
230 | }
231 |
--------------------------------------------------------------------------------
/src-tauri/src/args.rs:
--------------------------------------------------------------------------------
1 | use logger::trace;
2 | use once_cell::sync::Lazy;
3 | use std::env;
4 | use std::sync::Mutex;
5 |
6 | static QUIET_MODE: Lazy> = Lazy::new(|| Mutex::new(false));
7 |
8 | pub fn parse_args() {
9 | let args: Vec = env::args().collect();
10 | trace!("Arguments: {:?}", args);
11 |
12 | if args.len() == 3 && args[1] == "-s" && args[2] == "quiet" {
13 | let mut quiet_mode = QUIET_MODE.lock().unwrap();
14 | *quiet_mode = true;
15 | }
16 | }
17 |
18 | pub fn is_quiet_mode() -> bool {
19 | let quiet_mode = QUIET_MODE.lock().unwrap();
20 | *quiet_mode
21 | }
22 |
--------------------------------------------------------------------------------
/src-tauri/src/cleanup.rs:
--------------------------------------------------------------------------------
1 | use crate::network;
2 | use crate::ray;
3 | use logger::info;
4 | use tauri::{plugin::Plugin, AppHandle, RunEvent, Runtime};
5 |
6 | pub struct CleanupPlugin;
7 |
8 | impl Plugin for CleanupPlugin {
9 | fn name(&self) -> &'static str {
10 | "cleanup-plugin"
11 | }
12 |
13 | fn on_event(&mut self, _app: &AppHandle, event: &RunEvent) {
14 | match event {
15 | RunEvent::Exit => {
16 | exit_cleanly();
17 | }
18 | _ => {}
19 | }
20 | }
21 | }
22 |
23 | pub fn init() -> CleanupPlugin {
24 | CleanupPlugin {}
25 | }
26 |
27 | pub fn exit_cleanly() {
28 | network::disable_proxies();
29 | let _ = ray::stop() && ray::force_kill();
30 | info!("Cleanup completed");
31 | }
32 |
--------------------------------------------------------------------------------
/src-tauri/src/dirs.rs:
--------------------------------------------------------------------------------
1 | use dirs;
2 | use once_cell::sync::Lazy;
3 | use serde_json::{json, Value};
4 | use std::env;
5 |
6 | static APP_DATA_DIR: Lazy