feat: 删除分组同时删除内存key
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
|||||||
"gpt-load/internal/channel"
|
"gpt-load/internal/channel"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -345,20 +346,20 @@ func (s *Server) UpdateGroup(c *gin.Context) {
|
|||||||
|
|
||||||
// GroupResponse defines the structure for a group response, excluding sensitive or large fields.
|
// GroupResponse defines the structure for a group response, excluding sensitive or large fields.
|
||||||
type GroupResponse struct {
|
type GroupResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Endpoint string `json:"endpoint"`
|
Endpoint string `json:"endpoint"`
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Upstreams datatypes.JSON `json:"upstreams"`
|
Upstreams datatypes.JSON `json:"upstreams"`
|
||||||
ChannelType string `json:"channel_type"`
|
ChannelType string `json:"channel_type"`
|
||||||
Sort int `json:"sort"`
|
Sort int `json:"sort"`
|
||||||
TestModel string `json:"test_model"`
|
TestModel string `json:"test_model"`
|
||||||
ParamOverrides datatypes.JSONMap `json:"param_overrides"`
|
ParamOverrides datatypes.JSONMap `json:"param_overrides"`
|
||||||
Config datatypes.JSONMap `json:"config"`
|
Config datatypes.JSONMap `json:"config"`
|
||||||
LastValidatedAt *time.Time `json:"last_validated_at"`
|
LastValidatedAt *time.Time `json:"last_validated_at"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// newGroupResponse creates a new GroupResponse from a models.Group.
|
// newGroupResponse creates a new GroupResponse from a models.Group.
|
||||||
@@ -391,7 +392,6 @@ func (s *Server) newGroupResponse(group *models.Group) *GroupResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// DeleteGroup handles deleting a group.
|
// DeleteGroup handles deleting a group.
|
||||||
func (s *Server) DeleteGroup(c *gin.Context) {
|
func (s *Server) DeleteGroup(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
@@ -400,6 +400,19 @@ func (s *Server) DeleteGroup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First, get all API keys for this group to clean up from memory store
|
||||||
|
var apiKeys []models.APIKey
|
||||||
|
if err := s.DB.Where("group_id = ?", id).Find(&apiKeys).Error; err != nil {
|
||||||
|
response.Error(c, app_errors.ParseDBError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract key IDs for memory store cleanup
|
||||||
|
var keyIDs []uint
|
||||||
|
for _, key := range apiKeys {
|
||||||
|
keyIDs = append(keyIDs, key.ID)
|
||||||
|
}
|
||||||
|
|
||||||
// Use a transaction to ensure atomicity
|
// Use a transaction to ensure atomicity
|
||||||
tx := s.DB.Begin()
|
tx := s.DB.Begin()
|
||||||
if tx.Error != nil {
|
if tx.Error != nil {
|
||||||
@@ -412,20 +425,25 @@ func (s *Server) DeleteGroup(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Also delete associated API keys
|
// First check if the group exists
|
||||||
|
var group models.Group
|
||||||
|
if err := tx.First(&group, id).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
response.Error(c, app_errors.ParseDBError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete associated API keys first due to foreign key constraint
|
||||||
if err := tx.Where("group_id = ?", id).Delete(&models.APIKey{}).Error; err != nil {
|
if err := tx.Where("group_id = ?", id).Delete(&models.APIKey{}).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
response.Error(c, app_errors.ErrDatabase)
|
response.Error(c, app_errors.ErrDatabase)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := tx.Delete(&models.Group{}, id); result.Error != nil {
|
// Then delete the group
|
||||||
|
if err := tx.Delete(&models.Group{}, id).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
response.Error(c, app_errors.ParseDBError(result.Error))
|
response.Error(c, app_errors.ParseDBError(err))
|
||||||
return
|
|
||||||
} else if result.RowsAffected == 0 {
|
|
||||||
tx.Rollback()
|
|
||||||
response.Error(c, app_errors.ErrResourceNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,15 +453,34 @@ func (s *Server) DeleteGroup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up memory store (Redis) - this is done after successful DB transaction
|
||||||
|
// to maintain consistency. If this fails, the keys will be cleaned up during
|
||||||
|
// the next key pool reload.
|
||||||
|
if len(keyIDs) > 0 {
|
||||||
|
if err := s.KeyService.KeyProvider.RemoveKeysFromStore(uint(id), keyIDs); err != nil {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"groupID": id,
|
||||||
|
"keyCount": len(keyIDs),
|
||||||
|
"error": err,
|
||||||
|
}).Error("Failed to remove keys from memory store")
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"message": "Group and associated keys deleted successfully",
|
||||||
|
"warning": "Some keys may remain in memory cache and will be cleaned up during next restart",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.Success(c, gin.H{"message": "Group and associated keys deleted successfully"})
|
response.Success(c, gin.H{"message": "Group and associated keys deleted successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigOption represents a single configurable option for a group.
|
// ConfigOption represents a single configurable option for a group.
|
||||||
type ConfigOption struct {
|
type ConfigOption struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
DefaultValue any `json:"default_value"`
|
DefaultValue any `json:"default_value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupConfigOptions returns a list of available configuration options for groups.
|
// GetGroupConfigOptions returns a list of available configuration options for groups.
|
||||||
|
@@ -459,6 +459,44 @@ func (p *KeyProvider) RemoveInvalidKeys(groupID uint) (int64, error) {
|
|||||||
return removedCount, err
|
return removedCount, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveKeysFromStore 直接从内存存储中移除指定的键,不涉及数据库操作
|
||||||
|
// 这个方法适用于数据库已经删除但需要清理内存存储的场景
|
||||||
|
func (p *KeyProvider) RemoveKeysFromStore(groupID uint, keyIDs []uint) error {
|
||||||
|
if len(keyIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
activeKeysListKey := fmt.Sprintf("group:%d:active_keys", groupID)
|
||||||
|
|
||||||
|
// 第一步:直接删除整个 active_keys 列表
|
||||||
|
if err := p.store.Delete(activeKeysListKey); err != nil {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"groupID": groupID,
|
||||||
|
"error": err,
|
||||||
|
}).Error("Failed to delete active keys list")
|
||||||
|
// 继续执行hash删除,因为即使列表删除失败,hash仍然需要清理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二步:批量删除所有相关的key hash
|
||||||
|
for _, keyID := range keyIDs {
|
||||||
|
keyHashKey := fmt.Sprintf("key:%d", keyID)
|
||||||
|
if err := p.store.Delete(keyHashKey); err != nil {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"keyID": keyID,
|
||||||
|
"error": err,
|
||||||
|
}).Error("Failed to delete key hash")
|
||||||
|
// 继续删除其他keys,不因单个失败而中断
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"groupID": groupID,
|
||||||
|
"keyCount": len(keyIDs),
|
||||||
|
}).Info("Successfully cleaned up group keys from store")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// addKeyToStore is a helper to add a single key to the cache.
|
// addKeyToStore is a helper to add a single key to the cache.
|
||||||
func (p *KeyProvider) addKeyToStore(key *models.APIKey) error {
|
func (p *KeyProvider) addKeyToStore(key *models.APIKey) error {
|
||||||
// 1. Store key details in HASH
|
// 1. Store key details in HASH
|
||||||
|
Reference in New Issue
Block a user