本章节讲解如何构建SpringCloud alibaba项目,以父子工程形式搭建。
此次框架选型是对Spring Cloud Netflix 框架组件的升级与替换。
Spring Cloud alibaba组件包括:Nacos (discovery、config)、Sentinel
Spring Cloud 组件包括:OpenFeign+ LoadBalancer 、Sleuth、Gateway
其他组件:RabbitMQ 、redis、mybatis-plus、knife4j、JWT、ShardingSphere-jdbc
例如:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.10version>
<relativePath/>
parent>
<groupId>com.kelvingroupId>
<artifactId>onlinestoreartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>pompackaging>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<spring-boot.version>2.7.10spring-boot.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>compilescope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
package com.kelvin.common.http;
/***
* @title HttpStatusInfoInterface
* @desctption HTTP状态信息接口
* @author Administrator
* @create 2023/5/18 10:28
**/
public interface HttpStatusInfoInterface {
int getCode();
String getMessage();
}
package com.kelvin.common.http;
/***
* @title HttpStatusEnum
* @desctption Http状态码
* @author Administrator
* @create 2023/5/18 10:30
**/
public enum HttpStatusEnum implements HttpStatusInfoInterface{
//定义状态枚举值
SUCCESS(200 , "成功!"),
NOROLE(300 , "权限不足!"),
USER_TOKEN_NOT_EXISTS(301 , "无效的token信息!"),
PRODUCT_STOCK_NOT_ENOUGH(302 , "商品的库存不足!"),
BODY_NOT_MATCH(400 , "数据格式不匹配!"),
NOT_FOUND(404 , "访问资源不存在!"),
FLOW_LIMIT(490 , "接口流量已超出,限制访问!"),
INTERNAM_SERVER_ERROR(500 , "服务器内部错误!"),
SERVER_BUSY(503 , "服务器正忙,请稍后再试!"),
REQUEST_METHOD_SUPPORT_ERROR(10001 , "当前请求方法不支持!"),
REQUEST_DATA_NULL(10002 , "当前请求参数为空!"),
USER_NOT_EXISTS(10003 , "该用户不存在!"),
USER_INVALID(10004 , "当前登录信息已失效,请重新登录!"),
PASSWORD_ERROR(10005 , "密码错误!"),
USER_NAME_LOCK(10006 , "该账号已被锁定!")
;
//状态码
private int code;
//提示信息
private String message;
HttpStatusEnum(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
}
package com.kelvin.common.http;
import lombok.Data;
/***
* @title ResultDTO
* @desctption 控制器的统一返回类
* @author Administrator
* @create 2023/5/16 9:48
**/
@Data
public class ResultDTO<T> {
private Integer code ;
private String message;
private T data;
public ResultDTO() {
}
public ResultDTO(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
}
package com.kelvin.common.http;
/***
* @title HttpResultGenerator
* @desctption
* @author Administrator
* @create 2023/5/18 10:43
**/
public class HttpResultGenerator {
//正常返回时调用方法
public static ResultDTO success(Object data) {
return new ResultDTO(HttpStatusEnum.SUCCESS.getCode() , "接口调用成功!" , data);
}
//失败时调用方法(入参是异常枚举)
public static ResultDTO fail(HttpStatusEnum httpStatusEnum) {
return new ResultDTO(httpStatusEnum.getCode() , httpStatusEnum.getMessage() , null);
}
//失败时调用方法(提供给GlobalExceptionHandler类使用)
public static ResultDTO fail(int code , String message) {
return new ResultDTO(code , message , null);
}
}
package com.kelvin.common.dto;
import lombok.Data;
/***
* @title TokenDTO
* @desctption
* @author Administrator
* @create 2023/6/8 10:01
**/
@Data
public class TokenDTO {
private String token;
}
修改父工程指向
<parent>
<groupId>com.kelvingroupId>
<artifactId>onlinestoreartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-loadbalancerartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
<version>1.8.5version>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
<version>1.8.6version>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.kelvingroupId>
<artifactId>store-commonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-loadbalancerartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
server:
port: 80
spring:
main:
web-application-type: reactive
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos server 的地址
gateway: #网关路由配置
routes:
#将 drp-user-service 提供的服务隐藏起来,不暴露给客户端,只给客户端暴露 API 网关的地址 80
- id: user-api_routh #路由 id,没有固定规则,但唯一,建议与服务名对应
uri: lb://user-api #匹配后提供服务的路由地址
predicates:
#以下是断言条件,必选全部符合条件
- Path=/user/** #断言,路径匹配 注意:Path 中 P 为大写
- Method=GET,POST #只能时 GET 请求时,才能访问
metadata:
connect-timeout: 10
#单位毫秒
response-timeout: 10000
- id: authority-service_routh #路由 id,没有固定规则,但唯一,建议与服务名对应
uri: lb://auth-service #匹配后提供服务的路由地址
predicates:
#以下是断言条件,必选全部符合条件
- Path=/auth/** #断言,路径匹配 注意:Path 中 P 为大写
- Method=GET,POST #只能时 GET 请求时,才能访问
metadata:
connect-timeout: 10
#单位毫秒
response-timeout: 10000
sentinel:
transport:
#配置 Sentinel dashboard 地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#开启除了controller层其他链路调用
web-context-unify: false
package com.kelvin.storegateway.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.CorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
@Configuration
public class WebConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowedHeaders(Arrays.asList("content-type","token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public CorsWebFilter corsWebFilter(CorsConfigurationSource corsConfigurationSource) {
return new CorsWebFilter(corsConfigurationSource);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
}
package com.kelvin.storegateway.service;
import com.kelvin.common.dto.TokenDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
/***
* @title AuthService
* @desctption
* @author Administrator
* @create 2023/6/8 10:04
**/
@Component
@FeignClient(value = "auth-service")
public interface AuthService {
@PostMapping("auth/isTokenExpiration")
public Boolean validateToken(TokenDTO token);
}
package com.kelvin.storegateway.filter;
import com.kelvin.common.dto.TokenDTO;
import com.kelvin.storegateway.service.AuthService;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.stream.Collectors;
/***
* @title DrfGlobalFilter
* @desctption 登录验证
* @author Administrator
* @create 2023/5/15 14:17
**/
@Component
public class GatewayWebFilter implements GlobalFilter {
@Resource
private AuthService authService;
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
//如果登录请求,不用验证token
String path = request.getURI().getPath();
if(!path.contains("login")) {
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst("token");
if(StringUtils.isEmpty(token)) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
} else {
TokenDTO tokenDTO = new TokenDTO();
tokenDTO.setToken(token);
//token验证不通过,返回给前端401
Boolean aBoolean = authService.validateToken(tokenDTO);
if(aBoolean){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
}
}
return chain.filter(exchange);
}
}
通过上述操作可以轻松搭建出微服务架构,扩展十分容易。
父子工程构建已可用,持续更新中…