SpringBoot多环境动态环境切换(nacos)

目录

1. 环境变量切换

1.1 建立各环境配置文件

1.2 设置环境变量

2. nacos配置中心动态切换

2.1 配置文件

2.2 nacos配置

2.3 启动服务

3. 同一nacos环境下服务不同环境控制

3.1 cloud方式

3.1.1 引入依赖

3.1.2 添加配置

3.1.3 添加环境变量

3.1.4 启动服务

3.1.5 读取数据库配置

4.Maven方式

4.1 创建配置文件

4.2 更改pom文件

4.3 maven打包

总结

常见问题

1.读取不到配置


多环境配置说明:

        在项目实际开发过程中,可能会有不同的环境,例如开发环境,测试环境和生产环境。不同的环境,对应的配置信息是不同的,将项目发布到不同的环境,需要去更改对应环境的配置信息,如果每次都是手动去更改环境,非常不友好,且容易漏掉配置,如果能够实现不同环境的自动识别,动态切换,将极大的提高工作效率。下面介绍一下自己在工作中使用到的多环境配置方法。

1. 环境变量切换

SpringBoot打包服务时,一些参数需要从外界获取,可以通过在配置文件中配置环境变量实现

spring:
  datasource:
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:table}?useUnicode=true&characterEncoding=UTF-8
      username: ${DB_USER:root}
      password: ${DB_PASSWORD:root}

上面案例中,${DB_HOST:localhost} 服务会先在系统环境变量 DB_HOST 中获取值,如果获取不到则使用默认值 localhost

​ 我们可以利用系统环境变量读取机制,在不同环境下给变量配置不同值,通过读取对应变量的值,获取不同的运行配置文件:

1.1 建立各环境配置文件

分别为 application-dev.yml 和 application-prod.yml 分别代表开发环境和生产环境的配置:


# application.yml
spring:
  profiles:
    active: ${HUACHUN_PROFILES_ACTIVE:dev}

# application-dev.yml
server:
  port: 9001

# application-prod.yml
server:
  port: 9002

这里只简单的测试环境切换服务端口号

1.2 设置环境变量

网上有示例配置和系统变量后IDEA自动会有环境配置,我的没有所以手动加上(可能是我使用的破解版吧 ,不知道是不是这个原因...)

如果使用的 linux 物理机, 使用 vim /etc/profile 命令,添加上一下配置

  SPRING_PROFILES_ACTIVE=prod
  export SPRING_PROFILES_ACTIVE

1.3 启动服务

2. nacos配置中心动态切换

        通过环境变量的方式,不同环境的配置可以直接观察到,不太安全(当然可以将关键配置抽离成环境变量)。可以将配置文件全部放置在 nacos 中,然后通过统一的域名访问 nacos 服务器,在不同的环境配置域名对应的host ,指向不同的nacos服务器地址,从而读取对应的配置。

2.1 配置文件

# application.yml
spring:
  profiles:
    active: ${HUACHUN_PROFILES_ACTIVE}

# application-prod.yml
nacos:
  config:
    server-addr: nacos_addr:port    # 这里填写相应环境nacos配置中心地址和端口号
    bootstrap:
      enable: true
      log-enable: true
    data-ids: environment.yaml
    type: yaml
    group: dev
    auto-refresh: true
#    namespace: 0e1ff256-8d17-4e94-a4dd-d81c06e9b56c   # 如果需要指定环境可以添加namespace属性

2.2 nacos配置

 nacos中 environment.yaml 配置如下:

server:
  port: 9004

2.3 启动服务

服务启动后发现已经从nacos读取到服务端口号

访问服务测试正常

3. 同一nacos环境下服务不同环境控制

        上面两种方式都是不同的环境对应不同的配置中心,如果多个环境都是使用的同一个配置中心(使用nacos作为配置中心),那么可以通过 dataId 实现不同环境的隔离。

3.1 cloud方式

3.1.1 引入依赖

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

        <!-- 启动获取配置中心配置 原文链接:https://blog.csdn.net/chy2z/article/details/119464497 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.0.3</version>
        </dependency>

如果不引用spring-cloud-starter-bootstrap则不会读取nacos配置

3.1.2 添加配置

添加 bootstrap.yml 配置文件

spring:
  profiles:
    active: ${HUACHUN_PROFILES_ACTIVE}
  cloud:
    nacos:
      config:
        server-addr: nacos_addr:port  # nacos服务访问地址和端口
        enabled: true
        file-extension: yaml
        prefix: environment
        group: dev
        refresh-enabled: true

nacos配置三个文件,端口好分别是9904,9908,9909(这里主要是快速简单的测试读取配置) 

3.1.3 添加环境变量

3.1.4 启动服务

3.1.5 读取数据库配置

1. 配置如下

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: ${env.project.db.username}
    password: ${env.project.db.password}
    url: jdbc:mysql://${env.project.db.adrr}:${env.project.db.port}/hhmt_cpa?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    type: com.alibaba.druid.pool.DruidDataSource

2. nacos配置

 

 访问正常

4.Maven方式

4.1 创建配置文件

application-dev.yml 和 application-prod.yml 分别代表开发环境和生产环境的配置

# application.yml
spring:
  profiles:
    active: ${HUACHUN_PROFILES_ACTIVE:dev}

# application-dev.yml
server:
  port: 9001

# application-prod.yml
server:
  port: 9002

4.2 更改pom文件

 <profiles>
        <profile>
            <id>dev</id>
            <properties>
                <profileActive>dev</profileActive>
            </properties>
            <!--默认开发环境-->
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <profileActive>test</profileActive>
            </properties>
        </profile>
        <profile>
            <id>pro</id>
            <properties>
                <profileActive>pro</profileActive>
            </properties>
        </profile>
    </profiles>

4.3 maven打包

maven  -P dev

原文链接:https://blog.csdn.net/qq_41863849/article/details/122145312

5.配置加密

5.1 服务配置文件中加密

通常在连接数据库时候明文信息不太安全,所以需要加密,这里展示数据库连接用户名密码加解密,其他参数同理

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: JAS(sNhUs2TnBigaKiBTIeobYA==)
    password: JAS(BaqtsIAiaTrZx84TJaMWAq2ryBPvvKbl)

5.1.1 引入依赖

        <!-- web应用依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- cloud整合nacos依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

        <!-- cloud启动依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.0.3</version>
        </dependency>

        <!-- mybatis-plus依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- mysql依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector.version}</version>
        </dependency>

        <!-- 数据库连接池依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid-spring.version}</version>
        </dependency>

        <!-- lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- 加解密依赖 -->
        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

5.1.2 加解密工具类

package com.huachun.utils;

import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;

public class JasyptUtil {

    /**
     * 加密方法
     *
     * @param password jasypt所需要的加密密码配置
     * @param value    需要加密的密码
     */
    public static String encyptPwd(String password, String value) {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(password);
        config.setAlgorithm("PBEWithMD5AndDES");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        String result = encryptor.encrypt(value);
        return result;
    }


    /**
     * 解密
     *
     * @param password jasypt所需要的加密密码配置
     * @param value    需要解密的密码
     */
    public static String decyptPwd(String password, String value) {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(password);
        config.setAlgorithm("PBEWithMD5AndDES");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        String realV = getValue(value);
        String result = encryptor.decrypt(realV);
        return result;
    }

    public static String getValue(String value) {
        if (value.contains("JAS")) {
            return value.substring(4, value.length() - 1);
        }
        return value;
    }

}

5.1.3 添加监听

package com.huachun.listener;

import com.huachun.utils.JasyptUtil;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class EnvironmentPreparedListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment env = event.getEnvironment();
        MutablePropertySources pss = env.getPropertySources();
        List<PropertySource> list = new ArrayList<>();
        for (PropertySource ps : pss) {
            Map<String, Object> map = new HashMap<>();
            if (ps instanceof OriginTrackedMapPropertySource) {
                OriginTrackedMapPropertySource propertySource = new OriginTrackedMapPropertySource(ps.getName(), map);
                Map<String, Object> src = (Map<String, Object>) ps.getSource();
                src.forEach((k, v) -> {
                    String strValue = String.valueOf(v);
                    if (strValue.startsWith("JAS(") && strValue.endsWith(")")) {
                        v = JasyptUtil.decyptPwd(k, v.toString());
                    }
                    map.put(k, v);
                });
                list.add(propertySource);
            }
        }
        /**
         此处是删除原来的 OriginTrackedMapPropertySource 对象,
         把解密后新生成的放入到 Environment,为什么不直接修改原来的
         OriginTrackedMapPropertySource 对象,此处不做过多解释
         不懂的可以去看看它对应的源码,也算是留一个悬念,也是希望大家
         能够没事多看一看源码。
         */
        list.forEach(ps -> {
            pss.remove(ps.getName());
            pss.addLast(ps);
        });

    }
}

5.1.4 添加控制类

package com.huachun.controller;

import com.huachun.model.HcTest;
import com.huachun.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/env")
public class EnvCdDbController {

    @Autowired
    private TestService testService;

    @GetMapping("/{id}")
    public HcTest env(@PathVariable("id") String id) {
        return testService.getData(id);
    }
}

5.1.5 访问测试

访问成功,查询数据库没有问题,说明解密成功。也可以打断点进去看 

5.2 SpringCloud读取Nacos中加密参数

虽然在服务配置文件中可以正常加解密,但是并不灵活,通常还是需要将参数信息放到nacos配置中心管理。

总结

1.springboot方式使用application.yml配置

2.springcloud方式使用bootstrap.yml配置

常见问题

1.读取不到配置

参考原文:spring-cloud使用nacos配置中心的坑_黑暗行动的博客-CSDN博客


版权声明:本文为hauchun原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。