use std::pin::Pin; use actix_web::middleware::from_fn; use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, web}; use include_dir::{Dir, include_dir}; use log::{debug, error, info, warn}; use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod}; use reqwest::Client; use sqlx::postgres::PgPool; mod auth; mod db; mod models; use auth::{generate_token, jwt_middleware}; use db::{create_pool, insert_weather_data}; use models::{ErrorResponse, WeChatApiResponse, WeChatLoginRequest, WeatherData}; use crate::models::{Claims, TokenResponse}; // 编译时嵌入整个 static 目录 static STATIC_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/static"); static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates"); // 加载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) } #[post("/api/login")] async fn login( pool: web::Data, req: web::Json, http_client: web::Data, ) -> impl Responder { // 从环境变量获取微信小程序配置 let appid = match std::env::var("WECHAT_APPID") { Ok(id) => { debug!("成功加载微信小程序appid"); id } Err(_) => { error!("环境变量中未配置WECHAT_APPID"); return HttpResponse::InternalServerError().json(ErrorResponse { error: "服务器配置错误:缺少微信小程序appid".to_string(), errcode: None, errmsg: None, }); } }; let secret = match std::env::var("WECHAT_SECRET") { Ok(secret) => { debug!("成功加载微信小程序secret"); secret } Err(_) => { error!("环境变量中未配置WECHAT_SECRET"); return HttpResponse::InternalServerError().json(ErrorResponse { error: "服务器配置错误:缺少微信小程序secret".to_string(), errcode: None, errmsg: None, }); } }; // 构建微信API请求URL let url = format!( "https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code", appid, secret, req.code ); debug!("准备调用微信API: {}", url.replace(&secret, "***")); // 日志中隐藏secret // 调用微信API let wechat_response = match http_client.get(&url).send().await { Ok(response) => { debug!("微信API请求成功,状态码: {}", response.status()); response } Err(e) => { error!("微信API请求失败: {}", e); return HttpResponse::InternalServerError().json(ErrorResponse { error: "请求微信服务失败".to_string(), errcode: None, errmsg: None, }); } }; // 解析微信API响应 let wechat_data: WeChatApiResponse = match wechat_response.json().await { Ok(data) => { debug!("微信API响应解析成功"); data } Err(e) => { error!("微信API响应解析失败: {}", e); return HttpResponse::InternalServerError().json(ErrorResponse { error: "解析微信响应失败".to_string(), errcode: None, errmsg: None, }); } }; // 检查微信API响应中的错误 if let Some(errcode) = wechat_data.errcode { warn!( "微信登录验证失败 - 错误码: {}, 错误信息: {:?}", errcode, wechat_data.errmsg ); return HttpResponse::BadRequest().json(ErrorResponse { error: "微信登录失败".to_string(), errcode: Some(errcode), errmsg: wechat_data.errmsg, }); } // 获取openid let openid = match wechat_data.openid { Some(id) => { // 日志中只显示openid的前4位和后4位,保护用户隐私 let masked_openid = format!("{}***{}", &id[0..4], &id[id.len() - 4..]); debug!("获取到用户openid: {}", masked_openid); id } None => { error!("微信API响应中未包含openid"); return HttpResponse::InternalServerError().json(ErrorResponse { error: "微信API未返回openid".to_string(), errcode: None, errmsg: None, }); } }; // 检查用户是否已存在,不存在则创建 let query = r#" INSERT INTO users (openid, name, type) VALUES ($1, left($1, 8), 2) ON CONFLICT (openid) DO NOTHING RETURNING id "#; let user_id = match sqlx::query_as::<_, (i32,)>(query) .bind(&openid) .fetch_optional(pool.get_ref()) .await { Ok(Some((id,))) => { info!("用户已存在,用户ID: {}", id); id } Ok(None) => { // 新用户创建成功,查询ID match sqlx::query_as::<_, (i32,)>("SELECT id FROM users WHERE openid = $1") .bind(&openid) .fetch_one(pool.get_ref()) .await { Ok((id,)) => { info!("新用户创建成功,用户ID: {}", id); id } Err(e) => { error!("新用户创建后查询ID失败: {}", e); return HttpResponse::InternalServerError().json(ErrorResponse { error: "创建用户后查询ID失败".to_string(), errcode: Some(500), errmsg: Some(e.to_string()), }); } } } Err(e) => { error!("用户查询/创建失败: {}", e); return HttpResponse::InternalServerError().json(ErrorResponse { error: "用户信息处理失败".to_string(), errcode: Some(500), errmsg: Some(e.to_string()), }); } }; // 生成JWT token let jwt_secret = match std::env::var("JWT_SECRET") { Ok(secret) => secret, Err(_) => { error!("环境变量中未配置JWT_SECRET"); return HttpResponse::InternalServerError().json(ErrorResponse { error: "服务器配置错误:缺少JWT_SECRET".to_string(), errcode: Some(500), errmsg: None, }); } }; // 生成token let token = match generate_token(user_id, &openid, 2, &jwt_secret) { Ok(t) => { // 日志中只显示token的前8位,保护安全 debug!("JWT生成成功: {}", &t[0..8]); t } Err(e) => { error!("JWT生成失败: {}", e); return HttpResponse::InternalServerError().json(ErrorResponse { error: "生成身份令牌失败".to_string(), errcode: Some(500), errmsg: Some(e.to_string()), }); } }; info!("用户登录成功,用户ID: {}", user_id); let token_response = TokenResponse::new(token); HttpResponse::Ok().json(token_response) } #[post("/api/post-weather-data")] async fn post_weather_data( data: web::Json, pool: web::Data, ) -> impl Responder { info!("Received weather data, preparing to insert into DB..."); // 调用 db.rs 中的函数来处理数据库逻辑 match insert_weather_data(pool.get_ref(), &data).await { Ok(inserted_id) => { info!( "Successfully inserted weather data with id: {}", inserted_id ); HttpResponse::Ok().json(serde_json::json!({ "success": true, "message": "Weather data inserted successfully", "inserted_id": inserted_id, "received_assignment": data.assignment_number })) } Err(error_msg) => { error!("Database operation failed: {}", error_msg); // 根据错误信息返回统一格式的错误响应 let mut errcode = 500; if error_msg.starts_with("未找到openid") { errcode = 404; } HttpResponse::Ok().json(serde_json::json!({ "success": false, "errcode": errcode, "errmsg": error_msg })) } } } // main.rs #[post("/api/generate-temp-token/{resource_id}")] async fn generate_temp_token_handler( path: web::Path, pool: web::Data, claims: web::ReqData, // 依赖原有JWT验证,确保是合法用户 ) -> impl Responder { let resource_id = path.into_inner(); let openid: &String = &claims.openid; // 校验用户是否有权限访问该资源(防止生成其他用户的资源token) match db::get_weather_details(pool.get_ref(), resource_id).await { Ok(weather_data) => { if weather_data.openid != *openid { return HttpResponse::Forbidden().json(ErrorResponse { error: "无权限为该资源生成临时token".to_string(), errcode: Some(403), errmsg: None, }); } } Err(e) => { return HttpResponse::BadRequest().json(ErrorResponse { error: format!("资源不存在: {}", e), errcode: Some(404), errmsg: None, }); } } // 获取JWT密钥 let jwt_secret = match std::env::var("JWT_SECRET") { Ok(secret) => secret, Err(_) => { return HttpResponse::InternalServerError().json(ErrorResponse { error: "服务器配置错误:缺少JWT_SECRET".to_string(), errcode: Some(500), errmsg: None, }); } }; // 生成临时token(有效期10分钟) let temp_token = match auth::generate_temp_token(openid, resource_id, &jwt_secret, 10) { Ok(token) => token, Err(e) => { return HttpResponse::InternalServerError().json(ErrorResponse { error: format!("生成临时token失败: {}", e), errcode: Some(500), errmsg: None, }); } }; HttpResponse::Ok().json(serde_json::json!({ "temp_token": temp_token, "expire_minutes": 10, })) } // 获取天气数据详情 #[get("/weather/details")] async fn get_weather_details( pool: web::Data, claims: Option>, query: web::Query, ) -> impl Responder { let jwt_secret = match std::env::var("JWT_SECRET") { Ok(secret) => secret, Err(_) => { return HttpResponse::InternalServerError().json(ErrorResponse { error: "服务器配置错误:缺少JWT_SECRET".to_string(), errcode: Some(500), errmsg: None, }); } }; // 标记是否使用临时token let mut is_temp_token = false; // 1. 优先检查临时token(URL参数) let (openid, weather_id) = if let Some(temp_token) = query.get("temp_token").and_then(|v| v.as_str()) { is_temp_token = true; // 标记为临时token访问 // 验证临时token let temp_claims = match auth::verify_temp_token(temp_token, &jwt_secret) { Ok(c) => c, Err(e) => { return HttpResponse::Unauthorized().json(ErrorResponse { error: format!("临时token无效: {}", e), errcode: Some(401), errmsg: None, }); } }; // 从临时token中获取资源ID (temp_claims.openid, temp_claims.resource_id) } // 2. 若没有临时token,使用原有JWT验证(需要查询参数id) else if let Some(claims) = claims { // 从查询参数获取id let weather_id = match query.get("id").and_then(|v| v.as_i64()) { Some(id) => id as i32, None => { return HttpResponse::BadRequest().json(ErrorResponse { error: "缺少资源ID参数(id)".to_string(), errcode: Some(400), errmsg: None, }); } }; (claims.openid.clone(), weather_id) } // 3. 两种验证方式都没有,返回未授权 else { return HttpResponse::Unauthorized().json(ErrorResponse { error: "缺少token(需提供JWT或临时token)".to_string(), errcode: Some(401), errmsg: None, }); }; // 获取天气数据 let weather_data = match db::get_weather_details(pool.get_ref(), weather_id).await { Ok(data) => { if data.openid != openid { return HttpResponse::Forbidden().json(ErrorResponse { error: "无权限访问该数据".to_string(), errcode: Some(403), errmsg: None, }); } data } Err(error_msg) => { error!("获取天气数据详情失败: {}", error_msg); let errcode = if error_msg.starts_with("未找到") { 404 } else { 500 }; return HttpResponse::Ok().json(serde_json::json!({ "success": false, "errcode": errcode, "errmsg": error_msg })); } }; // 根据访问方式返回不同格式 if is_temp_token { // 临时token访问:返回HTML页面,数据交由前端afterbody.js渲染 // 读取模板文件 let index_html = match TEMPLATES_DIR.get_file("index.html") { Some(file) => file.contents_utf8().unwrap_or_default(), None => { return HttpResponse::InternalServerError().json(ErrorResponse { error: "无法找到模板文件".to_string(), errcode: Some(500), errmsg: None, }); } }; // 将天气数据序列化为JSON字符串,供前端JS使用 let weather_data_json = serde_json::to_string(&weather_data).unwrap_or_default(); // 生成数据注入脚本:将数据挂载到window对象,供afterbody.js访问 let data_script = format!( r#" "#, weather_data_json ); // 将数据脚本插入到模板中,同时显示详细信息容器 let rendered_html = index_html .replace("", &data_script) .replace( "style=\"display: none;\"", "", // 显示详细信息容器,供前端渲染内容 ); HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(rendered_html) } else { // JWT访问:返回JSON数据 HttpResponse::Ok().json(serde_json::json!({ "success": true, "data": weather_data })) } } // 修改main.rs中的get_weather_brief函数 #[get("/weather")] async fn get_weather_brief( query: web::Query, pool: web::Data, claims: web::ReqData, ) -> impl Responder { // 同时处理数字和字符串类型的page参数 let page = match query.get("page") { Some(v) => { // 尝试直接解析为数字 if let Some(num) = v.as_i64() { num as i32 } // 尝试将字符串解析为数字 else if let Some(s) = v.as_str() { s.parse().unwrap_or(1) } // 解析失败用默认值 else { 1 } } None => 1, // 无参数时用默认值 }; // 同理优化limit参数解析 let limit = match query.get("limit") { Some(v) => { if let Some(num) = v.as_i64() { num as i32 } else if let Some(s) = v.as_str() { s.parse().unwrap_or(10) } else { 10 } } None => 10, }; debug!("获取天气数据列表, 页码: {}, 每页条数: {}", page, limit); match db::get_weather_list(pool.get_ref(), claims.user_id, page, limit).await { Ok(response) => { // 这里的response是WeatherListResponse类型 // 打印获取到的天气列表和总数 debug!( "获取到的天气列表: {:?}, 总条数: {}", response.list, response.total ); HttpResponse::Ok().json(serde_json::json!({ "success": true, "data": response.list, // 分页数据 "page": page, "limit": limit, "total": response.total // 总条数(关键修改) })) } Err(error_msg) => { error!("获取天气数据列表失败: {}", error_msg); HttpResponse::Ok().json(serde_json::json!({ "success": false, "errcode": 500, "errmsg": error_msg })) } } } // 删除天气数据 #[delete("/weather/delete/{id}")] async fn delete_weather( path: web::Path, pool: web::Data, claims: web::ReqData, ) -> impl Responder { let weather_id = path.into_inner(); info!("删除天气数据, ID: {}", weather_id); match db::delete_weather_data(pool.get_ref(), weather_id, claims.user_id).await { Ok(_) => HttpResponse::Ok().json(serde_json::json!({ "success": true, "message": format!("天气数据 {} 已成功删除", weather_id) })), Err(error_msg) => { error!("删除天气数据失败: {}", error_msg); let errcode = if error_msg.starts_with("未找到") { 404 } else { 500 }; HttpResponse::Ok().json(serde_json::json!({ "success": false, "errcode": errcode, "errmsg": error_msg })) } } } // 在 main.rs 中添加以下函数,用于处理静态文件请求 async fn serve_static_files(path: web::Path) -> impl Responder { // 获取请求的文件路径(例如 "css/style.css") let file_path = path.into_inner(); // 从嵌入的 STATIC_DIR 中查找文件 match STATIC_DIR.get_file(&file_path) { Some(file) => { // 根据文件扩展名设置 Content-Type let content_type = match file_path.split('.').last() { Some("css") => "text/css", Some("js") => "application/javascript", Some("html") => "text/html", Some("ttf") | Some("woff") | Some("woff2") => "font/woff2", // 字体文件 _ => "application/octet-stream", // 默认类型 }; HttpResponse::Ok() .content_type(content_type) .body(file.contents()) } None => { // 文件不存在时返回 404 HttpResponse::NotFound().body("静态文件不存在") } } } // 创建服务器配置的函数 fn create_server_config( pool: PgPool, http_client: Client, ) -> 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)) // 公开接口(无需验证) // 注册静态文件服务:处理 /static/* 路径的请求 .service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files))) .service(login) .service(get_weather_details) // 新增 // 需要验证的接口(使用 from_fn 包装中间件) .service( web::scope("") .wrap(from_fn(jwt_middleware)) // 关键修改:用 from_fn 包装 .service(post_weather_data) .service(get_weather_brief) // 新增 .service(generate_temp_token_handler) .service(delete_weather), ) } #[actix_web::main] async fn main() -> std::io::Result<()> { if let Err(e) = dotenvy::dotenv() { error!("警告:加载.env文件失败,使用系统环境变量: {}", e); } // 打印当前 RUST_LOG 的值 let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "未设置".to_string()); error!("当前 RUST_LOG 级别:{}", rust_log); env_logger::init(); info!("logger init successful"); // 初始化数据库连接池 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); } }; // 初始化HTTP客户端 let http_client = Client::new(); info!("Attempting to start server..."); // 尝试多个端口 let ports = vec![443, 8443, 8080, 3000, 8000, 8888]; let mut server: Option< Pin> + Unpin>>, > = None; let mut bound_port = 0; // 记录绑定的端口 // 使用&ports创建引用迭代器,而不是获取所有权 for port in &ports { let addr = format!("0.0.0.0:{}", port); info!("Trying to bind to {}", addr); // 为每个服务器创建克隆的连接池和HTTP客户端 let pool_clone = pool.clone(); let http_client_clone = http_client.clone(); // 根据端口选择是否使用SSL if *port == 443 || *port == 8443 { // 对于443和8443端口,使用HTTPS // 为每个端口创建新的SSL构建器 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()) }) .bind_openssl(&addr, ssl_builder) // 直接传递构建器 { Ok(s) => { info!("Successfully bound to {} with HTTPS", addr); // 将服务器运行Future转换为正确的类型 let server_future = s.run(); // 使用Box::pin将Pin>转换为Pin> + Unpin>> 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 { // 对于其他端口,使用HTTP match HttpServer::new(move || { create_server_config(pool_clone.clone(), http_client_clone.clone()) }) .bind(&addr) { Ok(s) => { info!("Successfully bound to {} with HTTP", addr); // 将服务器运行Future转换为正确的类型 let server_future = s.run(); // 使用Box::pin将Pin>转换为Pin> + Unpin>> 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) => { // 使用记录的端口判断是否为HTTPS 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); } } }