From 6d680260a2077507b1961eb11188de0d9957aa93 Mon Sep 17 00:00:00 2001 From: Milky0217 Date: Sat, 27 Sep 2025 16:21:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86weatherlistresponse?= =?UTF-8?q?=E5=B9=B6=E8=BF=9B=E8=A1=8C=E4=BA=86=E4=B8=80=E4=BA=9B=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db.rs | 130 +++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 124 +++++++++++++++++++++++++++++++++++++++++++++-- src/models.rs | 73 +++++++++++++++++++++++++--- 3 files changed, 316 insertions(+), 11 deletions(-) diff --git a/src/db.rs b/src/db.rs index 533ff7f..214bf0c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -3,7 +3,7 @@ use std::env; use std::error::Error; // 从 models 模块引入 WeatherData 结构体 -use crate::models::WeatherData; +use crate::models::{WeatherData, WeatherDataBrief, WeatherListResponse}; // 这个函数封装了所有与数据库交互的逻辑 pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData) -> Result { @@ -118,3 +118,131 @@ pub async fn create_pool() -> Result> { Ok(pool) } + +// 在db.rs中添加以下函数 + +// 获取天气数据详情 +pub async fn get_weather_details(pool: &PgPool, weather_id: i32) -> Result { + let query = r#" + SELECT + wd.id, wd.title, wd.date, wd.hour, wd.min, + wd.longitude::float8 as longitude, wd.latitude::float8 as latitude, + wd.daysincejanfirst, wd.theta, wd.solardeclination, + wd.sunaltitude, wd.overallcloudiness, wd.lowcloudiness, + wd.cloudindex, wd.solarradiationlevel, wd.hasmeasuredwindspeed, + wd.measuredwindspeed, wd.convertedwindspeed, wd.measurementheight, + wd.areatype, wd.pointwindspeed, wd.atmosphericstability, + wd.suitabilitydegree, wd.winddirection, wd.averagewinddirection, + wd.winddirectionstandarddeviation, wd.windspeed, wd.averagewindspeed, + wd.windspeedsuitability, wd.winddirectionsuitability, wd.overallsuitability, + wd.inspectiontype, wd.assignmentnumber, wd.calculatedwindspeed, + u.openid + FROM weather_data wd + JOIN users u ON wd.user_id = u.id + WHERE wd.id = $1 + "#; + + let row = match sqlx::query_as::<_, WeatherData>(query) + .bind(weather_id) + .fetch_optional(pool) + .await + { + Ok(Some(row)) => row, + Ok(None) => return Err(format!("未找到ID为 {} 的天气数据", weather_id)), + Err(e) => return Err(format!("查询天气数据失败: {}", e)), + }; + + Ok(row) +} + +// 替换db.rs中的get_weather_list函数 +pub async fn get_weather_list( + pool: &PgPool, + user_id: i32, + page: i32, + limit: i32, +) -> Result { + let offset = (page - 1) * limit; + + // 1. 查询符合条件的总条数 + let total_query = r#" + SELECT COUNT(*) as total + FROM weather_data + WHERE user_id = $1 + "#; + let total = match sqlx::query_as::<_, (i64,)>(total_query) + .bind(user_id) + .fetch_one(pool) + .await + { + Ok((count,)) => count, + Err(e) => return Err(format!("查询总条数失败: {}", e)), + }; + + // 2. 查询当前页数据列表 + let list_query = r#" + SELECT + wd.id, wd.title, wd.date, wd.hour, wd.min, wd.longitude, wd.latitude + FROM weather_data wd + WHERE wd.user_id = $1 + ORDER BY wd.date DESC, wd.hour DESC, wd.min DESC + LIMIT $2 OFFSET $3 + "#; + let list = match sqlx::query_as::<_, WeatherDataBrief>(list_query) + .bind(user_id) + .bind(limit) + .bind(offset) + .fetch_all(pool) + .await + { + Ok(data) => data, + Err(e) => return Err(format!("查询天气数据列表失败: {}", e)), + }; + + // 3. 包装结果并返回 + Ok(WeatherListResponse { list, total }) +} + +// 删除天气数据 +pub async fn delete_weather_data( + pool: &PgPool, + weather_id: i32, + user_id: i32, +) -> Result<(), String> { + // 先检查数据是否存在且属于当前用户 + let query_check = r#" + SELECT id FROM weather_data + WHERE id = $1 AND user_id = $2 + "#; + + let exists = match sqlx::query_as::<_, (i32,)>(query_check) + .bind(weather_id) + .bind(user_id) + .fetch_optional(pool) + .await + { + Ok(Some(_)) => true, + Ok(None) => return Err(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id)), + Err(e) => return Err(format!("检查天气数据失败: {}", e)), + }; + + if !exists { + return Err(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id)); + } + + // 执行删除操作 + let query_delete = r#" + DELETE FROM weather_data + WHERE id = $1 AND user_id = $2 + "#; + + match sqlx::query(query_delete) + .bind(weather_id) + .bind(user_id) + .execute(pool) + .await + { + Ok(_) => Ok(()), + Err(e) => Err(format!("删除天气数据失败: {}", e)), + } +} diff --git a/src/main.rs b/src/main.rs index ab62146..add46cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ use actix_web::middleware::from_fn; -use actix_web::{App, HttpResponse, HttpServer, Responder, Result, post, web}; +use actix_web::{App, HttpResponse, HttpServer, Responder, Result, delete, get, post, web}; use reqwest::Client; use sqlx::postgres::PgPool; - mod auth; mod db; mod models; @@ -11,6 +10,8 @@ use auth::{generate_token, jwt_middleware}; use db::{create_pool, insert_weather_data}; use models::{ErrorResponse, WeChatApiResponse, WeChatLoginRequest, WeatherData}; +use crate::models::Claims; + // 登录的API端点 #[post("/api/login")] async fn login( @@ -199,6 +200,120 @@ async fn post_weather_data( } } +// 在main.rs中添加以下处理函数 + +// 获取天气数据详情 +#[get("/weather/details/{id}")] +async fn get_weather_details( + path: web::Path, + pool: web::Data, + claims: web::ReqData, +) -> impl Responder { + let weather_id = path.into_inner(); + println!("获取天气数据详情, ID: {}", weather_id); + + match db::get_weather_details(pool.get_ref(), weather_id).await { + Ok(weather_data) => { + // 检查数据是否属于当前用户 + if weather_data.openid != claims.openid { + return HttpResponse::Forbidden().json(ErrorResponse { + error: "无权限访问该数据".to_string(), + errcode: Some(403), + errmsg: None, + }); + } + + HttpResponse::Ok().json(serde_json::json!({ + "success": true, + "data": weather_data + })) + } + Err(error_msg) => { + eprintln!("获取天气数据详情失败: {}", 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中的get_weather函数 +#[get("/weather")] +async fn get_weather( + query: web::Query, + pool: web::Data, + claims: web::ReqData, +) -> impl Responder { + let page = query.get("page").and_then(|v| v.as_i64()).unwrap_or(1) as i32; + let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(10) as i32; + + println!("获取天气数据列表, 页码: {}, 每页条数: {}", page, limit); + + match db::get_weather_list(pool.get_ref(), claims.user_id, page, limit).await { + Ok(response) => { + // 这里的response是WeatherListResponse类型 + // 打印获取到的天气列表和总数 + println!( + "获取到的天气列表: {:?}, 总条数: {}", + 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) => { + eprintln!("获取天气数据列表失败: {}", 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(); + println!("删除天气数据, 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) => { + eprintln!("删除天气数据失败: {}", 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 + })) + } + } +} + // 创建服务器配置的函数 fn create_server_config( pool: PgPool, @@ -221,7 +336,10 @@ fn create_server_config( .service( web::scope("") .wrap(from_fn(jwt_middleware)) // 关键修改:用 from_fn 包装 - .service(post_weather_data), + .service(post_weather_data) + .service(get_weather_details) // 新增 + .service(get_weather) // 新增 + .service(delete_weather), // 新增 ) } diff --git a/src/models.rs b/src/models.rs index f741df2..b400079 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,6 +1,8 @@ use chrono::NaiveDate; use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; use sqlx::FromRow; +use std::convert::TryFrom; // 定义JWT载荷结构体 #[derive(Debug, Serialize, Deserialize, Clone)] @@ -36,6 +38,61 @@ pub struct ErrorResponse { pub errmsg: Option, } +// 定义新类型,包装 Vec(当前 crate 内的类型) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FloatVec(pub Vec); + +// 为 Vec 实现从 JsonValue 的转换 +impl TryFrom for FloatVec { + type Error = String; + + fn try_from(value: JsonValue) -> Result { + // 检查 JSON 值是否为数组 + let arr = value + .as_array() + .ok_or_else(|| "JSON 类型错误:期望数组,但得到其他类型".to_string())?; + + // 遍历数组元素,转换为 f64 + let mut result = Vec::with_capacity(arr.len()); + for item in arr { + let num = item + .as_f64() + .ok_or_else(|| format!("JSON 元素类型错误:期望数字,但得到 {:?}", item))?; + result.push(num); + } + + Ok(FloatVec(result)) + } +} + +#[derive(Debug, Deserialize, Serialize, FromRow)] // 添加 FromRow 派生 +pub struct WeatherDataBrief { + #[sqlx(rename = "id")] // 数据库表的 id 列(自增主键),查询映射时使用 + pub id: i32, + + pub title: String, + + pub date: NaiveDate, + + #[serde(rename = "hours")] + #[sqlx(rename = "hour")] // 数据库中是 hour 字段 + pub hours: i32, + + #[serde(rename = "min")] + #[sqlx(rename = "min")] + pub min: i32, + + pub longitude: f64, + pub latidute: f64, +} + +// 在models.rs中添加(可放在WeatherDataBrief下方) +#[derive(Debug, Serialize, FromRow)] +pub struct WeatherListResponse { + pub list: Vec, // 分页数据列表 + pub total: i64, // 总数据条数 +} + // 天气数据结构体(修改后) #[derive(Debug, Deserialize, Serialize, FromRow)] // 添加 FromRow 派生 pub struct WeatherData { @@ -50,7 +107,7 @@ pub struct WeatherData { #[serde(rename = "assignmentnumber")] #[sqlx(rename = "assignmentnumber")] - pub assignment_number: String, + pub assignment_number: Option, #[serde(rename = "atmosphericStability")] #[sqlx(rename = "atmosphericstability")] @@ -66,7 +123,7 @@ pub struct WeatherData { #[serde(rename = "calculatedwindspeed")] #[sqlx(rename = "calculatedwindspeed")] - pub calculated_wind_speed: f64, + pub calculated_wind_speed: Option, #[serde(rename = "cloudIndex")] #[sqlx(rename = "cloudindex")] @@ -74,7 +131,7 @@ pub struct WeatherData { #[serde(rename = "convertedWindSpeed")] #[sqlx(rename = "convertedwindspeed")] - pub converted_wind_speed: f64, + pub converted_wind_speed: i32, pub date: NaiveDate, @@ -92,7 +149,7 @@ pub struct WeatherData { #[serde(rename = "inspectiontype")] #[sqlx(rename = "inspectiontype")] - pub inspection_type: String, + pub inspection_type: Option, pub latitude: f64, pub longitude: f64, @@ -133,7 +190,7 @@ pub struct WeatherData { #[serde(rename = "solarRadiationLevel")] #[sqlx(rename = "solarradiationlevel")] - pub solar_radiation_level: f64, + pub solar_radiation_level: i32, #[serde(rename = "suitabilityDegree")] #[sqlx(rename = "suitabilitydegree")] @@ -148,7 +205,8 @@ pub struct WeatherData { #[serde(rename = "windDirection")] #[sqlx(rename = "winddirection")] - pub wind_direction: Vec, + #[sqlx(try_from = "serde_json::Value")] + pub wind_direction: FloatVec, #[serde(rename = "windDirectionStandardDeviation")] #[sqlx(rename = "winddirectionstandarddeviation")] @@ -160,7 +218,8 @@ pub struct WeatherData { #[serde(rename = "windSpeed")] #[sqlx(rename = "windspeed")] - pub wind_speed: Vec, + #[sqlx(try_from = "serde_json::Value")] + pub wind_speed: FloatVec, #[serde(rename = "windSpeedSuitability")] #[sqlx(rename = "windspeedsuitability")]