feat: 添加环境变量管理工具和部署配置改版
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user