From d5d33c3ace8f738bd7e672265765d39f63a32959 Mon Sep 17 00:00:00 2001 From: Victor_Jay Date: Sun, 31 May 2026 00:28:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E5=88=9B=E5=BB=BA=E4=BC=9A=E8=AF=9D=20+=20?= =?UTF-8?q?=E5=A4=87=E6=B3=A8=E8=BE=93=E5=85=A5=E6=A1=86=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - login.js 增加防重复提交锁,请求期间禁用按钮 - Manager.Create 增加幂等检查,同用户同IP同UA 10秒内复用已有会话 - 移除当前设备备注输入框的 readonly 限制,改为 data-current 标记 - 修复 readonly 状态下输入框完全透明无法点击的问题 --- internal/session/manager.go | 44 ++++++++++++++++--- .../MetaLab-2026/html/settings/index.html | 2 +- .../MetaLab-2026/static/css/settings.css | 14 +++--- templates/MetaLab-2026/static/js/login.js | 22 +++++++++- 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/internal/session/manager.go b/internal/session/manager.go index 4959a07..7667d42 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -2,6 +2,7 @@ package session import ( "log" + "sync" "time" "metazone.cc/metalab/internal/config" @@ -15,18 +16,46 @@ type userStore interface { // Manager 会话管理器:创建、验证、销毁、续期 type Manager struct { - store Store - userRepo userStore - cfg *config.Config + store Store + userRepo userStore + cfg *config.Config + mu sync.Mutex + recentLogin map[uint]*loginEntry // uid → 最近一次登录记录(防重复创建) +} + +type loginEntry struct { + sid string + ip string + userAgent string + createdAt time.Time } // NewManager 构造函数 func NewManager(store Store, userRepo userStore, cfg *config.Config) *Manager { - return &Manager{store: store, userRepo: userRepo, cfg: cfg} + return &Manager{ + store: store, + userRepo: userRepo, + cfg: cfg, + recentLogin: make(map[uint]*loginEntry), + } } -// Create 创建会话并写入存储 +// Create 创建会话并写入存储(同用户+同 IP+同 UA 在 10 秒内重复调用时返回已有会话,防重复创建) func (m *Manager) Create(user *model.User, rememberMe bool, ip, userAgent string) (string, error) { + m.mu.Lock() + // 幂等检查:同用户+同 IP+同 UA,10 秒内不重复创建 + if entry, ok := m.recentLogin[user.ID]; ok { + if entry.ip == ip && entry.userAgent == userAgent && time.Since(entry.createdAt) < 10*time.Second { + // 验证旧会话仍然有效 + if s, _ := m.store.Get(entry.sid); s != nil { + m.mu.Unlock() + log.Printf("[SessionManager] Create dedup uid=%d reuse sid=%s", user.ID, entry.sid[:16]+"...") + return entry.sid, nil + } + } + } + m.mu.Unlock() + sid, err := NewID() if err != nil { return "", err @@ -47,6 +76,11 @@ func (m *Manager) Create(user *model.User, rememberMe bool, ip, userAgent string if err := m.store.Set(s); err != nil { return "", err } + + m.mu.Lock() + m.recentLogin[user.ID] = &loginEntry{sid: sid, ip: ip, userAgent: userAgent, createdAt: now} + m.mu.Unlock() + log.Printf("[SessionManager] Created sid=%s uid=%d rememberMe=%v ip=%s", sid[:16]+"...", user.ID, rememberMe, ip) return sid, nil } diff --git a/templates/MetaLab-2026/html/settings/index.html b/templates/MetaLab-2026/html/settings/index.html index 7dcf627..4c7f9e1 100644 --- a/templates/MetaLab-2026/html/settings/index.html +++ b/templates/MetaLab-2026/html/settings/index.html @@ -125,7 +125,7 @@ 最后活动 {{.LastAccess.Format "2006-01-02 15:04"}}
- +
diff --git a/templates/MetaLab-2026/static/css/settings.css b/templates/MetaLab-2026/static/css/settings.css index 748fc7c..7e5a215 100644 --- a/templates/MetaLab-2026/static/css/settings.css +++ b/templates/MetaLab-2026/static/css/settings.css @@ -690,15 +690,15 @@ body { background: #fff; } -.session-remark-input[readonly] { - background: transparent; - border-color: transparent; - cursor: default; +.session-remark-input[data-current="true"] { + background: rgba(46, 204, 113, .03); + border-color: rgba(46, 204, 113, .2); } -.session-remark-input[readonly]:focus { - box-shadow: none; - border-color: transparent; +.session-remark-input[data-current="true"]:focus { + border-color: var(--color-accent); + box-shadow: 0 0 0 3px rgba(52, 152, 219, .15); + background: #fff; } .session-action { diff --git a/templates/MetaLab-2026/static/js/login.js b/templates/MetaLab-2026/static/js/login.js index 28018ab..3976053 100644 --- a/templates/MetaLab-2026/static/js/login.js +++ b/templates/MetaLab-2026/static/js/login.js @@ -55,7 +55,14 @@ doLogin(email, password); }); + var loginInProgress = false; + function doLogin(email, password) { + if (loginInProgress) return; + loginInProgress = true; + submitBtn.disabled = true; + submitBtn.textContent = '登录中...'; + var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/auth/login', true); xhr.setRequestHeader('Content-Type', 'application/json'); @@ -65,7 +72,9 @@ try { resp = JSON.parse(xhr.responseText); } catch (err) { resp = null; } if (xhr.status === 200 && resp && resp.success && resp.action === 'confirm_restore') { - // 注销账号登录 → 二次确认恢复 + loginInProgress = false; + submitBtn.disabled = false; + submitBtn.textContent = '登录'; if (confirm(resp.message + '\n\n确认继续登录将中止注销流程。')) { doConfirmRestore(email, password); } @@ -75,6 +84,9 @@ window.location.href = '/'; }, 800); } else { + loginInProgress = false; + submitBtn.disabled = false; + submitBtn.textContent = '登录'; var msg = (resp && resp.message) ? resp.message : '登录失败,请稍后重试'; window.MetaLab.toast(toast, msg, true); } @@ -84,6 +96,11 @@ } function doConfirmRestore(email, password) { + if (loginInProgress) return; + loginInProgress = true; + submitBtn.disabled = true; + submitBtn.textContent = '恢复中...'; + var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/auth/confirm-restore', true); xhr.setRequestHeader('Content-Type', 'application/json'); @@ -98,6 +115,9 @@ window.location.href = '/'; }, 800); } else { + loginInProgress = false; + submitBtn.disabled = false; + submitBtn.textContent = '登录'; var msg = (resp && resp.message) ? resp.message : '操作失败,请稍后重试'; window.MetaLab.toast(toast, msg, true); }