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

@@ -3,6 +3,7 @@ package config
import (
"errors"
"fmt"
"strings"
"sync/atomic"
"time"
)
@@ -21,6 +22,7 @@ type Config struct {
SMS SMSConfig `mapstructure:"sms"`
JWT JWTConfig `mapstructure:"jwt"`
DefaultAdmin DefaultAdminConfig `mapstructure:"default_admin"`
Storage StorageConfig `mapstructure:"storage"`
}
// ServerConfig HTTP 服务器配置
@@ -120,7 +122,61 @@ type DefaultAdminConfig struct {
Phone string `mapstructure:"phone"`
}
// Validate 验证配置
// StorageConfig 对象存储配置
type StorageConfig struct {
Provider string `mapstructure:"provider"` // 存储提供商s3
S3 S3Config `mapstructure:"s3"` // S3 兼容存储配置
Presign PresignConfig `mapstructure:"presign"` // 预签名 URL 配置
TempDir string `mapstructure:"temp_dir"` // 临时文件目录
}
// S3Config S3 兼容存储配置
type S3Config struct {
Endpoint string `mapstructure:"endpoint"` // 服务端点http://obs-helf.cucloud.cn
Region string `mapstructure:"region"` // 区域cn-langfang-2
Bucket string `mapstructure:"bucket"` // 存储桶名称
AccessKeyID string `mapstructure:"access_key_id"` // 访问密钥 ID
SecretAccessKey string `mapstructure:"secret_access_key"` // 访问密钥
UseSSL bool `mapstructure:"use_ssl"` // 是否使用 SSL
PathStyle bool `mapstructure:"path_style"` // 是否使用路径风格(兼容性)
}
// PresignConfig 预签名 URL 配置
type PresignConfig struct {
UploadExpires time.Duration `mapstructure:"upload_expires"` // 上传 URL 有效期默认15m
DownloadExpires time.Duration `mapstructure:"download_expires"` // 下载 URL 有效期默认24h
}
type requiredField struct {
value string
name string
envName string
}
func (c *Config) ValidateRequired() error {
fields := []requiredField{
{c.Database.Host, "database.host", "JUNHONG_DATABASE_HOST"},
{c.Database.User, "database.user", "JUNHONG_DATABASE_USER"},
{c.Database.Password, "database.password", "JUNHONG_DATABASE_PASSWORD"},
{c.Database.DBName, "database.dbname", "JUNHONG_DATABASE_DBNAME"},
{c.Redis.Address, "redis.address", "JUNHONG_REDIS_ADDRESS"},
{c.JWT.SecretKey, "jwt.secret_key", "JUNHONG_JWT_SECRET_KEY"},
}
var missing []string
for _, f := range fields {
if f.value == "" {
missing = append(missing, fmt.Sprintf(" - %s (环境变量: %s)", f.name, f.envName))
}
}
if len(missing) > 0 {
return fmt.Errorf("缺少必填配置项:\n%s", strings.Join(missing, "\n"))
}
return nil
}
func (c *Config) Validate() error {
// 服务器验证
if c.Server.Address == "" {
@@ -184,28 +240,24 @@ func (c *Config) Validate() error {
return fmt.Errorf("invalid configuration: middleware.rate_limiter.storage: invalid storage type (current value: %s, expected: memory or redis)", c.Middleware.RateLimiter.Storage)
}
// 短信服务验证
if c.SMS.GatewayURL == "" {
return fmt.Errorf("invalid configuration: sms.gateway_url: must be non-empty (current value: empty)")
}
if c.SMS.Username == "" {
return fmt.Errorf("invalid configuration: sms.username: must be non-empty (current value: empty)")
}
if c.SMS.Password == "" {
return fmt.Errorf("invalid configuration: sms.password: must be non-empty (current value: empty)")
}
if c.SMS.Signature == "" {
return fmt.Errorf("invalid configuration: sms.signature: must be non-empty (current value: empty)")
}
if c.SMS.Timeout < 5*time.Second || c.SMS.Timeout > 60*time.Second {
return fmt.Errorf("invalid configuration: sms.timeout: duration out of range (current value: %s, expected: 5s-60s)", c.SMS.Timeout)
// 短信服务验证(可选,配置 GatewayURL 时才验证其他字段)
if c.SMS.GatewayURL != "" {
if c.SMS.Username == "" {
return fmt.Errorf("invalid configuration: sms.username: must be non-empty when gateway_url is configured")
}
if c.SMS.Password == "" {
return fmt.Errorf("invalid configuration: sms.password: must be non-empty when gateway_url is configured")
}
if c.SMS.Signature == "" {
return fmt.Errorf("invalid configuration: sms.signature: must be non-empty when gateway_url is configured")
}
if c.SMS.Timeout > 0 && (c.SMS.Timeout < 5*time.Second || c.SMS.Timeout > 60*time.Second) {
return fmt.Errorf("invalid configuration: sms.timeout: duration out of range (current value: %s, expected: 5s-60s)", c.SMS.Timeout)
}
}
// JWT 验证
if c.JWT.SecretKey == "" {
return fmt.Errorf("invalid configuration: jwt.secret_key: must be non-empty (current value: empty)")
}
if len(c.JWT.SecretKey) < 32 {
// JWT 验证SecretKey 必填验证在 ValidateRequired 中处理)
if len(c.JWT.SecretKey) > 0 && len(c.JWT.SecretKey) < 32 {
return fmt.Errorf("invalid configuration: jwt.secret_key: secret key too short (current length: %d, expected: >= 32)", len(c.JWT.SecretKey))
}
if c.JWT.TokenDuration < 1*time.Hour || c.JWT.TokenDuration > 720*time.Hour {