基于Spring Cloud Alibaba 解决方案实现文件上传
一、业务描述
基于Spring Cloud Alibaba解决方案实现文件上传,例如:
二、工程创建及初始化
1、工程结构
参考如下工程结构,进行项目创建,例如:
2、创建父工程:02-sca-files
创建项目父工程用来管理项目依赖
3、父工程初始化
打开父工程的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>
4、创建文件服务工程
创建用于处理文件上传业务的工程,例如:
5、创建客户端服务工程
创建一个客户端工程,在此工程中定义一些静态页面,例如文件上传页面。
三、文件资源服务实现
1、创建sca-resource工程,服务端
1.1)添加项目依赖
在sca-resource工程中添加如下依赖:
<!--Spring Boot Web (服务-内置tomcat)-->
<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>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
1.2)服务初始化配置,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 #从哪里去查找服务
# resources: #配置静态资源存储位置(默认resources/static目录),现在写到了配置中心
# static-locations: file:d:/uploads
# sentinel:
# eager: true #即时通讯,服务启动时就要与sentinel通讯,在sentinel控制台创建服务菜单
# transport:
# dashboard: localhost:8180
# port: 8719
#自定义上传的资源地址和服务器地址(写到了配置中心)
#jt:
# resource:
# path: d:/uploads/
# host: http://localhost:8881/
#logging:
# level:
# com.jt: debug
1.3)构建项目启动类
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);
}
}
1.4)idea 启动nacos
第一步:打开服务编辑配置,例如:
第二步:添加Shell Script,例如:
第三步:添加nacos相关信息,例如:
启动成功:
1.5) 启动sca-resource
2、创建sca-resource-ui工程,客户端
2.1)添加项目依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.2)构建项目启动类
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication .class, args);
}
}
2.3)创建文件上传页面
2.3.1)创建静态资源目录 static
2.3.2)定义文件上传页面fileupload.html文件,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上载演示</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<style>
ul>li{
list-style-type: none;
}
</style>
</head>
<body>
<h1>文件上传案例演示:</h1>
<form id="fileForm" method="post"
enctype="multipart/form-data"
onsubmit="return doUpload()">
<div>
<ul>
<li><input id="uploadFile" type="file" name="uploadFile"></li>
<li><button type="submit">上传文件</button></li>
</ul>
</div>
</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);
let url="http://localhost:8881/resource/upload/";
//异步提交方式1
axios.post(url,form)
.then(function (response){
alert("upload ok")
console.log(response.data);
})
.catch(function (e){//失败时执行catch代码块
console.log(e);
})
//异步提交方式2
// axios({
// url:"http://localhost:8881/resource/upload/",
// method:"post",
// data:form
// }).then(function(response){
// alert("upload ok")
// console.log(response.data);
// })
}
</script>
</html>
2.3.4)启动服务,访问网址:http://localhost:8080/fileupload.html
3、启动服务访问测试
第一步:启动nacos
第二步:启动sca-resource服务
第三步:启动sca-resource-ui服务
第四步:打开浏览器进行访问测试,例如:
4、Controller逻辑实现
定义处理上传请求的Controller对象,例如:
package com.jt.resource.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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;
/**
* 通过此对象实现文件上传服务
*/
@Slf4j //lombok自动创建日志对象
//@CrossOrigin //加了过滤器,这个注解就失效了
@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获取当前时间的一个目录
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);
String fileRealPath=resourceHost+dateDir+"/"+newFileName;
log.debug("fileRealPath {}",fileRealPath);
//后续可以将上传的文件信息写入到数据库?
return fileRealPath;
}
}
5、跨域配置实现
访问MVC时,会先经过过过滤器,如果过滤器有配置,controlle层的@CrossOrigin这个注解就失效了,因此,需要配置基于过滤器方式进行跨域配置
我们在通过客户端工程,访问文件上传服务时,需要进行跨域配置,例如:
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> filterFilterRegistrationBean(){
//1.对此过滤器进行配置(跨域设置-url,method)
UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
CorsConfiguration config=new CorsConfiguration();
//2.构建url规则
//2.1允许哪种请求头跨域
config.addAllowedHeader("*");
//2.2允许哪种方法类型跨域 get post delete put
config.addAllowedMethod("*");
//2.3允许哪些请求源(ip:port)跨域
config.addAllowedOrigin("*");
//2.4是否允许携带cookie跨域
config.setAllowCredentials(true);
//2.5将这个跨域配置应用到具体的url
configSource.registerCorsConfiguration("/**", config);
//3.注册过滤器
FilterRegistrationBean<CorsFilter> fBean= new FilterRegistrationBean(new CorsFilter(configSource));
//4.设置过滤器优先级
fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return fBean;
}
}
6、测试,上传是否成功
http://localhost:8080/fileupload.html
7、上传文件后,文件预览
7.1)修改配置文件:
server:
port: 8881
spring:
application:
name: sca-resource #应用()名字
cloud:
nacos:
config:
server-addr: localhost:8848 #配置文件地址
file-extension: yml
discovery:
server-addr: localhost:8848 #从哪里去查找服务
resources: #配置静态资源存储位置(默认resources/static目录),现在写到了配置中心
static-locations: file:d:/uploads //从这和
7.2)测试
8、基于后端配置文件bootstrap.yml,动态接收上传路径、预览路径
8.1)修改配置文件
server:
port: 8881
spring:
application:
name: sca-resource
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yml
discovery:
server-addr: localhost:8848
resources: #配置静态资源存储位置(默认resources/static目录),现在写到了配置中心
static-locations: file:d:/uploads
# sentinel:
# eager: true #服务启动时就要与sentinel通讯,在sentinel控制台创建服务菜单
# transport:
# dashboard: localhost:8180
# port: 8719
#自定义上传的资源地址和服务器地址(写到了配置中心)
jt:
resource:
path: d:/uploads/
host: http://localhost:8881/
8.2)修改后端代码
//读取配置文件中的参数
//获取上传路径
@Value("${jt.resource.path}")
private String resourcePath;//"d:/uploads/";
//获取预览虚拟地址前缀
@Value("${jt.resource.host}")
private String resourceHost;//"http://localhost:8881/";
9、基于nacos服务注册中心配置,动态接收上传路径、预览路径
9.1)添加配置
9.2)重启nasoc服务
@RefreshScope//这个注解描述类时,当配置中心数据发生变化会对属性进行重新初始化,不需要重启服务
@RefreshScope//这个注解描述类时,当配置中心数据发生变化会对属性进行重新初始化,不需要重启服务
@Slf4j //lombok自动创建日志对象
//@CrossOrigin //加了过滤器,这个注解就失效了
@RestController
@RequestMapping("/resource/")
四、API网关(Gateway)工程实践
1、概述
API 网关是外部资源对服务内部资源访问的入口,所以文件上传请求应该首先请求的是网关服务,然后由网关服务转发到具体的资源服务上。
2、服务调用架构
3、工程项目结构设计
4、创建网关工程及初始化
第一步:创建sca-resource-gateway工程,例如:
第二步:添加项目依赖,例如:
<?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>02-sca-gateway</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<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>
<!--云服务配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
</project>
第三步:创建配置文件bootstrap.xml,然后进行初始配置,例如:
server:
port: 9000
spring:
application:
name: sca-resource-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway: # API Gateway (负责API管理)
discovery:
locator:
enabled: true #开启通过服务注册中心的serviceId创建路由
routes:
- id: route01 #http://localhost:9000/nacos/provider/echo/hello
#uri: http://localhost:8081/
uri: lb://sca-resource #lb表示负载均衡,为服务前缀,不能随意写,底层调用的ribbon实现的
predicates: ###匹配规则
- Path=/sca/resource/upload/**
filters:
- StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos
logging:
level:
org.springframework.cloud.gateway: debug
第四步:构建项目启动类,并进行服务启动,检测是否正确,例如:
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);
}
}
5、网关跨域配置
当我们基于Ajax技术访问网关时,需要在网关层面进行跨域设计,例如:
package com.jt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
//@Configuration
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允许所有请求方式跨域:get,post,..
config.addAllowedMethod("*");
//2.4允许携带有效cookie进行跨域
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**",config);
return new CorsWebFilter(source);
}
}
Spring Gateway工程中的跨域设计,除了可以在网关项目,以java代码方式进行跨域过滤器配置,还可以直接在配置文件进行跨域配置,例如:
spring:
cloud:
gateway:
globalcors: #跨域配置
corsConfigurations:
'[/**]':
allowedOrigins: "*" #允许所有ip:port进行跨域
allowedHeaders: "*" #允许所有请求头跨域
allowedMethods: "*" #允许所有请求方式跨域:get,post,..
allowCredentials: true #允许携带有效cookie进行跨域
6、启动工程进行服务访问
首先打开网关(Gateway),资源服务器(Resource),客户端工程服务(UI),然后修改fileupload.html文件中访问资源服务端的url,例如:
let url="http://localhost:9999/nacos/resource/upload/";
接下来进行访问测试,例如:
http://localhost:8080/fileupload.html
问题总结
1、跨域访问问题
2、Nacos 服务注册问题
3、文件上传404问题
4、请求资源405异常
5、请求资源500异常
6、BeanCreationException 异常
7、服务名无效或没有定义
8、静态资源
1、静态资源源存储位置(默认resources/static目录),存在该目录下,访问时:localhos:端口号/网页名字
2、resources: #配置静态资源存储位置(默认resources/static目录),现在写到了配置中心
static-locations: file:d:/uploads
修改了静态资源,存储在d:/uploads下,访问时:localhos:端口号/uploads/网页