187 lines
6.2 KiB
Go
187 lines
6.2 KiB
Go
package platform
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"infinite-canvas/backend/internal/kernel"
|
|
"strings"
|
|
"time"
|
|
|
|
"infinite-canvas/backend/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const featureAvailabilitySettingKey = "feature_availability"
|
|
|
|
const (
|
|
FeatureShortDrama = "shortDrama"
|
|
FeatureTaskCenter = "taskCenter"
|
|
FeatureCredits = "credits"
|
|
FeatureCustomChannels = "customChannels"
|
|
FeatureFrontendModels = "frontendModels"
|
|
FeaturePluginCenter = "pluginCenter"
|
|
FeatureSystemPlugins = "systemPluginsVisibleToUsers"
|
|
FeatureTimelineTranscription = "timelineTranscription"
|
|
)
|
|
|
|
type FeatureAvailability struct {
|
|
ShortDramaEnabled bool `json:"shortDramaEnabled"`
|
|
TaskCenterEnabled bool `json:"taskCenterEnabled"`
|
|
CreditsEnabled bool `json:"creditsEnabled"`
|
|
CustomChannelsEnabled bool `json:"customChannelsEnabled"`
|
|
FrontendModelsEnabled bool `json:"frontendModelsEnabled"`
|
|
PluginCenterEnabled bool `json:"pluginCenterEnabled"`
|
|
SystemPluginsVisibleToUsers bool `json:"systemPluginsVisibleToUsers"`
|
|
TimelineTranscriptionEnabled bool `json:"timelineTranscriptionEnabled"`
|
|
}
|
|
|
|
type PublicFeatureAvailability struct {
|
|
FeatureAvailability
|
|
Configured bool `json:"configured"`
|
|
UpdatedBy string `json:"updatedBy,omitempty"`
|
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
|
}
|
|
|
|
func DefaultFeatureAvailability() FeatureAvailability {
|
|
// 缺少配置代表尚未由运维接管;前台模型需要明确配置后才开放。
|
|
return FeatureAvailability{
|
|
ShortDramaEnabled: true,
|
|
TaskCenterEnabled: true,
|
|
CreditsEnabled: true,
|
|
CustomChannelsEnabled: true,
|
|
FrontendModelsEnabled: false,
|
|
PluginCenterEnabled: true,
|
|
SystemPluginsVisibleToUsers: true,
|
|
TimelineTranscriptionEnabled: true,
|
|
}
|
|
}
|
|
|
|
func (s *Service) FeatureAvailability() (*PublicFeatureAvailability, error) {
|
|
setting, value, err := s.readFeatureAvailability()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return publicFeatureAvailability(setting, value), nil
|
|
}
|
|
|
|
func (s *Service) AdminFeatureAvailability(actor *model.User) (*PublicFeatureAvailability, error) {
|
|
if err := s.requireAdmin(actor); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.FeatureAvailability()
|
|
}
|
|
|
|
func (s *Service) UpdateFeatureAvailability(actor *model.User, value FeatureAvailability) (*PublicFeatureAvailability, error) {
|
|
if err := s.requireAdmin(actor); err != nil {
|
|
return nil, err
|
|
}
|
|
current, before, err := s.readFeatureAvailability()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
encoded, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
setting := model.SystemSetting{Key: featureAvailabilitySettingKey, ValueJSON: string(encoded), UpdatedBy: actor.ID}
|
|
if current != nil {
|
|
setting.CreatedAt = current.CreatedAt
|
|
}
|
|
if err := s.repo.SaveSystemSetting(&setting); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.appendAdminAudit(actor, "feature_availability.update", "system_setting", featureAvailabilitySettingKey, "更新功能开放配置", map[string]any{"before": before, "after": value}); err != nil {
|
|
return nil, err
|
|
}
|
|
return publicFeatureAvailability(&setting, value), nil
|
|
}
|
|
|
|
func (s *Service) FeatureEnabled(feature string) (bool, error) {
|
|
_, value, err := s.readFeatureAvailability()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
switch feature {
|
|
case FeatureShortDrama:
|
|
return value.ShortDramaEnabled, nil
|
|
case FeatureTaskCenter:
|
|
return value.TaskCenterEnabled, nil
|
|
case FeatureCredits:
|
|
return value.CreditsEnabled, nil
|
|
case FeatureCustomChannels:
|
|
return value.CustomChannelsEnabled, nil
|
|
case FeatureFrontendModels:
|
|
return value.FrontendModelsEnabled, nil
|
|
case FeaturePluginCenter:
|
|
return value.PluginCenterEnabled, nil
|
|
case FeatureSystemPlugins:
|
|
return value.SystemPluginsVisibleToUsers, nil
|
|
case FeatureTimelineTranscription:
|
|
return value.TimelineTranscriptionEnabled, nil
|
|
default:
|
|
return false, errors.New("未知功能开放配置")
|
|
}
|
|
}
|
|
|
|
func (s *Service) RequireFeature(feature string) error {
|
|
enabled, err := s.FeatureEnabled(feature)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if enabled {
|
|
return nil
|
|
}
|
|
switch feature {
|
|
case FeatureShortDrama:
|
|
return kernel.Forbidden("短剧创作暂未开放")
|
|
case FeatureTaskCenter:
|
|
return kernel.Forbidden("任务中心暂未开放")
|
|
case FeatureCredits:
|
|
return kernel.Forbidden("积分功能暂未开放")
|
|
case FeatureCustomChannels:
|
|
return kernel.Forbidden("自定义渠道暂未开放")
|
|
case FeatureFrontendModels:
|
|
return kernel.Forbidden("前台模型目录暂未开放")
|
|
case FeaturePluginCenter:
|
|
return kernel.Forbidden("插件中心暂未开放")
|
|
case FeatureSystemPlugins:
|
|
return kernel.Forbidden("系统插件暂未向普通用户展示")
|
|
case FeatureTimelineTranscription:
|
|
return kernel.Forbidden("字幕转写暂未开放")
|
|
default:
|
|
return kernel.Forbidden("该功能暂未开放")
|
|
}
|
|
}
|
|
|
|
func (s *Service) readFeatureAvailability() (*model.SystemSetting, FeatureAvailability, error) {
|
|
setting, err := s.repo.SystemSetting(featureAvailabilitySettingKey)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, DefaultFeatureAvailability(), nil
|
|
}
|
|
if err != nil {
|
|
return nil, FeatureAvailability{}, err
|
|
}
|
|
// 以全开放默认值为基底,避免已有三字段配置在升级后因缺少新字段而意外关闭功能。
|
|
value := DefaultFeatureAvailability()
|
|
if strings.TrimSpace(setting.ValueJSON) == "" || json.Unmarshal([]byte(setting.ValueJSON), &value) != nil {
|
|
return nil, FeatureAvailability{}, errors.New("功能开放配置格式无效")
|
|
}
|
|
return setting, value, nil
|
|
}
|
|
|
|
func publicFeatureAvailability(setting *model.SystemSetting, value FeatureAvailability) *PublicFeatureAvailability {
|
|
result := &PublicFeatureAvailability{FeatureAvailability: value, Configured: setting != nil}
|
|
if setting != nil {
|
|
result.UpdatedBy = setting.UpdatedBy
|
|
result.UpdatedAt = setting.UpdatedAt
|
|
}
|
|
return result
|
|
}
|
|
|
|
// PublicFeatureAvailability projects the persisted feature flags without
|
|
// exposing the platform service internals to compatibility callers.
|
|
func ProjectFeatureAvailability(setting *model.SystemSetting, value FeatureAvailability) *PublicFeatureAvailability {
|
|
return publicFeatureAvailability(setting, value)
|
|
}
|