
【基于 SwooleHyperf 的微服务实战】 第四周·周四多环境配置管理与配置加密今天我们进入的主题是多环境配置管理与配置加密。在实际微服务项目中开发、测试、生产环境的配置各不相同且敏感信息如数据库密码、API 密钥绝不能以明文形式暴露。昨天我们已经将配置迁移到了 Nacos今天将在此基础上实现多环境隔离和配置加密让应用在任意环境下都能安全、正确地运行做到“一处打包处处运行”。今日目标理解多环境配置的常见模式使用 Nacos命名空间实现 dev/test/prod 环境隔离。掌握让 Hyperf 根据环境变量动态选择 Nacos 配置集的技巧。学习配置加密的核心思路使用AES 加密保护数据库密码等敏感项。编写一个配置解密服务在应用启动时自动解密加密字段使应用无感使用。验证多环境切换效果通过修改APP_ENV即可加载不同 Nacos 配置敏感信息全程密文存储。一、环境准备约 20 分钟继续使用hyperf-app项目和 Docker 环境确保 Nacos、Consul 等服务已启动。docker-composeexecswoolebashcd/var/www/hyperf-app今天我们需要在 Nacos 中创建多个命名空间并为每个环境准备不同的配置。同时为了演示加密我们会生成一个 AES 密钥用于加解密。二、知识核心多环境策略与配置加密原理约 1 小时1. 多环境配置管理方法常见有三种模式多文件法.env.dev、.env.prod启动时通过环境变量指定。不利于集中管理。配置中心分组Nacos 提供Namespace命名空间每个空间有独立的配置列表。不同环境使用不同 Namespace服务通过namespace_id自动加载对应配置。Data ID 前缀法同一个 Namespace 下通过 Data ID 携带环境后缀如hyperf-app-dev、hyperf-app-prod然后根据APP_ENV动态拼接。推荐使用Namespace因为它能物理隔离配置还可以结合权限控制生产环境配置仅运维可见。2. 配置加密的重要性配置文件中的密码、Token、证书等一旦泄露后果严重。配置加密要做到存储加密提交到 Git 或配置中心的是密文即使被看到也无法直接使用。透明解密应用启动时自动解密业务代码不用修改。密钥管理解密密钥不能与密文存放在一起可通过环境变量、密钥管理服务如 KMS或容器注入。我们将演示一个轻量方案自定义加密/解密函数使用 AES-256-CBC 算法密钥通过环境变量注入绝不写入配置文件。三、实战多环境隔离与加密存储约 2.5 小时步骤 1在 Nacos 中创建 dev 和 prod 命名空间打开 Nacos 控制台http://localhost:8848/nacos进入左侧菜单命名空间。点击新建命名空间命名空间名dev命名空间ID输入自定义 ID如dev-001或者自动生成但我们需要记住它。再次新建prod命名空间ID 自定如prod-001。创建完成后在命名空间列表能看到两个空间及各自的 ID。重要复制两个命名空间的 ID后面配置要用。步骤 2在各命名空间中创建同名 Data ID进入配置列表通过右上角切换到dev命名空间。新建配置Data ID:hyperf-app与昨天一致配置内容dev 环境{app_name:Hyperf-Dev,database:{default:{host:mysql,port:3306,database:hyperf_blog_dev,username:dev_user,password:ENC(加密后的密码)}},redis:{default:{host:redis,password:}}}切换到prod命名空间创建同名 Data IDhyperf-app内容{app_name:Hyperf-Prod,database:{default:{host:mysql-prod.internal,port:3306,database:hyperf_blog_prod,username:prod_user,password:ENC(另一种加密)}},redis:{default:{host:redis.prod.internal,password:ENC(redis密码密文)}}}注意password字段我们使用了ENC(...)占位符表示该值需要解密。步骤 3修改 config_center.php 支持动态命名空间编辑config/autoload/config_center.php使namespace_id根据环境变量决定?phpreturn[driverHyperf\ConfigNacos\NacosDriver::class,client[hostnacos,port8848,usernamenacos,passwordnacos,],config[data_idhyperf-app,groupDEFAULT_GROUP,namespace_idenv(NACOS_NAMESPACE_ID,),// 通过环境变量注入typejson,],listener[enabletrue,interval3,],];在.env中增加# 本地开发使用 dev 命名空间 NACOS_NAMESPACE_IDdev-001如果切换到生产环境只需修改该值为prod-001实际部署时通过环境变量注入不要写在.env中。步骤 4实现配置解密服务我们创建一个工具类App\Helpers\ConfigDecryptor负责在配置加载后扫描包含ENC(...)的值并解密。生成 AES 密钥示例生产需安全生成php-recho base64_encode(random_bytes(32));# 输出类似 6a7b... 的 Base64 字符串将这个密钥通过环境变量注入绝不能写在代码里在.env中添加CONFIG_ENCRYPT_KEY你的Base64密钥创建app/Helpers/ConfigDecryptor.php?phpnamespaceApp\Helpers;useHyperf\Contract\ConfigInterface;useHyperf\Di\Annotation\Inject;useHyperf\Utils\ApplicationContext;classConfigDecryptor{privatestring$key;privatestring$cipheraes-256-cbc;publicfunction__construct(){$this-keybase64_decode(env(CONFIG_ENCRYPT_KEY,));if(strlen($this-key)!32){thrownew\RuntimeException(AES key must be 32 bytes (256 bits).);}}/** * 解密配置数组中所有以 ENC() 包裹的值 */publicfunctiondecryptArray(array$config):array{array_walk_recursive($config,function($value){if(is_string($value)str_starts_with($value,ENC()str_ends_with($value,))){$encryptedsubstr($value,4,-1);$value$this-decrypt($encrypted);}});return$config;}publicfunctionencrypt(string$plaintext):string{$ivrandom_bytes(openssl_cipher_iv_length($this-cipher));$encryptedopenssl_encrypt($plaintext,$this-cipher,$this-key,OPENSSL_RAW_DATA,$iv);returnbase64_encode($iv.$encrypted);}privatefunctiondecrypt(string$payload):string{$database64_decode($payload);$ivLengthopenssl_cipher_iv_length($this-cipher);$ivsubstr($data,0,$ivLength);$encryptedsubstr($data,$ivLength);returnopenssl_decrypt($encrypted,$this-cipher,$this-key,OPENSSL_RAW_DATA,$iv);}}步骤 5在配置加载后触发解密Hyperf 在配置中心拉取配置后会触发一个事件Hyperf\ConfigCenter\Event\ConfigChanged或类似我们可以监听该事件对配置进行后处理。更简单的方法是在 Nacos 驱动加载配置后会合并到ConfigInterface我们可以通过一个BootApplication监听器来解密配置。创建app/Listener/DecryptConfigListener.php?phpnamespaceApp\Listener;useApp\Helpers\ConfigDecryptor;useHyperf\ConfigCenter\Event\ConfigChanged;useHyperf\Contract\ConfigInterface;useHyperf\Event\Annotation\Listener;useHyperf\Event\Contract\ListenerInterface;useHyperf\Di\Annotation\Inject;#[Listener]classDecryptConfigListenerimplementsListenerInterface{#[Inject]privateConfigInterface$config;#[Inject]privateConfigDecryptor$decryptor;publicfunctionlisten():array{return[ConfigChanged::class,];}publicfunctionprocess(object$event){// 获取所有配置解密后重新设置// 注意$config-all() 可能不存在这里假设可以获取所有配置// 更实际的做法是只处理数据库、redis 等关键配置避免全量$dbConfig$this-config-get(databases.default,[]);if(!empty($dbConfig)){$decrypted$this-decryptor-decryptArray($dbConfig);$this-config-set(databases.default,$decrypted);}$redisConfig$this-config-get(redis.default,[]);if(!empty($redisConfig)){$decrypted$this-decryptor-decryptArray($redisConfig);$this-config-set(redis.default,$decrypted);}// 也可以处理其他配置项...}}由于ConfigChanged事件可能需要配置中心组件触发如果未触发我们也可以在BootApplication事件中执行一次初始化解密。为了确保数据库密码在连接池创建前被解密建议在BeforeMainServerStart或OnWorkerStart事件中处理。这里为了简单我们直接在config_center.php的merge_mode配合自定义配置解析器或者我们在ConfigDecryptor中采用更直接的方法在config/autoload/databases.php中不写真实密码而是从环境变量中读然后结合解密逻辑。但这样违背了配置中心集中管理的理念。一个更稳健的实战方案只在 Nacos 中存储密文在数据库配置文件中通过环境变量DB_PASSWORD传递明文而DB_PASSWORD又由外部注入解密后的值。不过我们今天是教学环境可以演示在应用层解密配置。我们采用监听BootApplication的方式确保数据库配置在连接池启动前被解密。修改DecryptConfigListeneruseHyperf\Framework\Event\BootApplication;#[Listener]classDecryptConfigListenerimplementsListenerInterface{publicfunctionlisten():array{return[BootApplication::class,];}publicfunctionprocess(object$event){// 处理 databases 和 redis 配置$dbKeydatabases;$redisKeyredis;// ...}}但这样只能解密一次后续 Nacos 更新不会自动解密。为了完美需要监听配置中心的OnPipeMessage或ConfigChanged事件。在 Hyperf 3.0 中配置中心驱动的拉取流程中会触发ConfigChanged我们可以复用。步骤 6加密实际密码并更新 Nacos利用ConfigDecryptor的encrypt方法加密真实密码。编写一个简单的命令行脚本bin/encrypt-config.php#!/usr/bin/env php?phpuseApp\Helpers\ConfigDecryptor;require__DIR__./../vendor/autoload.php;$decryptornewConfigDecryptor();echo加密 dev_password: .$decryptor-encrypt(dev_password).PHP_EOL;echo加密 prod_password: .$decryptor-encrypt(prod_password).PHP_EOL;运行得到密文将其填入 Nacos 配置的password字段格式为ENC(密文)。步骤 7验证多环境切换与解密重启服务确保APP_ENV对应 dev 且NACOS_NAMESPACE_IDdev-001php bin/hyperf.php start访问任意需要数据库的接口系统正常连接数据库数据库密码是解密后的明文。如果我们在config/autoload/databases.php中仍保留静态配置需要注意优先级Nacos 配置会覆盖本地配置所以本地可以不写真实密码。通过config()函数查看数据库密码是否已解密为明文注意安全仅测试用在控制器中临时添加return[db_passconfig(databases.default.password)];应返回明文密码证明解密成功。现在修改.env中NACOS_NAMESPACE_ID为prod-001重启服务。如果 prod 命名空间中的数据库指向不同的服务器则应用将连接到生产库。当然我们只是演示配置切换实际不连生产库。四、成果测试与验证约 1 小时测试清单检验项方法通过标准Nacos 多命名空间创建Nacos 控制台查看存在 dev 和 prod 两个空间各有配置环境变量切换修改.env中NACOS_NAMESPACE_ID重启服务服务加载对应空间的配置可通过测试接口查看app_name配置加密存储Nacos 控制台查看密码字段显示为ENC(密文)形式非明文应用解密成功访问测试接口查看数据库密码字段或连接成功密码为明文数据库连接正常密钥安全CONFIG_ENCRYPT_KEY仅存在于.env不提交到 Git代码中无硬编码检查 Git 提交不包含密钥配置更新解密修改 Nacos 中加密字段的密文等待服务刷新后再次请求新密码生效数据库连接可能需重连但配置已更新常见问题解密失败确保密钥长度 32 字节Base64 编码正确。密文格式ENC(...)解析无误。数据库连接失败解密后密码可能仍不正确检查加密时原始密码。命名空间 ID 错误确保.env中的 ID 与 Nacos 命名空间 ID 完全一致。配置覆盖顺序如果本地databases.php中的密码也配置了Nacos 会覆盖但解密监听器应该处理最终值。五、今日作业与学习产出提交代码将ConfigDecryptor、DecryptConfigListener、修改后的config_center.php和.env.example不含真实密钥提交到 Git。扩展实践加密所有敏感配置项Redis 密码、JWT 密钥等并测试解密。研究hyperf/encryption组件或自定义注解实现更优雅的加密字段标记。学习笔记画出多环境配置管理的架构图代码仓库无敏感信息→ CI/CD 注入环境变量 → 配置中心加载 解密 → 应用运行。对比不同配置加密方案环境变量、配置文件加密工具、KMS分析各自的优缺点和适用场景。挑战任务使用Docker Secrets或Kubernetes Secrets传递解密密钥而不是环境变量。研究 HashiCorp Vault 等专业密钥管理工具思考在企业级项目中如何与 Hyperf 集成。通过今天的学习你实现了微服务配置管理的“最后一公里”环境隔离与安全加密。这标志着你的服务已经具备生产级部署的成熟度可以安全地在多个环境中流转。明天我们将进行第二阶段综合实战把服务注册、配置中心、熔断限流等全部串联打造一个完整的微服务治理体系。