use actix_web::middleware::from_fn; use actix_web::{App, HttpServer, web}; use tracing::{error, info}; use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod}; use reqwest::Client; use sqlx::postgres::PgPool; use std::pin::Pin; mod auth; mod config; mod db; mod error; mod handlers; mod models; use auth::jwt_middleware; use config::AppConfig; use db::create_pool; use handlers::{ admin_get_user, admin_update_user_payment, add_favorite, create_order, delete_weather, generate_temp_token_handler, get_current_user_profile, get_favorites, get_user_quota, get_weather_brief, get_weather_details, health_check, login, mock_confirm, post_weather_data, refresh_token, remove_favorite, root, save_user_profile, serve_static_files, }; use models::AppState; // 加载TLS证书和私钥 fn create_ssl_acceptor() -> Result> { let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; // 从环境变量获取证书和私钥路径 let key_path = std::env::var("SSL_KEY_PATH").unwrap_or_else(|_| "path/to/private.key".to_string()); let cert_path = std::env::var("SSL_CERT_PATH").unwrap_or_else(|_| "path/to/certificate.crt".to_string()); builder.set_private_key_file(&key_path, SslFiletype::PEM)?; builder.set_certificate_chain_file(&cert_path)?; // 返回构建器 Ok(builder) } fn create_server_config( pool: PgPool, http_client: Client, app_state: AppState, ) -> App< impl actix_web::dev::ServiceFactory< actix_web::dev::ServiceRequest, Config = (), Response = actix_web::dev::ServiceResponse, Error = actix_web::Error, InitError = (), >, > { App::new() .app_data(web::Data::new(pool)) .app_data(web::Data::new(http_client)) .app_data(web::Data::new(app_state)) // 根路径(无需认证) .service(root) // #[get("/")] - 返回服务信息 // 静态文件(无需认证) .service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files))) // API 接口 .service(login) // #[post("/api/login")] .service(refresh_token) // #[post("/api/refresh-token")](公开接口,无需认证) .service(get_weather_details) // #[get("/weather/details")](支持 JWT 或 temp_token,公开接口) .service(health_check) // #[get("/health")](公开接口,无需认证) // 受保护接口(JWT) .service( web::scope("") .wrap(from_fn(jwt_middleware)) .service(post_weather_data) // #[post("/api/post-weather-data")] .service(get_weather_brief) // #[get("/api/weather")] .service(generate_temp_token_handler) // #[post("/api/generate-temp-token/{resource_id}")] .service(delete_weather) // #[delete("/api/weather/delete/{id}")] .service(get_current_user_profile) // #[get("/api/user/profile")] .service(save_user_profile) // #[put("/api/user/profile")] .service(admin_get_user) // #[get("/api/admin/users/{id}")] .service(admin_update_user_payment) // #[put("/api/admin/users/{id}/payment")] .service(create_order) // #[post("/api/payment/create-order")] .service(mock_confirm) // #[post("/api/payment/mock-confirm")] .service(get_user_quota) // #[get("/api/user/quota")] .service(get_favorites) // #[get("/api/favorites")] .service(add_favorite) // #[post("/api/favorites/{id}")] .service(remove_favorite) // #[delete("/api/favorites/{id}")] ) // 健康检查 .service(health_check) } #[actix_web::main] async fn main() -> std::io::Result<()> { // 加载配置文件(支持多环境) let app_config = match AppConfig::load() { Ok(cfg) => cfg, Err(e) => { eprintln!("配置加载失败: {}", e); std::process::exit(1); } }; // 设置环境变量(向后兼容依赖 env var 的组件) unsafe { std::env::set_var("DATABASE_URL", &app_config.database_url); std::env::set_var("JWT_SECRET", &app_config.jwt_secret); std::env::set_var("WECHAT_APPID", &app_config.wechat_appid); std::env::set_var("WECHAT_SECRET", &app_config.wechat_secret); std::env::set_var("SSL_KEY_PATH", &app_config.ssl_key_path); std::env::set_var("SSL_CERT_PATH", &app_config.ssl_cert_path); std::env::set_var("RUST_LOG", &app_config.rust_log); std::env::set_var("FREE_USER_DATA_LIMIT", app_config.free_user_data_limit.to_string()); std::env::set_var("APP_VERSION", &app_config.app_version); } // 初始化文件日志(JSON 格式,带轮转) let log_dir = std::path::Path::new("./logs"); std::fs::create_dir_all(log_dir).ok(); // 确保日志目录存在 let file_appender = tracing_appender::rolling::Builder::new() .rotation(tracing_appender::rolling::Rotation::DAILY) .filename_prefix("rust-backend") .filename_suffix("log") .build(log_dir) .expect("无法创建日志文件"); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); // 保持文件 guard 存活(使用 Box 泄漏) std::mem::forget(_guard); tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::from_default_env() .add_directive(format!("rust_backend={}", app_config.rust_log).parse().unwrap()) .add_directive(format!("actix_web={}", app_config.rust_log).parse().unwrap()) .add_directive("sqlx=warn".parse().unwrap()) ) .with_target(true) .with_thread_ids(false) // 生产环境可开启 .with_file(true) .with_line_number(true) .with_writer(non_blocking) .with_ansi(false) // 文件中不使用 ANSI 颜色 .json() // JSON 格式便于 ELK 解析 .init(); info!("日志系统初始化成功,JSON格式,输出到 ./logs/"); let app_state = match AppState::load() { Ok(state) => state, Err(e) => { error!("环境变量配置错误: {}", e); std::process::exit(1); } }; let pool = match create_pool().await { Ok(pool) => pool, Err(e) => { error!("Failed to create database pool: {}", e); error!("Please check your database connection configuration in .env file"); std::process::exit(1); } }; let http_client = Client::new(); info!("Attempting to start server..."); let is_production = std::env::var("APP_ENV").unwrap_or_else(|_| "development".into()) == "production"; let ports = if is_production { vec![4433, 8443, 8080, 3000, 8000, 8888] } else { vec![8080, 3000, 8000, 8888, 4433, 8443] }; let mut server: Option< Pin> + Unpin>>, > = None; let mut bound_port = 0; for port in &ports { let addr = format!("127.0.0.1:{}", port); info!("Trying to bind to {}", addr); let pool_clone = pool.clone(); let http_client_clone = http_client.clone(); let app_state_clone = app_state.clone(); if *port == 443 || *port == 8443 || *port == 4433 { let ssl_builder = match create_ssl_acceptor() { Ok(builder) => builder, Err(e) => { error!("Failed to create SSL acceptor: {}", e); continue; } }; match HttpServer::new(move || { create_server_config(pool_clone.clone(), http_client_clone.clone(), app_state_clone.clone()) }) .bind_openssl(&addr, ssl_builder) { Ok(s) => { info!("Successfully bound to {} with HTTPS", addr); let server_future = s.run(); server = Some(Box::pin(server_future)); bound_port = *port; break; } Err(e) => { error!("Failed to bind to {} with HTTPS: {}", addr, e); if e.kind() == std::io::ErrorKind::PermissionDenied { error!(" -> Permission denied. Try running with sudo or use a port > 1024"); } continue; } } } else { match HttpServer::new(move || { create_server_config(pool_clone.clone(), http_client_clone.clone(), app_state_clone.clone()) }) .bind(&addr) { Ok(s) => { info!("Successfully bound to {} with HTTP", addr); let server_future = s.run(); server = Some(Box::pin(server_future)); bound_port = *port; break; } Err(e) => { error!("Failed to bind to {} with HTTP: {}", addr, e); if e.kind() == std::io::ErrorKind::PermissionDenied { error!( " -> Permission denied. Try running with sudo or use a port > 1024" ); } continue; } } } } match server { Some(s) => { if bound_port == 443 || bound_port == 8443 { info!("Server started successfully with HTTPS"); } else { info!("Server started successfully with HTTP"); } s.await } None => { error!("Failed to bind to any port. Please check your system configuration."); std::process::exit(1); } } }