- 修复服务器

This commit is contained in:
Pine
2026-05-13 00:45:50 +08:00
parent 2bd68815fd
commit a5b96eed6e
5 changed files with 123 additions and 19 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ pub fn run() {
// 等待 HTTP 服务器就绪后再打开窗口
for i in 0..50 {
if let Ok(mut s) = std::net::TcpStream::connect_timeout(
&"127.0.0.1:10801".parse().unwrap(),
&"0.0.0.0:10801".parse().unwrap(),
Duration::from_millis(500),
) {
// 发送一个简单的 HTTP 请求测试
+84 -16
View File
@@ -2,8 +2,9 @@ use crate::events::EVENTS;
use crate::models::*;
use crate::storage::{get_media_type, is_allowed_extension, STORAGE};
use axum::{
body::Body,
extract::{Form, Multipart, State},
http::StatusCode,
http::{Response, StatusCode, Uri},
response::sse::{Event, KeepAlive, Sse},
response::Json,
routing::{get, post},
@@ -11,6 +12,7 @@ use axum::{
};
use chrono::Local;
use futures::stream::Stream;
use rust_embed::RustEmbed;
use std::convert::Infallible;
use std::path::PathBuf;
use std::sync::Arc;
@@ -20,6 +22,77 @@ use tokio_stream::StreamExt;
use tower_http::cors::CorsLayer;
use tower_http::services::ServeDir;
// ===================== 嵌入式前端文件 =====================
// dist/ 编译进二进制,无需文件系统依赖
#[derive(RustEmbed)]
#[folder = "../dist"]
struct Asset;
fn mime_type(path: &str) -> &'static str {
let ext = path.rsplit('.').next().unwrap_or("");
match ext {
"html" => "text/html; charset=utf-8",
"css" => "text/css; charset=utf-8",
"js" => "application/javascript; charset=utf-8",
"png" => "image/png",
"jpg" | "jpeg" => "image/jpeg",
"svg" => "image/svg+xml",
"ico" => "image/x-icon",
"json" => "application/json",
"woff2" => "font/woff2",
"woff" => "font/woff",
"ttf" => "font/ttf",
_ => "application/octet-stream",
}
}
async fn spa_fallback(uri: Uri) -> Response<Body> {
let path = uri.path().trim_start_matches('/');
// 1. 尝试从嵌入式文件提供
if !path.is_empty() && !path.starts_with("api/") {
if let Some(file) = Asset::get(path) {
return Response::builder()
.header("Content-Type", mime_type(path))
.body(Body::from(file.data.into_owned()))
.unwrap();
}
}
// 2. SPA 回退:所有非 API 路径都返回 index.html
if !path.starts_with("api/") {
if let Some(file) = Asset::get("index.html") {
return Response::builder()
.header("Content-Type", "text/html; charset=utf-8")
.body(Body::from(file.data.into_owned()))
.unwrap();
}
}
// 3. 文件系统回退(开发模式可用)
let dist = dist_dir();
let file_path = dist.join(path);
if let Ok(data) = fs::read(&file_path) {
return Response::builder()
.header("Content-Type", mime_type(path))
.body(Body::from(data))
.unwrap();
}
let index = dist.join("index.html");
if let Ok(data) = fs::read(&index) {
return Response::builder()
.header("Content-Type", "text/html; charset=utf-8")
.body(Body::from(data))
.unwrap();
}
Response::builder()
.status(StatusCode::NOT_FOUND)
.header("Content-Type", "text/plain; charset=utf-8")
.body(Body::from("Not Found"))
.unwrap()
}
// ===================== 媒体目录 =====================
fn media_dir() -> PathBuf {
let mut path = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
@@ -60,10 +133,15 @@ fn dist_dir() -> PathBuf {
// 2. 相对于可执行文件路径
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
// 二进制在 src-tauri/target/release/dpm → dist 在 dpm/dist/
// 开发模式: 二进制在 src-tauri/target/debug/dpm → dist 在项目根目录
c.push(dir.join("../../../dist"));
// macOS .app bundle: dpm.app/Contents/MacOS/dpm
c.push(dir.join("../../dist"));
// macOS .app bundle 资源目录: dpm.app/Contents/Resources/dist/
c.push(dir.join("../Resources/dist"));
// Windows/Linux: 资源在二进制同目录下的 dist/
c.push(dir.join("dist"));
// Windows NSIS 安装: 资源在 resources/dist/
c.push(dir.join("resources/dist"));
}
}
c
@@ -78,7 +156,7 @@ fn dist_dir() -> PathBuf {
}
}
// Fallback:直接尝试最常见的路径
// Fallback
let fallback = PathBuf::from("../dist");
eprintln!("[dpm] 警告: 找不到 dist 目录 (已尝试 {:?}),使用: {:?}", candidates, fallback);
fallback
@@ -86,10 +164,6 @@ fn dist_dir() -> PathBuf {
// ===================== 路由构建 =====================
pub fn create_router(state: AppState) -> Router {
let dist = dist_dir();
println!("[dpm] 静态文件目录: {:?}", dist);
let dist_str = dist.to_string_lossy().to_string();
Router::new()
.route("/api/login", post(login_handler))
.route("/api/settings", get(get_settings).post(update_settings))
@@ -105,14 +179,8 @@ pub fn create_router(state: AppState) -> Router {
.route("/api/events", get(sse_handler))
// 媒体文件:用户目录 ~/Downloads/Media/
.nest_service("/file", ServeDir::new(media_dir_str()))
// SPA 静待文件回退
.fallback_service(
ServeDir::new(&dist_str)
.append_index_html_on_directories(true)
.fallback(tower_http::services::ServeFile::new(
format!("{}/index.html", dist_str),
)),
)
// SPA 回退 → 嵌入式文件(跨平台,编译到二进制中)
.fallback_service(get(spa_fallback))
.layer(CorsLayer::permissive())
.with_state(state)
}