Skip to content

Commit b509144

Browse files
committed
feat(webapi): 实现静态资源动态查找和日志系统增强
- 添加 fern 和 log 依赖用于日志记录 - 实现 find_static_directory 函数,支持从多个位置查找静态资源目录 - 修改 index_handler 和 static_handler,优先从物理目录提供静态文件 - 添加 setup_logger 函数,同时输出日志到终端和文件 - 使用 log crate 替换 println 和 eprintln 宏 - 在启动时输出当前工作目录和可执行文件路径 - 增强错误处理,记录数据库迁移失败等错误信息 - 添加路径遍历攻击防护,确保请求文件在静态目录内
1 parent 5fe0f39 commit b509144

3 files changed

Lines changed: 181 additions & 16 deletions

File tree

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

file_classification_webapi/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ lazy_static = "1.5"
1616
serde_json = "1.0"
1717
once_cell = "1.21"
1818
env_logger = "0.11"
19+
log = "0.4"
1920
utoipa = { workspace = true, features = ["actix_extras"] }
2021
utoipa-swagger-ui = { workspace = true, features = ["actix-web"] }
2122
actix-files = "0.6.8"
2223
rust-embed = "8.8.0"
2324
mime_guess = "2.0.5"
25+
fern = "0.6"
2426

2527
[[bin]]
2628
name = "file_classification_webapi"

file_classification_webapi/src/bin/file_classification_webapi.rs

Lines changed: 168 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ use file_classification_core::utils::database::establish_connection;
77
use file_classification_core::utils::database::run_pending_migrations;
88
use rust_embed::RustEmbed;
99
use std::path::Path;
10+
use std::path::PathBuf;
1011
use utils::database::establish_connection_pool;
12+
use log;
13+
use fern;
14+
use chrono;
1115

1216
// 嵌入静态资源
1317
#[derive(RustEmbed)]
@@ -32,33 +36,181 @@ fn handle_embedded_file(path: &str) -> HttpResponse {
3236
}
3337
}
3438

39+
/// 查找静态文件目录,支持从不同目录运行程序
40+
fn find_static_directory() -> std::io::Result<PathBuf> {
41+
// 首先尝试从可执行文件所在目录查找
42+
if let Ok(exe_path) = std::env::current_exe() {
43+
if let Some(exe_dir) = exe_path.parent() {
44+
let static_dir = exe_dir.join("static");
45+
if Path::new(&static_dir).exists() {
46+
log::info!("找到静态资源目录: {:?}", static_dir);
47+
return Ok(static_dir);
48+
}
49+
}
50+
}
51+
52+
// 然后尝试从当前工作目录查找
53+
if let Ok(current_dir) = std::env::current_dir() {
54+
let static_dir = current_dir.join("static");
55+
if Path::new(&static_dir).exists() {
56+
log::info!("找到静态资源目录: {:?}", static_dir);
57+
return Ok(static_dir);
58+
}
59+
60+
// 尝试从当前工作目录的子目录 file_classification_webapi 中查找
61+
let static_dir = current_dir.join("file_classification_webapi").join("static");
62+
if Path::new(&static_dir).exists() {
63+
log::info!("找到静态资源目录: {:?}", static_dir);
64+
return Ok(static_dir);
65+
}
66+
}
67+
68+
// 如果都没找到,则返回默认路径并让后续逻辑处理错误
69+
if let Ok(current_dir) = std::env::current_dir() {
70+
let static_dir = current_dir.join("static");
71+
log::warn!("静态文件目录不存在: {:?}", static_dir);
72+
Err(std::io::Error::new(std::io::ErrorKind::NotFound, format!("静态文件目录不存在: {:?}", static_dir)))
73+
} else {
74+
log::error!("无法确定静态文件目录位置");
75+
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "无法确定静态文件目录位置"))
76+
}
77+
}
78+
3579
async fn index_handler() -> HttpResponse {
80+
// 首先尝试从物理目录提供文件
81+
match find_static_directory() {
82+
Ok(static_dir) => {
83+
let index_path = static_dir.join("index.html");
84+
if index_path.exists() {
85+
log::info!("从物理目录提供 index.html 文件: {:?}", index_path);
86+
return HttpResponse::Ok()
87+
.content_type("text/html; charset=utf-8")
88+
.body(std::fs::read(index_path).unwrap_or_else(|_| Vec::new()));
89+
}
90+
}
91+
Err(_) => {
92+
// 物理目录不存在,回退到嵌入资源
93+
log::info!("物理目录中未找到 index.html,回退到嵌入资源");
94+
}
95+
}
96+
97+
// 回退到嵌入资源
98+
log::info!("从嵌入资源提供 index.html 文件");
3699
handle_embedded_file("index.html")
37100
}
38101

39102
async fn static_handler(path: web::Path<String>) -> HttpResponse {
40103
let path = path.into_inner();
104+
105+
// 首先尝试从物理目录提供文件
106+
match find_static_directory() {
107+
Ok(static_dir) => {
108+
let file_path = static_dir.join(&path);
109+
log::debug!("尝试从物理目录提供文件: {:?}, 请求路径: {}", file_path, path);
110+
111+
if file_path.exists() && file_path.is_file() {
112+
// 确保请求的文件在 static 目录内,防止路径遍历攻击
113+
if let Ok(abs_file_path) = file_path.canonicalize() {
114+
if abs_file_path.starts_with(&static_dir.canonicalize().unwrap_or(static_dir.clone())) {
115+
log::info!("从物理目录提供文件: {:?}", file_path);
116+
let content = std::fs::read(&file_path);
117+
match content {
118+
Ok(data) => {
119+
let mime = mime_guess::from_path(&path).first_or_octet_stream();
120+
return HttpResponse::Ok()
121+
.content_type(mime.as_ref())
122+
.body(data);
123+
}
124+
Err(e) => {
125+
log::error!("读取文件失败 {:?}: {}", file_path, e);
126+
}
127+
}
128+
} else {
129+
log::warn!("文件路径不在静态目录内: {:?}", file_path);
130+
}
131+
}
132+
} else {
133+
log::debug!("文件不存在或不是文件: {:?}", file_path);
134+
}
135+
}
136+
Err(e) => {
137+
// 物理目录不存在,回退到嵌入资源
138+
log::info!("查找物理目录失败: {},回退到嵌入资源,请求路径: {}", e, path);
139+
}
140+
}
141+
142+
// 回退到嵌入资源
143+
log::info!("从嵌入资源提供文件: {}", path);
41144
handle_embedded_file(&path)
42145
}
43146

147+
/// 初始化日志系统,同时输出到终端和文件
148+
fn setup_logger() -> Result<(), fern::InitError> {
149+
// 创建 logs 目录(如果不存在)
150+
std::fs::create_dir_all("logs")?;
151+
152+
// 获取当前日期时间作为日志文件名
153+
let local_time = chrono::Local::now();
154+
let date_str = local_time.format("%Y-%m-%d").to_string();
155+
let log_file_path = format!("logs/{}.log", date_str);
156+
157+
let log_level = std::env::var("RUST_LOG")
158+
.ok()
159+
.and_then(|s| s.parse().ok())
160+
.unwrap_or(log::LevelFilter::Info);
161+
162+
fern::Dispatch::new()
163+
.format(|out, message, record| {
164+
out.finish(format_args!(
165+
"[{}][{}][{}] {}",
166+
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
167+
record.level(),
168+
record.target(),
169+
message
170+
))
171+
})
172+
// 同时输出到终端和文件
173+
.chain(std::io::stdout())
174+
.chain(fern::log_file(log_file_path)?)
175+
.level(log_level)
176+
.apply()?;
177+
178+
Ok(())
179+
}
180+
44181
#[actix_web::main]
45182
async fn main() -> std::io::Result<()> {
46-
env_logger::init();
183+
// 初始化日志记录器
184+
setup_logger().expect("日志系统初始化失败");
185+
186+
// 输出日志等级信息
187+
let log_level = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string());
188+
log::info!("日志等级设置为: {}", log_level);
189+
190+
log::info!("正在启动文件分类 Web API...");
47191

48-
println!("正在启动文件分类 Web API...");
192+
// 运行待处理的数据库迁移
193+
let mut conn = establish_connection();
194+
if let Err(e) = run_pending_migrations(&mut conn) {
195+
log::error!("数据库迁移失败: {}", e);
196+
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
197+
}
49198

50-
// 运行待处理的数据库迁移
51-
let mut conn = establish_connection();
52-
if let Err(e) = run_pending_migrations(&mut conn) {
53-
eprintln!("数据库迁移失败: {}", e);
54-
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
55-
}
199+
let pool = establish_connection_pool();
56200

57-
let pool = establish_connection_pool();
201+
// 输出当前工作目录
202+
if let Ok(current_dir) = std::env::current_dir() {
203+
log::info!("当前工作目录: {:?}", current_dir);
204+
}
205+
206+
// 输出可执行文件路径
207+
if let Ok(exe_path) = std::env::current_exe() {
208+
log::info!("可执行文件路径: {:?}", exe_path);
209+
}
58210

59-
// 在HttpServer::new中添加新的路由
60-
HttpServer::new(move || {
61-
App::new()
211+
// 在HttpServer::new中添加新的路由
212+
HttpServer::new(move || {
213+
App::new()
62214
.app_data(web::Data::new(pool.clone()))
63215
.wrap(Logger::default())
64216
// API路由 - 放在静态文件服务之前以确保优先匹配
@@ -121,8 +273,8 @@ async fn main() -> std::io::Result<()> {
121273
// 静态文件服务 - 使用嵌入的资源
122274
.route("/", web::get().to(index_handler))
123275
.route("/{filename:.*}", web::get().to(static_handler))
124-
})
125-
.bind("127.0.0.1:8082")?
126-
.run()
127-
.await
276+
})
277+
.bind("127.0.0.1:8082")?
278+
.run()
279+
.await
128280
}

0 commit comments

Comments
 (0)