- 新增计费模块:执行开始建单、终态结算/取消/失败处理,支持按条/按秒/按token计费 - 新增执行生命周期跟踪:优雅关停时取消运行中执行并等待落库 - 新增异步任务等待/通知机制(Wait/Notify) - 重构执行记录落库与进度上报,统一失败分类与重试语义 - 重命名文件:async_task.go→async.go、flow_checkpoint_store.go→exec_checkpoint.go、flow_graph_util.go→exec_record.go - 更新 .gitignore 与数据库密码配置
228 lines
6.3 KiB
Go
228 lines
6.3 KiB
Go
package values
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
)
|
|
|
|
// UnwrapSchemaWrapper 递归剥掉 json-schema-editor 输出的 {type, value/attrs} 包裹层,
|
|
// 只保留干净的 key/value 嵌套结构。
|
|
// 示例:
|
|
//
|
|
// {"a": {"type":"string","value":"hi"}} → {"a": "hi"}
|
|
// {"b": {"type":"object","attrs":{"c":1}}} → {"b": {"c": 1}}
|
|
// {"arr": {"type":"array","attrs":[{"type":"number","value":1}]}} → {"arr": [1]}
|
|
func UnwrapSchemaWrapper(v any) any {
|
|
switch val := v.(type) {
|
|
case map[string]any:
|
|
// 识别包裹节点:{type: "<jsonType>", value/attrs: <实际值>, ...}
|
|
if t, ok := val["type"].(string); ok && isSchemaEditorType(t) {
|
|
dataKey := "value"
|
|
if t == "object" || t == "array" {
|
|
dataKey = "attrs"
|
|
}
|
|
if raw, has := val[dataKey]; has {
|
|
return UnwrapSchemaWrapper(raw)
|
|
}
|
|
}
|
|
res := make(map[string]any, len(val))
|
|
for k, child := range val {
|
|
res[k] = UnwrapSchemaWrapper(child)
|
|
}
|
|
return res
|
|
case []any:
|
|
res := make([]any, len(val))
|
|
for i, item := range val {
|
|
res[i] = UnwrapSchemaWrapper(item)
|
|
}
|
|
return res
|
|
default:
|
|
return val
|
|
}
|
|
}
|
|
|
|
// isSchemaEditorType 是否为 json-schema-editor 的 6 种类型标识
|
|
func isSchemaEditorType(t string) bool {
|
|
switch t {
|
|
case "string", "number", "boolean", "null", "object", "array":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// MapResultByTemplate 按 template 定义的结构,从 source 中拷贝对应字段的值。
|
|
// 只保留 template 里出现的字段:对象字段按同名字段递归拷贝,数组字段按模板元素结构逐元素过滤,标量字段直接拷贝 source 的值。
|
|
func MapResultByTemplate(template map[string]any, source map[string]any) map[string]any {
|
|
result := make(map[string]any, len(template))
|
|
for key, tmplVal := range template {
|
|
srcVal, ok := source[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if tmplMap, isMap := tmplVal.(map[string]any); isMap {
|
|
if srcMap, isMap := srcVal.(map[string]any); isMap {
|
|
result[key] = MapResultByTemplate(tmplMap, srcMap)
|
|
}
|
|
continue
|
|
}
|
|
if tmplArr, isArr := tmplVal.([]any); isArr {
|
|
result[key] = mapTemplateArray(tmplArr, srcVal)
|
|
continue
|
|
}
|
|
result[key] = srcVal
|
|
}
|
|
return result
|
|
}
|
|
|
|
// mapTemplateArray 按模板数组的元素结构映射 source 数组:
|
|
// 模板首元素为对象时,逐元素按 MapResultByTemplate 过滤只保留模板字段;
|
|
// 模板数组为空或首元素非对象(无法确定元素结构)时,原样拷贝 source 数组。
|
|
func mapTemplateArray(tmplArr []any, srcVal any) any {
|
|
srcList, ok := srcVal.([]any)
|
|
if !ok || len(tmplArr) == 0 {
|
|
return srcVal
|
|
}
|
|
elemTmpl, ok := tmplArr[0].(map[string]any)
|
|
if !ok {
|
|
return srcVal
|
|
}
|
|
result := make([]any, 0, len(srcList))
|
|
for _, srcElem := range srcList {
|
|
if srcMap, isMap := srcElem.(map[string]any); isMap {
|
|
result = append(result, MapResultByTemplate(elemTmpl, srcMap))
|
|
} else {
|
|
result = append(result, srcElem)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// assignBySchemaType 按当前字段声明的 schema 类型把值类型化:
|
|
// string 遇数组/对象转 JSON 字符串;number/boolean 解析字符串;object/array 解析 JSON 字符串;其余原样返回
|
|
func assignBySchemaType(node map[string]interface{}, value any) any {
|
|
t, _ := node["type"].(string)
|
|
return assignByType(t, value)
|
|
}
|
|
|
|
// assignByType 按字段声明的 type 把值类型化;walkMap 的 schema 节点与 parseMap 的模型参数共用
|
|
func assignByType(t string, value any) any {
|
|
switch t {
|
|
case "string":
|
|
return toSchemaString(value)
|
|
case "number":
|
|
return toSchemaNumber(value)
|
|
case "boolean":
|
|
return toSchemaBool(value)
|
|
case "object", "array":
|
|
return toSchemaStruct(value)
|
|
default:
|
|
return value
|
|
}
|
|
}
|
|
|
|
// toSchemaString 转 string:字符串原样,数组元素拼成字符串(单元素取元素本身,多元素逗号连接),对象序列化为 JSON 字符串
|
|
func toSchemaString(v any) any {
|
|
switch val := v.(type) {
|
|
case []interface{}:
|
|
parts := make([]string, 0, len(val))
|
|
for _, item := range val {
|
|
parts = append(parts, toPlainString(item))
|
|
}
|
|
return strings.Join(parts, ",")
|
|
case map[string]interface{}:
|
|
if b, err := json.Marshal(val); err == nil {
|
|
return string(b)
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// toPlainString 把数组元素转成不带括号的纯字符串:
|
|
// 数组([]any / 类型化切片)逐元素取纯字符串,单元素取元素本身,多元素逗号连接;
|
|
// 对象序列化为 JSON 字符串;其余原样字符串化。
|
|
func toPlainString(v any) string {
|
|
if s, ok := v.(string); ok {
|
|
return s
|
|
}
|
|
switch val := v.(type) {
|
|
case []interface{}:
|
|
parts := make([]string, 0, len(val))
|
|
for _, item := range val {
|
|
parts = append(parts, toPlainString(item))
|
|
}
|
|
return strings.Join(parts, ",")
|
|
case map[string]interface{}:
|
|
if b, err := json.Marshal(val); err == nil {
|
|
return string(b)
|
|
}
|
|
}
|
|
rv := reflect.ValueOf(v)
|
|
if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array) {
|
|
parts := make([]string, 0, rv.Len())
|
|
for i := 0; i < rv.Len(); i++ {
|
|
parts = append(parts, toPlainString(rv.Index(i).Interface()))
|
|
}
|
|
return strings.Join(parts, ",")
|
|
}
|
|
if b, err := json.Marshal(v); err == nil {
|
|
return string(b)
|
|
}
|
|
return gconv.String(v)
|
|
}
|
|
|
|
// toSchemaNumber 转 number:数字原样,字符串尝试解析为 float64,失败原样返回
|
|
func toSchemaNumber(v any) any {
|
|
if s, ok := v.(string); ok {
|
|
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
|
return f
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// toSchemaBool 转 boolean:布尔原样,字符串尝试解析为 bool,失败原样返回
|
|
func toSchemaBool(v any) any {
|
|
if s, ok := v.(string); ok {
|
|
if b, err := strconv.ParseBool(s); err == nil {
|
|
return b
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// toSchemaStruct 转 object/array:合法 JSON 字符串解析为结构化数据,否则原样返回
|
|
func toSchemaStruct(v any) any {
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
return v
|
|
}
|
|
if !json.Valid([]byte(s)) {
|
|
return v
|
|
}
|
|
var out any
|
|
if err := json.Unmarshal([]byte(s), &out); err != nil {
|
|
return v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// schemaValueEmpty 值是否为空;0/false 视为有效值不剔除
|
|
func schemaValueEmpty(v interface{}) bool {
|
|
switch val := v.(type) {
|
|
case nil:
|
|
return true
|
|
case string:
|
|
return val == ""
|
|
case []interface{}:
|
|
return len(val) == 0
|
|
case map[string]interface{}:
|
|
return len(val) == 0
|
|
default:
|
|
return false
|
|
}
|
|
}
|