fix: P1/P2 优化 - OAuth验证 + API响应 + 缓存击穿 + Webhook关闭
P1 - OAuth auth_url origin 验证: - 添加 validateOAuthUrl() 函数验证 OAuth URL origin - 仅允许同源或可信 OAuth 提供商 - LoginPage 和 ProfileSecurityPage 调用前验证 P2 - API 响应运行时类型验证: - 添加 isApiResponse() 运行时验证函数 - parseJsonResponse 验证响应结构完整性 P2 - 缓存击穿防护 (singleflight): - AuthMiddleware.isJTIBlacklisted 使用 singleflight.Group - 防止 L1 miss 时并发请求同时打 L2 P2 - Webhook 服务优雅关闭: - WebhookService 添加 Shutdown() 方法 - 服务器关闭时等待 worker 完成 - main.go 集成 shutdown 调用
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/user-management-system/internal/auth"
|
||||
"github.com/user-management-system/internal/cache"
|
||||
@@ -25,6 +26,7 @@ type AuthMiddleware struct {
|
||||
permissionRepo *repository.PermissionRepository
|
||||
l1Cache *cache.L1Cache
|
||||
cacheManager *cache.CacheManager
|
||||
sfGroup singleflight.Group
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(
|
||||
@@ -116,12 +118,22 @@ func (m *AuthMiddleware) isJTIBlacklisted(jti string) bool {
|
||||
}
|
||||
|
||||
key := "jwt_blacklist:" + jti
|
||||
|
||||
// 先检查 L1 缓存
|
||||
if _, ok := m.l1Cache.Get(key); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// L1 miss 时使用 singleflight 防止缓存击穿
|
||||
// 多个并发请求只会触发一次 L2 查询
|
||||
if m.cacheManager != nil {
|
||||
if _, ok := m.cacheManager.Get(context.Background(), key); ok {
|
||||
val, err, _ := m.sfGroup.Do(key, func() (interface{}, error) {
|
||||
found, _ := m.cacheManager.Get(context.Background(), key)
|
||||
return found, nil
|
||||
})
|
||||
if err == nil && val != nil {
|
||||
// 回写 L1 缓存
|
||||
m.l1Cache.Set(key, true, 5*time.Minute)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,29 @@ func (s *WebhookService) startWorkers() {
|
||||
})
|
||||
}
|
||||
|
||||
// Shutdown 优雅关闭 Webhook 服务
|
||||
// 等待所有处理中的投递任务完成,最多等待 timeout
|
||||
func (s *WebhookService) Shutdown(ctx context.Context) error {
|
||||
// 1. 停止接收新任务:关闭队列
|
||||
close(s.queue)
|
||||
|
||||
// 2. 等待所有 worker 完成
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
s.wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// 正常完成
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Publish 发布事件:找到订阅该事件的所有 Webhook,异步投递
|
||||
func (s *WebhookService) Publish(ctx context.Context, eventType domain.WebhookEventType, data interface{}) {
|
||||
if !s.config.Enabled {
|
||||
@@ -270,7 +293,10 @@ func (s *WebhookService) recordDelivery(task *deliveryTask, statusCode int, body
|
||||
if success {
|
||||
delivery.DeliveredAt = &now
|
||||
}
|
||||
_ = s.repo.CreateDelivery(context.Background(), delivery)
|
||||
// 使用带超时的独立 context,防止 DB 写入无限等待
|
||||
writeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = s.repo.CreateDelivery(writeCtx, delivery)
|
||||
}
|
||||
|
||||
// CreateWebhook 创建 Webhook
|
||||
|
||||
Reference in New Issue
Block a user