记录搭建详细过程
使用一个主项目管理依赖,其他都是子项目
1:搭建主项目子项目
首先创建主项目,选择的是maven.直接file-new-project创建,
创建maven项目
创建完成后是个简单的maven项目,删掉src文件夹变成了:
然后修改pom.xml
首先把<packaging>packaging>修改成
然后增加依赖内容
注意:
dependencyManagement( 管理 jar 包的版本 , 让子项目中引用一个依赖而不用显示的列出版本号 )
dependencyManagement 与 dependencies 区别 :
dependencies 即使在子项目中不写该依赖项 , 那么子项目仍然会从父项目中继承该依赖项(全部继
承)
dependencyManagement 里只是声明依赖 , 并不实现引入 , 因此子项目需要显示的声明需要用的依
赖。
如果不在子项目中声明依赖 , 是不会从父项目中继承下来的。
只有在子项目中写了该依赖项 , 并且没有指定具体版本 , 才会从父项目中继承该项 , 并且 version 和
scope 都读取自父 pom 。
另外如果子项目中指定了版本号 , 那么会使用子项目中指定的 jar 版本。
4.0.0
org.example
ceshiwanzheng
1.0-SNAPSHOT
pom
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
Greenwich.SR1
2.7.0
org.springframework.boot
spring-boot-autoconfigure
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-dependencies
2.1.6.RELEASE
pom
import
com.alibaba.cloud
spring-cloud-alibaba-sentinel
2.1.1.RELEASE
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.1.0.RELEASE
pom
import
org.springframework.cloud
spring-cloud-starter-openfeign
2.1.1.RELEASE
pom
import
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.0.1
主项目就算创建完成了。
然后创建子项目,对着主项目,右键New-Module 这样创建就是子项目:
创建时候选择spring创建,
注意:Type是maven。 java Version选择自己的,反正我的Java是1.8的
选择依赖的必须选择spring Web,不然后面会有springboot启动类没有run的错误
然后创建出的文件夹如下:provider项目就是子项目
然后修改主项目和子项目的pom
主项目修改,把子项目provider加入到主项目中
子项目修改pom,修改较多,我一次性把所有可能需要的都加进去了,复制这个pom。修改一下项目信息
和parent信息就可以直接使用了,parent就是主项目的信息。
4.0.0
com.example
provider
0.0.1-SNAPSHOT
provider
Demo project for Spring Boot
org.example
ceshiwanzheng
1.0-SNAPSHOT
8
8
org.springframework.cloud
spring-cloud-starter-openfeign
commons-codec
commons-codec
1.11
io.github.openfeign
feign-httpclient
10.1.0
cn.hutool
hutool-all
5.8.5
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
8.0.11
com.baomidou
mybatis-plus-boot-starter
3.4.3
com.baomidou
mybatis-plus-core
3.4.3.1
org.springframework.boot
spring-boot-starter-test
test
com.baomidou
mybatis-plus-generator
3.5.3
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
com.baomidou
mybatis-plus
3.5.2
org.mybatis.spring.boot
mybatis-spring-boot-starter
com.baomidou
mybatis-plus-extension
3.5.2
org.springframework.boot
spring-boot-starter-logging
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-alibaba-sentinel
redis.clients
jedis
2.9.1
org.springframework.boot
spring-boot-starter-web
org.apache.commons
commons-pool2
2.9.0
org.projectlombok
lombok
true
LATEST
junit
junit
4.12
com.alibaba
druid
1.2.3
src/main/java
**/*.xml
src/main/resources
**/**
false
org.springframework.boot
spring-boot-maven-plugin
true
然后创建bootstrap.yaml。因为nacos是先读取boostrap.yaml里的配置再读取applicaion.yaml。在使用配置中心的时候application.yaml可以自己写也可以直接配置在nacos的配置中心里面。下面是bootstrap.yaml 下面的配置都是与在nacos操作页面上进行配置的一致。在操作页面配置后需要进行对应更改。
server:
port: 8888
spring:
application:
name: provider
profiles:
active: dev # 环境,自己对应
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #注册中心地址
config:
server-addr: 127.0.0.1:8848 #配置中心地址
file-extension: yaml #后缀名
group: devGroup #分组,自己对应
namespace: c38781c7-c36d-412a-9d8e-43b7547d4ae8 #命名空间的id,自己对应
我的applicaion
spring:
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:./../images
datasource:
url: jdbc:mysql://127.0.0.1:3306/mre?&serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: chengdu2020
druid:
max-active: 200
# 超过时间限制是否回收
remove-abandoned: true
# 超时时间;单位为秒。180秒=3分钟
remove-abandoned-timeout: 180
# 关闭abanded连接时输出错误日志
log-abandoned: true
mybatis-plus:
configuration:
map-underscore-to-camel-case: false #开启数据库下划线字段映射为驼峰
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl #开启sql控制台打印
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
然后要正确启动的话就需要把启动类加上@EnableDiscoveryClient 这是服务发现
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
最后。获取配置中心的值的例子
写个controller用于测试
@RestController("/provider")
@Slf4j
@RefreshScope //动态刷新
public class TestController {
@Value("${config.info}")
private String info; //该属性值是从nacos配置中心拉取到的配置
@GetMapping("/testConfig")
public String testConfig(){
return info;
}
}
如图:获取config.info的值就和获取本地yml里面的值是一样的方式。
而配置这个值就需要打开nacos管理中心,首先是下载nacos。我下载的是windows版本。然后运行nacos.复制这个地址到服务器上就能用
打开网址登陆,默认账号密码都是nacos 登陆后如下:首先是配置命名空间,然后在配置列表里就可以点进对应配置命名空间的yaml ,这里的命名空间后面这串代码就是要填写在bootstrap.yaml里的命名空间id 然后点击+ 增加配置文件,
配置的属性就要和boostrap.yaml里对应了
到这一步nacos的配置中心和注册中心就算使用完成了。
上面完成的是nacos的配置。。接下来是完成gateWay的
gateway是网关。最常见的作用是统一访问路径。如:多个服务的端口号是不一样的。对于前端来说访问不同服务就得加不同的端口号地址。 使用gateway 那么访问的端口号就是gateway得的端口号
在建立好前面的provider之后。创建一个服务是gateway
在pom.xml几乎和provider一样。只是多加了
org.springframework.cloud
spring-cloud-starter-gateway
boostrap.xml
server:
port: 9999
spring:
application:
name: gateway
profiles:
active: dev # 环境
cloud:
nacos:
discovery:
server-addr: 192.168.150.1:8848 #注册中心地址
config:
server-addr: 192.168.150.1:8848 #配置中心地址
file-extension: yaml #后缀名
group: devGroup #分组
namespace: c38781c7-c36d-412a-9d8e-43b7547d4ae8 #命名空间的id
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
routes: # 路由数组,路由就是指定当请求满足什么条件的时候,转到 哪个微服务
- id: provider # 路由的Id,没有固定规则,但要求唯一,建议配合服务名
# uri: http://localhost:9083 # 匹配后,请求转发到的地址
uri: lb://provider # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
predicates: # 断言:就是路由转发要满足的条件
- Path=/provider/** # 当请求路径满足Path指定的规则时,才进行路由转发
- id: consumer
# uri: http://localhost:9084
uri: lb://consumer # lb 指的是负载均衡,后面跟的是具体微服务在nacos中的标识
predicates:
- Path=/prefix/consumer/**
filters: # 过滤器(在请求传递过程中,对请求做一些手脚)
- StripPrefix=1 # 在请求转发之前去掉一层路径,http://localhost:9081/prefix/consumer/service,实际请求会去掉prefix
application.yaml和provider一样就行
启动类也一样就行,
然后测试。访问localhost:9999/provider/testConfig 就可以实际访问到provider服务对应的方法了。
网关弄好后可以做一些事情,比如。gateway里写filter获取request和response的信息
这里我把我完整代码贴出来 gateway的代码结构如下:
1:HttpRequestFilter 获取request的filter. 因为多个filter获取数据其实会出现request只能访问一次的情况。
这个代码不存在这个问题,可以直接使用。
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String method = request.getMethodValue();
String contentType = request.getHeaders().getFirst("Content-Type");
if ("POST".equals(method)) {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
try {
String bodyString = new String(bytes, "utf-8");
System.out.println(bodyString);
System.out.println(request.getPath());
System.out.println(request.getURI());
log.info(bodyString);//打印请求参数
exchange.getAttributes().put("POST_BODY", bodyString);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
DataBufferUtils.release(dataBuffer);
Flux cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest)
.build());
});
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -200;
}
}
RequestUtil是用于解析request的
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import reactor.core.publisher.Flux;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author mjw
* @date 2020/3/30
*/
public class RequestUtil
{
/**
* 读取body内容
* @param serverHttpRequest
* @return
*/
public static String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest){
//获取请求体
Flux body = serverHttpRequest.getBody();
StringBuilder sb = new StringBuilder();
body.subscribe(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
// DataBufferUtils.release(buffer);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
sb.append(bodyString);
});
return formatStr(sb.toString());
}
/**
* 去掉空格,换行和制表符
* @param str
* @return
*/
private static String formatStr(String str){
if (str != null && str.length() > 0) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(str);
return m.replaceAll("");
}
return str;
}
}
HttpResponseFilter是获取response的
import java.nio.charset.StandardCharsets;
import java.util.List;
@Slf4j
@Component
public class HttpResponseFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
ServerHttpResponse originalResponse = exchange.getResponse();
System.out.println(originalResponse.isCommitted());
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono writeWith(Publisher extends DataBuffer> body) {
if (body instanceof Flux) {
Flux extends DataBuffer> fluxBody = (Flux extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffer);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
//释放掉内存
DataBufferUtils.release(join);
String s = new String(content, StandardCharsets.UTF_8);
List strings = exchange.getResponse().getHeaders().get(HttpHeaders.CONTENT_ENCODING);
s = new String(content, StandardCharsets.UTF_8);
System.out.println("响应信息"+s);
log.info("bodyString: {}", s);//打印请求响应值
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
return -200;
}
}
AuthGlobalFilter是想用于登陆相关的
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author mjw
* @date 2020/3/24
*/
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered
{
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
String bodyContent = RequestUtil.resolveBodyFromRequest(exchange.getRequest());
System.out.println("你好不好啊"+bodyContent);
// TODO 身份认证相关逻辑
return chain.filter(exchange.mutate().build());
}
@Override
public int getOrder()
{
return -100;
}
}
所有代码就写完了
我们再回到provider这个服务
因为要获取body的原因。我改了controller的testConfig方法
这样。前端postman测试。就可以使用 raw application/json 这样的方式传过来。ConditionDto是随意写的用于测试的实体类。
@PostMapping("/testConfig")
public String testConfig ( @RequestBody ConditionDto request){
System.out.println(request.getCity());
return info;
}
ConditionDto.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ConditionDto {
private String city;
private String name;
}
就这样。在使用postman访问http://localhost:9999/provider/testConfig的时候就可以获取到body的内容了。