Go实现防抖锁功能

Go实现防抖锁功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// @package    : utils
// @file : anti_shake_lock.go
// @author : hqd8080
// @contact : hanquanding@163.com
// @time : 2025/8/22
// @description: anti_shake_lock

package utils

import (
"context"
"time"

"github.com/redis/go-redis/v9"
)

// AntiShakeLock 防抖锁
type AntiShakeLock struct {
c *redis.Client
}

func NewAntiShakeLock(c *redis.Client) *AntiShakeLock {
return &AntiShakeLock{c: c}
}

// TryLock 尝试获取锁(检查是否允许执行操作)
// true:允许执行(获取锁成功)
// false:不允许执行(处于防抖动窗口)
func (l *AntiShakeLock) TryLock(ctx context.Context, key string, exp time.Duration) (bool, error) {
return l.c.SetNX(ctx, key, 1, exp).Result()
}

// ReleaseLock 释放锁
func (l *AntiShakeLock) ReleaseLock(ctx context.Context, key string) error {
return l.c.Del(ctx, key).Err()
}

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// @package    : utils
// @file : anti_shake_lock_test.go
// @author : hqd8080
// @contact : hanquanding@163.com
// @time : 2025/8/22
// @description: anti_shake_lock_test

package utils

import (
"context"
"fmt"
"testing"
"time"

"github.com/redis/go-redis/v9"
)

func TestAntiShakeLock(t *testing.T) {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%s", "localhost", "6379"),
Password: "123456",
DB: 0,
})

// 防抖动锁:500ms 窗口
redisKey := fmt.Sprintf("%s:%d:%s", "app", 10001, "hqd8080")
allowed, err := NewAntiShakeLock(rdb).TryLock(ctx, redisKey, 500*time.Millisecond)
if err != nil {
t.Fatalf("获取防抖动锁失败:%v", err)
}

if !allowed {
t.Fatal("操作频繁!")
} else {
t.Log("获得防抖动锁成功、执行业务!")
if err = NewAntiShakeLock(rdb).ReleaseLock(ctx, redisKey); err != nil {
t.Fatal(err)
}
}

// 模拟 5 次快速点击(间隔 100ms)
for i := 1; i <= 5; i++ {
allowed, err = NewAntiShakeLock(rdb).TryLock(ctx, redisKey, 500*time.Millisecond)
if err != nil {
fmt.Printf("第 %d 次操作错误: %v\n", i, err)
continue
}
if allowed {
fmt.Printf("第 %d 次操作有效,执行业务逻辑\n", i)
// 这里执行实际业务(如提交表单、领取奖励等)
} else {
fmt.Printf("第 %d 次操作被拦截(处于抖动窗口)\n", i)
}
// 间隔100ms再次请求(模拟快速点击)
time.Sleep(100 * time.Millisecond)
}
}