feat: sync deployed v1.0.0-rc.38-custom.42
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s

Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
cneicy committed 2026-09-26 04:49:11 +08:00
1 parent c2b44332d6
commit a5f7b8e586
828 files changed
+122253 -13702

No files matched your search

+141 -5
View File
@@ -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),