Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions common/topup-ratio.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"encoding/json"
"sync"
)

var TopupGroupRatio = map[string]float64{
Expand All @@ -10,7 +11,21 @@ var TopupGroupRatio = map[string]float64{
"svip": 1,
}

var topupGroupRatioMutex sync.RWMutex

func GetTopupGroupRatioCopy() map[string]float64 {
topupGroupRatioMutex.RLock()
defer topupGroupRatioMutex.RUnlock()
cp := make(map[string]float64, len(TopupGroupRatio))
for k, v := range TopupGroupRatio {
cp[k] = v
}
return cp
}
Comment thread
x-Ai marked this conversation as resolved.

func TopupGroupRatio2JSONString() string {
topupGroupRatioMutex.RLock()
defer topupGroupRatioMutex.RUnlock()
jsonBytes, err := json.Marshal(TopupGroupRatio)
if err != nil {
SysError("error marshalling model ratio: " + err.Error())
Expand All @@ -19,12 +34,20 @@ func TopupGroupRatio2JSONString() string {
}

func UpdateTopupGroupRatioByJSONString(jsonStr string) error {
TopupGroupRatio = make(map[string]float64)
return json.Unmarshal([]byte(jsonStr), &TopupGroupRatio)
var tmp map[string]float64
if err := json.Unmarshal([]byte(jsonStr), &tmp); err != nil {
return err
}
topupGroupRatioMutex.Lock()
TopupGroupRatio = tmp
topupGroupRatioMutex.Unlock()
return nil
}

func GetTopupGroupRatio(name string) float64 {
topupGroupRatioMutex.RLock()
ratio, ok := TopupGroupRatio[name]
topupGroupRatioMutex.RUnlock()
if !ok {
SysError("topup group ratio not found: " + name)
return 1
Expand Down
316 changes: 313 additions & 3 deletions controller/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func GetStatus(c *gin.Context) {
common.OptionMapRWMutex.RLock()
defer common.OptionMapRWMutex.RUnlock()

// 获取用户角色信息(如果已登录)
var userRole int = -1
if role := c.GetInt("role"); role > 0 {
userRole = role
}

data := gin.H{
"version": common.Version,
"start_time": common.StartTime,
Expand All @@ -63,6 +69,7 @@ func GetStatus(c *gin.Context) {
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"docs_link": operation_setting.GetGeneralSetting().DocsLink,
"invitation_enabled": operation_setting.GetGeneralSetting().InvitationEnabled,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,
Expand All @@ -87,9 +94,8 @@ func GetStatus(c *gin.Context) {
"announcements_enabled": cs.AnnouncementsEnabled,
"faq_enabled": cs.FAQEnabled,

// 模块管理配置
"HeaderNavModules": common.OptionMap["HeaderNavModules"],
"SidebarModulesAdmin": common.OptionMap["SidebarModulesAdmin"],
// 模块管理配置 - 根据用户权限过滤
"header_nav_modules": filterHeaderNavModulesForUser(common.OptionMap["HeaderNavModules"], userRole),

"oidc_enabled": system_setting.GetOIDCSettings().Enabled,
"oidc_client_id": system_setting.GetOIDCSettings().ClientId,
Expand Down Expand Up @@ -303,3 +309,307 @@ func ResetPassword(c *gin.Context) {
})
return
}

// filterHeaderNavModulesForUser 根据用户权限过滤顶栏模块配置
func filterHeaderNavModulesForUser(headerNavModulesRaw interface{}, userRole int) interface{} {
// 如果配置为空,返回原配置
if headerNavModulesRaw == nil {
return headerNavModulesRaw
}

headerNavModulesStr, ok := headerNavModulesRaw.(string)
if !ok || headerNavModulesStr == "" {
return headerNavModulesRaw
}

// 解析配置
var config map[string]interface{}
if err := json.Unmarshal([]byte(headerNavModulesStr), &config); err != nil {
// 解析失败时返回空配置,采用安全优先策略
return "{}"
}

// 对于所有用户(包括未登录用户),都需要移除被禁用的模块
filteredConfig := make(map[string]interface{})
for key, value := range config {
// 首先检查模块是否启用
if isHeaderNavModuleEnabled(key, value) {
// 未登录用户:仅移除被禁用的模块,保留启用模块(包括 pricing,以便前端根据 requireAuth 决定跳转到 /login)
if userRole == -1 {
filteredConfig[key] = value
continue
}

// 超级管理员可以看到所有启用的模块
if userRole >= common.RoleRootUser {
filteredConfig[key] = value
} else {
// 管理员和普通用户:进行权限检查
if hasHeaderNavModulePermission(key, value, userRole) {
filteredConfig[key] = value
}
}
}
// 被禁用的模块(isHeaderNavModuleEnabled返回false)不会被添加到filteredConfig中
}

// 转换回JSON字符串
filteredBytes, err := json.Marshal(filteredConfig)
if err != nil {
return "{}"
}

return string(filteredBytes)
}

// FilterSidebarModulesAdminForUser 根据用户权限过滤侧边栏管理配置
func FilterSidebarModulesAdminForUser(sidebarModulesRaw interface{}, userRole int) interface{} {
// 如果用户未登录,返回空配置以保护敏感信息
if userRole == -1 {
return "{}"
}

if sidebarModulesRaw == nil {
return sidebarModulesRaw
}

sidebarModulesStr, ok := sidebarModulesRaw.(string)
if !ok || sidebarModulesStr == "" {
return sidebarModulesRaw
}

// 解析配置
var config map[string]interface{}
if err := json.Unmarshal([]byte(sidebarModulesStr), &config); err != nil {
// 解析失败时返回空配置,采用安全优先策略
return "{}"
}



// 对于所有用户,移除被禁用的模块
filteredConfig := make(map[string]interface{})
for sectionKey, sectionValue := range config {
if sectionObj, ok := sectionValue.(map[string]interface{}); ok {
// 检查区域是否启用
sectionEnabledByConfig := true
if enabled, hasEnabled := sectionObj["enabled"]; hasEnabled {
if enabledBool, ok := enabled.(bool); ok && !enabledBool {
sectionEnabledByConfig = false
}
}

// 如果区域被配置为禁用,跳过整个区域(但console区域需要特殊处理,因为数据看板始终可访问)
if !sectionEnabledByConfig && sectionKey != "console" {
continue
}

filteredSection := make(map[string]interface{})
hasValidModules := false // 标记区域是否有有效的模块

// 复制区域配置
for moduleKey, moduleValue := range sectionObj {
// 检查用户是否有权限访问此模块
modulePath := sectionKey + "." + moduleKey

// 数据看板始终允许访问,强制设置为启用
if modulePath == "console.detail" {
filteredSection[moduleKey] = true
hasValidModules = true
} else if moduleKey == "enabled" {
// 只有当区域启用时才复制enabled字段
if sectionEnabledByConfig {
filteredSection[moduleKey] = moduleValue
}
} else if sectionEnabledByConfig && hasModulePermissionForUser(userRole, modulePath, moduleValue) {
// 处理嵌套权限(如admin.user.groupManagement)
if moduleObj, ok := moduleValue.(map[string]interface{}); ok {
filteredModule := make(map[string]interface{})

// 首先检查模块本身是否启用
if enabled, hasEnabled := moduleObj["enabled"]; hasEnabled {
if enabledBool, ok := enabled.(bool); ok && !enabledBool {
continue // 跳过被禁用的模块
}
filteredModule["enabled"] = enabled
}

// 过滤子模块
for subKey, subValue := range moduleObj {
if subKey == "enabled" {
continue // enabled字段已经处理过了
}

subModulePath := modulePath + "." + subKey

// 检查子模块是否启用
if subValueBool, ok := subValue.(bool); ok && !subValueBool {
continue // 跳过被禁用的子模块
}

if hasModulePermissionForUser(userRole, subModulePath, subValue) {
filteredModule[subKey] = subValue
}
}

// 只有当过滤后的模块不为空时才添加
if len(filteredModule) > 0 {
filteredSection[moduleKey] = filteredModule
hasValidModules = true
}
} else {
// 检查简单模块值是否启用
if moduleValueBool, ok := moduleValue.(bool); ok && !moduleValueBool {
continue // 跳过被禁用的简单模块
}
filteredSection[moduleKey] = moduleValue
hasValidModules = true
}
}
}

// 只有当区域有有效模块时才添加到结果中
if hasValidModules && len(filteredSection) > 0 {
filteredConfig[sectionKey] = filteredSection
}
}
}

// 转换回JSON字符串
filteredBytes, err := json.Marshal(filteredConfig)
if err != nil {
return "{}"
}

return string(filteredBytes)
}

// isHeaderNavModuleEnabled 检查顶栏模块是否启用
func isHeaderNavModuleEnabled(moduleKey string, moduleValue interface{}) bool {
switch v := moduleValue.(type) {
case bool:
return v
case map[string]interface{}:
if enabled, hasEnabled := v["enabled"]; hasEnabled {
if enabledBool, ok := enabled.(bool); ok {
return enabledBool
}
}
return true // 如果没有enabled字段,默认启用
default:
return true
}
}

// hasHeaderNavModulePermission 检查用户是否有权限访问顶栏模块
func hasHeaderNavModulePermission(moduleKey string, moduleValue interface{}, userRole int) bool {
// 对于模型广场,需要检查requireAuth配置
if moduleKey == "pricing" {
return checkPricingModulePermission(moduleValue, userRole)
}

// 未登录用户和普通用户只能访问基础模块
if userRole < common.RoleAdminUser {
allowedModules := map[string]bool{
"home": true,
"console": true,
"docs": true, // 文档允许未登录用户访问
"about": true, // 关于页面允许未登录用户访问
}
return allowedModules[moduleKey]
}

// 管理员可以访问更多模块
return true
}

// checkPricingModulePermission 检查模型广场模块的权限
func checkPricingModulePermission(moduleValue interface{}, userRole int) bool {
// 如果是布尔值配置,默认不需要登录
if boolValue, ok := moduleValue.(bool); ok {
return boolValue // 简单的启用/禁用
}

// 如果是对象配置,检查requireAuth设置
if objValue, ok := moduleValue.(map[string]interface{}); ok {
// 检查模块是否启用
if enabled, hasEnabled := objValue["enabled"]; hasEnabled {
if enabledBool, ok := enabled.(bool); ok && !enabledBool {
return false // 模块被禁用
}
}

// 检查是否需要登录
if requireAuth, hasRequireAuth := objValue["requireAuth"]; hasRequireAuth {
if requireAuthBool, ok := requireAuth.(bool); ok && requireAuthBool {
// 需要登录才能访问,未登录用户不能访问
return userRole >= common.RoleCommonUser
}
}

// 默认不需要登录
return true
}

// 其他情况默认允许
return true
}

// hasModulePermissionForUser 检查用户是否有权限访问指定模块
func hasModulePermissionForUser(userRole int, modulePath string, moduleValue interface{}) bool {
// 数据看板始终允许访问,不受控制台区域开关影响
if modulePath == "console.detail" {
return true
}

// 普通用户只能访问基础功能
if userRole < common.RoleAdminUser {
return isUserModuleAllowedInFilter(modulePath)
}

// 管理员需要检查模块是否启用
if userRole >= common.RoleAdminUser && userRole < common.RoleRootUser {
// 检查模块值是否为启用状态
switch v := moduleValue.(type) {
case bool:
return v
case map[string]interface{}:
if enabled, hasEnabled := v["enabled"]; hasEnabled {
if enabledBool, ok := enabled.(bool); ok {
return enabledBool
}
}
return true // 如果没有enabled字段,默认启用
default:
return true
}
}

return true
}

// isUserModuleAllowedInFilter 检查普通用户是否允许访问指定模块(用于过滤)
func isUserModuleAllowedInFilter(modulePath string) bool {
// 数据看板始终允许访问,不受控制台区域开关影响
if modulePath == "console.detail" {
return true
}

// 普通用户允许访问的模块列表
allowedModules := map[string]bool{
"console.enabled": true,
"console.detail": true,
"console.token": true,
"console.log": true,
"console.midjourney": true,
"console.task": true,
"personal.enabled": true,
"personal.topup": true,
"personal.personal": true,
"chat.enabled": true,
"chat.playground": true,
"chat.chat": true,
}

return allowedModules[modulePath]
}
Loading