支付系统核心:配额限制与用户付费状态管理

- 添加 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 数据库迁移脚本
This commit is contained in:
2026-03-25 13:17:36 +08:00
parent 63b6d5df8f
commit 484146aba9
5 changed files with 117 additions and 19 deletions

View File

@@ -1,28 +1,30 @@
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::env;
use std::error::Error;
use chrono::Utc;
// 从 models 模块引入 WeatherData 结构体
use crate::models::{WeatherData, WeatherDataBrief, WeatherListResponse};
use crate::models::{User, WeatherData, WeatherDataBrief, WeatherListResponse};
// 用于插入weather_data的数据
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData) -> Result<i32, String> {
// 1. 根据 openid 查询 user_id (这部分逻辑不变)
let user_id = match sqlx::query_as::<_, (i32,)>("SELECT id FROM users WHERE openid = $1")
.bind(&weather_data.openid)
.fetch_optional(pool)
.await
{
Ok(Some((id,))) => id,
Ok(None) => {
return Err(format!("未找到openid为 {} 的用户", weather_data.openid));
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result<i32, 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() > 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());
}
Err(e) => {
return Err(format!("查询用户失败: {}", e));
}
};
}
// 2. 准备插入数据的 SQL 语句 (SQL本身不变)
// 准备插入数据的 SQL 语句
let insert_query = r#"
INSERT INTO weather_data (
user_id, title, date, hour, min, longitude, latitude, daysincejanfirst,
@@ -242,3 +244,68 @@ pub async fn delete_weather_data(
Err(e) => Err(format!("删除天气数据失败: {}", e)),
}
}
// 根据用户ID获取用户信息
pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result<User, String> {
let query = r#"
SELECT
id, name, openid, phone, type, desc, is_paid, is_admin, paid_expires_at
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<i64, String> {
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<chrono::DateTime<chrono::Utc>>,
) -> 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)),
}
}