feat: 验证key分离

This commit is contained in:
tbphp
2025-07-11 22:39:28 +08:00
parent 0cf590111f
commit 70554b8fe5
5 changed files with 175 additions and 85 deletions

View File

@@ -14,15 +14,6 @@ type ChannelProxy interface {
// BuildUpstreamURL constructs the target URL for the upstream service.
BuildUpstreamURL(originalURL *url.URL, group *models.Group) (string, error)
// ModifyRequest allows the channel to add specific headers or modify the request
ModifyRequest(req *http.Request, apiKey *models.APIKey, group *models.Group)
// IsStreamRequest checks if the request is for a streaming response,
IsStreamRequest(c *gin.Context, bodyBytes []byte) bool
// ValidateKey checks if the given API key is valid.
ValidateKey(ctx context.Context, key string) (bool, error)
// IsConfigStale checks if the channel's configuration is stale compared to the provided group.
IsConfigStale(group *models.Group) bool
@@ -31,4 +22,16 @@ type ChannelProxy interface {
// GetStreamClient returns the client for streaming requests.
GetStreamClient() *http.Client
// ModifyRequest allows the channel to add specific headers or modify the request
ModifyRequest(req *http.Request, apiKey *models.APIKey, group *models.Group)
// IsStreamRequest checks if the request is for a streaming response,
IsStreamRequest(c *gin.Context, bodyBytes []byte) bool
// ExtractKey extracts the API key from the request.
ExtractKey(c *gin.Context) string
// ValidateKey checks if the given API key is valid.
ValidateKey(ctx context.Context, key string) (bool, error)
}

View File

@@ -40,6 +40,40 @@ func (ch *GeminiChannel) ModifyRequest(req *http.Request, apiKey *models.APIKey,
req.URL.RawQuery = q.Encode()
}
// IsStreamRequest checks if the request is for a streaming response.
func (ch *GeminiChannel) IsStreamRequest(c *gin.Context, bodyBytes []byte) bool {
path := c.Request.URL.Path
if strings.HasSuffix(path, ":streamGenerateContent") {
return true
}
// Also check for standard streaming indicators as a fallback.
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
return true
}
if c.Query("stream") == "true" {
return true
}
return false
}
// ExtractKey extracts the API key from the X-Goog-Api-Key header or the "key" query parameter.
func (ch *GeminiChannel) ExtractKey(c *gin.Context) string {
// 1. Check X-Goog-Api-Key header
if key := c.GetHeader("X-Goog-Api-Key"); key != "" {
return key
}
// 2. Check "key" query parameter
if key := c.Query("key"); key != "" {
return key
}
return ""
}
// ValidateKey checks if the given API key is valid by making a generateContent request.
func (ch *GeminiChannel) ValidateKey(ctx context.Context, key string) (bool, error) {
upstreamURL := ch.getUpstreamURL()
@@ -89,22 +123,3 @@ func (ch *GeminiChannel) ValidateKey(ctx context.Context, key string) (bool, err
return false, fmt.Errorf("[status %d] %s", resp.StatusCode, parsedError)
}
// IsStreamRequest checks if the request is for a streaming response.
// For Gemini, this is primarily determined by the URL path.
func (ch *GeminiChannel) IsStreamRequest(c *gin.Context, bodyBytes []byte) bool {
path := c.Request.URL.Path
if strings.HasSuffix(path, ":streamGenerateContent") {
return true
}
// Also check for standard streaming indicators as a fallback.
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
return true
}
if c.Query("stream") == "true" {
return true
}
return false
}

View File

@@ -33,12 +33,44 @@ func newOpenAIChannel(f *Factory, group *models.Group) (ChannelProxy, error) {
}, nil
}
// ModifyRequest sets the Authorization header for the OpenAI service.
func (ch *OpenAIChannel) ModifyRequest(req *http.Request, apiKey *models.APIKey, group *models.Group) {
req.Header.Set("Authorization", "Bearer "+apiKey.KeyValue)
}
// IsStreamRequest checks if the request is for a streaming response using the pre-read body.
func (ch *OpenAIChannel) IsStreamRequest(c *gin.Context, bodyBytes []byte) bool {
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
return true
}
if c.Query("stream") == "true" {
return true
}
type streamPayload struct {
Stream bool `json:"stream"`
}
var p streamPayload
if err := json.Unmarshal(bodyBytes, &p); err == nil {
return p.Stream
}
return false
}
// ExtractKey extracts the API key from the Authorization header.
func (ch *OpenAIChannel) ExtractKey(c *gin.Context) string {
authHeader := c.GetHeader("Authorization")
if authHeader != "" {
const bearerPrefix = "Bearer "
if strings.HasPrefix(authHeader, bearerPrefix) {
return authHeader[len(bearerPrefix):]
}
}
return ""
}
// ValidateKey checks if the given API key is valid by making a chat completion request.
func (ch *OpenAIChannel) ValidateKey(ctx context.Context, key string) (bool, error) {
upstreamURL := ch.getUpstreamURL()
@@ -90,24 +122,3 @@ func (ch *OpenAIChannel) ValidateKey(ctx context.Context, key string) (bool, err
return false, fmt.Errorf("[status %d] %s", resp.StatusCode, parsedError)
}
// IsStreamRequest checks if the request is for a streaming response using the pre-read body.
func (ch *OpenAIChannel) IsStreamRequest(c *gin.Context, bodyBytes []byte) bool {
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
return true
}
if c.Query("stream") == "true" {
return true
}
type streamPayload struct {
Stream bool `json:"stream"`
}
var p streamPayload
if err := json.Unmarshal(bodyBytes, &p); err == nil {
return p.Stream
}
return false
}