Files
Oandtbphp 6da82242d8 feat(subscription): support custom upstream URLs (#579)
* feat(codex): support custom base URL

* fix(codex): require HTTPS for custom base URL

* docs: document changed subscription functions

* fix(groups): 展示并区分详情中的默认与自定义地址

* fix(groups): 统一上游地址配置交互与图标位置

* fix(subscription): 绑定额度重置操作的上游地址

* fix(gateway): 隔离旧上游请求的健康状态更新

---------

Co-authored-by: tbphp <tangb7420@gmail.com>
2026-09-08 19:44:39 +08:00

95 lines
2.5 KiB
Go

package control
import (
"context"
"net/url"
"strings"
"unicode/utf8"
"github.com/gin-gonic/gin"
"gpt-load/internal/channel"
app_errors "gpt-load/internal/platform/errors"
"gpt-load/internal/platform/response"
)
const channelMaxQueryRunes = 200
type ChannelListResponse struct {
Items []ChannelListItem `json:"items"`
Total int `json:"total"`
}
type ChannelListItem struct {
channel.Descriptor
DefaultBaseURL string `json:"default_base_url"`
}
type channelDefaultBaseURLProvider interface {
DefaultBaseURL(channel.ID) (string, bool, error)
}
func (s *Service) ListChannels(ctx context.Context, query string) (ChannelListResponse, error) {
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return ChannelListResponse{}, err
}
if s == nil || s.channelRegistry == nil {
return ChannelListResponse{}, app_errors.ErrInternalServer
}
descriptors := s.channelRegistry.Search(query)
items := make([]ChannelListItem, 0, len(descriptors))
for _, descriptor := range descriptors {
item := ChannelListItem{Descriptor: descriptor}
if len(descriptor.DefaultBaseURLs) > 0 {
item.DefaultBaseURL = descriptor.DefaultBaseURLs[0]
} else if s.channelDefaultBaseURLs != nil {
baseURL, unique, err := s.channelDefaultBaseURLs.DefaultBaseURL(descriptor.ID)
if err != nil {
return ChannelListResponse{}, err
}
if unique {
item.DefaultBaseURL = baseURL
}
}
items = append(items, item)
}
return ChannelListResponse{Items: items, Total: len(items)}, nil
}
func parseChannelQuery(rawQuery string, forceQuery bool) (string, *app_errors.APIError) {
if forceQuery && rawQuery == "" {
return "", app_errors.ErrBadRequest
}
values, err := url.ParseQuery(rawQuery)
if err != nil {
return "", app_errors.ErrBadRequest
}
for key, entries := range values {
if key != "q" || len(entries) != 1 {
return "", app_errors.ErrBadRequest
}
}
query := strings.TrimSpace(values.Get("q"))
if utf8.RuneCountInString(query) > channelMaxQueryRunes {
return "", app_errors.ErrBadRequest
}
return query, nil
}
func (s *Server) handleListChannels(c *gin.Context) {
query, apiErr := parseChannelQuery(c.Request.URL.RawQuery, c.Request.URL.ForceQuery)
if apiErr != nil {
writeServiceError(c, "list_channels", apiErr)
return
}
result, err := s.service.ListChannels(c.Request.Context(), query)
if err != nil {
writeServiceError(c, "list_channels", err)
return
}
response.SuccessI18n(c, "common.success", result)
}