This commit is contained in:
2025-08-28 00:55:35 +08:00
commit 410f54a65e
93 changed files with 9863 additions and 0 deletions

View File

@ -0,0 +1,65 @@
use axum::{Router, routing::{post, get}, Json, extract::State};
use axum::http::{HeaderMap, HeaderValue};
use serde::{Serialize, Deserialize};
use crate::{db::Db, response::ApiResponse, error::AppError};
use crate::services::{auth_service, role_service, menu_service};
use crate::middlewares::jwt::{AuthUser, decode_token};
use crate::models::user;
#[derive(Deserialize)]
pub struct LoginReq { pub username: String, pub password: String }
#[derive(Serialize)]
pub struct UserInfo { pub id: i64, pub username: String, pub nickname: Option<String>, pub status: i32 }
impl From<user::Model> for UserInfo {
fn from(m: user::Model) -> Self { Self { id: m.id, username: m.username, nickname: m.nickname, status: m.status } }
}
#[derive(Serialize)]
pub struct LoginResp { pub access_token: String, pub user: UserInfo }
pub fn router() -> Router<Db> {
Router::new()
.route("/login", post(login))
.route("/logout", post(logout))
.route("/refresh", get(refresh))
.route("/menus", get(current_user_menus))
}
pub async fn login(State(db): State<Db>, Json(req): Json<LoginReq>) -> Result<(HeaderMap, Json<ApiResponse<LoginResp>>), AppError> {
let res = auth_service::login(&db, req.username, req.password).await?;
let mut headers = HeaderMap::new();
let cookie = format!("refresh_token={}; HttpOnly; SameSite=Lax; Path=/; Max-Age=1209600", res.refresh);
headers.insert(axum::http::header::SET_COOKIE, HeaderValue::from_str(&cookie).unwrap());
Ok((headers, Json(ApiResponse::ok(LoginResp { access_token: res.access, user: res.user.into() }))))
}
pub async fn logout(State(db): State<Db>, user: AuthUser) -> Result<Json<ApiResponse<serde_json::Value>>, AppError> {
auth_service::logout(&db, user.uid).await?;
Ok(Json(ApiResponse::ok(serde_json::json!({"success": true}))))
}
pub async fn refresh(State(db): State<Db>, headers: HeaderMap) -> Result<(HeaderMap, Json<ApiResponse<serde_json::Value>>), AppError> {
let cookie_header = headers.get(axum::http::header::COOKIE).and_then(|v| v.to_str().ok()).unwrap_or("");
let refresh = cookie_header.split(';').find_map(|p| {
let kv: Vec<&str> = p.trim().splitn(2, '=').collect();
if kv.len()==2 && kv[0]=="refresh_token" { Some(kv[1].to_string()) } else { None }
}).ok_or(AppError::Unauthorized)?;
let secret = std::env::var("JWT_SECRET").unwrap();
let claims = decode_token(&refresh, &secret)?;
if claims.typ != "refresh" { return Err(AppError::Unauthorized); }
let (access, new_refresh) = auth_service::rotate_refresh(&db, claims.uid, refresh).await?;
let mut headers = HeaderMap::new();
let cookie = format!("refresh_token={}; HttpOnly; SameSite=Lax; Path=/; Max-Age=1209600", new_refresh);
headers.insert(axum::http::header::SET_COOKIE, HeaderValue::from_str(&cookie).unwrap());
Ok((headers, Json(ApiResponse::ok(serde_json::json!({"access_token": access})))) )
}
pub async fn current_user_menus(State(db): State<Db>, user: AuthUser) -> Result<Json<ApiResponse<Vec<menu_service::MenuInfo>>>, AppError> {
let ids = role_service::get_menu_ids_by_user_id(&db, user.uid).await?;
let menus = menu_service::list_by_ids(&db, ids).await?;
Ok(Json(ApiResponse::ok(menus)))
}

View File

@ -0,0 +1,36 @@
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
use serde::Deserialize;
use crate::{db::Db, services::department_service, response::ApiResponse, error::AppError};
#[derive(Deserialize)]
struct ListParams { keyword: Option<String> }
pub fn router() -> Router<Db> { Router::new()
.route("/departments", get(list).post(create))
.route("/departments/{id}", put(update).delete(delete_department))
}
async fn list(State(db): State<Db>, Query(p): Query<ListParams>) -> Result<Json<ApiResponse<Vec<department_service::DepartmentInfo>>>, AppError> {
let res = department_service::list_all(&db, p.keyword).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct CreateReq { parent_id: Option<i64>, name: String, order_no: Option<i32>, status: Option<i32> }
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<department_service::DepartmentInfo>>, AppError> {
let res = department_service::create(&db, department_service::CreateDepartmentInput { parent_id: req.parent_id, name: req.name, order_no: req.order_no, status: req.status }).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct UpdateReq { parent_id: Option<i64>, name: Option<String>, order_no: Option<i32>, status: Option<i32> }
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<department_service::DepartmentInfo>>, AppError> {
let res = department_service::update(&db, id, department_service::UpdateDepartmentInput { parent_id: req.parent_id, name: req.name, order_no: req.order_no, status: req.status }).await?;
Ok(Json(ApiResponse::ok(res)))
}
async fn delete_department(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
department_service::delete(&db, id).await?;
Ok(Json(ApiResponse::ok(true)))
}

View File

@ -0,0 +1,9 @@
use axum::{Router, routing::get, extract::{Query, State}, Json};
use crate::{db::Db, response::ApiResponse, services::log_service, error::AppError};
pub fn router() -> Router<Db> { Router::new().route("/logs", get(list)) }
async fn list(State(db): State<Db>, Query(p): Query<log_service::ListParams>) -> Result<Json<ApiResponse<log_service::PageResp<log_service::LogInfo>>>, AppError> {
let res = log_service::list(&db, p).await.map_err(|e| AppError::Anyhow(anyhow::anyhow!(e)))?;
Ok(Json(ApiResponse::ok(res)))
}

View File

@ -0,0 +1,36 @@
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
use serde::Deserialize;
use crate::{db::Db, services::menu_service, response::ApiResponse, error::AppError};
#[derive(Deserialize)]
struct ListParams { keyword: Option<String> }
pub fn router() -> Router<Db> { Router::new()
.route("/menus", get(list).post(create))
.route("/menus/{id}", put(update).delete(delete_menu))
}
async fn list(State(db): State<Db>, Query(p): Query<ListParams>) -> Result<Json<ApiResponse<Vec<menu_service::MenuInfo>>>, AppError> {
let res = menu_service::list_all(&db, p.keyword).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct CreateReq { parent_id: Option<i64>, name: String, path: Option<String>, component: Option<String>, r#type: i32, icon: Option<String>, order_no: Option<i32>, visible: Option<bool>, status: Option<i32>, keep_alive: Option<bool>, perms: Option<String> }
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<menu_service::MenuInfo>>, AppError> {
let res = menu_service::create(&db, menu_service::CreateMenuInput { parent_id: req.parent_id, name: req.name, path: req.path, component: req.component, r#type: req.r#type, icon: req.icon, order_no: req.order_no, visible: req.visible, status: req.status, keep_alive: req.keep_alive, perms: req.perms }).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct UpdateReq { parent_id: Option<i64>, name: Option<String>, path: Option<String>, component: Option<String>, r#type: Option<i32>, icon: Option<String>, order_no: Option<i32>, visible: Option<bool>, status: Option<i32>, keep_alive: Option<bool>, perms: Option<String> }
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<menu_service::MenuInfo>>, AppError> {
let res = menu_service::update(&db, id, menu_service::UpdateMenuInput { parent_id: req.parent_id, name: req.name, path: req.path, component: req.component, r#type: req.r#type, icon: req.icon, order_no: req.order_no, visible: req.visible, status: req.status, keep_alive: req.keep_alive, perms: req.perms }).await?;
Ok(Json(ApiResponse::ok(res)))
}
async fn delete_menu(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
menu_service::delete(&db, id).await?;
Ok(Json(ApiResponse::ok(true)))
}

23
backend/src/routes/mod.rs Normal file
View File

@ -0,0 +1,23 @@
pub mod auth;
pub mod users;
pub mod roles;
pub mod menus;
pub mod departments;
pub mod logs;
// 新增岗位
pub mod positions;
use axum::Router;
use crate::db::Db;
pub fn api_router() -> Router<Db> {
Router::new()
.nest("/auth", auth::router())
.merge(users::router())
.merge(roles::router())
.merge(menus::router())
.merge(departments::router())
.merge(logs::router())
// 合并岗位路由
.merge(positions::router())
}

View File

@ -0,0 +1,38 @@
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
use serde::Deserialize;
use crate::{db::Db, services::position_service, response::ApiResponse, error::AppError};
#[derive(Deserialize)]
struct PageParams { page: Option<u64>, page_size: Option<u64>, keyword: Option<String> }
pub fn router() -> Router<Db> { Router::new()
.route("/positions", get(list).post(create))
.route("/positions/{id}", put(update).delete(delete_position))
}
async fn list(State(db): State<Db>, Query(p): Query<PageParams>) -> Result<Json<ApiResponse<position_service::PageResp<position_service::PositionInfo>>>, AppError> {
let page = p.page.unwrap_or(1); let page_size = p.page_size.unwrap_or(10);
let res = position_service::list(&db, page, page_size, p.keyword).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct CreateReq { name: String, code: String, description: Option<String>, status: Option<i32>, order_no: Option<i32> }
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<position_service::PositionInfo>>, AppError> {
let res = position_service::create(&db, position_service::CreatePositionInput { name: req.name, code: req.code, description: req.description, status: req.status, order_no: req.order_no }).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct UpdateReq { name: Option<String>, description: Option<String>, status: Option<i32>, order_no: Option<i32> }
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<position_service::PositionInfo>>, AppError> {
let res = position_service::update(&db, id, position_service::UpdatePositionInput { name: req.name, description: req.description, status: req.status, order_no: req.order_no }).await?;
Ok(Json(ApiResponse::ok(res)))
}
async fn delete_position(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
position_service::delete(&db, id).await?;
Ok(Json(ApiResponse::ok(true)))
}

View File

@ -0,0 +1,52 @@
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
use serde::Deserialize;
use crate::{db::Db, services::role_service, response::ApiResponse, error::AppError};
#[derive(Deserialize)]
struct PageParams { page: Option<u64>, page_size: Option<u64>, keyword: Option<String> }
pub fn router() -> Router<Db> { Router::new()
.route("/roles", get(list).post(create))
.route("/roles/{id}", put(update).delete(delete_role))
.route("/roles/{id}/menus", get(get_menus).put(set_menus))
}
async fn list(State(db): State<Db>, Query(p): Query<PageParams>) -> Result<Json<ApiResponse<role_service::PageResp<role_service::RoleInfo>>>, AppError> {
let page = p.page.unwrap_or(1); let page_size = p.page_size.unwrap_or(10);
let res = role_service::list(&db, page, page_size, p.keyword).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct CreateReq { name: String, code: String, description: Option<String>, status: Option<i32> }
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<role_service::RoleInfo>>, AppError> {
let res = role_service::create(&db, role_service::CreateRoleInput { name: req.name, code: req.code, description: req.description, status: req.status }).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct UpdateReq { name: Option<String>, description: Option<String>, status: Option<i32> }
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<role_service::RoleInfo>>, AppError> {
let res = role_service::update(&db, id, role_service::UpdateRoleInput { name: req.name, description: req.description, status: req.status }).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct IdsReq { ids: Vec<i64> }
async fn get_menus(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, AppError> {
let ids = role_service::get_menu_ids(&db, id).await?;
Ok(Json(ApiResponse::ok(ids)))
}
async fn set_menus(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, AppError> {
role_service::set_menu_ids(&db, id, req.ids).await?;
Ok(Json(ApiResponse::ok(true)))
}
async fn delete_role(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
role_service::delete(&db, id).await?;
Ok(Json(ApiResponse::ok(true)))
}

View File

@ -0,0 +1,88 @@
use axum::{Router, routing::{get, post, put}, extract::{Path, Query, State}, Json};
use serde::Deserialize;
use crate::{db::Db, services::{user_service, role_service, department_service, position_service}, response::ApiResponse};
#[derive(Deserialize)]
struct PageParams { page: Option<u64>, page_size: Option<u64>, keyword: Option<String> }
pub fn router() -> Router<Db> { Router::new()
.route("/users", get(list).post(create))
.route("/users/{id}", put(update).delete(delete_user))
.route("/users/{id}/reset_password", post(reset_password))
.route("/users/{id}/roles", get(get_roles).put(set_roles))
.route("/users/{id}/departments", get(get_departments).put(set_departments))
// 新增岗位关联
.route("/users/{id}/positions", get(get_positions).put(set_positions))
}
async fn list(State(db): State<Db>, Query(p): Query<PageParams>) -> Result<Json<ApiResponse<user_service::PageResp<user_service::UserInfo>>>, crate::error::AppError> {
let page = p.page.unwrap_or(1); let page_size = p.page_size.unwrap_or(10);
let res = user_service::list(&db, page, page_size, p.keyword).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct CreateReq { username: String, password: String, nickname: Option<String>, status: Option<i32> }
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<user_service::UserInfo>>, crate::error::AppError> {
let input = user_service::CreateUserInput { username: req.username, password: req.password, nickname: req.nickname, status: req.status };
let res = user_service::create(&db, input).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct UpdateReq { nickname: Option<String>, status: Option<i32> }
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<user_service::UserInfo>>, crate::error::AppError> {
let res = user_service::update(&db, id, user_service::UpdateUserInput { nickname: req.nickname, status: req.status }).await?;
Ok(Json(ApiResponse::ok(res)))
}
#[derive(Deserialize)]
struct IdsReq { ids: Vec<i64> }
async fn get_roles(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, crate::error::AppError> {
let ids = role_service::get_role_ids_by_user_id(&db, id).await?;
Ok(Json(ApiResponse::ok(ids)))
}
async fn set_roles(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
role_service::set_role_ids_by_user_id(&db, id, req.ids).await?;
Ok(Json(ApiResponse::ok(true)))
}
// 新增:用户部门分配
async fn get_departments(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, crate::error::AppError> {
let ids = department_service::get_department_ids_by_user_id(&db, id).await?;
Ok(Json(ApiResponse::ok(ids)))
}
async fn set_departments(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
department_service::set_department_ids_by_user_id(&db, id, req.ids).await?;
Ok(Json(ApiResponse::ok(true)))
}
// 新增:用户岗位分配
async fn get_positions(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, crate::error::AppError> {
let ids = position_service::get_position_ids_by_user_id(&db, id).await?;
Ok(Json(ApiResponse::ok(ids)))
}
async fn set_positions(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
position_service::set_position_ids_by_user_id(&db, id, req.ids).await?;
Ok(Json(ApiResponse::ok(true)))
}
#[derive(Deserialize)]
struct ResetReq { password: String }
async fn reset_password(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<ResetReq>) -> Result<Json<ApiResponse<()>>, crate::error::AppError> {
let input = user_service::ResetPasswordInput { password: req.password };
user_service::reset_password(&db, id, input).await?;
Ok(Json(ApiResponse::ok(())))
}
async fn delete_user(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
user_service::delete(&db, id).await?;
Ok(Json(ApiResponse::ok(true)))
}