| | 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 |
| | } |
| | } |
| |
|