Merge branch 'tbphp:main' into main
This commit is contained in:
10
.github/workflows/docker-build.yml
vendored
10
.github/workflows/docker-build.yml
vendored
@@ -18,6 +18,12 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Log in to Container Registry
|
- name: Log in to Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -29,7 +35,9 @@ jobs:
|
|||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: |
|
||||||
|
tbphp/gpt-load
|
||||||
|
ghcr.io/${{ github.repository }}
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
tags: |
|
tags: |
|
||||||
|
@@ -204,7 +204,7 @@ GPT-Load 采用双层配置架构:
|
|||||||
| 请求超时 | `request_timeout` | 600 | ✅ | 转发请求完整生命周期超时(秒) |
|
| 请求超时 | `request_timeout` | 600 | ✅ | 转发请求完整生命周期超时(秒) |
|
||||||
| 连接超时 | `connect_timeout` | 15 | ✅ | 与上游服务建立连接超时(秒) |
|
| 连接超时 | `connect_timeout` | 15 | ✅ | 与上游服务建立连接超时(秒) |
|
||||||
| 空闲连接超时 | `idle_conn_timeout` | 120 | ✅ | HTTP 客户端空闲连接超时(秒) |
|
| 空闲连接超时 | `idle_conn_timeout` | 120 | ✅ | HTTP 客户端空闲连接超时(秒) |
|
||||||
| 响应头超时 | `response_header_timeout` | 15 | ✅ | 等待上游响应头超时(秒) |
|
| 响应头超时 | `response_header_timeout` | 600 | ✅ | 等待上游响应头超时(秒) |
|
||||||
| 最大空闲连接数 | `max_idle_conns` | 100 | ✅ | 连接池最大空闲连接总数 |
|
| 最大空闲连接数 | `max_idle_conns` | 100 | ✅ | 连接池最大空闲连接总数 |
|
||||||
| 每主机最大空闲连接数 | `max_idle_conns_per_host` | 50 | ✅ | 每个上游主机最大空闲连接数 |
|
| 每主机最大空闲连接数 | `max_idle_conns_per_host` | 50 | ✅ | 每个上游主机最大空闲连接数 |
|
||||||
|
|
||||||
|
@@ -204,7 +204,7 @@ Dynamic configuration is stored in the database and supports real-time modificat
|
|||||||
| Request Timeout | `request_timeout` | 600 | ✅ | Forward request complete lifecycle timeout (seconds) |
|
| Request Timeout | `request_timeout` | 600 | ✅ | Forward request complete lifecycle timeout (seconds) |
|
||||||
| Connection Timeout | `connect_timeout` | 15 | ✅ | Timeout for establishing connection with upstream service (seconds) |
|
| Connection Timeout | `connect_timeout` | 15 | ✅ | Timeout for establishing connection with upstream service (seconds) |
|
||||||
| Idle Connection Timeout | `idle_conn_timeout` | 120 | ✅ | HTTP client idle connection timeout (seconds) |
|
| Idle Connection Timeout | `idle_conn_timeout` | 120 | ✅ | HTTP client idle connection timeout (seconds) |
|
||||||
| Response Header Timeout | `response_header_timeout` | 15 | ✅ | Timeout for waiting upstream response headers (seconds) |
|
| Response Header Timeout | `response_header_timeout` | 600 | ✅ | Timeout for waiting upstream response headers (seconds) |
|
||||||
| Max Idle Connections | `max_idle_conns` | 100 | ✅ | Connection pool maximum total idle connections |
|
| Max Idle Connections | `max_idle_conns` | 100 | ✅ | Connection pool maximum total idle connections |
|
||||||
| Max Idle Connections Per Host | `max_idle_conns_per_host` | 50 | ✅ | Maximum idle connections per upstream host |
|
| Max Idle Connections Per Host | `max_idle_conns_per_host` | 50 | ✅ | Maximum idle connections per upstream host |
|
||||||
|
|
||||||
|
@@ -83,8 +83,8 @@ func isValidGroupName(name string) bool {
|
|||||||
if name == "" {
|
if name == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// 允许使用小写字母、数字和下划线,长度在 3 到 30 个字符之间
|
// 允许使用小写字母、数字、下划线和中划线,长度在 3 到 30 个字符之间
|
||||||
match, _ := regexp.MatchString("^[a-z0-9_]{3,30}$", name)
|
match, _ := regexp.MatchString("^[a-z0-9_-]{3,30}$", name)
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ func (s *Server) CreateGroup(c *gin.Context) {
|
|||||||
// Data Cleaning and Validation
|
// Data Cleaning and Validation
|
||||||
name := strings.TrimSpace(req.Name)
|
name := strings.TrimSpace(req.Name)
|
||||||
if !isValidGroupName(name) {
|
if !isValidGroupName(name) {
|
||||||
response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "Invalid group name format. Use 3-30 lowercase letters, numbers, and underscores."))
|
response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "无效的分组名称。只能包含小写字母、数字、中划线或下划线,长度3-30位"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ func (s *Server) UpdateGroup(c *gin.Context) {
|
|||||||
if req.Name != nil {
|
if req.Name != nil {
|
||||||
cleanedName := strings.TrimSpace(*req.Name)
|
cleanedName := strings.TrimSpace(*req.Name)
|
||||||
if !isValidGroupName(cleanedName) {
|
if !isValidGroupName(cleanedName) {
|
||||||
response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "Invalid group name format. Name is required and must be 3-30 lowercase letters, numbers, or underscores."))
|
response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, "无效的分组名称格式。只能包含小写字母、数字、中划线或下划线,长度3-30位"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
group.Name = cleanedName
|
group.Name = cleanedName
|
||||||
|
@@ -136,11 +136,6 @@ func Auth(
|
|||||||
if strings.HasPrefix(path, "/api") {
|
if strings.HasPrefix(path, "/api") {
|
||||||
// Handle backend API authentication
|
// Handle backend API authentication
|
||||||
key = extractBearerKey(c)
|
key = extractBearerKey(c)
|
||||||
if key == "" || key != authConfig.Key {
|
|
||||||
response.Error(c, app_errors.ErrUnauthorized)
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(path, "/proxy/") {
|
} else if strings.HasPrefix(path, "/proxy/") {
|
||||||
// Handle proxy authentication
|
// Handle proxy authentication
|
||||||
key, err = extractProxyKey(c, groupManager, channelFactory)
|
key, err = extractProxyKey(c, groupManager, channelFactory)
|
||||||
@@ -161,7 +156,7 @@ func Auth(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if key == "" {
|
if key == "" || key != authConfig.Key {
|
||||||
response.Error(c, app_errors.ErrUnauthorized)
|
response.Error(c, app_errors.ErrUnauthorized)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
|
@@ -188,7 +188,7 @@ func (ps *ProxyServer) executeRequestWithRetry(
|
|||||||
var parsedError string
|
var parsedError string
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusCode = 0
|
statusCode = 500
|
||||||
errorMessage = err.Error()
|
errorMessage = err.Error()
|
||||||
logrus.Debugf("Request failed (attempt %d/%d) for key %s: %v", retryCount+1, cfg.MaxRetries, utils.MaskAPIKey(apiKey.KeyValue), err)
|
logrus.Debugf("Request failed (attempt %d/%d) for key %s: %v", retryCount+1, cfg.MaxRetries, utils.MaskAPIKey(apiKey.KeyValue), err)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -23,10 +23,10 @@ type SystemSettings struct {
|
|||||||
RequestLogWriteIntervalMinutes int `json:"request_log_write_interval_minutes" default:"5" name:"日志延迟写入周期(分钟)" category:"基础参数" desc:"请求日志从缓存写入数据库的周期(分钟),0为实时写入数据。" validate:"min=0"`
|
RequestLogWriteIntervalMinutes int `json:"request_log_write_interval_minutes" default:"5" name:"日志延迟写入周期(分钟)" category:"基础参数" desc:"请求日志从缓存写入数据库的周期(分钟),0为实时写入数据。" validate:"min=0"`
|
||||||
|
|
||||||
// 请求设置
|
// 请求设置
|
||||||
RequestTimeout int `json:"request_timeout" default:"600" name:"请求超时(秒)" category:"请求设置" desc:"转发请求的完整生命周期超时(秒),包括连接、重试等。" validate:"min=1"`
|
RequestTimeout int `json:"request_timeout" default:"600" name:"请求超时(秒)" category:"请求设置" desc:"转发请求的完整生命周期超时(秒)等。" validate:"min=1"`
|
||||||
ConnectTimeout int `json:"connect_timeout" default:"15" name:"连接超时(秒)" category:"请求设置" desc:"与上游服务建立新连接的超时时间(秒)。" validate:"min=1"`
|
ConnectTimeout int `json:"connect_timeout" default:"15" name:"连接超时(秒)" category:"请求设置" desc:"与上游服务建立新连接的超时时间(秒)。" validate:"min=1"`
|
||||||
IdleConnTimeout int `json:"idle_conn_timeout" default:"120" name:"空闲连接超时(秒)" category:"请求设置" desc:"HTTP 客户端中空闲连接的超时时间(秒)。" validate:"min=1"`
|
IdleConnTimeout int `json:"idle_conn_timeout" default:"120" name:"空闲连接超时(秒)" category:"请求设置" desc:"HTTP 客户端中空闲连接的超时时间(秒)。" validate:"min=1"`
|
||||||
ResponseHeaderTimeout int `json:"response_header_timeout" default:"15" name:"响应头超时(秒)" category:"请求设置" desc:"等待上游服务响应头的最长时间(秒),用于流式请求。" validate:"min=1"`
|
ResponseHeaderTimeout int `json:"response_header_timeout" default:"600" name:"响应头超时(秒)" category:"请求设置" desc:"等待上游服务响应头的最长时间(秒)。" validate:"min=1"`
|
||||||
MaxIdleConns int `json:"max_idle_conns" default:"100" name:"最大空闲连接数" category:"请求设置" desc:"HTTP 客户端连接池中允许的最大空闲连接总数。" validate:"min=1"`
|
MaxIdleConns int `json:"max_idle_conns" default:"100" name:"最大空闲连接数" category:"请求设置" desc:"HTTP 客户端连接池中允许的最大空闲连接总数。" validate:"min=1"`
|
||||||
MaxIdleConnsPerHost int `json:"max_idle_conns_per_host" default:"50" name:"每主机最大空闲连接数" category:"请求设置" desc:"HTTP 客户端连接池对每个上游主机允许的最大空闲连接数。" validate:"min=1"`
|
MaxIdleConnsPerHost int `json:"max_idle_conns_per_host" default:"50" name:"每主机最大空闲连接数" category:"请求设置" desc:"HTTP 客户端连接池对每个上游主机允许的最大空闲连接数。" validate:"min=1"`
|
||||||
|
|
||||||
|
@@ -92,8 +92,8 @@ const rules: FormRules = {
|
|||||||
trigger: ["blur", "input"],
|
trigger: ["blur", "input"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /^[a-z]+$/,
|
pattern: /^[a-z0-9_-]{3,30}$/,
|
||||||
message: "只能输入小写字母",
|
message: "只能包含小写字母、数字、中划线或下划线,长度3-30位",
|
||||||
trigger: ["blur", "input"],
|
trigger: ["blur", "input"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -330,7 +330,10 @@ async function handleSubmit() {
|
|||||||
<h4 class="section-title">基础信息</h4>
|
<h4 class="section-title">基础信息</h4>
|
||||||
|
|
||||||
<n-form-item label="分组名称" path="name">
|
<n-form-item label="分组名称" path="name">
|
||||||
<n-input v-model:value="formData.name" placeholder="请输入分组名称,如:gemini" />
|
<n-input
|
||||||
|
v-model:value="formData.name"
|
||||||
|
placeholder="作为路由的一部分,如:gemini-pro-group"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item label="显示名称" path="display_name">
|
<n-form-item label="显示名称" path="display_name">
|
||||||
|
Reference in New Issue
Block a user