添加管理员API端点

- .env 添加 FREE_USER_DATA_LIMIT 配置
- 新增 GET /api/admin/users/{id} 管理员获取用户信息
- 新增 PUT /api/admin/users/{id}/payment 更新用户付费状态
- post_weather_data 集成 JWT 认证,使用 claims.user_id
- 管理员接口通过数据库查询验证 is_admin
This commit is contained in:
2026-03-25 13:17:53 +08:00
parent 484146aba9
commit fa34e6597f
2 changed files with 125 additions and 11 deletions

3
.env
View File

@@ -6,4 +6,5 @@ JWT_SECRET="your_super_secret_key"
SSL_KEY_PATH=/etc/ssl/private/private.key
SSL_CERT_PATH=/etc/ssl/certs/full_chain.pem
RUST_LOG=info
APP_VERSION="0.2.0"
APP_VERSION="0.2.0"
FREE_USER_DATA_LIMIT=20

View File

@@ -1,5 +1,5 @@
use actix_web::middleware::from_fn;
use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, web};
use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, put, web};
use include_dir::{Dir, include_dir};
use log::{debug, error, info, warn};
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
@@ -14,7 +14,7 @@ use auth::{generate_token, jwt_middleware};
use db::{create_pool, insert_weather_data};
use models::{ErrorResponse, WeChatApiResponse, WeChatLoginRequest, WeatherData};
use crate::models::{Claims, TokenResponse};
use crate::models::{Claims, TokenResponse, UpdatePaymentRequest};
// 编译时嵌入整个 static 目录
static STATIC_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/static");
@@ -214,11 +214,14 @@ async fn login(
async fn post_weather_data(
data: web::Json<WeatherData>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
info!("Received weather data, preparing to insert into DB...");
let user_id = claims.user_id;
// 调用 db.rs 中的函数来处理数据库逻辑
match insert_weather_data(pool.get_ref(), &data).await {
match insert_weather_data(pool.get_ref(), &data, user_id).await {
Ok(inserted_id) => {
info!(
"Successfully inserted weather data with id: {}",
@@ -233,14 +236,9 @@ async fn post_weather_data(
}
Err(error_msg) => {
error!("Database operation failed: {}", error_msg);
// 根据错误信息返回统一格式的错误响应
let mut errcode = 500;
if error_msg.starts_with("未找到openid") {
errcode = 404;
}
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": errcode,
"errcode": 500,
"errmsg": error_msg
}))
}
@@ -547,6 +545,119 @@ async fn delete_weather(
}
}
// 管理员获取用户信息
#[get("/api/admin/users/{id}")]
async fn admin_get_user(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
let target_user_id = path.into_inner();
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": "获取用户信息失败"
}));
}
};
if !current_user.is_admin {
return HttpResponse::Forbidden().json(serde_json::json!({
"success": false,
"errcode": 403,
"errmsg": "无权限执行此操作"
}));
}
// 获取目标用户信息
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
}))
}
}
}
// 管理员更新用户付费状态
#[put("/api/admin/users/{id}/payment")]
async fn admin_update_user_payment(
path: web::Path<i32>,
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<UpdatePaymentRequest>,
) -> impl Responder {
let target_user_id = path.into_inner();
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": "获取用户信息失败"
}));
}
};
if !current_user.is_admin {
return HttpResponse::Forbidden().json(serde_json::json!({
"success": false,
"errcode": 403,
"errmsg": "无权限执行此操作"
}));
}
// 解析 paid_expires_at
let paid_expires_at = match &body.paid_expires_at {
Some(date_str) => match chrono::DateTime::parse_from_rfc3339(date_str) {
Ok(dt) => Some(dt.with_timezone(&chrono::Utc)),
Err(e) => {
return HttpResponse::BadRequest().json(serde_json::json!({
"success": false,
"errcode": 400,
"errmsg": 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
}))
}
}
}
// 在 main.rs 中添加以下函数,用于处理静态文件请求
async fn serve_static_files(path: web::Path<String>) -> impl Responder {
// 获取请求的文件路径(例如 "css/style.css"
@@ -615,7 +726,9 @@ fn create_server_config(
.service(post_weather_data)
.service(get_weather_brief) // 新增
.service(generate_temp_token_handler)
.service(delete_weather),
.service(delete_weather)
.service(admin_get_user)
.service(admin_update_user_payment),
)
}