v01
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
# UAdmin 配置文件
|
||||
server:
|
||||
host: "127.0.0.1"
|
||||
port: 3000
|
||||
port: 8080
|
||||
|
||||
database:
|
||||
url: "mysql://root:123456@127.0.0.1:3306/uadmin"
|
||||
@ -10,5 +10,5 @@ database:
|
||||
sqlx_logging: true
|
||||
|
||||
jwt:
|
||||
secret: "your-secret-key-here-please-change-in-production"
|
||||
secret: "test"
|
||||
expires_in: 86400 # 24 hours in seconds
|
||||
@ -1,7 +1,7 @@
|
||||
# 开发环境配置
|
||||
PORT=3001
|
||||
REACT_APP_ENV=development
|
||||
REACT_APP_API_URL=http://localhost:8080
|
||||
REACT_APP_API_URL=http://localhost:3001
|
||||
REACT_APP_API_TIMEOUT=10000
|
||||
REACT_APP_DEBUG=true
|
||||
GENERATE_SOURCEMAP=true
|
||||
|
||||
30
frontend/index.html
Normal file
30
frontend/index.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="UAdmin - 用户权限管理系统"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>UAdmin - 用户权限管理系统</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
15599
frontend/package-lock.json
generated
15599
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,12 +4,8 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.126",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
@ -18,24 +14,30 @@
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"env-cmd": "^10.1.0"
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^24.3.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^5.4.19"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start:dev": "env-cmd -f .env.development react-scripts start",
|
||||
"start:prod": "env-cmd -f .env.production react-scripts start",
|
||||
"start:test": "env-cmd -f .env.test react-scripts start",
|
||||
"build": "env-cmd -f .env.production react-scripts build",
|
||||
"build:dev": "env-cmd -f .env.development react-scripts build",
|
||||
"build:prod": "env-cmd -f .env.production react-scripts build",
|
||||
"build:test": "env-cmd -f .env.test react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"start": "vite",
|
||||
"dev": "env-cmd -f .env.development vite",
|
||||
"prod": "env-cmd -f .env.production vite",
|
||||
"start:test": "env-cmd -f .env.test vite",
|
||||
"build": "env-cmd -f .env.production vite build",
|
||||
"build:dev": "env-cmd -f .env.development vite build",
|
||||
"build:prod": "env-cmd -f .env.production vite build",
|
||||
"build:test": "env-cmd -f .env.test vite build",
|
||||
"test": "vitest",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { ConfigProvider, Spin, App as AntdApp } from 'antd';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import { AuthProvider, useAuth } from './hooks/useAuth';
|
||||
import Layout from './components/Layout';
|
||||
@ -13,12 +13,30 @@ import Permissions from './pages/Permissions';
|
||||
import Menus from './pages/Menus';
|
||||
import './App.css';
|
||||
|
||||
// 全局message配置
|
||||
import { message } from 'antd';
|
||||
message.config({
|
||||
top: 100,
|
||||
duration: 2,
|
||||
maxCount: 3,
|
||||
});
|
||||
|
||||
// 受保护的路由组件
|
||||
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '100vh',
|
||||
background: '#f0f2f5'
|
||||
}}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
@ -33,7 +51,17 @@ const PublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '100vh',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
||||
}}>
|
||||
<Spin size="large" style={{ color: 'white' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
@ -46,40 +74,42 @@ const PublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
function App() {
|
||||
return (
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* 公共路由 */}
|
||||
<Route path="/login" element={
|
||||
<PublicRoute>
|
||||
<Login />
|
||||
</PublicRoute>
|
||||
} />
|
||||
<Route path="/register" element={
|
||||
<PublicRoute>
|
||||
<Register />
|
||||
</PublicRoute>
|
||||
} />
|
||||
|
||||
{/* 受保护的路由 */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Layout />
|
||||
</ProtectedRoute>
|
||||
}>
|
||||
<Route index element={<Navigate to="/dashboard" replace />} />
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="users" element={<Users />} />
|
||||
<Route path="roles" element={<Roles />} />
|
||||
<Route path="permissions" element={<Permissions />} />
|
||||
<Route path="menus" element={<Menus />} />
|
||||
</Route>
|
||||
|
||||
{/* 404 重定向 */}
|
||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
<AntdApp>
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* 公共路由 */}
|
||||
<Route path="/login" element={
|
||||
<PublicRoute>
|
||||
<Login />
|
||||
</PublicRoute>
|
||||
} />
|
||||
<Route path="/register" element={
|
||||
<PublicRoute>
|
||||
<Register />
|
||||
</PublicRoute>
|
||||
} />
|
||||
|
||||
{/* 受保护的路由 */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Layout />
|
||||
</ProtectedRoute>
|
||||
}>
|
||||
<Route index element={<Navigate to="/dashboard" replace />} />
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="users" element={<Users />} />
|
||||
<Route path="roles" element={<Roles />} />
|
||||
<Route path="permissions" element={<Permissions />} />
|
||||
<Route path="menus" element={<Menus />} />
|
||||
</Route>
|
||||
|
||||
{/* 404 重定向 */}
|
||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
</AntdApp>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,8 +49,9 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
|
||||
const login = async (data: LoginRequest): Promise<boolean> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log('useAuth: 开始登录请求', data);
|
||||
const response = await apiService.login(data);
|
||||
console.log('useAuth: 登录响应', response);
|
||||
|
||||
setToken(response.token);
|
||||
setUser(response.user);
|
||||
@ -60,19 +61,26 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
localStorage.setItem('user', JSON.stringify(response.user));
|
||||
|
||||
message.success('登录成功');
|
||||
console.log('useAuth: 登录成功');
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Login failed:', error);
|
||||
message.error(error.response?.data?.message || '登录失败');
|
||||
console.error('useAuth: 登录失败', error);
|
||||
// 检查是否是apiService抛出的业务错误
|
||||
if (error.response?.data?.code && error.response.data.code !== 200) {
|
||||
message.error(error.response.data.message || '登录失败');
|
||||
} else if (error.response?.status === 401) {
|
||||
message.error('用户名或密码错误');
|
||||
} else if (error.message) {
|
||||
message.error(error.message);
|
||||
} else {
|
||||
message.error('登录失败');
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const register = async (data: RegisterRequest): Promise<boolean> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await apiService.register(data);
|
||||
message.success('注册成功,请登录');
|
||||
return true;
|
||||
@ -80,8 +88,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
console.error('Register failed:', error);
|
||||
message.error(error.response?.data?.message || '注册失败');
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Form, Input, Button, Card, Typography, message } from 'antd';
|
||||
import { Form, Input, Button, Card, Typography } from 'antd';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { useAuth } from '../hooks/useAuth';
|
||||
import { LoginRequest } from '../types';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import message from 'antd/es/message';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@ -15,12 +16,18 @@ const Login: React.FC = () => {
|
||||
const onFinish = async (values: LoginRequest) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('开始登录:', values);
|
||||
const success = await login(values);
|
||||
console.log('登录结果:', success);
|
||||
if (success) {
|
||||
console.log('登录成功,跳转到dashboard');
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
console.log('登录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
message.error('登录过程中发生错误');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ class ApiService {
|
||||
|
||||
constructor() {
|
||||
this.api = axios.create({
|
||||
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
|
||||
baseURL: '/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -53,10 +53,13 @@ class ApiService {
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token过期或无效,清除本地存储并跳转到登录页
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
// 如果不是登录请求,才清除token并跳转
|
||||
if (!error.config?.url?.includes('/auth/login')) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
// 对于登录请求的401错误,直接返回错误让组件处理
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@ -65,7 +68,13 @@ class ApiService {
|
||||
|
||||
// 认证相关API
|
||||
async login(data: LoginRequest): Promise<LoginResponse> {
|
||||
const response = await this.api.post<ApiResponse<LoginResponse>>('/auth/login', data);
|
||||
const response = await this.api.post<ApiResponse<LoginResponse>>("/auth/login", data);
|
||||
// 检查业务错误码
|
||||
if (response.data.code !== 200) {
|
||||
const error = new Error(response.data.message);
|
||||
(error as any).response = { data: response.data };
|
||||
throw error;
|
||||
}
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
|
||||
232
frontend/src/services/api.ts.bak
Normal file
232
frontend/src/services/api.ts.bak
Normal file
@ -0,0 +1,232 @@
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import {
|
||||
User,
|
||||
Role,
|
||||
Permission,
|
||||
Menu,
|
||||
CreateUserRequest,
|
||||
UpdateUserRequest,
|
||||
CreateRoleRequest,
|
||||
UpdateRoleRequest,
|
||||
CreatePermissionRequest,
|
||||
UpdatePermissionRequest,
|
||||
CreateMenuRequest,
|
||||
UpdateMenuRequest,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
RegisterRequest,
|
||||
ApiResponse,
|
||||
PaginatedResponse,
|
||||
QueryParams,
|
||||
} from '../types';
|
||||
|
||||
class ApiService {
|
||||
private api: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.api = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// 请求拦截器 - 添加认证token
|
||||
this.api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器 - 处理错误
|
||||
this.api.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// 如果不是登录请求,才清除token并跳转
|
||||
if (!error.config?.url?.includes('/auth/login')) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
// 对于登录请求的401错误,直接返回错误让组件处理
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 认证相关API
|
||||
async login(data: LoginRequest): Promise<LoginResponse> {
|
||||
const response = await this.api.post<ApiResponse<LoginResponse>>('/auth/login', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async register(data: RegisterRequest): Promise<User> {
|
||||
const response = await this.api.post<ApiResponse<User>>('/auth/register', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
await this.api.post('/auth/logout');
|
||||
}
|
||||
|
||||
// 用户管理API
|
||||
async getUsers(params?: QueryParams): Promise<PaginatedResponse<User>> {
|
||||
const response = await this.api.get('/users', { params });
|
||||
const backendData = response.data.data;
|
||||
// 转换后端数据结构到前端期望的结构
|
||||
return {
|
||||
data: backendData.items.map((user: any) => ({
|
||||
...user,
|
||||
status: user.status === 1 ? 'active' : 'inactive'
|
||||
})),
|
||||
total: backendData.total,
|
||||
page: backendData.page,
|
||||
per_page: backendData.page_size,
|
||||
total_pages: Math.ceil(backendData.total / backendData.page_size)
|
||||
};
|
||||
}
|
||||
|
||||
async getUser(id: number): Promise<User> {
|
||||
const response = await this.api.get<ApiResponse<User>>(`/users/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createUser(data: CreateUserRequest): Promise<User> {
|
||||
const response = await this.api.post<ApiResponse<User>>('/users', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updateUser(id: number, data: UpdateUserRequest): Promise<User> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<User>>(`/users/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deleteUser(id: number): Promise<void> {
|
||||
await this.api.delete(`/users/${id}`);
|
||||
}
|
||||
|
||||
// 角色管理API
|
||||
async getRoles(params?: QueryParams): Promise<PaginatedResponse<Role>> {
|
||||
const response = await this.api.get<ApiResponse<PaginatedResponse<Role>>>('/roles', { params });
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getAllRoles(): Promise<Role[]> {
|
||||
const response = await this.api.get<ApiResponse<Role[]>>('/roles/all');
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getRole(id: number): Promise<Role> {
|
||||
const response = await this.api.get<ApiResponse<Role>>(`/roles/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createRole(data: CreateRoleRequest): Promise<Role> {
|
||||
const response = await this.api.post<ApiResponse<Role>>('/roles', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updateRole(id: number, data: UpdateRoleRequest): Promise<Role> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<Role>>(`/roles/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deleteRole(id: number): Promise<void> {
|
||||
await this.api.delete(`/roles/${id}`);
|
||||
}
|
||||
|
||||
// 权限管理API
|
||||
async getPermissions(params?: QueryParams): Promise<PaginatedResponse<Permission>> {
|
||||
const response = await this.api.get<ApiResponse<PaginatedResponse<Permission>>>('/permissions', { params });
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getAllPermissions(): Promise<Permission[]> {
|
||||
const response = await this.api.get<ApiResponse<Permission[]>>('/permissions/all');
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getPermission(id: number): Promise<Permission> {
|
||||
const response = await this.api.get<ApiResponse<Permission>>(`/permissions/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createPermission(data: CreatePermissionRequest): Promise<Permission> {
|
||||
const response = await this.api.post<ApiResponse<Permission>>('/permissions', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updatePermission(id: number, data: UpdatePermissionRequest): Promise<Permission> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<Permission>>(`/permissions/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deletePermission(id: number): Promise<void> {
|
||||
await this.api.delete(`/permissions/${id}`);
|
||||
}
|
||||
|
||||
// 菜单管理API
|
||||
async getMenus(params?: QueryParams): Promise<PaginatedResponse<Menu>> {
|
||||
const response = await this.api.get<ApiResponse<PaginatedResponse<Menu>>>('/menus', { params });
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getAllMenus(): Promise<Menu[]> {
|
||||
const response = await this.api.get<ApiResponse<Menu[]>>('/menus/all');
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getMenu(id: number): Promise<Menu> {
|
||||
const response = await this.api.get<ApiResponse<Menu>>(`/menus/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createMenu(data: CreateMenuRequest): Promise<Menu> {
|
||||
const response = await this.api.post<ApiResponse<Menu>>('/menus', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updateMenu(id: number, data: UpdateMenuRequest): Promise<Menu> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<Menu>>(`/menus/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deleteMenu(id: number): Promise<void> {
|
||||
await this.api.delete(`/menus/${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const apiService = new ApiService();
|
||||
export default apiService;
|
||||
30
frontend/vite.config.ts
Normal file
30
frontend/vite.config.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3001,
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
configure: (proxy, options) => {
|
||||
proxy.on('proxyReq', (proxyReq, req, res) => {
|
||||
console.log('Proxying request:', req.method, req.url);
|
||||
console.log('Headers:', req.headers);
|
||||
});
|
||||
proxy.on('proxyRes', (proxyRes, req, res) => {
|
||||
console.log('Proxy response:', proxyRes.statusCode, 'for', req.url);
|
||||
});
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.log('Proxy error:', err.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -37,15 +37,21 @@ impl 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);
|
||||
match Self::from_file("config.yml") {
|
||||
Ok(config) => {
|
||||
tracing::info!("Successfully loaded config from config.yml: port={}", config.server.port);
|
||||
return Ok(config);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::info!("Failed to load config.yml: {}, using default config", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果config.yml不存在,使用默认配置
|
||||
Ok(Config {
|
||||
let default_config = Config {
|
||||
server: ServerConfig {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 3000,
|
||||
port: 3000, // 修改默认端口为3000
|
||||
},
|
||||
database: DatabaseConfig {
|
||||
url: "mysql://root:123456@127.0.0.1:3306/uadmin".to_string(),
|
||||
@ -57,6 +63,8 @@ impl Config {
|
||||
secret: "your-secret-key-here-please-change-in-production".to_string(),
|
||||
expires_in: 86400,
|
||||
},
|
||||
})
|
||||
};
|
||||
tracing::info!("Using default config: port={}", default_config.server.port);
|
||||
Ok(default_config)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set,
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::Utc;
|
||||
@ -12,6 +13,7 @@ use chrono::Utc;
|
||||
use crate::{
|
||||
models::{user, ApiResponse},
|
||||
utils::{jwt::generate_token, password::{hash_password, verify_password}},
|
||||
routes::AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -46,41 +48,63 @@ pub struct UserInfo {
|
||||
}
|
||||
|
||||
pub async fn login(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Json(payload): Json<LoginRequest>,
|
||||
) -> Result<Json<ApiResponse<AuthResponse>>, StatusCode> {
|
||||
) -> impl IntoResponse {
|
||||
// 查找用户
|
||||
let user = user::Entity::find()
|
||||
let user = match user::Entity::find()
|
||||
.filter(user::Column::Username.eq(&payload.username))
|
||||
.filter(user::Column::Status.eq(1))
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
{
|
||||
Ok(user) => user,
|
||||
Err(_) => {
|
||||
return Json(ApiResponse::<AuthResponse>::error(
|
||||
500,
|
||||
"数据库查询失败".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let user = match user {
|
||||
Some(user) => user,
|
||||
None => {
|
||||
return Ok(Json(ApiResponse::error(
|
||||
return Json(ApiResponse::<AuthResponse>::error(
|
||||
401,
|
||||
"用户名或密码错误".to_string(),
|
||||
)))
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// 验证密码
|
||||
let is_valid = verify_password(&payload.password, &user.password_hash)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let is_valid = match verify_password(&payload.password, &user.password_hash) {
|
||||
Ok(valid) => valid,
|
||||
Err(_) => {
|
||||
return Json(ApiResponse::<AuthResponse>::error(
|
||||
500,
|
||||
"密码验证失败".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
if !is_valid {
|
||||
return Ok(Json(ApiResponse::error(
|
||||
return Json(ApiResponse::<AuthResponse>::error(
|
||||
401,
|
||||
"用户名或密码错误".to_string(),
|
||||
)));
|
||||
));
|
||||
}
|
||||
|
||||
// 生成JWT token
|
||||
let token = generate_token(user.id, user.username.clone())
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let token = match generate_token(user.id, user.username.clone(), &app_state.config.jwt) {
|
||||
Ok(token) => token,
|
||||
Err(_) => {
|
||||
return Json(ApiResponse::<AuthResponse>::error(
|
||||
500,
|
||||
"Token生成失败".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let auth_response = AuthResponse {
|
||||
token,
|
||||
@ -94,17 +118,17 @@ pub async fn login(
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Json(ApiResponse::success(auth_response)))
|
||||
Json(ApiResponse::<AuthResponse>::success(auth_response))
|
||||
}
|
||||
|
||||
pub async fn register(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Json(payload): Json<RegisterRequest>,
|
||||
) -> Result<Json<ApiResponse<AuthResponse>>, StatusCode> {
|
||||
// 检查用户名是否已存在
|
||||
let existing_user = user::Entity::find()
|
||||
.filter(user::Column::Username.eq(&payload.username))
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -118,7 +142,7 @@ pub async fn register(
|
||||
// 检查邮箱是否已存在
|
||||
let existing_email = user::Entity::find()
|
||||
.filter(user::Column::Email.eq(&payload.email))
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -148,12 +172,12 @@ pub async fn register(
|
||||
};
|
||||
|
||||
let user = new_user
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 生成JWT token
|
||||
let token = generate_token(user.id, user.username.clone())
|
||||
let token = generate_token(user.id, user.username.clone(), &app_state.config.jwt)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let auth_response = AuthResponse {
|
||||
|
||||
@ -4,7 +4,7 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter,
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, QueryFilter,
|
||||
QueryOrder, Set,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@ -16,6 +16,7 @@ use crate::{
|
||||
menu::{self, CreateMenuRequest, UpdateMenuRequest, MenuTreeResponse},
|
||||
ApiResponse,
|
||||
},
|
||||
routes::AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -26,7 +27,7 @@ pub struct MenuQuery {
|
||||
}
|
||||
|
||||
pub async fn get_menus(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Query(params): Query<MenuQuery>,
|
||||
) -> Result<Json<ApiResponse<Vec<MenuTreeResponse>>>, StatusCode> {
|
||||
let mut query = menu::Entity::find();
|
||||
@ -44,7 +45,7 @@ pub async fn get_menus(
|
||||
|
||||
let menus = query
|
||||
.order_by_asc(menu::Column::SortOrder)
|
||||
.all(&db)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -55,13 +56,13 @@ pub async fn get_menus(
|
||||
}
|
||||
|
||||
pub async fn create_menu(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
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)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -92,7 +93,7 @@ pub async fn create_menu(
|
||||
};
|
||||
|
||||
let menu = new_menu
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -100,13 +101,13 @@ pub async fn create_menu(
|
||||
}
|
||||
|
||||
pub async fn update_menu(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
Json(payload): Json<UpdateMenuRequest>,
|
||||
) -> Result<Json<ApiResponse<menu::Model>>, StatusCode> {
|
||||
// 查找菜单
|
||||
let menu = menu::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -131,7 +132,7 @@ pub async fn update_menu(
|
||||
|
||||
// 检查父菜单是否存在
|
||||
let parent_menu = menu::Entity::find_by_id(parent_id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -179,7 +180,7 @@ pub async fn update_menu(
|
||||
menu.updated_at = Set(Utc::now());
|
||||
|
||||
let updated_menu = menu
|
||||
.update(&db)
|
||||
.update(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -187,12 +188,12 @@ pub async fn update_menu(
|
||||
}
|
||||
|
||||
pub async fn delete_menu(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
// 查找菜单
|
||||
let menu = menu::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -201,7 +202,7 @@ pub async fn delete_menu(
|
||||
// 检查是否有子菜单
|
||||
let children = menu::Entity::find()
|
||||
.filter(menu::Column::ParentId.eq(id))
|
||||
.all(&db)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -213,7 +214,7 @@ pub async fn delete_menu(
|
||||
}
|
||||
|
||||
// 删除菜单
|
||||
menu.delete(&db)
|
||||
menu.delete(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||
QueryOrder, Set,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@ -15,6 +15,7 @@ use crate::{
|
||||
permission::{self, CreatePermissionRequest, UpdatePermissionRequest, PermissionResponse},
|
||||
ApiResponse, PageResponse,
|
||||
},
|
||||
routes::AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -28,7 +29,7 @@ pub struct PermissionQuery {
|
||||
}
|
||||
|
||||
pub async fn get_permissions(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Query(params): Query<PermissionQuery>,
|
||||
) -> Result<Json<ApiResponse<PageResponse<PermissionResponse>>>, StatusCode> {
|
||||
let page = params.page.unwrap_or(1);
|
||||
@ -53,14 +54,14 @@ pub async fn get_permissions(
|
||||
// 获取总数
|
||||
let total = query
|
||||
.clone()
|
||||
.count(&db)
|
||||
.count(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 分页查询
|
||||
let permissions = query
|
||||
.order_by_desc(permission::Column::CreatedAt)
|
||||
.paginate(&db, page_size)
|
||||
.paginate(&app_state.db, page_size)
|
||||
.fetch_page(page - 1)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
@ -85,13 +86,13 @@ pub async fn get_permissions(
|
||||
}
|
||||
|
||||
pub async fn get_all_permissions(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
) -> 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)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -114,13 +115,13 @@ pub async fn get_all_permissions(
|
||||
}
|
||||
|
||||
pub async fn create_permission(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Json(payload): Json<CreatePermissionRequest>,
|
||||
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
|
||||
// 检查权限键是否已存在
|
||||
let existing_permission = permission::Entity::find()
|
||||
.filter(permission::Column::Key.eq(&payload.key))
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -146,7 +147,7 @@ pub async fn create_permission(
|
||||
};
|
||||
|
||||
let permission = new_permission
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -166,13 +167,13 @@ pub async fn create_permission(
|
||||
}
|
||||
|
||||
pub async fn update_permission(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
Json(payload): Json<UpdatePermissionRequest>,
|
||||
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
|
||||
// 查找权限
|
||||
let permission = permission::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -191,7 +192,7 @@ pub async fn update_permission(
|
||||
if key != &permission.key {
|
||||
let existing_permission = permission::Entity::find()
|
||||
.filter(permission::Column::Key.eq(key))
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -228,7 +229,7 @@ pub async fn update_permission(
|
||||
permission.updated_at = Set(Utc::now());
|
||||
|
||||
let updated_permission = permission
|
||||
.update(&db)
|
||||
.update(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -248,12 +249,12 @@ pub async fn update_permission(
|
||||
}
|
||||
|
||||
pub async fn delete_permission(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
// 查找权限
|
||||
let permission = permission::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -261,7 +262,7 @@ pub async fn delete_permission(
|
||||
Some(permission) => {
|
||||
// 删除权限(级联删除会自动处理关联表)
|
||||
permission
|
||||
.delete(&db)
|
||||
.delete(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||
QueryOrder, Set,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@ -15,6 +15,7 @@ use crate::{
|
||||
role::{self, CreateRoleRequest, UpdateRoleRequest, RoleResponse},
|
||||
role_permission, permission, ApiResponse, PageResponse,
|
||||
},
|
||||
routes::AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -26,7 +27,7 @@ pub struct RoleQuery {
|
||||
}
|
||||
|
||||
pub async fn get_roles(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Query(params): Query<RoleQuery>,
|
||||
) -> Result<Json<ApiResponse<PageResponse<RoleResponse>>>, StatusCode> {
|
||||
let page = params.page.unwrap_or(1);
|
||||
@ -45,24 +46,24 @@ pub async fn get_roles(
|
||||
// 获取总数
|
||||
let total = query
|
||||
.clone()
|
||||
.count(&db)
|
||||
.count(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 分页查询
|
||||
// 获取分页数据
|
||||
let roles = query
|
||||
.order_by_desc(role::Column::CreatedAt)
|
||||
.paginate(&db, page_size)
|
||||
.paginate(&app_state.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)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -78,27 +79,28 @@ pub async fn get_roles(
|
||||
}
|
||||
|
||||
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>,
|
||||
State(app_state): State<AppState>,
|
||||
Json(payload): Json<CreateRoleRequest>,
|
||||
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
|
||||
// 检查角色名是否已存在
|
||||
let existing_role = role::Entity::find()
|
||||
.filter(role::Column::Name.eq(&payload.name))
|
||||
.one(&db)
|
||||
.one(&app_state.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 {
|
||||
@ -109,13 +111,13 @@ pub async fn create_role(
|
||||
updated_at: Set(now),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
let role = new_role
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 分配权限
|
||||
|
||||
// 如果有权限ID,创建角色权限关联
|
||||
if let Some(permission_ids) = payload.permission_ids {
|
||||
for permission_id in permission_ids {
|
||||
let role_permission = role_permission::ActiveModel {
|
||||
@ -125,19 +127,19 @@ pub async fn create_role(
|
||||
..Default::default()
|
||||
};
|
||||
role_permission
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色权限信息
|
||||
|
||||
// 获取角色的权限信息
|
||||
let permissions = role
|
||||
.find_related(permission::Entity)
|
||||
.all(&db)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
|
||||
let role_response = RoleResponse {
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
@ -147,23 +149,22 @@ pub async fn create_role(
|
||||
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>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
Json(payload): Json<UpdateRoleRequest>,
|
||||
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
|
||||
// 查找角色
|
||||
let role = role::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let role = match role {
|
||||
Some(role) => role,
|
||||
|
||||
let mut role: role::ActiveModel = match role {
|
||||
Some(role) => role.into(),
|
||||
None => {
|
||||
return Ok(Json(ApiResponse::error(
|
||||
404,
|
||||
@ -171,9 +172,7 @@ pub async fn update_role(
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let mut role: role::ActiveModel = role.into();
|
||||
|
||||
|
||||
// 更新字段
|
||||
if let Some(name) = payload.name {
|
||||
role.name = Set(name);
|
||||
@ -185,22 +184,22 @@ pub async fn update_role(
|
||||
role.status = Set(status);
|
||||
}
|
||||
role.updated_at = Set(Utc::now());
|
||||
|
||||
|
||||
let updated_role = role
|
||||
.update(&db)
|
||||
.update(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 更新权限关联
|
||||
|
||||
// 如果有权限ID更新,先删除现有关联,再创建新关联
|
||||
if let Some(permission_ids) = payload.permission_ids {
|
||||
// 删除现有权限关联
|
||||
role_permission::Entity::delete_many()
|
||||
.filter(role_permission::Column::RoleId.eq(id))
|
||||
.exec(&db)
|
||||
.exec(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 添加新的权限关联
|
||||
|
||||
// 创建新的权限关联
|
||||
let now = Utc::now();
|
||||
for permission_id in permission_ids {
|
||||
let role_permission = role_permission::ActiveModel {
|
||||
@ -210,19 +209,19 @@ pub async fn update_role(
|
||||
..Default::default()
|
||||
};
|
||||
role_permission
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色权限信息
|
||||
|
||||
// 获取更新后的权限信息
|
||||
let permissions = updated_role
|
||||
.find_related(permission::Entity)
|
||||
.all(&db)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
|
||||
let role_response = RoleResponse {
|
||||
id: updated_role.id,
|
||||
name: updated_role.name,
|
||||
@ -232,27 +231,25 @@ pub async fn update_role(
|
||||
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>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
// 查找角色
|
||||
let role = role::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
|
||||
match role {
|
||||
Some(role) => {
|
||||
// 删除角色(级联删除会自动处理关联表)
|
||||
role.delete(&db)
|
||||
role.delete(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(Json(ApiResponse::success(())))
|
||||
}
|
||||
None => Ok(Json(ApiResponse::error(
|
||||
|
||||
@ -4,7 +4,7 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||
QueryOrder, Set,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@ -16,6 +16,7 @@ use crate::{
|
||||
user_role, role, ApiResponse, PageResponse,
|
||||
},
|
||||
utils::password::hash_password,
|
||||
routes::AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -28,7 +29,7 @@ pub struct UserQuery {
|
||||
}
|
||||
|
||||
pub async fn get_users(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Query(params): Query<UserQuery>,
|
||||
) -> Result<Json<ApiResponse<PageResponse<UserResponse>>>, StatusCode> {
|
||||
let page = params.page.unwrap_or(1);
|
||||
@ -50,14 +51,14 @@ pub async fn get_users(
|
||||
// 获取总数
|
||||
let total = query
|
||||
.clone()
|
||||
.count(&db)
|
||||
.count(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 分页查询
|
||||
let users = query
|
||||
.order_by_desc(user::Column::CreatedAt)
|
||||
.paginate(&db, page_size)
|
||||
.paginate(&app_state.db, page_size)
|
||||
.fetch_page(page - 1)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
@ -67,7 +68,7 @@ pub async fn get_users(
|
||||
for user in users {
|
||||
let roles = user
|
||||
.find_related(role::Entity)
|
||||
.all(&db)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -90,13 +91,13 @@ pub async fn get_users(
|
||||
}
|
||||
|
||||
pub async fn create_user(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Json(payload): Json<CreateUserRequest>,
|
||||
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
|
||||
// 检查用户名是否已存在
|
||||
let existing_user = user::Entity::find()
|
||||
.filter(user::Column::Username.eq(&payload.username))
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -110,7 +111,7 @@ pub async fn create_user(
|
||||
// 检查邮箱是否已存在
|
||||
let existing_email = user::Entity::find()
|
||||
.filter(user::Column::Email.eq(&payload.email))
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -140,7 +141,7 @@ pub async fn create_user(
|
||||
};
|
||||
|
||||
let user = new_user
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -154,7 +155,7 @@ pub async fn create_user(
|
||||
..Default::default()
|
||||
};
|
||||
user_role
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
}
|
||||
@ -163,7 +164,7 @@ pub async fn create_user(
|
||||
// 获取用户角色信息
|
||||
let roles = user
|
||||
.find_related(role::Entity)
|
||||
.all(&db)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -184,13 +185,13 @@ pub async fn create_user(
|
||||
}
|
||||
|
||||
pub async fn update_user(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
Json(payload): Json<UpdateUserRequest>,
|
||||
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
|
||||
// 查找用户
|
||||
let user = user::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -230,7 +231,7 @@ pub async fn update_user(
|
||||
user.updated_at = Set(Utc::now());
|
||||
|
||||
let updated_user = user
|
||||
.update(&db)
|
||||
.update(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -239,7 +240,7 @@ pub async fn update_user(
|
||||
// 删除现有角色关联
|
||||
user_role::Entity::delete_many()
|
||||
.filter(user_role::Column::UserId.eq(id))
|
||||
.exec(&db)
|
||||
.exec(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -253,7 +254,7 @@ pub async fn update_user(
|
||||
..Default::default()
|
||||
};
|
||||
user_role
|
||||
.insert(&db)
|
||||
.insert(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
}
|
||||
@ -262,7 +263,7 @@ pub async fn update_user(
|
||||
// 获取用户角色信息
|
||||
let roles = updated_user
|
||||
.find_related(role::Entity)
|
||||
.all(&db)
|
||||
.all(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@ -283,19 +284,19 @@ pub async fn update_user(
|
||||
}
|
||||
|
||||
pub async fn delete_user(
|
||||
State(db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
// 查找用户
|
||||
let user = user::Entity::find_by_id(id)
|
||||
.one(&db)
|
||||
.one(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
match user {
|
||||
Some(user) => {
|
||||
// 删除用户(级联删除会自动处理关联表)
|
||||
user.delete(&db)
|
||||
user.delete(&app_state.db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
|
||||
29
src/main.rs
29
src/main.rs
@ -9,7 +9,9 @@ mod utils;
|
||||
use std::env;
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber;
|
||||
use sea_orm::Database;
|
||||
use config::Config;
|
||||
use routes::AppState;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@ -28,19 +30,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
info!("Starting UAdmin API Server...");
|
||||
|
||||
// 加载配置
|
||||
info!("About to load configuration from config.yml...");
|
||||
let config = match Config::load() {
|
||||
Ok(config) => {
|
||||
info!("✅ Configuration loaded successfully: host={}, port={}", config.server.host, config.server.port);
|
||||
config
|
||||
}
|
||||
Err(e) => {
|
||||
info!("❌ Failed to load configuration: {}", e);
|
||||
panic!("Configuration loading failed: {}", e);
|
||||
}
|
||||
};
|
||||
info!("Configuration loading completed, proceeding with database connection...");
|
||||
|
||||
// 建立数据库连接
|
||||
let db = database::establish_connection().await?;
|
||||
let db = Database::connect(&config.database.url).await?;
|
||||
info!("Database connected successfully");
|
||||
|
||||
// 运行数据库迁移
|
||||
database::run_migrations(&db).await?;
|
||||
info!("Database migrations completed");
|
||||
|
||||
// 创建路由
|
||||
let app = routes::create_routes(db);
|
||||
// 创建应用状态
|
||||
let app_state = AppState {
|
||||
db,
|
||||
config: config.clone(),
|
||||
};
|
||||
|
||||
// 加载配置
|
||||
let config = Config::load().expect("Failed to load configuration");
|
||||
// 创建路由
|
||||
let app = routes::create_routes(app_state);
|
||||
let addr = format!("{}:{}", config.server.host, config.server.port);
|
||||
|
||||
info!("Server running on http://{}", addr);
|
||||
|
||||
@ -4,11 +4,13 @@ use axum::{
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use crate::utils::jwt::{extract_token_from_header, verify_token};
|
||||
use crate::{
|
||||
utils::jwt::{extract_token_from_header, verify_token},
|
||||
routes::AppState,
|
||||
};
|
||||
|
||||
pub async fn auth_middleware(
|
||||
State(_db): State<DatabaseConnection>,
|
||||
State(app_state): State<AppState>,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, StatusCode> {
|
||||
@ -46,7 +48,7 @@ pub async fn auth_middleware(
|
||||
};
|
||||
|
||||
// 验证JWT token
|
||||
match verify_token(token) {
|
||||
match verify_token(token, &app_state.config.jwt) {
|
||||
Ok(claims) => {
|
||||
// 将用户信息添加到请求扩展中
|
||||
request.extensions_mut().insert(claims);
|
||||
|
||||
@ -8,19 +8,31 @@ use tower_http::cors::{Any, CorsLayer};
|
||||
use crate::{
|
||||
handlers::{auth, user, role, menu, permission},
|
||||
middleware::auth::auth_middleware,
|
||||
config::config::Config,
|
||||
};
|
||||
|
||||
pub fn create_routes(db: DatabaseConnection) -> Router<()> {
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: DatabaseConnection,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
pub fn create_routes(app_state: AppState) -> Router<()> {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any);
|
||||
|
||||
Router::new()
|
||||
// 认证路由(无需认证)
|
||||
// 无需认证的路由
|
||||
let public_routes = Router::new()
|
||||
.route("/api/auth/login", post(auth::login))
|
||||
.route("/api/auth/register", post(auth::register))
|
||||
|
||||
.route("/", get(|| async { "UAdmin API Server is running!" }))
|
||||
.route("/health", get(|| async { "OK" }))
|
||||
.with_state(app_state.clone());
|
||||
|
||||
// 需要认证的路由
|
||||
let protected_routes = Router::new()
|
||||
// 用户管理路由
|
||||
.route("/api/users", get(user::get_users))
|
||||
.route("/api/users", post(user::create_user))
|
||||
@ -46,13 +58,13 @@ pub fn create_routes(db: DatabaseConnection) -> Router<()> {
|
||||
.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(axum::middleware::from_fn_with_state(app_state.clone(), auth_middleware))
|
||||
.with_state(app_state.clone());
|
||||
|
||||
// 合并路由并添加CORS
|
||||
Router::new()
|
||||
.merge(public_routes)
|
||||
.merge(protected_routes)
|
||||
.layer(cors)
|
||||
// 添加认证中间件(除了登录、注册和健康检查路由)
|
||||
.layer(axum::middleware::from_fn_with_state(db.clone(), auth_middleware))
|
||||
.with_state(db)
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use chrono::{Duration, Utc};
|
||||
use crate::config::config::JwtConfig;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Claims {
|
||||
@ -25,17 +25,15 @@ impl Claims {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_token(user_id: i32, username: String) -> Result<String, jsonwebtoken::errors::Error> {
|
||||
pub fn generate_token(user_id: i32, username: String, jwt_config: &JwtConfig) -> 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());
|
||||
let key = EncodingKey::from_secret(jwt_config.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());
|
||||
pub fn verify_token(token: &str, jwt_config: &JwtConfig) -> Result<Claims, jsonwebtoken::errors::Error> {
|
||||
let key = DecodingKey::from_secret(jwt_config.secret.as_ref());
|
||||
let validation = Validation::default();
|
||||
|
||||
decode::<Claims>(token, &key, &validation).map(|data| data.claims)
|
||||
|
||||
Reference in New Issue
Block a user