feat: 添加环境变量管理工具和部署配置改版
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s

主要改动:
- 新增交互式环境配置脚本 (scripts/setup-env.sh)
- 新增本地启动快捷脚本 (scripts/run-local.sh)
- 新增环境变量模板文件 (.env.example)
- 部署模式改版:使用嵌入式配置 + 环境变量覆盖
- 添加对象存储功能支持
- 改进 IoT 卡片导入任务
- 优化 OpenAPI 文档生成
- 删除旧的配置文件,改用嵌入式默认配置
This commit is contained in:
2026-01-26 10:28:29 +08:00
parent 194078674a
commit 45aa7deb87
94 changed files with 6532 additions and 1967 deletions

View File

@@ -0,0 +1,69 @@
package bootstrap
import (
"fmt"
"os"
"path/filepath"
"github.com/break/junhong_cmp_fiber/pkg/config"
"go.uber.org/zap"
)
type DirectoryResult struct {
TempDir string
AppLogDir string
AccessLogDir string
Fallbacks []string
}
func EnsureDirectories(cfg *config.Config, logger *zap.Logger) (*DirectoryResult, error) {
result := &DirectoryResult{}
directories := []struct {
path string
configKey string
resultPtr *string
}{
{cfg.Storage.TempDir, "storage.temp_dir", &result.TempDir},
{filepath.Dir(cfg.Logging.AppLog.Filename), "logging.app_log.filename", &result.AppLogDir},
{filepath.Dir(cfg.Logging.AccessLog.Filename), "logging.access_log.filename", &result.AccessLogDir},
}
for _, dir := range directories {
if dir.path == "" || dir.path == "." {
continue
}
actualPath, fallback, err := ensureDirectory(dir.path, logger)
if err != nil {
return nil, fmt.Errorf("创建目录 %s (%s) 失败: %w", dir.path, dir.configKey, err)
}
*dir.resultPtr = actualPath
if fallback {
result.Fallbacks = append(result.Fallbacks, actualPath)
}
}
return result, nil
}
func ensureDirectory(path string, logger *zap.Logger) (actualPath string, fallback bool, err error) {
if err := os.MkdirAll(path, 0755); err != nil {
if os.IsPermission(err) {
fallbackPath := filepath.Join(os.TempDir(), "junhong", filepath.Base(path))
if mkErr := os.MkdirAll(fallbackPath, 0755); mkErr != nil {
return "", false, fmt.Errorf("原路径 %s 权限不足,降级路径 %s 也创建失败: %w", path, fallbackPath, mkErr)
}
if logger != nil {
logger.Warn("目录权限不足,使用降级路径",
zap.String("original", path),
zap.String("fallback", fallbackPath),
)
}
return fallbackPath, true, nil
}
return "", false, err
}
return path, false, nil
}

View File

@@ -0,0 +1,100 @@
package bootstrap
import (
"os"
"path/filepath"
"testing"
"github.com/break/junhong_cmp_fiber/pkg/config"
)
func TestEnsureDirectories_Success(t *testing.T) {
tmpDir := t.TempDir()
cfg := &config.Config{
Storage: config.StorageConfig{
TempDir: filepath.Join(tmpDir, "storage"),
},
Logging: config.LoggingConfig{
AppLog: config.LogRotationConfig{Filename: filepath.Join(tmpDir, "logs", "app.log")},
AccessLog: config.LogRotationConfig{Filename: filepath.Join(tmpDir, "logs", "access.log")},
},
}
result, err := EnsureDirectories(cfg, nil)
if err != nil {
t.Fatalf("EnsureDirectories() 失败: %v", err)
}
if result.TempDir != cfg.Storage.TempDir {
t.Errorf("TempDir 期望 %s, 实际 %s", cfg.Storage.TempDir, result.TempDir)
}
if result.AppLogDir != filepath.Join(tmpDir, "logs") {
t.Errorf("AppLogDir 期望 %s, 实际 %s", filepath.Join(tmpDir, "logs"), result.AppLogDir)
}
if _, err := os.Stat(result.TempDir); os.IsNotExist(err) {
t.Error("TempDir 目录未创建")
}
if _, err := os.Stat(result.AppLogDir); os.IsNotExist(err) {
t.Error("AppLogDir 目录未创建")
}
}
func TestEnsureDirectories_ExistingDirs(t *testing.T) {
tmpDir := t.TempDir()
storageDir := filepath.Join(tmpDir, "storage")
os.MkdirAll(storageDir, 0755)
cfg := &config.Config{
Storage: config.StorageConfig{TempDir: storageDir},
Logging: config.LoggingConfig{
AppLog: config.LogRotationConfig{Filename: filepath.Join(tmpDir, "logs", "app.log")},
AccessLog: config.LogRotationConfig{Filename: filepath.Join(tmpDir, "logs", "access.log")},
},
}
result, err := EnsureDirectories(cfg, nil)
if err != nil {
t.Fatalf("EnsureDirectories() 失败: %v", err)
}
if result.TempDir != storageDir {
t.Errorf("已存在目录应返回原路径")
}
}
func TestEnsureDirectories_EmptyPaths(t *testing.T) {
cfg := &config.Config{
Storage: config.StorageConfig{TempDir: ""},
Logging: config.LoggingConfig{
AppLog: config.LogRotationConfig{Filename: ""},
AccessLog: config.LogRotationConfig{Filename: ""},
},
}
result, err := EnsureDirectories(cfg, nil)
if err != nil {
t.Fatalf("EnsureDirectories() 空路径时不应失败: %v", err)
}
if len(result.Fallbacks) != 0 {
t.Error("空路径不应产生降级")
}
}
func TestEnsureDirectory_Fallback(t *testing.T) {
path, fallback, err := ensureDirectory("/root/no_permission_dir_test_"+t.Name(), nil)
if err != nil {
if os.Getuid() == 0 {
t.Skip("以 root 身份运行,跳过权限测试")
}
t.Skip("无法测试权限降级场景")
}
if fallback {
if !filepath.HasPrefix(path, os.TempDir()) {
t.Errorf("降级路径应在临时目录下,实际: %s", path)
}
os.RemoveAll(path)
}
}