mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-03 02:24:36 +00:00
feat : 已参考注释说明的方案:
1、登录请求 — 失败时 return,由 mutation 状态驱动错误 UI 2.、ocalStorage 存储 — 独立 try/catch,失败仅 console.warn,不影响后续流程 3、页面导航 — 始终在登录成功后执行,不受存储异常干扰 4、把整个读取过程都纳入异常处理,避免页面初始化阶段被存储异常中断。 5、将 save(detachedEntity) 改为定向 JPQL update,只更新 failedAttempts 和 lockedUntil 两个字段,彻底消除并发覆盖风险
This commit is contained in:
parent
a3def057fa
commit
fb3de15f85
3 changed files with 36 additions and 16 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package com.iflytek.skillhub.auth.local;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -29,14 +30,20 @@ public class LocalAuthFailedService {
|
|||
|
||||
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void handleFailedLogin(LocalCredential credential) {
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
|
||||
public void handleFailedLogin(@Nonnull Long credentialId) {
|
||||
|
||||
LocalCredential credential = credentialRepository.findById(credentialId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Invalid credential id"));
|
||||
|
||||
int failedAttempts = credential.getFailedAttempts() + 1;
|
||||
credential.setFailedAttempts(failedAttempts);
|
||||
if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
|
||||
credential.setLockedUntil(currentTime().plus(LOCK_DURATION));
|
||||
Instant lockedUntil = credential.getLockedUntil();
|
||||
|
||||
if (failedAttempts >= MAX_FAILED_ATTEMPTS && lockedUntil == null) {
|
||||
lockedUntil = currentTime().plus(LOCK_DURATION);
|
||||
}
|
||||
credentialRepository.save(credential);
|
||||
|
||||
credentialRepository.updateFailedAttemptsAndLockedUntil(credentialId, failedAttempts, lockedUntil);
|
||||
}
|
||||
|
||||
private Instant currentTime() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.iflytek.skillhub.auth.local;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
|
|
@ -15,4 +18,8 @@ public interface LocalCredentialRepository extends JpaRepository<LocalCredential
|
|||
Optional<LocalCredential> findByUserId(String userId);
|
||||
|
||||
boolean existsByUsernameIgnoreCase(String username);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE LocalCredential c SET c.failedAttempts = :failedAttempts, c.lockedUntil = :lockedUntil WHERE c.id = :id")
|
||||
int updateFailedAttemptsAndLockedUntil(Long id, int failedAttempts, Instant lockedUntil);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,17 +35,17 @@ export function LoginPage() {
|
|||
|
||||
// Load saved username from localStorage on mount
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem(REMEMBER_ME_KEY)
|
||||
if (saved) {
|
||||
try {
|
||||
try {
|
||||
const saved = localStorage.getItem(REMEMBER_ME_KEY)
|
||||
if (saved) {
|
||||
const { username: savedUsername } = JSON.parse(saved)
|
||||
if (savedUsername) {
|
||||
setUsername(savedUsername)
|
||||
setRememberMe(true)
|
||||
}
|
||||
} catch {
|
||||
// Invalid data, ignore
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load remembered username:', e)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
|
@ -76,7 +76,12 @@ export function LoginPage() {
|
|||
setFieldErrors({})
|
||||
try {
|
||||
await loginMutation.mutateAsync({ username: trimmedUsername, password })
|
||||
// Save username to localStorage if remember me is checked
|
||||
} catch {
|
||||
// mutation state drives the error UI
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (rememberMe) {
|
||||
localStorage.setItem(REMEMBER_ME_KEY, JSON.stringify({
|
||||
username: trimmedUsername
|
||||
|
|
@ -84,10 +89,11 @@ export function LoginPage() {
|
|||
} else {
|
||||
localStorage.removeItem(REMEMBER_ME_KEY)
|
||||
}
|
||||
await navigate({ to: returnTo })
|
||||
} catch {
|
||||
// mutation state drives the error UI
|
||||
} catch (e) {
|
||||
console.warn('Failed to save remember-me preference:', e)
|
||||
}
|
||||
|
||||
await navigate({ to: returnTo })
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue