Files
adminandClaude Opus 4.7 414c7de007 feat: 分支闯关判分与终局结算(含单测)
choose 校验节点/选项归属与解锁链,终局在 common.WithLock 内按
技术设计 4.3/4.4 结算:首次到达才结算、连续失败限扣、余额不为负、
完美集齐全终局 +20 并解锁计策卡与下一计;路径流水尽力而为。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-13 12:47:01 +08:00

64 lines
2.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package common
import (
"context"
"crypto/rand"
"encoding/hex"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
)
// ErrLockHeld 锁被占用:重试 retries 次仍未获取时返回。
var ErrLockHeld = gerror.New("操作过于频繁,请稍后再试")
// WithLock 业务互斥锁(唯一入口):
// - expire 必须 > 0,进程崩溃后锁自动过期兜底不死锁;fn 耗时必须小于 expire,fn 内禁止长耗时 IO
// - 拿不到锁重试 retries 次、每次间隔 retryIntervalretries=0 立即失败;ctx 取消/超时同样终止等待)
// - 中间件故障不重试,直接返回错误
// - 锁实现按配置自动选择:配置了 redis 节点 → redis 锁(跨实例互斥);未配置 → gcache 内存锁(单实例互斥)。
// redis 后端需引入 github.com/gogf/gf/contrib/redis/v2 适配器并接入 SET NX EX + token 对比删除;
// 当前未配置 redis 时使用内存锁。
// - 无论 fn 成功、失败还是 panic,锁在函数退出时自动释放
func WithLock[T any](ctx context.Context, key string, expire time.Duration, retries int, retryInterval time.Duration, fn func() (T, error)) (T, error) {
var zero T
if expire <= 0 {
return zero, gerror.New("锁过期时间必须大于 0")
}
if lockBackendIsRedis(ctx) {
return zero, gerror.New("配置了 redis 锁后端,但当前未接入 contrib/redis 适配器")
}
token := randomToken()
for attempt := 0; ; attempt++ {
ok, err := gcache.SetIfNotExist(ctx, key, token, expire)
if err != nil {
return zero, err
}
if ok {
defer func() { _, _ = gcache.Remove(ctx, key) }()
return fn()
}
if attempt >= retries {
return zero, ErrLockHeld
}
select {
case <-ctx.Done():
return zero, ctx.Err()
case <-time.After(retryInterval):
}
}
}
func lockBackendIsRedis(ctx context.Context) bool {
v, err := g.Cfg().Get(ctx, "redis", nil)
return err == nil && v != nil && !v.IsEmpty()
}
func randomToken() string {
b := make([]byte, 16)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}