210 lines
6.0 KiB
Go
210 lines
6.0 KiB
Go
package canvas
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"math"
|
|
"strings"
|
|
"yingce/backend/internal/kernel"
|
|
)
|
|
|
|
var userAssetKinds = map[string]struct{}{
|
|
"text": {},
|
|
"image": {},
|
|
"video": {},
|
|
"audio": {},
|
|
"model": {},
|
|
"entity": {},
|
|
}
|
|
|
|
func validateUserAssetDocument(raw json.RawMessage) error {
|
|
var object map[string]json.RawMessage
|
|
if err := json.Unmarshal(raw, &object); err != nil {
|
|
return kernel.BadAuthRequest("素材数据格式错误")
|
|
}
|
|
kind, err := requiredJSONStringField(object, "kind")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kind = strings.TrimSpace(kind)
|
|
if _, ok := userAssetKinds[kind]; !ok {
|
|
return kernel.BadAuthRequest("不支持的素材类型")
|
|
}
|
|
if _, err := requiredJSONStringField(object, "title"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONStringField(object, "coverUrl"); err != nil {
|
|
return err
|
|
}
|
|
if err := validateUserAssetTags(object); err != nil {
|
|
return err
|
|
}
|
|
data, err := requiredJSONObjectField(object, "data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return validateUserAssetData(kind, data)
|
|
}
|
|
|
|
func validateUserAssetTags(object map[string]json.RawMessage) error {
|
|
raw, ok := object["tags"]
|
|
if !ok {
|
|
return kernel.BadAuthRequest("素材缺少 tags 字段")
|
|
}
|
|
trimmed := bytes.TrimSpace(raw)
|
|
if len(trimmed) == 0 || trimmed[0] != '[' {
|
|
return kernel.BadAuthRequest("素材 tags 必须是字符串数组")
|
|
}
|
|
var tags []string
|
|
if err := json.Unmarshal(raw, &tags); err != nil {
|
|
return kernel.BadAuthRequest("素材 tags 必须是字符串数组")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateUserAssetData(kind string, data map[string]json.RawMessage) error {
|
|
switch kind {
|
|
case "text":
|
|
_, err := requiredJSONStringField(data, "content")
|
|
return err
|
|
case "image":
|
|
if err := requireMediaLocator(data, "dataUrl", "图片"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "width"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "height"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "bytes"); err != nil {
|
|
return err
|
|
}
|
|
return requireNonEmptyJSONString(data, "mimeType")
|
|
case "video":
|
|
if err := requireMediaLocator(data, "url", "视频"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "width"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "height"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "bytes"); err != nil {
|
|
return err
|
|
}
|
|
return requireNonEmptyJSONString(data, "mimeType")
|
|
case "audio":
|
|
if err := requireMediaLocator(data, "url", "音频"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "bytes"); err != nil {
|
|
return err
|
|
}
|
|
return requireNonEmptyJSONString(data, "mimeType")
|
|
case "model":
|
|
if err := requireMediaLocator(data, "url", "模型"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := requiredJSONNumberField(data, "bytes"); err != nil {
|
|
return err
|
|
}
|
|
if err := requireNonEmptyJSONString(data, "mimeType"); err != nil {
|
|
return err
|
|
}
|
|
return requireNonEmptyJSONString(data, "fileName")
|
|
case "entity":
|
|
return requireJSONObjectFieldPresent(data, "definition")
|
|
default:
|
|
return kernel.BadAuthRequest("不支持的素材类型")
|
|
}
|
|
}
|
|
|
|
func requireMediaLocator(data map[string]json.RawMessage, primaryKey string, label string) error {
|
|
primary, err := optionalJSONStringField(data, primaryKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
storageKey, err := optionalJSONStringField(data, "storageKey")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.TrimSpace(primary) == "" && strings.TrimSpace(storageKey) == "" {
|
|
return kernel.BadAuthRequest(label + "素材缺少 " + primaryKey + " 或 storageKey")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func requiredJSONStringField(object map[string]json.RawMessage, key string) (string, error) {
|
|
raw, ok := object[key]
|
|
if !ok {
|
|
return "", kernel.BadAuthRequest("素材缺少 " + key + " 字段")
|
|
}
|
|
var value string
|
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
return "", kernel.BadAuthRequest("素材字段 " + key + " 必须是字符串")
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func optionalJSONStringField(object map[string]json.RawMessage, key string) (string, error) {
|
|
raw, ok := object[key]
|
|
if !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
return "", nil
|
|
}
|
|
var value string
|
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
return "", kernel.BadAuthRequest("素材字段 " + key + " 必须是字符串")
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func requireNonEmptyJSONString(object map[string]json.RawMessage, key string) error {
|
|
value, err := requiredJSONStringField(object, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.TrimSpace(value) == "" {
|
|
return kernel.BadAuthRequest("素材字段 " + key + " 不能为空")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func requiredJSONNumberField(object map[string]json.RawMessage, key string) (float64, error) {
|
|
raw, ok := object[key]
|
|
if !ok {
|
|
return 0, kernel.BadAuthRequest("素材缺少 " + key + " 字段")
|
|
}
|
|
// json.Unmarshal(null, &float64) succeeds without assigning a value.
|
|
// Reject null explicitly so the persisted JSON obeys the client contract.
|
|
if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
return 0, kernel.BadAuthRequest("素材字段 " + key + " 必须是数字")
|
|
}
|
|
var value float64
|
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
return 0, kernel.BadAuthRequest("素材字段 " + key + " 必须是数字")
|
|
}
|
|
if math.IsNaN(value) || math.IsInf(value, 0) || value < 0 {
|
|
return 0, kernel.BadAuthRequest("素材字段 " + key + " 必须是非负数字")
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func requiredJSONObjectField(object map[string]json.RawMessage, key string) (map[string]json.RawMessage, error) {
|
|
raw, ok := object[key]
|
|
if !ok {
|
|
return nil, kernel.BadAuthRequest("素材缺少 " + key + " 字段")
|
|
}
|
|
var nested map[string]json.RawMessage
|
|
if err := json.Unmarshal(raw, &nested); err != nil || nested == nil {
|
|
return nil, kernel.BadAuthRequest("素材字段 " + key + " 必须是对象")
|
|
}
|
|
return nested, nil
|
|
}
|
|
|
|
func requireJSONObjectFieldPresent(object map[string]json.RawMessage, key string) error {
|
|
_, err := requiredJSONObjectField(object, key)
|
|
return err
|
|
}
|