Files
gpt-load/internal/services/key_service.go
2025-07-05 14:50:58 +08:00

223 lines
6.5 KiB
Go

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"`
}
// DeleteKeysResult holds the result of deleting multiple keys.
type DeleteKeysResult struct {
DeletedCount int `json:"deleted_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
}
// ParseKeysFromText parses a string of keys from various formats into a string slice.
// This function is exported to be shared with the handler layer.
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
}
// DeleteMultipleKeys handles the business logic of deleting keys from a text block.
func (s *KeyService) DeleteMultipleKeys(groupID uint, keysText string) (*DeleteKeysResult, error) {
// 1. Parse keys from the text block
keysToDelete := s.ParseKeysFromText(keysText)
if len(keysToDelete) == 0 {
return nil, fmt.Errorf("no valid keys found in the input text")
}
// 2. Perform the deletion
// GORM's batch delete doesn't easily return which ones were deleted vs. ignored.
// We perform a bulk delete and then count the remaining to calculate the result.
result := s.DB.Where("group_id = ? AND key_value IN ?", groupID, keysToDelete).Delete(&models.APIKey{})
if result.Error != nil {
return nil, result.Error
}
deletedCount := int(result.RowsAffected)
ignoredCount := len(keysToDelete) - deletedCount
// 3. 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 &DeleteKeysResult{
DeletedCount: deletedCount,
IgnoredCount: ignoredCount,
TotalInGroup: totalInGroup,
}, nil
}
// ListKeysInGroupQuery builds a query to list all keys within a specific group, filtered by status.
// It returns a GORM query builder, allowing the handler to apply pagination.
func (s *KeyService) ListKeysInGroupQuery(groupID uint, statusFilter string, searchKeyword string) *gorm.DB {
query := s.DB.Model(&models.APIKey{}).Where("group_id = ?", groupID)
if statusFilter != "" {
query = query.Where("status = ?", statusFilter)
}
if searchKeyword != "" {
// Use LIKE for fuzzy search on the key_value
query = query.Where("key_value LIKE ?", "%"+searchKeyword+"%")
}
return query
}