|
|
package controller |
|
|
|
|
|
import ( |
|
|
"encoding/json" |
|
|
"fmt" |
|
|
"net/http" |
|
|
"strconv" |
|
|
"strings" |
|
|
|
|
|
"github.com/QuantumNous/new-api/common" |
|
|
"github.com/QuantumNous/new-api/constant" |
|
|
"github.com/QuantumNous/new-api/dto" |
|
|
"github.com/QuantumNous/new-api/model" |
|
|
"github.com/QuantumNous/new-api/relay/channel/volcengine" |
|
|
"github.com/QuantumNous/new-api/service" |
|
|
|
|
|
"github.com/gin-gonic/gin" |
|
|
) |
|
|
|
|
|
type OpenAIModel struct { |
|
|
ID string `json:"id"` |
|
|
Object string `json:"object"` |
|
|
Created int64 `json:"created"` |
|
|
OwnedBy string `json:"owned_by"` |
|
|
Permission []struct { |
|
|
ID string `json:"id"` |
|
|
Object string `json:"object"` |
|
|
Created int64 `json:"created"` |
|
|
AllowCreateEngine bool `json:"allow_create_engine"` |
|
|
AllowSampling bool `json:"allow_sampling"` |
|
|
AllowLogprobs bool `json:"allow_logprobs"` |
|
|
AllowSearchIndices bool `json:"allow_search_indices"` |
|
|
AllowView bool `json:"allow_view"` |
|
|
AllowFineTuning bool `json:"allow_fine_tuning"` |
|
|
Organization string `json:"organization"` |
|
|
Group string `json:"group"` |
|
|
IsBlocking bool `json:"is_blocking"` |
|
|
} `json:"permission"` |
|
|
Root string `json:"root"` |
|
|
Parent string `json:"parent"` |
|
|
} |
|
|
|
|
|
type OpenAIModelsResponse struct { |
|
|
Data []OpenAIModel `json:"data"` |
|
|
Success bool `json:"success"` |
|
|
} |
|
|
|
|
|
func parseStatusFilter(statusParam string) int { |
|
|
switch strings.ToLower(statusParam) { |
|
|
case "enabled", "1": |
|
|
return common.ChannelStatusEnabled |
|
|
case "disabled", "0": |
|
|
return 0 |
|
|
default: |
|
|
return -1 |
|
|
} |
|
|
} |
|
|
|
|
|
func clearChannelInfo(channel *model.Channel) { |
|
|
if channel.ChannelInfo.IsMultiKey { |
|
|
channel.ChannelInfo.MultiKeyDisabledReason = nil |
|
|
channel.ChannelInfo.MultiKeyDisabledTime = nil |
|
|
} |
|
|
} |
|
|
|
|
|
func GetAllChannels(c *gin.Context) { |
|
|
pageInfo := common.GetPageQuery(c) |
|
|
channelData := make([]*model.Channel, 0) |
|
|
idSort, _ := strconv.ParseBool(c.Query("id_sort")) |
|
|
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode")) |
|
|
statusParam := c.Query("status") |
|
|
|
|
|
statusFilter := parseStatusFilter(statusParam) |
|
|
|
|
|
typeStr := c.Query("type") |
|
|
typeFilter := -1 |
|
|
if typeStr != "" { |
|
|
if t, err := strconv.Atoi(typeStr); err == nil { |
|
|
typeFilter = t |
|
|
} |
|
|
} |
|
|
|
|
|
var total int64 |
|
|
|
|
|
if enableTagMode { |
|
|
tags, err := model.GetPaginatedTags(pageInfo.GetStartIdx(), pageInfo.GetPageSize()) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) |
|
|
return |
|
|
} |
|
|
for _, tag := range tags { |
|
|
if tag == nil || *tag == "" { |
|
|
continue |
|
|
} |
|
|
tagChannels, err := model.GetChannelsByTag(*tag, idSort, false) |
|
|
if err != nil { |
|
|
continue |
|
|
} |
|
|
filtered := make([]*model.Channel, 0) |
|
|
for _, ch := range tagChannels { |
|
|
if statusFilter == common.ChannelStatusEnabled && ch.Status != common.ChannelStatusEnabled { |
|
|
continue |
|
|
} |
|
|
if statusFilter == 0 && ch.Status == common.ChannelStatusEnabled { |
|
|
continue |
|
|
} |
|
|
if typeFilter >= 0 && ch.Type != typeFilter { |
|
|
continue |
|
|
} |
|
|
filtered = append(filtered, ch) |
|
|
} |
|
|
channelData = append(channelData, filtered...) |
|
|
} |
|
|
total, _ = model.CountAllTags() |
|
|
} else { |
|
|
baseQuery := model.DB.Model(&model.Channel{}) |
|
|
if typeFilter >= 0 { |
|
|
baseQuery = baseQuery.Where("type = ?", typeFilter) |
|
|
} |
|
|
if statusFilter == common.ChannelStatusEnabled { |
|
|
baseQuery = baseQuery.Where("status = ?", common.ChannelStatusEnabled) |
|
|
} else if statusFilter == 0 { |
|
|
baseQuery = baseQuery.Where("status != ?", common.ChannelStatusEnabled) |
|
|
} |
|
|
|
|
|
baseQuery.Count(&total) |
|
|
|
|
|
order := "priority desc" |
|
|
if idSort { |
|
|
order = "id desc" |
|
|
} |
|
|
|
|
|
err := baseQuery.Order(order).Limit(pageInfo.GetPageSize()).Offset(pageInfo.GetStartIdx()).Omit("key").Find(&channelData).Error |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
for _, datum := range channelData { |
|
|
clearChannelInfo(datum) |
|
|
} |
|
|
|
|
|
countQuery := model.DB.Model(&model.Channel{}) |
|
|
if statusFilter == common.ChannelStatusEnabled { |
|
|
countQuery = countQuery.Where("status = ?", common.ChannelStatusEnabled) |
|
|
} else if statusFilter == 0 { |
|
|
countQuery = countQuery.Where("status != ?", common.ChannelStatusEnabled) |
|
|
} |
|
|
var results []struct { |
|
|
Type int64 |
|
|
Count int64 |
|
|
} |
|
|
_ = countQuery.Select("type, count(*) as count").Group("type").Find(&results).Error |
|
|
typeCounts := make(map[int64]int64) |
|
|
for _, r := range results { |
|
|
typeCounts[r.Type] = r.Count |
|
|
} |
|
|
common.ApiSuccess(c, gin.H{ |
|
|
"items": channelData, |
|
|
"total": total, |
|
|
"page": pageInfo.GetPage(), |
|
|
"page_size": pageInfo.GetPageSize(), |
|
|
"type_counts": typeCounts, |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func FetchUpstreamModels(c *gin.Context) { |
|
|
id, err := strconv.Atoi(c.Param("id")) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
channel, err := model.GetChannelById(id, true) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
baseURL := constant.ChannelBaseURLs[channel.Type] |
|
|
if channel.GetBaseURL() != "" { |
|
|
baseURL = channel.GetBaseURL() |
|
|
} |
|
|
|
|
|
var url string |
|
|
switch channel.Type { |
|
|
case constant.ChannelTypeGemini: |
|
|
|
|
|
url = fmt.Sprintf("%s/v1beta/openai/models", baseURL) |
|
|
case constant.ChannelTypeAli: |
|
|
url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL) |
|
|
case constant.ChannelTypeZhipu_v4: |
|
|
url = fmt.Sprintf("%s/api/paas/v4/models", baseURL) |
|
|
case constant.ChannelTypeVolcEngine: |
|
|
if baseURL == volcengine.DoubaoCodingPlan { |
|
|
url = fmt.Sprintf("%s/v1/models", volcengine.DoubaoCodingPlanOpenAIBaseURL) |
|
|
} else { |
|
|
url = fmt.Sprintf("%s/v1/models", baseURL) |
|
|
} |
|
|
default: |
|
|
url = fmt.Sprintf("%s/v1/models", baseURL) |
|
|
} |
|
|
|
|
|
|
|
|
key, _, apiErr := channel.GetNextEnabledKey() |
|
|
if apiErr != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": fmt.Sprintf("获取渠道密钥失败: %s", apiErr.Error()), |
|
|
}) |
|
|
return |
|
|
} |
|
|
key = strings.TrimSpace(key) |
|
|
|
|
|
|
|
|
var body []byte |
|
|
switch channel.Type { |
|
|
case constant.ChannelTypeAnthropic: |
|
|
body, err = GetResponseBody("GET", url, channel, GetClaudeAuthHeader(key)) |
|
|
default: |
|
|
body, err = GetResponseBody("GET", url, channel, GetAuthHeader(key)) |
|
|
} |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
var result OpenAIModelsResponse |
|
|
if err = json.Unmarshal(body, &result); err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": fmt.Sprintf("解析响应失败: %s", err.Error()), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
var ids []string |
|
|
for _, model := range result.Data { |
|
|
id := model.ID |
|
|
if channel.Type == constant.ChannelTypeGemini { |
|
|
id = strings.TrimPrefix(id, "models/") |
|
|
} |
|
|
ids = append(ids, id) |
|
|
} |
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": ids, |
|
|
}) |
|
|
} |
|
|
|
|
|
func FixChannelsAbilities(c *gin.Context) { |
|
|
success, fails, err := model.FixAbility() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": gin.H{ |
|
|
"success": success, |
|
|
"fails": fails, |
|
|
}, |
|
|
}) |
|
|
} |
|
|
|
|
|
func SearchChannels(c *gin.Context) { |
|
|
keyword := c.Query("keyword") |
|
|
group := c.Query("group") |
|
|
modelKeyword := c.Query("model") |
|
|
statusParam := c.Query("status") |
|
|
statusFilter := parseStatusFilter(statusParam) |
|
|
idSort, _ := strconv.ParseBool(c.Query("id_sort")) |
|
|
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode")) |
|
|
channelData := make([]*model.Channel, 0) |
|
|
if enableTagMode { |
|
|
tags, err := model.SearchTags(keyword, group, modelKeyword, idSort) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
for _, tag := range tags { |
|
|
if tag != nil && *tag != "" { |
|
|
tagChannel, err := model.GetChannelsByTag(*tag, idSort, false) |
|
|
if err == nil { |
|
|
channelData = append(channelData, tagChannel...) |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
channels, err := model.SearchChannels(keyword, group, modelKeyword, idSort) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
channelData = channels |
|
|
} |
|
|
|
|
|
if statusFilter == common.ChannelStatusEnabled || statusFilter == 0 { |
|
|
filtered := make([]*model.Channel, 0, len(channelData)) |
|
|
for _, ch := range channelData { |
|
|
if statusFilter == common.ChannelStatusEnabled && ch.Status != common.ChannelStatusEnabled { |
|
|
continue |
|
|
} |
|
|
if statusFilter == 0 && ch.Status == common.ChannelStatusEnabled { |
|
|
continue |
|
|
} |
|
|
filtered = append(filtered, ch) |
|
|
} |
|
|
channelData = filtered |
|
|
} |
|
|
|
|
|
|
|
|
typeCounts := make(map[int64]int64) |
|
|
for _, channel := range channelData { |
|
|
typeCounts[int64(channel.Type)]++ |
|
|
} |
|
|
|
|
|
typeParam := c.Query("type") |
|
|
typeFilter := -1 |
|
|
if typeParam != "" { |
|
|
if tp, err := strconv.Atoi(typeParam); err == nil { |
|
|
typeFilter = tp |
|
|
} |
|
|
} |
|
|
|
|
|
if typeFilter >= 0 { |
|
|
filtered := make([]*model.Channel, 0, len(channelData)) |
|
|
for _, ch := range channelData { |
|
|
if ch.Type == typeFilter { |
|
|
filtered = append(filtered, ch) |
|
|
} |
|
|
} |
|
|
channelData = filtered |
|
|
} |
|
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("p", "1")) |
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) |
|
|
if page < 1 { |
|
|
page = 1 |
|
|
} |
|
|
if pageSize <= 0 { |
|
|
pageSize = 20 |
|
|
} |
|
|
|
|
|
total := len(channelData) |
|
|
startIdx := (page - 1) * pageSize |
|
|
if startIdx > total { |
|
|
startIdx = total |
|
|
} |
|
|
endIdx := startIdx + pageSize |
|
|
if endIdx > total { |
|
|
endIdx = total |
|
|
} |
|
|
|
|
|
pagedData := channelData[startIdx:endIdx] |
|
|
|
|
|
for _, datum := range pagedData { |
|
|
clearChannelInfo(datum) |
|
|
} |
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": gin.H{ |
|
|
"items": pagedData, |
|
|
"total": total, |
|
|
"type_counts": typeCounts, |
|
|
}, |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func GetChannel(c *gin.Context) { |
|
|
id, err := strconv.Atoi(c.Param("id")) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
channel, err := model.GetChannelById(id, false) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
if channel != nil { |
|
|
clearChannelInfo(channel) |
|
|
} |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": channel, |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func GetChannelKey(c *gin.Context) { |
|
|
userId := c.GetInt("id") |
|
|
channelId, err := strconv.Atoi(c.Param("id")) |
|
|
if err != nil { |
|
|
common.ApiError(c, fmt.Errorf("渠道ID格式错误: %v", err)) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
channel, err := model.GetChannelById(channelId, true) |
|
|
if err != nil { |
|
|
common.ApiError(c, fmt.Errorf("获取渠道信息失败: %v", err)) |
|
|
return |
|
|
} |
|
|
|
|
|
if channel == nil { |
|
|
common.ApiError(c, fmt.Errorf("渠道不存在")) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("查看渠道密钥信息 (渠道ID: %d)", channelId)) |
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "获取成功", |
|
|
"data": map[string]interface{}{ |
|
|
"key": channel.Key, |
|
|
}, |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
func validateTwoFactorAuth(twoFA *model.TwoFA, code string) bool { |
|
|
|
|
|
if cleanCode, err := common.ValidateNumericCode(code); err == nil { |
|
|
if isValid, _ := twoFA.ValidateTOTPAndUpdateUsage(cleanCode); isValid { |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if isValid, err := twoFA.ValidateBackupCodeAndUpdateUsage(code); err == nil && isValid { |
|
|
return true |
|
|
} |
|
|
|
|
|
return false |
|
|
} |
|
|
|
|
|
|
|
|
func validateChannel(channel *model.Channel, isAdd bool) error { |
|
|
|
|
|
if err := channel.ValidateSettings(); err != nil { |
|
|
return fmt.Errorf("渠道额外设置[channel setting] 格式错误:%s", err.Error()) |
|
|
} |
|
|
|
|
|
|
|
|
if isAdd { |
|
|
if channel == nil || channel.Key == "" { |
|
|
return fmt.Errorf("channel cannot be empty") |
|
|
} |
|
|
|
|
|
|
|
|
for _, m := range channel.GetModels() { |
|
|
if len(m) > 255 { |
|
|
return fmt.Errorf("模型名称过长: %s", m) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if channel.Type == constant.ChannelTypeVertexAi { |
|
|
if channel.Other == "" { |
|
|
return fmt.Errorf("部署地区不能为空") |
|
|
} |
|
|
|
|
|
regionMap, err := common.StrToMap(channel.Other) |
|
|
if err != nil { |
|
|
return fmt.Errorf("部署地区必须是标准的Json格式,例如{\"default\": \"us-central1\", \"region2\": \"us-east1\"}") |
|
|
} |
|
|
|
|
|
if regionMap["default"] == nil { |
|
|
return fmt.Errorf("部署地区必须包含default字段") |
|
|
} |
|
|
} |
|
|
|
|
|
return nil |
|
|
} |
|
|
|
|
|
type AddChannelRequest struct { |
|
|
Mode string `json:"mode"` |
|
|
MultiKeyMode constant.MultiKeyMode `json:"multi_key_mode"` |
|
|
BatchAddSetKeyPrefix2Name bool `json:"batch_add_set_key_prefix_2_name"` |
|
|
Channel *model.Channel `json:"channel"` |
|
|
} |
|
|
|
|
|
func getVertexArrayKeys(keys string) ([]string, error) { |
|
|
if keys == "" { |
|
|
return nil, nil |
|
|
} |
|
|
var keyArray []interface{} |
|
|
err := common.Unmarshal([]byte(keys), &keyArray) |
|
|
if err != nil { |
|
|
return nil, fmt.Errorf("批量添加 Vertex AI 必须使用标准的JsonArray格式,例如[{key1}, {key2}...],请检查输入: %w", err) |
|
|
} |
|
|
cleanKeys := make([]string, 0, len(keyArray)) |
|
|
for _, key := range keyArray { |
|
|
var keyStr string |
|
|
switch v := key.(type) { |
|
|
case string: |
|
|
keyStr = strings.TrimSpace(v) |
|
|
default: |
|
|
bytes, err := json.Marshal(v) |
|
|
if err != nil { |
|
|
return nil, fmt.Errorf("Vertex AI key JSON 编码失败: %w", err) |
|
|
} |
|
|
keyStr = string(bytes) |
|
|
} |
|
|
if keyStr != "" { |
|
|
cleanKeys = append(cleanKeys, keyStr) |
|
|
} |
|
|
} |
|
|
if len(cleanKeys) == 0 { |
|
|
return nil, fmt.Errorf("批量添加 Vertex AI 的 keys 不能为空") |
|
|
} |
|
|
return cleanKeys, nil |
|
|
} |
|
|
|
|
|
func AddChannel(c *gin.Context) { |
|
|
addChannelRequest := AddChannelRequest{} |
|
|
err := c.ShouldBindJSON(&addChannelRequest) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
if err := validateChannel(addChannelRequest.Channel, true); err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
addChannelRequest.Channel.CreatedTime = common.GetTimestamp() |
|
|
keys := make([]string, 0) |
|
|
switch addChannelRequest.Mode { |
|
|
case "multi_to_single": |
|
|
addChannelRequest.Channel.ChannelInfo.IsMultiKey = true |
|
|
addChannelRequest.Channel.ChannelInfo.MultiKeyMode = addChannelRequest.MultiKeyMode |
|
|
if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi && addChannelRequest.Channel.GetOtherSettings().VertexKeyType != dto.VertexKeyTypeAPIKey { |
|
|
array, err := getVertexArrayKeys(addChannelRequest.Channel.Key) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(array) |
|
|
addChannelRequest.Channel.Key = strings.Join(array, "\n") |
|
|
} else { |
|
|
cleanKeys := make([]string, 0) |
|
|
for _, key := range strings.Split(addChannelRequest.Channel.Key, "\n") { |
|
|
if key == "" { |
|
|
continue |
|
|
} |
|
|
key = strings.TrimSpace(key) |
|
|
cleanKeys = append(cleanKeys, key) |
|
|
} |
|
|
addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(cleanKeys) |
|
|
addChannelRequest.Channel.Key = strings.Join(cleanKeys, "\n") |
|
|
} |
|
|
keys = []string{addChannelRequest.Channel.Key} |
|
|
case "batch": |
|
|
if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi && addChannelRequest.Channel.GetOtherSettings().VertexKeyType != dto.VertexKeyTypeAPIKey { |
|
|
|
|
|
keys, err = getVertexArrayKeys(addChannelRequest.Channel.Key) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
} else { |
|
|
keys = strings.Split(addChannelRequest.Channel.Key, "\n") |
|
|
} |
|
|
case "single": |
|
|
keys = []string{addChannelRequest.Channel.Key} |
|
|
default: |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "不支持的添加模式", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
channels := make([]model.Channel, 0, len(keys)) |
|
|
for _, key := range keys { |
|
|
if key == "" { |
|
|
continue |
|
|
} |
|
|
localChannel := addChannelRequest.Channel |
|
|
localChannel.Key = key |
|
|
if addChannelRequest.BatchAddSetKeyPrefix2Name && len(keys) > 1 { |
|
|
keyPrefix := localChannel.Key |
|
|
if len(localChannel.Key) > 8 { |
|
|
keyPrefix = localChannel.Key[:8] |
|
|
} |
|
|
localChannel.Name = fmt.Sprintf("%s %s", localChannel.Name, keyPrefix) |
|
|
} |
|
|
channels = append(channels, *localChannel) |
|
|
} |
|
|
err = model.BatchInsertChannels(channels) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
service.ResetProxyClientCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func DeleteChannel(c *gin.Context) { |
|
|
id, _ := strconv.Atoi(c.Param("id")) |
|
|
channel := model.Channel{Id: id} |
|
|
err := channel.Delete() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func DeleteDisabledChannel(c *gin.Context) { |
|
|
rows, err := model.DeleteDisabledChannel() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": rows, |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
type ChannelTag struct { |
|
|
Tag string `json:"tag"` |
|
|
NewTag *string `json:"new_tag"` |
|
|
Priority *int64 `json:"priority"` |
|
|
Weight *uint `json:"weight"` |
|
|
ModelMapping *string `json:"model_mapping"` |
|
|
Models *string `json:"models"` |
|
|
Groups *string `json:"groups"` |
|
|
ParamOverride *string `json:"param_override"` |
|
|
HeaderOverride *string `json:"header_override"` |
|
|
} |
|
|
|
|
|
func DisableTagChannels(c *gin.Context) { |
|
|
channelTag := ChannelTag{} |
|
|
err := c.ShouldBindJSON(&channelTag) |
|
|
if err != nil || channelTag.Tag == "" { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "参数错误", |
|
|
}) |
|
|
return |
|
|
} |
|
|
err = model.DisableChannelByTag(channelTag.Tag) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func EnableTagChannels(c *gin.Context) { |
|
|
channelTag := ChannelTag{} |
|
|
err := c.ShouldBindJSON(&channelTag) |
|
|
if err != nil || channelTag.Tag == "" { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "参数错误", |
|
|
}) |
|
|
return |
|
|
} |
|
|
err = model.EnableChannelByTag(channelTag.Tag) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func EditTagChannels(c *gin.Context) { |
|
|
channelTag := ChannelTag{} |
|
|
err := c.ShouldBindJSON(&channelTag) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "参数错误", |
|
|
}) |
|
|
return |
|
|
} |
|
|
if channelTag.Tag == "" { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "tag不能为空", |
|
|
}) |
|
|
return |
|
|
} |
|
|
if channelTag.ParamOverride != nil { |
|
|
trimmed := strings.TrimSpace(*channelTag.ParamOverride) |
|
|
if trimmed != "" && !json.Valid([]byte(trimmed)) { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "参数覆盖必须是合法的 JSON 格式", |
|
|
}) |
|
|
return |
|
|
} |
|
|
channelTag.ParamOverride = common.GetPointer[string](trimmed) |
|
|
} |
|
|
if channelTag.HeaderOverride != nil { |
|
|
trimmed := strings.TrimSpace(*channelTag.HeaderOverride) |
|
|
if trimmed != "" && !json.Valid([]byte(trimmed)) { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "请求头覆盖必须是合法的 JSON 格式", |
|
|
}) |
|
|
return |
|
|
} |
|
|
channelTag.HeaderOverride = common.GetPointer[string](trimmed) |
|
|
} |
|
|
err = model.EditChannelByTag(channelTag.Tag, channelTag.NewTag, channelTag.ModelMapping, channelTag.Models, channelTag.Groups, channelTag.Priority, channelTag.Weight, channelTag.ParamOverride, channelTag.HeaderOverride) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
type ChannelBatch struct { |
|
|
Ids []int `json:"ids"` |
|
|
Tag *string `json:"tag"` |
|
|
} |
|
|
|
|
|
func DeleteChannelBatch(c *gin.Context) { |
|
|
channelBatch := ChannelBatch{} |
|
|
err := c.ShouldBindJSON(&channelBatch) |
|
|
if err != nil || len(channelBatch.Ids) == 0 { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "参数错误", |
|
|
}) |
|
|
return |
|
|
} |
|
|
err = model.BatchDeleteChannels(channelBatch.Ids) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": len(channelBatch.Ids), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
type PatchChannel struct { |
|
|
model.Channel |
|
|
MultiKeyMode *string `json:"multi_key_mode"` |
|
|
KeyMode *string `json:"key_mode"` |
|
|
} |
|
|
|
|
|
func UpdateChannel(c *gin.Context) { |
|
|
channel := PatchChannel{} |
|
|
err := c.ShouldBindJSON(&channel) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
if err := validateChannel(&channel.Channel, false); err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
originChannel, err := model.GetChannelById(channel.Id, true) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
channel.ChannelInfo = originChannel.ChannelInfo |
|
|
|
|
|
|
|
|
if channel.MultiKeyMode != nil && *channel.MultiKeyMode != "" { |
|
|
channel.ChannelInfo.MultiKeyMode = constant.MultiKeyMode(*channel.MultiKeyMode) |
|
|
} |
|
|
|
|
|
|
|
|
if channel.KeyMode != nil && channel.ChannelInfo.IsMultiKey { |
|
|
switch *channel.KeyMode { |
|
|
case "append": |
|
|
|
|
|
if originChannel.Key != "" { |
|
|
var newKeys []string |
|
|
var existingKeys []string |
|
|
|
|
|
|
|
|
if strings.HasPrefix(strings.TrimSpace(originChannel.Key), "[") { |
|
|
|
|
|
var arr []json.RawMessage |
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(originChannel.Key)), &arr); err == nil { |
|
|
existingKeys = make([]string, len(arr)) |
|
|
for i, v := range arr { |
|
|
existingKeys[i] = string(v) |
|
|
} |
|
|
} |
|
|
} else { |
|
|
|
|
|
existingKeys = strings.Split(strings.Trim(originChannel.Key, "\n"), "\n") |
|
|
} |
|
|
|
|
|
|
|
|
if channel.Type == constant.ChannelTypeVertexAi && channel.GetOtherSettings().VertexKeyType != dto.VertexKeyTypeAPIKey { |
|
|
|
|
|
if strings.HasPrefix(strings.TrimSpace(channel.Key), "[") { |
|
|
array, err := getVertexArrayKeys(channel.Key) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "追加密钥解析失败: " + err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
newKeys = array |
|
|
} else { |
|
|
|
|
|
newKeys = []string{channel.Key} |
|
|
} |
|
|
|
|
|
allKeys := append(existingKeys, newKeys...) |
|
|
channel.Key = strings.Join(allKeys, "\n") |
|
|
} else { |
|
|
|
|
|
inputKeys := strings.Split(channel.Key, "\n") |
|
|
for _, key := range inputKeys { |
|
|
key = strings.TrimSpace(key) |
|
|
if key != "" { |
|
|
newKeys = append(newKeys, key) |
|
|
} |
|
|
} |
|
|
|
|
|
allKeys := append(existingKeys, newKeys...) |
|
|
channel.Key = strings.Join(allKeys, "\n") |
|
|
} |
|
|
} |
|
|
case "replace": |
|
|
|
|
|
} |
|
|
} |
|
|
err = channel.Update() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
service.ResetProxyClientCache() |
|
|
channel.Key = "" |
|
|
clearChannelInfo(&channel.Channel) |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": channel, |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func FetchModels(c *gin.Context) { |
|
|
var req struct { |
|
|
BaseURL string `json:"base_url"` |
|
|
Type int `json:"type"` |
|
|
Key string `json:"key"` |
|
|
} |
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil { |
|
|
c.JSON(http.StatusBadRequest, gin.H{ |
|
|
"success": false, |
|
|
"message": "Invalid request", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
baseURL := req.BaseURL |
|
|
if baseURL == "" { |
|
|
baseURL = constant.ChannelBaseURLs[req.Type] |
|
|
} |
|
|
|
|
|
client := &http.Client{} |
|
|
url := fmt.Sprintf("%s/v1/models", baseURL) |
|
|
|
|
|
request, err := http.NewRequest("GET", url, nil) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusInternalServerError, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
key := strings.TrimSpace(req.Key) |
|
|
|
|
|
key = strings.Split(key, "\n")[0] |
|
|
request.Header.Set("Authorization", "Bearer "+key) |
|
|
|
|
|
response, err := client.Do(request) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusInternalServerError, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
if response.StatusCode != http.StatusOK { |
|
|
c.JSON(http.StatusInternalServerError, gin.H{ |
|
|
"success": false, |
|
|
"message": "Failed to fetch models", |
|
|
}) |
|
|
return |
|
|
} |
|
|
defer response.Body.Close() |
|
|
|
|
|
var result struct { |
|
|
Data []struct { |
|
|
ID string `json:"id"` |
|
|
} `json:"data"` |
|
|
} |
|
|
|
|
|
if err := json.NewDecoder(response.Body).Decode(&result); err != nil { |
|
|
c.JSON(http.StatusInternalServerError, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
var models []string |
|
|
for _, model := range result.Data { |
|
|
models = append(models, model.ID) |
|
|
} |
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"data": models, |
|
|
}) |
|
|
} |
|
|
|
|
|
func BatchSetChannelTag(c *gin.Context) { |
|
|
channelBatch := ChannelBatch{} |
|
|
err := c.ShouldBindJSON(&channelBatch) |
|
|
if err != nil || len(channelBatch.Ids) == 0 { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "参数错误", |
|
|
}) |
|
|
return |
|
|
} |
|
|
err = model.BatchSetChannelTag(channelBatch.Ids, channelBatch.Tag) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": len(channelBatch.Ids), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
func GetTagModels(c *gin.Context) { |
|
|
tag := c.Query("tag") |
|
|
if tag == "" { |
|
|
c.JSON(http.StatusBadRequest, gin.H{ |
|
|
"success": false, |
|
|
"message": "tag不能为空", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
channels, err := model.GetChannelsByTag(tag, false, false) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusInternalServerError, gin.H{ |
|
|
"success": false, |
|
|
"message": err.Error(), |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
var longestModels string |
|
|
maxLength := 0 |
|
|
|
|
|
|
|
|
for _, channel := range channels { |
|
|
if channel.Models != "" { |
|
|
currentModels := strings.Split(channel.Models, ",") |
|
|
if len(currentModels) > maxLength { |
|
|
maxLength = len(currentModels) |
|
|
longestModels = channel.Models |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": longestModels, |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func CopyChannel(c *gin.Context) { |
|
|
id, err := strconv.Atoi(c.Param("id")) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{"success": false, "message": "invalid id"}) |
|
|
return |
|
|
} |
|
|
|
|
|
suffix := c.DefaultQuery("suffix", "_复制") |
|
|
resetBalance := true |
|
|
if rbStr := c.DefaultQuery("reset_balance", "true"); rbStr != "" { |
|
|
if v, err := strconv.ParseBool(rbStr); err == nil { |
|
|
resetBalance = v |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
origin, err := model.GetChannelById(id, true) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
clone := *origin |
|
|
clone.Id = 0 |
|
|
clone.CreatedTime = common.GetTimestamp() |
|
|
clone.Name = origin.Name + suffix |
|
|
clone.TestTime = 0 |
|
|
clone.ResponseTime = 0 |
|
|
if resetBalance { |
|
|
clone.Balance = 0 |
|
|
clone.UsedQuota = 0 |
|
|
} |
|
|
|
|
|
|
|
|
if err := model.BatchInsertChannels([]model.Channel{clone}); err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) |
|
|
return |
|
|
} |
|
|
model.InitChannelCache() |
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": gin.H{"id": clone.Id}}) |
|
|
} |
|
|
|
|
|
|
|
|
type MultiKeyManageRequest struct { |
|
|
ChannelId int `json:"channel_id"` |
|
|
Action string `json:"action"` |
|
|
KeyIndex *int `json:"key_index,omitempty"` |
|
|
Page int `json:"page,omitempty"` |
|
|
PageSize int `json:"page_size,omitempty"` |
|
|
Status *int `json:"status,omitempty"` |
|
|
} |
|
|
|
|
|
|
|
|
type MultiKeyStatusResponse struct { |
|
|
Keys []KeyStatus `json:"keys"` |
|
|
Total int `json:"total"` |
|
|
Page int `json:"page"` |
|
|
PageSize int `json:"page_size"` |
|
|
TotalPages int `json:"total_pages"` |
|
|
|
|
|
EnabledCount int `json:"enabled_count"` |
|
|
ManualDisabledCount int `json:"manual_disabled_count"` |
|
|
AutoDisabledCount int `json:"auto_disabled_count"` |
|
|
} |
|
|
|
|
|
type KeyStatus struct { |
|
|
Index int `json:"index"` |
|
|
Status int `json:"status"` |
|
|
DisabledTime int64 `json:"disabled_time,omitempty"` |
|
|
Reason string `json:"reason,omitempty"` |
|
|
KeyPreview string `json:"key_preview"` |
|
|
} |
|
|
|
|
|
|
|
|
func ManageMultiKeys(c *gin.Context) { |
|
|
request := MultiKeyManageRequest{} |
|
|
err := c.ShouldBindJSON(&request) |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
channel, err := model.GetChannelById(request.ChannelId, true) |
|
|
if err != nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "渠道不存在", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
if !channel.ChannelInfo.IsMultiKey { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "该渠道不是多密钥模式", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
lock := model.GetChannelPollingLock(channel.Id) |
|
|
lock.Lock() |
|
|
defer lock.Unlock() |
|
|
|
|
|
switch request.Action { |
|
|
case "get_key_status": |
|
|
keys := channel.GetKeys() |
|
|
|
|
|
|
|
|
page := request.Page |
|
|
pageSize := request.PageSize |
|
|
if page <= 0 { |
|
|
page = 1 |
|
|
} |
|
|
if pageSize <= 0 { |
|
|
pageSize = 50 |
|
|
} |
|
|
|
|
|
|
|
|
var enabledCount, manualDisabledCount, autoDisabledCount int |
|
|
|
|
|
|
|
|
var allKeyStatusList []KeyStatus |
|
|
for i, key := range keys { |
|
|
status := 1 |
|
|
var disabledTime int64 |
|
|
var reason string |
|
|
|
|
|
if channel.ChannelInfo.MultiKeyStatusList != nil { |
|
|
if s, exists := channel.ChannelInfo.MultiKeyStatusList[i]; exists { |
|
|
status = s |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
switch status { |
|
|
case 1: |
|
|
enabledCount++ |
|
|
case 2: |
|
|
manualDisabledCount++ |
|
|
case 3: |
|
|
autoDisabledCount++ |
|
|
} |
|
|
|
|
|
if status != 1 { |
|
|
if channel.ChannelInfo.MultiKeyDisabledTime != nil { |
|
|
disabledTime = channel.ChannelInfo.MultiKeyDisabledTime[i] |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledReason != nil { |
|
|
reason = channel.ChannelInfo.MultiKeyDisabledReason[i] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
keyPreview := key |
|
|
if len(key) > 10 { |
|
|
keyPreview = key[:10] + "..." |
|
|
} |
|
|
|
|
|
allKeyStatusList = append(allKeyStatusList, KeyStatus{ |
|
|
Index: i, |
|
|
Status: status, |
|
|
DisabledTime: disabledTime, |
|
|
Reason: reason, |
|
|
KeyPreview: keyPreview, |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
var filteredKeyStatusList []KeyStatus |
|
|
if request.Status != nil { |
|
|
for _, keyStatus := range allKeyStatusList { |
|
|
if keyStatus.Status == *request.Status { |
|
|
filteredKeyStatusList = append(filteredKeyStatusList, keyStatus) |
|
|
} |
|
|
} |
|
|
} else { |
|
|
filteredKeyStatusList = allKeyStatusList |
|
|
} |
|
|
|
|
|
|
|
|
filteredTotal := len(filteredKeyStatusList) |
|
|
totalPages := (filteredTotal + pageSize - 1) / pageSize |
|
|
if totalPages == 0 { |
|
|
totalPages = 1 |
|
|
} |
|
|
if page > totalPages { |
|
|
page = totalPages |
|
|
} |
|
|
|
|
|
|
|
|
start := (page - 1) * pageSize |
|
|
end := start + pageSize |
|
|
if end > filteredTotal { |
|
|
end = filteredTotal |
|
|
} |
|
|
|
|
|
|
|
|
var pageKeyStatusList []KeyStatus |
|
|
if start < filteredTotal { |
|
|
pageKeyStatusList = filteredKeyStatusList[start:end] |
|
|
} |
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "", |
|
|
"data": MultiKeyStatusResponse{ |
|
|
Keys: pageKeyStatusList, |
|
|
Total: filteredTotal, |
|
|
Page: page, |
|
|
PageSize: pageSize, |
|
|
TotalPages: totalPages, |
|
|
EnabledCount: enabledCount, |
|
|
ManualDisabledCount: manualDisabledCount, |
|
|
AutoDisabledCount: autoDisabledCount, |
|
|
}, |
|
|
}) |
|
|
return |
|
|
|
|
|
case "disable_key": |
|
|
if request.KeyIndex == nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "未指定要禁用的密钥索引", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
keyIndex := *request.KeyIndex |
|
|
if keyIndex < 0 || keyIndex >= channel.ChannelInfo.MultiKeySize { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "密钥索引超出范围", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
if channel.ChannelInfo.MultiKeyStatusList == nil { |
|
|
channel.ChannelInfo.MultiKeyStatusList = make(map[int]int) |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledTime == nil { |
|
|
channel.ChannelInfo.MultiKeyDisabledTime = make(map[int]int64) |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledReason == nil { |
|
|
channel.ChannelInfo.MultiKeyDisabledReason = make(map[int]string) |
|
|
} |
|
|
|
|
|
channel.ChannelInfo.MultiKeyStatusList[keyIndex] = 2 |
|
|
|
|
|
err = channel.Update() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "密钥已禁用", |
|
|
}) |
|
|
return |
|
|
|
|
|
case "enable_key": |
|
|
if request.KeyIndex == nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "未指定要启用的密钥索引", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
keyIndex := *request.KeyIndex |
|
|
if keyIndex < 0 || keyIndex >= channel.ChannelInfo.MultiKeySize { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "密钥索引超出范围", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
if channel.ChannelInfo.MultiKeyStatusList != nil { |
|
|
delete(channel.ChannelInfo.MultiKeyStatusList, keyIndex) |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledTime != nil { |
|
|
delete(channel.ChannelInfo.MultiKeyDisabledTime, keyIndex) |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledReason != nil { |
|
|
delete(channel.ChannelInfo.MultiKeyDisabledReason, keyIndex) |
|
|
} |
|
|
|
|
|
err = channel.Update() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "密钥已启用", |
|
|
}) |
|
|
return |
|
|
|
|
|
case "enable_all_keys": |
|
|
|
|
|
var enabledCount int |
|
|
if channel.ChannelInfo.MultiKeyStatusList != nil { |
|
|
enabledCount = len(channel.ChannelInfo.MultiKeyStatusList) |
|
|
} |
|
|
|
|
|
channel.ChannelInfo.MultiKeyStatusList = make(map[int]int) |
|
|
channel.ChannelInfo.MultiKeyDisabledTime = make(map[int]int64) |
|
|
channel.ChannelInfo.MultiKeyDisabledReason = make(map[int]string) |
|
|
|
|
|
err = channel.Update() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": fmt.Sprintf("已启用 %d 个密钥", enabledCount), |
|
|
}) |
|
|
return |
|
|
|
|
|
case "disable_all_keys": |
|
|
|
|
|
if channel.ChannelInfo.MultiKeyStatusList == nil { |
|
|
channel.ChannelInfo.MultiKeyStatusList = make(map[int]int) |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledTime == nil { |
|
|
channel.ChannelInfo.MultiKeyDisabledTime = make(map[int]int64) |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledReason == nil { |
|
|
channel.ChannelInfo.MultiKeyDisabledReason = make(map[int]string) |
|
|
} |
|
|
|
|
|
var disabledCount int |
|
|
for i := 0; i < channel.ChannelInfo.MultiKeySize; i++ { |
|
|
status := 1 |
|
|
if s, exists := channel.ChannelInfo.MultiKeyStatusList[i]; exists { |
|
|
status = s |
|
|
} |
|
|
|
|
|
|
|
|
if status == 1 { |
|
|
channel.ChannelInfo.MultiKeyStatusList[i] = 2 |
|
|
disabledCount++ |
|
|
} |
|
|
} |
|
|
|
|
|
if disabledCount == 0 { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "没有可禁用的密钥", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
err = channel.Update() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": fmt.Sprintf("已禁用 %d 个密钥", disabledCount), |
|
|
}) |
|
|
return |
|
|
|
|
|
case "delete_key": |
|
|
if request.KeyIndex == nil { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "未指定要删除的密钥索引", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
keyIndex := *request.KeyIndex |
|
|
if keyIndex < 0 || keyIndex >= channel.ChannelInfo.MultiKeySize { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "密钥索引超出范围", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
keys := channel.GetKeys() |
|
|
var remainingKeys []string |
|
|
var newStatusList = make(map[int]int) |
|
|
var newDisabledTime = make(map[int]int64) |
|
|
var newDisabledReason = make(map[int]string) |
|
|
|
|
|
newIndex := 0 |
|
|
for i, key := range keys { |
|
|
|
|
|
if i == keyIndex { |
|
|
continue |
|
|
} |
|
|
|
|
|
remainingKeys = append(remainingKeys, key) |
|
|
|
|
|
|
|
|
if channel.ChannelInfo.MultiKeyStatusList != nil { |
|
|
if status, exists := channel.ChannelInfo.MultiKeyStatusList[i]; exists && status != 1 { |
|
|
newStatusList[newIndex] = status |
|
|
} |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledTime != nil { |
|
|
if t, exists := channel.ChannelInfo.MultiKeyDisabledTime[i]; exists { |
|
|
newDisabledTime[newIndex] = t |
|
|
} |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledReason != nil { |
|
|
if r, exists := channel.ChannelInfo.MultiKeyDisabledReason[i]; exists { |
|
|
newDisabledReason[newIndex] = r |
|
|
} |
|
|
} |
|
|
newIndex++ |
|
|
} |
|
|
|
|
|
if len(remainingKeys) == 0 { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "不能删除最后一个密钥", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
channel.Key = strings.Join(remainingKeys, "\n") |
|
|
channel.ChannelInfo.MultiKeySize = len(remainingKeys) |
|
|
channel.ChannelInfo.MultiKeyStatusList = newStatusList |
|
|
channel.ChannelInfo.MultiKeyDisabledTime = newDisabledTime |
|
|
channel.ChannelInfo.MultiKeyDisabledReason = newDisabledReason |
|
|
|
|
|
err = channel.Update() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": "密钥已删除", |
|
|
}) |
|
|
return |
|
|
|
|
|
case "delete_disabled_keys": |
|
|
keys := channel.GetKeys() |
|
|
var remainingKeys []string |
|
|
var deletedCount int |
|
|
var newStatusList = make(map[int]int) |
|
|
var newDisabledTime = make(map[int]int64) |
|
|
var newDisabledReason = make(map[int]string) |
|
|
|
|
|
newIndex := 0 |
|
|
for i, key := range keys { |
|
|
status := 1 |
|
|
if channel.ChannelInfo.MultiKeyStatusList != nil { |
|
|
if s, exists := channel.ChannelInfo.MultiKeyStatusList[i]; exists { |
|
|
status = s |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if status == 3 { |
|
|
deletedCount++ |
|
|
} else { |
|
|
remainingKeys = append(remainingKeys, key) |
|
|
|
|
|
if status != 1 { |
|
|
newStatusList[newIndex] = status |
|
|
if channel.ChannelInfo.MultiKeyDisabledTime != nil { |
|
|
if t, exists := channel.ChannelInfo.MultiKeyDisabledTime[i]; exists { |
|
|
newDisabledTime[newIndex] = t |
|
|
} |
|
|
} |
|
|
if channel.ChannelInfo.MultiKeyDisabledReason != nil { |
|
|
if r, exists := channel.ChannelInfo.MultiKeyDisabledReason[i]; exists { |
|
|
newDisabledReason[newIndex] = r |
|
|
} |
|
|
} |
|
|
} |
|
|
newIndex++ |
|
|
} |
|
|
} |
|
|
|
|
|
if deletedCount == 0 { |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "没有需要删除的自动禁用密钥", |
|
|
}) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
channel.Key = strings.Join(remainingKeys, "\n") |
|
|
channel.ChannelInfo.MultiKeySize = len(remainingKeys) |
|
|
channel.ChannelInfo.MultiKeyStatusList = newStatusList |
|
|
channel.ChannelInfo.MultiKeyDisabledTime = newDisabledTime |
|
|
channel.ChannelInfo.MultiKeyDisabledReason = newDisabledReason |
|
|
|
|
|
err = channel.Update() |
|
|
if err != nil { |
|
|
common.ApiError(c, err) |
|
|
return |
|
|
} |
|
|
|
|
|
model.InitChannelCache() |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": true, |
|
|
"message": fmt.Sprintf("已删除 %d 个自动禁用的密钥", deletedCount), |
|
|
"data": deletedCount, |
|
|
}) |
|
|
return |
|
|
|
|
|
default: |
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
"success": false, |
|
|
"message": "不支持的操作", |
|
|
}) |
|
|
return |
|
|
} |
|
|
} |
|
|
|