feat(backend): jwt middleware, login with redis rate limit, /auth/me

This commit is contained in:
2026-09-04 23:58:26 +08:00
parent a2f202ada6
commit bc88cabc47
9 changed files with 341 additions and 8 deletions
+78
View File
@@ -0,0 +1,78 @@
package redispkg
import (
"context"
"crypto/rand"
"encoding/hex"
"log"
"time"
"github.com/redis/go-redis/v9"
)
type R struct{ c *redis.Client }
func New(url string) *R {
if url == "" {
return &R{}
}
opt, err := redis.ParseURL(url)
if err != nil {
log.Printf("bad REDIS_URL (%v): redis disabled", err)
return &R{}
}
return &R{c: redis.NewClient(opt)}
}
func (r *R) Get(ctx context.Context, key string) (string, bool) {
if r.c == nil {
return "", false
}
v, err := r.c.Get(ctx, key).Result()
if err != nil {
return "", false // 故障=miss
}
return v, true
}
func (r *R) Set(ctx context.Context, key, val string, ttl time.Duration) {
if r.c == nil {
return
}
if err := r.c.Set(ctx, key, val, ttl).Err(); err != nil {
log.Printf("redis set %s: %v", key, err)
}
}
func (r *R) IncrWindow(ctx context.Context, key string, ttl time.Duration) int {
if r.c == nil {
return 1
}
n, err := r.c.Incr(ctx, key).Result()
if err != nil {
return 1
}
if n == 1 {
r.c.Expire(ctx, key, ttl)
}
return int(n)
}
func (r *R) Lock(ctx context.Context, key string, ttl time.Duration) (func(), bool) {
noop := func() {}
if r.c == nil {
return noop, true
}
b := make([]byte, 8)
rand.Read(b)
tok := hex.EncodeToString(b)
ok, err := r.c.SetNX(ctx, key, tok, ttl).Result()
if err != nil || !ok {
return noop, false
}
return func() {
r.c.Eval(ctx,
"if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end",
[]string{key}, tok)
}, true
}
+24
View File
@@ -0,0 +1,24 @@
package redispkg
import (
"context"
"testing"
"time"
)
func TestDisabledIsSafe(t *testing.T) {
r := New("")
ctx := context.Background()
if _, ok := r.Get(ctx, "x"); ok {
t.Fatal("disabled Get must miss")
}
r.Set(ctx, "x", "y", time.Second) // 不 panic
if n := r.IncrWindow(ctx, "k", time.Second); n != 1 {
t.Fatal("disabled IncrWindow must allow")
}
un, ok := r.Lock(ctx, "lk", time.Second)
if !ok {
t.Fatal("disabled Lock must always acquire")
}
un()
}