fix: P0-02 prevent login attempt counter race condition

Add atomic Increment method to cache layers:
- L2Cache interface: add Increment method signature
- RedisCache: implement using Redis INCRBY
- L1Cache: implement with mutex-protected counter
- CacheManager: add Increment that updates both L1 and L2

Update incrementFailAttempts to use atomic Increment instead
of Get-Increment-Set pattern, preventing TOCTOU race.
This commit is contained in:
2026-04-18 13:45:09 +08:00
parent 32a3d4c9e0
commit ca7ba5ccdf
4 changed files with 84 additions and 9 deletions

41
internal/cache/l1.go vendored
View File

@@ -169,3 +169,44 @@ func (c *L1Cache) Cleanup() {
c.removeFromAccessOrder(key)
}
}
// Increment 原子递增(用于登录失败计数器等原子操作场景)
func (c *L1Cache) Increment(key string, delta int64, ttl time.Duration) int64 {
c.mu.Lock()
defer c.mu.Unlock()
var expiration int64
if ttl > 0 {
expiration = time.Now().Add(ttl).UnixNano()
}
current := int64(0)
if item, ok := c.items[key]; ok {
if item.Expired() {
delete(c.items, key)
c.removeFromAccessOrder(key)
} else {
if v, ok := item.Value.(int64); ok {
current = v
} else if v, ok := item.Value.(int); ok {
current = int64(v)
} else if v, ok := item.Value.(float64); ok {
current = int64(v)
}
}
}
newVal := current + delta
c.items[key] = &CacheItem{
Value: newVal,
Expiration: expiration,
}
if _, exists := c.items[key]; !exists {
c.accessOrder = append(c.accessOrder, key)
} else {
c.updateAccessOrder(key)
}
return newVal
}