Spring Cloud Alibaba 个人学习笔记

Spring Cloud Alibaba

一、简介

1.1 官方描述

Spring Cloud Alibaba provides a one-stop solution for distributed application development. It contains all the components required to develop distributed applications, making it easy for you to develop your applications using Spring Cloud.
With Spring Cloud Alibaba, you only need to add some annotations and a small amount of configurations to connect Spring Cloud applications to the distributed solutions of Alibaba, and build a distributed application system with Alibaba middleware.

1.2 架构图

在这里插入图片描述

图片来源:processon

1.3 模块

在这里插入图片描述

二、项目搭建

2.1 maven

创建maven项目,配置pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
    </parent>
    <groupId>cn.flowboot.e.commerce</groupId>
    <artifactId>E-commerce-cloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <!-- 工程项目版本 -->
        <E-commerce-cloud>1.0-SNAPSHOT</E-commerce-cloud>
        <!-- Spring Cloud 依赖 -->
        <spring.cloud.version>Hoxton.SR12</spring.cloud.version>
        <!-- spring cloud alibaba 依赖 -->
        <spring.cloud.alibaba.version>2.2.7.RELEASE</spring.cloud.alibaba.version>
        <!--  依赖 -->
        <lombok.version>1.16.18</lombok.version>
        <!-- commons-lang3 依赖 -->
        <commons-lang3.version>3.11</commons-lang3.version>
        <!-- commons-collections4 依赖 -->
        <commons-collections4.version>4.4</commons-collections4.version>
        <!-- hutool-all 依赖 -->
        <hutool-all.version>5.6.0</hutool-all.version>
        <!-- jwt  依赖 -->
        <jwt.version>0.11.2</jwt.version>
        <!-- fastjson 依赖 -->
        <fastjson.version>1.2.78</fastjson.version>
        <!-- mybatis plus 依赖 -->
        <mybatis-plus.version>3.4.2</mybatis-plus.version>
        <!-- druid 依赖 -->
        <druid.version>1.2.6</druid.version>
        <!-- bitwalker 依赖 -->
        <bitwalker.version>1.21</bitwalker.version>
        <!--  依赖 -->
        <swagger.version>3.0.0</swagger.version>
        <!-- kaptcha  依赖 -->
        <kaptcha.version>2.3.2</kaptcha.version>
        <!-- mybatis-spring  依赖 -->
        <mybatis-spring-boot.version>2.1.4</mybatis-spring-boot.version>
        <!-- pagehelper  依赖 -->
        <pagehelper.boot.version>1.3.1</pagehelper.boot.version>
        <!-- oshi 依赖 -->
        <oshi.version>5.8.0</oshi.version>
        <!-- jna 依赖 -->
        <jna.version>5.8.0</jna.version>
        <!--  io 依赖 -->
        <commons.io.version>2.11.0</commons.io.version>
        <!-- fileupload 依赖 -->
        <commons.fileupload.version>1.4</commons.fileupload.version>
        <!-- collections 依赖 -->
        <commons.collections.version>3.2.2</commons.collections.version>
        <!-- poi 依赖 -->
        <poi.version>4.1.2</poi.version>
        <!--  velocity 依赖 -->
        <velocity.version>1.7</velocity.version>

    </properties>


    <!-- 项目依赖管理 父项目只是声明依赖,子项目需要写明需要的依赖(可以省略版本信息) -->
    <dependencyManagement>
        <dependencies>
            <!-- spring cloud 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- spring cloud alibaba 依赖 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- lombok 工具通过在代码编译时期动态的将注解替换为具体的代码,   IDEA 需要添加 lombok 插件 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang3.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-collections4</artifactId>
                <version>${commons-collections4.version}</version>
            </dependency>
            <!-- collections工具类 -->
            <dependency>
                <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
                <version>${commons.collections.version}</version>
            </dependency>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool-all.version}</version>
            </dependency>
            <!-- Token生成与解析-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-api</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-impl</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-jackson</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
            <!-- 服务/工具组件 -->
            <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!-- 解析客户端操作系统、浏览器等 -->
            <dependency>
                <groupId>eu.bitwalker</groupId>
                <artifactId>UserAgentUtils</artifactId>
                <version>${bitwalker.version}</version>
            </dependency>

            <!-- pagehelper 分页插件 -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>${pagehelper.boot.version}</version>
            </dependency>

            <!-- 获取系统信息 -->
            <dependency>
                <groupId>com.github.oshi</groupId>
                <artifactId>oshi-core</artifactId>
                <version>${oshi.version}</version>
            </dependency>

            <dependency>
                <groupId>net.java.dev.jna</groupId>
                <artifactId>jna</artifactId>
                <version>${jna.version}</version>
            </dependency>

            <dependency>
                <groupId>net.java.dev.jna</groupId>
                <artifactId>jna-platform</artifactId>
                <version>${jna.version}</version>
            </dependency>

            <!-- Swagger3依赖 -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-boot-starter</artifactId>
                <version>${swagger.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>io.swagger</groupId>
                        <artifactId>swagger-models</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>

            <!-- 文件上传工具类 -->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>${commons.fileupload.version}</version>
            </dependency>

            <!-- excel工具 -->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.version}</version>
            </dependency>

            <!-- 验证码 -->
            <dependency>
                <groupId>com.github.penggle</groupId>
                <artifactId>kaptcha</artifactId>
                <version>${kaptcha.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <!-- 配置远程仓库 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

2.2 创建基础模块及基础功能

2.2.1 创建子模块

创建子模块e-commerce-common和e-commerce-mvc-config

e-commerce-common/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>E-commerce-cloud</artifactId>
        <groupId>cn.flowboot.e.commerce</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>e-commerce-common</artifactId>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

e-commerce-mvc-config/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>E-commerce-cloud</artifactId>
        <groupId>cn.flowboot.e.commerce</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>e-commerce-mvc-config</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>cn.flowboot.e.commerce</groupId>
            <artifactId>e-commerce-common</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

在主pom.xml添加模块管理

 <!-- 项目依赖管理 父项目只是声明依赖,子项目需要写明需要的依赖(可以省略版本信息) -->
 <dependencyManagement>
	 <dependencies>
	     <!-- 本项目模块 依赖 -->
	     <dependency>
	         <groupId>cn.flowboot.e.commerce</groupId>
	         <artifactId>e-commerce-common</artifactId>
	         <version>${E-commerce-cloud}</version>
	     </dependency>
	     <dependency>
	         <groupId>cn.flowboot.e.commerce</groupId>
	         <artifactId>e-commerce-mvc-config</artifactId>
	         <version>${E-commerce-cloud}</version>
	     </dependency>

		...
		
     </dependencies>
</dependencyManagement>

2.2.2 基础项目代码准备

e-commerce-common/…/CommonResponse

package cn.flowboot.e.commerce.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * <h1>通用响应对象定义</h1>
 *
 *  {
 *      "code":0,
 *      "message":"",
 *      "data":{}
 *  }
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResponse<T> implements Serializable {
   

    private final static int SUCCESS_CODE = 0;
    private final static String SUCCESS_MSG = "success";
    private final static int FAIL_CODE = -1;
    private final static String FAIL_MSG = "fail";
    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误消息
     */
    private String message;
    /**
     * 泛型响应数据
     */
    private T data;


    /**
     * <h2> build - 构建通用消息 - 全参数<h2>
     * version: 1.0 - 2022/3/2
     * @param code 状态码
     * @param message 消息
     * @param data 泛型数据
     * @return {@link CommonResponse }
     */
    public static <T> CommonResponse build(Integer code, String message, T data){
   
        return new CommonResponse<T>(code,message,data);
    }

    /**
     * <h2> build - 构建通用消息 - 状态消息<h2>
     * version: 1.0 - 2022/3/2
     * @param code 状态码
     * @param message 消息
     * @return {@link CommonResponse }
     */
    public static <T> CommonResponse build(Integer code, String message){
   
        return new CommonResponse<T>(code,message,null);
    }

    /**
     * <h2> success - 默认成功通用消息<h2>
     * version: 1.0 - 2022/3/2
     * @return {@link CommonResponse }
     */
    public static  CommonResponse success(){
   
        return new CommonResponse(FAIL_CODE,FAIL_MSG,null);
    }

    /**
     * <h2> success - 成功通用消息 - 消息<h2>
     * version: 1.0 - 2022/3/2
     * @param message 消息
     * @return {@link CommonResponse }
     */
    public static  CommonResponse success( String message){
   
        return new CommonResponse(FAIL_CODE,message,null);
    }

    /**
     * <h2> success - 成功通用消息 - 消息数据<h2>
     * version: 1.0 - 2022/3/2
     * @param message 消息
     * @param data 泛型数据
     * @return {@link CommonResponse }
     */
    public static <T> CommonResponse success(String message,T data){
   
        return new CommonResponse<T>(FAIL_CODE,message,data);
    }

    /**
     * <h2> fail - 默认失败通用消息<h2>
     * version: 1.0 - 2022/3/2
     * @return {@link CommonResponse }
     */
    public static  CommonResponse fail(){
   
        return new CommonResponse(FAIL_CODE,FAIL_MSG,null);
    }

    /**
     * 通用消息
     * @param message 消息
     * @return {@link CommonResponse}
     */

    /**
     * <h2> fail - 失败通用消息 - 消息<h2>
     * version: 1.0 - 2022/3/2
     * @param message 消息
     * @return {@link CommonResponse }
     */
    public static  CommonResponse fail( String message){
   
        return new CommonResponse(FAIL_CODE,message,null);
    }

    /**
     * <h2> fail - 失败通用消息 - 消息数据 <h2>
     * version: 1.0 - 2022/3/2
     * @param message 消息
     * @param data  泛型数据
     * @return {@link CommonResponse }
     */
    public static <T> CommonResponse fail(String message,T data){
   
        return new CommonResponse<T>(FAIL_CODE,message,data);
    }

}

e-commerce-mvc-config/…/annotation/IgnoreResponseAdvice

package cn.flowboot.e.commerce.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <h1>忽略返回通用响应</h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@Target({
   ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
   
}

e-commerce-mvc-config/…/advice/CommonResponseDataAdvice

package cn.flowboot.e.commerce.advice;

import cn.flowboot.e.commerce.annotation.IgnoreResponseAdvice;
import cn.flowboot.e.commerce.vo.CommonResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.Objects;

/**
 * <h1>实现统一响应</h1>
 * @RestControllerAdvice value 限制生效的包
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@RestControllerAdvice(value = "cn.flowboot.e.commerce")
public class CommonResponseDataAdvice implements ResponseBodyAdvice {
   

    /**
     * 判断是否需要被处理
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
   
        if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnoreResponseAdvice.class)
                || Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(IgnoreResponseAdvice.class)){
   
            return false;
        }
        return true;
    }

    /**
     * 处理
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
                                  Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
   

        CommonResponse response = CommonResponse.success();
        if (null == o){
   
            return CommonResponse.success();
        } else if (o instanceof  CommonResponse){
   
            return  o;
        } else if (o instanceof String) {
   
            response.setData(o);
            return JSONObject.toJSONString(response);
        } else {
   
            response.setData(o);
            return response;
        }
    }
}

e-commerce-mvc-config/…/advice/GlobalExceptionAdvice

package cn.flowboot.e.commerce.advice;

import cn.flowboot.e.commerce.vo.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

/**
 * <h1></h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
   

    @ExceptionHandler(value = Exception.class)
    public CommonResponse<String> handlerCommerceException(HttpServletRequest req,Exception ex){
   
        log.error("commerce service has error  [{}]: {}",req.getRequestURI(),ex.getMessage(),ex);
        return CommonResponse.fail("business error",ex.getMessage());
    }
}

三、Nacos 服务注册与配置中心

3.1 简介

官网 | 文档 | 架构

官网解释:

服务 (Service)

服务是指一个或一组软件功能(例如特定信息的检索或一组操作的执行),其目的是不同的客户端可以为不同的目的重用(例如通过跨进程的网络调用)。Nacos 支持主流的服务生态,如 Kubernetes Service、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service。

服务注册中心 (Service Registry)

服务注册中心,它是服务,其实例及元数据的数据库。服务实例在启动时注册到服务注册表,并在关闭时注销。服务和路由器的客户端查询服务注册表以查找服务的可用实例。服务注册中心可能会调用服务实例的健康检查 API 来验证它是否能够处理请求。

服务元数据 (Service Metadata)

服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据。

服务提供方 (Service Provider)

是指提供可复用和可调用服务的应用方。

服务消费方 (Service Consumer)

是指会发起对某个服务调用的应用方。

配置 (Configuration)

在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成这个步骤。配置变更是调整系统运行时的行为的有效手段之一。

配置管理 (Configuration Management)

在数据中心中,系统中所有配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理。

名字服务 (Naming Service)

提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info, DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的2大场景。

配置服务 (Configuration Service)

在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。

  • 如上解释来自官网文档

3.2 安装使用

本部分直接访问官网: nacos快速开始

3.3 配置MySQL持久化

  • 修改配置,指定MySQL地址、用户名、端口号
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/e-commerce-nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root

创建数据库e-commerce-nacos,并执行conf/nacos-mysql.sql文件
运行nacos

3.4 客户端编写

创建模块添加服务发现和服务配置maven

<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos config -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

启动入口

package cn.flowboot.e.commerce;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * <h1>nacos 客户端 启动入口</h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@EnableDiscoveryClient
@SpringBootApplication
public class NacosClientApplication {
   

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

3.4.1 服务发现

配置 bootstrap.yml

server:
  port: 8000
  servlet:
    context-path: /nacos-client
spring:
  application:
    name: e-commerce-nacos-client
  cloud:
    nacos:
      #服务发现
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        namespace: e-commerce-nacos-server

namespace: e-commerce-nacos-server 为手动指定id创建的命名空间

NacosClientServiceImpl 此处省略接口定义代码

package cn.flowboot.e.commerce.service.impl;

import cn.flowboot.e.commerce.service.NacosClientService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * <h1>客户端实现类</h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class NacosClientServiceImpl implements NacosClientService {
   
    private final DiscoveryClient discoveryClient;

    /**
     * <h2> getNacosClientList - 获取服务发现中的客户端<h2>
     * version: 1.0 - 2022/3/2
     * @param
     * @return {@link List< ServiceInstance> }
     */
    @Override
    public List<ServiceInstance> getNacosClientInfo(String serviceId){
   
        log.info("request nacos client to get service instance info:[{}]",serviceId);
        return discoveryClient.getInstances(serviceId);
    }
}

package cn.flowboot.e.commerce.controller;

import cn.flowboot.e.commerce.service.NacosClientService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
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;

import java.util.List;

/**
 * <h1></h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@RequiredArgsConstructor
@Slf4j
@RestController
public class NacosClientController {
   

    private final NacosClientService nacosClientService;

    @GetMapping("/service/instance/{serviceId}")
    public List<ServiceInstance> logNacosClientInfo(@PathVariable("serviceId") String serviceId){
   
        log.info("request nacos client to get service instance info:[{}]",serviceId);
        return nacosClientService.getNacosClientInfo(serviceId);
    }

}

测试结果:

{
   
  "code": 0,
  "message": "",
  "data": [
    {
   
      "serviceId": "e-commerce-nacos-client",
      "host": "172.21.64.1",
      "port": 8000,
      "secure": false,
      "metadata": {
   
        "nacos.instanceId": null,
        "nacos.weight": "1.0",
        "nacos.cluster": "DEFAULT",
        "nacos.ephemeral": "true",
        "nacos.healthy": "true",
        "preserved.register.source": "SPRING_CLOUD"
      },
      "uri": "http://172.21.64.1:8000",
      "instanceId": null,
      "scheme": null
    }
  ]
}

3.4.2 服务配置

NacosClientController 添加方法

@Value(value = "${flowboot.version}")
private String version;

@GetMapping("version")
public String getVersion(){
   
    return version;
}

本地yml测试是没问题的

flowboot:
  version: "1.0.0"

{“code”:0,“data”:“1.0.0”,“message”:“success”}

配置yaml

server:
  port: 8000
  servlet:
    context-path: /nacos-client
spring:
  application:
    name: e-commerce-nacos-client
  cloud:
    nacos:
      #服务发现
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        namespace: e-commerce-nacos-server
      #服务配置
      ## nacos 文件名:${spring.application.name}- ${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
      ## ${prefix}-${spring.profile.active}.${file-extension}
      config:
        enabled: true
        # 配置中心地址
        server-addr: 127.0.0.1:8848
        # 配置文件格式
        file-extension: yaml
        # 服务命名空间
        namespace: e-commerce-nacos-server

  profiles:
    active: dev

配置完在nacos面板中添加配置,如下是对应yaml的配置文件命名规则,值与本地不同,本地注释,重新启动请求获取配置的值,如果已经是nacos设置的说明配置成功
在这里插入图片描述

3.4.3 动态配置

@Value 的场景下在,可在类上添加注解@RefreshScope即可实现动态刷新,如:

@RefreshScope
public class NacosClientController {
   
	...
}

重启后就可以动态修改值
配置文件添加

spring:
  cloud:
    nacos:
      config:
        ...
        #启动刷新
        refresh-enabled: true

3.4.4 配置共享

配置(完整配置)

server:
  port: 8000
  servlet:
    context-path: /nacos-client
spring:
  application:
    name: e-commerce-nacos-client
  cloud:
    nacos:
      #服务发现
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        namespace: e-commerce-nacos-server
      #服务配置
      ## nacos 文件名:${spring.application.name}- ${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
      config:
        enabled: true
        # 配置中心地址
        server-addr: 127.0.0.1:8848
        # 配置文件格式
        file-extension: yaml
        # 服务命名空间
        namespace: e-commerce-nacos-server
        #启动刷新
        refresh-enabled: true
        # 支持多个共享 Data Id 的配置,优先级小于extension-configs,自定义 Data Id 配置 属性是个集合,内部由 Config POJO 组成。Config 有 3 个属性,分别是 dataId, group 以及 refresh
        shared-configs[0]:
          data-id: common.yaml # 配置文件名-Data Id
          group: DEFAULT_GROUP   # 默认为DEFAULT_GROUP
          refresh: true   # 是否动态刷新,默认为false
        # 扩展配置
        extension-configs[0]:
          data-id: file.yaml
          group: DEFAULT_GROUP     # 默认为DEFAULT_GROUP
          refresh: true            # 是否动态刷新,默认为false
  profiles:
    active: dev

重新运行,日志中出现common.yaml说明共享配置文件被读取,可以通过读取值测试
Located property source: [BootstrapPropertySource {name=‘bootstrapProperties-e-commerce-nacos-client-dev.yaml,DEFAULT_GROUP’}, BootstrapPropertySource {name=‘bootstrapProperties-e-commerce-nacos-client.yaml,DEFAULT_GROUP’}, BootstrapPropertySource {name=‘bootstrapProperties-e-commerce-nacos-client,DEFAULT_GROUP’}, BootstrapPropertySource {name=‘bootstrapProperties-common.yaml,DEFAULT_GROUP’}]

其中配置优先级 : shared-configs < extension-configs < 自动

四、SpringBoot Admin 监控服务器

4.1 创建模块

创建子模块并添加maven

<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.2.4</version>
</dependency>
<dependency>
    <groupId>cn.flowboot.e.commerce</groupId>
    <artifactId>e-commerce-mvc-config</artifactId>
</dependency>

4.2 启用Admin Server

启动入口程序,加上@EnableAdminServer注解即可

package cn.flowboot.e.commerce;

import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * <h1></h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@EnableAdminServer
@SpringBootApplication
public class AdminApplication {
   

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

4.3 配置yaml

注册到服务发现,可以管理其他微服务

server:
  port: 7001
  servlet:
    context-path: /admin
spring:
  application:
    name: e-commerce-admin
  cloud:
    nacos:
      #服务发现
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        namespace: e-commerce-nacos-server
        metadata:
          management:
            context-path: ${
   server.servlet.context-path}/actuator
  thymeleaf:
    check-template: false
    check-template-location: false

#暴露端点
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: '*'

其他微服务需要被监控需要添加actuator依赖,直接添加到common模块依赖中

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

在nacos-client配置文件中添加

server:
  port: 8000
  servlet:
    context-path: /nacos-client
spring:
  application:
    name: e-commerce-nacos-client
  cloud:
    nacos:
      #服务发现
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        namespace: e-commerce-nacos-server
        # SpringBoot Admin 新增
        metadata:
          management:
            context-path: ${
   server.servlet.context-path}/actuator
      #服务配置
      ## nacos 文件名:${spring.application.name}- ${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
      config:
        enabled: true
        # 配置中心地址
        server-addr: 127.0.0.1:8848
        # 配置文件格式
        file-extension: yaml
        # 服务命名空间
        namespace: e-commerce-nacos-server
        #启动刷新
        refresh-enabled: true
        # 支持多个共享 Data Id 的配置,优先级小于extension-configs,自定义 Data Id 配置 属性是个集合,内部由 Config POJO 组成。Config 有 3 个属性,分别是 dataId, group 以及 refresh
        shared-configs[0]:
          data-id: common.yaml # 配置文件名-Data Id
          group: DEFAULT_GROUP   # 默认为DEFAULT_GROUP
          refresh: true   # 是否动态刷新,默认为false
        # 扩展配置
#        extension-configs[0]:
#          data-id: file.yaml
#          group: DEFAULT_GROUP     # 默认为DEFAULT_GROUP
#          refresh: true            # 是否动态刷新,默认为false
  profiles:
    active: dev
#flowboot:
#  version: "1.0.0"

# SpringBoot Admin 新增
# 暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

http://localhost:7001/admin/
在这里插入图片描述

4.4 安全配置

4.4.1 添加Security

maven

<!-- 开启认证功能-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

4.4.2 配置账号

配置security账号

server:
  port: 7001
  servlet:
    context-path: /admin
spring:
  application:
    name: e-commerce-admin
  security:
    user:
      name: admin
      password: admin
  cloud:
    nacos:
      #服务发现
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
        namespace: e-commerce-nacos-server
        metadata:
          management:
            context-path: ${
   server.servlet.context-path}/actuator
          user:
            name: admin
            password: admin
  thymeleaf:
    check-template: false
    check-template-location: false

#暴露端点
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: '*'

4.4.3 安全认证处理

conf/SecuritySecureConfig

package cn.flowboot.e.commerce.conf;

import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

/**
 * <h1>配置安全认证</h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/02
 */
@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
   

    /** 应用上下文路径 */
    private final String adminContextPath;

    public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
   

        this.adminContextPath = adminServerProperties.getContextPath();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   

        SavedRequestAwareAuthenticationSuccessHandler successHandler =
                new SavedRequestAwareAuthenticationSuccessHandler();

        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(adminContextPath + "/");

        http.authorizeRequests()
                // 1. 配置所有的静态资源和登录页可以公开访问
                .antMatchers(adminContextPath + "/assets/**").permitAll()
                .antMatchers(adminContextPath + "/login").permitAll()
                // 2. 其他请求, 必须要经过认证
                .anyRequest().authenticated()
                .and()
                // 3. 配置登录和登出路径
                .formLogin().loginPage(adminContextPath + "/login")
                .successHandler(successHandler)
                .and()
                .logout().logoutUrl(adminContextPath + "/logout")
                .and()
                // 4. 开启 http basic 支持, 其他的服务模块注册时需要使用
                .httpBasic()
                .and()
                // 5. 开启基于 cookie 的 csrf 保护
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // 6. 忽略这些路径的 csrf 保护以便其他的模块可以实现注册
                .ignoringAntMatchers(
                        adminContextPath + "/instances",
                        adminContextPath + "/actuator/**"
                );
    }
}

重启项目访问
在这里插入图片描述

4.5 监控警告

4.5.1 邮件通知

添加maven

<!-- 监控告警 email-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

添加配置

spring:
  # 被监控的应用状态变更为 DOWN、OFFLINE、UNKNOWN 时, 会自动发出告警: 实例的状态、原因、实例地址等信息
  # 需要在 pom.xml 文件中添加 spring-boot-starter-mail 依赖
  # 配置发送告警的邮箱服务器
  # 但是, 这个要能连接上, 否则会报错
  mail:
    host: smtp.ym.163.com
    username: xxx@163.com
    password: xxxxxx
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          connectiontimeout: 600000
          timeout: 600000
          writetimeout: 600000
          auth: true
          socketFactory:
            class: javax.net.ssl.SSLSocketFactory
            port: 25
  # 监控告警通知
  boot:
    admin:
      notify:
        mail:
          from: ${
   spring.mail.username}
          to: ["xxx@qq.com"]
          cc: ["xxx@qq.com"]

4.5.2 自定义监控

notifier /AppNotifier

package cn.flowboot.e.commerce.notifier;

import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
 * <h1>自定义报警</h1>
 *
 * @version 1.0
 * @author: Vincent Vic
 * @since: 2022/03/03
 */
@Slf4j
@Component
@SuppressWarnings("all")
public class AppNotifier extends AbstractEventNotifier {
   

    private final InstanceRepository repository;

    protected AppNotifier(InstanceRepository repository) {
   
        super(repository);
        this.repository = repository;
    }

    /**
     * <h2> doNotify - 事件通知 <h2>
     * version: 1.0 - 2022/3/3
     * @param event
     * @param instance
     * @return {@link Mono< Void> }
     */
    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
   
        return Mono.fromRunnable(() -> {
   

            if (event instanceof InstanceStatusChangedEvent) {
   
                log.info("Instance Status Change: [{}], [{}], [{}]",
                        instance.getRegistration().getName(), event.getInstance(),
                        ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());
            } else {
   
                log.info("Instance Info: [{}], [{}], [{}]",
                        instance.getRegistration().getName(), event.getInstance(),
                        event.getType());
            }

        });

    }
}

五、Gateway

参考:来自网友的官方文档

5.1 架构图

在这里插入图片描述

5.2 Predicate 断言

Predicate 断言:这是一个Java 8 Function Predicate。输入类型是 Spring Framework ServerWebExchange。这允许开发人员可以匹配来自HTTP请求的任何内容,例如Header或参数。

Predicate 由Java8引入,位于java.util.function包中,是一个FunctionalInterface (函数式接口)


    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

通常用在stream的filter 中,表示是否满足过滤条件

5.3 路由

Route 路由:gateway的基本构建模块。它由ID、目标URI、断言集合和过滤器集合组成。如果聚合断言结果为真,则匹配到该路由。

5.3.1 静态路由

yaml配置

spring:
  cloud:
    # 静态路由
    gateway:
      discovery:
        locator:
          enabled: true # gateway可以通过开启以下配置来打开根据服务的serviceId来匹配路由,默认是大写
      routes:
        - id: 163  # 路由 ID,保持唯一
          uri: http://www.163.com/ # uri指目标服务地址,lb代表从注册中心获取服务
          predicates: # 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
            - Path=/163
        - id: good1  # 路由 ID,保持唯一
          uri: http://localhost:8800/ # uri指目标服务地址,lb代表从注册中心获取服务
          predicates: # 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
            - Path=/good/**,
          #  若无StripPrefix过滤器时,gateway 发送请求到后台服务producer的url就是http://producer/producerInEureka/hello
          #  若有StripPrefix过滤器时,gateway会根据StripPrefix=1所配的值(这里是1)去掉URL路径中的部分前缀(这里去掉一个前缀,即去掉producerInEureka)
          filters:
            - StripPrefix=1
        - id: good2  # 路由 ID,保持唯一
          uri: lb://e-commerce-demo-good # uri指目标服务地址,lb代表从注册中心获取服务
          predicates: # 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
            - Path=/g/**,
          filters:
            - StripPrefix=1
            # 它的作用和StripPrefix正相反,是在URL路径前面添加一部分的前缀 对应工程设置了server.servlet.context-path
            - PrefixPath=/demo-good

5.3.2 动态路由

GatewayConfig :配置类, 读取 Nacos 相关的配置项, 用于配置监听器

package cn.flowboot.e.commerce.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * <h1>配置类, 读取 Nacos 相关的配置项, 用于配置监听器</h1>
 * */
@Configuration
public class GatewayConfig {
   

    /** 读取配置的超时时间 */
    public static final long DEFAULT_TIMEOUT = 30000;

    /** Nacos 服务器地址 */
    public static String NACOS_SERVER_ADDR;

    /** 命名空间 */
    public static String NACOS_NAMESPACE;

    /** data-id */
    public static String NACOS_ROUTE_DATA_ID;

    /** 分组 id */
    public static String NACOS_ROUTE_GROUP;

    @Value("${spring.cloud.nacos.discovery.server-addr}")
    public void setNacosServerAddr(String nacosServerAddr) {
   
        NACOS_SERVER_ADDR = nacosServerAddr;
    }

    @Value("${spring.cloud.nacos.discovery.namespace}")
    public void setNacosNamespace(String nacosNamespace) {
   
        NACOS_NAMESPACE = nacosNamespace;
    }

    @Value("${nacos.gateway.route.config.data-id}")
    public void setNacosRouteDataId(String nacosRouteDataId) {
   
        NACOS_ROUTE_DATA_ID = nacosRouteDataId;
    }

    @Value("${nacos.gateway.route.config.group}")
    public void setNacosRouteGroup(String nacosRouteGroup) {
   
        NACOS_ROUTE_GROUP = nacosRouteGroup;
    }
}

DynamicRouteServiceImpl :事件推送 Aware: 动态更新路由网关 Service

package cn.flowboot.e.commerce.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 事件推送 Aware: 动态更新路由网关 Service
 * */
@RequiredArgsConstructor
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
   

    /** 写路由定义 */
    private final RouteDefinitionWriter routeDefinitionW
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值