From 85efea3fb8f722bacf8e851cfb9ab0f779b71bae Mon Sep 17 00:00:00 2001 From: CaIon Date: Fri, 11 Jul 2025 21:12:17 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(channel):=20implement=20multi-?= =?UTF-8?q?key=20mode=20handling=20and=20improve=20channel=20update=20logi?= =?UTF-8?q?c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/channel.go | 20 +++- model/channel.go | 23 ++++- model/{cache.go => channel_cache.go} | 34 ++++--- web/src/components/table/ChannelsTable.js | 107 +++++++++++++++++----- web/src/pages/Channel/EditChannel.js | 94 ++++++++++--------- 5 files changed, 192 insertions(+), 86 deletions(-) rename model/{cache.go => channel_cache.go} (82%) diff --git a/controller/channel.go b/controller/channel.go index 6e387064..ee6ddeba 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -718,8 +718,13 @@ func DeleteChannelBatch(c *gin.Context) { return } +type PatchChannel struct { + model.Channel + MultiKeyMode *string `json:"multi_key_mode"` +} + func UpdateChannel(c *gin.Context) { - channel := model.Channel{} + channel := PatchChannel{} err := c.ShouldBindJSON(&channel) if err != nil { c.JSON(http.StatusOK, gin.H{ @@ -761,6 +766,19 @@ func UpdateChannel(c *gin.Context) { } } } + if channel.MultiKeyMode != nil && *channel.MultiKeyMode != "" { + originChannel, err := model.GetChannelById(channel.Id, false) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + } + if originChannel.ChannelInfo.IsMultiKey { + channel.ChannelInfo = originChannel.ChannelInfo + channel.ChannelInfo.MultiKeyMode = constant.MultiKeyMode(*channel.MultiKeyMode) + } + } err = channel.Update() if err != nil { c.JSON(http.StatusOK, gin.H{ diff --git a/model/channel.go b/model/channel.go index fea4ce61..6079bf3c 100644 --- a/model/channel.go +++ b/model/channel.go @@ -117,7 +117,15 @@ func (channel *Channel) GetNextEnabledKey() (string, *types.NewAPIError) { // Randomly pick one enabled key return keys[enabledIdx[rand.Intn(len(enabledIdx))]], nil case constant.MultiKeyModePolling: + defer func() { + if !common.MemoryCacheEnabled { + _ = channel.Save() + } else { + CacheUpdateChannel(channel) + } + }() // Start from the saved polling index and look for the next enabled key + println(channel.ChannelInfo.MultiKeyPollingIndex) start := channel.ChannelInfo.MultiKeyPollingIndex if start < 0 || start >= len(keys) { start = 0 @@ -127,6 +135,7 @@ func (channel *Channel) GetNextEnabledKey() (string, *types.NewAPIError) { if getStatus(idx) == common.ChannelStatusEnabled { // update polling index for next call (point to the next position) channel.ChannelInfo.MultiKeyPollingIndex = (idx + 1) % len(keys) + println(channel.ChannelInfo.MultiKeyPollingIndex) return keys[idx], nil } } @@ -273,14 +282,20 @@ func SearchChannels(keyword string, group string, model string, idSort bool) ([] } func GetChannelById(id int, selectAll bool) (*Channel, error) { - channel := Channel{Id: id} + channel := &Channel{Id: id} var err error = nil if selectAll { - err = DB.First(&channel, "id = ?", id).Error + err = DB.First(channel, "id = ?", id).Error } else { - err = DB.Omit("key").First(&channel, "id = ?", id).Error + err = DB.Omit("key").First(channel, "id = ?", id).Error } - return &channel, err + if err != nil { + return nil, err + } + if channel == nil { + return nil, errors.New("channel not found") + } + return channel, nil } func BatchInsertChannels(channels []Channel) error { diff --git a/model/cache.go b/model/channel_cache.go similarity index 82% rename from model/cache.go rename to model/channel_cache.go index 340d14ff..f4e38a39 100644 --- a/model/cache.go +++ b/model/channel_cache.go @@ -14,7 +14,7 @@ import ( "github.com/gin-gonic/gin" ) -var group2model2channels map[string]map[string][]*Channel +var group2model2channels map[string]map[string][]int var channelsIDM map[int]*Channel var channelSyncLock sync.RWMutex @@ -34,10 +34,10 @@ func InitChannelCache() { for _, ability := range abilities { groups[ability.Group] = true } - newGroup2model2channels := make(map[string]map[string][]*Channel) + newGroup2model2channels := make(map[string]map[string][]int) newChannelsIDM := make(map[int]*Channel) for group := range groups { - newGroup2model2channels[group] = make(map[string][]*Channel) + newGroup2model2channels[group] = make(map[string][]int) } for _, channel := range channels { newChannelsIDM[channel.Id] = channel @@ -46,9 +46,9 @@ func InitChannelCache() { models := strings.Split(channel.Models, ",") for _, model := range models { if _, ok := newGroup2model2channels[group][model]; !ok { - newGroup2model2channels[group][model] = make([]*Channel, 0) + newGroup2model2channels[group][model] = make([]int, 0) } - newGroup2model2channels[group][model] = append(newGroup2model2channels[group][model], channel) + newGroup2model2channels[group][model] = append(newGroup2model2channels[group][model], channel.Id) } } } @@ -57,7 +57,7 @@ func InitChannelCache() { for group, model2channels := range newGroup2model2channels { for model, channels := range model2channels { sort.Slice(channels, func(i, j int) bool { - return channels[i].GetPriority() > channels[j].GetPriority() + return newChannelsIDM[channels[i]].GetPriority() > newChannelsIDM[channels[j]].GetPriority() }) newGroup2model2channels[group][model] = channels } @@ -136,8 +136,12 @@ func getRandomSatisfiedChannel(group string, model string, retry int) (*Channel, } uniquePriorities := make(map[int]bool) - for _, channel := range channels { - uniquePriorities[int(channel.GetPriority())] = true + for _, channelId := range channels { + if channel, ok := channelsIDM[channelId]; ok { + uniquePriorities[int(channel.GetPriority())] = true + } else { + return nil, fmt.Errorf("数据库一致性错误,渠道# %d 不存在,请联系管理员修复", channelId) + } } var sortedUniquePriorities []int for priority := range uniquePriorities { @@ -152,9 +156,13 @@ func getRandomSatisfiedChannel(group string, model string, retry int) (*Channel, // get the priority for the given retry number var targetChannels []*Channel - for _, channel := range channels { - if channel.GetPriority() == targetPriority { - targetChannels = append(targetChannels, channel) + for _, channelId := range channels { + if channel, ok := channelsIDM[channelId]; ok { + if channel.GetPriority() == targetPriority { + targetChannels = append(targetChannels, channel) + } + } else { + return nil, fmt.Errorf("数据库一致性错误,渠道# %d 不存在,请联系管理员修复", channelId) } } @@ -210,9 +218,11 @@ func CacheUpdateChannel(channel *Channel) { } channelSyncLock.Lock() defer channelSyncLock.Unlock() - if channel == nil { return } + + println("CacheUpdateChannel:", channel.Id, channel.Name, channel.Status, channel.ChannelInfo.MultiKeyPollingIndex) + channelsIDM[channel.Id] = channel } diff --git a/web/src/components/table/ChannelsTable.js b/web/src/components/table/ChannelsTable.js index 67be1a2b..14a366f1 100644 --- a/web/src/components/table/ChannelsTable.js +++ b/web/src/components/table/ChannelsTable.js @@ -42,19 +42,20 @@ import { IconTreeTriangleDown, IconSearch, IconMore, - IconList + IconList, IconDescend2 } from '@douyinfe/semi-icons'; import { loadChannelModels, isMobile, copy } from '../../helpers'; import EditTagModal from '../../pages/Channel/EditTagModal.js'; import { useTranslation } from 'react-i18next'; import { useTableCompactMode } from '../../hooks/useTableCompactMode'; +import { FaRandom } from 'react-icons/fa'; const ChannelsTable = () => { const { t } = useTranslation(); let type2label = undefined; - const renderType = (type, multiKey = false) => { + const renderType = (type, channelInfo = undefined) => { if (!type2label) { type2label = new Map(); for (let i = 0; i < CHANNEL_OPTIONS.length; i++) { @@ -65,13 +66,20 @@ const ChannelsTable = () => { let icon = getChannelIcon(type); - if (multiKey) { + if (channelInfo?.is_multi_key) { icon = ( -
- - {icon} -
- ); + channelInfo?.multi_key_mode === 'random' ? ( +
+ + {icon} +
+ ) : ( +
+ + {icon} +
+ ) + ) } return ( @@ -587,24 +595,70 @@ const ChannelsTable = () => { /> - {record.status === 1 ? ( - + { + record.status === 1 ? ( + + ) : ( + + ) + } + manageChannel(record.id, 'enable_all', record), + } + ]} + > + + record.status === 1 ? ( + + ) : ( + + ) )}