feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
+141
-5
@@ -73,6 +73,68 @@ func clearChannelInfo(channel *model.Channel) {
|
||||
}
|
||||
}
|
||||
|
||||
func channelIDsFromChannels(channels []*model.Channel) []int {
|
||||
ids := make([]int, 0, len(channels))
|
||||
for _, channel := range channels {
|
||||
if channel != nil && channel.Id > 0 {
|
||||
ids = append(ids, channel.Id)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func closeActiveChannelWebSockets(channelIDs []int) {
|
||||
service.CloseActiveWebSocketsForChannels(channelIDs, service.ChannelDisabledCloseReason)
|
||||
}
|
||||
|
||||
func hasEnabledMultiKey(channel *model.Channel) bool {
|
||||
if channel == nil || !channel.ChannelInfo.IsMultiKey {
|
||||
return true
|
||||
}
|
||||
keys := channel.GetKeys()
|
||||
if len(keys) == 0 {
|
||||
return false
|
||||
}
|
||||
for i := range keys {
|
||||
if channel.ChannelInfo.MultiKeyStatusList == nil {
|
||||
return true
|
||||
}
|
||||
if status, ok := channel.ChannelInfo.MultiKeyStatusList[i]; !ok || status == common.ChannelStatusEnabled {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func disableMultiKeyChannelIfUnavailable(channel *model.Channel) bool {
|
||||
if channel == nil || !channel.ChannelInfo.IsMultiKey || hasEnabledMultiKey(channel) {
|
||||
return false
|
||||
}
|
||||
if channel.Status != common.ChannelStatusEnabled {
|
||||
return true
|
||||
}
|
||||
channel.Status = common.ChannelStatusManuallyDisabled
|
||||
info := channel.GetOtherInfo()
|
||||
info["status_reason"] = model.ChannelStatusReasonAllKeysDisabled
|
||||
info["status_time"] = common.GetTimestamp()
|
||||
channel.SetOtherInfo(info)
|
||||
return true
|
||||
}
|
||||
|
||||
func restoreMultiKeyChannelIfAvailable(channel *model.Channel) {
|
||||
if channel.Status != common.ChannelStatusManuallyDisabled || !hasEnabledMultiKey(channel) {
|
||||
return
|
||||
}
|
||||
info := channel.GetOtherInfo()
|
||||
if info["status_reason"] != model.ChannelStatusReasonAllKeysDisabled {
|
||||
return
|
||||
}
|
||||
channel.Status = common.ChannelStatusEnabled
|
||||
info["status_reason"] = ""
|
||||
info["status_time"] = common.GetTimestamp()
|
||||
channel.SetOtherInfo(info)
|
||||
}
|
||||
|
||||
func applyChannelStatusFilter(query *gorm.DB, statusFilter int) *gorm.DB {
|
||||
if statusFilter == common.ChannelStatusEnabled {
|
||||
return query.Where("status = ?", common.ChannelStatusEnabled)
|
||||
@@ -94,11 +156,38 @@ func buildChannelListQuery(group string, statusFilter int, typeFilter int) *gorm
|
||||
}
|
||||
|
||||
func GetChannelOps(c *gin.Context) {
|
||||
snapshot := model.CurrentRequestPolicy()
|
||||
automaticDisable, source := snapshot.AutoDisable, "global"
|
||||
if value, present := c.GetQuery("auto_ban"); present {
|
||||
autoBan, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
automaticDisable = snapshot.AutoDisable && autoBan
|
||||
if snapshot.AutoDisable {
|
||||
source = "global_and_channel"
|
||||
if !autoBan {
|
||||
source = "channel"
|
||||
}
|
||||
}
|
||||
}
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"retry_times": common.RetryTimes,
|
||||
"retry_times": snapshot.RetryTimes,
|
||||
"request_policy": gin.H{"automatic_disable": automaticDisable, "source": source},
|
||||
})
|
||||
}
|
||||
|
||||
func GetChannelDefaultBaseURLs(c *gin.Context) {
|
||||
baseURLs := make(map[int]string)
|
||||
for channelType, baseURL := range constant.ChannelBaseURLs {
|
||||
if baseURL != "" {
|
||||
baseURLs[channelType] = baseURL
|
||||
}
|
||||
}
|
||||
common.ApiSuccess(c, baseURLs)
|
||||
}
|
||||
|
||||
func GetAllChannels(c *gin.Context) {
|
||||
pageInfo := common.GetPageQuery(c)
|
||||
channelData := make([]*model.Channel, 0)
|
||||
@@ -485,6 +574,12 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
|
||||
if channel.Type == constant.ChannelTypeNewAPI && strings.TrimSpace(channel.GetBaseURL()) == "" {
|
||||
return fmt.Errorf("New API channel base URL cannot be empty")
|
||||
}
|
||||
if channel.Type == constant.ChannelTypeVLLM && strings.TrimSpace(channel.GetBaseURL()) == "" {
|
||||
return fmt.Errorf("vLLM channel base URL cannot be empty")
|
||||
}
|
||||
if channel.Type == constant.ChannelTypeSGLang && strings.TrimSpace(channel.GetBaseURL()) == "" {
|
||||
return fmt.Errorf("SGLang channel base URL cannot be empty")
|
||||
}
|
||||
|
||||
// 如果是添加操作,检查 channel 和 key 是否为空
|
||||
if isAdd {
|
||||
@@ -757,6 +852,7 @@ func DeleteChannel(c *gin.Context) {
|
||||
"id": id,
|
||||
"name": channelName,
|
||||
})
|
||||
closeActiveChannelWebSockets([]int{id})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -765,6 +861,13 @@ func DeleteChannel(c *gin.Context) {
|
||||
}
|
||||
|
||||
func DeleteDisabledChannel(c *gin.Context) {
|
||||
var ids []int
|
||||
if err := model.DB.Model(&model.Channel{}).
|
||||
Where("status = ? or status = ?", common.ChannelStatusAutoDisabled, common.ChannelStatusManuallyDisabled).
|
||||
Pluck("id", &ids).Error; err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
rows, err := model.DeleteDisabledChannel()
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
@@ -777,6 +880,7 @@ func DeleteDisabledChannel(c *gin.Context) {
|
||||
recordManageAudit(c, "channel.delete_disabled", map[string]any{
|
||||
"count": rows,
|
||||
})
|
||||
closeActiveChannelWebSockets(ids)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -807,6 +911,12 @@ func DisableTagChannels(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
channels, err := model.GetChannelsByTag(channelTag.Tag, false, false)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
ids := channelIDsFromChannels(channels)
|
||||
err = model.DisableChannelByTag(channelTag.Tag)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
@@ -816,6 +926,7 @@ func DisableTagChannels(c *gin.Context) {
|
||||
recordManageAudit(c, "channel.tag_disable", map[string]any{
|
||||
"tag": channelTag.Tag,
|
||||
})
|
||||
closeActiveChannelWebSockets(ids)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -936,6 +1047,7 @@ func DeleteChannelBatch(c *gin.Context) {
|
||||
recordManageAudit(c, "channel.delete_batch", map[string]any{
|
||||
"count": deletedCount,
|
||||
})
|
||||
closeActiveChannelWebSockets(channelBatch.Ids)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -1170,6 +1282,9 @@ func UpdateChannelStatus(c *gin.Context) {
|
||||
changed := model.UpdateChannelStatus(id, "", req.Status, "manual operation")
|
||||
if changed {
|
||||
model.InitChannelCache()
|
||||
if req.Status != common.ChannelStatusEnabled {
|
||||
closeActiveChannelWebSockets([]int{id})
|
||||
}
|
||||
}
|
||||
recordManageAudit(c, "channel.status_update", map[string]any{
|
||||
"id": id,
|
||||
@@ -1190,14 +1305,21 @@ func BatchUpdateChannelStatus(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
changedCount := 0
|
||||
var disabledIDs []int
|
||||
for _, id := range req.Ids {
|
||||
if model.UpdateChannelStatus(id, "", req.Status, "manual batch operation") {
|
||||
changedCount++
|
||||
if req.Status != common.ChannelStatusEnabled {
|
||||
disabledIDs = append(disabledIDs, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
if changedCount > 0 {
|
||||
model.InitChannelCache()
|
||||
}
|
||||
if len(disabledIDs) > 0 {
|
||||
closeActiveChannelWebSockets(disabledIDs)
|
||||
}
|
||||
recordManageAudit(c, "channel.status_update_batch", map[string]any{
|
||||
"count": changedCount,
|
||||
"total": len(req.Ids),
|
||||
@@ -1714,13 +1836,16 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
|
||||
channel.ChannelInfo.MultiKeyStatusList[keyIndex] = 2 // disabled
|
||||
|
||||
shouldCloseWebSocket := disableMultiKeyChannelIfUnavailable(channel)
|
||||
err = channel.Update()
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
model.InitChannelCache()
|
||||
if shouldCloseWebSocket {
|
||||
closeActiveChannelWebSockets([]int{channel.Id})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "密钥已禁用",
|
||||
@@ -1755,6 +1880,7 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
if channel.ChannelInfo.MultiKeyDisabledReason != nil {
|
||||
delete(channel.ChannelInfo.MultiKeyDisabledReason, keyIndex)
|
||||
}
|
||||
restoreMultiKeyChannelIfAvailable(channel)
|
||||
|
||||
err = channel.Update()
|
||||
if err != nil {
|
||||
@@ -1779,6 +1905,7 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
channel.ChannelInfo.MultiKeyStatusList = make(map[int]int)
|
||||
channel.ChannelInfo.MultiKeyDisabledTime = make(map[int]int64)
|
||||
channel.ChannelInfo.MultiKeyDisabledReason = make(map[int]string)
|
||||
restoreMultiKeyChannelIfAvailable(channel)
|
||||
|
||||
err = channel.Update()
|
||||
if err != nil {
|
||||
@@ -1827,13 +1954,16 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
shouldCloseWebSocket := disableMultiKeyChannelIfUnavailable(channel)
|
||||
err = channel.Update()
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
model.InitChannelCache()
|
||||
if shouldCloseWebSocket {
|
||||
closeActiveChannelWebSockets([]int{channel.Id})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": fmt.Sprintf("已禁用 %d 个密钥", disabledCount),
|
||||
@@ -1907,13 +2037,16 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
channel.ChannelInfo.MultiKeyDisabledTime = newDisabledTime
|
||||
channel.ChannelInfo.MultiKeyDisabledReason = newDisabledReason
|
||||
|
||||
shouldCloseWebSocket := disableMultiKeyChannelIfUnavailable(channel)
|
||||
err = channel.Update()
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
model.InitChannelCache()
|
||||
if shouldCloseWebSocket {
|
||||
closeActiveChannelWebSockets([]int{channel.Id})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "密钥已删除",
|
||||
@@ -1975,13 +2108,16 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
channel.ChannelInfo.MultiKeyDisabledTime = newDisabledTime
|
||||
channel.ChannelInfo.MultiKeyDisabledReason = newDisabledReason
|
||||
|
||||
shouldCloseWebSocket := disableMultiKeyChannelIfUnavailable(channel)
|
||||
err = channel.Update()
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
model.InitChannelCache()
|
||||
if shouldCloseWebSocket {
|
||||
closeActiveChannelWebSockets([]int{channel.Id})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": fmt.Sprintf("已删除 %d 个自动禁用的密钥", deletedCount),
|
||||
|
||||
Reference in new issue
Block a user