v01
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
# UAdmin 配置文件
|
# UAdmin 配置文件
|
||||||
server:
|
server:
|
||||||
host: "127.0.0.1"
|
host: "127.0.0.1"
|
||||||
port: 3000
|
port: 8080
|
||||||
|
|
||||||
database:
|
database:
|
||||||
url: "mysql://root:123456@127.0.0.1:3306/uadmin"
|
url: "mysql://root:123456@127.0.0.1:3306/uadmin"
|
||||||
@ -10,5 +10,5 @@ database:
|
|||||||
sqlx_logging: true
|
sqlx_logging: true
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: "your-secret-key-here-please-change-in-production"
|
secret: "test"
|
||||||
expires_in: 86400 # 24 hours in seconds
|
expires_in: 86400 # 24 hours in seconds
|
||||||
@ -1,7 +1,7 @@
|
|||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
PORT=3001
|
PORT=3001
|
||||||
REACT_APP_ENV=development
|
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_API_TIMEOUT=10000
|
||||||
REACT_APP_DEBUG=true
|
REACT_APP_DEBUG=true
|
||||||
GENERATE_SOURCEMAP=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,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||||
"@testing-library/jest-dom": "^6.7.0",
|
|
||||||
"@testing-library/react": "^16.3.0",
|
|
||||||
"@testing-library/user-event": "^13.5.0",
|
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/node": "^16.18.126",
|
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
@ -18,24 +14,30 @@
|
|||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router-dom": "^7.8.1",
|
"react-router-dom": "^7.8.1",
|
||||||
"react-scripts": "5.0.1",
|
|
||||||
"typescript": "^4.9.5",
|
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "vite",
|
||||||
"start:dev": "env-cmd -f .env.development react-scripts start",
|
"dev": "env-cmd -f .env.development vite",
|
||||||
"start:prod": "env-cmd -f .env.production react-scripts start",
|
"prod": "env-cmd -f .env.production vite",
|
||||||
"start:test": "env-cmd -f .env.test react-scripts start",
|
"start:test": "env-cmd -f .env.test vite",
|
||||||
"build": "env-cmd -f .env.production react-scripts build",
|
"build": "env-cmd -f .env.production vite build",
|
||||||
"build:dev": "env-cmd -f .env.development react-scripts build",
|
"build:dev": "env-cmd -f .env.development vite build",
|
||||||
"build:prod": "env-cmd -f .env.production react-scripts build",
|
"build:prod": "env-cmd -f .env.production vite build",
|
||||||
"build:test": "env-cmd -f .env.test react-scripts build",
|
"build:test": "env-cmd -f .env.test vite build",
|
||||||
"test": "react-scripts test",
|
"test": "vitest",
|
||||||
"eject": "react-scripts eject"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
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 zhCN from 'antd/locale/zh_CN';
|
||||||
import { AuthProvider, useAuth } from './hooks/useAuth';
|
import { AuthProvider, useAuth } from './hooks/useAuth';
|
||||||
import Layout from './components/Layout';
|
import Layout from './components/Layout';
|
||||||
@ -13,12 +13,30 @@ import Permissions from './pages/Permissions';
|
|||||||
import Menus from './pages/Menus';
|
import Menus from './pages/Menus';
|
||||||
import './App.css';
|
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 ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const { user, loading } = useAuth();
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
if (loading) {
|
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) {
|
if (!user) {
|
||||||
@ -33,7 +51,17 @@ const PublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
const { user, loading } = useAuth();
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
if (loading) {
|
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) {
|
if (user) {
|
||||||
@ -46,6 +74,7 @@ const PublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<ConfigProvider locale={zhCN}>
|
<ConfigProvider locale={zhCN}>
|
||||||
|
<AntdApp>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
@ -80,6 +109,7 @@ function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
</AntdApp>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,8 +49,9 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
|
|
||||||
const login = async (data: LoginRequest): Promise<boolean> => {
|
const login = async (data: LoginRequest): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
console.log('useAuth: 开始登录请求', data);
|
||||||
const response = await apiService.login(data);
|
const response = await apiService.login(data);
|
||||||
|
console.log('useAuth: 登录响应', response);
|
||||||
|
|
||||||
setToken(response.token);
|
setToken(response.token);
|
||||||
setUser(response.user);
|
setUser(response.user);
|
||||||
@ -60,19 +61,26 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
localStorage.setItem('user', JSON.stringify(response.user));
|
localStorage.setItem('user', JSON.stringify(response.user));
|
||||||
|
|
||||||
message.success('登录成功');
|
message.success('登录成功');
|
||||||
|
console.log('useAuth: 登录成功');
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Login failed:', error);
|
console.error('useAuth: 登录失败', error);
|
||||||
message.error(error.response?.data?.message || '登录失败');
|
// 检查是否是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;
|
return false;
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = async (data: RegisterRequest): Promise<boolean> => {
|
const register = async (data: RegisterRequest): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
|
||||||
await apiService.register(data);
|
await apiService.register(data);
|
||||||
message.success('注册成功,请登录');
|
message.success('注册成功,请登录');
|
||||||
return true;
|
return true;
|
||||||
@ -80,8 +88,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
console.error('Register failed:', error);
|
console.error('Register failed:', error);
|
||||||
message.error(error.response?.data?.message || '注册失败');
|
message.error(error.response?.data?.message || '注册失败');
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import '@ant-design/v5-patch-for-react-19';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
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 { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||||
import { useAuth } from '../hooks/useAuth';
|
import { useAuth } from '../hooks/useAuth';
|
||||||
import { LoginRequest } from '../types';
|
import { LoginRequest } from '../types';
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom';
|
||||||
|
import message from 'antd/es/message';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
@ -15,12 +16,18 @@ const Login: React.FC = () => {
|
|||||||
const onFinish = async (values: LoginRequest) => {
|
const onFinish = async (values: LoginRequest) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
|
console.log('开始登录:', values);
|
||||||
const success = await login(values);
|
const success = await login(values);
|
||||||
|
console.log('登录结果:', success);
|
||||||
if (success) {
|
if (success) {
|
||||||
|
console.log('登录成功,跳转到dashboard');
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
|
} else {
|
||||||
|
console.log('登录失败');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login error:', error);
|
console.error('Login error:', error);
|
||||||
|
message.error('登录过程中发生错误');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class ApiService {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.api = axios.create({
|
this.api = axios.create({
|
||||||
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
|
baseURL: '/api',
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -53,11 +53,14 @@ class ApiService {
|
|||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
// Token过期或无效,清除本地存储并跳转到登录页
|
// 如果不是登录请求,才清除token并跳转
|
||||||
|
if (!error.config?.url?.includes('/auth/login')) {
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
}
|
}
|
||||||
|
// 对于登录请求的401错误,直接返回错误让组件处理
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -65,7 +68,13 @@ class ApiService {
|
|||||||
|
|
||||||
// 认证相关API
|
// 认证相关API
|
||||||
async login(data: LoginRequest): Promise<LoginResponse> {
|
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!;
|
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>> {
|
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
// 尝试从config.yml加载配置
|
// 尝试从config.yml加载配置
|
||||||
if let Ok(config) = Self::from_file("config.yml") {
|
match Self::from_file("config.yml") {
|
||||||
|
Ok(config) => {
|
||||||
|
tracing::info!("Successfully loaded config from config.yml: port={}", config.server.port);
|
||||||
return Ok(config);
|
return Ok(config);
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::info!("Failed to load config.yml: {}, using default config", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果config.yml不存在,使用默认配置
|
// 如果config.yml不存在,使用默认配置
|
||||||
Ok(Config {
|
let default_config = Config {
|
||||||
server: ServerConfig {
|
server: ServerConfig {
|
||||||
host: "127.0.0.1".to_string(),
|
host: "127.0.0.1".to_string(),
|
||||||
port: 3000,
|
port: 3000, // 修改默认端口为3000
|
||||||
},
|
},
|
||||||
database: DatabaseConfig {
|
database: DatabaseConfig {
|
||||||
url: "mysql://root:123456@127.0.0.1:3306/uadmin".to_string(),
|
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(),
|
secret: "your-secret-key-here-please-change-in-production".to_string(),
|
||||||
expires_in: 86400,
|
expires_in: 86400,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
tracing::info!("Using default config: port={}", default_config.server.port);
|
||||||
|
Ok(default_config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::State,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set,
|
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@ -12,6 +13,7 @@ use chrono::Utc;
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::{user, ApiResponse},
|
models::{user, ApiResponse},
|
||||||
utils::{jwt::generate_token, password::{hash_password, verify_password}},
|
utils::{jwt::generate_token, password::{hash_password, verify_password}},
|
||||||
|
routes::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -46,41 +48,63 @@ pub struct UserInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Json(payload): Json<LoginRequest>,
|
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::Username.eq(&payload.username))
|
||||||
.filter(user::Column::Status.eq(1))
|
.filter(user::Column::Status.eq(1))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
{
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(_) => {
|
||||||
|
return Json(ApiResponse::<AuthResponse>::error(
|
||||||
|
500,
|
||||||
|
"数据库查询失败".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let user = match user {
|
let user = match user {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => {
|
None => {
|
||||||
return Ok(Json(ApiResponse::error(
|
return Json(ApiResponse::<AuthResponse>::error(
|
||||||
401,
|
401,
|
||||||
"用户名或密码错误".to_string(),
|
"用户名或密码错误".to_string(),
|
||||||
)))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证密码
|
// 验证密码
|
||||||
let is_valid = verify_password(&payload.password, &user.password_hash)
|
let is_valid = match verify_password(&payload.password, &user.password_hash) {
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
Ok(valid) => valid,
|
||||||
|
Err(_) => {
|
||||||
|
return Json(ApiResponse::<AuthResponse>::error(
|
||||||
|
500,
|
||||||
|
"密码验证失败".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if !is_valid {
|
if !is_valid {
|
||||||
return Ok(Json(ApiResponse::error(
|
return Json(ApiResponse::<AuthResponse>::error(
|
||||||
401,
|
401,
|
||||||
"用户名或密码错误".to_string(),
|
"用户名或密码错误".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成JWT token
|
// 生成JWT token
|
||||||
let token = generate_token(user.id, user.username.clone())
|
let token = match generate_token(user.id, user.username.clone(), &app_state.config.jwt) {
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
Ok(token) => token,
|
||||||
|
Err(_) => {
|
||||||
|
return Json(ApiResponse::<AuthResponse>::error(
|
||||||
|
500,
|
||||||
|
"Token生成失败".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let auth_response = AuthResponse {
|
let auth_response = AuthResponse {
|
||||||
token,
|
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(
|
pub async fn register(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Json(payload): Json<RegisterRequest>,
|
Json(payload): Json<RegisterRequest>,
|
||||||
) -> Result<Json<ApiResponse<AuthResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<AuthResponse>>, StatusCode> {
|
||||||
// 检查用户名是否已存在
|
// 检查用户名是否已存在
|
||||||
let existing_user = user::Entity::find()
|
let existing_user = user::Entity::find()
|
||||||
.filter(user::Column::Username.eq(&payload.username))
|
.filter(user::Column::Username.eq(&payload.username))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -118,7 +142,7 @@ pub async fn register(
|
|||||||
// 检查邮箱是否已存在
|
// 检查邮箱是否已存在
|
||||||
let existing_email = user::Entity::find()
|
let existing_email = user::Entity::find()
|
||||||
.filter(user::Column::Email.eq(&payload.email))
|
.filter(user::Column::Email.eq(&payload.email))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -148,12 +172,12 @@ pub async fn register(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let user = new_user
|
let user = new_user
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 生成JWT token
|
// 生成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)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
let auth_response = AuthResponse {
|
let auth_response = AuthResponse {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use axum::{
|
|||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter,
|
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, QueryFilter,
|
||||||
QueryOrder, Set,
|
QueryOrder, Set,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -16,6 +16,7 @@ use crate::{
|
|||||||
menu::{self, CreateMenuRequest, UpdateMenuRequest, MenuTreeResponse},
|
menu::{self, CreateMenuRequest, UpdateMenuRequest, MenuTreeResponse},
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
},
|
},
|
||||||
|
routes::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -26,7 +27,7 @@ pub struct MenuQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_menus(
|
pub async fn get_menus(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Query(params): Query<MenuQuery>,
|
Query(params): Query<MenuQuery>,
|
||||||
) -> Result<Json<ApiResponse<Vec<MenuTreeResponse>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<Vec<MenuTreeResponse>>>, StatusCode> {
|
||||||
let mut query = menu::Entity::find();
|
let mut query = menu::Entity::find();
|
||||||
@ -44,7 +45,7 @@ pub async fn get_menus(
|
|||||||
|
|
||||||
let menus = query
|
let menus = query
|
||||||
.order_by_asc(menu::Column::SortOrder)
|
.order_by_asc(menu::Column::SortOrder)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -55,13 +56,13 @@ pub async fn get_menus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_menu(
|
pub async fn create_menu(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Json(payload): Json<CreateMenuRequest>,
|
Json(payload): Json<CreateMenuRequest>,
|
||||||
) -> Result<Json<ApiResponse<menu::Model>>, StatusCode> {
|
) -> Result<Json<ApiResponse<menu::Model>>, StatusCode> {
|
||||||
// 如果有父菜单,检查父菜单是否存在
|
// 如果有父菜单,检查父菜单是否存在
|
||||||
if let Some(parent_id) = payload.parent_id {
|
if let Some(parent_id) = payload.parent_id {
|
||||||
let parent_menu = menu::Entity::find_by_id(parent_id)
|
let parent_menu = menu::Entity::find_by_id(parent_id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ pub async fn create_menu(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let menu = new_menu
|
let menu = new_menu
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -100,13 +101,13 @@ pub async fn create_menu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_menu(
|
pub async fn update_menu(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
Json(payload): Json<UpdateMenuRequest>,
|
Json(payload): Json<UpdateMenuRequest>,
|
||||||
) -> Result<Json<ApiResponse<menu::Model>>, StatusCode> {
|
) -> Result<Json<ApiResponse<menu::Model>>, StatusCode> {
|
||||||
// 查找菜单
|
// 查找菜单
|
||||||
let menu = menu::Entity::find_by_id(id)
|
let menu = menu::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.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)
|
let parent_menu = menu::Entity::find_by_id(parent_id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -179,7 +180,7 @@ pub async fn update_menu(
|
|||||||
menu.updated_at = Set(Utc::now());
|
menu.updated_at = Set(Utc::now());
|
||||||
|
|
||||||
let updated_menu = menu
|
let updated_menu = menu
|
||||||
.update(&db)
|
.update(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -187,12 +188,12 @@ pub async fn update_menu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_menu(
|
pub async fn delete_menu(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||||
// 查找菜单
|
// 查找菜单
|
||||||
let menu = menu::Entity::find_by_id(id)
|
let menu = menu::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -201,7 +202,7 @@ pub async fn delete_menu(
|
|||||||
// 检查是否有子菜单
|
// 检查是否有子菜单
|
||||||
let children = menu::Entity::find()
|
let children = menu::Entity::find()
|
||||||
.filter(menu::Column::ParentId.eq(id))
|
.filter(menu::Column::ParentId.eq(id))
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ pub async fn delete_menu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除菜单
|
// 删除菜单
|
||||||
menu.delete(&db)
|
menu.delete(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use axum::{
|
|||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||||
QueryOrder, Set,
|
QueryOrder, Set,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -15,6 +15,7 @@ use crate::{
|
|||||||
permission::{self, CreatePermissionRequest, UpdatePermissionRequest, PermissionResponse},
|
permission::{self, CreatePermissionRequest, UpdatePermissionRequest, PermissionResponse},
|
||||||
ApiResponse, PageResponse,
|
ApiResponse, PageResponse,
|
||||||
},
|
},
|
||||||
|
routes::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -28,7 +29,7 @@ pub struct PermissionQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_permissions(
|
pub async fn get_permissions(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Query(params): Query<PermissionQuery>,
|
Query(params): Query<PermissionQuery>,
|
||||||
) -> Result<Json<ApiResponse<PageResponse<PermissionResponse>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<PageResponse<PermissionResponse>>>, StatusCode> {
|
||||||
let page = params.page.unwrap_or(1);
|
let page = params.page.unwrap_or(1);
|
||||||
@ -53,14 +54,14 @@ pub async fn get_permissions(
|
|||||||
// 获取总数
|
// 获取总数
|
||||||
let total = query
|
let total = query
|
||||||
.clone()
|
.clone()
|
||||||
.count(&db)
|
.count(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 分页查询
|
// 分页查询
|
||||||
let permissions = query
|
let permissions = query
|
||||||
.order_by_desc(permission::Column::CreatedAt)
|
.order_by_desc(permission::Column::CreatedAt)
|
||||||
.paginate(&db, page_size)
|
.paginate(&app_state.db, page_size)
|
||||||
.fetch_page(page - 1)
|
.fetch_page(page - 1)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
@ -85,13 +86,13 @@ pub async fn get_permissions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_permissions(
|
pub async fn get_all_permissions(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
) -> Result<Json<ApiResponse<Vec<PermissionResponse>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<Vec<PermissionResponse>>>, StatusCode> {
|
||||||
let permissions = permission::Entity::find()
|
let permissions = permission::Entity::find()
|
||||||
.filter(permission::Column::Status.eq(1))
|
.filter(permission::Column::Status.eq(1))
|
||||||
.order_by_asc(permission::Column::Resource)
|
.order_by_asc(permission::Column::Resource)
|
||||||
.order_by_asc(permission::Column::Action)
|
.order_by_asc(permission::Column::Action)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -114,13 +115,13 @@ pub async fn get_all_permissions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_permission(
|
pub async fn create_permission(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Json(payload): Json<CreatePermissionRequest>,
|
Json(payload): Json<CreatePermissionRequest>,
|
||||||
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
|
||||||
// 检查权限键是否已存在
|
// 检查权限键是否已存在
|
||||||
let existing_permission = permission::Entity::find()
|
let existing_permission = permission::Entity::find()
|
||||||
.filter(permission::Column::Key.eq(&payload.key))
|
.filter(permission::Column::Key.eq(&payload.key))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -146,7 +147,7 @@ pub async fn create_permission(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let permission = new_permission
|
let permission = new_permission
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -166,13 +167,13 @@ pub async fn create_permission(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_permission(
|
pub async fn update_permission(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
Json(payload): Json<UpdatePermissionRequest>,
|
Json(payload): Json<UpdatePermissionRequest>,
|
||||||
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<PermissionResponse>>, StatusCode> {
|
||||||
// 查找权限
|
// 查找权限
|
||||||
let permission = permission::Entity::find_by_id(id)
|
let permission = permission::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -191,7 +192,7 @@ pub async fn update_permission(
|
|||||||
if key != &permission.key {
|
if key != &permission.key {
|
||||||
let existing_permission = permission::Entity::find()
|
let existing_permission = permission::Entity::find()
|
||||||
.filter(permission::Column::Key.eq(key))
|
.filter(permission::Column::Key.eq(key))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -228,7 +229,7 @@ pub async fn update_permission(
|
|||||||
permission.updated_at = Set(Utc::now());
|
permission.updated_at = Set(Utc::now());
|
||||||
|
|
||||||
let updated_permission = permission
|
let updated_permission = permission
|
||||||
.update(&db)
|
.update(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -248,12 +249,12 @@ pub async fn update_permission(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_permission(
|
pub async fn delete_permission(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||||
// 查找权限
|
// 查找权限
|
||||||
let permission = permission::Entity::find_by_id(id)
|
let permission = permission::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -261,7 +262,7 @@ pub async fn delete_permission(
|
|||||||
Some(permission) => {
|
Some(permission) => {
|
||||||
// 删除权限(级联删除会自动处理关联表)
|
// 删除权限(级联删除会自动处理关联表)
|
||||||
permission
|
permission
|
||||||
.delete(&db)
|
.delete(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use axum::{
|
|||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||||
QueryOrder, Set,
|
QueryOrder, Set,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -15,6 +15,7 @@ use crate::{
|
|||||||
role::{self, CreateRoleRequest, UpdateRoleRequest, RoleResponse},
|
role::{self, CreateRoleRequest, UpdateRoleRequest, RoleResponse},
|
||||||
role_permission, permission, ApiResponse, PageResponse,
|
role_permission, permission, ApiResponse, PageResponse,
|
||||||
},
|
},
|
||||||
|
routes::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -26,7 +27,7 @@ pub struct RoleQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_roles(
|
pub async fn get_roles(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Query(params): Query<RoleQuery>,
|
Query(params): Query<RoleQuery>,
|
||||||
) -> Result<Json<ApiResponse<PageResponse<RoleResponse>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<PageResponse<RoleResponse>>>, StatusCode> {
|
||||||
let page = params.page.unwrap_or(1);
|
let page = params.page.unwrap_or(1);
|
||||||
@ -45,24 +46,24 @@ pub async fn get_roles(
|
|||||||
// 获取总数
|
// 获取总数
|
||||||
let total = query
|
let total = query
|
||||||
.clone()
|
.clone()
|
||||||
.count(&db)
|
.count(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 分页查询
|
// 获取分页数据
|
||||||
let roles = query
|
let roles = query
|
||||||
.order_by_desc(role::Column::CreatedAt)
|
.order_by_desc(role::Column::CreatedAt)
|
||||||
.paginate(&db, page_size)
|
.paginate(&app_state.db, page_size)
|
||||||
.fetch_page(page - 1)
|
.fetch_page(page - 1)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 获取角色权限信息
|
// 转换为响应格式并获取权限信息
|
||||||
let mut role_responses = Vec::new();
|
let mut role_responses = Vec::new();
|
||||||
for role in roles {
|
for role in roles {
|
||||||
let permissions = role
|
let permissions = role
|
||||||
.find_related(permission::Entity)
|
.find_related(permission::Entity)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -78,17 +79,18 @@ pub async fn get_roles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let page_response = PageResponse::new(role_responses, total, page, page_size);
|
let page_response = PageResponse::new(role_responses, total, page, page_size);
|
||||||
|
|
||||||
Ok(Json(ApiResponse::success(page_response)))
|
Ok(Json(ApiResponse::success(page_response)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_role(
|
pub async fn create_role(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Json(payload): Json<CreateRoleRequest>,
|
Json(payload): Json<CreateRoleRequest>,
|
||||||
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
|
||||||
// 检查角色名是否已存在
|
// 检查角色名是否已存在
|
||||||
let existing_role = role::Entity::find()
|
let existing_role = role::Entity::find()
|
||||||
.filter(role::Column::Name.eq(&payload.name))
|
.filter(role::Column::Name.eq(&payload.name))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -111,11 +113,11 @@ pub async fn create_role(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let role = new_role
|
let role = new_role
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 分配权限
|
// 如果有权限ID,创建角色权限关联
|
||||||
if let Some(permission_ids) = payload.permission_ids {
|
if let Some(permission_ids) = payload.permission_ids {
|
||||||
for permission_id in permission_ids {
|
for permission_id in permission_ids {
|
||||||
let role_permission = role_permission::ActiveModel {
|
let role_permission = role_permission::ActiveModel {
|
||||||
@ -125,16 +127,16 @@ pub async fn create_role(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
role_permission
|
role_permission
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取角色权限信息
|
// 获取角色的权限信息
|
||||||
let permissions = role
|
let permissions = role
|
||||||
.find_related(permission::Entity)
|
.find_related(permission::Entity)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -147,23 +149,22 @@ pub async fn create_role(
|
|||||||
created_at: role.created_at,
|
created_at: role.created_at,
|
||||||
updated_at: role.updated_at,
|
updated_at: role.updated_at,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Json(ApiResponse::success(role_response)))
|
Ok(Json(ApiResponse::success(role_response)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_role(
|
pub async fn update_role(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
Json(payload): Json<UpdateRoleRequest>,
|
Json(payload): Json<UpdateRoleRequest>,
|
||||||
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
|
||||||
// 查找角色
|
// 查找角色
|
||||||
let role = role::Entity::find_by_id(id)
|
let role = role::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
let role = match role {
|
let mut role: role::ActiveModel = match role {
|
||||||
Some(role) => role,
|
Some(role) => role.into(),
|
||||||
None => {
|
None => {
|
||||||
return Ok(Json(ApiResponse::error(
|
return Ok(Json(ApiResponse::error(
|
||||||
404,
|
404,
|
||||||
@ -172,8 +173,6 @@ pub async fn update_role(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut role: role::ActiveModel = role.into();
|
|
||||||
|
|
||||||
// 更新字段
|
// 更新字段
|
||||||
if let Some(name) = payload.name {
|
if let Some(name) = payload.name {
|
||||||
role.name = Set(name);
|
role.name = Set(name);
|
||||||
@ -187,20 +186,20 @@ pub async fn update_role(
|
|||||||
role.updated_at = Set(Utc::now());
|
role.updated_at = Set(Utc::now());
|
||||||
|
|
||||||
let updated_role = role
|
let updated_role = role
|
||||||
.update(&db)
|
.update(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 更新权限关联
|
// 如果有权限ID更新,先删除现有关联,再创建新关联
|
||||||
if let Some(permission_ids) = payload.permission_ids {
|
if let Some(permission_ids) = payload.permission_ids {
|
||||||
// 删除现有权限关联
|
// 删除现有权限关联
|
||||||
role_permission::Entity::delete_many()
|
role_permission::Entity::delete_many()
|
||||||
.filter(role_permission::Column::RoleId.eq(id))
|
.filter(role_permission::Column::RoleId.eq(id))
|
||||||
.exec(&db)
|
.exec(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 添加新的权限关联
|
// 创建新的权限关联
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
for permission_id in permission_ids {
|
for permission_id in permission_ids {
|
||||||
let role_permission = role_permission::ActiveModel {
|
let role_permission = role_permission::ActiveModel {
|
||||||
@ -210,16 +209,16 @@ pub async fn update_role(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
role_permission
|
role_permission
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取角色权限信息
|
// 获取更新后的权限信息
|
||||||
let permissions = updated_role
|
let permissions = updated_role
|
||||||
.find_related(permission::Entity)
|
.find_related(permission::Entity)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -232,27 +231,25 @@ pub async fn update_role(
|
|||||||
created_at: updated_role.created_at,
|
created_at: updated_role.created_at,
|
||||||
updated_at: updated_role.updated_at,
|
updated_at: updated_role.updated_at,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Json(ApiResponse::success(role_response)))
|
Ok(Json(ApiResponse::success(role_response)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_role(
|
pub async fn delete_role(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||||
// 查找角色
|
// 查找角色
|
||||||
let role = role::Entity::find_by_id(id)
|
let role = role::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
match role {
|
match role {
|
||||||
Some(role) => {
|
Some(role) => {
|
||||||
// 删除角色(级联删除会自动处理关联表)
|
// 删除角色(级联删除会自动处理关联表)
|
||||||
role.delete(&db)
|
role.delete(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
Ok(Json(ApiResponse::success(())))
|
Ok(Json(ApiResponse::success(())))
|
||||||
}
|
}
|
||||||
None => Ok(Json(ApiResponse::error(
|
None => Ok(Json(ApiResponse::error(
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use axum::{
|
|||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter,
|
||||||
QueryOrder, Set,
|
QueryOrder, Set,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -16,6 +16,7 @@ use crate::{
|
|||||||
user_role, role, ApiResponse, PageResponse,
|
user_role, role, ApiResponse, PageResponse,
|
||||||
},
|
},
|
||||||
utils::password::hash_password,
|
utils::password::hash_password,
|
||||||
|
routes::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -28,7 +29,7 @@ pub struct UserQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_users(
|
pub async fn get_users(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Query(params): Query<UserQuery>,
|
Query(params): Query<UserQuery>,
|
||||||
) -> Result<Json<ApiResponse<PageResponse<UserResponse>>>, StatusCode> {
|
) -> Result<Json<ApiResponse<PageResponse<UserResponse>>>, StatusCode> {
|
||||||
let page = params.page.unwrap_or(1);
|
let page = params.page.unwrap_or(1);
|
||||||
@ -50,14 +51,14 @@ pub async fn get_users(
|
|||||||
// 获取总数
|
// 获取总数
|
||||||
let total = query
|
let total = query
|
||||||
.clone()
|
.clone()
|
||||||
.count(&db)
|
.count(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
// 分页查询
|
// 分页查询
|
||||||
let users = query
|
let users = query
|
||||||
.order_by_desc(user::Column::CreatedAt)
|
.order_by_desc(user::Column::CreatedAt)
|
||||||
.paginate(&db, page_size)
|
.paginate(&app_state.db, page_size)
|
||||||
.fetch_page(page - 1)
|
.fetch_page(page - 1)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
@ -67,7 +68,7 @@ pub async fn get_users(
|
|||||||
for user in users {
|
for user in users {
|
||||||
let roles = user
|
let roles = user
|
||||||
.find_related(role::Entity)
|
.find_related(role::Entity)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -90,13 +91,13 @@ pub async fn get_users(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_user(
|
pub async fn create_user(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Json(payload): Json<CreateUserRequest>,
|
Json(payload): Json<CreateUserRequest>,
|
||||||
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
|
||||||
// 检查用户名是否已存在
|
// 检查用户名是否已存在
|
||||||
let existing_user = user::Entity::find()
|
let existing_user = user::Entity::find()
|
||||||
.filter(user::Column::Username.eq(&payload.username))
|
.filter(user::Column::Username.eq(&payload.username))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ pub async fn create_user(
|
|||||||
// 检查邮箱是否已存在
|
// 检查邮箱是否已存在
|
||||||
let existing_email = user::Entity::find()
|
let existing_email = user::Entity::find()
|
||||||
.filter(user::Column::Email.eq(&payload.email))
|
.filter(user::Column::Email.eq(&payload.email))
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -140,7 +141,7 @@ pub async fn create_user(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let user = new_user
|
let user = new_user
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ pub async fn create_user(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
user_role
|
user_role
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
}
|
}
|
||||||
@ -163,7 +164,7 @@ pub async fn create_user(
|
|||||||
// 获取用户角色信息
|
// 获取用户角色信息
|
||||||
let roles = user
|
let roles = user
|
||||||
.find_related(role::Entity)
|
.find_related(role::Entity)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -184,13 +185,13 @@ pub async fn create_user(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_user(
|
pub async fn update_user(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
Json(payload): Json<UpdateUserRequest>,
|
Json(payload): Json<UpdateUserRequest>,
|
||||||
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
|
) -> Result<Json<ApiResponse<UserResponse>>, StatusCode> {
|
||||||
// 查找用户
|
// 查找用户
|
||||||
let user = user::Entity::find_by_id(id)
|
let user = user::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -230,7 +231,7 @@ pub async fn update_user(
|
|||||||
user.updated_at = Set(Utc::now());
|
user.updated_at = Set(Utc::now());
|
||||||
|
|
||||||
let updated_user = user
|
let updated_user = user
|
||||||
.update(&db)
|
.update(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -239,7 +240,7 @@ pub async fn update_user(
|
|||||||
// 删除现有角色关联
|
// 删除现有角色关联
|
||||||
user_role::Entity::delete_many()
|
user_role::Entity::delete_many()
|
||||||
.filter(user_role::Column::UserId.eq(id))
|
.filter(user_role::Column::UserId.eq(id))
|
||||||
.exec(&db)
|
.exec(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -253,7 +254,7 @@ pub async fn update_user(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
user_role
|
user_role
|
||||||
.insert(&db)
|
.insert(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
}
|
}
|
||||||
@ -262,7 +263,7 @@ pub async fn update_user(
|
|||||||
// 获取用户角色信息
|
// 获取用户角色信息
|
||||||
let roles = updated_user
|
let roles = updated_user
|
||||||
.find_related(role::Entity)
|
.find_related(role::Entity)
|
||||||
.all(&db)
|
.all(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
@ -283,19 +284,19 @@ pub async fn update_user(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_user(
|
pub async fn delete_user(
|
||||||
State(db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||||
// 查找用户
|
// 查找用户
|
||||||
let user = user::Entity::find_by_id(id)
|
let user = user::Entity::find_by_id(id)
|
||||||
.one(&db)
|
.one(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
match user {
|
match user {
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
// 删除用户(级联删除会自动处理关联表)
|
// 删除用户(级联删除会自动处理关联表)
|
||||||
user.delete(&db)
|
user.delete(&app_state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
|||||||
29
src/main.rs
29
src/main.rs
@ -9,7 +9,9 @@ mod utils;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use tracing::{info, Level};
|
use tracing::{info, Level};
|
||||||
use tracing_subscriber;
|
use tracing_subscriber;
|
||||||
|
use sea_orm::Database;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
use routes::AppState;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
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!("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");
|
info!("Database connected successfully");
|
||||||
|
|
||||||
// 运行数据库迁移
|
// 运行数据库迁移
|
||||||
database::run_migrations(&db).await?;
|
database::run_migrations(&db).await?;
|
||||||
info!("Database migrations completed");
|
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);
|
let addr = format!("{}:{}", config.server.host, config.server.port);
|
||||||
|
|
||||||
info!("Server running on http://{}", addr);
|
info!("Server running on http://{}", addr);
|
||||||
|
|||||||
@ -4,11 +4,13 @@ use axum::{
|
|||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
use sea_orm::DatabaseConnection;
|
use crate::{
|
||||||
use crate::utils::jwt::{extract_token_from_header, verify_token};
|
utils::jwt::{extract_token_from_header, verify_token},
|
||||||
|
routes::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn auth_middleware(
|
pub async fn auth_middleware(
|
||||||
State(_db): State<DatabaseConnection>,
|
State(app_state): State<AppState>,
|
||||||
mut request: Request,
|
mut request: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
@ -46,7 +48,7 @@ pub async fn auth_middleware(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 验证JWT token
|
// 验证JWT token
|
||||||
match verify_token(token) {
|
match verify_token(token, &app_state.config.jwt) {
|
||||||
Ok(claims) => {
|
Ok(claims) => {
|
||||||
// 将用户信息添加到请求扩展中
|
// 将用户信息添加到请求扩展中
|
||||||
request.extensions_mut().insert(claims);
|
request.extensions_mut().insert(claims);
|
||||||
|
|||||||
@ -8,19 +8,31 @@ use tower_http::cors::{Any, CorsLayer};
|
|||||||
use crate::{
|
use crate::{
|
||||||
handlers::{auth, user, role, menu, permission},
|
handlers::{auth, user, role, menu, permission},
|
||||||
middleware::auth::auth_middleware,
|
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()
|
let cors = CorsLayer::new()
|
||||||
.allow_origin(Any)
|
.allow_origin(Any)
|
||||||
.allow_methods(Any)
|
.allow_methods(Any)
|
||||||
.allow_headers(Any);
|
.allow_headers(Any);
|
||||||
|
|
||||||
Router::new()
|
// 无需认证的路由
|
||||||
// 认证路由(无需认证)
|
let public_routes = Router::new()
|
||||||
.route("/api/auth/login", post(auth::login))
|
.route("/api/auth/login", post(auth::login))
|
||||||
.route("/api/auth/register", post(auth::register))
|
.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", get(user::get_users))
|
||||||
.route("/api/users", post(user::create_user))
|
.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", put(permission::update_permission))
|
||||||
.route("/api/permissions/:id", delete(permission::delete_permission))
|
.route("/api/permissions/:id", delete(permission::delete_permission))
|
||||||
|
|
||||||
// 健康检查
|
// 添加认证中间件
|
||||||
.route("/", get(|| async { "UAdmin API Server is running!" }))
|
.layer(axum::middleware::from_fn_with_state(app_state.clone(), auth_middleware))
|
||||||
.route("/health", get(|| async { "OK" }))
|
.with_state(app_state.clone());
|
||||||
|
|
||||||
// 添加CORS中间件
|
// 合并路由并添加CORS
|
||||||
|
Router::new()
|
||||||
|
.merge(public_routes)
|
||||||
|
.merge(protected_routes)
|
||||||
.layer(cors)
|
.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 jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
|
use crate::config::config::JwtConfig;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Claims {
|
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 claims = Claims::new(user_id, username);
|
||||||
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
let key = EncodingKey::from_secret(jwt_config.secret.as_ref());
|
||||||
let key = EncodingKey::from_secret(secret.as_ref());
|
|
||||||
|
|
||||||
encode(&Header::default(), &claims, &key)
|
encode(&Header::default(), &claims, &key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_token(token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
|
pub fn verify_token(token: &str, jwt_config: &JwtConfig) -> Result<Claims, jsonwebtoken::errors::Error> {
|
||||||
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
let key = DecodingKey::from_secret(jwt_config.secret.as_ref());
|
||||||
let key = DecodingKey::from_secret(secret.as_ref());
|
|
||||||
let validation = Validation::default();
|
let validation = Validation::default();
|
||||||
|
|
||||||
decode::<Claims>(token, &key, &validation).map(|data| data.claims)
|
decode::<Claims>(token, &key, &validation).map(|data| data.claims)
|
||||||
|
|||||||
Reference in New Issue
Block a user