diff --git a/README.md b/README.md index 8f43a74eb..341733e90 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ +
+ > [!NOTE] > 本项目为开源项目,在[One API](https://github.com/songquanpeng/one-api)的基础上进行二次开发 @@ -115,24 +120,18 @@ docker run --name new-api -d --restart always -p 3000:3000 -e SQL_DSN="root:1234 ## Suno接口设置文档 [对接文档](Suno.md) -## 交流群 - - ## 界面截图 ![image](https://github.com/Calcium-Ion/new-api/assets/61247483/ad0e7aae-0203-471c-9716-2d83768927d4) -![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/f4f40ed4-8ccb-43d7-a580-90677827646d) +![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/90d7d763-6a77-4b36-9f76-2bb30f18583d) -![image](https://github.com/Calcium-Ion/new-api/assets/61247483/e414228a-3c35-429a-b298-6451d76d9032) 夜间模式 ![image](https://github.com/Calcium-Ion/new-api/assets/61247483/1c66b593-bb9e-4757-9720-ff2759539242) - -![image](https://github.com/Calcium-Ion/new-api/assets/61247483/5b3228e8-2556-44f7-97d6-4f8d8ee6effa) ![image](https://github.com/Calcium-Ion/new-api/assets/61247483/af9a07ee-5101-4b3d-8bd9-ae21a4fd7e9e) +## 交流群 + + ## 相关项目 - [One API](https://github.com/songquanpeng/one-api):原版项目 - [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy):Midjourney接口支持 diff --git a/common/utils.go b/common/utils.go index 3d95508cb..3d0cb6a00 100644 --- a/common/utils.go +++ b/common/utils.go @@ -128,6 +128,11 @@ func IntMax(a int, b int) int { } } +func IsIP(s string) bool { + ip := net.ParseIP(s) + return ip != nil +} + func GetUUID() string { code := uuid.New().String() code = strings.Replace(code, "-", "", -1) diff --git a/controller/model.go b/controller/model.go index 6b4a878b9..36beb2d18 100644 --- a/controller/model.go +++ b/controller/model.go @@ -146,22 +146,49 @@ func ListModels(c *gin.Context) { }) return } - models := model.GetGroupModels(user.Group) userOpenAiModels := make([]dto.OpenAIModels, 0) permission := getPermission() - for _, s := range models { - if _, ok := openAIModelsMap[s]; ok { - userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s]) + + modelLimitEnable := c.GetBool("token_model_limit_enabled") + if modelLimitEnable { + s, ok := c.Get("token_model_limit") + var tokenModelLimit map[string]bool + if ok { + tokenModelLimit = s.(map[string]bool) } else { - userOpenAiModels = append(userOpenAiModels, dto.OpenAIModels{ - Id: s, - Object: "model", - Created: 1626777600, - OwnedBy: "custom", - Permission: permission, - Root: s, - Parent: nil, - }) + tokenModelLimit = map[string]bool{} + } + for allowModel, _ := range tokenModelLimit { + if _, ok := openAIModelsMap[allowModel]; ok { + userOpenAiModels = append(userOpenAiModels, openAIModelsMap[allowModel]) + } else { + userOpenAiModels = append(userOpenAiModels, dto.OpenAIModels{ + Id: allowModel, + Object: "model", + Created: 1626777600, + OwnedBy: "custom", + Permission: permission, + Root: allowModel, + Parent: nil, + }) + } + } + } else { + models := model.GetGroupModels(user.Group) + for _, s := range models { + if _, ok := openAIModelsMap[s]; ok { + userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s]) + } else { + userOpenAiModels = append(userOpenAiModels, dto.OpenAIModels{ + Id: s, + Object: "model", + Created: 1626777600, + OwnedBy: "custom", + Permission: permission, + Root: s, + Parent: nil, + }) + } } } c.JSON(200, gin.H{ diff --git a/controller/token.go b/controller/token.go index 39e602463..50a368f6f 100644 --- a/controller/token.go +++ b/controller/token.go @@ -134,6 +134,7 @@ func AddToken(c *gin.Context) { UnlimitedQuota: token.UnlimitedQuota, ModelLimitsEnabled: token.ModelLimitsEnabled, ModelLimits: token.ModelLimits, + AllowIps: token.AllowIps, } err = cleanToken.Insert() if err != nil { @@ -221,6 +222,7 @@ func UpdateToken(c *gin.Context) { cleanToken.UnlimitedQuota = token.UnlimitedQuota cleanToken.ModelLimitsEnabled = token.ModelLimitsEnabled cleanToken.ModelLimits = token.ModelLimits + cleanToken.AllowIps = token.AllowIps } err = cleanToken.Update() if err != nil { diff --git a/middleware/auth.go b/middleware/auth.go index f9a590017..481960efa 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -175,6 +175,7 @@ func TokenAuth() func(c *gin.Context) { } else { c.Set("token_model_limit_enabled", false) } + c.Set("allow_ips", token.GetIpLimitsMap()) if len(parts) > 1 { if model.IsAdmin(token.UserId) { c.Set("specific_channel_id", parts[1]) diff --git a/middleware/distributor.go b/middleware/distributor.go index 3ca5b8f7f..9b55cc2d2 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -22,6 +22,14 @@ type ModelRequest struct { func Distribute() func(c *gin.Context) { return func(c *gin.Context) { + allowIpsMap := c.GetStringMap("allow_ips") + if len(allowIpsMap) != 0 { + clientIp := c.ClientIP() + if _, ok := allowIpsMap[clientIp]; !ok { + abortWithOpenAiMessage(c, http.StatusForbidden, "您的 IP 不在令牌允许访问的列表中") + return + } + } userId := c.GetInt("id") var channel *model.Channel channelId, ok := c.Get("specific_channel_id") diff --git a/model/token.go b/model/token.go index 272c5734f..18aa2979e 100644 --- a/model/token.go +++ b/model/token.go @@ -23,10 +23,33 @@ type Token struct { UnlimitedQuota bool `json:"unlimited_quota" gorm:"default:false"` ModelLimitsEnabled bool `json:"model_limits_enabled" gorm:"default:false"` ModelLimits string `json:"model_limits" gorm:"type:varchar(1024);default:''"` + AllowIps *string `json:"allow_ips" gorm:"default:''"` UsedQuota int `json:"used_quota" gorm:"default:0"` // used quota DeletedAt gorm.DeletedAt `gorm:"index"` } +func (token *Token) GetIpLimitsMap() map[string]any { + // delete empty spaces + //split with \n + ipLimitsMap := make(map[string]any) + if token.AllowIps == nil { + return ipLimitsMap + } + cleanIps := strings.ReplaceAll(*token.AllowIps, " ", "") + if cleanIps == "" { + return ipLimitsMap + } + ips := strings.Split(cleanIps, "\n") + for _, ip := range ips { + ip = strings.TrimSpace(ip) + ip = strings.ReplaceAll(ip, ",", "") + if common.IsIP(ip) { + ipLimitsMap[ip] = true + } + } + return ipLimitsMap +} + func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) { var tokens []*Token var err error @@ -130,7 +153,7 @@ func (token *Token) Insert() error { // Update Make sure your token's fields is completed, because this will update non-zero values func (token *Token) Update() error { var err error - err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota", "model_limits_enabled", "model_limits").Updates(token).Error + err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota", "model_limits_enabled", "model_limits", "allow_ips").Updates(token).Error return err } diff --git a/web/src/App.js b/web/src/App.js index 18cfdd05f..c56dd095c 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -20,12 +20,11 @@ import Redemption from './pages/Redemption'; import TopUp from './pages/TopUp'; import Log from './pages/Log'; import Chat from './pages/Chat'; -import Chat2Link from './pages/Chat2Link'; +import Chat2Link from './pages/Chat2Link'; import { Layout } from '@douyinfe/semi-ui'; import Midjourney from './pages/Midjourney'; import Pricing from './pages/Pricing/index.js'; import Task from "./pages/Task/index.js"; -// import Detail from './pages/Detail'; const Home = lazy(() => import('./pages/Home')); const Detail = lazy(() => import('./pages/Detail')); @@ -59,204 +58,203 @@ function App() { }, []); return ( -