feat: 更新环境配置和错误处理逻辑
- 更新后端端口号和环境配置,添加Redis支持 - 改进错误处理,添加带消息的未授权和禁止访问错误 - 优化前端登录流程和错误提示 - 更新前端页面标题和欢迎信息 - 清理未使用的代码模块
This commit is contained in:
@ -4,7 +4,9 @@ use crate::response::ApiResponse;
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum AppError {
|
||||
#[error("unauthorized")] Unauthorized,
|
||||
#[error("unauthorized: {0}")] UnauthorizedMsg(String),
|
||||
#[error("forbidden")] Forbidden,
|
||||
#[error("forbidden: {0}")] ForbiddenMsg(String),
|
||||
#[error("bad request: {0}")] BadRequest(String),
|
||||
#[error("not found")] NotFound,
|
||||
#[error(transparent)] Db(#[from] sea_orm::DbErr),
|
||||
@ -16,7 +18,9 @@ impl IntoResponse for AppError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let (status, code, msg) = match &self {
|
||||
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, 401, "unauthorized".to_string()),
|
||||
AppError::UnauthorizedMsg(m) => (StatusCode::UNAUTHORIZED, 401, m.clone()),
|
||||
AppError::Forbidden => (StatusCode::FORBIDDEN, 403, "forbidden".to_string()),
|
||||
AppError::ForbiddenMsg(m) => (StatusCode::FORBIDDEN, 403, m.clone()),
|
||||
AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, 400, m.clone()),
|
||||
AppError::NotFound => (StatusCode::NOT_FOUND, 404, "not found".into()),
|
||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, 500, "internal error".into()),
|
||||
|
||||
@ -7,7 +7,7 @@ pub mod models;
|
||||
pub mod services;
|
||||
pub mod routes;
|
||||
pub mod utils;
|
||||
pub mod workflow;
|
||||
//pub mod workflow;
|
||||
|
||||
use axum::Router;
|
||||
use axum::http::{HeaderValue, Method};
|
||||
@ -17,12 +17,12 @@ use axum::middleware;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// 增强:支持通过 ENV_FILE 指定要加载的环境文件
|
||||
// 增强:支持通过 ENV_FILE 指定要加载的环境文件,并记录实际加载的文件
|
||||
// - ENV_FILE=prod 或 production => .env.prod
|
||||
// - ENV_FILE=dev 或 development => .env
|
||||
// - ENV_FILE=staging => .env.staging
|
||||
// - ENV_FILE=任意字符串 => 视为显式文件名或路径
|
||||
if let Ok(v) = std::env::var("ENV_FILE") {
|
||||
let env_file_used: Option<String> = if let Ok(v) = std::env::var("ENV_FILE") {
|
||||
let filename = match v.trim() {
|
||||
"" => ".env".to_string(),
|
||||
"prod" | "production" => ".env.prod".to_string(),
|
||||
@ -30,10 +30,16 @@ async fn main() -> anyhow::Result<()> {
|
||||
"staging" | "pre" | "preprod" | "pre-production" => ".env.staging".to_string(),
|
||||
other => other.to_string(),
|
||||
};
|
||||
dotenvy::from_filename(&filename).ok();
|
||||
match dotenvy::from_filename_override(&filename) {
|
||||
Ok(_) => Some(filename),
|
||||
Err(_) => Some(format!("{} (not found)", filename)),
|
||||
}
|
||||
} else {
|
||||
dotenvy::dotenv().ok();
|
||||
}
|
||||
match dotenvy::dotenv_override() {
|
||||
Ok(path) => Some(path.to_string_lossy().to_string()),
|
||||
Err(_) => None,
|
||||
}
|
||||
};
|
||||
|
||||
tracing_subscriber::fmt().with_env_filter(tracing_subscriber::EnvFilter::from_default_env()).init();
|
||||
|
||||
@ -91,7 +97,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
.layer(cors)
|
||||
.layer(middleware::from_fn_with_state(db.clone(), middlewares::logging::request_logger));
|
||||
|
||||
let addr = format!("{}:{}", std::env::var("APP_HOST").unwrap_or("0.0.0.0".into()), std::env::var("APP_PORT").unwrap_or("8080".into()));
|
||||
// 读取并记录最终使用的主机与端口(默认端口改为 9898)
|
||||
let app_host = std::env::var("APP_HOST").unwrap_or("0.0.0.0".into());
|
||||
let app_port = std::env::var("APP_PORT").unwrap_or("9898".into());
|
||||
if let Some(f) = &env_file_used { tracing::info!("env file loaded: {}", f); } else { tracing::info!("env file loaded: <none>"); }
|
||||
tracing::info!("resolved APP_HOST={} APP_PORT={}", app_host, app_port);
|
||||
|
||||
let addr = format!("{}:{}", app_host, app_port);
|
||||
tracing::info!("listening on {}", addr);
|
||||
axum::serve(tokio::net::TcpListener::bind(addr).await?, app).await?;
|
||||
Ok(())
|
||||
|
||||
@ -7,10 +7,17 @@ use sea_orm::ActiveValue::NotSet;
|
||||
pub struct LoginResult { pub user: user::Model, pub access: String, pub refresh: String }
|
||||
|
||||
pub async fn login(db: &Db, username: String, password_plain: String) -> Result<LoginResult, AppError> {
|
||||
let u = user::Entity::find().filter(user::Column::Username.eq(username.clone())).one(db).await?.ok_or(AppError::Unauthorized)?;
|
||||
if u.status != 1 { return Err(AppError::Forbidden); }
|
||||
let ok = password::verify_password(&password_plain, &u.password_hash).map_err(|_| AppError::Unauthorized)?;
|
||||
if !ok { return Err(AppError::Unauthorized); }
|
||||
let u = user::Entity::find()
|
||||
.filter(user::Column::Username.eq(username.clone()))
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or(AppError::UnauthorizedMsg("用户名或密码错误".into()))?;
|
||||
|
||||
if u.status != 1 { return Err(AppError::ForbiddenMsg("账户已禁用".into())); }
|
||||
|
||||
let ok = password::verify_password(&password_plain, &u.password_hash)
|
||||
.map_err(|_| AppError::UnauthorizedMsg("用户名或密码错误".into()))?;
|
||||
if !ok { return Err(AppError::UnauthorizedMsg("用户名或密码错误".into())); }
|
||||
|
||||
let access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username);
|
||||
let refresh_claims = crate::middlewares::jwt::new_refresh_claims(u.id, &u.username);
|
||||
@ -79,7 +86,7 @@ pub async fn rotate_refresh(db: &Db, uid: i64, old_refresh: String) -> Result<(S
|
||||
let existing = refresh_token::Entity::find().filter(refresh_token::Column::UserId.eq(uid)).filter(refresh_token::Column::TokenHash.eq(token_hash.clone())).one(db).await?;
|
||||
|
||||
if !is_valid_redis && existing.is_none() {
|
||||
return Err(AppError::Unauthorized);
|
||||
return Err(AppError::Unauthorized);
|
||||
}
|
||||
|
||||
let u = user::Entity::find_by_id(uid).one(db).await?.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
Reference in New Issue
Block a user