diff --git a/supply-api/config/config.dev.yaml b/supply-api/config/config.dev.yaml new file mode 100644 index 00000000..dd8ed293 --- /dev/null +++ b/supply-api/config/config.dev.yaml @@ -0,0 +1,37 @@ +# Supply API Development Configuration +server: + addr: ":18082" + read_timeout: 10s + write_timeout: 15s + idle_timeout: 30s + shutdown_timeout: 5s + +database: + host: "localhost" + port: 5432 + user: "postgres" + password: "" + database: "supply_db" + max_open_conns: 25 + max_idle_conns: 5 + conn_max_lifetime: 1h + conn_max_idle_time: 10m + +redis: + host: "localhost" + port: 6379 + password: "" + db: 0 + pool_size: 10 + +token: + secret_key: "${SUPPLY_TOKEN_SECRET_KEY}" + issuer: "lijiaoqiao/supply-api" + access_token_ttl: 1h + refresh_token_ttl: 168h + revocation_cache_ttl: 30s + +audit: + buffer_size: 1000 + flush_interval: 5s + export_timeout: 30s diff --git a/supply-api/config/sms.example.yaml b/supply-api/config/sms.example.yaml new file mode 100644 index 00000000..a388dc97 --- /dev/null +++ b/supply-api/config/sms.example.yaml @@ -0,0 +1,47 @@ +# SMS Service Configuration +# 短信服务配置 + +sms: + # 是否启用SMS服务;默认 false 时运行时会拒绝验证码校验 + enabled: false + + # SMS服务提供商: tencent (腾讯云) 或 aliyun (阿里云) + provider: tencent + + # 运行时读取扁平字段,而不是按 provider 分组的嵌套字段 + app_id: "1400123456" # 腾讯云: SDK AppID;阿里云: AccessKey ID + app_secret: "your_app_key" # 腾讯云: AppKey;阿里云: AccessKey Secret + sign_name: "供应链平台" # 短信签名 + template_code: "SMS_123456789" # 短信模板 CODE + region: "ap-guangzhou" # 腾讯云区域;阿里云可忽略 + endpoint: "" # 自定义 endpoint,可选 + + # 通用配置 + code_length: 6 # 验证码长度,默认6位 + code_expire_mins: 5 # 验证码有效期(分钟),默认5分钟 + +# 示例:启用腾讯云SMS +# sms: +# enabled: true +# provider: tencent +# app_id: "1400123456" +# app_secret: "a1b2c3d4e5f6g7h8i9j0" +# sign_name: "供应链平台" +# template_code: "SMS_123456789" +# region: "ap-guangzhou" +# code_length: 6 +# code_expire_mins: 5 + +# 示例:启用阿里云SMS +# sms: +# enabled: true +# provider: aliyun +# app_id: "LTAI5tXXXXXXXXXXXX" +# app_secret: | +# -----BEGIN PRIVATE KEY----- +# MIIEvQIBADANBgkqhkiG9w0BAQEFAASC... +# -----END PRIVATE KEY----- +# sign_name: "供应链平台" +# template_code: "SMS_123456789" +# code_length: 6 +# code_expire_mins: 5 diff --git a/supply-api/internal/config/config_samples_test.go b/supply-api/internal/config/config_samples_test.go new file mode 100644 index 00000000..dba7cf89 --- /dev/null +++ b/supply-api/internal/config/config_samples_test.go @@ -0,0 +1,31 @@ +package config + +import ( + "path/filepath" + "testing" +) + +func TestDevSampleConfigLoads(t *testing.T) { + configPath := filepath.Join("..", "..", "config", "config.dev.yaml") + + cfg, err := LoadFromPath("dev", configPath) + if err != nil { + t.Fatalf("expected dev sample config to load, got error: %v", err) + } + + if cfg.Server.Addr != ":18082" { + t.Fatalf("expected addr :18082, got %s", cfg.Server.Addr) + } + if cfg.Database.Host != "localhost" { + t.Fatalf("expected database host localhost, got %s", cfg.Database.Host) + } + if cfg.Redis.Host != "localhost" { + t.Fatalf("expected redis host localhost, got %s", cfg.Redis.Host) + } + if cfg.Token.Issuer != "lijiaoqiao/supply-api" { + t.Fatalf("expected issuer lijiaoqiao/supply-api, got %s", cfg.Token.Issuer) + } + if cfg.Settlement.WithdrawEnabled { + t.Fatal("expected dev sample config to keep withdraw disabled by default") + } +} diff --git a/supply-api/internal/sms/config_example_test.go b/supply-api/internal/sms/config_example_test.go new file mode 100644 index 00000000..d0b79aba --- /dev/null +++ b/supply-api/internal/sms/config_example_test.go @@ -0,0 +1,45 @@ +package sms + +import ( + "os" + "path/filepath" + "testing" + + "gopkg.in/yaml.v3" +) + +type smsExampleRoot struct { + SMS Config `yaml:"sms"` +} + +func TestSMSExampleYAML_MatchesRuntimeConfigShape(t *testing.T) { + path := filepath.Join("..", "..", "config", "sms.example.yaml") + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed to read sms example config: %v", err) + } + + var root smsExampleRoot + if err := yaml.Unmarshal(content, &root); err != nil { + t.Fatalf("failed to parse sms example config: %v", err) + } + + if root.SMS.Provider != ProviderTencent { + t.Fatalf("expected provider %q, got %q", ProviderTencent, root.SMS.Provider) + } + if root.SMS.CodeLength != 6 { + t.Fatalf("expected code length 6, got %d", root.SMS.CodeLength) + } + if root.SMS.CodeExpireMins != 5 { + t.Fatalf("expected code expiry 5, got %d", root.SMS.CodeExpireMins) + } + if root.SMS.SignName == "" { + t.Fatal("expected sign_name to be populated in example config") + } + if root.SMS.TemplateCode == "" { + t.Fatal("expected template_code to be populated in example config") + } + if root.SMS.Region == "" { + t.Fatal("expected region to be populated in example config") + } +}