修复验证码服务在 PostgreSQL 下的事务行锁查询
Some checks are pending
Security Checks / security (push) Waiting to run
Some checks are pending
Security Checks / security (push) Waiting to run
This commit is contained in:
parent
b1f976fd7a
commit
2697ba7ac0
@ -186,6 +186,7 @@ func (s *EventStore) TryAcquireCleanupLease(ctx context.Context, now time.Time)
|
||||
now = s.now()
|
||||
}
|
||||
session := s.engine.NewSession().Context(ctx)
|
||||
defer session.Close()
|
||||
if err := session.Begin(); err != nil {
|
||||
return cleanupLease{}, false, fmt.Errorf("begin cleanup lease transaction: %w", err)
|
||||
}
|
||||
@ -196,12 +197,9 @@ func (s *EventStore) TryAcquireCleanupLease(ctx context.Context, now time.Time)
|
||||
if err := insertCleanupState(session, now); err != nil {
|
||||
return rollback(fmt.Errorf("initialize cleanup state: %w", err))
|
||||
}
|
||||
query := session.Where("state_key = ?", cleanupStateKey)
|
||||
if s.engine.DriverName() != "sqlite3" && s.engine.DriverName() != "sqlite" {
|
||||
query = query.ForUpdate()
|
||||
}
|
||||
var state models.VerificationCleanupState
|
||||
has, err := query.Get(&state)
|
||||
has, err := getVerificationRowForUpdate(session, &state,
|
||||
"SELECT * FROM verification_cleanup_state WHERE state_key = ?", cleanupStateKey)
|
||||
if err != nil {
|
||||
return rollback(fmt.Errorf("load cleanup state: %w", err))
|
||||
}
|
||||
|
||||
@ -102,11 +102,8 @@ func (r *PersistentRateLimiter) allowWithRelease(ctx context.Context, request Ra
|
||||
return rollback(fmt.Errorf("initialize verification rate limit: %w", err))
|
||||
}
|
||||
var row models.VerificationRateLimit
|
||||
query := session.Where("dimension_key = ?", key)
|
||||
if r.engine.DriverName() != "sqlite3" && r.engine.DriverName() != "sqlite" {
|
||||
query = query.ForUpdate()
|
||||
}
|
||||
has, err := query.Get(&row)
|
||||
has, err := getVerificationRowForUpdate(session, &row,
|
||||
"SELECT * FROM verification_rate_limit WHERE dimension_key = ?", key)
|
||||
if err != nil {
|
||||
return rollback(fmt.Errorf("load verification rate limit: %w", err))
|
||||
}
|
||||
@ -159,11 +156,8 @@ func (r *PersistentRateLimiter) releaseCooldown(rows []models.VerificationRateLi
|
||||
for _, row := range rows {
|
||||
// 同一维度已有新发送时,不清理新请求的冷却,也不重置小时和每日计数。
|
||||
var current models.VerificationRateLimit
|
||||
query := session.ID(row.ID)
|
||||
if r.engine.DriverName() != "sqlite3" && r.engine.DriverName() != "sqlite" {
|
||||
query = query.ForUpdate()
|
||||
}
|
||||
has, err := query.Get(¤t)
|
||||
has, err := getVerificationRowForUpdate(session, ¤t,
|
||||
"SELECT * FROM verification_rate_limit WHERE id = ?", row.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
27
backend/services/verification/row_lock.go
Normal file
27
backend/services/verification/row_lock.go
Normal file
@ -0,0 +1,27 @@
|
||||
package verification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// 使用参数化查询保留事务行锁,避免当前 Xorm 仅支持 MySQL 的行锁构造器。
|
||||
func getVerificationRowForUpdate(session *xorm.Session, bean any, query string, args ...any) (bool, error) {
|
||||
statement, err := verificationLockQuery(session.Engine().DriverName(), query)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return session.SQL(statement, args...).Get(bean)
|
||||
}
|
||||
|
||||
func verificationLockQuery(driver, query string) (string, error) {
|
||||
switch driver {
|
||||
case "postgres", "mysql":
|
||||
return query + " FOR UPDATE", nil
|
||||
case "sqlite", "sqlite3":
|
||||
return query, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported verification lock driver: %s", driver)
|
||||
}
|
||||
}
|
||||
25
backend/services/verification/row_lock_test.go
Normal file
25
backend/services/verification/row_lock_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
package verification
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestVerificationLockQuery(t *testing.T) {
|
||||
const query = "SELECT * FROM verification_rate_limit WHERE dimension_key = ?"
|
||||
for _, driver := range []string{"postgres", "mysql", "sqlite", "sqlite3"} {
|
||||
t.Run(driver, func(t *testing.T) {
|
||||
got, err := verificationLockQuery(driver, query)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := query
|
||||
if driver == "postgres" || driver == "mysql" {
|
||||
want += " FOR UPDATE"
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("query = %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
if got, err := verificationLockQuery("unknown", query); err == nil || got != "" {
|
||||
t.Fatalf("unsupported driver did not fail closed: %q, %v", got, err)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user