use sqlx::{PgPool, postgres::PgPoolOptions}; use std::env; use std::error::Error; use chrono::Utc; // 从 models 模块引入 WeatherData 结构体 use crate::models::{User, WeatherData, WeatherDataBrief, WeatherListResponse}; // 用于插入weather_data的数据 pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result { // 配额检查:非付费用户数据条数限制 let user = get_user_by_id(pool, user_id).await?; let is_paid_active = user.is_paid && (user.paid_expires_at.is_none() || user.paid_expires_at.unwrap() > Utc::now()); if !is_paid_active { let current_count = count_user_weather_data(pool, user_id).await?; let limit: i64 = env::var("FREE_USER_DATA_LIMIT") .unwrap_or_else(|_| "20".to_string()) .parse() .unwrap_or(20); if current_count >= limit { return Err("数据条数已达上限,请升级为付费用户".to_string()); } } // 准备插入数据的 SQL 语句 let insert_query = r#" INSERT INTO weather_data ( user_id, title, date, hour, min, longitude, latitude, daysincejanfirst, theta, solardeclination, sunaltitude, overallcloudiness, lowcloudiness, cloudindex, solarradiationlevel, hasmeasuredwindspeed, measuredwindspeed, convertedwindspeed, pointheight, areatype, pointwindspeed, atmosphericstability, suitabilitydegree, winddirection, averagewinddirection, winddirectionstandarddeviation, windspeed, averagewindspeed, windspeedsuitability, winddirectionsuitability, overallsuitability, inspectiontype, assignmentnumber, calculatedwindspeed, hasspotcheckwindspeed, version, is_favorite ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37 ) RETURNING id "#; // 3. 执行插入操作 let inserted_id = match sqlx::query_as::<_, (i32,)>(insert_query) .bind(user_id) .bind(&weather_data.title) .bind(weather_data.date) .bind(weather_data.hours) .bind(weather_data.min) .bind(&weather_data.longitude) .bind(&weather_data.latitude) .bind(weather_data.day_since_jan_first) .bind(weather_data.theta) .bind(weather_data.solar_declination) .bind(weather_data.sun_altitude) .bind(&weather_data.overall_cloudiness) .bind(&weather_data.low_cloudiness) .bind(&weather_data.cloud_index) .bind(weather_data.solar_radiation_level) .bind(weather_data.has_measured_wind_speed) .bind(weather_data.measured_wind_speed) // <-- 修改: 直接绑定 Option .bind(weather_data.converted_wind_speed) // <-- 修改: 直接绑定 Option,并移除 `as i32` .bind(weather_data.point_height) // <-- 修改: 直接绑定 Option .bind(&weather_data.area_type) .bind(weather_data.point_wind_speed) // <-- 修改: 直接绑定 Option .bind(&weather_data.atmospheric_stability) .bind(&weather_data.suitability_degree) .bind( serde_json::to_value(&weather_data.wind_direction) .map_err(|e| format!("JSON 序列化失败: {}", e))?, ) .bind(weather_data.average_wind_direction) .bind(weather_data.wind_direction_standard_deviation) .bind( serde_json::to_value(&weather_data.wind_speed) .map_err(|e| format!("JSON 序列化失败: {}", e))?, ) .bind(weather_data.average_wind_speed) .bind(&weather_data.wind_speed_suitability) .bind(&weather_data.wind_direction_suitability) .bind(&weather_data.overall_suitability) .bind(&weather_data.inspection_type) .bind(&weather_data.assignment_number) .bind(weather_data.calculated_wind_speed) // 这个本来就是 Option,无需修改 .bind(weather_data.has_spot_check_wind_speed) .bind(&weather_data.version) .bind(weather_data.is_favorite) .fetch_one(pool) .await { Ok((id,)) => id, Err(e) => { return Err(format!("插入数据失败: {}", e)); } }; // 4. 成功,返回插入的 ID Ok(inserted_id) } pub async fn create_pool() -> Result> { // 获取数据库URL并提供友好错误信息 let database_url = env::var("DATABASE_URL").map_err(|_| "环境变量DATABASE_URL未设置,请在.env文件中配置")?; // 配置连接池参数 let pool = PgPoolOptions::new() .max_connections(20) // 根据应用需求调整最大连接数 .acquire_timeout(std::time::Duration::from_secs(30)) // 获取连接的超时时间 .connect(&database_url) .await .map_err(|e| format!("数据库连接失败: {}", e))?; Ok(pool) } // 获取天气数据详情 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, wd.latitude, wd.daysincejanfirst, wd.theta, wd.solardeclination, wd.sunaltitude, wd.overallcloudiness, wd.lowcloudiness, wd.cloudindex, wd.solarradiationlevel, wd.hasmeasuredwindspeed, wd.measuredwindspeed, wd.convertedwindspeed, wd.pointheight, 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, wd.hasspotcheckwindspeed, wd.is_favorite, u.openid, wd.version 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) } // 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, wd.is_favorite 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 = r#" DELETE FROM weather_data WHERE id = $1 AND user_id = $2 "#; let result = sqlx::query(query) .bind(weather_id) .bind(user_id) .execute(pool) .await .map_err(|e| format!("删除天气数据失败: {}", e))?; if result.rows_affected() == 0 { return Err(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id)); } Ok(()) } // 根据用户ID获取用户信息 pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result { let query = r#" SELECT id, name, openid, phone, type, "desc", is_paid, is_admin, paid_expires_at, avatar_url, nickname FROM users WHERE id = $1 "#; let row = match sqlx::query_as::<_, User>(query) .bind(user_id) .fetch_optional(pool) .await { Ok(Some(row)) => row, Ok(None) => return Err(format!("未找到ID为 {} 的用户", user_id)), Err(e) => return Err(format!("查询用户信息失败: {}", e)), }; Ok(row) } // 统计用户的天气数据条数 pub async fn count_user_weather_data(pool: &PgPool, user_id: i32) -> Result { let query = r#" SELECT COUNT(*) FROM weather_data WHERE user_id = $1 "#; let count = match sqlx::query_as::<_, (i64,)>(query) .bind(user_id) .fetch_one(pool) .await { Ok((count,)) => count, Err(e) => return Err(format!("查询天气数据条数失败: {}", e)), }; Ok(count) } // 更新用户付费状态 pub async fn update_user_payment_status( pool: &PgPool, user_id: i32, is_paid: bool, paid_expires_at: Option>, ) -> Result<(), String> { let query = r#" UPDATE users SET is_paid = $1, paid_expires_at = $2 WHERE id = $3 "#; match sqlx::query(query) .bind(is_paid) .bind(paid_expires_at) .bind(user_id) .execute(pool) .await { Ok(_) => Ok(()), Err(e) => Err(format!("更新用户付费状态失败: {}", e)), } } // 更新用户个人信息 pub async fn update_user_profile( pool: &PgPool, user_id: i32, nickname: &Option, avatar_url: &Option, ) -> Result<(), String> { let query = r#" UPDATE users SET nickname = COALESCE($1, nickname), avatar_url = COALESCE($2, avatar_url) WHERE id = $3 "#; match sqlx::query(query) .bind(nickname) .bind(avatar_url) .bind(user_id) .execute(pool) .await { Ok(_) => Ok(()), Err(e) => Err(format!("更新用户个人信息失败: {}", e)), } } // ===== 支付系统 DB 函数 ===== /// 创建待支付订单 pub async fn create_payment_order( pool: &PgPool, user_id: i32, order_no: &str, package_type: &str, amount: i32, expires_at: Option>, ) -> Result<(), String> { let query = r#" INSERT INTO payment_orders (user_id, order_no, package_type, amount, expires_at) VALUES ($1, $2, $3, $4, $5) "#; sqlx::query(query) .bind(user_id) .bind(order_no) .bind(package_type) .bind(amount) .bind(expires_at) .execute(pool) .await .map_err(|e| format!("创建订单失败: {}", e))?; Ok(()) } /// 确认订单支付(模拟):更新订单状态 + 激活用户付费 /// /// 返回该订单的 expires_at(永久会员为 None) pub async fn confirm_payment_order( pool: &PgPool, order_no: &str, user_id: i32, ) -> Result>, String> { let row = sqlx::query_as::<_, (i32, String, Option>)>( r#"SELECT user_id, status, expires_at FROM payment_orders WHERE order_no = $1"#, ) .bind(order_no) .fetch_optional(pool) .await .map_err(|e| format!("查询订单失败: {}", e))?; let (order_user_id, status, expires_at) = match row { Some(r) => r, None => return Err("订单不存在".to_string()), }; if order_user_id != user_id { return Err("无权操作此订单".to_string()); } if status != "pending" { return Err("订单状态异常,无法确认支付".to_string()); } sqlx::query( r#"UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1"#, ) .bind(order_no) .execute(pool) .await .map_err(|e| format!("更新订单状态失败: {}", e))?; sqlx::query( r#"UPDATE users SET is_paid = true, paid_expires_at = $1 WHERE id = $2"#, ) .bind(expires_at) .bind(user_id) .execute(pool) .await .map_err(|e| format!("更新用户付费状态失败: {}", e))?; Ok(expires_at) } /// 获取用户配额信息 /// /// 返回 (已用条数, 是否付费活跃, 到期时间) pub async fn get_user_quota( pool: &PgPool, user_id: i32, ) -> Result<(i64, bool, Option>), String> { let user = get_user_by_id(pool, user_id).await?; let is_paid_active = user.is_paid && (user.paid_expires_at.is_none() || user.paid_expires_at.unwrap() > chrono::Utc::now()); let used = count_user_weather_data(pool, user_id).await?; Ok((used, is_paid_active, user.paid_expires_at)) } // ===== 收藏功能 DB 函数 ===== /// 获取收藏列表(分页) pub async fn get_favorites_list( pool: &PgPool, user_id: i32, page: i32, limit: i32, ) -> Result { let offset = (page - 1) * limit; let total_query = r#" SELECT COUNT(*) as total FROM weather_data WHERE user_id = $1 AND is_favorite = true "#; let total = sqlx::query_as::<_, (i64,)>(total_query) .bind(user_id) .fetch_one(pool) .await .map_err(|e| format!("查询收藏总数失败: {}", e))? .0; let list_query = r#" SELECT id, title, date, hour, min, longitude, latitude, is_favorite FROM weather_data WHERE user_id = $1 AND is_favorite = true ORDER BY date DESC, hour DESC, min DESC LIMIT $2 OFFSET $3 "#; let list = sqlx::query_as::<_, WeatherDataBrief>(list_query) .bind(user_id) .bind(limit) .bind(offset) .fetch_all(pool) .await .map_err(|e| format!("查询收藏列表失败: {}", e))?; Ok(WeatherListResponse { list, total }) } /// 设置天气数据的收藏状态 pub async fn set_weather_favorite( pool: &PgPool, weather_id: i32, user_id: i32, is_favorite: bool, ) -> Result<(), String> { let query = r#" UPDATE weather_data SET is_favorite = $1 WHERE id = $2 AND user_id = $3 "#; let result = sqlx::query(query) .bind(is_favorite) .bind(weather_id) .bind(user_id) .execute(pool) .await .map_err(|e| format!("更新收藏状态失败: {}", e))?; if result.rows_affected() == 0 { return Err(format!("未找到ID为 {} 的天气数据或无权限修改", weather_id)); } Ok(()) }