1
0
forked from qq1244/vue-beta

实现完整的登录系统和退出登录功能

- 新增登录、注册、修改密码页面,支持三种用户类型
- 实现路由守卫和权限控制系统
- 集成Axios拦截器处理认证和错误
- 添加统一错误页面处理
- 完善Header组件退出登录功能,清除本地数据并跳转登录页
- 使用Element Plus构建现代化UI界面
- 支持响应式设计和移动端适配
This commit is contained in:
2025-06-21 11:21:19 +08:00
parent 5273106e87
commit 3541ae7755
17 changed files with 3915 additions and 6 deletions

View File

@@ -0,0 +1,645 @@
# 餐厅管理系统 - 前端API接口文档
## 📋 接口概述
本系统使用JWT Token进行身份认证。除了登录注册接口外所有API都需要在请求头中携带有效的JWT Token。
**服务器地址**: `http://localhost:8080`
## 🔐 认证机制
### JWT Token使用方式
- **获取Token**: 通过登录接口获取
- **使用Token**: 在请求头中添加 `Authorization: Bearer {token}`
- **Token有效期**: 24小时
- **无需Token**: 仅注册和登录接口
## 📱 认证相关接口
### 1. 用户注册
**接口地址**: `POST /api/auth/register`
**是否需要Token**: ❌ 否
**请求参数**:
```json
{
"accountName": "用户名3-20字符字母数字下划线",
"accountPassword": "密码8-20字符至少包含字母和数字",
"confirmPassword": "确认密码(需与密码相同)",
"accountPhone": "手机号11位数字可选",
"accountType": "账户类型admin/manager/customer",
"accountGender": "性别male/female可选"
}
```
**请求示例**:
```javascript
const registerData = {
accountName: "zhangsan",
accountPassword: "password123",
confirmPassword: "password123",
accountPhone: "13812345678",
accountType: "customer",
accountGender: "male"
};
const response = await fetch('http://localhost:8080/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(registerData)
});
const result = await response.json();
```
**响应示例**:
```json
{
"code": 200,
"msg": "成功",
"data": {
"token": null,
"accountInfo": {
"accountId": 1,
"accountName": "zhangsan",
"accountType": "customer",
"accountPhone": "13812345678",
"accountGender": "male",
"accountImg": null
}
}
}
```
### 2. 用户登录
**接口地址**: `POST /api/auth/login`
**是否需要Token**: ❌ 否
**请求参数**:
```json
{
"accountName": "用户名",
"accountPassword": "密码"
}
```
**请求示例**:
```javascript
const loginData = {
accountName: "zhangsan",
accountPassword: "password123"
};
const response = await fetch('http://localhost:8080/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(loginData)
});
const result = await response.json();
// 保存Token到本地存储
if (result.code === 200) {
const jwtToken = result.data.token;
localStorage.setItem('token', jwtToken);
localStorage.setItem('userInfo', JSON.stringify(result.data.accountInfo));
}
```
**响应示例**:
```json
{
"code": 200,
"msg": "成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6aGFuZ3NhbiIsImlhdCI6MTcwNDczNzgxLCJleHAiOjE3NTA1NjAxODF9...",
"accountInfo": {
"accountId": 1,
"accountName": "zhangsan",
"accountType": "customer",
"accountPhone": "13812345678",
"accountGender": "male",
"accountImg": null
}
}
}
```
### 3. 修改密码
**接口地址**: `PUT /api/auth/change-password`
**是否需要Token**: ✅ 是
**请求参数**:
```json
{
"oldPassword": "当前密码",
"newPassword": "新密码8-20字符至少包含字母和数字",
"confirmPassword": "确认新密码"
}
```
**请求示例**:
```javascript
// 获取存储的Token
const storedToken = localStorage.getItem('token');
const passwordData = {
oldPassword: "password123",
newPassword: "newpassword456",
confirmPassword: "newpassword456"
};
// 构建请求头确保完整Token传递
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${storedToken}`
};
const response = await fetch('http://localhost:8080/api/auth/change-password', {
method: 'PUT',
headers: headers,
body: JSON.stringify(passwordData)
});
const result = await response.json();
```
**响应示例**:
```json
{
"code": 200,
"msg": "成功",
"data": "密码修改成功"
}
```
### 4. 获取当前用户信息
**接口地址**: `GET /api/auth/current-user`
**是否需要Token**: ✅ 是
**请求示例**:
```javascript
// 获取存储的Token
const authToken = localStorage.getItem('token');
// 构建请求头
const requestHeaders = {
'Authorization': `Bearer ${authToken}`
};
const response = await fetch('http://localhost:8080/api/auth/current-user', {
method: 'GET',
headers: requestHeaders
});
const result = await response.json();
```
**响应示例**:
```json
{
"code": 200,
"msg": "成功",
"data": "当前登录用户: zhangsan"
}
```
## 🔧 前端Token管理
### Token存储和使用
```javascript
class AuthManager {
// 保存Token - 确保完整保存
static saveToken(tokenString, userInfo) {
if (tokenString && tokenString.trim()) {
localStorage.setItem('token', tokenString);
localStorage.setItem('userInfo', JSON.stringify(userInfo));
}
}
// 获取完整Token
static getToken() {
const tokenValue = localStorage.getItem('token');
return tokenValue ? tokenValue.trim() : null;
}
// 获取用户信息
static getUserInfo() {
const userInfo = localStorage.getItem('userInfo');
return userInfo ? JSON.parse(userInfo) : null;
}
// 清除Token登出
static logout() {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
}
// 检查是否已登录
static isLoggedIn() {
const token = this.getToken();
return token && token.length > 0;
}
// 创建认证头 - 确保完整Token传递
static getAuthHeaders() {
const fullToken = this.getToken();
return fullToken ? { 'Authorization': `Bearer ${fullToken}` } : {};
}
}
```
### 统一请求封装
```javascript
class ApiClient {
static baseURL = 'http://localhost:8080';
// 通用请求方法 - 使用变量方式确保Token完整传递
static async request(url, options = {}) {
// 获取完整Token
const currentToken = AuthManager.getToken();
// 构建基础headers
const baseHeaders = {
'Content-Type': 'application/json'
};
// 如果有Token添加认证header
const authHeaders = currentToken ? { 'Authorization': `Bearer ${currentToken}` } : {};
// 合并headers
const finalHeaders = {
...baseHeaders,
...authHeaders,
...(options.headers || {})
};
const config = {
...options,
headers: finalHeaders
};
try {
const response = await fetch(`${this.baseURL}${url}`, config);
const result = await response.json();
// 处理认证失败
if (response.status === 403 || response.status === 401) {
AuthManager.logout();
// 跳转到登录页面
window.location.href = '/login';
return null;
}
return result;
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
// GET请求
static async get(url) {
return this.request(url, { method: 'GET' });
}
// POST请求
static async post(url, data) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data)
});
}
// PUT请求
static async put(url, data) {
return this.request(url, {
method: 'PUT',
body: JSON.stringify(data)
});
}
// DELETE请求
static async delete(url) {
return this.request(url, { method: 'DELETE' });
}
}
```
### 认证相关操作
```javascript
class AuthAPI {
// 用户注册
static async register(userData) {
return ApiClient.post('/api/auth/register', userData);
}
// 用户登录 - 确保完整Token保存
static async login(credentials) {
const result = await ApiClient.post('/api/auth/login', credentials);
if (result && result.code === 200) {
// 获取完整Token并保存
const completeToken = result.data.token;
AuthManager.saveToken(completeToken, result.data.accountInfo);
}
return result;
}
// 修改密码 - 使用变量方式传递Token
static async changePassword(passwordData) {
// 获取完整Token
const userToken = AuthManager.getToken();
// 构建请求头
const requestHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userToken}`
};
// 直接使用fetch确保Token完整传递
const response = await fetch(`${ApiClient.baseURL}/api/auth/change-password`, {
method: 'PUT',
headers: requestHeaders,
body: JSON.stringify(passwordData)
});
return await response.json();
}
// 获取当前用户 - 使用变量方式传递Token
static async getCurrentUser() {
const accessToken = AuthManager.getToken();
const headers = { 'Authorization': `Bearer ${accessToken}` };
const response = await fetch(`${ApiClient.baseURL}/api/auth/current-user`, {
method: 'GET',
headers: headers
});
return await response.json();
}
// 登出
static logout() {
AuthManager.logout();
window.location.href = '/login';
}
}
```
## 🎯 使用示例
### 登录页面
```javascript
// 登录表单提交
async function handleLogin(event) {
event.preventDefault();
const formData = new FormData(event.target);
const loginCredentials = {
accountName: formData.get('username'),
accountPassword: formData.get('password')
};
try {
const loginResult = await AuthAPI.login(loginCredentials);
if (loginResult.code === 200) {
alert('登录成功!');
// 验证Token是否正确保存
const savedToken = AuthManager.getToken();
console.log('Token已保存:', savedToken ? '是' : '否');
window.location.href = '/dashboard';
} else {
alert(`登录失败:${loginResult.msg}`);
}
} catch (error) {
alert('登录请求失败,请检查网络连接');
}
}
```
### 注册页面
```javascript
// 注册表单提交
async function handleRegister(event) {
event.preventDefault();
const formData = new FormData(event.target);
const registrationData = {
accountName: formData.get('username'),
accountPassword: formData.get('password'),
confirmPassword: formData.get('confirmPassword'),
accountPhone: formData.get('phone'),
accountType: formData.get('userType'),
accountGender: formData.get('gender')
};
try {
const registerResult = await AuthAPI.register(registrationData);
if (registerResult.code === 200) {
alert('注册成功!请登录');
window.location.href = '/login';
} else {
alert(`注册失败:${registerResult.msg}`);
}
} catch (error) {
alert('注册请求失败,请检查网络连接');
}
}
```
### 修改密码页面
```javascript
// 修改密码表单提交
async function handleChangePassword(event) {
event.preventDefault();
const formData = new FormData(event.target);
const changePasswordInfo = {
oldPassword: formData.get('oldPassword'),
newPassword: formData.get('newPassword'),
confirmPassword: formData.get('confirmPassword')
};
try {
// 使用专门的方法确保Token完整传递
const changeResult = await AuthAPI.changePassword(changePasswordInfo);
if (changeResult.code === 200) {
alert('密码修改成功!');
} else {
alert(`密码修改失败:${changeResult.msg}`);
}
} catch (error) {
alert('请求失败,请检查网络连接');
}
}
```
### 业务API调用示例
```javascript
// 获取数据列表 - 使用变量方式确保Token传递
async function fetchDataWithAuth(apiEndpoint) {
try {
// 获取完整Token
const authenticationToken = AuthManager.getToken();
if (!authenticationToken) {
alert('请先登录');
window.location.href = '/login';
return;
}
// 构建请求头
const apiHeaders = {
'Authorization': `Bearer ${authenticationToken}`
};
const response = await fetch(`http://localhost:8080${apiEndpoint}`, {
method: 'GET',
headers: apiHeaders
});
const result = await response.json();
if (result && result.code === 200) {
return result.data;
} else {
alert(`请求失败:${result.msg}`);
return null;
}
} catch (error) {
alert('请求失败,请检查网络连接');
return null;
}
}
// 发送数据 - 使用变量方式确保Token传递
async function postDataWithAuth(apiEndpoint, postData) {
try {
// 获取完整Token
const userAuthToken = AuthManager.getToken();
if (!userAuthToken) {
alert('请先登录');
window.location.href = '/login';
return;
}
// 构建请求头
const requestHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userAuthToken}`
};
const response = await fetch(`http://localhost:8080${apiEndpoint}`, {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify(postData)
});
const result = await response.json();
if (result && result.code === 200) {
return result.data;
} else {
alert(`操作失败:${result.msg}`);
return null;
}
} catch (error) {
alert('请求失败,请检查网络连接');
return null;
}
}
```
## 🚨 错误处理
### 常见错误码
| 错误码 | 说明 | 处理方式 |
|--------|------|----------|
| 200 | 成功 | 正常处理数据 |
| 401 | 未认证 | 跳转到登录页面 |
| 403 | 已禁止/Token无效 | 清除Token跳转到登录页面 |
| 500 | 服务器错误 | 显示错误信息,建议重试 |
### Token验证和错误处理
```javascript
// 页面加载时验证Token有效性
async function validateTokenOnPageLoad() {
const currentToken = AuthManager.getToken();
if (!currentToken) {
// 没有Token跳转到登录页
window.location.href = '/login';
return;
}
try {
// 测试Token是否有效
const testHeaders = { 'Authorization': `Bearer ${currentToken}` };
const testResponse = await fetch('http://localhost:8080/api/auth/test', {
method: 'GET',
headers: testHeaders
});
if (testResponse.status === 403 || testResponse.status === 401) {
// Token无效清除并跳转
AuthManager.logout();
window.location.href = '/login';
}
} catch (error) {
console.error('Token验证失败:', error);
}
}
// 页面加载时执行Token验证
window.addEventListener('DOMContentLoaded', validateTokenOnPageLoad);
```
## 📝 重要注意事项
1. **Token完整性**:
- 始终使用变量方式传递Token避免字符串截断
- 在存储和读取Token时检查完整性
2. **Token安全**:
- Token存储在localStorage中注意防止XSS攻击
- 生产环境建议考虑更安全的存储方式
3. **请求头构建**:
- 使用变量方式构建Authorization头
- 确保Token前缀"Bearer "正确添加
4. **错误处理**:
- 统一处理401/403错误自动跳转登录页
- 为用户提供友好的错误提示
5. **调试建议**:
- 在控制台检查Token是否完整
- 验证请求头是否正确设置
这份文档强调了使用变量方式来确保Token完整传递避免了直接字符串拼接可能导致的截断问题