diff --git a/README.md b/README.md index 79bda3c6..6a019e36 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用 ## 部署 ### 基于 Docker 进行部署 -部署命令:`docker run --name one-api -d --restart always -p 3000:3000 -v /home/ubuntu/data/one-api:/data justsong/one-api` +部署命令:`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` 更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR` @@ -266,8 +266,8 @@ https://openai.justsong.cn ## 常见问题 1. 额度是什么?怎么计算的?One API 的额度计算有问题? - + 额度 = token * 倍率 - + 倍率包括分组的倍率,以及补全的倍率。 + + 额度 = 分组倍率 * 模型倍率 * (提示 token 数 + 补全 token 数 * 补全倍率) + + 其中补全倍率对于 GPT3.5 固定为 1.33,GPT4 为 2,与官方保持一致。 + 如果是非流模式,官方接口会返回消耗的总 token,但是你要注意提示和补全的消耗倍率不一样。 2. 账户额度足够为什么提示额度不足? + 请检查你的令牌额度是否足够,这个和账户额度是分开的。 diff --git a/common/group-ratio.go b/common/group-ratio.go index b9efbdad..1ec73c78 100644 --- a/common/group-ratio.go +++ b/common/group-ratio.go @@ -11,7 +11,7 @@ var GroupRatio = map[string]float64{ func GroupRatio2JSONString() string { jsonBytes, err := json.Marshal(GroupRatio) if err != nil { - SysError("Error marshalling model ratio: " + err.Error()) + SysError("error marshalling model ratio: " + err.Error()) } return string(jsonBytes) } @@ -24,7 +24,7 @@ func UpdateGroupRatioByJSONString(jsonStr string) error { func GetGroupRatio(name string) float64 { ratio, ok := GroupRatio[name] if !ok { - SysError("Group ratio not found: " + name) + SysError("group ratio not found: " + name) return 1 } return ratio diff --git a/common/model-ratio.go b/common/model-ratio.go index d6160c90..3f9934a8 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -40,7 +40,7 @@ var ModelRatio = map[string]float64{ func ModelRatio2JSONString() string { jsonBytes, err := json.Marshal(ModelRatio) if err != nil { - SysError("Error marshalling model ratio: " + err.Error()) + SysError("error marshalling model ratio: " + err.Error()) } return string(jsonBytes) } @@ -53,7 +53,7 @@ func UpdateModelRatioByJSONString(jsonStr string) error { func GetModelRatio(name string) float64 { ratio, ok := ModelRatio[name] if !ok { - SysError("Model ratio not found: " + name) + SysError("model ratio not found: " + name) return 30 } return ratio diff --git a/common/redis.go b/common/redis.go index a46ddf70..07aa67ac 100644 --- a/common/redis.go +++ b/common/redis.go @@ -20,7 +20,7 @@ func InitRedisClient() (err error) { SysLog("Redis is enabled") opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING")) if err != nil { - FatalLog(err) + FatalLog("failed to parse Redis connection string: " + err.Error()) } RDB = redis.NewClient(opt) @@ -28,13 +28,16 @@ func InitRedisClient() (err error) { defer cancel() _, err = RDB.Ping(ctx).Result() + if err != nil { + FatalLog("Redis ping test failed: " + err.Error()) + } return err } func ParseRedisOption() *redis.Options { opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING")) if err != nil { - panic(err) + FatalLog("failed to parse Redis connection string: " + err.Error()) } return opt } diff --git a/controller/channel-test.go b/controller/channel-test.go index 9137407d..0de17116 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -129,7 +129,7 @@ func disableChannel(channelId int, channelName string, reason string) { content := fmt.Sprintf("通道「%s」(#%d)已被禁用,原因:%s", channelName, channelId, reason) err := common.SendEmail(subject, common.RootUserEmail, content) if err != nil { - common.SysError(fmt.Sprintf("发送邮件失败:%s", err.Error())) + common.SysError(fmt.Sprintf("failed to send email: %s", err.Error())) } } @@ -176,7 +176,7 @@ func testAllChannels(c *gin.Context) error { } err := common.SendEmail("通道测试完成", common.RootUserEmail, "通道测试完成,如果没有收到禁用通知,说明所有通道都正常") if err != nil { - common.SysError(fmt.Sprintf("发送邮件失败:%s", err.Error())) + common.SysError(fmt.Sprintf("failed to send email: %s", err.Error())) } testAllChannelsLock.Lock() testAllChannelsRunning = false diff --git a/controller/relay-text.go b/controller/relay-text.go index 52c1dd88..66a09c10 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -152,7 +152,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { quotaDelta := quota - preConsumedQuota err := model.PostConsumeTokenQuota(tokenId, quotaDelta) if err != nil { - common.SysError("Error consuming token remain quota: " + err.Error()) + common.SysError("error consuming token remain quota: " + err.Error()) } //tokenName := c.GetString("token_name") //model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)", tokenName, textRequest.Model, common.LogQuota(quota), modelRatio, groupRatio)) @@ -186,7 +186,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { for scanner.Scan() { data := scanner.Text() if len(data) < 6 { // must be something wrong! - common.SysError("Invalid stream response: " + data) + common.SysError("invalid stream response: " + data) continue } dataChan <- data @@ -197,7 +197,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { var streamResponse ChatCompletionsStreamResponse err = json.Unmarshal([]byte(data), &streamResponse) if err != nil { - common.SysError("Error unmarshalling stream response: " + err.Error()) + common.SysError("error unmarshalling stream response: " + err.Error()) return } for _, choice := range streamResponse.Choices { @@ -207,7 +207,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { var streamResponse CompletionsStreamResponse err = json.Unmarshal([]byte(data), &streamResponse) if err != nil { - common.SysError("Error unmarshalling stream response: " + err.Error()) + common.SysError("error unmarshalling stream response: " + err.Error()) return } for _, choice := range streamResponse.Choices { diff --git a/controller/relay.go b/controller/relay.go index 047e2860..811c5516 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -119,7 +119,7 @@ func Relay(c *gin.Context) { "error": err.OpenAIError, }) channelId := c.GetInt("channel_id") - common.SysError(fmt.Sprintf("Relay error (channel #%d): %s", channelId, err.Message)) + common.SysError(fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Message)) // https://platform.openai.com/docs/guides/error-codes/api-errors if common.AutomaticDisableChannelEnabled && (err.Type == "insufficient_quota" || err.Code == "invalid_api_key") { channelId := c.GetInt("channel_id") diff --git a/docker-compose.yml b/docker-compose.yml index a9e1f844..b234ebfc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.4' services: one-api: - image: ghcr.io/songquanpeng/one-api:latest + image: justsong/one-api:latest container_name: one-api restart: always command: --log-dir /app/logs @@ -11,12 +11,22 @@ services: volumes: - ./data:/data - ./logs:/app/logs - # environment: - # REDIS_CONN_STRING: redis://default:redispw@localhost:49153 - # SESSION_SECRET: random_string - # SQL_DSN: root:123456@tcp(localhost:3306)/one-api + environment: + - SQL_DSN=root:123456@tcp(host.docker.internal:3306)/one-api # 修改此行,或注释掉以使用 SQLite 作为数据库 + - REDIS_CONN_STRING=redis://redis:6379 + - SESSION_SECRET=random_string # 修改为随机字符串 + - TZ=Asia/Shanghai +# - SYNC_FREQUENCY=60 # 多机部署时从节点取消注释该行 +# - FRONTEND_BASE_URL=https://openai.justsong.cn # 多机部署时从节点取消注释该行 healthcheck: - test: ["CMD-SHELL", "curl -s http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk '{print $2}' | grep 'true'"] + test: [ "CMD-SHELL", "curl -s http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk '{print $2}' | grep 'true'" ] interval: 30s timeout: 10s retries: 3 + + redis: + image: redis:latest + container_name: redis + restart: always + ports: + - "6379:6379" diff --git a/main.go b/main.go index fb6dc1b5..a6430a12 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" - "log" "one-api/common" "one-api/middleware" "one-api/model" @@ -30,19 +29,19 @@ func main() { // Initialize SQL Database err := model.InitDB() if err != nil { - common.FatalLog(err) + common.FatalLog("failed to initialize database: " + err.Error()) } defer func() { err := model.CloseDB() if err != nil { - common.FatalLog(err) + common.FatalLog("failed to close database: " + err.Error()) } }() // Initialize Redis err = common.InitRedisClient() if err != nil { - common.FatalLog(err) + common.FatalLog("failed to initialize Redis: " + err.Error()) } // Initialize options @@ -53,7 +52,7 @@ func main() { if os.Getenv("SYNC_FREQUENCY") != "" { frequency, err := strconv.Atoi(os.Getenv("SYNC_FREQUENCY")) if err != nil { - common.FatalLog(err) + common.FatalLog("failed to parse SYNC_FREQUENCY: " + err.Error()) } go model.SyncOptions(frequency) if common.RedisEnabled { @@ -84,6 +83,6 @@ func main() { } err = server.Run(":" + port) if err != nil { - log.Println(err) + common.FatalLog("failed to start HTTP server: " + err.Error()) } } diff --git a/model/cache.go b/model/cache.go index 8caa2cfe..82841d24 100644 --- a/model/cache.go +++ b/model/cache.go @@ -137,13 +137,13 @@ func InitChannelCache() { channelSyncLock.Lock() group2model2channels = newGroup2model2channels channelSyncLock.Unlock() - common.SysLog("Channels synced from database") + common.SysLog("channels synced from database") } func SyncChannelCache(frequency int) { for { time.Sleep(time.Duration(frequency) * time.Second) - common.SysLog("Syncing channels from database") + common.SysLog("syncing channels from database") InitChannelCache() } } diff --git a/model/main.go b/model/main.go index b4932ff5..5bc5ce19 100644 --- a/model/main.go +++ b/model/main.go @@ -54,7 +54,7 @@ func InitDB() (err error) { PrepareStmt: true, // precompile SQL }) } - common.SysLog("Database connected") + common.SysLog("database connected") if err == nil { DB = db if !common.IsMasterNode { @@ -88,7 +88,7 @@ func InitDB() (err error) { if err != nil { return err } - common.SysLog("Database migrated") + common.SysLog("database migrated") err = createRootAccountIfNeed() return err } else { diff --git a/model/option.go b/model/option.go index 12b1ce70..cbf14d0b 100644 --- a/model/option.go +++ b/model/option.go @@ -75,7 +75,7 @@ func loadOptionsFromDatabase() { for _, option := range options { err := updateOptionMap(option.Key, option.Value) if err != nil { - common.SysError("Failed to update option map: " + err.Error()) + common.SysError("failed to update option map: " + err.Error()) } } } @@ -83,7 +83,7 @@ func loadOptionsFromDatabase() { func SyncOptions(frequency int) { for { time.Sleep(time.Duration(frequency) * time.Second) - common.SysLog("Syncing options from database") + common.SysLog("syncing options from database") loadOptionsFromDatabase() } } diff --git a/model/redemption.go b/model/redemption.go index d60eb649..b821fd7c 100644 --- a/model/redemption.go +++ b/model/redemption.go @@ -64,7 +64,7 @@ func Redeem(key string, userId int) (quota int, err error) { redemption.Status = common.RedemptionCodeStatusUsed err := redemption.SelectUpdate() if err != nil { - common.SysError("更新兑换码状态失败:" + err.Error()) + common.SysError("failed to update redemption status: " + err.Error()) } RecordLog(userId, LogTypeTopup, fmt.Sprintf("通过兑换码充值 %s", common.LogQuota(redemption.Quota))) }() diff --git a/model/token.go b/model/token.go index d739cea6..83760f45 100644 --- a/model/token.go +++ b/model/token.go @@ -45,7 +45,7 @@ func ValidateUserToken(key string) (token *Token, err error) { token.Status = common.TokenStatusExpired err := token.SelectUpdate() if err != nil { - common.SysError("更新令牌状态失败:" + err.Error()) + common.SysError("failed to update token status" + err.Error()) } return nil, errors.New("该令牌已过期") } @@ -53,7 +53,7 @@ func ValidateUserToken(key string) (token *Token, err error) { token.Status = common.TokenStatusExhausted err := token.SelectUpdate() if err != nil { - common.SysError("更新令牌状态失败:" + err.Error()) + common.SysError("failed to update token status" + err.Error()) } return nil, errors.New("该令牌额度已用尽") } @@ -61,7 +61,7 @@ func ValidateUserToken(key string) (token *Token, err error) { token.AccessedTime = common.GetTimestamp() err := token.SelectUpdate() if err != nil { - common.SysError("更新令牌失败:" + err.Error()) + common.SysError("failed to update token" + err.Error()) } }() return token, nil @@ -166,7 +166,7 @@ func PreConsumeTokenQuota(tokenId int, quota int) (err error) { go func() { email, err := GetUserEmail(token.UserId) if err != nil { - common.SysError("获取用户邮箱失败:" + err.Error()) + common.SysError("failed to fetch user email: " + err.Error()) } prompt := "您的额度即将用尽" if noMoreQuota { @@ -177,7 +177,7 @@ func PreConsumeTokenQuota(tokenId int, quota int) (err error) { err = common.SendEmail(prompt, email, fmt.Sprintf("%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。
充值链接:%s", prompt, userQuota, topUpLink, topUpLink)) if err != nil { - common.SysError("发送邮件失败:" + err.Error()) + common.SysError("failed to send email" + err.Error()) } } }() diff --git a/model/user.go b/model/user.go index 922b410f..726991e5 100644 --- a/model/user.go +++ b/model/user.go @@ -220,7 +220,7 @@ func IsAdmin(userId int) bool { var user User err := DB.Where("id = ?", userId).Select("role").Find(&user).Error if err != nil { - common.SysError("No such user " + err.Error()) + common.SysError("no such user " + err.Error()) return false } return user.Role >= common.RoleAdminUser @@ -233,7 +233,7 @@ func IsUserEnabled(userId int) bool { var user User err := DB.Where("id = ?", userId).Select("status").Find(&user).Error if err != nil { - common.SysError("No such user " + err.Error()) + common.SysError("no such user " + err.Error()) return false } return user.Status == common.UserStatusEnabled @@ -300,6 +300,6 @@ func UpdateUserUsedQuotaAndRequestCount(id int, quota int) { }, ).Error if err != nil { - common.SysError("Failed to update user used quota and request count: " + err.Error()) + common.SysError("failed to update user used quota and request count: " + err.Error()) } } diff --git a/one-api.service b/one-api.service index 174afe36..17e236bc 100644 --- a/one-api.service +++ b/one-api.service @@ -1,11 +1,16 @@ +# File path: /etc/systemd/system/one-api.service +# sudo systemctl daemon-reload +# sudo systemctl start one-api +# sudo systemctl enable one-api +# sudo systemctl status one-api [Unit] Description=One API Service After=network.target [Service] -User=yourusername # 守护进程用户名 -WorkingDirectory=/path/to/One-API # One API运行路径 -ExecStart=/path/to/One-API/one-api --port 3000 --log-dir /path/to/One-API/logs # 端口 +User=ubuntu # 注意修改用户名 +WorkingDirectory=/path/to/one-api # 注意修改路径 +ExecStart=/path/to/one-api/one-api --port 3000 --log-dir /path/to/one-api/logs # 注意修改路径和端口号 Restart=always RestartSec=5