From 6d2824589c68a12351ffb95f0619787f687cd984 Mon Sep 17 00:00:00 2001 From: Milky0217 Date: Thu, 26 Mar 2026 14:28:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=AD=98=E5=82=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 avatar_url 和 nickname 字段到 users 表 - 修改 get_user_by_id 查询包含新字段 - 添加 save_user_profile API - 优化 deploy.sh 默认使用 release 模式 --- deploy.sh | 70 +----- migrations/002_add_user_profile_fields.sql | 2 + migrations/003_set_all_users_paid.sql | 1 + src/db.rs | 72 +++--- src/main.rs | 275 ++++++++------------- src/models.rs | 18 +- 6 files changed, 162 insertions(+), 276 deletions(-) create mode 100644 migrations/002_add_user_profile_fields.sql create mode 100644 migrations/003_set_all_users_paid.sql diff --git a/deploy.sh b/deploy.sh index 9309e0f..1319792 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,23 +1,19 @@ #!/bin/bash # ============================================================================== -# 部署脚本 - Rust Backend (支持编译模式选择) +# 部署脚本 - Rust Backend # ============================================================================== # --- 1. 配置 --- -# 项目名称 PROJECT_NAME="rust-backend" -# 远程服务器信息 REMOTE_USER="root" REMOTE_HOST="1panel-server" REMOTE_DIR="/root/rust/rust_backend" -# 远程服务名称 (用于 systemctl restart) SERVICE_NAME="rust-backend.service" # --- 2. 脚本初始化 --- set -euo pipefail -# 定义日志函数 log_info() { echo -e "\033[32m[INFO]\033[0m $1" } @@ -27,50 +23,7 @@ log_error() { exit 1 } -# 打印帮助信息 -usage() { - echo "用法: $0 [--release]" - echo "" - echo "选项:" - echo " --release 以 Release 模式编译并部署 (默认为 Debug 模式)" - echo " -h, --help 显示此帮助信息" - exit 0 -} - -# --- 3. 参数解析与动态配置 --- - -# 默认为 Debug 模式 -IS_RELEASE=false -BUILD_MODE="debug" -CARGO_ARGS="" -TARGET_BASE_DIR="/home/milky/Documents/ASD-backend/rust-backend/target/x86_64-unknown-linux-gnu/" - -# 解析命令行参数 -if [[ "$#" -gt 0 ]]; then - case "$1" in - --release) - IS_RELEASE=true - BUILD_MODE="release" - CARGO_ARGS="--release" - ;; - -h|--help) - usage - ;; - *) - log_error "未知参数: '$1'。使用 '$0 --help' 查看用法。" - ;; - esac -fi - -# 根据模式动态设置目标目录 -TARGET_DIR="${TARGET_BASE_DIR}${BUILD_MODE}/" - -log_info "部署模式: ${BUILD_MODE}" -log_info "本地目标目录: ${TARGET_DIR}" - -# --- 4. 核心功能函数 --- - -# 检查必要的命令是否存在 +# --- 3. 检查依赖 --- check_dependencies() { local deps=("cargo" "rsync" "ssh") for cmd in "${deps[@]}"; do @@ -81,20 +34,19 @@ check_dependencies() { log_info "所有依赖项检查通过。" } -# 编译项目 +# --- 4. 编译项目 --- build_project() { - log_info "开始编译项目 (${BUILD_MODE}模式)..." - # 使用动态的 CARGO_ARGS - if cargo build ${CARGO_ARGS} --target x86_64-unknown-linux-gnu --quiet; then + log_info "开始编译项目 (release模式)..." + if cargo build --release; then log_info "项目编译成功。" else log_error "项目编译失败!" fi } -# 上传二进制文件 +# --- 5. 上传二进制文件 --- upload_binary() { - local binary_path="${TARGET_DIR}${PROJECT_NAME}" + local binary_path="./target/release/${PROJECT_NAME}" if [ ! -f "$binary_path" ]; then log_error "编译产物未找到: $binary_path" fi @@ -104,7 +56,7 @@ upload_binary() { log_info "文件上传成功。" } -# 重启远程服务 +# --- 6. 重启远程服务 --- restart_remote_service() { log_info "正在重启远程服务 '${SERVICE_NAME}'..." if ssh "${REMOTE_USER}@${REMOTE_HOST}" "systemctl restart ${SERVICE_NAME}"; then @@ -114,10 +66,9 @@ restart_remote_service() { fi } -# --- 5. 主执行流程 --- - +# --- 7. 主执行流程 --- main() { - log_info "========== 开始部署 (${BUILD_MODE}模式) ==========" + log_info "========== 开始部署 ==========" check_dependencies build_project upload_binary @@ -125,5 +76,4 @@ main() { log_info "========== 部署完成! ==========" } -# 执行主函数 main diff --git a/migrations/002_add_user_profile_fields.sql b/migrations/002_add_user_profile_fields.sql new file mode 100644 index 0000000..4a9bbd6 --- /dev/null +++ b/migrations/002_add_user_profile_fields.sql @@ -0,0 +1,2 @@ +ALTER TABLE users ADD COLUMN IF NOT EXISTS avatar_url VARCHAR DEFAULT NULL; +ALTER TABLE users ADD COLUMN IF NOT EXISTS nickname VARCHAR DEFAULT NULL; diff --git a/migrations/003_set_all_users_paid.sql b/migrations/003_set_all_users_paid.sql new file mode 100644 index 0000000..a90cfe6 --- /dev/null +++ b/migrations/003_set_all_users_paid.sql @@ -0,0 +1 @@ +UPDATE users SET is_paid = true WHERE is_paid = false; diff --git a/src/db.rs b/src/db.rs index dab77e8..94ed775 100644 --- a/src/db.rs +++ b/src/db.rs @@ -57,7 +57,7 @@ pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user .bind(&weather_data.overall_cloudiness) .bind(&weather_data.low_cloudiness) .bind(&weather_data.cloud_index) - .bind(weather_data.solar_radiation_level as i32) + .bind(weather_data.solar_radiation_level) .bind(weather_data.has_measured_wind_speed) .bind(weather_data.measured_wind_speed) // <-- 修改: 直接绑定 Option .bind(weather_data.converted_wind_speed) // <-- 修改: 直接绑定 Option,并移除 `as i32` @@ -99,11 +99,6 @@ pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user } pub async fn create_pool() -> Result> { - // 加载环境变量,非致命错误处理 - if let Err(e) = dotenvy::dotenv() { - eprintln!("警告: 无法加载.env文件 - {}", e); - } - // 获取数据库URL并提供友好错误信息 let database_url = env::var("DATABASE_URL").map_err(|_| "环境变量DATABASE_URL未设置,请在.env文件中配置")?; @@ -201,55 +196,36 @@ pub async fn get_weather_list( Ok(WeatherListResponse { list, total }) } -// 删除天气数据 pub async fn delete_weather_data( pool: &PgPool, weather_id: i32, user_id: i32, ) -> Result<(), String> { - // 先检查数据是否存在且属于当前用户 - let query_check = r#" - SELECT id FROM weather_data - WHERE id = $1 AND user_id = $2 - "#; - - let exists = match sqlx::query_as::<_, (i32,)>(query_check) - .bind(weather_id) - .bind(user_id) - .fetch_optional(pool) - .await - { - Ok(Some(_)) => true, - Ok(None) => return Err(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id)), - Err(e) => return Err(format!("检查天气数据失败: {}", e)), - }; - - if !exists { - return Err(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id)); - } - - // 执行删除操作 - let query_delete = r#" + let query = r#" DELETE FROM weather_data WHERE id = $1 AND user_id = $2 "#; - match sqlx::query(query_delete) + let result = sqlx::query(query) .bind(weather_id) .bind(user_id) .execute(pool) .await - { - Ok(_) => Ok(()), - Err(e) => Err(format!("删除天气数据失败: {}", e)), + .map_err(|e| format!("删除天气数据失败: {}", e))?; + + if result.rows_affected() == 0 { + return Err(format!("未找到ID为 {} 的天气数据或无权限删除", weather_id)); } + + Ok(()) } // 根据用户ID获取用户信息 pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result { let query = r#" SELECT - id, name, openid, phone, type, "desc", is_paid, is_admin, paid_expires_at + id, name, openid, phone, type, "desc", is_paid, is_admin, paid_expires_at, + avatar_url, nickname FROM users WHERE id = $1 "#; @@ -309,3 +285,29 @@ pub async fn update_user_payment_status( Err(e) => Err(format!("更新用户付费状态失败: {}", e)), } } + +// 更新用户个人信息 +pub async fn update_user_profile( + pool: &PgPool, + user_id: i32, + nickname: &Option, + avatar_url: &Option, +) -> Result<(), String> { + let query = r#" + UPDATE users + SET nickname = COALESCE($1, nickname), + avatar_url = COALESCE($2, avatar_url) + WHERE id = $3 + "#; + + match sqlx::query(query) + .bind(nickname) + .bind(avatar_url) + .bind(user_id) + .execute(pool) + .await + { + Ok(_) => Ok(()), + Err(e) => Err(format!("更新用户个人信息失败: {}", e)), + } +} diff --git a/src/main.rs b/src/main.rs index aacf1d9..18a2133 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use include_dir::{Dir, include_dir}; use log::{debug, error, info, warn}; use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod}; use reqwest::Client; +use serde::Deserialize; use sqlx::postgres::PgPool; use std::pin::Pin; mod auth; @@ -20,6 +21,26 @@ use crate::models::{Claims, TokenResponse, UpdatePaymentRequest}; static STATIC_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/static"); static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates"); +#[derive(Clone)] +struct AppState { + jwt_secret: String, + wechat_appid: String, + wechat_secret: String, +} + +impl AppState { + fn load() -> Result { + 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())?, + }) + } +} + // 加载TLS证书和私钥 fn create_ssl_acceptor() -> Result> { let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; @@ -42,46 +63,14 @@ async fn login( pool: web::Data, req: web::Json, http_client: web::Data, + app_state: web::Data, ) -> impl Responder { - // 从环境变量获取微信小程序配置 - let appid = match std::env::var("WECHAT_APPID") { - Ok(id) => { - debug!("成功加载微信小程序appid"); - id - } - Err(_) => { - error!("环境变量中未配置WECHAT_APPID"); - return HttpResponse::InternalServerError().json(ErrorResponse { - error: "服务器配置错误:缺少微信小程序appid".to_string(), - errcode: None, - errmsg: None, - }); - } - }; - - let secret = match std::env::var("WECHAT_SECRET") { - Ok(secret) => { - debug!("成功加载微信小程序secret"); - secret - } - Err(_) => { - error!("环境变量中未配置WECHAT_SECRET"); - return HttpResponse::InternalServerError().json(ErrorResponse { - error: "服务器配置错误:缺少微信小程序secret".to_string(), - errcode: None, - errmsg: None, - }); - } - }; - - // 构建微信API请求URL let url = format!( "https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code", - appid, secret, req.code + app_state.wechat_appid, app_state.wechat_secret, req.code ); - debug!("准备调用微信API: {}", url.replace(&secret, "***")); // 日志中隐藏secret + debug!("准备调用微信API: {}", url.replace(&app_state.wechat_secret, "***")); - // 调用微信API let wechat_response = match http_client.get(&url).send().await { Ok(response) => { debug!("微信API请求成功,状态码: {}", response.status()); @@ -97,7 +86,6 @@ async fn login( } }; - // 解析微信API响应 let wechat_data: WeChatApiResponse = match wechat_response.json().await { Ok(data) => { debug!("微信API响应解析成功"); @@ -113,7 +101,6 @@ async fn login( } }; - // 检查微信API响应中的错误 if let Some(errcode) = wechat_data.errcode { warn!( "微信登录验证失败 - 错误码: {}, 错误信息: {:?}", @@ -126,10 +113,8 @@ async fn login( }); } - // 获取openid let openid = match wechat_data.openid { Some(id) => { - // 日志中只显示openid的前4位和后4位,保护用户隐私 let masked_openid = format!("{}***{}", &id[0..4], &id[id.len() - 4..]); debug!("获取到用户openid: {}", masked_openid); id @@ -144,25 +129,19 @@ async fn login( } }; - // 【核心修改】使用标准的 UPSERT 语法,原子性地创建或获取用户 let upsert_query = r#" INSERT INTO users (openid, name, type) VALUES ($1, left($1, 8), 2) - ON CONFLICT (openid) DO UPDATE SET - -- 这里可以更新一些需要变更的字段,例如最后登录时间 - -- 如果没有需要更新的,可以使用一个技巧来确保 RETURNING 总是有效 - id = users.id + ON CONFLICT (openid) DO UPDATE SET id = users.id RETURNING id "#; - // 使用 fetch_one,因为我们确定这个查询总能返回一个 id let user_id = match sqlx::query_as::<_, (i32,)>(upsert_query) .bind(&openid) - .fetch_one(pool.get_ref()) // <--- 注意这里是 fetch_one + .fetch_one(pool.get_ref()) .await { Ok((id,)) => { - // 无论是新用户还是老用户,都成功获取了 ID info!("用户登录/注册成功,用户ID: {}", id); id } @@ -176,23 +155,8 @@ async fn login( } }; - // 生成JWT token - let jwt_secret = match std::env::var("JWT_SECRET") { - Ok(secret) => secret, - Err(_) => { - error!("环境变量中未配置JWT_SECRET"); - return HttpResponse::InternalServerError().json(ErrorResponse { - error: "服务器配置错误:缺少JWT_SECRET".to_string(), - errcode: Some(500), - errmsg: None, - }); - } - }; - - // 生成token - let token = match generate_token(user_id, &openid, 2, &jwt_secret) { + let token = match generate_token(user_id, &openid, 2, &app_state.jwt_secret) { Ok(t) => { - // 日志中只显示token的前8位,保护安全 debug!("JWT生成成功: {}", &t[0..8]); t } @@ -245,17 +209,16 @@ async fn post_weather_data( } } -// main.rs #[post("/api/generate-temp-token/{resource_id}")] async fn generate_temp_token_handler( path: web::Path, pool: web::Data, - claims: web::ReqData, // 依赖原有JWT验证,确保是合法用户 + claims: web::ReqData, + app_state: web::Data, ) -> impl Responder { let resource_id = path.into_inner(); let openid: &String = &claims.openid; - // 校验用户是否有权限访问该资源(防止生成其他用户的资源token) match db::get_weather_details(pool.get_ref(), resource_id).await { Ok(weather_data) => { if weather_data.openid != *openid { @@ -275,20 +238,7 @@ async fn generate_temp_token_handler( } } - // 获取JWT密钥 - let jwt_secret = match std::env::var("JWT_SECRET") { - Ok(secret) => secret, - Err(_) => { - return HttpResponse::InternalServerError().json(ErrorResponse { - error: "服务器配置错误:缺少JWT_SECRET".to_string(), - errcode: Some(500), - errmsg: None, - }); - } - }; - - // 生成临时token(有效期10分钟) - let temp_token = match auth::generate_temp_token(openid, resource_id, &jwt_secret, 10) { + 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 { @@ -306,32 +256,18 @@ async fn generate_temp_token_handler( })) } -// 获取天气数据详情 #[get("/weather/details")] async fn get_weather_details( pool: web::Data, claims: Option>, query: web::Query, + app_state: web::Data, ) -> impl Responder { - let jwt_secret = match std::env::var("JWT_SECRET") { - Ok(secret) => secret, - Err(_) => { - return HttpResponse::InternalServerError().json(ErrorResponse { - error: "服务器配置错误:缺少JWT_SECRET".to_string(), - errcode: Some(500), - errmsg: None, - }); - } - }; - - // 标记是否使用临时token let mut is_temp_token = false; - // 1. 优先检查临时token(URL参数) let (openid, weather_id) = if let Some(temp_token) = query.get("temp_token").and_then(|v| v.as_str()) { - is_temp_token = true; // 标记为临时token访问 - // 验证临时token - let temp_claims = match auth::verify_temp_token(temp_token, &jwt_secret) { + 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 { @@ -341,13 +277,8 @@ async fn get_weather_details( }); } }; - - // 从临时token中获取资源ID (temp_claims.openid, temp_claims.resource_id) - } - // 2. 若没有临时token,使用原有JWT验证(需要查询参数id) - else if let Some(claims) = claims { - // 从查询参数获取id + } else if let Some(claims) = claims { let weather_id = match query.get("id").and_then(|v| v.as_i64()) { Some(id) => id as i32, None => { @@ -358,11 +289,8 @@ async fn get_weather_details( }); } }; - (claims.openid.clone(), weather_id) - } - // 3. 两种验证方式都没有,返回未授权 - else { + } else { return HttpResponse::Unauthorized().json(ErrorResponse { error: "缺少token(需提供JWT或临时token)".to_string(), errcode: Some(401), @@ -370,7 +298,6 @@ async fn get_weather_details( }); }; - // 获取天气数据 let weather_data = match db::get_weather_details(pool.get_ref(), weather_id).await { Ok(data) => { if data.openid != openid { @@ -396,10 +323,8 @@ async fn get_weather_details( })); } }; - // 根据访问方式返回不同格式 + if is_temp_token { - // 临时token访问:返回HTML页面,数据交由前端afterbody.js渲染 - // 读取模板文件 let index_html = match TEMPLATES_DIR.get_file("index.html") { Some(file) => file.contents_utf8().unwrap_or_default(), None => { @@ -411,33 +336,27 @@ async fn get_weather_details( } }; - // 将天气数据序列化为JSON字符串,供前端JS使用 let weather_data_json = serde_json::to_string(&weather_data).unwrap_or_default(); - - // 生成数据注入脚本:将数据挂载到window对象,供afterbody.js访问 let data_script = format!( r#" "#, weather_data_json ); - // 将数据脚本插入到模板中,同时显示详细信息容器 let rendered_html = index_html .replace("", &data_script) .replace( "style=\"display: none;\"", - "", // 显示详细信息容器,供前端渲染内容 + "", ); HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(rendered_html) } else { - // JWT访问:返回JSON数据 HttpResponse::Ok().json(serde_json::json!({ "success": true, "data": weather_data @@ -445,42 +364,36 @@ async fn get_weather_details( } } -// 修改main.rs中的get_weather_brief函数 #[get("/weather")] async fn get_weather_brief( query: web::Query, pool: web::Data, claims: web::ReqData, ) -> impl Responder { - // 同时处理数字和字符串类型的page参数 let page = match query.get("page") { Some(v) => { - // 尝试直接解析为数字 - if let Some(num) = v.as_i64() { + let raw = if let Some(num) = v.as_i64() { num as i32 - } - // 尝试将字符串解析为数字 - else if let Some(s) = v.as_str() { + } else if let Some(s) = v.as_str() { s.parse().unwrap_or(1) - } - // 解析失败用默认值 - else { + } else { 1 - } + }; + raw.max(1) } - None => 1, // 无参数时用默认值 + None => 1, }; - // 同理优化limit参数解析 let limit = match query.get("limit") { Some(v) => { - if let Some(num) = v.as_i64() { + let raw = if let Some(num) = v.as_i64() { num as i32 } else if let Some(s) = v.as_str() { s.parse().unwrap_or(10) } else { 10 - } + }; + raw.clamp(1, 100) } None => 10, }; @@ -489,18 +402,16 @@ async fn get_weather_brief( match db::get_weather_list(pool.get_ref(), claims.user_id, page, limit).await { Ok(response) => { - // 这里的response是WeatherListResponse类型 - // 打印获取到的天气列表和总数 debug!( "获取到的天气列表: {:?}, 总条数: {}", response.list, response.total ); HttpResponse::Ok().json(serde_json::json!({ "success": true, - "data": response.list, // 分页数据 + "data": response.list, "page": page, "limit": limit, - "total": response.total // 总条数(关键修改) + "total": response.total })) } Err(error_msg) => { @@ -670,7 +581,7 @@ async fn serve_static_files(path: web::Path) -> impl Responder { match STATIC_DIR.get_file(&file_path) { Some(file) => { // 根据文件扩展名设置 Content-Type - let content_type = match file_path.split('.').last() { + let content_type = match file_path.split('.').next_back() { Some("ico") => "image/x-icon", Some("css") => "text/css", Some("js") => "application/javascript", @@ -708,7 +619,6 @@ async fn get_current_user_profile( match db::get_user_by_id(pool.get_ref(), claims.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()); @@ -717,6 +627,8 @@ async fn get_current_user_profile( "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, @@ -735,10 +647,43 @@ async fn get_current_user_profile( } } -// 创建服务器配置的函数 +// 保存用户信息请求体 +#[derive(Debug, Deserialize)] +pub struct SaveUserProfileRequest { + pub nickname: Option, + pub avatar_url: Option, +} + +// 保存用户信息(普通用户可用) +#[put("/api/user/profile")] +async fn save_user_profile( + pool: web::Data, + claims: web::ReqData, + body: web::Json, +) -> impl Responder { + info!("保存用户信息, 用户ID: {}", claims.user_id); + + match db::update_user_profile(pool.get_ref(), claims.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 + })) + } + } +} + fn create_server_config( pool: PgPool, http_client: Client, + app_state: AppState, ) -> App< impl actix_web::dev::ServiceFactory< actix_web::dev::ServiceRequest, @@ -751,20 +696,19 @@ fn create_server_config( App::new() .app_data(web::Data::new(pool)) .app_data(web::Data::new(http_client)) - // 公开接口(无需验证) - // 注册静态文件服务:处理 /static/* 路径的请求 + .app_data(web::Data::new(app_state)) .service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files))) .service(login) - .service(get_weather_details) // 新增 - // 需要验证的接口(使用 from_fn 包装中间件) + .service(get_weather_details) .service( web::scope("") - .wrap(from_fn(jwt_middleware)) // 关键修改:用 from_fn 包装 + .wrap(from_fn(jwt_middleware)) .service(post_weather_data) .service(get_weather_brief) .service(generate_temp_token_handler) .service(delete_weather) .service(get_current_user_profile) + .service(save_user_profile) .service(admin_get_user) .service(admin_update_user_payment), ) @@ -773,17 +717,20 @@ fn create_server_config( #[actix_web::main] async fn main() -> std::io::Result<()> { if let Err(e) = dotenvy::dotenv() { - error!("警告:加载.env文件失败,使用系统环境变量: {}", e); + eprintln!("警告:加载.env文件失败,使用系统环境变量: {}", e); } - // 打印当前 RUST_LOG 的值 - let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "未设置".to_string()); - error!("当前 RUST_LOG 级别:{}", rust_log); - env_logger::init(); info!("logger init successful"); - // 初始化数据库连接池 + let app_state = match AppState::load() { + Ok(state) => state, + Err(e) => { + error!("环境变量配置错误: {}", e); + std::process::exit(1); + } + }; + let pool = match create_pool().await { Ok(pool) => pool, Err(e) => { @@ -793,31 +740,25 @@ async fn main() -> std::io::Result<()> { } }; - // 初始化HTTP客户端 let http_client = Client::new(); info!("Attempting to start server..."); - // 尝试多个端口 let ports = vec![4433, 8443, 8080, 3000, 8000, 8888]; let mut server: Option< Pin> + Unpin>>, > = None; - let mut bound_port = 0; // 记录绑定的端口 + let mut bound_port = 0; - // 使用&ports创建引用迭代器,而不是获取所有权 for port in &ports { let addr = format!("127.0.0.1:{}", port); info!("Trying to bind to {}", addr); - // 为每个服务器创建克隆的连接池和HTTP客户端 let pool_clone = pool.clone(); let http_client_clone = http_client.clone(); + let app_state_clone = app_state.clone(); - // 根据端口选择是否使用SSL if *port == 443 || *port == 8443 { - // 对于443和8443端口,使用HTTPS - // 为每个端口创建新的SSL构建器 let ssl_builder = match create_ssl_acceptor() { Ok(builder) => builder, Err(e) => { @@ -827,17 +768,15 @@ async fn main() -> std::io::Result<()> { }; match HttpServer::new(move || { - create_server_config(pool_clone.clone(), http_client_clone.clone()) + create_server_config(pool_clone.clone(), http_client_clone.clone(), app_state_clone.clone()) }) - .bind_openssl(&addr, ssl_builder) // 直接传递构建器 + .bind_openssl(&addr, ssl_builder) { Ok(s) => { info!("Successfully bound to {} with HTTPS", addr); - // 将服务器运行Future转换为正确的类型 let server_future = s.run(); - // 使用Box::pin将Pin>转换为Pin> + Unpin>> server = Some(Box::pin(server_future)); - bound_port = *port; // 记录绑定的端口 + bound_port = *port; break; } Err(e) => { @@ -849,19 +788,16 @@ async fn main() -> std::io::Result<()> { } } } else { - // 对于其他端口,使用HTTP match HttpServer::new(move || { - create_server_config(pool_clone.clone(), http_client_clone.clone()) + create_server_config(pool_clone.clone(), http_client_clone.clone(), app_state_clone.clone()) }) .bind(&addr) { Ok(s) => { info!("Successfully bound to {} with HTTP", addr); - // 将服务器运行Future转换为正确的类型 let server_future = s.run(); - // 使用Box::pin将Pin>转换为Pin> + Unpin>> server = Some(Box::pin(server_future)); - bound_port = *port; // 记录绑定的端口 + bound_port = *port; break; } Err(e) => { @@ -879,7 +815,6 @@ async fn main() -> std::io::Result<()> { match server { Some(s) => { - // 使用记录的端口判断是否为HTTPS if bound_port == 443 || bound_port == 8443 { info!("Server started successfully with HTTPS"); } else { diff --git a/src/models.rs b/src/models.rs index be16b37..05281e3 100644 --- a/src/models.rs +++ b/src/models.rs @@ -40,22 +40,12 @@ pub struct TokenResponse { } impl TokenResponse { - /// 快速创建登录成功的令牌响应 - /// - token: 生成的JWT令牌字符串 pub fn new(token: String) -> Self { Self { - success: true, // 登录成功场景固定为true + success: true, token, } } - - /// 创建登录失败的响应(可选,用于统一错误响应格式) - pub fn failed() -> Self { - Self { - success: false, - token: String::new(), // 失败时令牌为空 - } - } } #[derive(Debug, Deserialize)] @@ -294,6 +284,12 @@ pub struct User { pub is_admin: bool, #[sqlx(rename = "paid_expires_at")] pub paid_expires_at: Option>, + #[serde(rename = "avatarUrl")] + #[sqlx(rename = "avatar_url")] + pub avatar_url: Option, + #[serde(rename = "nickname")] + #[sqlx(rename = "nickname")] + pub nickname: Option, } // 管理员更新用户付费状态的请求体