119 lines
3.5 KiB
Rust
119 lines
3.5 KiB
Rust
|
|
//! 认证路由 — 登录/登出/当前用户信息
|
|||
|
|
//!
|
|||
|
|
//! - POST /api/auth/login — 手机号+密码登录,返回 JWT token
|
|||
|
|
//! - POST /api/auth/logout — 登出(客户端清除 token 即可,服务端无状态)
|
|||
|
|
//! - GET /api/auth/me — 获取当前登录用户信息及权限码
|
|||
|
|
|
|||
|
|
use axum::extract::State;
|
|||
|
|
use axum::Json;
|
|||
|
|
use serde::Deserialize;
|
|||
|
|
use serde_json::{json, Value};
|
|||
|
|
|
|||
|
|
use crate::error::AppError;
|
|||
|
|
use crate::middleware::auth::{self, CurrentUser};
|
|||
|
|
use crate::commands::AppState;
|
|||
|
|
|
|||
|
|
/// 登录请求体
|
|||
|
|
#[derive(Debug, Deserialize)]
|
|||
|
|
pub(crate) struct LoginInput {
|
|||
|
|
phone: String,
|
|||
|
|
password: String,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// POST /api/auth/login — 用户登录
|
|||
|
|
pub async fn login(
|
|||
|
|
State(state): State<AppState>,
|
|||
|
|
Json(input): Json<LoginInput>,
|
|||
|
|
) -> Result<Json<Value>, AppError> {
|
|||
|
|
let pool = &state.mysql;
|
|||
|
|
|
|||
|
|
if input.phone.is_empty() || input.password.is_empty() {
|
|||
|
|
return Err(AppError::BadRequest("手机号和密码不能为空".into()));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询用户(含密码比对)
|
|||
|
|
let user_row: Option<(i64, Option<String>, String, String, i32, i32)> = sqlx::query_as(
|
|||
|
|
"SELECT id, name, phone, password, role, status FROM users WHERE phone = ?"
|
|||
|
|
)
|
|||
|
|
.bind(&input.phone)
|
|||
|
|
.fetch_optional(pool)
|
|||
|
|
.await?;
|
|||
|
|
|
|||
|
|
let (id, name, phone, stored_password, role, status) =
|
|||
|
|
user_row.ok_or_else(|| AppError::Unauthorized("手机号或密码错误".into()))?;
|
|||
|
|
|
|||
|
|
// 检查账户状态
|
|||
|
|
if status == 0 {
|
|||
|
|
return Err(AppError::Forbidden("账户已被禁用".into()));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 密码校验(argon2 哈希比对)
|
|||
|
|
let valid = auth::verify_password(&input.password, &stored_password)
|
|||
|
|
.map_err(|_| AppError::Internal("密码验证失败".into()))?;
|
|||
|
|
if !valid {
|
|||
|
|
return Err(AppError::Unauthorized("手机号或密码错误".into()));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成 JWT token
|
|||
|
|
let token = auth::generate_token(id, &phone)?;
|
|||
|
|
|
|||
|
|
// 查询用户权限码
|
|||
|
|
let permissions = auth::load_user_permissions(pool, id).await?;
|
|||
|
|
let perm_list: Vec<String> = permissions.into_iter().collect();
|
|||
|
|
|
|||
|
|
Ok(Json(json!({
|
|||
|
|
"code": 0,
|
|||
|
|
"data": {
|
|||
|
|
"token": token,
|
|||
|
|
"user": {
|
|||
|
|
"id": id,
|
|||
|
|
"name": name,
|
|||
|
|
"phone": phone,
|
|||
|
|
"role": role,
|
|||
|
|
"permissions": perm_list,
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"message": "登录成功"
|
|||
|
|
})))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// POST /api/auth/logout — 用户登出
|
|||
|
|
pub async fn logout() -> Json<Value> {
|
|||
|
|
// JWT 无状态,客户端清除 token 即可
|
|||
|
|
Json(json!({ "code": 0, "data": null, "message": "登出成功" }))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// GET /api/auth/me — 获取当前用户信息及权限
|
|||
|
|
#[allow(clippy::type_complexity)]
|
|||
|
|
pub async fn me(
|
|||
|
|
user: CurrentUser,
|
|||
|
|
State(state): State<AppState>,
|
|||
|
|
) -> Result<Json<Value>, AppError> {
|
|||
|
|
let pool = &state.mysql;
|
|||
|
|
// 查询用户详细信息
|
|||
|
|
let row: Option<(i64, Option<String>, String, i32, Option<i64>)> = sqlx::query_as(
|
|||
|
|
"SELECT id, name, phone, role, organization_id FROM users WHERE id = ?"
|
|||
|
|
)
|
|||
|
|
.bind(user.user_id)
|
|||
|
|
.fetch_optional(pool)
|
|||
|
|
.await?;
|
|||
|
|
|
|||
|
|
let (id, name, phone, role, org_id) =
|
|||
|
|
row.ok_or_else(|| AppError::NotFound("用户不存在".into()))?;
|
|||
|
|
|
|||
|
|
let perm_list: Vec<String> = user.permissions.into_iter().collect();
|
|||
|
|
|
|||
|
|
Ok(Json(json!({
|
|||
|
|
"code": 0,
|
|||
|
|
"data": {
|
|||
|
|
"id": id,
|
|||
|
|
"name": name,
|
|||
|
|
"phone": phone,
|
|||
|
|
"role": role,
|
|||
|
|
"organization_id": org_id,
|
|||
|
|
"permissions": perm_list,
|
|||
|
|
},
|
|||
|
|
"message": "ok"
|
|||
|
|
})))
|
|||
|
|
}
|