SpringBoot项目开发(十三):文件上传,监听、过滤非自身网站的请求,对资源进行防盗链等

这里写图片描述

以上图的图片上传功能为例,可能有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,项目源码下载

下篇 AOP,收集日志、统计方法执行时长

相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页