diff --git a/README.md b/README.md index 66eef0e1..07fa3c85 100644 --- a/README.md +++ b/README.md @@ -410,6 +410,12 @@ https://openai.justsong.cn + 检查是否启用了 HTTPS,浏览器会拦截 HTTPS 域名下的 HTTP 请求。 6. 报错:`当前分组负载已饱和,请稍后再试` + 上游通道 429 了。 +7. 升级之后我的数据会丢失吗? + + 如果使用 MySQL,不会。 + + 如果使用 SQLite,需要按照我所给的部署命令挂载 volume 持久化 one-api.db 数据库文件,否则容器重启后数据会丢失。 +8. 升级之前数据库需要做变更吗? + + 一般情况下不需要,系统将在初始化的时候自动调整。 + + 如果需要的话,我会在更新日志中说明,并给出脚本。 ## 相关项目 * [FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统 diff --git a/common/constants.go b/common/constants.go index b84f27ad..6bdb31a4 100644 --- a/common/constants.go +++ b/common/constants.go @@ -165,9 +165,10 @@ const ( ) const ( - ChannelStatusUnknown = 0 - ChannelStatusEnabled = 1 // don't use 0, 0 is the default value! - ChannelStatusDisabled = 2 // also don't use 0 + ChannelStatusUnknown = 0 + ChannelStatusEnabled = 1 // don't use 0, 0 is the default value! + ChannelStatusManuallyDisabled = 2 // also don't use 0 + ChannelStatusAutoDisabled = 3 ) const ( diff --git a/controller/channel-test.go b/controller/channel-test.go index caadd2f2..f9b31a1d 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -209,7 +209,7 @@ func disableChannel(channelId int, channelName string, reason string) { if common.RootUserEmail == "" { common.RootUserEmail = model.GetRootUserEmail() } - model.UpdateChannelStatusById(channelId, common.ChannelStatusDisabled) + model.UpdateChannelStatusById(channelId, common.ChannelStatusAutoDisabled) subject := fmt.Sprintf("通道「%s」(#%d)已被禁用", channelName, channelId) content := fmt.Sprintf("通道「%s」(#%d)已被禁用,原因:%s", channelName, channelId, reason) err := common.SendEmail(subject, common.RootUserEmail, content) diff --git a/controller/channel.go b/controller/channel.go index d2403003..852fbf1b 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -128,6 +128,23 @@ func DeleteChannel(c *gin.Context) { return } +func DeleteManuallyDisabledChannel(c *gin.Context) { + rows, err := model.DeleteChannelByStatus(common.ChannelStatusManuallyDisabled) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + "data": rows, + }) + return +} + func UpdateChannel(c *gin.Context) { channel := model.Channel{} err := c.ShouldBindJSON(&channel) diff --git a/model/channel.go b/model/channel.go index 76db6dd9..d20a5172 100644 --- a/model/channel.go +++ b/model/channel.go @@ -187,3 +187,8 @@ func updateChannelUsedQuota(id int, quota int) { common.SysError("failed to update channel used quota: " + err.Error()) } } + +func DeleteChannelByStatus(status int64) (int64, error) { + result := DB.Where("status = ?", status).Delete(&Channel{}) + return result.RowsAffected, result.Error +} diff --git a/model/log.go b/model/log.go index c189e01d..d26da9a2 100644 --- a/model/log.go +++ b/model/log.go @@ -8,14 +8,14 @@ import ( ) type Log struct { - Id int `json:"id"` + Id int `json:"id;index:idx_created_at_id,priority:1"` UserId int `json:"user_id" gorm:"index"` - CreatedAt int64 `json:"created_at" gorm:"bigint;index"` - Type int `json:"type" gorm:"index"` + CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:2;index:idx_created_at_type"` + Type int `json:"type" gorm:"index:idx_created_at_type"` Content string `json:"content"` - Username string `json:"username" gorm:"index;default:''"` + Username string `json:"username" gorm:"index:index_username_model_name,priority:2;default:''"` TokenName string `json:"token_name" gorm:"index;default:''"` - ModelName string `json:"model_name" gorm:"index;default:''"` + ModelName string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"` Quota int `json:"quota" gorm:"default:0"` PromptTokens int `json:"prompt_tokens" gorm:"default:0"` CompletionTokens int `json:"completion_tokens" gorm:"default:0"` diff --git a/model/main.go b/model/main.go index 852f14e8..08182634 100644 --- a/model/main.go +++ b/model/main.go @@ -82,6 +82,7 @@ func InitDB() (err error) { if !common.IsMasterNode { return nil } + common.SysLog("database migration started") err = db.AutoMigrate(&Channel{}) if err != nil { return err diff --git a/router/api-router.go b/router/api-router.go index a2bcc8b6..f2a9fc4e 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -76,6 +76,7 @@ func SetApiRouter(router *gin.Engine) { channelRoute.GET("/update_balance/:id", controller.UpdateChannelBalance) channelRoute.POST("/", controller.AddChannel) channelRoute.PUT("/", controller.UpdateChannel) + channelRoute.DELETE("/manually_disabled", controller.DeleteManuallyDisabledChannel) channelRoute.DELETE("/:id", controller.DeleteChannel) } tokenRoute := apiRouter.Group("/token") diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index 519910f4..d5a29007 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import {Button, Form, Input, Label, Pagination, Popup, Table} from 'semantic-ui-react'; +import { Button, Form, Input, Label, Pagination, Popup, Table } from 'semantic-ui-react'; import { Link } from 'react-router-dom'; import { API, showError, showInfo, showNotice, showSuccess, timestamp2string } from '../helpers'; @@ -96,7 +96,7 @@ const ChannelsTable = () => { }); }, []); - const manageChannel = async (id, action, idx, priority) => { + const manageChannel = async (id, action, idx, value) => { let data = { id }; let res; switch (action) { @@ -112,10 +112,20 @@ const ChannelsTable = () => { res = await API.put('/api/channel/', data); break; case 'priority': - if (priority === '') { + if (value === '') { return; } - data.priority = parseInt(priority); + data.priority = parseInt(value); + res = await API.put('/api/channel/', data); + break; + case 'weight': + if (value === '') { + return; + } + data.weight = parseInt(value); + if (data.weight < 0) { + data.weight = 0; + } res = await API.put('/api/channel/', data); break; } @@ -142,9 +152,23 @@ const ChannelsTable = () => { return ; case 2: return ( - + + 已禁用 + } + content='本渠道被手动禁用' + basic + /> + ); + case 3: + return ( + + 已禁用 + } + content='本渠道被程序自动禁用' + basic + /> ); default: return ( @@ -216,6 +240,17 @@ const ChannelsTable = () => { } }; + const deleteAllManuallyDisabledChannels = async () => { + const res = await API.delete(`/api/channel/manually_disabled`); + const { success, message, data } = res.data; + if (success) { + showSuccess(`已删除所有手动禁用渠道,共计 ${data} 个`); + await refresh(); + } else { + showError(message); + } + }; + const updateChannelBalance = async (id, name, idx) => { const res = await API.get(`/api/channel/update_balance/${id}/`); const { success, message, balance } = res.data; @@ -343,10 +378,10 @@ const ChannelsTable = () => { 余额 { - sortChannel('priority'); - }} + style={{ cursor: 'pointer' }} + onClick={() => { + sortChannel('priority'); + }} > 优先级 @@ -390,18 +425,18 @@ const ChannelsTable = () => { { - manageChannel( - channel.id, - 'priority', - idx, - event.target.value, - ); - }}> - - } - content='渠道选择优先级,越高越优先' - basic + trigger={ { + manageChannel( + channel.id, + 'priority', + idx, + event.target.value + ); + }}> + + } + content='渠道选择优先级,越高越优先' + basic /> @@ -481,6 +516,20 @@ const ChannelsTable = () => { + + 删除所有手动禁用渠道 + + } + on='click' + flowing + hoverable + > + +