refactor: 前后端目录结构模块化 + v2 优化移植与缺陷修复
前端: - src/srv/api → src/api,按域分文件夹(auth/cluster/host/storage/deployment/collector) - 新增 http.ts 引擎(http.get/post/put/delete),域模块改用语义化调用 - index.html 增加 favicon(匹配应用 logo 的 emerald + Activity 脉冲) 后端分层增强(保留包名): - 新增 repository/:从 models 拆出 JsonDB 持久化层(InitDB/GetDB/Shutdown/RequestSave) - 新增 dto/:从 controllers 抽出各域请求契约(带 binding 校验) - 新增 pkg/crypto、pkg/jwt:从 utils 拆出安全原语(外部 jwt 别名 jwtv5 避免冲突) - models 只留领域实体 + sanitizer;utils 只留 gin/应用相关(response/errors) v2 优化移植与缺陷修复: - 移植密钥分离/SSH TOFU/并发写安全/Loki 生产级配置/Docker 加固 - 修复 deployment.go 并发丢更新、Helm values 重复键、EncryptField 哨兵、loki-gateway 端点等审计缺陷 - 实现真正的优雅关停(main.go http.Server + signal + repository.Shutdown) - 修复登录契约:后端返回 accessToken,前端 auth.ts/Login.tsx 对齐 - 补 crypto/jwt/repository 单元测试(含并发写回归) 验证: go build/vet/test 全绿;npm run build 通过;登录→鉴权→优雅关停冒烟通过 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
fb8d10231a
commit
327e282356
93 files changed
+8433
-3089
No files matched your search
@@ -1,12 +1,22 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { Activity, Eye, EyeOff, Shield, ServerOff } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { authApi } from '@/srv/api'
|
||||
import { authApi } from '@/api'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function Login() {
|
||||
/**
|
||||
* Login 页面 - 优化版本
|
||||
*
|
||||
* 优化点:
|
||||
* 1. React.memo 包裹组件
|
||||
* 2. 表单处理使用 useCallback 缓存
|
||||
* 3. 错误处理使用 sonner toast 替代 alert
|
||||
* 4. 清理 useEffect 中的未使用变量
|
||||
*/
|
||||
const Login = memo(function Login() {
|
||||
const [username, setUsername] = useState('admin')
|
||||
const [password, setPassword] = useState('admin123')
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
@@ -24,50 +34,67 @@ export default function Login() {
|
||||
}
|
||||
// Quick backend check
|
||||
checkBackend()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [navigate])
|
||||
|
||||
const checkBackend = async () => {
|
||||
// 后端状态检查 - useCallback缓存
|
||||
const checkBackend = useCallback(async () => {
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000)
|
||||
await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: '{}',
|
||||
// 用公开的健康检查端点探活;避免打 /auth/login(生产环境被 nginx 限流 5r/m,
|
||||
// 反复刷新登录页会触发限流并锁死真正的登录请求)
|
||||
const res = await fetch('/api/v1/health', {
|
||||
method: 'GET',
|
||||
signal: controller.signal,
|
||||
})
|
||||
clearTimeout(timeoutId)
|
||||
// Even a 400 error means backend is up
|
||||
setBackendStatus('online')
|
||||
setBackendStatus(res.ok ? 'online' : 'offline')
|
||||
} catch {
|
||||
setBackendStatus('offline')
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError('')
|
||||
// 表单提交处理 - useCallback缓存
|
||||
const handleSubmit = useCallback(
|
||||
async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const result = await authApi.login(username, password)
|
||||
localStorage.setItem('token', result.token)
|
||||
localStorage.setItem('user', JSON.stringify(result.user))
|
||||
navigate('/')
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : '登录失败'
|
||||
if (message.includes('Cannot connect')) {
|
||||
setError('无法连接后端服务,请确认后端已启动(端口 8080)。')
|
||||
setBackendStatus('offline')
|
||||
} else if (message.includes('invalid') || message.includes('password') || message.includes('密码')) {
|
||||
setError('用户名或密码错误')
|
||||
} else {
|
||||
setError(message)
|
||||
try {
|
||||
const result = await authApi.login(username, password)
|
||||
localStorage.setItem('token', result.accessToken)
|
||||
localStorage.setItem('refreshToken', result.refreshToken)
|
||||
localStorage.setItem('user', JSON.stringify(result.user))
|
||||
toast.success('登录成功')
|
||||
navigate('/')
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : '登录失败'
|
||||
if (message.includes('Cannot connect')) {
|
||||
setError('无法连接后端服务,请确认后端已启动(端口 8080)。')
|
||||
setBackendStatus('offline')
|
||||
} else if (
|
||||
message.includes('invalid') ||
|
||||
message.includes('password') ||
|
||||
message.includes('密码') ||
|
||||
message.includes('Unauthorized')
|
||||
) {
|
||||
setError('用户名或密码错误')
|
||||
} else {
|
||||
setError(message)
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
[username, password, navigate]
|
||||
)
|
||||
|
||||
// 密码显示切换 - useCallback缓存
|
||||
const togglePassword = useCallback(() => {
|
||||
setShowPassword((prev) => !prev)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 flex items-center justify-center p-4">
|
||||
@@ -120,6 +147,7 @@ export default function Login() {
|
||||
placeholder="请输入用户名"
|
||||
required
|
||||
className="h-10"
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -134,13 +162,19 @@ export default function Login() {
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
className="h-10 pr-10"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
onClick={togglePassword}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
{showPassword ? (
|
||||
<EyeOff className="w-4 h-4" />
|
||||
) : (
|
||||
<Eye className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,4 +197,6 @@ export default function Login() {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default Login
|
||||
Reference in new issue
Block a user