feat(saved-query): 支持独立 SQL 文件与自定义存储目录

- 将已存查询内容从 JSON 拆分为独立 .sql 文件,并采用可读名称和数字后缀处理冲突
- 自动迁移旧版内联 SQL 与摘要文件名,保留外部编辑并在失败时回滚
- 支持选择、迁移、打开及恢复默认存储目录,补充数据根切换串行保护
- 新增已存查询右键在文件夹中打开,并同步处理查询重命名后的文件路径
- 补齐 Wails、浏览器模拟、六语言文案及后端和前端回归测试
This commit is contained in:
Syngnat
2026-07-24 11:29:04 +08:00
parent 4bc131264b
commit d3909c7dff
27 changed files with 2694 additions and 52 deletions

View File

@@ -10,9 +10,11 @@ import (
)
const (
bootstrapFileName = "storage_root.json"
bootstrapLockFileName = bootstrapFileName + ".lock"
configuredLogFileName = "gonavi.log"
bootstrapFileName = "storage_root.json"
bootstrapLockFileName = bootstrapFileName + ".lock"
configuredLogFileName = "gonavi.log"
savedQueryDirectoryName = "saved_queries"
savedQueryDirectoryProbePrefix = ".gonavi-saved-query-"
)
const dataRootEnvName = "GONAVI_DATA_ROOT"
@@ -63,8 +65,9 @@ func SetActiveRootErrorDetail(err error) error {
}
type bootstrapConfig struct {
DataRoot string `json:"dataRoot,omitempty"`
LogDirectory string `json:"logDirectory,omitempty"`
DataRoot string `json:"dataRoot,omitempty"`
LogDirectory string `json:"logDirectory,omitempty"`
SavedQueryDirectory string `json:"savedQueryDirectory,omitempty"`
}
func readBootstrapConfig() (bootstrapConfig, error) {
@@ -85,7 +88,8 @@ func readBootstrapConfig() (bootstrapConfig, error) {
func writeBootstrapConfig(cfg bootstrapConfig) error {
cfg.DataRoot = strings.TrimSpace(cfg.DataRoot)
cfg.LogDirectory = strings.TrimSpace(cfg.LogDirectory)
if cfg.DataRoot == "" && cfg.LogDirectory == "" {
cfg.SavedQueryDirectory = strings.TrimSpace(cfg.SavedQueryDirectory)
if cfg.DataRoot == "" && cfg.LogDirectory == "" && cfg.SavedQueryDirectory == "" {
if err := os.Remove(BootstrapPath()); err != nil && !os.IsNotExist(err) {
return err
}
@@ -228,6 +232,17 @@ func DriverRoot(activeRoot string) string {
return filepath.Join(root, "drivers")
}
func DefaultSavedQueryDirectory(activeRoot string) string {
root := strings.TrimSpace(activeRoot)
if root == "" {
root = MustResolveActiveRoot()
}
if abs, err := filepath.Abs(root); err == nil {
root = abs
}
return filepath.Join(filepath.Clean(root), savedQueryDirectoryName)
}
func SetActiveRoot(root string) (string, error) {
targetRoot, err := normalizeRoot(root)
if err != nil {
@@ -303,3 +318,66 @@ func SetConfiguredLogDirectory(directory string) (string, error) {
}
return target, nil
}
func ResolveConfiguredSavedQueryDirectory() (string, error) {
bootstrapConfigMu.Lock()
cfg, err := readBootstrapConfig()
bootstrapConfigMu.Unlock()
if err != nil {
return "", err
}
directory := strings.TrimSpace(cfg.SavedQueryDirectory)
if directory == "" {
return "", nil
}
abs, err := filepath.Abs(directory)
if err != nil {
return "", err
}
return filepath.Clean(abs), nil
}
func ResolveSavedQueryDirectory(activeRoot string) (string, error) {
directory, err := ResolveConfiguredSavedQueryDirectory()
if err != nil {
return "", err
}
if directory != "" {
return directory, nil
}
return DefaultSavedQueryDirectory(activeRoot), nil
}
func SetConfiguredSavedQueryDirectory(directory string) (string, error) {
target := strings.TrimSpace(directory)
if target != "" {
abs, resolveErr := filepath.Abs(target)
if resolveErr != nil {
return "", resolveErr
}
target = filepath.Clean(abs)
if mkdirErr := os.MkdirAll(target, 0o755); mkdirErr != nil {
return "", mkdirErr
}
probe, createErr := os.CreateTemp(target, savedQueryDirectoryProbePrefix)
if createErr != nil {
return "", createErr
}
probePath := probe.Name()
if closeErr := probe.Close(); closeErr != nil {
_ = os.Remove(probePath)
return "", closeErr
}
if removeErr := os.Remove(probePath); removeErr != nil {
return "", removeErr
}
}
if err := updateBootstrapConfig(func(cfg *bootstrapConfig) {
cfg.SavedQueryDirectory = target
}); err != nil {
return "", err
}
return target, nil
}