ForcePilot/web/src/stores/user.js
Wenjie Zhang 747c5de88f feat(auth): 实现账户登录失败次数限制和锁定机制(注意,需要迁移数据库)
- 数据库用户模型新增登录失败限制相关字段(login_failed_count, last_failed_login, login_locked_until)
- 增加用户方法判断是否处于登录锁定状态及计算锁定持续时间
- 登录接口新增登录锁定检测,密码错误时增加失败计数及设置锁定时间
- 登录成功时重置失败计数和锁定状态
- 新增数据库迁移模块,自动检测并执行数据库结构更新,确保新增字段同步
- 前端登录页添加账户锁定状态显示及倒计时功能
- 前端请求中捕获423锁定状态,展示对应锁定提示,禁用登录按钮
- 优化本地状态管理,支持登录锁定错误抛出与异常处理机制
2025-09-17 03:59:51 +08:00

259 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// 状态
const token = ref(localStorage.getItem('user_token') || '')
const userId = ref(parseInt(localStorage.getItem('user_id') || '0') || null)
const username = ref(localStorage.getItem('username') || '')
const userRole = ref(localStorage.getItem('user_role') || '')
// 计算属性
const isLoggedIn = computed(() => !!token.value)
const isAdmin = computed(() => userRole.value === 'admin' || userRole.value === 'superadmin')
const isSuperAdmin = computed(() => userRole.value === 'superadmin')
// 动作
async function login(credentials) {
try {
const formData = new FormData()
formData.append('username', credentials.username)
formData.append('password', credentials.password)
const response = await fetch('/api/auth/token', {
method: 'POST',
body: formData
})
if (!response.ok) {
const error = await response.json()
// 如果是423锁定状态码抛出包含状态码的错误
if (response.status === 423) {
const lockError = new Error(error.detail || '账户被锁定')
lockError.status = 423
lockError.headers = response.headers
throw lockError
}
throw new Error(error.detail || '登录失败')
}
const data = await response.json()
// 更新状态
token.value = data.access_token
userId.value = data.user_id
username.value = data.username
userRole.value = data.role
// 保存到本地存储
localStorage.setItem('user_token', data.access_token)
localStorage.setItem('user_id', data.user_id)
localStorage.setItem('username', data.username)
localStorage.setItem('user_role', data.role)
return true
} catch (error) {
console.error('登录错误:', error)
throw error
}
}
function logout() {
// 清除状态
token.value = ''
userId.value = null
username.value = ''
userRole.value = ''
// 清除本地存储
localStorage.removeItem('user_token')
localStorage.removeItem('user_id')
localStorage.removeItem('username')
localStorage.removeItem('user_role')
}
async function initialize(admin) {
try {
const response = await fetch('/api/auth/initialize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(admin)
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || '初始化管理员失败')
}
const data = await response.json()
// 更新状态
token.value = data.access_token
userId.value = data.user_id
username.value = data.username
userRole.value = data.role
// 保存到本地存储
localStorage.setItem('user_token', data.access_token)
localStorage.setItem('user_id', data.user_id)
localStorage.setItem('username', data.username)
localStorage.setItem('user_role', data.role)
return true
} catch (error) {
console.error('初始化管理员错误:', error)
throw error
}
}
async function checkFirstRun() {
try {
const response = await fetch('/api/auth/check-first-run')
const data = await response.json()
return data.first_run
} catch (error) {
console.error('检查首次运行状态错误:', error)
return false
}
}
// 用于API请求的授权头
function getAuthHeaders() {
return {
'Authorization': `Bearer ${token.value}`
}
}
// 用户管理功能
async function getUsers() {
try {
const response = await fetch('/api/auth/users', {
headers: {
...getAuthHeaders()
}
})
if (!response.ok) {
throw new Error('获取用户列表失败')
}
return await response.json()
} catch (error) {
console.error('获取用户列表错误:', error)
throw error
}
}
async function createUser(userData) {
try {
const response = await fetch('/api/auth/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...getAuthHeaders()
},
body: JSON.stringify(userData)
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || '创建用户失败')
}
return await response.json()
} catch (error) {
console.error('创建用户错误:', error)
throw error
}
}
async function updateUser(userId, userData) {
try {
const response = await fetch(`/api/auth/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...getAuthHeaders()
},
body: JSON.stringify(userData)
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || '更新用户失败')
}
return await response.json()
} catch (error) {
console.error('更新用户错误:', error)
throw error
}
}
async function deleteUser(userId) {
try {
const response = await fetch(`/api/auth/users/${userId}`, {
method: 'DELETE',
headers: {
...getAuthHeaders()
}
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || '删除用户失败')
}
return await response.json()
} catch (error) {
console.error('删除用户错误:', error)
throw error
}
}
return {
// 状态
token,
userId,
username,
userRole,
// 计算属性
isLoggedIn,
isAdmin,
isSuperAdmin,
// 方法
login,
logout,
initialize,
checkFirstRun,
getAuthHeaders,
getUsers,
createUser,
updateUser,
deleteUser
}
})
// 检查当前用户是否有管理员权限
export const checkAdminPermission = () => {
const userStore = useUserStore()
if (!userStore.isAdmin) {
throw new Error('需要管理员权限')
}
return true
}
// 检查当前用户是否有超级管理员权限
export const checkSuperAdminPermission = () => {
const userStore = useUserStore()
if (!userStore.isSuperAdmin) {
throw new Error('需要超级管理员权限')
}
return true
}