做完了一部分,备份一下,防止以外删除
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Research: Fiber Middleware Integration
|
||||
|
||||
**Feature**: 001-fiber-middleware-integration
|
||||
**Date**: 2025-11-10
|
||||
**Feature**: 001-fiber-middleware-integration
|
||||
**Date**: 2025-11-10
|
||||
**Phase**: 0 - Research & Discovery
|
||||
|
||||
## Overview
|
||||
@@ -26,20 +26,20 @@ This document resolves technical unknowns and establishes best practices for int
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
log.Info("Config file changed", zap.String("file", e.Name))
|
||||
|
||||
|
||||
// Reload config atomically
|
||||
newConfig := &Config{}
|
||||
if err := viper.Unmarshal(newConfig); err != nil {
|
||||
log.Error("Failed to reload config", zap.Error(err))
|
||||
return // Keep existing config
|
||||
}
|
||||
|
||||
|
||||
// Validate new config
|
||||
if err := newConfig.Validate(); err != nil {
|
||||
log.Error("Invalid config", zap.Error(err))
|
||||
return // Keep existing config
|
||||
}
|
||||
|
||||
|
||||
// Atomic swap using sync/atomic
|
||||
atomic.StorePointer(&globalConfig, unsafe.Pointer(newConfig))
|
||||
})
|
||||
@@ -191,12 +191,12 @@ type TokenValidator struct {
|
||||
func (v *TokenValidator) Validate(token string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
|
||||
// Check Redis availability
|
||||
if err := v.redis.Ping(ctx).Err(); err != nil {
|
||||
return "", ErrRedisUnavailable // Fail closed
|
||||
}
|
||||
|
||||
|
||||
// Get user ID from token
|
||||
userID, err := v.redis.Get(ctx, constants.RedisAuthTokenKey(token)).Result()
|
||||
if err == redis.Nil {
|
||||
@@ -205,7 +205,7 @@ func (v *TokenValidator) Validate(token string) (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("redis get: %w", err)
|
||||
}
|
||||
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
@@ -217,12 +217,12 @@ func KeyAuth(validator *validator.TokenValidator, logger *zap.Logger) fiber.Hand
|
||||
userID, err := validator.Validate(key)
|
||||
if err != nil {
|
||||
logger.Warn("Token validation failed",
|
||||
zap.String("request_id", c.Locals("requestid").(string)),
|
||||
zap.String("request_id", c.Locals(constants.ContextKeyRequestID).(string)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
||||
// Store user ID in context
|
||||
c.Locals("user_id", userID)
|
||||
return true, nil
|
||||
@@ -413,32 +413,32 @@ func main() {
|
||||
// Create root context with cancellation
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
|
||||
// Initialize components with context
|
||||
cfg := config.Load()
|
||||
go config.Watch(ctx, cfg) // Pass context to watcher
|
||||
|
||||
|
||||
app := setupApp(cfg)
|
||||
|
||||
|
||||
// Graceful shutdown signal handling
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
|
||||
go func() {
|
||||
if err := app.Listen(cfg.Server.Address); err != nil {
|
||||
log.Fatal("Server failed", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
<-quit // Block until signal
|
||||
log.Info("Shutting down server...")
|
||||
|
||||
|
||||
cancel() // Cancel context (stops config watcher)
|
||||
|
||||
|
||||
if err := app.ShutdownWithTimeout(30 * time.Second); err != nil {
|
||||
log.Error("Forced shutdown", zap.Error(err))
|
||||
}
|
||||
|
||||
|
||||
log.Info("Server stopped")
|
||||
}
|
||||
```
|
||||
@@ -455,7 +455,7 @@ func Watch(ctx context.Context, cfg *Config) {
|
||||
// Reload config logic
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
<-ctx.Done() // Block until cancelled
|
||||
log.Info("Config watcher stopped")
|
||||
}
|
||||
@@ -512,22 +512,22 @@ func TestTokenValidator_Validate(t *testing.T) {
|
||||
},
|
||||
// More cases...
|
||||
}
|
||||
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockRedis := &mock.Redis{}
|
||||
tt.setupMock(mockRedis)
|
||||
|
||||
|
||||
validator := NewTokenValidator(mockRedis, zap.NewNop())
|
||||
userID, err := validator.Validate(tt.token)
|
||||
|
||||
|
||||
if err != tt.wantErr {
|
||||
t.Errorf("got error %v, want %v", err, tt.wantErr)
|
||||
}
|
||||
if userID != tt.wantUser {
|
||||
t.Errorf("got userID %s, want %s", userID, tt.wantUser)
|
||||
}
|
||||
|
||||
|
||||
mockRedis.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
@@ -538,7 +538,7 @@ func TestTokenValidator_Validate(t *testing.T) {
|
||||
```go
|
||||
func TestMiddlewareChain(t *testing.T) {
|
||||
// Start testcontainer Redis
|
||||
redisContainer, err := testcontainers.GenericContainer(ctx,
|
||||
redisContainer, err := testcontainers.GenericContainer(ctx,
|
||||
testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: testcontainers.ContainerRequest{
|
||||
Image: "redis:7-alpine",
|
||||
@@ -548,10 +548,10 @@ func TestMiddlewareChain(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer redisContainer.Terminate(ctx)
|
||||
|
||||
|
||||
// Setup app with middleware
|
||||
app := setupTestApp(redisContainer)
|
||||
|
||||
|
||||
// Test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -571,22 +571,22 @@ func TestMiddlewareChain(t *testing.T) {
|
||||
},
|
||||
// More cases...
|
||||
}
|
||||
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.setupToken != nil {
|
||||
tt.setupToken(redisClient)
|
||||
}
|
||||
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/test", nil)
|
||||
for k, v := range tt.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedStatus, resp.StatusCode)
|
||||
|
||||
|
||||
// Parse response body and check code
|
||||
var body response.Response
|
||||
json.NewDecoder(resp.Body).Decode(&body)
|
||||
|
||||
Reference in New Issue
Block a user