From 4701897e2e5115310ef5ba78ccbec8fe2a43475b Mon Sep 17 00:00:00 2001 From: JustSong Date: Mon, 2 Oct 2023 12:11:02 +0800 Subject: [PATCH 1/4] chore: sync database indices with pro version --- model/log.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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"` From cbd62011b89e06390f8090b3766da7f82cf53060 Mon Sep 17 00:00:00 2001 From: JustSong Date: Mon, 2 Oct 2023 12:13:30 +0800 Subject: [PATCH 2/4] chore: add database migration prompt --- model/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/model/main.go b/model/main.go index d422c4e0..0e962049 100644 --- a/model/main.go +++ b/model/main.go @@ -81,6 +81,7 @@ func InitDB() (err error) { if !common.IsMasterNode { return nil } + common.SysLog("database migration started") err = db.AutoMigrate(&Channel{}) if err != nil { return err From 8d34b7a77ea7915a029c089e497510e0baf59858 Mon Sep 17 00:00:00 2001 From: JustSong Date: Mon, 2 Oct 2023 13:06:27 +0800 Subject: [PATCH 3/4] feat: able to delete all manually disabled channels (close #539) --- common/constants.go | 7 ++- controller/channel-test.go | 2 +- controller/channel.go | 17 +++++ model/channel.go | 5 ++ router/api-router.go | 1 + web/src/components/ChannelsTable.js | 97 ++++++++++++++++++++++------- 6 files changed, 101 insertions(+), 28 deletions(-) diff --git a/common/constants.go b/common/constants.go index fe412e1a..d07dcc8a 100644 --- a/common/constants.go +++ b/common/constants.go @@ -156,9 +156,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 f7a565a2..1974ef6e 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -141,7 +141,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 50b2b5f6..41a55550 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -127,6 +127,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 61fe9093..36bb78a5 100644 --- a/model/channel.go +++ b/model/channel.go @@ -176,3 +176,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/router/api-router.go b/router/api-router.go index d12bc54b..5ec385dc 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -74,6 +74,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 7c8457d0..57d45c55 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 ( @@ -202,7 +226,7 @@ const ChannelsTable = () => { showInfo(`通道 ${name} 测试成功,耗时 ${time.toFixed(2)} 秒。`); } else { showError(message); - showNotice("当前版本测试是通过按照 OpenAI API 格式使用 gpt-3.5-turbo 模型进行非流式请求实现的,因此测试报错并不一定代表通道不可用,该功能后续会修复。") + showNotice('当前版本测试是通过按照 OpenAI API 格式使用 gpt-3.5-turbo 模型进行非流式请求实现的,因此测试报错并不一定代表通道不可用,该功能后续会修复。'); } }; @@ -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 + > + + Date: Mon, 2 Oct 2023 13:15:35 +0800 Subject: [PATCH 4/4] docs: update readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8af3abf0..98e38b8e 100644 --- a/README.md +++ b/README.md @@ -389,6 +389,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 大语言模型的知识库问答系统