diff --git a/README.en.md b/README.en.md index b29d4544..f9abfee8 100644 --- a/README.en.md +++ b/README.en.md @@ -285,6 +285,10 @@ If the channel ID is not provided, load balancing will be used to distribute the ## Note This project is an open-source project. Please use it in compliance with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**. It must not be used for illegal purposes. -This project is open-sourced under the MIT license. One must somehow retain the copyright information of One API. +This project is released under the MIT license. Based on this, attribution and a link to this project must be included at the bottom of the page. + +The same applies to derivative projects based on this project. + +If you do not wish to include attribution, prior authorization must be obtained. According to the MIT license, users should bear the risk and responsibility of using this project, and the developer of this open-source project is not responsible for this. diff --git a/README.md b/README.md index 1f522f03..fad214c1 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,10 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用 ### 基于 Docker 进行部署 部署命令:`docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api` +如果上面的镜像无法拉取,可以尝试使用 GitHub 的 Docker 镜像,将上面的 `justsong/one-api` 替换为 `ghcr.io/songquanpeng/one-api` 即可。 + +如果你的并发量较大,推荐设置 `SQL_DSN`,详见下面[环境变量](#环境变量)一节。 + 更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR` `-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。 @@ -158,8 +162,8 @@ sudo service nginx restart ### 多机部署 1. 所有服务器 `SESSION_SECRET` 设置一样的值。 2. 必须设置 `SQL_DSN`,使用 MySQL 数据库而非 SQLite,所有服务器连接同一个数据库。 -3. 所有从服务器必须设置 `NODE_TYPE` 为 `slave`。 -4. 设置 `SYNC_FREQUENCY` 后服务器将定期从数据库同步配置。 +3. 所有从服务器必须设置 `NODE_TYPE` 为 `slave`,不设置则默认为主服务器。 +4. 设置 `SYNC_FREQUENCY` 后服务器将定期从数据库同步配置,在使用远程数据库的情况下,推荐设置该项并启用 Redis,无论主从。 5. 从服务器可以选择设置 `FRONTEND_BASE_URL`,以重定向页面请求到主服务器。 6. 从服务器上**分别**装好 Redis,设置好 `REDIS_CONN_STRING`,这样可以做到在缓存未过期的情况下数据库零访问,可以减少延迟。 7. 如果主服务器访问数据库延迟也比较高,则也需要启用 Redis,并设置 `SYNC_FREQUENCY`,以定期从数据库同步配置。 @@ -231,6 +235,8 @@ docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://ope 等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。 +**Note**:如果你不知道某个配置项的含义,可以临时删掉值以看到进一步的提示文字。 + ## 使用方法 在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。 @@ -261,7 +267,10 @@ graph LR + 例子:`SESSION_SECRET=random_string` 3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 8.0 版本。 + 例子:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi` -4. `FRONTEND_BASE_URL`:设置之后将使用指定的前端地址,而非后端地址,仅限从服务器设置。 + + 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。 + + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。 + + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。 +4. `FRONTEND_BASE_URL`:设置之后将重定向页面请求到指定的地址,仅限从服务器设置。 + 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn` 5. `SYNC_FREQUENCY`:设置之后将定期与数据库同步配置,单位为秒,未设置则不进行同步。 + 例子:`SYNC_FREQUENCY=60` @@ -317,6 +326,8 @@ https://openai.justsong.cn ## 注意 本项目为开源项目,请在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 -本项目使用 MIT 协议进行开源,请以某种方式保留 One API 的版权信息。 +本项目使用 MIT 协议进行开源,**在此基础上**,必须在页面底部保留署名以及指向本项目的链接。如果不想保留署名,必须首先获得授权。 + +同样适用于基于本项目的二开项目。 依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。 \ No newline at end of file diff --git a/controller/token.go b/controller/token.go index c486e341..5341ea3a 100644 --- a/controller/token.go +++ b/controller/token.go @@ -180,10 +180,10 @@ func UpdateToken(c *gin.Context) { return } if token.Status == common.TokenStatusEnabled { - if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() { + if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() && cleanToken.ExpiredTime != -1 { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": "令牌已过期,无法启用,请先修改令牌过期时间", + "message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期", }) return } diff --git a/i18n/en.json b/i18n/en.json index fe15cd20..f74b5102 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -36,7 +36,7 @@ "通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)", "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。": "The current group load is saturated, please try again later, or upgrade your account to improve service quality.", "令牌名称长度必须在1-20之间": "The length of the token name must be between 1-20", - "令牌已过期,无法启用,请先修改令牌过期时间": "The token has expired and cannot be enabled. Please modify the token expiration time first", + "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.", "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota", "管理员关闭了密码登录": "The administrator has turned off password login", "无法保存会话信息,请重试": "Unable to save session information, please try again", diff --git a/router/main.go b/router/main.go index 2cb50364..b8ac4055 100644 --- a/router/main.go +++ b/router/main.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/gin-gonic/gin" "net/http" + "one-api/common" "os" "strings" ) @@ -14,6 +15,10 @@ func SetRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) { SetDashboardRouter(router) SetRelayRouter(router) frontendBaseUrl := os.Getenv("FRONTEND_BASE_URL") + if common.IsMasterNode && frontendBaseUrl != "" { + frontendBaseUrl = "" + common.SysLog("FRONTEND_BASE_URL is ignored on master node") + } if frontendBaseUrl == "" { SetWebRouter(router, buildFS, indexPage) } else { diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index a8df2fc3..54439ec5 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -60,8 +60,8 @@ const ChannelsTable = () => { if (startIdx === 0) { setChannels(data); } else { - let newChannels = channels; - newChannels.push(...data); + let newChannels = [...channels]; + newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); setChannels(newChannels); } } else { @@ -82,7 +82,7 @@ const ChannelsTable = () => { const refresh = async () => { setLoading(true); - await loadChannels(0); + await loadChannels(activePage - 1); }; useEffect(() => { @@ -240,7 +240,7 @@ const ChannelsTable = () => { if (channels.length === 0) return; setLoading(true); let sortedChannels = [...channels]; - if (typeof sortedChannels[0][key] === 'string'){ + if (typeof sortedChannels[0][key] === 'string') { sortedChannels.sort((a, b) => { return ('' + a[key]).localeCompare(b[key]); }); diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index 6d78d22a..15b6a6bf 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -109,7 +109,7 @@ const LogsTable = () => { setLogs(data); } else { let newLogs = [...logs]; - newLogs.push(...data); + newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); setLogs(newLogs); } } else { diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js index 54f4ef8c..b42f7df8 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/TokensTable.js @@ -45,8 +45,8 @@ const TokensTable = () => { if (startIdx === 0) { setTokens(data); } else { - let newTokens = tokens; - newTokens.push(...data); + let newTokens = [...tokens]; + newTokens.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); setTokens(newTokens); } } else { @@ -67,7 +67,7 @@ const TokensTable = () => { const refresh = async () => { setLoading(true); - await loadTokens(0); + await loadTokens(activePage - 1); } useEffect(() => { diff --git a/web/src/pages/Token/EditToken.js b/web/src/pages/Token/EditToken.js index aa2fb2a7..a4b6044f 100644 --- a/web/src/pages/Token/EditToken.js +++ b/web/src/pages/Token/EditToken.js @@ -11,7 +11,7 @@ const EditToken = () => { const [loading, setLoading] = useState(isEdit); const originInputs = { name: '', - remain_quota: 0, + remain_quota: isEdit ? 0 : 500000, expired_time: -1, unlimited_quota: false };