Files
asd-backend/src/models.rs

404 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_ytpe
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,
}
}
}
// 兼容旧的 TokenResponse
#[derive(Debug, Serialize, Clone)]
pub struct TokenResponse {
pub success: bool,
pub token: String,
}
impl TokenResponse {
pub fn new(token: String) -> Self {
Self {
success: true,
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<String>,
pub errcode: Option<i32>,
pub errmsg: Option<String>,
}
// 定义新类型,包装 Vec<f64>(当前 crate 内的类型)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FloatVec(pub Vec<f64>);
// 为 Vec<f64> 实现从 JsonValue 的转换
impl TryFrom<JsonValue> for FloatVec {
type Error = String;
fn try_from(value: JsonValue) -> Result<Self, Self::Error> {
// 检查 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<WeatherDataBrief>, // 分页数据列表
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<String>,
#[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<f64>, // 这个已经是 Option很好
#[serde(rename = "cloudIndex")]
#[sqlx(rename = "cloudindex")]
pub cloud_index: String,
// --- 以下是修改的重点 ---
// 这些字段在前端是条件性发送的,因此在 Rust 中也必须是可选的
#[serde(rename = "convertedWindSpeed")]
#[sqlx(rename = "convertedwindspeed")]
pub converted_wind_speed: Option<f64>,
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<String>,
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<f64>,
#[serde(rename = "pointHeight")]
#[sqlx(rename = "pointheight")]
pub point_height: Option<f64>,
#[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>, // <-- 修改: f64 -> Option<f64>
#[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<String>,
#[sqlx(rename = "phone")]
pub phone: Option<String>,
#[serde(rename = "type")]
#[sqlx(rename = "type")]
pub user_type: i32,
#[serde(rename = "desc")]
#[sqlx(rename = "desc")]
pub description: Option<String>,
#[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<chrono::DateTime<chrono::Utc>>,
#[serde(rename = "avatarUrl")]
#[sqlx(rename = "avatar_url")]
pub avatar_url: Option<String>,
#[serde(rename = "nickname")]
#[sqlx(rename = "nickname")]
pub nickname: Option<String>,
}
// 管理员更新用户付费状态的请求体
#[derive(Debug, Deserialize)]
pub struct UpdatePaymentRequest {
pub is_member: bool,
pub membership_expires_at: Option<String>, // ISO 8601 格式
}
// 应用状态结构体
#[derive(Clone)]
pub struct AppState {
pub jwt_secret: String,
pub wechat_appid: String,
pub wechat_secret: String,
pub free_user_data_limit: i32,
// ===== 支付宝配置 =====
pub alipay_app_id: Option<String>,
pub alipay_private_key: Option<String>,
pub alipay_alipay_public_key: Option<String>,
pub alipay_gateway: Option<String>,
}
impl AppState {
pub fn load() -> Result<Self, String> {
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())?,
free_user_data_limit: std::env::var("FREE_USER_DATA_LIMIT")
.map(|v| v.parse().unwrap_or(20))
.unwrap_or(20),
// 支付宝配置(可选,未配置时使用模拟支付)
alipay_app_id: std::env::var("ALIPAY_APP_ID").ok(),
alipay_private_key: std::env::var("ALIPAY_PRIVATE_KEY").ok(),
alipay_alipay_public_key: std::env::var("ALIPAY_ALIPAY_PUBLIC_KEY").ok(),
alipay_gateway: std::env::var("ALIPAY_GATEWAY").ok(),
})
}
}
// ===== 支付系统 =====
/// 创建订单请求体
#[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<chrono::DateTime<chrono::Utc>>,
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
pub created_at: chrono::DateTime<chrono::Utc>,
}