跳转到内容

配置管理规范

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:环境特定配置覆盖默认配置,遵循”约定优于配置”原则。
  • 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/
  • Standard:按优先级选择以下方式注入密钥:
方式适用场景说明
密钥管理服务(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 填入实际值

  • Standard:配置变更遵循以下流程:
提出变更 → 评估影响 → 修改配置 → 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"

project/
├── src/main/resources/
│ ├── application.yml # 默认共享配置
│ ├── application-dev.yml # 开发环境
│ ├── application-test.yml # 测试环境
│ ├── application-staging.yml # 预发布环境
│ └── application-prod.yml # 生产环境(仅含占位符)
├── .env.example # 环境变量模板(提交到仓库)
├── .env # 本地环境变量(不提交)
└── config/
└── logback-spring.xml # 日志配置
project/
├── .env # 本地环境变量(不提交)
├── .env.example # 环境变量模板(提交)
├── .env.development # 开发环境变量(提交)
├── .env.staging # 预发布环境变量(提交)
├── .env.production # 生产环境变量(提交,仅含非敏感项)
├── src/
│ └── config/
│ ├── index.ts # 配置统一入口
│ └── env.ts # 环境变量读取与校验
// 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 天)