目录
项目简介
业务描述
基于Spring Cloud Alibaba解决方案实现文件上传,例如
初始架构设计
本次项目实践,整体上基于前后端分离架构,服务设计上基于spring cloud alibaba解决方案进行实现,例如:
说明,为了降低学习难度,这里只做了初始架构设计,后续会逐步基于这个架构进行演进,例如我们会加上网关工程,认证工程等.
工程创建及初始化
工程结构
参考如下工程结构,进行项目创建,例如:
创建父工程
创建项目父工程用来管理项目依赖.
创建文件服务工程
创建用于处理文件上传业务的工程,例如:
创建客户端服务工程
创建一个客户端工程,在此工程中定义一些静态页面,例如文件上传页面.
父工程初始化
打开父工程的pom.xml文件,添加如下依赖:
<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>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
文件资源服务实现
添加项目依赖
在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>
服务初始化配置
在项目的resources目录下创建bootstrap.yml配置文件(假如后续配置信息要写到配置中心配置文件名必须为bootstrap.yml),并添加如下内容:
server:
port: 8881
spring:
application:
name: sca-resource
servlet:
multipart:
max-file-size: 1000MB #控制上传文件的大小
max-request-size: 1000MB #请求数据大小
resources: #localhost:8881/app.png
static-locations: file:D:/pengke/ready/uploads #静态资源的存储目录,默认是resource/static
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
jt:
resource:
path: D:/pengke/ready/uploads
host: http://localhost:8881/
构建项目启动类
在当前工程中,创建项目启动类,例如:
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class, args);
}
}
类创建以后,启动当前项目,检测是否可以启动成功,是否有配置错误.
Controller逻辑实现
定义处理上传请求的Controller对象,例如:
package com.cy.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.CrossOrigin;
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 sun.util.calendar.LocalGregorianCalendar;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.UUID;
@RestController
@RequestMapping("/resource/")
@Slf4j
@RefreshScope//假如属性的值来自配置中心,配置中心内容变化,属性的值也要变化,就需要添加上此注解
//@CrossOrigin //用于在controller层面处理跨域
public class ResourceController {
@Value("${jt.resource.path}")
private String resourcePath;
@Value("${jt.resource.host}")
private String resourceHost;
/**
* 通过此方法处理文件上传的请求
* @param uploadFile 接收要上传的文件数据(参数名一定要与客户端提交
* @return 文件上传以后在服务器的实际存储路径,可以基于http协议访问到资源
* */
@PostMapping("/upload/")//文件上传的请求方式必须是post
public String uploadFile(MultipartFile uploadFile) throws IOException {
/*1.创建文件的存储目录(按年月日的结构进行存储)*/
//1.1 获取当前日期对应的字符串
//1.1.1 方式1
//SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd");
//String dateStr = sdf.format(new Date());
//1.1.2 方式二(基于jdk8中提供的日期API)
String dateStr =
DateTimeFormatter.ofPattern("/yyyy/MM/dd/").format(LocalDate.now());
log.debug("dateStr : {}",dateStr);
//1.2创建文件目录
File uploadDir = new File(resourcePath,dateStr);
if(!uploadDir.exists())uploadDir.mkdirs();
/*2.给文件一个新的名字(文件前缀随机产生,文件后缀不能变)*/
//2.1获取原始文件名
String originalFilename = uploadFile.getOriginalFilename();
//2.2构建文件前缀
String filePrefix = UUID.randomUUID().toString();
//2.3获取文件名的后缀
String fileSuffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//2.4构建新的文件名
String newFilename = filePrefix+fileSuffix;
log.debug("newFilename {}",newFilename);
/*3.上传文件到指定目录*/
uploadFile.transferTo(new File(uploadDir,newFilename));
/*4.通过http协议可以访问到的资源路径*/
String accessAddress = resourceHost+dateStr+"/"+newFilename;
log.debug("accessAddress: {} ",accessAddress);
return accessAddress;
跨域配置实现
我们在通过客户端工程,访问文件上传服务时,需要进行跨域配置,在服务端的跨域配置中有多种方案,最常见是在过滤器的层面进行跨域设计,例如:
package com.cy.config;
import org.apache.catalina.filters.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
//方案2:在过滤器层面实现跨域配置
//@Configuration
public class CorsFilterConfig {
/**
* 定义跨域过滤器的配置,并且将其注册到spring容器中*/
@Bean
public FilterRegistrationBean<CorsFilter> filterFilterRegistrationBean(){
//1.对此过滤器进行配置(跨域设置-url,method)
UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
CorsConfiguration config=new CorsConfiguration();
//允许哪种请求头跨域
config.addAllowedHeader("*");
//允许哪种方法类型跨域 get post delete put
config.addAllowedMethod("*");
// 允许哪些请求源(ip:port)跨域
config.addAllowedOrigin("*");
//是否允许携带cookie跨域
config.setAllowCredentials(true);
//2.注册过滤器并设置其优先级
configSource.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> fBean= new FilterRegistrationBean(new org.springframework.web.filter.CorsFilter (configSource));
fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return fBean;
客户端工程逻辑实现
本次项目我们的客户端工程基于springboot工程进行设计,项目上线时可以将其静态资源直接放到一个静态资源目录中.
添加依赖
在sca-resource-ui工程的pom文件中添加web依赖,例如:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
构建项目启动类
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);
}
}
创建文件上传页面
在工程的resources目录下创建static目录(假如这个目录已经存在则无需创建),然后在此目录创建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>
</head>
<body>
<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(){
//获得用户选中的所有图片(获得数组)
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/";
axios.post(url,form)
.then(function (response){
alert("upload ok")
console.log(response.data);
})
.catch(function (e){//失败时执行catch代码块
console.log(e);
})
}
</script>
</html>
启动服务访问测试
第一步:启动nacos服务(在这里做服务的注册和配置管理)
第二步:启动sca-resource服务,此服务提供文件上传功能
第三步:启动sca-resource-ui服务,此服务为客户端工程,提供静态资源的访问.所有页面放到此工程中.
第四步:打开浏览器,访问sca-resource-ui工程下的文件上传页面,例如:
API网关(Gateway)工程实践
概述
API 网关是外部资源对服务内部资源访问的入口,所以文件上传请求应该首先请求的是网关服务,然后由网关服务转发到具体的资源服务上。
服务调用架构
工程项目结构设计
创建网关工程及初始化
第一步:创建sca-resource-gateway工程,例如:
第二步:添加项目依赖,例如:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<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>
第三步:创建配置文件bootstrap.xml,然后进行初始配置,例如:
server:
port: 9000
spring:
application:
name: sca-resource-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
gateway:
discovery:
locator:
enabled: true
routes:
- id: router01
uri: lb://sca-resource
predicates:
- Path=/sca/resource/upload/**
filters:
- StripPrefix=1
# globalcors: #跨域配置
# corsConfigurations:
# '[/**]':
# allowedOrigins: "*"
# allowedHeaders: "*"
# allowedMethods: "*"
# allowCredentials: true
第四步:构建项目启动类,并进行服务启动,检测是否正确,例如:
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);
}
}
网关跨域配置
当我们基于Ajax技术访问网关时,需要在网关层面进行跨域设计,例如:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
//跨域分析
//1.@CrossOrigin 可以从controller层面解决单个controller跨域问题
//2.@CorsFilter 这个springMVC 中给出过滤器层面的跨域,可以解决多个controller的跨域问题
//3.网关中的CorsWebFilter 这个是spring webflux中的过滤器,可以解决网关层面
// 解决多个服务的跨域问题,这就不需要每个服务都写一遍跨域了
//@Configuration
public class CorsFilterConfig {
/**
* 过滤器的层面配置跨域
* @return 为网关层面的跨域过滤器
* */
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource configSource =
new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
configSource.registerCorsConfiguration("/**",config);
return new CorsWebFilter(configSource);
}
}
Spring Gateway工程中的跨域设计,除了可以在网关项目中以java代码方式进行跨域过滤器配置,还可以直接在配置文件进行跨域配置,例如:
spring:
cloud:
gateway:
globalcors: #跨域配置
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods: "*"
allowCredentials: true
启动工程进行服务访问
首先打开网关(Gateway),资源服务器(Resource),客户端工程服务(UI),然后修改fileupload.html文件中访问资源服务端的url,例如
let url="http://localhost:9999/nacos/resource/upload/";
接下来进行访问测试,例如: