Files
asd-backend/src/models.rs
Milky0217 484146aba9 支付系统核心:配额限制与用户付费状态管理
- 添加 tokio 依赖用于异步测试
- 启用 User 结构体,添加 is_paid/is_admin/paid_expires_at 字段
- 添加 UpdatePaymentRequest 请求体
- insert_weather_data 集成配额检查逻辑
- 新增 get_user_by_id、count_user_weather_data、update_user_payment_status
- 添加 payment_fields 数据库迁移脚本
2026-03-25 13:17:36 +08:00

305 lines
8.6 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;
// 定义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
}
// 登录成功后的令牌响应结构体
/// 仅包含操作状态和令牌信息
#[derive(Debug, Serialize, Clone)]
pub struct TokenResponse {
/// 操作状态true表示登录成功false表示失败
pub success: bool,
/// 登录成功后生成的JWT令牌客户端后续请求需携带此令牌
pub token: String,
}
impl TokenResponse {
/// 快速创建登录成功的令牌响应
/// - token: 生成的JWT令牌字符串
pub fn new(token: String) -> Self {
Self {
success: true, // 登录成功场景固定为true
token,
}
}
/// 创建登录失败的响应(可选,用于统一错误响应格式)
pub fn failed() -> Self {
Self {
success: false,
token: String::new(), // 失败时令牌为空
}
}
}
#[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>,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: 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,
}
// 在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")]
#[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 = "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,
}
#[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_paid")]
pub is_paid: bool,
#[sqlx(rename = "is_admin")]
pub is_admin: bool,
#[sqlx(rename = "paid_expires_at")]
pub paid_expires_at: Option<chrono::DateTime<chrono::Utc>>,
}
// 管理员更新用户付费状态的请求体
#[derive(Debug, Deserialize)]
pub struct UpdatePaymentRequest {
pub is_paid: bool,
pub paid_expires_at: Option<String>, // ISO 8601 格式
}