init
This commit is contained in:
62
src/config/config.rs
Normal file
62
src/config/config.rs
Normal 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
21
src/config/database.rs
Normal 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
4
src/config/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod database;
|
||||
pub mod config;
|
||||
|
||||
pub use config::Config;
|
||||
14
src/database.rs
Normal file
14
src/database.rs
Normal 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
172
src/handlers/auth.rs
Normal 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
279
src/handlers/menu.rs
Normal 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
5
src/handlers/mod.rs
Normal 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
275
src/handlers/permission.rs
Normal 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
263
src/handlers/role.rs
Normal 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
309
src/handlers/user.rs
Normal 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
53
src/main.rs
Normal 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
57
src/middleware/auth.rs
Normal 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
1
src/middleware/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod auth;
|
||||
94
src/models/menu.rs
Normal file
94
src/models/menu.rs
Normal 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
61
src/models/mod.rs
Normal 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
78
src/models/permission.rs
Normal 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
85
src/models/role.rs
Normal 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>,
|
||||
}
|
||||
47
src/models/role_permission.rs
Normal file
47
src/models/role_permission.rs
Normal 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
82
src/models/user.rs
Normal 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
47
src/models/user_role.rs
Normal 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
58
src/routes.rs
Normal 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
50
src/utils/jwt.rs
Normal 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
2
src/utils/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod jwt;
|
||||
pub mod password;
|
||||
9
src/utils/password.rs
Normal file
9
src/utils/password.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user