Settings 与 States 分离、环境区分、密钥管理
- Requirement:必须严格区分 Settings(配置) 与 States(状态),禁止混存。
| 维度 | Settings(配置) | States(状态) |
|---|
| 定义 | 应用启动时加载的静态参数 | 运行时动态产生/变化的数据 |
| 生命周期 | 随应用启动加载,运行期间不变 | 随业务变化,持久化存储 |
| 存储位置 | 配置文件 / 环境变量 / 配置中心 | 数据库 / 缓存 / 消息队列 |
| 修改方式 | 重启应用或热加载配置 | 通过业务接口修改 |
| 示例 | 数据库连接串、端口、超时时间 | 用户会话、订单状态、计数器 |
| 类别 | 说明 | 存储方式 |
|---|
| 构建时配置 | 编译期确定,如版本号、构建时间 | 编译时注入 |
| 部署时配置 | 环境相关,如数据库地址、端口 | 环境变量 / 配置文件 |
| 运行时配置 | 可动态调整,如功能开关、限流阈值 | 配置中心(Nacos / Apollo / etcd) |
- ❌ 将用户偏好、业务状态写入配置文件。
- ❌ 配置中包含运行时计算的数据(如当前时间戳)。
- ❌ 将数据库连接池中的活跃连接数作为配置项。
- Standard:至少划分以下 4 个环境,每个环境独立配置:
| 环境 | 用途 | 配置文件 | 数据源 |
|---|
dev | 本地开发 | application-dev.yml | 本地 / 共享开发库 |
test | 集成测试 | application-test.yml | 测试环境数据库 |
staging / pre | 预发布验证 | application-staging.yml | 预发布环境(与生产同构) |
prod | 生产环境 | application-prod.yml | 生产数据库 |
- Requirement:配置文件按环境命名,格式为
application-<env>.yml / .env.<env>。
- Requirement:默认配置文件
application.yml / .env 存放各环境共享的默认值。
- Standard:环境特定配置覆盖默认配置,遵循”约定优于配置”原则。
环境变量 > 命令行参数 > 环境配置文件 > 默认配置文件 > 代码默认值
# application.yml(默认共享配置)
server:
port: 8080
spring:
application:
name: user-service
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_dev
username: root
password: root
redis:
host: localhost
port: 6379
# application-prod.yml
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
- Standard:前端通过
.env 文件管理环境变量,变量名必须以 VITE_ / REACT_APP_ 前缀开头。
# .env.development
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_ENV=development
# .env.production
VITE_API_BASE_URL=https://api.example.com/api
VITE_APP_ENV=production
// 使用
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
- Caution:前端环境变量会被打包到产物中,禁止存放密钥等敏感信息。
- Requirement:密钥、密码、Token、证书等敏感信息 禁止 明文写入代码或配置文件。
- Requirement:
.env 文件必须加入 .gitignore,仓库中仅保留 .env.example 模板。
# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key
secrets/
| 方式 | 适用场景 | 说明 |
|---|
| 密钥管理服务(KMS) | 生产环境 | AWS KMS、阿里云 KMS、HashiCorp Vault |
| 环境变量 | 容器化部署 | 通过 K8s Secret / Docker Secret 注入 |
| 配置中心加密 | 集中配置 | Nacos / Apollo 加密配置 |
本地 .env | 开发环境 | 不提交到仓库 |
- Standard:配置文件中使用占位符引用环境变量,禁止明文写入。
# application-prod.yml
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD} # 从环境变量注入
- Standard:生产环境密钥必须定期轮换(建议 ≤ 90 天)。
- Standard:密钥轮换需支持双密钥过渡期,避免服务中断。
- Caution:密钥轮换后,旧密钥必须立即作废并从所有配置中移除。
- Standard:仓库提供
.env.example 模板,开发者复制为 .env 后填入本地值。
# .env.example
DB_HOST=localhost
DB_PORT=5432
DB_NAME=user_dev
DB_USERNAME=root
DB_PASSWORD=change_me
# 本地使用
cp .env.example .env
# 编辑 .env 填入实际值
提出变更 → 评估影响 → 修改配置 → Code Review → 测试验证 → 灰度发布 → 全量生效
- Requirement:生产环境配置变更必须经过审批,禁止直接修改生产配置。
- Standard:配置变更需记录变更日志(谁、何时、改了什么、为什么)。
- Standard:支持热加载的配置(如功能开关、限流阈值)通过配置中心动态推送。
- Caution:数据库连接、端口等基础设施配置不支持热加载,需重启服务。
- Standard:热加载配置需提供降级方案,配置中心不可用时使用本地缓存配置。
- Standard:应用启动时校验配置完整性,缺失关键配置时启动失败并输出明确错误信息。
- Standard:使用配置 Schema 校验(如 Pydantic、JSON Schema、Spring Boot Configuration Properties Validation)。
Python Pydantic 示例:
from pydantic import BaseSettings, Field, validator
class Settings(BaseSettings):
app_name: str = "user-service"
environment: str = Field(..., regex="^(dev|test|staging|prod)$")
db_url: str
db_pool_size: int = Field(default=10, ge=1, le=100)
redis_host: str
redis_port: int = Field(default=6379, ge=1, le=65535)
@validator("db_url")
def validate_db_url(cls, v):
if not v.startswith(("postgresql://", "mysql://")):
raise ValueError("db_url must be a valid database URL")
return v
class Config:
env_file = ".env"
│ ├── application.yml # 默认共享配置
│ ├── application-dev.yml # 开发环境
│ ├── application-test.yml # 测试环境
│ ├── application-staging.yml # 预发布环境
│ └── application-prod.yml # 生产环境(仅含占位符)
├── .env.example # 环境变量模板(提交到仓库)
└── logback-spring.xml # 日志配置
├── .env.example # 环境变量模板(提交)
├── .env.development # 开发环境变量(提交)
├── .env.staging # 预发布环境变量(提交)
├── .env.production # 生产环境变量(提交,仅含非敏感项)
// src/config/index.ts
const required = (key: string): string => {
const value = import.meta.env[key];
if (!value) {
throw new Error(`Missing required env variable: ${key}`);
}
return value;
};
export const config = {
apiBaseUrl: required('VITE_API_BASE_URL'),
appEnv: import.meta.env.VITE_APP_ENV ?? 'development',
enableMock: import.meta.env.VITE_ENABLE_MOCK === 'true',
sentryDsn: import.meta.env.VITE_SENTRY_DSN,
} as const;
export type AppConfig = typeof config;
// config/config.go
package config
import (
"os"
"strconv"
)
type Config struct {
AppName string
Environment string
DBURL string
RedisHost string
RedisPort int
}
func Load() (*Config, error) {
cfg := &Config{
AppName: getEnv("APP_NAME", "user-service"),
Environment: getEnv("ENVIRONMENT", "dev"),
DBURL: os.Getenv("DB_URL"),
RedisHost: getEnv("REDIS_HOST", "localhost"),
RedisPort: getEnvInt("REDIS_PORT", 6379),
}
if cfg.DBURL == "" {
return nil, fmt.Errorf("DB_URL is required")
}
return cfg, nil
}
func getEnv(key, defaultValue string) string {
if v := os.Getenv(key); v != "" {
return v
}
return defaultValue
}
func getEnvInt(key string, defaultValue int) int {
if v := os.Getenv(key); v != "" {
if i, err := strconv.Atoi(v); err == nil {
return i
}
}
return defaultValue
}
| 反模式 | 问题 | 建议 |
|---|
| 密钥明文写入配置文件 | 密钥泄露风险 | 使用环境变量 / KMS |
.env 提交到 Git 仓库 | 密钥泄露 | 加入 .gitignore,仅提交 .env.example |
| 配置与状态混存 | 难以维护、配置膨胀 | 严格分离,状态存数据库 |
| 不同环境共用同一数据库 | 数据污染 | 各环境独立数据源 |
| 生产配置直接在代码中修改 | 无审计、易出错 | 通过配置中心或 CI 注入 |
| 配置无校验 | 启动失败或运行时异常 | 启动时 Schema 校验 |
| 前端打包敏感信息 | 密钥暴露在客户端 | 密钥只存后端,前端仅存非敏感配置 |
| 密钥长期不轮换 | 泄露后影响大 | 定期轮换(≤ 90 天) |