- 修复服务器
This commit is contained in:
+84
-16
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user