Commit 0b745373 by luxl Committed by GitHub

Merge pull request #8 from Calcium-Ion/main

11
parents 4577235c 580dfd94
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
<img src="https://github.com/Calcium-Ion/new-api/assets/61247483/de536a8a-0161-47a7-a0a2-66ef6de81266" width="500"> <img src="https://github.com/Calcium-Ion/new-api/assets/61247483/de536a8a-0161-47a7-a0a2-66ef6de81266" width="500">
## 界面截图 ## 界面截图
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/d1ac216e-0804-4105-9fdc-66b35022d861)
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/3ca0b282-00ff-4c96-bf9d-e29ef615c605) ![image](https://github.com/Calcium-Ion/new-api/assets/61247483/3ca0b282-00ff-4c96-bf9d-e29ef615c605)
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/f4f40ed4-8ccb-43d7-a580-90677827646d) ![image](https://github.com/Calcium-Ion/new-api/assets/61247483/f4f40ed4-8ccb-43d7-a580-90677827646d)
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/90d7d763-6a77-4b36-9f76-2bb30f18583d) ![image](https://github.com/Calcium-Ion/new-api/assets/61247483/90d7d763-6a77-4b36-9f76-2bb30f18583d)
......
package common
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"github.com/chai2010/webp"
"image"
"io"
"net/http"
"strings"
)
func DecodeBase64ImageData(base64String string) (image.Config, error) {
// 去除base64数据的URL前缀(如果有)
if idx := strings.Index(base64String, ","); idx != -1 {
base64String = base64String[idx+1:]
}
// 将base64字符串解码为字节切片
decodedData, err := base64.StdEncoding.DecodeString(base64String)
if err != nil {
fmt.Println("Error: Failed to decode base64 string")
return image.Config{}, err
}
// 创建一个bytes.Buffer用于存储解码后的数据
reader := bytes.NewReader(decodedData)
config, err := getImageConfig(reader)
return config, err
}
func DecodeUrlImageData(imageUrl string) (image.Config, error) {
response, err := http.Get(imageUrl)
if err != nil {
SysLog(fmt.Sprintf("fail to get image from url: %s", err.Error()))
return image.Config{}, err
}
// 限制读取的字节数,防止下载整个图片
limitReader := io.LimitReader(response.Body, 8192)
config, err := getImageConfig(limitReader)
response.Body.Close()
return config, err
}
func getImageConfig(reader io.Reader) (image.Config, error) {
// 读取图片的头部信息来获取图片尺寸
config, _, err := image.DecodeConfig(reader)
if err != nil {
err = errors.New(fmt.Sprintf("fail to decode image config(gif, jpg, png): %s", err.Error()))
SysLog(err.Error())
config, err = webp.DecodeConfig(reader)
if err != nil {
err = errors.New(fmt.Sprintf("fail to decode image config(webp): %s", err.Error()))
SysLog(err.Error())
}
}
if err != nil {
return image.Config{}, err
}
return config, nil
}
package common
import (
"fmt"
"github.com/shirou/gopsutil/cpu"
"os"
"runtime/pprof"
"time"
)
// Monitor 定时监控cpu使用率,超过阈值输出pprof文件
func Monitor() {
for {
percent, err := cpu.Percent(time.Second, false)
if err != nil {
panic(err)
}
if percent[0] > 80 {
fmt.Println("cpu usage too high")
// write pprof file
if _, err := os.Stat("./pprof"); os.IsNotExist(err) {
err := os.Mkdir("./pprof", os.ModePerm)
if err != nil {
SysLog("创建pprof文件夹失败 " + err.Error())
continue
}
}
f, err := os.Create("./pprof/" + fmt.Sprintf("cpu-%s.pprof", time.Now().Format("20060102150405")))
if err != nil {
SysLog("创建pprof文件失败 " + err.Error())
continue
}
err = pprof.StartCPUProfile(f)
if err != nil {
SysLog("启动pprof失败 " + err.Error())
continue
}
time.Sleep(10 * time.Second) // profile for 30 seconds
pprof.StopCPUProfile()
f.Close()
}
time.Sleep(30 * time.Second)
}
}
...@@ -19,12 +19,12 @@ import ( ...@@ -19,12 +19,12 @@ import (
func UpdateMidjourneyTask() { func UpdateMidjourneyTask() {
//revocer //revocer
imageModel := "midjourney" imageModel := "midjourney"
for {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Printf("UpdateMidjourneyTask panic: %v", err) log.Printf("UpdateMidjourneyTask panic: %v", err)
} }
}() }()
for {
time.Sleep(time.Duration(15) * time.Second) time.Sleep(time.Duration(15) * time.Second)
tasks := model.GetAllUnFinishTasks() tasks := model.GetAllUnFinishTasks()
if len(tasks) != 0 { if len(tasks) != 0 {
...@@ -55,7 +55,6 @@ func UpdateMidjourneyTask() { ...@@ -55,7 +55,6 @@ func UpdateMidjourneyTask() {
// 设置超时时间 // 设置超时时间
timeout := time.Second * 5 timeout := time.Second * 5
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// 使用带有超时的 context 创建新的请求 // 使用带有超时的 context 创建新的请求
req = req.WithContext(ctx) req = req.WithContext(ctx)
...@@ -68,8 +67,8 @@ func UpdateMidjourneyTask() { ...@@ -68,8 +67,8 @@ func UpdateMidjourneyTask() {
log.Printf("UpdateMidjourneyTask error: %v", err) log.Printf("UpdateMidjourneyTask error: %v", err)
continue continue
} }
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body) responseBody, err := io.ReadAll(resp.Body)
resp.Body.Close()
log.Printf("responseBody: %s", string(responseBody)) log.Printf("responseBody: %s", string(responseBody))
var responseItem Midjourney var responseItem Midjourney
// err = json.NewDecoder(resp.Body).Decode(&responseItem) // err = json.NewDecoder(resp.Body).Decode(&responseItem)
...@@ -83,12 +82,12 @@ func UpdateMidjourneyTask() { ...@@ -83,12 +82,12 @@ func UpdateMidjourneyTask() {
if err1 == nil && err2 == nil { if err1 == nil && err2 == nil {
jsonData, err3 := json.Marshal(responseWithoutStatus) jsonData, err3 := json.Marshal(responseWithoutStatus)
if err3 != nil { if err3 != nil {
log.Fatalf("UpdateMidjourneyTask error1: %v", err3) log.Printf("UpdateMidjourneyTask error1: %v", err3)
continue continue
} }
err4 := json.Unmarshal(jsonData, &responseStatus) err4 := json.Unmarshal(jsonData, &responseStatus)
if err4 != nil { if err4 != nil {
log.Fatalf("UpdateMidjourneyTask error2: %v", err4) log.Printf("UpdateMidjourneyTask error2: %v", err4)
continue continue
} }
responseItem.Status = strconv.Itoa(responseStatus.Status) responseItem.Status = strconv.Itoa(responseStatus.Status)
...@@ -138,6 +137,7 @@ func UpdateMidjourneyTask() { ...@@ -138,6 +137,7 @@ func UpdateMidjourneyTask() {
log.Printf("UpdateMidjourneyTask error5: %v", err) log.Printf("UpdateMidjourneyTask error5: %v", err)
} }
log.Printf("UpdateMidjourneyTask success: %v", task) log.Printf("UpdateMidjourneyTask success: %v", task)
cancel()
} }
} }
} }
......
...@@ -147,7 +147,7 @@ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode ...@@ -147,7 +147,7 @@ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode
var textResponse ImageResponse var textResponse ImageResponse
defer func(ctx context.Context) { defer func(ctx context.Context) {
if consumeQuota { if consumeQuota {
err := model.PostConsumeTokenQuota(tokenId, userId, quota, 0, true) err := model.PostConsumeTokenQuota(tokenId, userQuota, quota, 0, true)
if err != nil { if err != nil {
common.SysError("error consuming token remain quota: " + err.Error()) common.SysError("error consuming token remain quota: " + err.Error())
} }
......
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/chai2010/webp"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkoukk/tiktoken-go" "github.com/pkoukk/tiktoken-go"
"image" "image"
...@@ -75,29 +74,21 @@ func getImageToken(imageUrl MessageImageUrl) (int, error) { ...@@ -75,29 +74,21 @@ func getImageToken(imageUrl MessageImageUrl) (int, error) {
if imageUrl.Detail == "low" { if imageUrl.Detail == "low" {
return 85, nil return 85, nil
} }
var config image.Config
response, err := http.Get(imageUrl.Url) var err error
if strings.HasPrefix(imageUrl.Url, "http") {
common.SysLog(fmt.Sprintf("downloading image: %s", imageUrl.Url))
config, err = common.DecodeUrlImageData(imageUrl.Url)
} else {
common.SysLog(fmt.Sprintf("decoding image"))
config, err = common.DecodeBase64ImageData(imageUrl.Url)
}
if err != nil { if err != nil {
fmt.Println("Error: Failed to get the URL")
return 0, err return 0, err
} }
// 限制读取的字节数,防止下载整个图片
limitReader := io.LimitReader(response.Body, 8192)
response.Body.Close()
// 读取图片的头部信息来获取图片尺寸
config, _, err := image.DecodeConfig(limitReader)
if err != nil {
common.SysLog(fmt.Sprintf("fail to decode image config(gif, jpg, png): %s", err.Error()))
config, err = webp.DecodeConfig(limitReader)
if err != nil {
common.SysLog(fmt.Sprintf("fail to decode image config(webp): %s", err.Error()))
}
}
if config.Width == 0 || config.Height == 0 { if config.Width == 0 || config.Height == 0 {
return 0, errors.New(fmt.Sprintf("fail to decode image config: %s", err.Error())) return 0, errors.New(fmt.Sprintf("fail to decode image config: %s", imageUrl.Url))
} }
if config.Width < 512 && config.Height < 512 { if config.Width < 512 && config.Height < 512 {
if imageUrl.Detail == "auto" || imageUrl.Detail == "" { if imageUrl.Detail == "auto" || imageUrl.Detail == "" {
......
...@@ -79,6 +79,7 @@ func setupLogin(user *model.User, c *gin.Context) { ...@@ -79,6 +79,7 @@ func setupLogin(user *model.User, c *gin.Context) {
DisplayName: user.DisplayName, DisplayName: user.DisplayName,
Role: user.Role, Role: user.Role,
Status: user.Status, Status: user.Status,
Group: user.Group,
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"message": "", "message": "",
...@@ -284,6 +285,42 @@ func GenerateAccessToken(c *gin.Context) { ...@@ -284,6 +285,42 @@ func GenerateAccessToken(c *gin.Context) {
return return
} }
type TransferAffQuotaRequest struct {
Quota int `json:"quota" binding:"required"`
}
func TransferAffQuota(c *gin.Context) {
id := c.GetInt("id")
user, err := model.GetUserById(id, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
tran := TransferAffQuotaRequest{}
if err := c.ShouldBindJSON(&tran); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
err = user.TransferAffQuotaToQuota(tran.Quota)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "划转失败 " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "划转成功",
})
}
func GetAffCode(c *gin.Context) { func GetAffCode(c *gin.Context) {
id := c.GetInt("id") id := c.GetInt("id")
user, err := model.GetUserById(id, true) user, err := model.GetUserById(id, true)
...@@ -330,6 +367,28 @@ func GetSelf(c *gin.Context) { ...@@ -330,6 +367,28 @@ func GetSelf(c *gin.Context) {
return return
} }
func GetUserModels(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
id = c.GetInt("id")
}
user, err := model.GetUserById(id, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
models := model.GetGroupModels(user.Group)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": models,
})
return
}
func UpdateUser(c *gin.Context) { func UpdateUser(c *gin.Context) {
var updatedUser model.User var updatedUser model.User
err := json.NewDecoder(c.Request.Body).Decode(&updatedUser) err := json.NewDecoder(c.Request.Body).Decode(&updatedUser)
......
...@@ -17,6 +17,7 @@ require ( ...@@ -17,6 +17,7 @@ require (
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/pkoukk/tiktoken-go v0.1.1 github.com/pkoukk/tiktoken-go v0.1.1
github.com/samber/lo v1.38.1 github.com/samber/lo v1.38.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/star-horizon/go-epay v0.0.0-20230204124159-fa2e2293fdc2 github.com/star-horizon/go-epay v0.0.0-20230204124159-fa2e2293fdc2
golang.org/x/crypto v0.14.0 golang.org/x/crypto v0.14.0
gorm.io/driver/mysql v1.4.3 gorm.io/driver/mysql v1.4.3
...@@ -33,6 +34,7 @@ require ( ...@@ -33,6 +34,7 @@ require (
github.com/dlclark/regexp2 v1.8.1 // indirect github.com/dlclark/regexp2 v1.8.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/go-sql-driver/mysql v1.6.0 // indirect
...@@ -54,8 +56,11 @@ require ( ...@@ -54,8 +56,11 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/net v0.17.0 // indirect
......
...@@ -33,6 +33,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv ...@@ -33,6 +33,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
...@@ -131,6 +133,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA ...@@ -131,6 +133,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/star-horizon/go-epay v0.0.0-20230204124159-fa2e2293fdc2 h1:avbt5a8F/zbYwFzTugrqWOBJe/K1cJj6+xpr+x1oVAI= github.com/star-horizon/go-epay v0.0.0-20230204124159-fa2e2293fdc2 h1:avbt5a8F/zbYwFzTugrqWOBJe/K1cJj6+xpr+x1oVAI=
github.com/star-horizon/go-epay v0.0.0-20230204124159-fa2e2293fdc2/go.mod h1:SiffGCWGGMVwujne2dUQbJ5zUVD1V1Yj0hDuTfqFNEo= github.com/star-horizon/go-epay v0.0.0-20230204124159-fa2e2293fdc2/go.mod h1:SiffGCWGGMVwujne2dUQbJ5zUVD1V1Yj0hDuTfqFNEo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
...@@ -146,6 +150,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o ...@@ -146,6 +150,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
...@@ -154,6 +162,8 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY ...@@ -154,6 +162,8 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
...@@ -165,6 +175,7 @@ golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9 ...@@ -165,6 +175,7 @@ golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
...@@ -172,6 +183,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc ...@@ -172,6 +183,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
......
...@@ -83,6 +83,12 @@ func main() { ...@@ -83,6 +83,12 @@ func main() {
common.SysLog("batch update enabled with interval " + strconv.Itoa(common.BatchUpdateInterval) + "s") common.SysLog("batch update enabled with interval " + strconv.Itoa(common.BatchUpdateInterval) + "s")
model.InitBatchUpdater() model.InitBatchUpdater()
} }
if os.Getenv("ENABLE_PPROF") == "true" {
go common.Monitor()
common.SysLog("pprof enabled")
}
controller.InitTokenEncoders() controller.InitTokenEncoders()
// Initialize HTTP server // Initialize HTTP server
......
...@@ -13,6 +13,17 @@ type Ability struct { ...@@ -13,6 +13,17 @@ type Ability struct {
Priority *int64 `json:"priority" gorm:"bigint;default:0;index"` Priority *int64 `json:"priority" gorm:"bigint;default:0;index"`
} }
func GetGroupModels(group string) []string {
var abilities []Ability
//去重
DB.Where("`group` = ?", group).Distinct("model").Find(&abilities)
models := make([]string, 0, len(abilities))
for _, ability := range abilities {
models = append(models, ability.Model)
}
return models
}
func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) { func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) {
ability := Ability{} ability := Ability{}
groupCol := "`group`" groupCol := "`group`"
......
...@@ -220,6 +220,7 @@ func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuo ...@@ -220,6 +220,7 @@ func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuo
} }
if sendEmail { if sendEmail {
if (quota + preConsumedQuota) != 0 {
quotaTooLow := userQuota >= common.QuotaRemindThreshold && userQuota-(quota+preConsumedQuota) < common.QuotaRemindThreshold quotaTooLow := userQuota >= common.QuotaRemindThreshold && userQuota-(quota+preConsumedQuota) < common.QuotaRemindThreshold
noMoreQuota := userQuota-(quota+preConsumedQuota) <= 0 noMoreQuota := userQuota-(quota+preConsumedQuota) <= 0
if quotaTooLow || noMoreQuota { if quotaTooLow || noMoreQuota {
...@@ -244,6 +245,7 @@ func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuo ...@@ -244,6 +245,7 @@ func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuo
}() }()
} }
} }
}
if !token.UnlimitedQuota { if !token.UnlimitedQuota {
if quota > 0 { if quota > 0 {
......
...@@ -27,6 +27,9 @@ type User struct { ...@@ -27,6 +27,9 @@ type User struct {
RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number
Group string `json:"group" gorm:"type:varchar(32);default:'default'"` Group string `json:"group" gorm:"type:varchar(32);default:'default'"`
AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"` AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"`
AffCount int `json:"aff_count" gorm:"type:int;default:0;column:aff_count"`
AffQuota int `json:"aff_quota" gorm:"type:int;default:0;column:aff_quota"` // 邀请剩余额度
AffHistoryQuota int `json:"aff_history_quota" gorm:"type:int;default:0;column:aff_history"` // 邀请历史额度
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"` InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
} }
...@@ -77,6 +80,54 @@ func DeleteUserById(id int) (err error) { ...@@ -77,6 +80,54 @@ func DeleteUserById(id int) (err error) {
return user.Delete() return user.Delete()
} }
func inviteUser(inviterId int) (err error) {
user, err := GetUserById(inviterId, true)
if err != nil {
return err
}
user.AffCount++
user.AffQuota += common.QuotaForInviter
user.AffHistoryQuota += common.QuotaForInviter
return DB.Save(user).Error
}
func (user *User) TransferAffQuotaToQuota(quota int) error {
// 检查quota是否小于最小额度
if float64(quota) < common.QuotaPerUnit {
return fmt.Errorf("转移额度最小为%s!", common.LogQuota(int(common.QuotaPerUnit)))
}
// 开始数据库事务
tx := DB.Begin()
if tx.Error != nil {
return tx.Error
}
defer tx.Rollback() // 确保在函数退出时事务能回滚
// 加锁查询用户以确保数据一致性
err := tx.Set("gorm:query_option", "FOR UPDATE").First(&user, user.Id).Error
if err != nil {
return err
}
// 再次检查用户的AffQuota是否足够
if user.AffQuota < quota {
return errors.New("邀请额度不足!")
}
// 更新用户额度
user.AffQuota -= quota
user.Quota += quota
// 保存用户状态
if err := tx.Save(user).Error; err != nil {
return err
}
// 提交事务
return tx.Commit().Error
}
func (user *User) Insert(inviterId int) error { func (user *User) Insert(inviterId int) error {
var err error var err error
if user.Password != "" { if user.Password != "" {
...@@ -101,8 +152,9 @@ func (user *User) Insert(inviterId int) error { ...@@ -101,8 +152,9 @@ func (user *User) Insert(inviterId int) error {
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", common.LogQuota(common.QuotaForInvitee))) RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", common.LogQuota(common.QuotaForInvitee)))
} }
if common.QuotaForInviter > 0 { if common.QuotaForInviter > 0 {
_ = IncreaseUserQuota(inviterId, common.QuotaForInviter) //_ = IncreaseUserQuota(inviterId, common.QuotaForInviter)
RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("邀请用户赠送 %s", common.LogQuota(common.QuotaForInviter))) RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("邀请用户赠送 %s", common.LogQuota(common.QuotaForInviter)))
_ = inviteUser(inviterId)
} }
} }
return nil return nil
......
...@@ -39,6 +39,7 @@ func SetApiRouter(router *gin.Engine) { ...@@ -39,6 +39,7 @@ func SetApiRouter(router *gin.Engine) {
selfRoute.Use(middleware.UserAuth()) selfRoute.Use(middleware.UserAuth())
{ {
selfRoute.GET("/self", controller.GetSelf) selfRoute.GET("/self", controller.GetSelf)
selfRoute.GET("/models", controller.GetUserModels)
selfRoute.PUT("/self", controller.UpdateSelf) selfRoute.PUT("/self", controller.UpdateSelf)
selfRoute.DELETE("/self", controller.DeleteSelf) selfRoute.DELETE("/self", controller.DeleteSelf)
selfRoute.GET("/token", controller.GenerateAccessToken) selfRoute.GET("/token", controller.GenerateAccessToken)
...@@ -46,6 +47,7 @@ func SetApiRouter(router *gin.Engine) { ...@@ -46,6 +47,7 @@ func SetApiRouter(router *gin.Engine) {
selfRoute.POST("/topup", controller.TopUp) selfRoute.POST("/topup", controller.TopUp)
selfRoute.POST("/pay", controller.RequestEpay) selfRoute.POST("/pay", controller.RequestEpay)
selfRoute.POST("/amount", controller.RequestAmount) selfRoute.POST("/amount", controller.RequestAmount)
selfRoute.POST("/aff_transfer", controller.TransferAffQuota)
} }
adminRoute := userRoute.Group("/") adminRoute := userRoute.Group("/")
......
...@@ -106,7 +106,7 @@ const LogsTable = () => { ...@@ -106,7 +106,7 @@ const LogsTable = () => {
return ( return (
record.type === 0 || record.type === 2 ? record.type === 0 || record.type === 2 ?
<div> <div>
<Tag color='grey' size='large' onClick={()=>{ <Tag color='grey' size='large' onClick={() => {
copyText(text) copyText(text)
}}> {text} </Tag> }}> {text} </Tag>
</div> </div>
...@@ -133,7 +133,7 @@ const LogsTable = () => { ...@@ -133,7 +133,7 @@ const LogsTable = () => {
return ( return (
record.type === 0 || record.type === 2 ? record.type === 0 || record.type === 2 ?
<div> <div>
<Tag color={stringToColor(text)} size='large' onClick={()=>{ <Tag color={stringToColor(text)} size='large' onClick={() => {
copyText(text) copyText(text)
}}> {text} </Tag> }}> {text} </Tag>
</div> </div>
...@@ -202,11 +202,12 @@ const LogsTable = () => { ...@@ -202,11 +202,12 @@ const LogsTable = () => {
const [logType, setLogType] = useState(0); const [logType, setLogType] = useState(0);
const isAdminUser = isAdmin(); const isAdminUser = isAdmin();
let now = new Date(); let now = new Date();
// 初始化start_timestamp为前一天
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
username: '', username: '',
token_name: '', token_name: '',
model_name: '', model_name: '',
start_timestamp: timestamp2string(0), start_timestamp: timestamp2string(now.getTime() / 1000 - 86400),
end_timestamp: timestamp2string(now.getTime() / 1000 + 3600), end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
channel: '' channel: ''
}); });
...@@ -338,7 +339,7 @@ const LogsTable = () => { ...@@ -338,7 +339,7 @@ const LogsTable = () => {
showSuccess('已复制:' + text); showSuccess('已复制:' + text);
} else { } else {
// setSearchKeyword(text); // setSearchKeyword(text);
Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); Modal.error({title: '无法复制到剪贴板,请手动复制', content: text});
} }
} }
...@@ -412,10 +413,12 @@ const LogsTable = () => { ...@@ -412,10 +413,12 @@ const LogsTable = () => {
name='model_name' name='model_name'
onChange={value => handleInputChange(value, 'model_name')}/> onChange={value => handleInputChange(value, 'model_name')}/>
<Form.DatePicker field="start_timestamp" label='起始时间' style={{width: 272}} <Form.DatePicker field="start_timestamp" label='起始时间' style={{width: 272}}
initValue={start_timestamp}
value={start_timestamp} type='dateTime' value={start_timestamp} type='dateTime'
name='start_timestamp' name='start_timestamp'
onChange={value => handleInputChange(value, 'start_timestamp')}/> onChange={value => handleInputChange(value, 'start_timestamp')}/>
<Form.DatePicker field="end_timestamp" fluid label='结束时间' style={{width: 272}} <Form.DatePicker field="end_timestamp" fluid label='结束时间' style={{width: 272}}
initValue={end_timestamp}
value={end_timestamp} type='dateTime' value={end_timestamp} type='dateTime'
name='end_timestamp' name='end_timestamp'
onChange={value => handleInputChange(value, 'end_timestamp')}/> onChange={value => handleInputChange(value, 'end_timestamp')}/>
......
...@@ -37,6 +37,12 @@ export function renderNumber(num) { ...@@ -37,6 +37,12 @@ export function renderNumber(num) {
} }
} }
export function getQuotaPerUnit() {
let quotaPerUnit = localStorage.getItem('quota_per_unit');
quotaPerUnit = parseFloat(quotaPerUnit);
return quotaPerUnit;
}
export function renderQuota(quota, digits = 2) { export function renderQuota(quota, digits = 2) {
let quotaPerUnit = localStorage.getItem('quota_per_unit'); let quotaPerUnit = localStorage.getItem('quota_per_unit');
let displayInCurrency = localStorage.getItem('display_in_currency'); let displayInCurrency = localStorage.getItem('display_in_currency');
......
import React from 'react'; import React from 'react';
import { Segment, Tab } from 'semantic-ui-react';
import SystemSetting from '../../components/SystemSetting'; import SystemSetting from '../../components/SystemSetting';
import { isRoot } from '../../helpers'; import {isRoot} from '../../helpers';
import OtherSetting from '../../components/OtherSetting'; import OtherSetting from '../../components/OtherSetting';
import PersonalSetting from '../../components/PersonalSetting'; import PersonalSetting from '../../components/PersonalSetting';
import OperationSetting from '../../components/OperationSetting'; import OperationSetting from '../../components/OperationSetting';
import {Layout, TabPane, Tabs} from "@douyinfe/semi-ui";
const Setting = () => { const Setting = () => {
let panes = [ let panes = [
{ {
menuItem: '个人设置', tab: '个人设置',
render: () => ( content: <PersonalSetting/>,
<Tab.Pane attached={false}> itemKey: '1'
<PersonalSetting />
</Tab.Pane>
)
} }
]; ];
if (isRoot()) { if (isRoot()) {
panes.push({ panes.push({
menuItem: '运营设置', tab: '运营设置',
render: () => ( content: <OperationSetting/>,
<Tab.Pane attached={false}> itemKey: '2'
<OperationSetting />
</Tab.Pane>
)
}); });
panes.push({ panes.push({
menuItem: '系统设置', tab: '系统设置',
render: () => ( content: <SystemSetting/>,
<Tab.Pane attached={false}> itemKey: '3'
<SystemSetting />
</Tab.Pane>
)
}); });
panes.push({ panes.push({
menuItem: '其他设置', tab: '其他设置',
render: () => ( content: <OtherSetting/>,
<Tab.Pane attached={false}> itemKey: '4'
<OtherSetting />
</Tab.Pane>
)
}); });
} }
return ( return (
<Segment> <div>
<Tab menu={{ secondary: true, pointing: true }} panes={panes} /> <Layout>
</Segment> <Layout.Content>
<Tabs type="line" defaultActiveKey="1">
{panes.map(pane => (
<TabPane itemKey={pane.itemKey} tab={pane.tab}>
{pane.content}
</TabPane>
))}
</Tabs>
</Layout.Content>
</Layout>
</div>
); );
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment