mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
feat: add storage schemas
This commit is contained in:
11
api.go
11
api.go
@@ -28,6 +28,7 @@ import (
|
||||
"m7s.live/v5/pb"
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/format"
|
||||
"m7s.live/v5/pkg/storage"
|
||||
"m7s.live/v5/pkg/util"
|
||||
)
|
||||
|
||||
@@ -724,7 +725,7 @@ func (s *Server) GetConfigFile(_ context.Context, req *emptypb.Empty) (res *pb.G
|
||||
func (s *Server) UpdateConfigFile(_ context.Context, req *pb.UpdateConfigFileRequest) (res *pb.SuccessResponse, err error) {
|
||||
if s.configFileContent != nil {
|
||||
s.configFileContent = []byte(req.Content)
|
||||
os.WriteFile(s.configFilePath, s.configFileContent, 0644)
|
||||
err = os.WriteFile(s.configFilePath, s.configFileContent, 0644)
|
||||
res = &pb.SuccessResponse{}
|
||||
} else {
|
||||
err = pkg.ErrNotFound
|
||||
@@ -1456,3 +1457,11 @@ func (s *Server) GetAlarmList(ctx context.Context, req *pb.AlarmListRequest) (re
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetStorageSchemas 获取所有已注册的存储类型 Schema
|
||||
// 用于前端动态渲染存储配置表单
|
||||
func (s *Server) GetStorageSchemas(w http.ResponseWriter, r *http.Request) {
|
||||
schemas := storage.GetSchemas()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(schemas)
|
||||
}
|
||||
|
||||
@@ -518,6 +518,13 @@ func (config *Config) schema(index int) (r any) {
|
||||
if valueType.Kind() == reflect.Ptr {
|
||||
valueType = valueType.Elem()
|
||||
}
|
||||
|
||||
// 特殊处理 map[string]any 类型(如 Storage 字段)
|
||||
// 使用动态 Schema 注册机制
|
||||
if keyType.Kind() == reflect.String && valueType.Kind() == reflect.Interface {
|
||||
return buildDynamicStorageSchema(config, index, isDefault, valueSource)
|
||||
}
|
||||
|
||||
valueIsStruct := valueType.Kind() == reflect.Struct && valueType != regexpType
|
||||
valueIsMap := valueType.Kind() == reflect.Map
|
||||
|
||||
@@ -1019,3 +1026,28 @@ func (config *Config) GetFormily() (r Object) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// buildDynamicStorageSchema 为 map[string]any 类型(如 Storage)构建动态表单 Schema
|
||||
// 使用 DynamicStorage 组件,前端会自动调用 /api/storage/schemas 获取存储类型列表
|
||||
func buildDynamicStorageSchema(config *Config, index int, isDefault bool, valueSource string) Property {
|
||||
// 获取当前配置的值
|
||||
currentValue := config.GetValue()
|
||||
|
||||
return Property{
|
||||
Type: "object",
|
||||
Title: config.name,
|
||||
Decorator: "FormItem",
|
||||
Component: "DynamicStorage",
|
||||
Index: index,
|
||||
Default: currentValue,
|
||||
ComplexType: "dynamic-storage",
|
||||
IsDefault: isDefault,
|
||||
ValueSource: valueSource,
|
||||
DecoratorProps: map[string]any{
|
||||
"tooltip": config.tag.Get("desc"),
|
||||
},
|
||||
ComponentProps: map[string]any{
|
||||
"apiUrl": "/api/storage/schemas",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +364,14 @@ func init() {
|
||||
Factory["cos"] = func(config any) (Storage, error) {
|
||||
var cosConfig COSStorageConfig
|
||||
config.Parse(&cosConfig, config.(map[string]any))
|
||||
return NewCOSStorage(cosConfig)
|
||||
return NewCOSStorage(&cosConfig)
|
||||
}
|
||||
|
||||
// 注册 COS 存储类型 Schema
|
||||
RegisterSchema(StorageSchema{
|
||||
Type: "cos",
|
||||
Name: "腾讯云 COS",
|
||||
Description: "腾讯云对象存储服务",
|
||||
Properties: GenerateSchemaFromStruct(COSStorageConfig{}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -357,6 +357,14 @@ func init() {
|
||||
Factory["oss"] = func(config any) (Storage, error) {
|
||||
var ossConfig OSSStorageConfig
|
||||
config.Parse(&ossConfig, config.(map[string]any))
|
||||
return NewOSSStorage(ossConfig)
|
||||
return NewOSSStorage(&ossConfig)
|
||||
}
|
||||
|
||||
// 注册 OSS 存储类型 Schema
|
||||
RegisterSchema(StorageSchema{
|
||||
Type: "oss",
|
||||
Name: "阿里云 OSS",
|
||||
Description: "阿里云对象存储服务",
|
||||
Properties: GenerateSchemaFromStruct(OSSStorageConfig{}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -446,4 +446,12 @@ func init() {
|
||||
config.Parse(&s3Config, conf.(map[string]any))
|
||||
return NewS3Storage(&s3Config)
|
||||
}
|
||||
|
||||
// 注册 S3 存储类型 Schema
|
||||
RegisterSchema(StorageSchema{
|
||||
Type: "s3",
|
||||
Name: "S3 存储",
|
||||
Description: "AWS S3 或兼容 S3 协议的对象存储(如 MinIO)",
|
||||
Properties: GenerateSchemaFromStruct(S3StorageConfig{}),
|
||||
})
|
||||
}
|
||||
|
||||
153
pkg/storage/schema.go
Normal file
153
pkg/storage/schema.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PropertyDef 属性定义
|
||||
type PropertyDef struct {
|
||||
Type string `json:"type"` // string, number, boolean, object, array
|
||||
Default any `json:"default,omitempty"` // 默认值
|
||||
Description string `json:"desc,omitempty"` // 描述
|
||||
Enum []string `json:"enum,omitempty"` // 枚举值
|
||||
Required bool `json:"required,omitempty"` // 是否必填
|
||||
Min *float64 `json:"min,omitempty"` // 最小值(number类型)
|
||||
Max *float64 `json:"max,omitempty"` // 最大值(number类型)
|
||||
Pattern string `json:"pattern,omitempty"` // 正则模式(string类型)
|
||||
Properties Schema `json:"properties,omitempty"` // 嵌套对象的属性
|
||||
}
|
||||
|
||||
// Schema 配置 Schema
|
||||
type Schema map[string]PropertyDef
|
||||
|
||||
// StorageSchema 存储类型 Schema
|
||||
type StorageSchema struct {
|
||||
Type string `json:"type"` // 存储类型标识
|
||||
Name string `json:"name"` // 显示名称
|
||||
Description string `json:"description"` // 描述
|
||||
Properties Schema `json:"properties"` // 配置属性
|
||||
}
|
||||
|
||||
// SchemaRegistry 存储类型 Schema 注册表
|
||||
var SchemaRegistry = make(map[string]StorageSchema)
|
||||
|
||||
// RegisterSchema 注册存储类型 Schema
|
||||
func RegisterSchema(schema StorageSchema) {
|
||||
SchemaRegistry[schema.Type] = schema
|
||||
}
|
||||
|
||||
// GetSchemas 获取所有已注册的存储类型 Schema
|
||||
func GetSchemas() map[string]StorageSchema {
|
||||
return SchemaRegistry
|
||||
}
|
||||
|
||||
// GetSchema 获取指定存储类型的 Schema
|
||||
func GetSchema(storageType string) (StorageSchema, bool) {
|
||||
schema, ok := SchemaRegistry[storageType]
|
||||
return schema, ok
|
||||
}
|
||||
|
||||
// GenerateSchemaFromStruct 从结构体自动生成 Schema
|
||||
// 支持 json/yaml tag 作为字段名,desc tag 作为描述,default tag 作为默认值
|
||||
func GenerateSchemaFromStruct(v any) Schema {
|
||||
schema := make(Schema)
|
||||
t := reflect.TypeOf(v)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return schema
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
// 获取字段名(优先使用 json tag,其次 yaml tag)
|
||||
fieldName := getFieldName(field)
|
||||
if fieldName == "" || fieldName == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
prop := PropertyDef{
|
||||
Type: getFieldType(field.Type),
|
||||
Description: field.Tag.Get("desc"),
|
||||
}
|
||||
|
||||
// 解析 default tag
|
||||
if defaultVal := field.Tag.Get("default"); defaultVal != "" {
|
||||
prop.Default = defaultVal
|
||||
}
|
||||
|
||||
// 解析 enum tag
|
||||
if enumVal := field.Tag.Get("enum"); enumVal != "" {
|
||||
prop.Enum = strings.Split(enumVal, ",")
|
||||
}
|
||||
|
||||
// 判断是否必填(通过检查是否有 required tag 或字段是否为指针类型)
|
||||
if field.Tag.Get("required") == "true" {
|
||||
prop.Required = true
|
||||
}
|
||||
|
||||
// 处理嵌套结构体
|
||||
if field.Type.Kind() == reflect.Struct && field.Type.String() != "time.Duration" {
|
||||
prop.Properties = GenerateSchemaFromStruct(reflect.New(field.Type).Interface())
|
||||
}
|
||||
|
||||
schema[fieldName] = prop
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
// getFieldName 获取字段名
|
||||
func getFieldName(field reflect.StructField) string {
|
||||
// 优先使用 json tag
|
||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
||||
parts := strings.Split(jsonTag, ",")
|
||||
return parts[0]
|
||||
}
|
||||
// 其次使用 yaml tag
|
||||
if yamlTag := field.Tag.Get("yaml"); yamlTag != "" {
|
||||
parts := strings.Split(yamlTag, ",")
|
||||
return parts[0]
|
||||
}
|
||||
// 最后使用字段名的小写形式
|
||||
return strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
// getFieldType 获取字段类型
|
||||
func getFieldType(t reflect.Type) string {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return "string"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
return "number"
|
||||
case reflect.Bool:
|
||||
return "boolean"
|
||||
case reflect.Slice, reflect.Array:
|
||||
return "array"
|
||||
case reflect.Map, reflect.Struct:
|
||||
// 特殊处理 time.Duration
|
||||
if t.String() == "time.Duration" {
|
||||
return "string" // Duration 在 YAML 中通常表示为字符串如 "30s"
|
||||
}
|
||||
return "object"
|
||||
case reflect.Ptr:
|
||||
return getFieldType(t.Elem())
|
||||
default:
|
||||
return "string"
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 注册 local 存储类型 Schema
|
||||
RegisterSchema(StorageSchema{
|
||||
Type: "local",
|
||||
Name: "本地存储",
|
||||
Description: "将文件存储到本地磁盘",
|
||||
Properties: GenerateSchemaFromStruct(LocalStorageConfig{}),
|
||||
})
|
||||
}
|
||||
@@ -284,6 +284,7 @@ func (s *Server) Start() (err error) {
|
||||
"/api/videotrack/sse/{streamPath...}": s.api_VideoTrack_SSE,
|
||||
"/api/audiotrack/sse/{streamPath...}": s.api_AudioTrack_SSE,
|
||||
"/annexb/{streamPath...}": s.annexB,
|
||||
"/api/storage/schemas": s.GetStorageSchemas,
|
||||
})
|
||||
|
||||
if s.config.DSN != "" {
|
||||
|
||||
Reference in New Issue
Block a user