use chrono::NaiveDate; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use sqlx::FromRow; use std::convert::TryFrom; // 默认值函数 fn default_spot_check_count() -> i32 { 5 } // 定义JWT载荷结构体 #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { // 标准字段:过期时间(必须) pub exp: i64, // 时间戳(秒) // 标准字段:签发时间 pub iat: i64, // 自定义字段:用户ID(根据业务需求添加) pub user_id: i32, // 自定义字段:openid(可选) pub openid: String, // 自定义字段:user_type pub user_type: i32, } // 临时访问用 token #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TempTokenClaims { pub exp: i64, // 过期时间(时间戳) pub iat: i64, // 签发时间 pub openid: String, // 用户身份标识 pub resource_id: i32, // 允许访问的资源ID(如天气数据id) } // 登录成功后的令牌响应结构体(双 token) #[derive(Debug, Serialize, Clone)] pub struct LoginResponse { pub success: bool, pub token: String, pub refresh_token: String, } impl LoginResponse { pub fn new(token: String, refresh_token: String) -> Self { Self { success: true, token, refresh_token, } } } // Refresh Token 请求 #[derive(Debug, Deserialize)] pub struct RefreshTokenRequest { pub refresh_token: String, } // Token 刷新响应 #[derive(Debug, Serialize)] pub struct TokenRefreshResponse { pub success: bool, pub token: String, pub refresh_token: String, } #[derive(Debug, Deserialize)] pub struct WeChatLoginRequest { pub code: String, } #[derive(Debug, Deserialize)] pub struct WeChatApiResponse { pub openid: Option, pub errcode: Option, 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: String, pub latitude: String, #[serde(rename = "isFavorite", default)] pub is_favorite: bool, } // 在models.rs中添加(可放在WeatherDataBrief下方) #[derive(Debug, Serialize, FromRow)] pub struct WeatherListResponse { pub list: Vec, // 分页数据列表 pub total: i64, // 总数据条数 } // 天气数据结构体(修改后) #[derive(Debug, Deserialize, Serialize, FromRow)] pub struct WeatherData { #[serde(skip_deserializing)] #[sqlx(rename = "id")] pub id: i32, #[serde(rename = "areaType")] #[sqlx(rename = "areatype")] pub area_type: String, #[serde(rename = "assignmentNumber")] #[sqlx(rename = "assignmentnumber")] pub assignment_number: Option, #[serde(rename = "atmosphericStability")] #[sqlx(rename = "atmosphericstability")] pub atmospheric_stability: String, #[serde(rename = "averageWindDirection")] #[sqlx(rename = "averagewinddirection")] pub average_wind_direction: f64, #[serde(rename = "averageWindSpeed")] #[sqlx(rename = "averagewindspeed")] pub average_wind_speed: f64, #[serde( rename = "calculatedWindSpeed", alias = "calculated_wind_speed", alias = "calculatedwindspeed" )] #[sqlx(rename = "calculatedwindspeed")] pub calculated_wind_speed: Option, // 这个已经是 Option,很好 #[serde(rename = "cloudIndex")] #[sqlx(rename = "cloudindex")] pub cloud_index: String, // --- 以下是修改的重点 --- // 这些字段在前端是条件性发送的,因此在 Rust 中也必须是可选的 #[serde(rename = "convertedWindSpeed")] #[sqlx(rename = "convertedwindspeed")] pub converted_wind_speed: Option, pub date: NaiveDate, #[serde(rename = "daySinceJanFirst")] #[sqlx(rename = "daysincejanfirst")] pub day_since_jan_first: i32, #[serde(rename = "hasMeasuredWindSpeed")] #[sqlx(rename = "hasmeasuredwindspeed")] pub has_measured_wind_speed: bool, #[serde(rename = "hasSpotCheckWindSpeed")] #[sqlx(rename = "hasspotcheckwindspeed")] pub has_spot_check_wind_speed: bool, #[serde(rename = "spotCheckCount", default = "default_spot_check_count")] #[sqlx(rename = "spotcheckcount")] pub spot_check_count: i32, #[serde(rename = "hours")] #[sqlx(rename = "hour")] pub hours: i32, #[serde(rename = "inspectionType")] #[sqlx(rename = "inspectiontype")] pub inspection_type: Option, pub latitude: String, pub longitude: String, #[serde(rename = "lowCloudiness")] #[sqlx(rename = "lowcloudiness")] pub low_cloudiness: String, #[serde(rename = "measuredWindSpeed")] #[sqlx(rename = "measuredwindspeed")] pub measured_wind_speed: Option, #[serde(rename = "pointHeight")] #[sqlx(rename = "pointheight")] pub point_height: Option, #[serde(rename = "min")] #[sqlx(rename = "min")] pub min: i32, pub openid: String, #[serde(rename = "overallCloudiness")] #[sqlx(rename = "overallcloudiness")] pub overall_cloudiness: String, #[serde(rename = "overallSuitability")] #[sqlx(rename = "overallsuitability")] pub overall_suitability: String, #[serde(rename = "pointWindSpeed")] #[sqlx(rename = "pointwindspeed")] pub point_wind_speed: Option, // <-- 修改: f64 -> Option #[serde(rename = "solarDeclination")] #[sqlx(rename = "solardeclination")] pub solar_declination: f64, #[serde(rename = "solarRadiationLevel")] #[sqlx(rename = "solarradiationlevel")] pub solar_radiation_level: i32, #[serde(rename = "suitabilityDegree")] #[sqlx(rename = "suitabilitydegree")] pub suitability_degree: String, #[serde(rename = "sunAltitude")] #[sqlx(rename = "sunaltitude")] pub sun_altitude: f64, pub theta: f64, pub title: String, pub version: String, #[serde(rename = "windDirection")] #[sqlx(rename = "winddirection")] #[sqlx(try_from = "serde_json::Value")] pub wind_direction: FloatVec, #[serde(rename = "windDirectionStandardDeviation")] #[sqlx(rename = "winddirectionstandarddeviation")] pub wind_direction_standard_deviation: f64, #[serde(rename = "windDirectionSuitability")] #[sqlx(rename = "winddirectionsuitability")] pub wind_direction_suitability: String, #[serde(rename = "windSpeed")] #[sqlx(rename = "windspeed")] #[sqlx(try_from = "serde_json::Value")] pub wind_speed: FloatVec, #[serde(rename = "windSpeedSuitability")] #[sqlx(rename = "windspeedsuitability")] pub wind_speed_suitability: String, #[serde(skip_deserializing)] #[serde(rename = "isFavorite", default)] pub is_favorite: bool, } #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct User { pub id: i32, #[serde(rename = "name")] #[sqlx(rename = "name")] pub name: String, #[sqlx(rename = "openid")] pub openid: Option, #[sqlx(rename = "phone")] pub phone: Option, #[serde(rename = "type")] #[sqlx(rename = "type")] pub user_type: i32, #[serde(rename = "desc")] #[sqlx(rename = "desc")] pub description: Option, #[sqlx(rename = "is_member")] pub is_member: bool, #[sqlx(rename = "is_admin")] pub is_admin: bool, #[sqlx(rename = "membership_expires_at")] pub membership_expires_at: Option>, #[serde(rename = "avatarUrl")] #[sqlx(rename = "avatar_url")] pub avatar_url: Option, #[serde(rename = "nickname")] #[sqlx(rename = "nickname")] pub nickname: Option, } // 管理员更新用户付费状态的请求体 #[derive(Debug, Deserialize)] pub struct UpdatePaymentRequest { pub is_member: bool, pub membership_expires_at: Option, // ISO 8601 格式 } // 应用状态结构体 #[derive(Clone)] pub struct AppState { pub jwt_secret: String, pub wechat_appid: String, pub wechat_secret: String, } impl AppState { pub fn load() -> Result { Ok(Self { jwt_secret: std::env::var("JWT_SECRET") .map_err(|_| "环境变量JWT_SECRET未设置".to_string())?, wechat_appid: std::env::var("WECHAT_APPID") .map_err(|_| "环境变量WECHAT_APPID未设置".to_string())?, wechat_secret: std::env::var("WECHAT_SECRET") .map_err(|_| "环境变量WECHAT_SECRET未设置".to_string())?, }) } } // ===== 支付系统 ===== /// 创建订单请求体 #[derive(Debug, Deserialize)] pub struct CreateOrderRequest { pub package_type: String, } /// 模拟确认支付请求体 #[derive(Debug, Deserialize)] pub struct MockConfirmRequest { pub order_id: String, } /// payment_orders 表行结构 #[derive(Debug, Serialize, FromRow)] pub struct PaymentOrder { pub id: i32, pub user_id: i32, pub order_no: String, pub package_type: String, pub amount: i32, pub status: String, pub paid_at: Option>, pub expires_at: Option>, pub created_at: chrono::DateTime, } // ===== 通知系统 ===== #[derive(Debug, Serialize, FromRow)] pub struct Notification { pub id: i32, pub scope: String, pub user_id: Option, #[sqlx(rename = "type")] pub type_: String, pub title: String, pub content: Option, pub priority: String, pub link: Option, pub is_read: bool, pub created_at: chrono::DateTime, pub expires_at: Option>, }