一 工程创建与初始化
1 创建父工程
file-->new module
2 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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jt</groupId>
<artifactId>02-sca-files</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3 删除父工程的src,再新建它的子过程
如果失误删除了创建的子过程,那么重新创建的时候会提示,该工程名已经exist存在,那是因为创建子工程时父工程下会有个modules标签,要把它删除掉,才可以创建同名的子工程.
注意:再创建它的子工程时,可能子工程的pom文件不可用,就要 :
4 子工程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>02-sca-files</artifactId>
<groupId>com.jt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-resource</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!--添加所需的依赖-->
<dependencies>
<!--Spring Boot Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--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>
<!--Sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--spring boot 流量监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
5 子工程resource包下创建bootstrap.yml配置文件
server:
port: 8881
spring:
application:
name: sca-resource
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yml
discovery:
server-addr: localhost:8848
# sentinel:
# eager: true #服务启动时与sentinel通讯,在sentinel控制台创建服务菜单
# transport:
# dashboard: localhost:8180
# port: 8719
6 子工程创建启动类
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class,args);
}
}
7 IDE中设置nacos启动
启动nacos: 启动后查看又没有coused by ,有没有报错的地方,检查原因再重新启动,ok即可
8 再创建一个子工程用于编辑UI界面
9 创建UI子工程的启动类,pom.xml文件添加web的依赖
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceUIApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceUIApplication.class, args);
}
}
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
</dependencies>
10 UI工程的resource包创建html文件用于编辑前端上传文件页面
10.1 resource下创建静态目录,名为static
10.2 目录下创建html文件,并编辑部分内容进行测试
1) 编辑测试内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<h1>文件上传案例演示</h1>
<form id="fileForm" method="post" enctype="multipart/form-data" onsubmit="return doUpload()">
<div>
<label>上传文件
<input id="uploadFile" type="file" name="uploadFile">
</label>
</div>
<button type="submit">上传文件</button>
</form>
</body>
<script>
//jquery代码的表单提交事件
function doUpload(){
debugger //可以在页面(客户端)控制台进行debug操作
//获得用户选中的所有图片(获得数组)
let files=document.getElementById("uploadFile").files;
if(files.length>0){
//获得用户选中的唯一图片(从数组中取出)
let file=files[0];
//开始上传这个图片
//由于上传代码比较多,不想和这里其它代码干扰,所以定义一个方法调用
upload(file);
}
//阻止表单提交效果
return false;
};
// 将file上传到服务器的方法
function upload(file){
//定义一个表单
let form=new FormData();
//将图片添加到表单中
form.append("uploadFile",file);
//异步提交,ajax请求最大弊端是不能跨域,一种方式是加@CrossOrigin
axios({
url:"http://localhost:8881/resource/upload/",
method:"post",
data:form
}).then(function(response){
alert("upload ok")
console.log(response.data);
})
}
</script>
</html>
2) 启动sca-resource和sca-resouceui的两个启动类,ui工程未创建yml配置文件来修改端口号,所以ui工程的端口号默认是8080,所以就在网页输入下面的url,可以查看到如下效果:
11 sca-resourse下创建控制层,用于实现文件上传服务
package com.jt.resource.controller;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/*通过此对象实现文件的上传服务*/
@CrossOrigin//跨域的一种方式,但是如果以后加拦截器,这种方式就没有作用了
@Slf4j
@RestController
@RequestMapping("/resource/")
public class ResourceController {
//当类上面添加了@Slf4J就不用自己创建下面的日志对象了
// private static final Logger log =
// LoggerFactory.getLogger(ResourceController.class);
private String resourcePath="d:/uploads/" ;
@PostMapping("/upload/")
public String uploadFile(MultipartFile uploadFile) throws IOException {
//1.创建文件存储目录(按照时间创建--yyyy/MM/dd)
//1.1获取当前时间的目录--JDK8新特性
String dateDir = DateTimeFormatter.ofPattern("yyyy/MM/dd")
.format(LocalDate.now());
//1.2构建目录文件对象
File uploadFileDir = new File(resourcePath,dateDir);
if (!uploadFileDir.exists())uploadFileDir.mkdirs();
//2.给文件起个名字,(尽量不重复)
//2.1获取原始文件后缀
String originalFilename = uploadFile.getOriginalFilename();
String ext = originalFilename.substring(
originalFilename.lastIndexOf("."));
//2.2 构建新的文件名--前缀+后缀
String newFilePrefix = UUID.randomUUID().toString();
String newFileName=newFilePrefix+ext;
//3.开始实现文件上传
//3.1构建新的文件对象,指向实际上传的文件最终地址
File file=new File(uploadFileDir,newFileName);
//3.2上传文件(向指定服务位置写文件数据)
uploadFile.transferTo(file);
return "upload ok";
}
}
注意: 前端使用Ajax异步请求局部刷新,但是缺点就是无法跨域,所以需要解决跨域的问题,
之前学过使用@CrossOrigin解决跨域的方式,但是如果以后添加过滤器,这种方式就没有作用了,会判定这种方式不合法!!!!!
目前,先以该跨域方式上传图片进行测试:
页面控制台:
12 sca-resource下添加过滤器跨域配置类,创建对象交给spring管理
package com.jt.resource.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/*第二种跨域方式:基于过滤器作跨域配置*/
@Configuration
public class CorsFilterConfig {
//Spring MVC中注册过滤器使用FilterRegistrationBean对象
@Bean
public FilterRegistrationBean<CorsFilter>
filterRegistrationBean (){
//1.构建基于Url方式的跨域对象
UrlBasedCorsConfigurationSource configSource =
new UrlBasedCorsConfigurationSource();
//2.构建url请求规则配置
CorsConfiguration config =new CorsConfiguration();
//2.1允许所有请求头跨域
config.addAllowedHeader("*");
//2.2允许所有请求方式(get,post,put,delete...)
config.addAllowedMethod("*");//get,post...
//2.3允许所有你请求ip,
config.addAllowedOrigin("*");//http://ip:port
//2.4允许携带cookie进行跨域
config.setAllowCredentials(true);
//2.5将这个跨域配置应用到具体的url
configSource.registerCorsConfiguration("/**",config);
//3.注册过滤器
FilterRegistrationBean fBean =
new FilterRegistrationBean(
new CorsFilter(configSource));
//4.设置过滤器优先升级
//fBean.setOrder(Integer.MIN_VALUE);//数字越小优先级越高
fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);//和上面一样,整数的最小值
return fBean;
}
}
启动sca-resource-ui和和sca-resource工程的启动类,进行上传文件测试:
观察控制台发现没有问题,再查看上传到指定路径下有没有上传成功
拓展
如果上传文件,启动后失败页面控制台报错,就需要在前端html文件添加debugger,来断点调试,找原因
13 上传图片,图片返回地址给后端控制台,这个地址在网页上可以显示出来
13.1 修改sca-resource控制层
package com.jt.resource.controller;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/*通过此对象实现文件的上传服务*/
//@CrossOrigin//跨域的一种方式,但是如果以后加拦截器,这种方式就没有作用了
@Slf4j
@RestController
@RequestMapping("/resource/")
public class ResourceController {
//当类上面添加了@Slf4J就不用自己创建下面的日志对象了
// private static final Logger log =
// LoggerFactory.getLogger(ResourceController.class);
@Value("${jt.resource.path}")//写到配置文件中
private String resourcePath;//="d:/uploads/" ;
@Value("${jt.resource.host}")
private String resourceHost;//="http://localhost:8881/";
@PostMapping("/upload/")
public String uploadFile(MultipartFile uploadFile) throws IOException {
//1.创建文件存储目录(按照时间创建--yyyy/MM/dd)
//1.1获取当前时间的目录--JDK8新特性
String dateDir = DateTimeFormatter.ofPattern("yyyy/MM/dd")
.format(LocalDate.now());
//1.2构建目录文件对象
File uploadFileDir = new File(resourcePath,dateDir);
if (!uploadFileDir.exists())uploadFileDir.mkdirs();
//2.给文件起个名字,(尽量不重复)
//2.1获取原始文件后缀
String originalFilename = uploadFile.getOriginalFilename();
String ext = originalFilename.substring(
originalFilename.lastIndexOf("."));
//2.2 构建新的文件名--前缀+后缀
String newFilePrefix = UUID.randomUUID().toString();
String newFileName=newFilePrefix+ext;//不加后缀ext会下载,因为它不识别
//3.开始实现文件上传
//3.1构建新的文件对象,指向实际上传的文件最终地址
File file=new File(uploadFileDir,newFileName);
//3.2上传文件(向指定服务位置写文件数据)
uploadFile.transferTo(file);
String fileRealPath = resourceHost + dateDir + "/" + newFileName;
log.debug("fileRealPath {}",fileRealPath);
//后续可以将上传的文件信息写入到数据库?
return fileRealPath;
}
}
13.2 配置中心收入资源存储地址,可动态获取资源地址和服务地址
这是因为以后上传的地址可能会发生变化,所以设为了动态
nacos服务注册中心的配置列表,新建sca-resource
13.3 上传图片操作
13.4 控制台返回值图片的地址,复制地址在网页查看
由地址级别返回的地址:
cv地址在网页查看:
14 添加网关gateway,进行跨域/限制转发请求以及负载均衡的功能(后期再学习如何认证)
14.1 在02-sca-files父工程下创建网关子工程sca-resource-gateway
注意:不能和其他工程的网关工程名字相同,有可能启动时调用失败
14.2 网关工程的pom.xml文件添加网关/nacos依赖
<dependencies>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
14.2 创建网关工程的启动类
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceGatewayApplication.class,args);
}
}
14.3 网关工程的resource包下创建名为bootsrap.yml的配置文件,连接noacs和网关路由配置等功能
server:
port: 9000
spring:
application:
name: sca-resource-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
file-extension: yml
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: router01
uri: lb://sca-resource
predicates:
- Path=/sca/resource/upload/**
filters:
- StripPrefix=1
14.4 解决跨域问题第一种方式: 创建跨域配置类
注意: 添加网关跨域配置类,需要把sca-resource包下的跨域配置类的@Configuretion注释掉,以后都有网关,所以跨域配置在网关编辑即可
package com.jt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
//import org.springframework.web.cors.UrlBasedCorsConfigurationSource;要和reactive的包区别开
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration //写个此跨域配置类也可以在bootstrap.yml文件中编辑跨域的配置,二选一即可
public class CorsFilterConfig {
@Bean
public CorsWebFilter corsWebFilter(){
//1.构建基于URL方式的跨域配置
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
//2.进行跨域配置
CorsConfiguration config=new CorsConfiguration();
//2.1允许所有ip:port进行跨域
config.addAllowedOrigin("*");
//2.2允许所有的请求头跨域
config.addAllowedHeader("*");
//2.3允许所有的请求方式跨域
config.addAllowedMethod("*");
//2.4允许携带有效cookie跨域
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**",config);
return new CorsWebFilter(source);
}
}
14.5 解决跨域问题第二种方式: 在网关配置文件bootstrap.yml文件中添加跨域配置
server:
port: 9000
spring:
application:
name: sca-resource-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
file-extension: yml
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: router01
uri: lb://sca-resource
predicates:
- Path=/sca/resource/upload/**
filters:
- StripPrefix=1 #去掉1个前缀,即path中的sca
globalcors: #跨域配置 或者写个跨域配置类二选一即可
cors-configurations:
'[/**]':
allowedOrigins: "*" #都要带s
allowedHeaders: "*"
allowedMethods: "*"
allowedCredentials: true
注意:解决跨域问题的两种方式二选一即可,第二中要注意格式,以及都要加s,选择第二种,要把第一种的@Configuration注释掉,启动时没就不会被调用,但是如果两种方式都用的话,会报错
14.6 修改UI子工程访问服务资源客户端的url
14.6 测试
第一步: 首先nacos启动再启动三个服务器(sca-resource/sca-resource-ui/sca-resource-gateway)
页面输入网页fileupload.html文件中的url进行上传文件
第二步:查看后端sca-resource服务器的控制台,复制返回的文件地址
第三步:复制文件地址,在网页上粘贴查看,若能查看代表成功
15 网关层面添加sentinel限流熔断功能
15.1 网关工程pom.xml文件添加sentinel依赖,进行限流和监控
<!--sentinel限流-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--限流监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--网关依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
15.2 网关工程的bootstrap.yml文件添加sentinel配置
sentinel: #限流设计 ,在spring.cloud下的
transport:
dashboard: localhost:8180
eager: true
15.3 启动网关项目,检测sentinel控制台的网关菜单
启动时,添加sentinel的jvm参数,通过此菜单可以让网关服务在sentinel控制台显示不一样的菜单
-Dcsp.sentinel.app.type=1
假如是在idea中,可以参考下面的图进行配置
15.4 自定义网关限流返回值
注意:这里要添加一个对象转换成json串的依赖Gson或者fastJson都可以,此时我们选择Gson,在网关工程pom.xml文件添加依赖为:
<!--Gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
package com.jt.config;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.google.gson.Gson;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/*自定义网关限流返回值*/
@Configuration
public class GatewayConfig {
public GatewayConfig(){ //构造方法
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(
ServerWebExchange serverWebExchange,
Throwable throwable) {
//1.构建响应数据
Map<String,Object> map=new HashMap<>();
map.put("state",429);
map.put("message","two many request");
//2.返回值方式一:基于alibaba 的fastjson将对象转换为json串
//String jsonStr = JSON.toJSONString(map);//fastjson
//2.返回值方式二:基于Google公司的gson将对象转换为json
Gson gson=new Gson();
String jsonStr = gson.toJson(map);
//3.创建Mono对象,将结果响应到客户端
return ServerResponse.ok().body(Mono.just(jsonStr),String.class);
}
});
}
}
15.5 启动服务器,查看访问效果:
1)启动顺序:
nacos->sentinel->sca-resource/sca-resource-ui/sca-resource-gateway
2) 网页输入页面url: http://localhost:8080/fileupload.html 进行文件上传
3) 修改ui界面客户端请求后触发的返回数据
4) 登录并查看sentinel控制台
15.6 编辑流控规则,查看网关限流效果
网页连续上传文件查看网页效果:
16 使用AOP添加日志对象,在方法执行之前和之后作日志记录
16.1 sca-resource工程pom.xml文件添加AOP依赖
<!--aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
16.2 自定义注解作为切入点
package com.jt.resource.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**自定义注解@RequiredLog*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String value() default "";
}
16.3 创建切面对象,用于做日志业务增强
package com.jt.resource.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Slf4j //创建日志对象
@Aspect //切面类的标识
@Component //交给spring管理的对象
public class LogAspect {
@Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
public void doLog(){}//锦--注解描述的方法
@Around("doLog()")
//@Around("@annotation(com.jt.resource.annotation.RequiredLog)")//可以直接把切入点写在Around里,但不好理解
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.debug("Before {}",System.currentTimeMillis());
Object result = joinPoint.proceed();//执行执行链(其他切面,目标方法--锦)
log.debug("After {}",System.currentTimeMillis());
return result;//目标方法--切入点方法的执行结果
}
}