This commit is contained in:
2025-08-20 00:42:01 +08:00
commit 06bb5439b4
73 changed files with 30196 additions and 0 deletions

62
src/config/config.rs Normal file
View File

@ -0,0 +1,62 @@
use serde::{Deserialize, Serialize};
use std::fs;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Config {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub jwt: JwtConfig,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DatabaseConfig {
pub url: String,
pub max_connections: u32,
pub connect_timeout: u64,
pub sqlx_logging: bool,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct JwtConfig {
pub secret: String,
pub expires_in: u64,
}
impl Config {
pub fn from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
let config: Config = serde_yaml::from_str(&content)?;
Ok(config)
}
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
// 尝试从config.yml加载配置
if let Ok(config) = Self::from_file("config.yml") {
return Ok(config);
}
// 如果config.yml不存在使用默认配置
Ok(Config {
server: ServerConfig {
host: "127.0.0.1".to_string(),
port: 3000,
},
database: DatabaseConfig {
url: "mysql://root:123456@127.0.0.1:3306/uadmin".to_string(),
max_connections: 100,
connect_timeout: 8,
sqlx_logging: true,
},
jwt: JwtConfig {
secret: "your-secret-key-here-please-change-in-production".to_string(),
expires_in: 86400,
},
})
}
}

21
src/config/database.rs Normal file
View File

@ -0,0 +1,21 @@
use sea_orm::{
ConnectOptions, Database, DatabaseConnection, DbErr,
};
use std::time::Duration;
use crate::config::Config;
pub async fn establish_connection() -> Result<DatabaseConnection, DbErr> {
let config = Config::load().expect("Failed to load configuration");
let mut opt = ConnectOptions::new(config.database.url);
opt.max_connections(config.database.max_connections)
.min_connections(5)
.connect_timeout(Duration::from_secs(config.database.connect_timeout))
.acquire_timeout(Duration::from_secs(8))
.idle_timeout(Duration::from_secs(8))
.max_lifetime(Duration::from_secs(8))
.sqlx_logging(config.database.sqlx_logging)
.sqlx_logging_level(tracing::log::LevelFilter::Info);
Database::connect(opt).await
}

4
src/config/mod.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod database;
pub mod config;
pub use config::Config;

14
src/database.rs Normal file
View File

@ -0,0 +1,14 @@
use sea_orm::{Database, DatabaseConnection, DbErr};
use crate::config::Config;
pub async fn establish_connection() -> Result<DatabaseConnection, DbErr> {
let config = Config::load().expect("Failed to load configuration");
Database::connect(&config.database.url).await
}
pub async fn run_migrations(db: &DatabaseConnection) -> Result<(), DbErr> {
use migration::{Migrator, MigratorTrait};
Migrator::up(db, None).await
}

172
src/handlers/auth.rs Normal file
View File

@ -0,0 +1,172 @@
use axum::{
extract::State,
http::StatusCode,
Json,
};
use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set,
};
use serde::{Deserialize, Serialize};
use chrono::Utc;
use crate::{
models::{user, ApiResponse},
utils::{jwt::generate_token, password::{hash_password, verify_password}},
};
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
pub username: String,
pub email: String,
pub password: String,
pub nickname: Option<String>,
pub phone: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct AuthResponse {
pub token: String,
pub user: UserInfo,
}
#[derive(Debug, Serialize)]
pub struct UserInfo {
pub id: i32,
pub username: String,
pub email: String,
pub nickname: Option<String>,
pub avatar: Option<String>,
pub phone: Option<String>,
}
pub async fn login(
State(db): State<DatabaseConnection>,
Json(payload): Json<LoginRequest>,
) -> Result<Json<ApiResponse<AuthResponse>>, StatusCode> {
// 查找用户
let user = user::Entity::find()
.filter(user::Column::Username.eq(&payload.username))
.filter(user::Column::Status.eq(1))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let user = match user {
Some(user) => user,
None => {
return Ok(Json(ApiResponse::error(
401,
"用户名或密码错误".to_string(),
)))
}
};
// 验证密码
let is_valid = verify_password(&payload.password, &user.password_hash)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if !is_valid {
return Ok(Json(ApiResponse::error(
401,
"用户名或密码错误".to_string(),
)));
}
// 生成JWT token
let token = generate_token(user.id, user.username.clone())
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let auth_response = AuthResponse {
token,
user: UserInfo {
id: user.id,
username: user.username,
email: user.email,
nickname: user.nickname,
avatar: user.avatar,
phone: user.phone,
},
};
Ok(Json(ApiResponse::success(auth_response)))
}
pub async fn register(
State(db): State<DatabaseConnection>,
Json(payload): Json<RegisterRequest>,
) -> Result<Json<ApiResponse<AuthResponse>>, StatusCode> {
// 检查用户名是否已存在
let existing_user = user::Entity::find()
.filter(user::Column::Username.eq(&payload.username))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if existing_user.is_some() {
return Ok(Json(ApiResponse::error(
400,
"用户名已存在".to_string(),
)));
}
// 检查邮箱是否已存在
let existing_email = user::Entity::find()
.filter(user::Column::Email.eq(&payload.email))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if existing_email.is_some() {
return Ok(Json(ApiResponse::error(
400,
"邮箱已存在".to_string(),
)));
}
// 加密密码
let password_hash = hash_password(&payload.password)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 创建用户
let now = Utc::now();
let new_user = user::ActiveModel {
username: Set(payload.username.clone()),
email: Set(payload.email.clone()),
password_hash: Set(password_hash),
nickname: Set(payload.nickname.clone()),
phone: Set(payload.phone.clone()),
status: Set(1),
created_at: Set(now),
updated_at: Set(now),
..Default::default()
};
let user = new_user
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 生成JWT token
let token = generate_token(user.id, user.username.clone())
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let auth_response = AuthResponse {
token,
user: UserInfo {
id: user.id,
username: user.username,
email: user.email,
nickname: user.nickname,
avatar: user.avatar,
phone: user.phone,
},
};
Ok(Json(ApiResponse::success(auth_response)))
}

279
src/handlers/menu.rs Normal file
View File

@ -0,0 +1,279 @@
use axum::{
extract::{Path, Query, State},
http::StatusCode,
Json,
};
use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter,
QueryOrder, Set,
};
use serde::Deserialize;
use chrono::Utc;
use std::collections::HashMap;
use crate::{
models::{
menu::{self, CreateMenuRequest, UpdateMenuRequest, MenuTreeResponse},
ApiResponse,
},
};
#[derive(Debug, Deserialize)]
pub struct MenuQuery {
pub name: Option<String>,
pub status: Option<i8>,
pub menu_type: Option<i8>,
}
pub async fn get_menus(
State(db): State<DatabaseConnection>,
Query(params): Query<MenuQuery>,
) -> Result<Json<ApiResponse<Vec<MenuTreeResponse>>>, StatusCode> {
let mut query = menu::Entity::find();
// 添加过滤条件
if let Some(name) = params.name {
query = query.filter(menu::Column::Name.contains(&name));
}
if let Some(status) = params.status {
query = query.filter(menu::Column::Status.eq(status));
}
if let Some(menu_type) = params.menu_type {
query = query.filter(menu::Column::MenuType.eq(menu_type));
}
let menus = query
.order_by_asc(menu::Column::SortOrder)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 构建菜单树
let menu_tree = build_menu_tree(menus);
Ok(Json(ApiResponse::success(menu_tree)))
}
pub async fn create_menu(
State(db): State<DatabaseConnection>,
Json(payload): Json<CreateMenuRequest>,
) -> Result<Json<ApiResponse<menu::Model>>, StatusCode> {
// 如果有父菜单,检查父菜单是否存在
if let Some(parent_id) = payload.parent_id {
let parent_menu = menu::Entity::find_by_id(parent_id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if parent_menu.is_none() {
return Ok(Json(ApiResponse::error(
400,
"父菜单不存在".to_string(),
)));
}
}
// 创建菜单
let now = Utc::now();
let new_menu = menu::ActiveModel {
parent_id: Set(payload.parent_id),
name: Set(payload.name),
path: Set(payload.path),
component: Set(payload.component),
icon: Set(payload.icon),
sort_order: Set(payload.sort_order),
menu_type: Set(payload.menu_type),
visible: Set(payload.visible),
status: Set(1),
permission_key: Set(payload.permission_key),
created_at: Set(now),
updated_at: Set(now),
..Default::default()
};
let menu = new_menu
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ApiResponse::success(menu)))
}
pub async fn update_menu(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
Json(payload): Json<UpdateMenuRequest>,
) -> Result<Json<ApiResponse<menu::Model>>, StatusCode> {
// 查找菜单
let menu = menu::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let menu = match menu {
Some(menu) => menu,
None => {
return Ok(Json(ApiResponse::error(
404,
"菜单不存在".to_string(),
)))
}
};
// 如果更新父菜单,检查是否会形成循环引用
if let Some(parent_id) = payload.parent_id {
if parent_id == id {
return Ok(Json(ApiResponse::error(
400,
"不能将自己设为父菜单".to_string(),
)));
}
// 检查父菜单是否存在
let parent_menu = menu::Entity::find_by_id(parent_id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if parent_menu.is_none() {
return Ok(Json(ApiResponse::error(
400,
"父菜单不存在".to_string(),
)));
}
}
let mut menu: menu::ActiveModel = menu.into();
// 更新字段
if let Some(parent_id) = payload.parent_id {
menu.parent_id = Set(Some(parent_id));
}
if let Some(name) = payload.name {
menu.name = Set(name);
}
if let Some(path) = payload.path {
menu.path = Set(Some(path));
}
if let Some(component) = payload.component {
menu.component = Set(Some(component));
}
if let Some(icon) = payload.icon {
menu.icon = Set(Some(icon));
}
if let Some(sort_order) = payload.sort_order {
menu.sort_order = Set(sort_order);
}
if let Some(menu_type) = payload.menu_type {
menu.menu_type = Set(menu_type);
}
if let Some(visible) = payload.visible {
menu.visible = Set(visible);
}
if let Some(status) = payload.status {
menu.status = Set(status);
}
if let Some(permission_key) = payload.permission_key {
menu.permission_key = Set(Some(permission_key));
}
menu.updated_at = Set(Utc::now());
let updated_menu = menu
.update(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ApiResponse::success(updated_menu)))
}
pub async fn delete_menu(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
) -> Result<Json<ApiResponse<()>>, StatusCode> {
// 查找菜单
let menu = menu::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
match menu {
Some(menu) => {
// 检查是否有子菜单
let children = menu::Entity::find()
.filter(menu::Column::ParentId.eq(id))
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if !children.is_empty() {
return Ok(Json(ApiResponse::error(
400,
"请先删除子菜单".to_string(),
)));
}
// 删除菜单
menu.delete(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ApiResponse::success(())))
}
None => Ok(Json(ApiResponse::error(
404,
"菜单不存在".to_string(),
))),
}
}
// 构建菜单树的辅助函数
fn build_menu_tree(menus: Vec<menu::Model>) -> Vec<MenuTreeResponse> {
let mut menu_map: HashMap<i32, MenuTreeResponse> = HashMap::new();
let mut root_menus: Vec<MenuTreeResponse> = Vec::new();
// 将所有菜单转换为 MenuTreeResponse
for menu in menus {
let menu_response = MenuTreeResponse {
id: menu.id,
parent_id: menu.parent_id,
name: menu.name,
path: menu.path,
component: menu.component,
icon: menu.icon,
sort_order: menu.sort_order,
menu_type: menu.menu_type,
visible: menu.visible,
status: menu.status,
permission_key: menu.permission_key,
children: Vec::new(),
created_at: menu.created_at,
updated_at: menu.updated_at,
};
menu_map.insert(menu.id, menu_response);
}
// 构建树结构
let mut menu_map_clone = menu_map.clone();
for (_id, menu) in menu_map.iter() {
if let Some(parent_id) = menu.parent_id {
if let Some(parent) = menu_map_clone.get_mut(&parent_id) {
parent.children.push(menu.clone());
}
} else {
root_menus.push(menu.clone());
}
}
// 对每个层级的菜单按 sort_order 排序
sort_menu_tree(&mut root_menus);
root_menus
}
// 递归排序菜单树
fn sort_menu_tree(menus: &mut Vec<MenuTreeResponse>) {
menus.sort_by(|a, b| a.sort_order.cmp(&b.sort_order));
for menu in menus.iter_mut() {
sort_menu_tree(&mut menu.children);
}
}

5
src/handlers/mod.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod auth;
pub mod user;
pub mod role;
pub mod menu;
pub mod permission;

275
src/handlers/permission.rs Normal file
View File

@ -0,0 +1,275 @@
use axum::{
extract::{Path, Query, State},
http::StatusCode,
Json,
};
use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
QueryOrder, Set,
};
use serde::Deserialize;
use chrono::Utc;
use crate::{
models::{
permission::{self, CreatePermissionRequest, UpdatePermissionRequest, PermissionResponse},
ApiResponse, PageResponse,
},
};
#[derive(Debug, Deserialize)]
pub struct PermissionQuery {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub name: Option<String>,
pub resource: Option<String>,
pub action: Option<String>,
pub status: Option<i8>,
}
pub async fn get_permissions(
State(db): State<DatabaseConnection>,
Query(params): Query<PermissionQuery>,
) -> Result<Json<ApiResponse<PageResponse<PermissionResponse>>>, StatusCode> {
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(10);
let mut query = permission::Entity::find();
// 添加过滤条件
if let Some(name) = params.name {
query = query.filter(permission::Column::Name.contains(&name));
}
if let Some(resource) = params.resource {
query = query.filter(permission::Column::Resource.contains(&resource));
}
if let Some(action) = params.action {
query = query.filter(permission::Column::Action.contains(&action));
}
if let Some(status) = params.status {
query = query.filter(permission::Column::Status.eq(status));
}
// 获取总数
let total = query
.clone()
.count(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 分页查询
let permissions = query
.order_by_desc(permission::Column::CreatedAt)
.paginate(&db, page_size)
.fetch_page(page - 1)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let permission_responses: Vec<PermissionResponse> = permissions
.into_iter()
.map(|p| PermissionResponse {
id: p.id,
name: p.name,
key: p.key,
description: p.description,
resource: p.resource,
action: p.action,
status: p.status,
created_at: p.created_at,
updated_at: p.updated_at,
})
.collect();
let page_response = PageResponse::new(permission_responses, total, page, page_size);
Ok(Json(ApiResponse::success(page_response)))
}
pub async fn get_all_permissions(
State(db): State<DatabaseConnection>,
) -> Result<Json<ApiResponse<Vec<PermissionResponse>>>, StatusCode> {
let permissions = permission::Entity::find()
.filter(permission::Column::Status.eq(1))
.order_by_asc(permission::Column::Resource)
.order_by_asc(permission::Column::Action)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let permission_responses: Vec<PermissionResponse> = permissions
.into_iter()
.map(|p| PermissionResponse {
id: p.id,
name: p.name,
key: p.key,
description: p.description,
resource: p.resource,
action: p.action,
status: p.status,
created_at: p.created_at,
updated_at: p.updated_at,
})
.collect();
Ok(Json(ApiResponse::success(permission_responses)))
}
pub async fn create_permission(
State(db): State<DatabaseConnection>,
Json(payload): Json<CreatePermissionRequest>,
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
// 检查权限键是否已存在
let existing_permission = permission::Entity::find()
.filter(permission::Column::Key.eq(&payload.key))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if existing_permission.is_some() {
return Ok(Json(ApiResponse::error(
400,
"权限键已存在".to_string(),
)));
}
// 创建权限
let now = Utc::now();
let new_permission = permission::ActiveModel {
name: Set(payload.name),
key: Set(payload.key),
description: Set(payload.description),
resource: Set(payload.resource),
action: Set(payload.action),
status: Set(1),
created_at: Set(now),
updated_at: Set(now),
..Default::default()
};
let permission = new_permission
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let permission_response = PermissionResponse {
id: permission.id,
name: permission.name,
key: permission.key,
description: permission.description,
resource: permission.resource,
action: permission.action,
status: permission.status,
created_at: permission.created_at,
updated_at: permission.updated_at,
};
Ok(Json(ApiResponse::success(permission_response)))
}
pub async fn update_permission(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
Json(payload): Json<UpdatePermissionRequest>,
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
// 查找权限
let permission = permission::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let permission = match permission {
Some(permission) => permission,
None => {
return Ok(Json(ApiResponse::error(
404,
"权限不存在".to_string(),
)))
}
};
// 如果更新权限键,检查是否已存在
if let Some(ref key) = payload.key {
if key != &permission.key {
let existing_permission = permission::Entity::find()
.filter(permission::Column::Key.eq(key))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if existing_permission.is_some() {
return Ok(Json(ApiResponse::error(
400,
"权限键已存在".to_string(),
)));
}
}
}
let mut permission: permission::ActiveModel = permission.into();
// 更新字段
if let Some(name) = payload.name {
permission.name = Set(name);
}
if let Some(key) = payload.key {
permission.key = Set(key);
}
if let Some(description) = payload.description {
permission.description = Set(Some(description));
}
if let Some(resource) = payload.resource {
permission.resource = Set(resource);
}
if let Some(action) = payload.action {
permission.action = Set(action);
}
if let Some(status) = payload.status {
permission.status = Set(status);
}
permission.updated_at = Set(Utc::now());
let updated_permission = permission
.update(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let permission_response = PermissionResponse {
id: updated_permission.id,
name: updated_permission.name,
key: updated_permission.key,
description: updated_permission.description,
resource: updated_permission.resource,
action: updated_permission.action,
status: updated_permission.status,
created_at: updated_permission.created_at,
updated_at: updated_permission.updated_at,
};
Ok(Json(ApiResponse::success(permission_response)))
}
pub async fn delete_permission(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
) -> Result<Json<ApiResponse<()>>, StatusCode> {
// 查找权限
let permission = permission::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
match permission {
Some(permission) => {
// 删除权限(级联删除会自动处理关联表)
permission
.delete(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ApiResponse::success(())))
}
None => Ok(Json(ApiResponse::error(
404,
"权限不存在".to_string(),
))),
}
}

263
src/handlers/role.rs Normal file
View File

@ -0,0 +1,263 @@
use axum::{
extract::{Path, Query, State},
http::StatusCode,
Json,
};
use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
QueryOrder, Set,
};
use serde::Deserialize;
use chrono::Utc;
use crate::{
models::{
role::{self, CreateRoleRequest, UpdateRoleRequest, RoleResponse},
role_permission, permission, ApiResponse, PageResponse,
},
};
#[derive(Debug, Deserialize)]
pub struct RoleQuery {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub name: Option<String>,
pub status: Option<i8>,
}
pub async fn get_roles(
State(db): State<DatabaseConnection>,
Query(params): Query<RoleQuery>,
) -> Result<Json<ApiResponse<PageResponse<RoleResponse>>>, StatusCode> {
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(10);
let mut query = role::Entity::find();
// 添加过滤条件
if let Some(name) = params.name {
query = query.filter(role::Column::Name.contains(&name));
}
if let Some(status) = params.status {
query = query.filter(role::Column::Status.eq(status));
}
// 获取总数
let total = query
.clone()
.count(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 分页查询
let roles = query
.order_by_desc(role::Column::CreatedAt)
.paginate(&db, page_size)
.fetch_page(page - 1)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 获取角色权限信息
let mut role_responses = Vec::new();
for role in roles {
let permissions = role
.find_related(permission::Entity)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
role_responses.push(RoleResponse {
id: role.id,
name: role.name,
description: role.description,
status: role.status,
permissions,
created_at: role.created_at,
updated_at: role.updated_at,
});
}
let page_response = PageResponse::new(role_responses, total, page, page_size);
Ok(Json(ApiResponse::success(page_response)))
}
pub async fn create_role(
State(db): State<DatabaseConnection>,
Json(payload): Json<CreateRoleRequest>,
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
// 检查角色名是否已存在
let existing_role = role::Entity::find()
.filter(role::Column::Name.eq(&payload.name))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if existing_role.is_some() {
return Ok(Json(ApiResponse::error(
400,
"角色名已存在".to_string(),
)));
}
// 创建角色
let now = Utc::now();
let new_role = role::ActiveModel {
name: Set(payload.name),
description: Set(payload.description),
status: Set(1),
created_at: Set(now),
updated_at: Set(now),
..Default::default()
};
let role = new_role
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 分配权限
if let Some(permission_ids) = payload.permission_ids {
for permission_id in permission_ids {
let role_permission = role_permission::ActiveModel {
role_id: Set(role.id),
permission_id: Set(permission_id),
created_at: Set(now),
..Default::default()
};
role_permission
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}
}
// 获取角色权限信息
let permissions = role
.find_related(permission::Entity)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let role_response = RoleResponse {
id: role.id,
name: role.name,
description: role.description,
status: role.status,
permissions,
created_at: role.created_at,
updated_at: role.updated_at,
};
Ok(Json(ApiResponse::success(role_response)))
}
pub async fn update_role(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
Json(payload): Json<UpdateRoleRequest>,
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
// 查找角色
let role = role::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let role = match role {
Some(role) => role,
None => {
return Ok(Json(ApiResponse::error(
404,
"角色不存在".to_string(),
)))
}
};
let mut role: role::ActiveModel = role.into();
// 更新字段
if let Some(name) = payload.name {
role.name = Set(name);
}
if let Some(description) = payload.description {
role.description = Set(Some(description));
}
if let Some(status) = payload.status {
role.status = Set(status);
}
role.updated_at = Set(Utc::now());
let updated_role = role
.update(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 更新权限关联
if let Some(permission_ids) = payload.permission_ids {
// 删除现有权限关联
role_permission::Entity::delete_many()
.filter(role_permission::Column::RoleId.eq(id))
.exec(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 添加新的权限关联
let now = Utc::now();
for permission_id in permission_ids {
let role_permission = role_permission::ActiveModel {
role_id: Set(id),
permission_id: Set(permission_id),
created_at: Set(now),
..Default::default()
};
role_permission
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}
}
// 获取角色权限信息
let permissions = updated_role
.find_related(permission::Entity)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let role_response = RoleResponse {
id: updated_role.id,
name: updated_role.name,
description: updated_role.description,
status: updated_role.status,
permissions,
created_at: updated_role.created_at,
updated_at: updated_role.updated_at,
};
Ok(Json(ApiResponse::success(role_response)))
}
pub async fn delete_role(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
) -> Result<Json<ApiResponse<()>>, StatusCode> {
// 查找角色
let role = role::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
match role {
Some(role) => {
// 删除角色(级联删除会自动处理关联表)
role.delete(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ApiResponse::success(())))
}
None => Ok(Json(ApiResponse::error(
404,
"角色不存在".to_string(),
))),
}
}

309
src/handlers/user.rs Normal file
View File

@ -0,0 +1,309 @@
use axum::{
extract::{Path, Query, State},
http::StatusCode,
Json,
};
use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
QueryOrder, Set,
};
use serde::Deserialize;
use chrono::Utc;
use crate::{
models::{
user::{self, CreateUserRequest, UpdateUserRequest, UserResponse},
user_role, role, ApiResponse, PageResponse,
},
utils::password::hash_password,
};
#[derive(Debug, Deserialize)]
pub struct UserQuery {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub username: Option<String>,
pub email: Option<String>,
pub status: Option<i8>,
}
pub async fn get_users(
State(db): State<DatabaseConnection>,
Query(params): Query<UserQuery>,
) -> Result<Json<ApiResponse<PageResponse<UserResponse>>>, StatusCode> {
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(10);
let mut query = user::Entity::find();
// 添加过滤条件
if let Some(username) = params.username {
query = query.filter(user::Column::Username.contains(&username));
}
if let Some(email) = params.email {
query = query.filter(user::Column::Email.contains(&email));
}
if let Some(status) = params.status {
query = query.filter(user::Column::Status.eq(status));
}
// 获取总数
let total = query
.clone()
.count(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 分页查询
let users = query
.order_by_desc(user::Column::CreatedAt)
.paginate(&db, page_size)
.fetch_page(page - 1)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 获取用户角色信息
let mut user_responses = Vec::new();
for user in users {
let roles = user
.find_related(role::Entity)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
user_responses.push(UserResponse {
id: user.id,
username: user.username,
email: user.email,
nickname: user.nickname,
avatar: user.avatar,
phone: user.phone,
status: user.status,
roles,
created_at: user.created_at,
updated_at: user.updated_at,
});
}
let page_response = PageResponse::new(user_responses, total, page, page_size);
Ok(Json(ApiResponse::success(page_response)))
}
pub async fn create_user(
State(db): State<DatabaseConnection>,
Json(payload): Json<CreateUserRequest>,
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
// 检查用户名是否已存在
let existing_user = user::Entity::find()
.filter(user::Column::Username.eq(&payload.username))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if existing_user.is_some() {
return Ok(Json(ApiResponse::error(
400,
"用户名已存在".to_string(),
)));
}
// 检查邮箱是否已存在
let existing_email = user::Entity::find()
.filter(user::Column::Email.eq(&payload.email))
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
if existing_email.is_some() {
return Ok(Json(ApiResponse::error(
400,
"邮箱已存在".to_string(),
)));
}
// 加密密码
let password_hash = hash_password(&payload.password)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 创建用户
let now = Utc::now();
let new_user = user::ActiveModel {
username: Set(payload.username),
email: Set(payload.email),
password_hash: Set(password_hash),
nickname: Set(payload.nickname),
phone: Set(payload.phone),
status: Set(1),
created_at: Set(now),
updated_at: Set(now),
..Default::default()
};
let user = new_user
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 分配角色
if let Some(role_ids) = payload.role_ids {
for role_id in role_ids {
let user_role = user_role::ActiveModel {
user_id: Set(user.id),
role_id: Set(role_id),
created_at: Set(now),
..Default::default()
};
user_role
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}
}
// 获取用户角色信息
let roles = user
.find_related(role::Entity)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let user_response = UserResponse {
id: user.id,
username: user.username,
email: user.email,
nickname: user.nickname,
avatar: user.avatar,
phone: user.phone,
status: user.status,
roles,
created_at: user.created_at,
updated_at: user.updated_at,
};
Ok(Json(ApiResponse::success(user_response)))
}
pub async fn update_user(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
Json(payload): Json<UpdateUserRequest>,
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
// 查找用户
let user = user::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let user = match user {
Some(user) => user,
None => {
return Ok(Json(ApiResponse::error(
404,
"用户不存在".to_string(),
)))
}
};
let mut user: user::ActiveModel = user.into();
// 更新字段
if let Some(username) = payload.username {
user.username = Set(username);
}
if let Some(email) = payload.email {
user.email = Set(email);
}
if let Some(password) = payload.password {
let password_hash = hash_password(&password)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
user.password_hash = Set(password_hash);
}
if let Some(nickname) = payload.nickname {
user.nickname = Set(Some(nickname));
}
if let Some(phone) = payload.phone {
user.phone = Set(Some(phone));
}
if let Some(status) = payload.status {
user.status = Set(status);
}
user.updated_at = Set(Utc::now());
let updated_user = user
.update(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 更新角色关联
if let Some(role_ids) = payload.role_ids {
// 删除现有角色关联
user_role::Entity::delete_many()
.filter(user_role::Column::UserId.eq(id))
.exec(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 添加新的角色关联
let now = Utc::now();
for role_id in role_ids {
let user_role = user_role::ActiveModel {
user_id: Set(id),
role_id: Set(role_id),
created_at: Set(now),
..Default::default()
};
user_role
.insert(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}
}
// 获取用户角色信息
let roles = updated_user
.find_related(role::Entity)
.all(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let user_response = UserResponse {
id: updated_user.id,
username: updated_user.username,
email: updated_user.email,
nickname: updated_user.nickname,
avatar: updated_user.avatar,
phone: updated_user.phone,
status: updated_user.status,
roles,
created_at: updated_user.created_at,
updated_at: updated_user.updated_at,
};
Ok(Json(ApiResponse::success(user_response)))
}
pub async fn delete_user(
State(db): State<DatabaseConnection>,
Path(id): Path<i32>,
) -> Result<Json<ApiResponse<()>>, StatusCode> {
// 查找用户
let user = user::Entity::find_by_id(id)
.one(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
match user {
Some(user) => {
// 删除用户(级联删除会自动处理关联表)
user.delete(&db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ApiResponse::success(())))
}
None => Ok(Json(ApiResponse::error(
404,
"用户不存在".to_string(),
))),
}
}

53
src/main.rs Normal file
View File

@ -0,0 +1,53 @@
mod config;
mod database;
mod handlers;
mod middleware;
mod models;
mod routes;
mod utils;
use std::env;
use tracing::{info, Level};
use tracing_subscriber;
use config::Config;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 加载环境变量
dotenv::dotenv().ok();
// 初始化日志
let log_level = env::var("RUST_LOG")
.unwrap_or_else(|_| "info".to_string())
.parse::<Level>()
.unwrap_or(Level::INFO);
tracing_subscriber::fmt()
.with_max_level(log_level)
.init();
info!("Starting UAdmin API Server...");
// 建立数据库连接
let db = database::establish_connection().await?;
info!("Database connected successfully");
// 运行数据库迁移
database::run_migrations(&db).await?;
info!("Database migrations completed");
// 创建路由
let app = routes::create_routes(db);
// 加载配置
let config = Config::load().expect("Failed to load configuration");
let addr = format!("{}:{}", config.server.host, config.server.port);
info!("Server running on http://{}", addr);
// 启动服务器
let listener = tokio::net::TcpListener::bind(&addr).await?;
axum::serve(listener, app).await?;
Ok(())
}

57
src/middleware/auth.rs Normal file
View File

@ -0,0 +1,57 @@
use axum::{
extract::{Request, State},
http::StatusCode,
middleware::Next,
response::Response,
};
use sea_orm::DatabaseConnection;
use crate::utils::jwt::{extract_token_from_header, verify_token};
pub async fn auth_middleware(
State(_db): State<DatabaseConnection>,
mut request: Request,
next: Next,
) -> Result<Response, StatusCode> {
let headers = request.headers().clone();
// 跳过OPTIONS请求CORS预检请求
if request.method() == axum::http::Method::OPTIONS {
return Ok(next.run(request).await);
}
// 跳过认证的路径
let path = request.uri().path();
let skip_auth_paths = [
"/api/auth/login",
"/api/auth/register",
"/",
"/health",
];
if skip_auth_paths.contains(&path) {
return Ok(next.run(request).await);
}
// 获取Authorization头
let auth_header = headers
.get("authorization")
.and_then(|header| header.to_str().ok());
let token = match auth_header {
Some(header) => match extract_token_from_header(header) {
Some(token) => token,
None => return Err(StatusCode::UNAUTHORIZED),
},
None => return Err(StatusCode::UNAUTHORIZED),
};
// 验证JWT token
match verify_token(token) {
Ok(claims) => {
// 将用户信息添加到请求扩展中
request.extensions_mut().insert(claims);
Ok(next.run(request).await)
}
Err(_) => Err(StatusCode::UNAUTHORIZED),
}
}

1
src/middleware/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod auth;

94
src/models/menu.rs Normal file
View File

@ -0,0 +1,94 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "menus")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub parent_id: Option<i32>,
pub name: String,
pub path: Option<String>,
pub component: Option<String>,
pub icon: Option<String>,
pub sort_order: i32,
pub menu_type: i8, // 0: 目录, 1: 菜单, 2: 按钮
pub visible: i8, // 0: 隐藏, 1: 显示
pub status: i8, // 0: 禁用, 1: 启用
pub permission_key: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "Entity",
from = "Column::ParentId",
to = "Column::Id"
)]
Parent,
#[sea_orm(
has_many = "Entity",
from = "Column::Id",
to = "Column::ParentId"
)]
Children,
}
impl Related<Entity> for Entity {
fn to() -> RelationDef {
Relation::Parent.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
// 菜单创建请求
#[derive(Debug, Deserialize)]
pub struct CreateMenuRequest {
pub parent_id: Option<i32>,
pub name: String,
pub path: Option<String>,
pub component: Option<String>,
pub icon: Option<String>,
pub sort_order: i32,
pub menu_type: i8,
pub visible: i8,
pub permission_key: Option<String>,
}
// 菜单更新请求
#[derive(Debug, Deserialize)]
pub struct UpdateMenuRequest {
pub parent_id: Option<i32>,
pub name: Option<String>,
pub path: Option<String>,
pub component: Option<String>,
pub icon: Option<String>,
pub sort_order: Option<i32>,
pub menu_type: Option<i8>,
pub visible: Option<i8>,
pub status: Option<i8>,
pub permission_key: Option<String>,
}
// 菜单树响应
#[derive(Debug, Serialize, Clone)]
pub struct MenuTreeResponse {
pub id: i32,
pub parent_id: Option<i32>,
pub name: String,
pub path: Option<String>,
pub component: Option<String>,
pub icon: Option<String>,
pub sort_order: i32,
pub menu_type: i8,
pub visible: i8,
pub status: i8,
pub permission_key: Option<String>,
pub children: Vec<MenuTreeResponse>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

61
src/models/mod.rs Normal file
View File

@ -0,0 +1,61 @@
pub mod user;
pub mod role;
pub mod menu;
pub mod permission;
pub mod user_role;
pub mod role_permission;
// 重新导出所有实体
pub use user::Entity as User;
pub use role::Entity as Role;
pub use menu::Entity as Menu;
pub use permission::Entity as Permission;
pub use user_role::Entity as UserRole;
pub use role_permission::Entity as RolePermission;
// 通用响应结构
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct ApiResponse<T> {
pub code: i32,
pub message: String,
pub data: Option<T>,
}
impl<T> ApiResponse<T> {
pub fn success(data: T) -> Self {
Self {
code: 200,
message: "Success".to_string(),
data: Some(data),
}
}
pub fn error(code: i32, message: String) -> Self {
Self {
code,
message,
data: None,
}
}
}
#[derive(Debug, Serialize)]
pub struct PageResponse<T> {
pub items: Vec<T>,
pub total: u64,
pub page: u64,
pub page_size: u64,
}
impl<T> PageResponse<T> {
pub fn new(items: Vec<T>, total: u64, page: u64, page_size: u64) -> Self {
Self {
items,
total,
page,
page_size,
}
}
}

78
src/models/permission.rs Normal file
View File

@ -0,0 +1,78 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "permissions")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(unique)]
pub name: String,
#[sea_orm(unique)]
pub key: String,
pub description: Option<String>,
pub resource: String, // 资源标识
pub action: String, // 操作类型create, read, update, delete
pub status: i8, // 0: 禁用, 1: 启用
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::role_permission::Entity")]
RolePermissions,
}
impl Related<super::role_permission::Entity> for Entity {
fn to() -> RelationDef {
Relation::RolePermissions.def()
}
}
impl Related<super::role::Entity> for Entity {
fn to() -> RelationDef {
super::role_permission::Relation::Role.def()
}
fn via() -> Option<RelationDef> {
Some(super::role_permission::Relation::Permission.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}
// 权限创建请求
#[derive(Debug, Deserialize)]
pub struct CreatePermissionRequest {
pub name: String,
pub key: String,
pub description: Option<String>,
pub resource: String,
pub action: String,
}
// 权限更新请求
#[derive(Debug, Deserialize)]
pub struct UpdatePermissionRequest {
pub name: Option<String>,
pub key: Option<String>,
pub description: Option<String>,
pub resource: Option<String>,
pub action: Option<String>,
pub status: Option<i8>,
}
// 权限响应
#[derive(Debug, Serialize)]
pub struct PermissionResponse {
pub id: i32,
pub name: String,
pub key: String,
pub description: Option<String>,
pub resource: String,
pub action: String,
pub status: i8,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

85
src/models/role.rs Normal file
View File

@ -0,0 +1,85 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "roles")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(unique)]
pub name: String,
pub description: Option<String>,
pub status: i8, // 0: 禁用, 1: 启用
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::user_role::Entity")]
UserRoles,
#[sea_orm(has_many = "super::role_permission::Entity")]
RolePermissions,
}
impl Related<super::user_role::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserRoles.def()
}
}
impl Related<super::role_permission::Entity> for Entity {
fn to() -> RelationDef {
Relation::RolePermissions.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
super::user_role::Relation::User.def()
}
fn via() -> Option<RelationDef> {
Some(super::user_role::Relation::Role.def().rev())
}
}
impl Related<super::permission::Entity> for Entity {
fn to() -> RelationDef {
super::role_permission::Relation::Permission.def()
}
fn via() -> Option<RelationDef> {
Some(super::role_permission::Relation::Role.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}
// 角色创建请求
#[derive(Debug, Deserialize)]
pub struct CreateRoleRequest {
pub name: String,
pub description: Option<String>,
pub permission_ids: Option<Vec<i32>>,
}
// 角色更新请求
#[derive(Debug, Deserialize)]
pub struct UpdateRoleRequest {
pub name: Option<String>,
pub description: Option<String>,
pub status: Option<i8>,
pub permission_ids: Option<Vec<i32>>,
}
// 角色响应
#[derive(Debug, Serialize)]
pub struct RoleResponse {
pub id: i32,
pub name: String,
pub description: Option<String>,
pub status: i8,
pub permissions: Vec<super::permission::Model>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

View File

@ -0,0 +1,47 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "role_permissions")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub role_id: i32,
pub permission_id: i32,
pub created_at: DateTime<Utc>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::role::Entity",
from = "Column::RoleId",
to = "super::role::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
Role,
#[sea_orm(
belongs_to = "super::permission::Entity",
from = "Column::PermissionId",
to = "super::permission::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
Permission,
}
impl Related<super::role::Entity> for Entity {
fn to() -> RelationDef {
Relation::Role.def()
}
}
impl Related<super::permission::Entity> for Entity {
fn to() -> RelationDef {
Relation::Permission.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

82
src/models/user.rs Normal file
View File

@ -0,0 +1,82 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(unique)]
pub username: String,
#[sea_orm(unique)]
pub email: String,
pub password_hash: String,
pub nickname: Option<String>,
pub avatar: Option<String>,
pub phone: Option<String>,
pub status: i8, // 0: 禁用, 1: 启用
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::user_role::Entity")]
UserRoles,
}
impl Related<super::user_role::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserRoles.def()
}
}
impl Related<super::role::Entity> for Entity {
fn to() -> RelationDef {
super::user_role::Relation::Role.def()
}
fn via() -> Option<RelationDef> {
Some(super::user_role::Relation::User.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}
// 用户创建请求
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub email: String,
pub password: String,
pub nickname: Option<String>,
pub phone: Option<String>,
pub role_ids: Option<Vec<i32>>,
}
// 用户更新请求
#[derive(Debug, Deserialize)]
pub struct UpdateUserRequest {
pub username: Option<String>,
pub email: Option<String>,
pub password: Option<String>,
pub nickname: Option<String>,
pub phone: Option<String>,
pub status: Option<i8>,
pub role_ids: Option<Vec<i32>>,
}
// 用户响应
#[derive(Debug, Serialize)]
pub struct UserResponse {
pub id: i32,
pub username: String,
pub email: String,
pub nickname: Option<String>,
pub avatar: Option<String>,
pub phone: Option<String>,
pub status: i8,
pub roles: Vec<super::role::Model>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

47
src/models/user_role.rs Normal file
View File

@ -0,0 +1,47 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user_roles")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub user_id: i32,
pub role_id: i32,
pub created_at: DateTime<Utc>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
User,
#[sea_orm(
belongs_to = "super::role::Entity",
from = "Column::RoleId",
to = "super::role::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
Role,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::role::Entity> for Entity {
fn to() -> RelationDef {
Relation::Role.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

58
src/routes.rs Normal file
View File

@ -0,0 +1,58 @@
use axum::{
routing::{delete, get, post, put},
Router,
};
use sea_orm::DatabaseConnection;
use tower_http::cors::{Any, CorsLayer};
use crate::{
handlers::{auth, user, role, menu, permission},
middleware::auth::auth_middleware,
};
pub fn create_routes(db: DatabaseConnection) -> Router<()> {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
Router::new()
// 认证路由(无需认证)
.route("/api/auth/login", post(auth::login))
.route("/api/auth/register", post(auth::register))
// 用户管理路由
.route("/api/users", get(user::get_users))
.route("/api/users", post(user::create_user))
.route("/api/users/:id", put(user::update_user))
.route("/api/users/:id", delete(user::delete_user))
// 角色管理路由
.route("/api/roles", get(role::get_roles))
.route("/api/roles", post(role::create_role))
.route("/api/roles/:id", put(role::update_role))
.route("/api/roles/:id", delete(role::delete_role))
// 菜单管理路由
.route("/api/menus", get(menu::get_menus))
.route("/api/menus", post(menu::create_menu))
.route("/api/menus/:id", put(menu::update_menu))
.route("/api/menus/:id", delete(menu::delete_menu))
// 权限管理路由
.route("/api/permissions", get(permission::get_permissions))
.route("/api/permissions/all", get(permission::get_all_permissions))
.route("/api/permissions", post(permission::create_permission))
.route("/api/permissions/:id", put(permission::update_permission))
.route("/api/permissions/:id", delete(permission::delete_permission))
// 健康检查
.route("/", get(|| async { "UAdmin API Server is running!" }))
.route("/health", get(|| async { "OK" }))
// 添加CORS中间件
.layer(cors)
// 添加认证中间件(除了登录、注册和健康检查路由)
.layer(axum::middleware::from_fn_with_state(db.clone(), auth_middleware))
.with_state(db)
}

50
src/utils/jwt.rs Normal file
View File

@ -0,0 +1,50 @@
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::env;
use chrono::{Duration, Utc};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
pub sub: String, // 用户ID
pub username: String,
pub exp: usize, // 过期时间
pub iat: usize, // 签发时间
}
impl Claims {
pub fn new(user_id: i32, username: String) -> Self {
let now = Utc::now();
let exp = now + Duration::hours(24); // 24小时过期
Self {
sub: user_id.to_string(),
username,
exp: exp.timestamp() as usize,
iat: now.timestamp() as usize,
}
}
}
pub fn generate_token(user_id: i32, username: String) -> Result<String, jsonwebtoken::errors::Error> {
let claims = Claims::new(user_id, username);
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
let key = EncodingKey::from_secret(secret.as_ref());
encode(&Header::default(), &claims, &key)
}
pub fn verify_token(token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
let key = DecodingKey::from_secret(secret.as_ref());
let validation = Validation::default();
decode::<Claims>(token, &key, &validation).map(|data| data.claims)
}
pub fn extract_token_from_header(auth_header: &str) -> Option<&str> {
if auth_header.starts_with("Bearer ") {
Some(&auth_header[7..])
} else {
None
}
}

2
src/utils/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod jwt;
pub mod password;

9
src/utils/password.rs Normal file
View File

@ -0,0 +1,9 @@
use bcrypt::{hash, verify, DEFAULT_COST};
pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> {
hash(password, DEFAULT_COST)
}
pub fn verify_password(password: &str, hash: &str) -> Result<bool, bcrypt::BcryptError> {
verify(password, hash)
}