以上图的图片上传功能为例,可能有A、B、C三个项目需要上传文件,如.zip、excel、images等文件,常用的方法是在各自项目中编写上传的后台代码,文件上传到项目的当前服务器路径下,其他项目需要时又复制一次,这种类似是单体式架构,不好扩展,那么开发一个统一的资源服务就很有必要,由这个资源服务统一管理,其他项目只要使用即可,资源服务器提供上传、下载、访问等功能。当然如果有很大量的文件资源,可以使用 Hadoop分布式的文件系统(HDFS) 去搭建资源服务器。
概括:1.提供资源上传服务,2.提供资源访问服务,3.对资源的访问进行过滤,防盗链
1.后台服务,Java环境,Spring Boot ,添加 web 引用 , 该引用默认使用SpringMVC , 本文会演示文件上传、访问、过滤访问、支持跨域,篇幅会长一点,最后提供源码下载,欢迎继续观看
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.文件上传 , 在application.properties中自定义上传路径,和覆盖SpringBoot默认静态资源路径,然后在启动类中 添加跨域和文件上传大小设置。静态资源路径是指系统可以直接访问的路径,且路径下的所有文件均可被用户直接读取,在Springboot中默认的静态资源路径有:classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,
### 文件上传的路径
web.upload.rootPath=D:/project/springboot/
### 表示所有的访问都经过静态资源路径
spring.mvc.static-path-pattern=/**
### 覆盖默认静态资源路径,最后加上自己的路径,这样上传后,就可以访问到
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,\
classpath:/static/,classpath:/public/,file:${web.upload.rootPath}
@Bean
public WebMvcConfigurer corsConfigurer(){
return new WebMvcConfigurerAdapter(){
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
@Bean
public MultipartConfigElement multipartConfigElement(){
MultipartConfigFactory factory = new MultipartConfigFactory();
//设置上传文件大小限制
factory.setMaxFileSize("20MB");
//设置上传总数据大小
factory.setMaxRequestSize("50MB");
return factory.createMultipartConfig();
}
添加一个UploadController,提供上传服务
@RestController
public class UploadController {
//配置文件上传根目录地址
@Value("${web.upload.rootPath}")
private String rootPath;
private static SimpleDateFormat dateFormat = new SimpleDateFormat("YYMMddHHmmsssss");
private static SimpleDateFormat rootPathFormate = new SimpleDateFormat("yyyy/MM/");//上传目录以年月为层级
//文件上传
@RequestMapping(value = "/upload/upload", method = RequestMethod.POST)
public MvcDataDto upload(HttpServletRequest request) {
MvcDataDto dto = MvcDataDto.getDefaultInstance();
dto.setResultCode(MvcDataDto.Fail);
dto.setResultMessage("文件上传失败");
try {
List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
for (int i = 0; i < files.size(); ++i) {
if (files.get(i) != null) {
dto.setResultObj(saveFiles(files.get(i)));
}
}
if (dto.getResultObj() != null) {
dto.setResultCode(MvcDataDto.Success);
dto.setResultMessage("文件上传成功");
}
} catch (Exception e) {
e.printStackTrace();
}
return dto;
}
//上传文件具体实现方法 -- 异步调用
@Async
public Map<String,Object> saveFiles(MultipartFile file) {
Map<String,Object> maps = new HashMap<>();
try {
if (!file.isEmpty()) {
//获取文件相关信息
byte[] bytes = file.getBytes();
String filename = file.getOriginalFilename();
int r = (int) ((Math.random() * 9 + 1) * 100000);
String attachId = dateFormat.format(new Date()) + String.valueOf(r).substring(0, 4);
String ext = filename.substring(filename.lastIndexOf("."), filename.length()); //后缀名
String newFilename = (attachId + ext).toLowerCase(); //新文件名
//保存文件
String attachPath = "uploadfiles/" + rootPathFormate.format(new Date());
String savePath = getRootPath(attachPath) + newFilename;
File fileToSave = new File(savePath);
FileCopyUtils.copy(bytes, fileToSave);
//保存数据记录
maps.put("fileName",newFilename);
maps.put("attachId",attachId);
maps.put("attachSize",new Long(bytes.length));
maps.put("attachPath",attachPath + newFilename);
System.out.println("save file path :" + fileToSave.getAbsolutePath());
fileToSave = null;
}
} catch (Exception e) {
e.printStackTrace();
}
return maps;
}
//判断文件夹是否存在,如不存在则创建,并返回路径
public String getRootPath(String attachPath) {
String filePath = rootPath + attachPath;
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
file = null;
return filePath;
}
}
3.新建html表单,选择一张图片,进行上传,返回了文件的路径 “uploadfiles/2018/05/1805141044000348953.jpg” ,利用上传服务返回的路径访问该图片
<form method="POST" enctype="multipart/form-data" action="/upload/upload">
<p>
文件:<input type="file" name="file"/>
</p>
<p>
<input type="submit" value="上传"/>
</p>
</form>
根据返回的路径访问资源,可以访问到
现在,上传服务就写好了,因设置了支持跨域,如果 A 网站使用了上传服务,传了一个 123.jpg, 得到文件路径 uploadfiles/2018/05/1805141044000348953.jpg,A 网站拿到路径后,保存到自己的业务数据库中,通过http地址访问资源,以后有了 B、C 网站,可以公用上传服务
4.服务过滤-防盗,如果有某个论坛挂了自己网站的图片,或者通过爬虫抓取了网站的图片路径,那么我们的图片就免费的给他们使用了,可以通过添加过滤器来监听请求的来源,对资源的访问进行限制。在项目中添加一个自定义的Filter,继续servlet的Filer,重新过滤逻辑。
@Configuration
public class ResourcesFilter implements Filter {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ResourcesFilter.class);
//能访问资源的列表
protected static List<Pattern> patterns = new ArrayList<Pattern>();
public ResourcesFilter(){
patterns.add(Pattern.compile("localhost"));//本地开发环境能访问资源
patterns.add(Pattern.compile("res.a.com"));//这个域名能访问资源
patterns.add(Pattern.compile("www.b.com"));//同上
patterns.add(Pattern.compile("www.c.com"));//...
}
//是否需要过滤
private boolean isInclude(String url) {
for (Pattern pattern : patterns) {
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
return true;
}
}
return false;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//过滤逻辑判断
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
//获取域名
String serverName = httpRequest.getServerName();
logger.info("httpRequest - serverName:"+ serverName);
//过滤域名,能访问资源列表的通过请求,否则返回404错误
if(isInclude(serverName)){
filterChain.doFilter(servletRequest, servletResponse);
}else{
httpResponse.setStatus(404);
httpResponse.sendError(404,"没有权限");
filterChain.doFilter(servletRequest, servletResponse);
return;
}
}
@Override
public void destroy() {
}
}
是不是很简单呢,当然中小型网站可以使用这种方式,大型网站的资源服务一般会使用第三方云OSS对象存储,或搭建 HDFS,项目源码下载