组件及版本

使用SpringCloud+SpringCloudAlibaba作为微服务解决方案:
注册中心:Nacos
配置中心:Nacos
负载均衡:Ribbon
远程调用:Feign
服务熔断:Sentinel
API网关:Gateway
调用链监控:Sleuth
分布式事务:Seata

要注意,使用SpringBoot、SpringCloud和SpringCloudAlibaba时,有版本对应关系。
可以在这里查看:
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

本项目使用的版本为:
SpringBoot:2.1.8.RELEASE
SpringCloud:Greenwich.SR3
SpringCloudAlibaba:2.1.0.RELEASE

因每个微服务都可能会用到SpringCloudAlibaba相关组件,所以把SpringCloudAlibaba相关组件,置于gulimall-common中。
在gulimall-common中引入SpingCloudAlibaba的依赖,在gulimall-common的pom.xml添加如下配置,先引入spring-cloud-alibaba-dependencies,后续相关组件逐步引入:

  1. <dependencyManagement>
  2. <dependencies>
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  6. <version>2.1.0.RELEASE</version>
  7. <type>pom</type>
  8. <scope>import</scope>
  9. </dependency>
  10. </dependencies>
  11. </dependencyManagement>

注册中心Nacos

使用Nacos作为注册中心。
第一步:将nacos作为注册中心的依赖,导入到gulimall-common中:

<!--        服务注册/发现-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

第二步:要使用Nacos作为配置中心,需要有一个Nacos server作为中间件,下载这个Nacos server中间件:
我们先装在我们的windows开发机器上运行,直接运行解压后的nacos目录下的bin目录下的startup.cmd脚本即可。
注意,较新一点的版本,默认为集群启动,这时直接启动会失败,给他改为单体启动再运行startup.cmd脚本,修改方式为:把startup.cmd脚本中的set MODE=”cluster” 改为 set MODE=”standalone”。
启动后如下:
image.png
这时,nacos server就在windows开发及的8848端口启动了。

第三步:在应用的配置文件application.yml中,配置Nacos server的地址:
.properties文件配置方式如下:

 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

第四步:开启服务的注册与发现功能:
即在对应微服务的SpringBoot启动类上,添加@EnableDiscoveryClient注解即可。我们以gulimall-coupon模块为例:

@EnableDiscoveryClient
@MapperScan("com.atguigu.gulimall.coupon.dao")
@SpringBootApplication
public class GulimallCouponApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallCouponApplication.class, args);
    }

}

第五步:要在配置文件中,为要注册到nacos注册中心的服务命名,这一步命名是不可缺少的。我们以gulimall-coupon模块为例:
image.png

测试一下:
启动gulimall-coupon,访问nacos server的控制台,即localhost:8848/nacos,账号密码为nacos/nacos,登陆进去,便可发现注册成功了:
image.png

把其他几个模块也都注册到注册中心:
image.png

远程调用OpenFeign

OpenFeign是SpringCloud的组件。
我们在一开始创建各服务模块时,就给各模块引入了OpenFeign的依赖:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

下面我们以实现一个例子,来演示使用OpenFiegn进行远程调用的步骤:
在gulimall-member会员服务中,去gulimall-coupon优惠券服务调用接口,来获取某个会员账户下的所有优惠券信息(暂时只是模拟数据,重点在于演示远程调用)。
第一步:想要通过OpenFiegn调用其他模块服务,要在本模块引入OpenFeign的依赖,依赖在上面有说。
第二步:在被调用方,写好一个正常的http请求的Controller方法,如gulimall-coupon中:

@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @RequestMapping("/member/list")
    public R memberCoupons(){
        CouponEntity couponEntity = new CouponEntity();
        couponEntity.setCouponName("满100减10");
        return R.ok().put("coupons",Arrays.asList(couponEntity));
    }
}

接下来,就是想要在gulimall-member会员服务中,调用这个远程接口,如下:
一、想要调用远程服务:
1.模块中要引入OpenFeign的依赖;
2.编写一个接口,这个接口来告诉SpringCloud需要调用远程服务(这些接口放在项目的feign包下);
1)声明接口的每个方法,是对应的调用远程服务的哪个请求;
3.开启远程调用功能
在项目的启动类上加上@EnableFeignClients注解,其中注解的basePackages属性,是用来指定那些包下的接口作为远程调用的接口

在完成了OpenFeign依赖的引入后,
首先,要在将要远程调用其他服务的服务的启动类上,加上@EnableFeignClients注解,来开启本服务远程调用的功能;
然后,创建一个包,放置远程调用的接口,本项目中包名为feign,接口用@FeignClient标注,用来告诉Spring,这是一个远程调用接口,并赋值,@FeignClient的值就是要调用的远程服务在nacos注册中心的名称。如:

package com.atguigu.gulimall.member.feign;

import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 本类说明:
 *  @FeignClient,此注解用来告诉SpringCloud,这是一个远程调用接口
 *
 * @author yuanhai
 * @date 2022年01月20日
 */
@FeignClient("gulimall-coupon")
public interface CouponFeignService {

    @RequestMapping("/coupon/coupon/member/list")
    public R memberCoupons();

}

其中方法的书写,直接把要调用的远程服务的地址对应的controller的方法的方法定义拿过来就好,但是@RequestMapping注解的值,要改为远程接口的全路径地址。
然后就可以在本服务的Controller中,正常创建方法,将远程接口注入后使用,直接调用远程接口中的方法即可:

@RestController
@RequestMapping("member/member")
public class MemberController {
    @Autowired
    private MemberService memberService;

    @Autowired
    CouponFeignService couponFeignService;

    @RequestMapping("/coupons")
    public R test(){
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setNickname("张三");
        R memberCoupons = couponFeignService.memberCoupons();
        return R.ok().put("member",memberEntity).put("coupons",memberCoupons.get("coupons"));
    }
}

测试:
image.png
此时就远程调用成功了。

配置中心Nacos

使用方式

第一步:
使用nacos的配置中心的功能,需要引入其配置中心的依赖。 考虑到每个微服务都需要用到配置中心的功能,我们将这个依赖导入到gulimall-common中:

 <!--        配置中心来做配置管理-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

第二步:
在微服务的类路径下,创建一个bootstrap.yml(或bootstrap.properties)配置文件,来配置naocs config的元数据。
这个bootstrap.yml(或bootstrap.properties)配置文件,会优先于application.yml配置文件加载。
先以优惠券模块gulimall-coupon为例:
image.png
在bootstrap.properties文件中,需要配置以下两个东西:
1,当前应用的名称
2,指定nacos配置中心的地址

spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

第三步:
在nacos中的配置列表里,新增配置:
image.png
image.png
点击发布。

这里面Data Id不是随便写的,要和项目做匹配,启动项目时,控制台打印如下,标红的地方就是我们使用的Data Id:
image.png

测试一下:
在gulimall-coupon模块的CouponController中,新建测试请求:


    @Value("${coupon.user.name}")
    private String name;

    @Value("${coupon.user.age}")
    private Integer age;


    @RequestMapping("/test")
    public R test(){
        return R.ok().put("name",name).put("age",age);
    }

浏览器访问http://localhost:7000/coupon/coupon/test,得到如下结果:

{“msg”:”success”,”code”:0,”name”:”zhangsan”,”age”:18}

自动刷新

使用nacos配置中心,可以在不重启应用的情况下,使做出改变的配置属性生效。
使用方式:
在使用了配置中心中的属性的controller的类上,标注上@RefreshScope注解。
如:

@RestController
@RequestMapping("coupon/coupon")
@RefreshScope
public class CouponController {
    @Autowired
    private CouponService couponService;


    @Value("${coupon.user.name}")
    private String name;

    @Value("${coupon.user.age}")
    private Integer age;


    @RequestMapping("/test")
    public R test(){
        return R.ok().put("name",name).put("age",age);
    }
}

命名空间与配置分组

命名空间:
用来做配置隔离,默认使用public(保留空间),默认新增的所有配置都在public空间。
不同的环境,利用命名空间来做环境隔离。
新建命名空间示例:
image.png
image.pngimage.png
image.png
image.png
这里我们新建了三个命名空间,分别时dev、test、prod,对应开发、测试、生产环境。
回到nacos控制台的配置管理->配置列表页面,可以看到public空间旁边多了这三个命名空间:
image.png
这个时候,由于我们新建gulimall-coupon.propeties时,是创建到了public默认命名空间的,选定其他三个我们自己创建的命名空间,是没有配置的。
现在我们选定生产环境prod,在生产环境中创建gulimall-coupon.properties:
image.png
这时,在public下,和prod下都有gulimall-coupon.properties。
注意:
这时,我们不同的命名空间下有两个同样Data Id的配置,但是我们的bootstrap中没有指定命名空间的话,会使用默认的命名空间:public。
如果想要自己指定命名空间,需要在bootstrap.properties中指定以下属性:
spring.cloud.nacos.config.namespace=命名空间的id
如:

spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=70eee986-974f-47bf-bea0-286e34500efc

此时,访问http://localhost:7000/coupon/coupon/test,会得到:

{“msg”:”success”,”code”:0,”name”:”produser”,”age”:18}

这里已经切换成使用prod命名空间的配置了。

配置集
所有配置的集合称为配置集
配置集ID
类似文件名
配置分组
默认所有的配置集都属于DEFAULT_GROUP。
我们创建配置时,有个Group栏目要填,默认为DEFAULT_GROUP,我们可以把它删掉,然后改成自己仙想要的组名。
使用时,通过在bootstrap.properties文件中配置分组属性,来指定使用哪个分组的配置,如:

spring.cloud.nacos.config.group=1111

这里spring.cloud.nacos.config.group的值为分组的名称。

本项目使用方式

本项目中,每个微服务创建自己的命名空间,使用配置分组来区分环境:dev,test,prod

同时加载多个配置集

随着项目越来越大,可能不会将所有的配置都写在同一个配置文件中,可能会拆分出来,如框架相关写成一个配置,数据源相关携程一个配置等。Nacos配置中心支持这种操作:
先将配置抽取出来:
如,将数据源相关抽取出来放到datasource.yml中:
image.png

再将mybatis-plus相关配置放到mybatis.yml中:
image.png
除此之外,其他的配置放在other.yml中:
image.png
此时,gulimall-coupon原本的配置文件被拆分成了datasource.yml、mybatis.yml、other.yml。
我们需要把这三个配置文件的内容,全部合起来,才能构成微服务模块需要的配置,所以需要进行如下操作:
我们需要让微服务模块启动的时候,同时加载多个我们需要的配置文件。
在bootstrap.properties文件中,原本的配置分组相关设置可以先注释掉(因为加载多个配置文件时,要分别指定各个配置文件所属的分组),
设置spring.cloud.nacos.config.ext-config属性,这个属性的值是一个list,所以这里可以写多个,
其中这个属性又有data-id、group、refresg三个属性,分别用来设定data id,分组,是否动态刷新。如下:

spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=coupon
#spring.cloud.nacos.config.group=dev

spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true

spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

这时,gulimall-coupon中的所有配置都被放到了配置中心,我们可以把原本项目中的application.yml配置全部注释掉,用nacos中的配置。
这里注释掉启动会报错,因为我们这种方式设置后,原本gulimall-coupon.properties这个配置文件,是会被默认加载,但是由于没有指定分组,所以会默认加载DEFAULT_GROUP下的gulimall-coupon.properties。然而实际上,coupon命名空间里,没有DEFAULT_GROUP下的gulimall-coupon.properties这个文件,所以没有被加载到。 这时可以在项目中新建一个application.properties,把原本gulimall-coupon.properties里面的属性写在里面,会发现,启动成功,并且读取到的时项目中application.properties文件里的属性。 即:配置中心里没有这个配置的话,会去项目的配置文件中找,如果项目的配置文件有需要的属性,就会使用项目的配置文件的属性。
image.png
这时,访问http://localhost:7000/coupon/coupon/test,如下:

{“msg”:”success”,”code”:0,”name”:”local-name”,”age”:18}

我们刚才把原本的bootstrap.properties中配置的spring.cloud.nacos.config.group属性注释掉了,闲杂现在把它取消注释,然后重启gulimall-coupon:

spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=coupon
spring.cloud.nacos.config.group=dev

spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true

spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

这时,重新访问http://localhost:7000/coupon/coupon/test,如下:

{“msg”:”success”,”code”:0,”name”:”dev”,”age”:22}

可以得知,通过spring.cloud.nacos.config.ext-config指定的配置文件,会使用自己的分组,而默认加载的配置文件,会使用spring.cloud.nacos.config.group属性设置的分组。

总结

一、如何使用Nacos作为配置中心统一管理配置
1.引入依赖:

com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config

2.创建一个bootstrap.properties文件。
这个文件中有两个比较重要的配置:
spring.application.name=gulimall-coupon # 当前应用的名称
spring.cloud.nacos.config.server-addr=127.0.0.1:8848 # nacos配置中心的地址
3.需要给配置中心默认添加一个Data Id(数据集),默认的Data Id规则为: 应用名称.properties
4.给 应用名称.properties 添加需要的任何配置
5.动态获取配置
结合@RefreshScope注解,这个注解用来动态获取并刷新配置;
结合@Value注解,@Value(“${配置项的名称}”)用来获取配置;
如果配置中心和当前应用的配置文件中都配置了相同的项,优先使用配置中心的配置。(即:同样的配置项配置中心有,项目的application.yml文件也有,则会用配置中心的)

二、Nacos配置中心的一些细节
1.命名空间
用来做配置隔离;
默认:public(保留空间):默认新增的所有配置都在public空间。
1.开发、测试、生产环境:利用命名空间来做环境隔离
注意:在bootstrap.properties;配置上,需要使用哪个命名空间下的配置,如:
spring.cloud.nacos.config.namespace=70eee986-974f-47bf-bea0-286e34500efc
spring.cloud.nacos.config.namespace的属性值为命名空间的id
2.开发中,可以每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
2.配置集
所有的配置的集合1
3.配置集ID
配置集ID:类似文件名
Data Id: 类似文件名
4.配置分组
默认所有的配置集都属于:DEFAULT_GROUP;
通过在bootstrap.properties中指定 spring.cloud.nacos.config.group=分组名,来指定使用哪个分组的配置

项目中的使用:每个微服务创建自己的命名空间,使用配置分组区分环境,dev,test,prod

三、同时加载多个配置集
1.微服务任何配置信息,任何配置文件都可以放在配置中心中
2.只需要在bootstrap.properties说明加载配置中心中哪些配置文件即可,如:
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
3.@Value,@ConfigurationProperties。。。
以前SpringBoot任何方法从配置文件中获取值,都能使用。
配置中心有的优先使用配置中心中的,没有就回去项目中的配置文件找。

网关Gateway

创建网关服务

创建一个项目,用来作为微服务网关。
使用springboot脚手架创建项目,创建时,选中gateway依赖。
网关也要依赖gulimall-common。
在nacos中为网关模块也创建名称空间以及相关配置文件。创建配置文件如下:
image.png

这里网关引入了gulimall-common,因此需要配置数据源相关信息,否则启动会报错。 可以在引入gulimall-common依赖时,将数据源相关依赖排除,也可以在启动类的@SpringbootApplication注解中,排除数据源相关配置,如下:
@SpringBootApplication(_exclude = {DataSourceAutoConfiguration.class})_

具体路由,断言,过滤器规则,后面边做边用。

前端访问路径统一访问网关

在原本的renren-fast-vue项目(后台管理系统的前端部分)中,访问后台的路径前缀为:

  // api接口请求地址
  window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';

这样做的话,只能统一访问固定的一个后台项目。然而项目分为多个微服务模块,每个模块有不同的地址,所以这里要把请求地址改为访问网关,如下:

 // api接口请求地址
  window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';

这里,在端口号之后加上/api,使所有前端请求之后,都带上/api的前缀。

网关相关配置

这时刷新页面,会返回到登录页,且登录页的验证码刷新不出来。
统一访问网关后,由于访问的地址为网关的地址,所以请求无法直接到达真正想要请求的地址,需要进行路由配置。
这里要把renren-fast(后台管理系统的后台),也注册到注册中心中去。
这里,我们先配置转发到renren-fast的路由:

spring:
  cloud:
    gateway:
      routes:
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

这里面,predicates断言,表示/api/的请求,都转发到uri: //renren-fast里面去,会在nacos中找到renren-fast对应的地址,其中lb代表loadbalance负载均衡。 同时,由于前端请求都带上了/api前缀,但是实际上renren-fast没有/api,而是/renren-fast,所以使用filters过滤规则中的RewritePath(路径重写),将/api转换为/renren-fast。**
这时,从首页登录可以刷新出验证码了。

网关统一配置跨域

上面的完成后,获取到验证码,会发现登录报错,无法登录,这是由于跨域问题导致的。
下面在网关中统一配置跨域相关信息:
在网关中添加如下配置类;

package com.atguigu.gulimall.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

/**
 * 本类说明:
 * 解决跨域问题配置类
 * @author yuanhai
 * @date 2022年02月11日
 */
@Configuration
public class GulimallCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();

        //1、配置跨域
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);

        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }

}

添加这个配置类,重启网关即可。
注意,后台renren-fast里面也配置了跨域配置,要把renren-fast里面的这个配置注释掉。

Gateway路由详细配置

spring:
  cloud:
    gateway:
      routes:

        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

        - id: third_party_route
          uri: lb://gulimall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

        - id: member_route
          uri: lb://gulimall-member
          predicates:
            - Path=/api/member/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

        - id: ware_route
          uri: lb://gulimall-ware
          predicates:
            - Path=/api/ware/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

由于除去后台管理模块是/api/,其他模块多了一层,/api/xxx/,因此要把其他模块的放在后台管理模块的前面。如果放到后面,那么直接/api/**就匹配上了,无法正确匹配。

同时,把其他微服务模块的配置,放到nacos中,并且要配置注册中心、配置中心相关地址。并且在各微服务模块的启动类上标注@EnableDiscoveryClient注解,来开启服务注册功能。