feat: 密钥管理
This commit is contained in:
206
internal/services/key_service.go
Normal file
206
internal/services/key_service.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gpt-load/internal/models"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AddKeysResult holds the result of adding multiple keys.
|
||||
type AddKeysResult struct {
|
||||
AddedCount int `json:"added_count"`
|
||||
IgnoredCount int `json:"ignored_count"`
|
||||
TotalInGroup int64 `json:"total_in_group"`
|
||||
}
|
||||
|
||||
// KeyService provides services related to API keys.
|
||||
type KeyService struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
// NewKeyService creates a new KeyService.
|
||||
func NewKeyService(db *gorm.DB) *KeyService {
|
||||
return &KeyService{DB: db}
|
||||
}
|
||||
|
||||
// AddMultipleKeys handles the business logic of creating new keys from a text block.
|
||||
func (s *KeyService) AddMultipleKeys(groupID uint, keysText string) (*AddKeysResult, error) {
|
||||
// 1. Parse keys from the text block
|
||||
keys := s.parseKeysFromText(keysText)
|
||||
if len(keys) == 0 {
|
||||
return nil, fmt.Errorf("no valid keys found in the input text")
|
||||
}
|
||||
|
||||
// 2. Get the group information for validation
|
||||
var group models.Group
|
||||
if err := s.DB.First(&group, groupID).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to find group: %w", err)
|
||||
}
|
||||
|
||||
// 3. Get existing keys in the group for deduplication
|
||||
var existingKeys []models.APIKey
|
||||
if err := s.DB.Where("group_id = ?", groupID).Select("key_value").Find(&existingKeys).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingKeyMap := make(map[string]bool)
|
||||
for _, k := range existingKeys {
|
||||
existingKeyMap[k.KeyValue] = true
|
||||
}
|
||||
|
||||
// 4. Prepare new keys with basic validation only
|
||||
var newKeysToCreate []models.APIKey
|
||||
uniqueNewKeys := make(map[string]bool)
|
||||
|
||||
for _, keyVal := range keys {
|
||||
trimmedKey := strings.TrimSpace(keyVal)
|
||||
if trimmedKey == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if key already exists
|
||||
if existingKeyMap[trimmedKey] || uniqueNewKeys[trimmedKey] {
|
||||
continue
|
||||
}
|
||||
|
||||
// 通用验证:只做基础格式检查,不做渠道特定验证
|
||||
if s.isValidKeyFormat(trimmedKey) {
|
||||
uniqueNewKeys[trimmedKey] = true
|
||||
newKeysToCreate = append(newKeysToCreate, models.APIKey{
|
||||
GroupID: groupID,
|
||||
KeyValue: trimmedKey,
|
||||
Status: "active",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addedCount := len(newKeysToCreate)
|
||||
// 更准确的忽略计数:包括重复的和无效的
|
||||
ignoredCount := len(keys) - addedCount
|
||||
|
||||
// 5. Insert new keys if any
|
||||
if addedCount > 0 {
|
||||
if err := s.DB.Create(&newKeysToCreate).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Get the new total count
|
||||
var totalInGroup int64
|
||||
if err := s.DB.Model(&models.APIKey{}).Where("group_id = ?", groupID).Count(&totalInGroup).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AddKeysResult{
|
||||
AddedCount: addedCount,
|
||||
IgnoredCount: ignoredCount,
|
||||
TotalInGroup: totalInGroup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *KeyService) parseKeysFromText(text string) []string {
|
||||
var keys []string
|
||||
|
||||
// First, try to parse as a JSON array of strings
|
||||
if json.Unmarshal([]byte(text), &keys) == nil && len(keys) > 0 {
|
||||
return s.filterValidKeys(keys)
|
||||
}
|
||||
|
||||
// 通用解析:通过分隔符分割文本,不使用复杂的正则表达式
|
||||
delimiters := regexp.MustCompile(`[\s,;|\n\r\t]+`)
|
||||
splitKeys := delimiters.Split(strings.TrimSpace(text), -1)
|
||||
|
||||
for _, key := range splitKeys {
|
||||
key = strings.TrimSpace(key)
|
||||
if key != "" {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
return s.filterValidKeys(keys)
|
||||
}
|
||||
|
||||
// filterValidKeys validates and filters potential API keys
|
||||
func (s *KeyService) filterValidKeys(keys []string) []string {
|
||||
var validKeys []string
|
||||
for _, key := range keys {
|
||||
key = strings.TrimSpace(key)
|
||||
if s.isValidKeyFormat(key) {
|
||||
validKeys = append(validKeys, key)
|
||||
}
|
||||
}
|
||||
return validKeys
|
||||
}
|
||||
|
||||
// isValidKeyFormat performs basic validation on key format
|
||||
func (s *KeyService) isValidKeyFormat(key string) bool {
|
||||
if len(key) < 4 || len(key) > 1000 {
|
||||
return false
|
||||
}
|
||||
|
||||
if key == "" ||
|
||||
strings.TrimSpace(key) == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
validChars := regexp.MustCompile(`^[a-zA-Z0-9_\-./+=:]+$`)
|
||||
return validChars.MatchString(key)
|
||||
}
|
||||
|
||||
// RestoreAllInvalidKeys sets the status of all 'inactive' keys in a group to 'active'.
|
||||
func (s *KeyService) RestoreAllInvalidKeys(groupID uint) (int64, error) {
|
||||
result := s.DB.Model(&models.APIKey{}).Where("group_id = ? AND status = ?", groupID, "inactive").Update("status", "active")
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// ClearAllInvalidKeys deletes all 'inactive' keys from a group.
|
||||
func (s *KeyService) ClearAllInvalidKeys(groupID uint) (int64, error) {
|
||||
result := s.DB.Where("group_id = ? AND status = ?", groupID, "inactive").Delete(&models.APIKey{})
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// DeleteSingleKey deletes a specific key from a group.
|
||||
func (s *KeyService) DeleteSingleKey(groupID, keyID uint) (int64, error) {
|
||||
result := s.DB.Where("group_id = ? AND id = ?", groupID, keyID).Delete(&models.APIKey{})
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// ExportKeys returns a list of keys for a group, filtered by status.
|
||||
func (s *KeyService) ExportKeys(groupID uint, filter string) ([]string, error) {
|
||||
query := s.DB.Model(&models.APIKey{}).Where("group_id = ?", groupID)
|
||||
|
||||
switch filter {
|
||||
case "valid":
|
||||
query = query.Where("status = ?", "active")
|
||||
case "invalid":
|
||||
query = query.Where("status = ?", "inactive")
|
||||
case "all":
|
||||
// No status filter needed
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid filter value. Use 'all', 'valid', or 'invalid'")
|
||||
}
|
||||
|
||||
var keys []string
|
||||
if err := query.Pluck("key_value", &keys).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// ListKeysInGroup lists all keys within a specific group, filtered by status.
|
||||
func (s *KeyService) ListKeysInGroup(groupID uint, statusFilter string) ([]models.APIKey, error) {
|
||||
var keys []models.APIKey
|
||||
query := s.DB.Where("group_id = ?", groupID)
|
||||
|
||||
if statusFilter != "" {
|
||||
query = query.Where("status = ?", statusFilter)
|
||||
}
|
||||
|
||||
if err := query.Find(&keys).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
Reference in New Issue
Block a user