feat: 统一前后端日志和错误处理

This commit is contained in:
2026-04-19 00:14:22 +08:00
parent fd21e641a2
commit 9d452c4e75
10 changed files with 602 additions and 663 deletions

View File

@@ -5,9 +5,10 @@ use chrono::Utc;
// 从 models 模块引入 WeatherData 结构体
use crate::models::{User, WeatherData, WeatherDataBrief, WeatherListResponse};
use crate::error::AppError;
// 用于插入weather_data的数据
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result<i32, String> {
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result<i32, AppError> {
// 配额检查:非付费用户数据条数限制
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());
@@ -20,7 +21,7 @@ pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user
.unwrap_or(20);
if current_count >= limit {
return Err("数据条数已达上限,请升级为付费用户".to_string());
return Err(AppError::Forbidden("数据条数已达上限,请升级为付费用户".to_string()));
}
}
@@ -68,13 +69,13 @@ pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user
.bind(&weather_data.suitability_degree)
.bind(
serde_json::to_value(&weather_data.wind_direction)
.map_err(|e| format!("JSON 序列化失败: {}", e))?,
.map_err(|e| AppError::Internal(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))?,
.map_err(|e| AppError::Internal(format!("JSON 序列化失败: {}", e)))?,
)
.bind(weather_data.average_wind_speed)
.bind(&weather_data.wind_speed_suitability)
@@ -91,7 +92,7 @@ pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user
{
Ok((id,)) => id,
Err(e) => {
return Err(format!("插入数据失败: {}", e));
return Err(AppError::Database(format!("插入数据失败: {}", e)));
}
};
@@ -116,7 +117,7 @@ pub async fn create_pool() -> Result<PgPool, Box<dyn Error>> {
}
// 获取天气数据详情
pub async fn get_weather_details(pool: &PgPool, weather_id: i32) -> Result<WeatherData, String> {
pub async fn get_weather_details(pool: &PgPool, weather_id: i32) -> Result<WeatherData, AppError> {
let query = r#"
SELECT
wd.id, wd.title, wd.date, wd.hour, wd.min,
@@ -142,8 +143,8 @@ pub async fn get_weather_details(pool: &PgPool, weather_id: i32) -> Result<Weath
.await
{
Ok(Some(row)) => row,
Ok(None) => return Err(format!("未找到ID为 {} 的天气数据", weather_id)),
Err(e) => return Err(format!("查询天气数据失败: {}", e)),
Ok(None) => return Err(AppError::NotFound(format!("未找到ID为 {} 的天气数据", weather_id))),
Err(e) => return Err(AppError::Database(format!("查询天气数据失败: {}", e))),
};
Ok(row)
@@ -155,7 +156,7 @@ pub async fn get_weather_list(
user_id: i32,
page: i32,
limit: i32,
) -> Result<WeatherListResponse, String> {
) -> Result<WeatherListResponse, AppError> {
let offset = (page - 1) * limit;
// 1. 查询符合条件的总条数
@@ -170,7 +171,7 @@ pub async fn get_weather_list(
.await
{
Ok((count,)) => count,
Err(e) => return Err(format!("查询总条数失败: {}", e)),
Err(e) => return Err(AppError::Database(format!("查询总条数失败: {}", e))),
};
// 2. 查询当前页数据列表
@@ -190,7 +191,7 @@ pub async fn get_weather_list(
.await
{
Ok(data) => data,
Err(e) => return Err(format!("查询天气数据列表失败: {}", e)),
Err(e) => return Err(AppError::Database(format!("查询天气数据列表失败: {}", e))),
};
// 3. 包装结果并返回
@@ -201,7 +202,7 @@ pub async fn delete_weather_data(
pool: &PgPool,
weather_id: i32,
user_id: i32,
) -> Result<(), String> {
) -> Result<(), AppError> {
let query = r#"
DELETE FROM weather_data
WHERE id = $1 AND user_id = $2
@@ -212,17 +213,17 @@ pub async fn delete_weather_data(
.bind(user_id)
.execute(pool)
.await
.map_err(|e| format!("删除天气数据失败: {}", e))?;
.map_err(|e| AppError::Database(format!("删除天气数据失败: {}", e)))?;
if result.rows_affected() == 0 {
return Err(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id));
return Err(AppError::NotFound(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id)));
}
Ok(())
}
// 根据用户ID获取用户信息
pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result<User, String> {
pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result<User, AppError> {
let query = r#"
SELECT
id, name, openid, phone, type, "desc", is_paid, is_admin, paid_expires_at,
@@ -237,15 +238,15 @@ pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result<User, String>
.await
{
Ok(Some(row)) => row,
Ok(None) => return Err(format!("未找到ID为 {} 的用户", user_id)),
Err(e) => return Err(format!("查询用户信息失败: {}", e)),
Ok(None) => return Err(AppError::NotFound(format!("未找到ID为 {} 的用户", user_id))),
Err(e) => return Err(AppError::Database(format!("查询用户信息失败: {}", e))),
};
Ok(row)
}
// 统计用户的天气数据条数
pub async fn count_user_weather_data(pool: &PgPool, user_id: i32) -> Result<i64, String> {
pub async fn count_user_weather_data(pool: &PgPool, user_id: i32) -> Result<i64, AppError> {
let query = r#"
SELECT COUNT(*) FROM weather_data WHERE user_id = $1
"#;
@@ -256,7 +257,7 @@ pub async fn count_user_weather_data(pool: &PgPool, user_id: i32) -> Result<i64,
.await
{
Ok((count,)) => count,
Err(e) => return Err(format!("查询天气数据条数失败: {}", e)),
Err(e) => return Err(AppError::Database(format!("查询天气数据条数失败: {}", e))),
};
Ok(count)
@@ -268,7 +269,7 @@ pub async fn update_user_payment_status(
user_id: i32,
is_paid: bool,
paid_expires_at: Option<chrono::DateTime<chrono::Utc>>,
) -> Result<(), String> {
) -> Result<(), AppError> {
let query = r#"
UPDATE users
SET is_paid = $1, paid_expires_at = $2
@@ -283,7 +284,7 @@ pub async fn update_user_payment_status(
.await
{
Ok(_) => Ok(()),
Err(e) => Err(format!("更新用户付费状态失败: {}", e)),
Err(e) => Err(AppError::Database(format!("更新用户付费状态失败: {}", e))),
}
}
@@ -293,7 +294,7 @@ pub async fn update_user_profile(
user_id: i32,
nickname: &Option<String>,
avatar_url: &Option<String>,
) -> Result<(), String> {
) -> Result<(), AppError> {
let query = r#"
UPDATE users
SET nickname = COALESCE($1, nickname),
@@ -309,7 +310,7 @@ pub async fn update_user_profile(
.await
{
Ok(_) => Ok(()),
Err(e) => Err(format!("更新用户个人信息失败: {}", e)),
Err(e) => Err(AppError::Database(format!("更新用户个人信息失败: {}", e))),
}
}
@@ -323,7 +324,7 @@ pub async fn create_payment_order(
package_type: &str,
amount: i32,
expires_at: Option<chrono::DateTime<chrono::Utc>>,
) -> Result<(), String> {
) -> Result<(), AppError> {
let query = r#"
INSERT INTO payment_orders (user_id, order_no, package_type, amount, expires_at)
VALUES ($1, $2, $3, $4, $5)
@@ -337,7 +338,7 @@ pub async fn create_payment_order(
.bind(expires_at)
.execute(pool)
.await
.map_err(|e| format!("创建订单失败: {}", e))?;
.map_err(|e| AppError::Database(format!("创建订单失败: {}", e)))?;
Ok(())
}
@@ -349,26 +350,26 @@ pub async fn confirm_payment_order(
pool: &PgPool,
order_no: &str,
user_id: i32,
) -> Result<Option<chrono::DateTime<chrono::Utc>>, String> {
) -> Result<Option<chrono::DateTime<chrono::Utc>>, AppError> {
let row = sqlx::query_as::<_, (i32, String, Option<chrono::DateTime<chrono::Utc>>)>(
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))?;
.map_err(|e| AppError::Database(format!("查询订单失败: {}", e)))?;
let (order_user_id, status, expires_at) = match row {
Some(r) => r,
None => return Err("订单不存在".to_string()),
None => return Err(AppError::NotFound("订单不存在".to_string())),
};
if order_user_id != user_id {
return Err("无权操作此订单".to_string());
return Err(AppError::Forbidden("无权操作此订单".to_string()));
}
if status != "pending" {
return Err("订单状态异常,无法确认支付".to_string());
return Err(AppError::BadRequest("订单状态异常,无法确认支付".to_string()));
}
sqlx::query(
@@ -377,7 +378,7 @@ pub async fn confirm_payment_order(
.bind(order_no)
.execute(pool)
.await
.map_err(|e| format!("更新订单状态失败: {}", e))?;
.map_err(|e| AppError::Database(format!("更新订单状态失败: {}", e)))?;
sqlx::query(
r#"UPDATE users SET is_paid = true, paid_expires_at = $1 WHERE id = $2"#,
@@ -386,7 +387,7 @@ pub async fn confirm_payment_order(
.bind(user_id)
.execute(pool)
.await
.map_err(|e| format!("更新用户付费状态失败: {}", e))?;
.map_err(|e| AppError::Database(format!("更新用户付费状态失败: {}", e)))?;
Ok(expires_at)
}
@@ -397,7 +398,7 @@ pub async fn confirm_payment_order(
pub async fn get_user_quota(
pool: &PgPool,
user_id: i32,
) -> Result<(i64, bool, Option<chrono::DateTime<chrono::Utc>>), String> {
) -> Result<(i64, bool, Option<chrono::DateTime<chrono::Utc>>), AppError> {
let user = get_user_by_id(pool, user_id).await?;
let is_paid_active = user.is_paid
&& (user.paid_expires_at.is_none()
@@ -414,7 +415,7 @@ pub async fn get_favorites_list(
user_id: i32,
page: i32,
limit: i32,
) -> Result<WeatherListResponse, String> {
) -> Result<WeatherListResponse, AppError> {
let offset = (page - 1) * limit;
let total_query = r#"
@@ -426,7 +427,7 @@ pub async fn get_favorites_list(
.bind(user_id)
.fetch_one(pool)
.await
.map_err(|e| format!("查询收藏总数失败: {}", e))?
.map_err(|e| AppError::Database(format!("查询收藏总数失败: {}", e)))?
.0;
let list_query = r#"
@@ -443,7 +444,7 @@ pub async fn get_favorites_list(
.bind(offset)
.fetch_all(pool)
.await
.map_err(|e| format!("查询收藏列表失败: {}", e))?;
.map_err(|e| AppError::Database(format!("查询收藏列表失败: {}", e)))?;
Ok(WeatherListResponse { list, total })
}
@@ -454,7 +455,7 @@ pub async fn set_weather_favorite(
weather_id: i32,
user_id: i32,
is_favorite: bool,
) -> Result<(), String> {
) -> Result<(), AppError> {
let query = r#"
UPDATE weather_data
SET is_favorite = $1
@@ -467,10 +468,10 @@ pub async fn set_weather_favorite(
.bind(user_id)
.execute(pool)
.await
.map_err(|e| format!("更新收藏状态失败: {}", e))?;
.map_err(|e| AppError::Database(format!("更新收藏状态失败: {}", e)))?;
if result.rows_affected() == 0 {
return Err(format!("未找到ID为 {} 的天气数据或无权限修改", weather_id));
return Err(AppError::NotFound(format!("未找到ID为 {} 的天气数据或无权限修改", weather_id)));
}
Ok(())

View File

@@ -1,57 +1,32 @@
use actix_web::{web, get, put, HttpResponse, Responder};
use actix_web::{web, get, put, HttpResponse};
use sqlx::postgres::PgPool;
use tracing::error;
use chrono::{DateTime, Utc};
use crate::db;
use crate::models::{Claims, ErrorResponse, UpdatePaymentRequest};
use crate::error::AppError;
use crate::models::{Claims, UpdatePaymentRequest};
#[get("/api/admin/users/{id}")]
pub async fn admin_get_user(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let target_user_id = path.into_inner();
tracing::info!("管理员获取用户信息, 目标用户ID: {}", target_user_id);
// 验证当前用户是否为管理员
let current_user = match db::get_user_by_id(pool.get_ref(), claims.user_id).await {
Ok(user) => user,
Err(e) => {
error!("获取当前用户信息失败: {}", e);
return HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": "获取用户信息失败"
}));
}
};
let current_user = db::get_user_by_id(pool.get_ref(), claims.user_id).await?;
if !current_user.is_admin {
return HttpResponse::Forbidden().json(serde_json::json!({
"success": false,
"errcode": 403,
"errmsg": "无权限执行此操作"
}));
return Err(AppError::Forbidden("无权限执行此操作".to_string()));
}
// 获取目标用户信息
match db::get_user_by_id(pool.get_ref(), target_user_id).await {
Ok(user) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": user
})),
Err(e) => {
error!("获取目标用户信息失败: {}", e);
let errcode = if e.starts_with("未找到") { 404 } else { 500 };
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": errcode,
"errmsg": e
}))
}
}
let user = db::get_user_by_id(pool.get_ref(), target_user_id).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": user
})))
}
#[put("/api/admin/users/{id}/payment")]
@@ -60,29 +35,14 @@ pub async fn admin_update_user_payment(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<UpdatePaymentRequest>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let target_user_id = path.into_inner();
tracing::info!("管理员更新用户付费状态, 目标用户ID: {}", target_user_id);
// 验证当前用户是否为管理员
let current_user = match db::get_user_by_id(pool.get_ref(), claims.user_id).await {
Ok(user) => user,
Err(e) => {
error!("获取当前用户信息失败: {}", e);
return HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": "获取用户信息失败"
}));
}
};
let current_user = db::get_user_by_id(pool.get_ref(), claims.user_id).await?;
if !current_user.is_admin {
return HttpResponse::Forbidden().json(serde_json::json!({
"success": false,
"errcode": 403,
"errmsg": "无权限执行此操作"
}));
return Err(AppError::Forbidden("无权限执行此操作".to_string()));
}
// 解析 paid_expires_at
@@ -90,29 +50,16 @@ pub async fn admin_update_user_payment(
Some(date_str) => match DateTime::parse_from_rfc3339(date_str) {
Ok(dt) => Some(dt.with_timezone(&Utc)),
Err(e) => {
return HttpResponse::BadRequest().json(serde_json::json!({
"success": false,
"errcode": 400,
"errmsg": format!("日期格式错误: {}", e)
}));
return Err(AppError::BadRequest(format!("日期格式错误: {}", e)));
}
},
None => None,
};
// 更新用户付费状态
match db::update_user_payment_status(pool.get_ref(), target_user_id, body.is_paid, paid_expires_at).await {
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": "用户付费状态已更新"
})),
Err(e) => {
error!("更新用户付费状态失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": e
}))
}
}
db::update_user_payment_status(pool.get_ref(), target_user_id, body.is_paid, paid_expires_at).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": "用户付费状态已更新"
})))
}

View File

@@ -1,8 +1,9 @@
use actix_web::{web, delete, get, post, HttpResponse, Responder};
use actix_web::{web, delete, get, post, HttpResponse};
use sqlx::postgres::PgPool;
use tracing::{debug, error, info};
use tracing::{debug, info};
use crate::db;
use crate::error::AppError;
use crate::models::Claims;
// GET /api/favorites - 获取收藏列表
@@ -11,31 +12,21 @@ pub async fn get_favorites(
query: web::Query<serde_json::Value>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let page = query.get("page").and_then(|v| v.as_i64()).unwrap_or(1).max(1) as i32;
let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(10).clamp(1, 100) as i32;
debug!("获取收藏列表, 页码: {}, 每页条数: {}", page, limit);
match db::get_favorites_list(pool.get_ref(), claims.user_id, page, limit).await {
Ok(response) => {
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": response.list,
"page": page,
"limit": limit,
"total": response.total
}))
}
Err(error_msg) => {
error!("获取收藏列表失败: {}", error_msg);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": error_msg
}))
}
}
let response = db::get_favorites_list(pool.get_ref(), claims.user_id, page, limit).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": response.list,
"page": page,
"limit": limit,
"total": response.total
})))
}
// POST /api/favorites/{id} - 添加收藏
@@ -44,25 +35,16 @@ pub async fn add_favorite(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let weather_id = path.into_inner();
info!("添加收藏, weather_id: {}", weather_id);
match db::set_weather_favorite(pool.get_ref(), weather_id, claims.user_id, true).await {
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": format!("已添加收藏")
})),
Err(error_msg) => {
error!("添加收藏失败: {}", error_msg);
let errcode = if error_msg.starts_with("未找到") { 404 } else { 500 };
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": errcode,
"errmsg": error_msg
}))
}
}
db::set_weather_favorite(pool.get_ref(), weather_id, claims.user_id, true).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": "已添加收藏"
})))
}
// DELETE /api/favorites/{id} - 取消收藏
@@ -71,23 +53,14 @@ pub async fn remove_favorite(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let weather_id = path.into_inner();
info!("取消收藏, weather_id: {}", weather_id);
match db::set_weather_favorite(pool.get_ref(), weather_id, claims.user_id, false).await {
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": format!("已取消收藏")
})),
Err(error_msg) => {
error!("取消收藏失败: {}", error_msg);
let errcode = if error_msg.starts_with("未找到") { 404 } else { 500 };
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": errcode,
"errmsg": error_msg
}))
}
}
db::set_weather_favorite(pool.get_ref(), weather_id, claims.user_id, false).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": "已取消收藏"
})))
}

View File

@@ -1,11 +1,11 @@
// handlers/payment.rs — 支付相关处理器
use actix_web::{get, post, web, HttpResponse, Responder};
use actix_web::{get, post, web, HttpResponse};
use chrono::Utc;
use sqlx::postgres::PgPool;
use tracing::error;
use uuid::Uuid;
use crate::db;
use crate::error::AppError;
use crate::models::{Claims, CreateOrderRequest, MockConfirmRequest};
struct PackageInfo {
@@ -45,24 +45,20 @@ pub async fn create_order(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<CreateOrderRequest>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let user_id = claims.user_id;
let pkg = match get_package_info(&body.package_type) {
Some(p) => p,
None => {
return HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 400,
"errmsg": "无效的套餐类型"
}));
return Err(AppError::BadRequest("无效的套餐类型".to_string()));
}
};
let order_no = Uuid::new_v4().to_string();
let expires_at = pkg.days.map(|d| Utc::now() + chrono::Duration::days(d));
match db::create_payment_order(
db::create_payment_order(
pool.get_ref(),
user_id,
&order_no,
@@ -70,28 +66,19 @@ pub async fn create_order(
pkg.amount,
expires_at,
)
.await
{
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"order_id": order_no,
"package_type": body.package_type,
"amount": pkg.amount,
"display_amount": pkg.display_amount,
"display_name": pkg.display_name,
"expires_at": expires_at,
}
})),
Err(e) => {
error!("创建订单失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": "创建订单失败,请重试"
}))
.await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"order_id": order_no,
"package_type": body.package_type,
"amount": pkg.amount,
"display_amount": pkg.display_amount,
"display_name": pkg.display_name,
"expires_at": expires_at,
}
}
})))
}
/// POST /api/payment/mock-confirm
@@ -100,26 +87,18 @@ pub async fn mock_confirm(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<MockConfirmRequest>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let user_id = claims.user_id;
match db::confirm_payment_order(pool.get_ref(), &body.order_id, user_id).await {
Ok(expires_at) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"is_paid_active": true,
"paid_expires_at": expires_at,
}
})),
Err(e) => {
error!("确认支付失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 400,
"errmsg": "支付确认失败,请重试"
}))
let expires_at = db::confirm_payment_order(pool.get_ref(), &body.order_id, user_id).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"is_paid_active": true,
"paid_expires_at": expires_at,
}
}
})))
}
/// GET /api/user/quota
@@ -127,34 +106,23 @@ pub async fn mock_confirm(
pub async fn get_user_quota(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let user_id = claims.user_id;
match db::get_user_quota(pool.get_ref(), user_id).await {
Ok((used, is_paid_active, paid_expires_at)) => {
let limit: i64 = std::env::var("FREE_USER_DATA_LIMIT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(20);
let (used, is_paid_active, paid_expires_at) = db::get_user_quota(pool.get_ref(), user_id).await?;
let limit: i64 = std::env::var("FREE_USER_DATA_LIMIT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(20);
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"used": used,
"limit": limit,
"unlimited": is_paid_active,
"is_paid_active": is_paid_active,
"paid_expires_at": paid_expires_at,
}
}))
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"used": used,
"limit": limit,
"unlimited": is_paid_active,
"is_paid_active": is_paid_active,
"paid_expires_at": paid_expires_at,
}
Err(e) => {
error!("获取配额信息失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": "获取配额信息失败"
}))
}
}
})))
}

View File

@@ -1,47 +1,37 @@
use actix_web::{web, get, put, HttpResponse, Responder};
use actix_web::{web, get, put, HttpResponse};
use sqlx::postgres::PgPool;
use tracing::error;
use tracing::info;
use serde::Deserialize;
use crate::db;
use crate::models::{Claims, ErrorResponse};
use crate::error::AppError;
use crate::models::Claims;
#[get("/api/user/profile")]
pub async fn get_current_user_profile(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let user_id = claims.user_id;
tracing::info!("获取当前用户信息, 用户ID: {}", user_id);
info!("获取当前用户信息, 用户ID: {}", user_id);
match db::get_user_by_id(pool.get_ref(), user_id).await {
Ok(user) => {
let is_paid_active = user.is_paid &&
(user.paid_expires_at.is_none() || user.paid_expires_at.unwrap() > chrono::Utc::now());
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"id": user.id,
"name": user.name,
"nickname": user.nickname,
"avatarUrl": user.avatar_url,
"is_paid": user.is_paid,
"is_paid_active": is_paid_active,
"is_admin": user.is_admin,
"paid_expires_at": user.paid_expires_at
}
}))
let user = db::get_user_by_id(pool.get_ref(), user_id).await?;
let is_paid_active = user.is_paid &&
(user.paid_expires_at.is_none() || user.paid_expires_at.unwrap() > chrono::Utc::now());
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"id": user.id,
"name": user.name,
"nickname": user.nickname,
"avatarUrl": user.avatar_url,
"is_paid": user.is_paid,
"is_paid_active": is_paid_active,
"is_admin": user.is_admin,
"paid_expires_at": user.paid_expires_at
}
Err(e) => {
error!("获取用户信息失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": e
}))
}
}
})))
}
#[derive(Debug, Deserialize)]
@@ -55,23 +45,12 @@ pub async fn save_user_profile(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<SaveUserProfileRequest>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let user_id = claims.user_id;
tracing::info!("保存用户信息, 用户ID: {}", user_id);
info!("保存用户信息, 用户ID: {}", user_id);
match db::update_user_profile(pool.get_ref(), user_id, &body.nickname, &body.avatar_url).await {
Ok(_) => {
HttpResponse::Ok().json(serde_json::json!({
"success": true
}))
}
Err(e) => {
error!("保存用户信息失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": e
}))
}
}
db::update_user_profile(pool.get_ref(), user_id, &body.nickname, &body.avatar_url).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true
})))
}

View File

@@ -1,45 +1,35 @@
use actix_web::{web, delete, get, post, HttpResponse, Responder};
use actix_web::{web, delete, get, post, HttpResponse};
use sqlx::postgres::PgPool;
use tracing::{debug, error, info};
use tracing::{debug, info};
use crate::auth;
use crate::db;
use crate::error::AppError;
use crate::handlers::TEMPLATES_DIR;
use crate::models::{AppState, Claims, ErrorResponse};
use crate::models::{AppState, Claims};
#[post("/api/post-weather-data")]
pub async fn post_weather_data(
data: web::Json<crate::models::WeatherData>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
info!("Received weather data, preparing to insert into DB...");
let user_id = claims.user_id;
// 调用 db.rs 中的函数来处理数据库逻辑
match db::insert_weather_data(pool.get_ref(), &data, user_id).await {
Ok(inserted_id) => {
info!(
"Successfully inserted weather data with id: {}",
inserted_id
);
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": "Weather data inserted successfully",
"inserted_id": inserted_id,
"received_assignment": data.assignment_number
}))
}
Err(error_msg) => {
error!("Database operation failed: {}", error_msg);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": error_msg
}))
}
}
let inserted_id = db::insert_weather_data(pool.get_ref(), &data, user_id).await?;
info!(
"Successfully inserted weather data with id: {}",
inserted_id
);
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": "Weather data inserted successfully",
"inserted_id": inserted_id,
"received_assignment": data.assignment_number
})))
}
#[post("/api/generate-temp-token/{resource_id}")]
@@ -48,45 +38,23 @@ pub async fn generate_temp_token_handler(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
app_state: web::Data<AppState>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let resource_id = path.into_inner();
let openid: &String = &claims.openid;
match db::get_weather_details(pool.get_ref(), resource_id).await {
Ok(weather_data) => {
if weather_data.openid != *openid {
return HttpResponse::Forbidden().json(ErrorResponse {
error: "无权限为该资源生成临时token".to_string(),
errcode: Some(403),
errmsg: None,
});
}
}
Err(e) => {
return HttpResponse::BadRequest().json(ErrorResponse {
error: format!("资源不存在: {}", e),
errcode: Some(404),
errmsg: None,
});
}
let weather_data = db::get_weather_details(pool.get_ref(), resource_id).await?;
if weather_data.openid != *openid {
return Err(AppError::Forbidden("无权限为该资源生成临时token".to_string()));
}
let temp_token = match auth::generate_temp_token(openid, resource_id, &app_state.jwt_secret, 10) {
Ok(token) => token,
Err(e) => {
return HttpResponse::InternalServerError().json(ErrorResponse {
error: format!("生成临时token失败: {}", e),
errcode: Some(500),
errmsg: None,
});
}
};
let temp_token = auth::generate_temp_token(openid, resource_id, &app_state.jwt_secret, 10)
.map_err(|e| AppError::Internal(format!("生成临时token失败: {}", e)))?;
HttpResponse::Ok().json(serde_json::json!({
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"temp_token": temp_token,
"expire_minutes": 10,
}))
})))
}
#[get("/weather/details")]
@@ -95,22 +63,14 @@ pub async fn get_weather_details(
claims: Option<web::ReqData<Claims>>,
query: web::Query<serde_json::Value>,
app_state: web::Data<AppState>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let mut is_temp_token = false;
let (openid, weather_id) =
if let Some(temp_token) = query.get("temp_token").and_then(|v| v.as_str()) {
is_temp_token = true;
let temp_claims = match auth::verify_temp_token(temp_token, &app_state.jwt_secret) {
Ok(c) => c,
Err(e) => {
return HttpResponse::Unauthorized().json(ErrorResponse {
error: format!("临时token无效: {}", e),
errcode: Some(401),
errmsg: None,
});
}
};
let temp_claims = auth::verify_temp_token(temp_token, &app_state.jwt_secret)
.map_err(|e| AppError::Unauthorized(format!("临时token无效: {}", e)))?;
(temp_claims.openid, temp_claims.resource_id)
} else if let Some(claims) = claims {
let weather_id = match query.get("id") {
@@ -120,57 +80,24 @@ pub async fn get_weather_details(
}
Some(v) if v.is_number() => v.as_f64().unwrap() as i32,
_ => {
return HttpResponse::BadRequest().json(ErrorResponse {
error: "缺少资源ID参数id".to_string(),
errcode: Some(400),
errmsg: None,
});
return Err(AppError::BadRequest("缺少资源ID参数id".to_string()));
}
};
(claims.openid.clone(), weather_id)
} else {
return HttpResponse::Unauthorized().json(ErrorResponse {
error: "缺少token需提供JWT或临时token".to_string(),
errcode: Some(401),
errmsg: None,
});
return Err(AppError::Unauthorized("缺少token需提供JWT或临时token".to_string()));
};
let weather_data = match db::get_weather_details(pool.get_ref(), weather_id).await {
Ok(data) => {
if data.openid != openid {
return HttpResponse::Forbidden().json(ErrorResponse {
error: "无权限访问该数据".to_string(),
errcode: Some(403),
errmsg: None,
});
}
data
}
Err(error_msg) => {
error!("获取天气数据详情失败: {}", error_msg);
let errcode = if error_msg.starts_with("未找到") {
404
} else {
500
};
return HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": errcode,
"errmsg": error_msg
}));
}
};
let weather_data = db::get_weather_details(pool.get_ref(), weather_id).await?;
if weather_data.openid != openid {
return Err(AppError::Forbidden("无权限访问该数据".to_string()));
}
if is_temp_token {
let index_html = match TEMPLATES_DIR.get_file("index.html") {
Some(file) => file.contents_utf8().unwrap_or_default(),
None => {
return HttpResponse::InternalServerError().json(ErrorResponse {
error: "无法找到模板文件".to_string(),
errcode: Some(500),
errmsg: None,
});
return Err(AppError::Internal("无法找到模板文件".to_string()));
}
};
@@ -191,14 +118,14 @@ pub async fn get_weather_details(
"",
);
HttpResponse::Ok()
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(rendered_html)
.body(rendered_html))
} else {
HttpResponse::Ok().json(serde_json::json!({
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": weather_data
}))
})))
}
}
@@ -207,7 +134,7 @@ pub async fn get_weather_brief(
query: web::Query<serde_json::Value>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let page = match query.get("page") {
Some(v) => {
let raw = if let Some(num) = v.as_i64() {
@@ -238,29 +165,18 @@ pub async fn get_weather_brief(
debug!("获取天气数据列表, 页码: {}, 每页条数: {}", page, limit);
match db::get_weather_list(pool.get_ref(), claims.user_id, page, limit).await {
Ok(response) => {
debug!(
"获取到的天气列表: {:?}, 总条数: {}",
response.list, response.total
);
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": response.list,
"page": page,
"limit": limit,
"total": response.total
}))
}
Err(error_msg) => {
error!("获取天气数据列表失败: {}", error_msg);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": error_msg
}))
}
}
let response = db::get_weather_list(pool.get_ref(), claims.user_id, page, limit).await?;
debug!(
"获取到的天气列表: {:?}, 总条数: {}",
response.list, response.total
);
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": response.list,
"page": page,
"limit": limit,
"total": response.total
})))
}
#[delete("/weather/delete/{id}")]
@@ -268,27 +184,13 @@ pub async fn delete_weather(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
) -> Result<HttpResponse, AppError> {
let weather_id = path.into_inner();
info!("删除天气数据, ID: {}", weather_id);
match db::delete_weather_data(pool.get_ref(), weather_id, claims.user_id).await {
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": format!("天气数据 {} 已成功删除", weather_id)
})),
Err(error_msg) => {
error!("删除天气数据失败: {}", error_msg);
let errcode = if error_msg.starts_with("未找到") {
404
} else {
500
};
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": errcode,
"errmsg": error_msg
}))
}
}
db::delete_weather_data(pool.get_ref(), weather_id, claims.user_id).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"success": true,
"message": format!("天气数据 {} 已成功删除", weather_id)
})))
}

View File

@@ -10,6 +10,7 @@ use std::pin::Pin;
mod auth;
mod config;
mod db;
mod error;
mod handlers;
mod models;