1. 新增标准调试参数
- templates/standard_overlays/ 目录下导入 std_face_debug、
std_test_sensitive、std_production_quiet 三个标准调试参数
- 启动时通过 ImportStandardOverlaysFromDir 自动导入 SQLite
- 支持 std_ 前缀只读保护,不可直接编辑/删除
2. 复制后不立即保存
- 调试参数克隆:重定向到新建页预填数据,点击保存才入库
- 识别模板克隆:重定向到模板编辑页预填,点击创建/进入可视化编辑才入库
- 可视化编辑也支持 clone_source 参数,编辑保存时创建新模板
3. 简化调试参数 JSON 结构
- 去掉 instance_overrides 层级,改为顶层 override 键
- 运行时渲染兼容新旧两种格式
- UI 去掉目标/目标数量字段
4. 清理死代码
- 移除从零新建空白调试参数/模板的代码路径
144 lines
4.7 KiB
Go
144 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"3588AdminBackend/internal/storage"
|
|
)
|
|
|
|
func ImportStandardTemplatesFromDir(repo *storage.AssetsRepo, dir string) (int, error) {
|
|
if repo == nil {
|
|
return 0, fmt.Errorf("asset repository is not configured")
|
|
}
|
|
dir = filepath.Clean(strings.TrimSpace(dir))
|
|
if dir == "" {
|
|
return 0, fmt.Errorf("standard template dir is empty")
|
|
}
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return 0, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
imported := 0
|
|
for _, file := range files {
|
|
if file.IsDir() || strings.ToLower(filepath.Ext(file.Name())) != ".json" {
|
|
continue
|
|
}
|
|
path := filepath.Join(dir, file.Name())
|
|
body, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return imported, err
|
|
}
|
|
var raw map[string]any
|
|
if err := json.Unmarshal(body, &raw); err != nil {
|
|
return imported, fmt.Errorf("%s: %w", path, err)
|
|
}
|
|
if raw == nil {
|
|
raw = map[string]any{}
|
|
}
|
|
name := strings.TrimSpace(firstString(raw["name"], strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))))
|
|
if name == "" {
|
|
return imported, fmt.Errorf("%s: template name is empty", path)
|
|
}
|
|
if err := validateConfigName(name); err != nil {
|
|
return imported, fmt.Errorf("%s: invalid template name %q: %w", path, name, err)
|
|
}
|
|
if !isStandardTemplateName(name) {
|
|
return imported, fmt.Errorf("%s: standard template name must start with std_", path)
|
|
}
|
|
if strings.TrimSpace(stringValue(raw["source"])) == "" {
|
|
raw["source"] = "standard"
|
|
}
|
|
body, err = marshalConfigJSON(raw)
|
|
if err != nil {
|
|
return imported, err
|
|
}
|
|
existing, err := repo.GetTemplate(name)
|
|
if err != nil {
|
|
return imported, err
|
|
}
|
|
if existing != nil &&
|
|
strings.TrimSpace(existing.Description) == strings.TrimSpace(stringValue(raw["description"])) &&
|
|
strings.TrimSpace(existing.BodyJSON) == strings.TrimSpace(string(body)) {
|
|
continue
|
|
}
|
|
if err := repo.SaveTemplate(name, stringValue(raw["description"]), string(body)); err != nil {
|
|
return imported, err
|
|
}
|
|
imported++
|
|
}
|
|
return imported, nil
|
|
}
|
|
|
|
func ImportStandardOverlaysFromDir(repo *storage.AssetsRepo, dir string) (int, error) {
|
|
if repo == nil {
|
|
return 0, fmt.Errorf("asset repository is not configured")
|
|
}
|
|
dir = filepath.Clean(strings.TrimSpace(dir))
|
|
if dir == "" {
|
|
return 0, fmt.Errorf("standard overlay dir is empty")
|
|
}
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return 0, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
imported := 0
|
|
for _, file := range files {
|
|
if file.IsDir() || strings.ToLower(filepath.Ext(file.Name())) != ".json" {
|
|
continue
|
|
}
|
|
path := filepath.Join(dir, file.Name())
|
|
body, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return imported, err
|
|
}
|
|
var raw map[string]any
|
|
if err := json.Unmarshal(body, &raw); err != nil {
|
|
return imported, fmt.Errorf("%s: %w", path, err)
|
|
}
|
|
if raw == nil {
|
|
raw = map[string]any{}
|
|
}
|
|
name := strings.TrimSpace(firstString(raw["name"], strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))))
|
|
if name == "" {
|
|
return imported, fmt.Errorf("%s: overlay name is empty", path)
|
|
}
|
|
if err := validateConfigName(name); err != nil {
|
|
return imported, fmt.Errorf("%s: invalid overlay name %q: %w", path, name, err)
|
|
}
|
|
if !isStandardTemplateName(name) {
|
|
return imported, fmt.Errorf("%s: standard overlay name must start with std_", path)
|
|
}
|
|
description := strings.TrimSpace(stringValue(raw["description"]))
|
|
delete(raw, "name")
|
|
delete(raw, "description")
|
|
body, err = marshalConfigJSON(raw)
|
|
if err != nil {
|
|
return imported, err
|
|
}
|
|
existing, err := repo.GetOverlay(name)
|
|
if err != nil {
|
|
return imported, err
|
|
}
|
|
if existing != nil &&
|
|
strings.TrimSpace(existing.Description) == description &&
|
|
strings.TrimSpace(existing.BodyJSON) == strings.TrimSpace(string(body)) {
|
|
continue
|
|
}
|
|
if err := repo.SaveOverlay(name, description, string(body)); err != nil {
|
|
return imported, err
|
|
}
|
|
imported++
|
|
}
|
|
return imported, nil
|
|
}
|