Files
sub2api/backend/internal/service/openai_cyber_session_block.go
T
李建琦 6d655c9903
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

100 lines
3.4 KiB
Go

package service
import (
"context"
"crypto/sha256"
"encoding/hex"
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/gin-gonic/gin"
)
// CyberSessionBlockStore 是 cyber 会话屏蔽表的存取接口。
// repository 层 gatewayCache 附带实现(类型断言探测接入,不改 GatewayCache
// 共享接口);测试 stub 不实现时屏蔽能力自动降级关闭。
type CyberSessionBlockStore interface {
SetCyberSessionBlocked(ctx context.Context, key string, ttl time.Duration) error
IsCyberSessionBlocked(ctx context.Context, key string) (bool, error)
}
// CyberSessionBlockKey 派生会话屏蔽 key:仅用显式会话标识(header
// session_id/conversation_id 或 body prompt_cache_key),混入 apiKeyID 隔离后
// sha256。无显式标识返回空串——调用方必须放行(粒度决策:不退化到
// user/apikey/内容派生)。
func CyberSessionBlockKey(apiKeyID int64, c *gin.Context, body []byte) string {
raw := explicitOpenAISessionID(c, body)
if raw == "" {
return ""
}
isolated := isolateOpenAISessionID(apiKeyID, raw)
sum := sha256.Sum256([]byte(isolated))
return hex.EncodeToString(sum[:])
}
// cyberSessionBlockStore 探测 cache 是否具备屏蔽存储能力。
// 注意:若未来以装饰器包装 GatewayCache(如日志/指标装饰器),该装饰器必须同时实现
// CyberSessionBlockStore,否则会话屏蔽能力将静默降级关闭
// (编译断言 var _ service.CyberSessionBlockStore = (*gatewayCache)(nil) 只覆盖
// *gatewayCache 本体,无法覆盖其外层包装)。
func (s *OpenAIGatewayService) cyberSessionBlockStore() CyberSessionBlockStore {
if s == nil || s.cache == nil {
return nil
}
store, ok := s.cache.(CyberSessionBlockStore)
if !ok {
return nil
}
return store
}
// CyberSessionBlockRuntime 返回 (开关, TTL)。开关默认关。
// 委托给 SettingService.GetCyberSessionBlockRuntime,进程内缓存避免热路径 DB 往返。
func (s *OpenAIGatewayService) CyberSessionBlockRuntime(ctx context.Context) (bool, time.Duration) {
if s == nil || s.settingService == nil {
return false, time.Hour
}
return s.settingService.GetCyberSessionBlockRuntime(ctx)
}
// MarkCyberSessionBlocked 把会话写入屏蔽表(写入点:cyber 命中后)。
// 开关关闭、key 为空或存储不可用时静默跳过。
func (s *OpenAIGatewayService) MarkCyberSessionBlocked(ctx context.Context, key string) {
if key == "" {
return
}
enabled, ttl := s.CyberSessionBlockRuntime(ctx)
if !enabled {
return
}
store := s.cyberSessionBlockStore()
if store == nil {
return
}
if err := store.SetCyberSessionBlocked(ctx, key, ttl); err != nil {
logger.LegacyPrintf("service.openai_gateway", "cyber session block write failed: err=%v", err)
}
}
// IsCyberSessionBlocked 查询会话是否被屏蔽(拦截点)。开关关闭、key 为空、
// 存储不可用或查询出错时返回 false(fail-open:屏蔽是增强防护,不阻断主链路)。
func (s *OpenAIGatewayService) IsCyberSessionBlocked(ctx context.Context, key string) bool {
if key == "" {
return false
}
enabled, _ := s.CyberSessionBlockRuntime(ctx)
if !enabled {
return false
}
store := s.cyberSessionBlockStore()
if store == nil {
return false
}
blocked, err := store.IsCyberSessionBlocked(ctx, key)
if err != nil {
logger.LegacyPrintf("service.openai_gateway", "cyber session block read failed: err=%v", err)
return false
}
return blocked
}