• SpringBoot


    前言

    记录下SpringBoot下静态资源存储服务器的搭建。


    环境

    win10 + SpringBoot2.5.3


    实现效果

    • 文件上传:

    在这里插入图片描述

    • 文件存储位置:

    在这里插入图片描述

    • 文件访问:

    在这里插入图片描述


    具体实现

    文件上传

    配置类

    • pom.xml
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
    
    • application.yml
    spring:
      # 文件编码 UTF8
      mandatory-file-encoding: UTF-8
    
    server:
      # 服务端口
      port: 8000
    
    #文件上传配置
    file:
      # 文件服务域名
      domain: http://localhost:8000/
      # 排除文件类型
      exclude:
      # 包括文件类型
      include:
        - .jpg
        - .png
        - .jpeg
      # 文件最大数量
      nums: 10
      # 服务器文件路径
      serve-path: assets/**
      # 单个文件最大体积
      single-limit: 2MB
      # 本地文件保存位置
      store-dir: assets/
    
    • yml读取工厂类
    import org.springframework.boot.env.YamlPropertySourceLoader;
    import org.springframework.core.env.PropertySource;
    import org.springframework.core.io.support.EncodedResource;
    import org.springframework.core.io.support.PropertySourceFactory;
    import java.io.IOException;
    import java.util.List;
    
    /**
     * @Description yml读取工厂类
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    public class YmlPropertySourceFactory implements PropertySourceFactory {
        @Override
        public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
            List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
            return sources.get(0);
        }
    }
    
    • 文件上传属性配置类
    import com.coisini.file.factory.YmlPropertySourceFactory;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.stereotype.Component;
    
    /**
     * @Description 文件上传属性配置类
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    @Component
    @ConfigurationProperties(prefix = "file")
    @PropertySource(value = "classpath:application.yml",
            encoding = "UTF-8",factory = YmlPropertySourceFactory.class)
    public class FilePropertiesConfiguration {
    
        private static final String[] DEFAULT_EMPTY_ARRAY = new String[0];
    
        private String storeDir = "/assets";
    
        private String singleLimit = "2MB";
    
        private Integer nums = 10;
    
        private String domain;
    
        private String[] exclude = DEFAULT_EMPTY_ARRAY;
    
        private String[] include = DEFAULT_EMPTY_ARRAY;
    
        public String getStoreDir() {
            return storeDir;
        }
    
        public void setStoreDir(String storeDir) {
            this.storeDir = storeDir;
        }
    
        public String getSingleLimit() {
            return singleLimit;
        }
    
        public void setSingleLimit(String singleLimit) {
            this.singleLimit = singleLimit;
        }
    
        public Integer getNums() {
            return nums;
        }
    
        public void setNums(Integer nums) {
            this.nums = nums;
        }
    
        public String[] getExclude() {
            return exclude;
        }
    
        public void setExclude(String[] exclude) {
            this.exclude = exclude;
        }
    
        public String[] getInclude() {
            return include;
        }
    
        public void setInclude(String[] include) {
            this.include = include;
        }
    
        public String getDomain() {
            return domain;
        }
    
        public void setDomain(String domain) {
            this.domain = domain;
        }
    }
    

    上传接口

    • 文件上传控制器
    import com.coisini.file.vo.FileVo;
    import com.coisini.file.service.FileService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.util.MultiValueMap;
    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 org.springframework.web.multipart.MultipartHttpServletRequest;
    import javax.servlet.http.HttpServletRequest;
    import java.util.List;
    
    /**
     * @Description 文件上传控制器
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    @RestController
    @RequestMapping("/file")
    public class FileController {
    
        @Autowired
        private FileService fileService;
    
        /**
         * 文件上传
         * @param request
         * @return
         */
        @PostMapping("/upload")
        public List<FileVo> upload(HttpServletRequest request) {
            MultipartHttpServletRequest multipartHttpServletRequest = ((MultipartHttpServletRequest) request);
            MultiValueMap<String, MultipartFile> fileMap = multipartHttpServletRequest.getMultiFileMap();
            List<FileVo> files = fileService.upload(fileMap);
            return files;
        }
    
    }
    
    • 文件上传接口
    import com.coisini.file.vo.FileVo;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.multipart.MultipartFile;
    import java.util.List;
    
    /**
     * @Description 文件上传接口
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    public interface FileService {
    
        /**
         * 上传文件
         * @param fileMap 文件map
         * @return 文件数据
         */
        List<FileVo> upload(MultiValueMap<String, MultipartFile> fileMap);
    
    }
    
    • 文件上传实现类
    import com.coisini.file.model.FileModel;
    import com.coisini.file.core.Uploader;
    import com.coisini.file.vo.FileVo;
    import com.coisini.file.service.FileService;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.multipart.MultipartFile;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @Description 文件上传实现类
     * @author coisini
     * @date
     * @Version 1.0
     */
    @Service
    public class FileServiceImpl implements FileService {
    
        @Autowired
        private Uploader uploader;
    
        @Value("${file.domain}")
        private String domain;
    
        @Value("${file.serve-path:assets/**}")
        private String servePath;
    
        @Override
        public List<FileVo> upload(MultiValueMap<String, MultipartFile> fileMap) {
            return uploader.upload(fileMap).stream().map(item ->{
                /**
                 * 这里可以拿到文件具体信息
                 * 在此做数据库保存记录操作等业务处理
                 */
                return transform(item);
            }).collect(Collectors.toList());
        }
    
        /**
         * 出参序列化
         * @param fileModel
         * @return
         */
        private FileVo transform(FileModel fileModel) {
            FileVo model = new FileVo();
            BeanUtils.copyProperties(fileModel, model);
            String s = servePath.split("/")[0];
            model.setUrl(domain + s + "/" + fileModel.getPath());
            return model;
        }
    }
    

    上传实现

    • 文件上传配置类
    import com.coisini.file.core.LocalUploader;
    import com.coisini.file.core.Uploader;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    
    /**
     * @Description 文件上传配置类
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    @Configuration
    public class UploaderConfiguration {
        /**
         * @return 本地文件上传实现类
         */
        @Bean
        @Order
        @ConditionalOnMissingBean
        public Uploader uploader(){
            return new LocalUploader();
        }
    }
    
    • 文件上传服务接口
    import com.coisini.file.model.FileModel;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.multipart.MultipartFile;
    import java.util.List;
    
    /**
     * @Description 文件上传服务接口
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    public interface Uploader {
    
        /**
         * 上传文件
         * @param fileMap 文件map
         * @return 文件数据
         */
        List<FileModel> upload(MultiValueMap<String, MultipartFile> fileMap);
    
    }
    
    • 本地上传实现类
    import com.coisini.file.config.FilePropertiesConfiguration;
    import com.coisini.file.model.FileModel;
    import com.coisini.file.util.FileUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.multipart.MultipartFile;
    import javax.annotation.PostConstruct;
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    /**
     * @Description 本地上传
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    @Slf4j
    public class LocalUploader implements Uploader {
    
        @Autowired
        private FilePropertiesConfiguration filePropertiesConfiguration;
    
        /**
         * 初始化本地存储
         * 依赖注入完成后初始化
         */
        @PostConstruct
        public void initStoreDir() {
            System.out.println("initStoreDir start:" + this.filePropertiesConfiguration.getStoreDir());
            FileUtil.initStoreDir(this.filePropertiesConfiguration.getStoreDir());
            System.out.println("initStoreDir end");
        }
    
        /**
         * 文件上传
         * @param fileMap 文件map
         * @return
         */
        @Override
        public List<FileModel> upload(MultiValueMap<String, MultipartFile> fileMap) {
            // 检查文件
            checkFileMap(fileMap);
            return handleMultipartFiles(fileMap);
        }
    
        /**
         * 文件配置
         * @return
         */
        protected FilePropertiesConfiguration getFilePropertiesConfiguration() {
            return filePropertiesConfiguration;
        }
    
        /**
         * 单个文件体积限制
         * @return
         */
        private long getSingleFileLimit() {
            String singleLimit = getFilePropertiesConfiguration().getSingleLimit();
            return FileUtil.parseSize(singleLimit);
        }
    
        /**
         * 检查文件
         * @param fileMap
         */
        protected void checkFileMap(MultiValueMap<String, MultipartFile> fileMap){
            if (fileMap.isEmpty()) {
                throw new RuntimeException("file not found");
            }
    
            // 上传文件数量限制
            int nums = getFilePropertiesConfiguration().getNums();
            if (fileMap.size() > nums) {
                throw new RuntimeException("too many files, amount of files must less than" + nums);
            }
        }
    
        /**
         * 文件处理
         * @param fileMap
         * @return
         */
        protected List<FileModel> handleMultipartFiles(MultiValueMap<String, MultipartFile> fileMap) {
            long singleFileLimit = getSingleFileLimit();
            List<FileModel> res = new ArrayList<>();
            fileMap.keySet().forEach(key -> fileMap.get(key).forEach(file -> {
                if (!file.isEmpty()) {
                    handleOneFile(res, singleFileLimit, file);
                }
            }));
            return res;
        }
    
        /**
         * 单文件处理
         * @param res
         * @param singleFileLimit
         * @param file
         */
        private void handleOneFile(List<FileModel> res, long singleFileLimit, MultipartFile file) {
            byte[] bytes = FileUtil.getFileBytes(file);
            String[] include = getFilePropertiesConfiguration().getInclude();
            String[] exclude = getFilePropertiesConfiguration().getExclude();
            String ext = UploadHelper.checkOneFile(include, exclude, singleFileLimit, file.getOriginalFilename(), bytes.length);
            String newFilename = UploadHelper.getNewFilename(ext);
            String storePath = getStorePath(newFilename);
            // 生成文件的md5值
            String md5 = FileUtil.getFileMD5(bytes);
            FileModel fileModelData = FileModel.builder().
                    name(newFilename).
                    md5(md5).
                    key(file.getName()).
                    path(storePath).
                    size(bytes.length).
                    extension(ext).
                    build();
    
            boolean ok = writeFile(bytes, newFilename);
            if (ok) {
                res.add(fileModelData);
            }
        }
    
        /**
         * 写入存储
         * @param bytes
         * @param newFilename
         * @return
         */
        protected boolean writeFile(byte[] bytes, String newFilename) {
            // 获取绝对路径
            String absolutePath =
                    FileUtil.getFileAbsolutePath(filePropertiesConfiguration.getStoreDir(), getStorePath(newFilename));
            System.out.println("absolutePath:" + absolutePath);
            try {
                BufferedOutputStream stream =
                        new BufferedOutputStream(new FileOutputStream(new File(absolutePath)));
                stream.write(bytes);
                stream.close();
            } catch (Exception e) {
                System.out.println("write file error:" + e);
                return false;
            }
            return true;
        }
    
        /**
         * 获取缓存地址
         * @param newFilename
         * @return
         */
        @SuppressWarnings("ResultOfMethodCallIgnored")
        protected String getStorePath(String newFilename) {
            Date now = new Date();
            String format = new SimpleDateFormat("yyyy/MM/dd").format(now);
            Path path = Paths.get(filePropertiesConfiguration.getStoreDir(), format).toAbsolutePath();
            File file = new File(path.toString());
            if (!file.exists()) {
                file.mkdirs();
            }
    
            return Paths.get(format, newFilename).toString();
        }
    }
    

    辅助类

    • 文件上传Helper
    import com.coisini.file.util.FileUtil;
    import java.util.UUID;
    
    /**
     * @Description 文件上传Helper
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    public class UploadHelper {
    
        /**
         * 单个文件检查
         * @param singleFileLimit 单个文件大小限制
         * @param originName      文件原始名称
         * @param length          文件大小
         * @return 文件的扩展名,例如: .jpg
         */
        public static String checkOneFile(String[] include, String[] exclude, long singleFileLimit, String originName, int length) {
            // 写到了本地
            String ext = FileUtil.getFileExt(originName);
            // 检测扩展
            if (!UploadHelper.checkExt(include, exclude, ext)) {
                throw new RuntimeException(ext + "文件类型不支持");
            }
            // 检测单个大小
            if (length > singleFileLimit) {
                throw new RuntimeException(originName + "文件不能超过" + singleFileLimit);
            }
            return ext;
        }
    
        /**
         * 检查文件后缀
         * @param ext 后缀名
         * @return 是否通过
         */
        public static boolean checkExt(String[] include, String[] exclude, String ext) {
            int inLen = include == null ? 0 : include.length;
            int exLen = exclude == null ? 0 : exclude.length;
            // 如果两者都有取 include,有一者则用一者
            if (inLen > 0 && exLen > 0) {
                return UploadHelper.findInInclude(include, ext);
            } else if (inLen > 0) {
                // 有include,无exclude
                return UploadHelper.findInInclude(include, ext);
            } else if (exLen > 0) {
                // 有exclude,无include
                return UploadHelper.findInExclude(exclude, ext);
            } else {
                // 二者都没有
                return true;
            }
        }
    
        /**
         * 检查允许的文件类型
         * @param include
         * @param ext
         * @return
         */
        public static boolean findInInclude(String[] include, String ext) {
            for (String s : include) {
                if (s.equals(ext)) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 检查不允许的文件类型
         * @param exclude
         * @param ext
         * @return
         */
        public static boolean findInExclude(String[] exclude, String ext) {
            for (String s : exclude) {
                if (s.equals(ext)) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 获得新文件的名称
         * @param ext 文件后缀
         * @return 新名称
         */
        public static String getNewFilename(String ext) {
            String uuid = UUID.randomUUID().toString().replace("-", "");
            return uuid + ext;
        }
    }
    
    • 文件工具类
    import org.springframework.util.DigestUtils;
    import org.springframework.util.StringUtils;
    import org.springframework.util.unit.DataSize;
    import org.springframework.web.multipart.MultipartFile;
    import java.io.File;
    import java.nio.file.FileSystem;
    import java.nio.file.FileSystems;
    import java.nio.file.Path;
    
    /**
     * @Description 文件工具类
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    public class FileUtil {
    
        /**
         * 获取当前文件系统
         * @return
         */
        public static FileSystem getDefaultFileSystem() {
            return FileSystems.getDefault();
        }
    
        /**
         * 是否绝对路径
         * @param str
         * @return
         */
        public static boolean isAbsolute(String str) {
            Path path = getDefaultFileSystem().getPath(str);
            return path.isAbsolute();
        }
    
        /**
         * 初始化存储文件夹
         * @param dir
         */
        @SuppressWarnings("ResultOfMethodCallIgnored")
        public static void initStoreDir(String dir) {
            String absDir;
            if (isAbsolute(dir)) {
                absDir = dir;
            } else {
                String cmd = getCmd();
                Path path = getDefaultFileSystem().getPath(cmd, dir);
                absDir = path.toAbsolutePath().toString();
            }
            File file = new File(absDir);
            if (!file.exists()) {
                file.mkdirs();
            }
        }
    
        /**
         * 获取程序当前路径
         * @return
         */
        public static String getCmd() {
            return System.getProperty("user.dir");
        }
    
        /**
         * 获取文件绝对路径
         * @param dir
         * @param filename
         * @return
         */
        public static String getFileAbsolutePath(String dir, String filename) {
            if (isAbsolute(dir)) {
                return getDefaultFileSystem()
                        .getPath(dir, filename)
                        .toAbsolutePath().toString();
            } else {
                return getDefaultFileSystem()
                        .getPath(getCmd(), dir, filename)
                        .toAbsolutePath().toString();
            }
        }
    
        /**
         * 获取文件扩展名
         * @param filename
         * @return
         */
        public static String getFileExt(String filename) {
            int index = filename.lastIndexOf('.');
            return filename.substring(index);
        }
    
        /**
         * 获取文件MD5值
         * @param bytes
         * @return
         */
        public static String getFileMD5(byte[] bytes) {
            return DigestUtils.md5DigestAsHex(bytes);
        }
    
        /**
         * 文件体积
         * @param size
         * @return
         */
        public static Long parseSize(String size) {
            DataSize singleLimitData = DataSize.parse(size);
            return singleLimitData.toBytes();
        }
    
        /**
         * 是否是绝对路径
         * @param path
         * @return
         */
        public static boolean isAbsolutePath(String path) {
            if (StringUtils.isEmpty(path)) {
                return false;
            } else {
                return '/' == path.charAt(0) || path.matches("^[a-zA-Z]:[/\\].*");
            }
        }
    
        /**
         * 文件字节
         * @param file 文件
         * @return 字节
         */
        public static byte[] getFileBytes(MultipartFile file) {
            byte[] bytes;
            try {
                bytes = file.getBytes();
            } catch (Exception e) {
                throw new RuntimeException("read file date failed");
            }
            return bytes;
        }
    }
    

    实体

    • 文件具体信息
    import lombok.*;
    
    /**
     * @Description 文件具体信息,可存储数据库
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    @Setter
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class FileModel {
    
        /**
         * url
         */
        private String url;
    
        /**
         * key
         */
        private String key;
    
        /**
         * 文件路径
         */
        private String path;
    
        /**
         * 文件名称
         */
        private String name;
    
        /**
         * 扩展名,例:.jpg
         */
        private String extension;
    
        /**
         * 文件大小
         */
        private Integer size;
    
        /**
         * md5值,防止上传重复文件
         */
        private String md5;
    }
    
    • 文件出参
    import lombok.Data;
    
    /**
     * @Description 文件出参
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    @Data
    public class FileVo {
    
        /**
         * 文件 key
         */
        private String key;
    
        /**
         * 文件路径
         */
        private String path;
    
        /**
         * 文件 URL
         */
        private String url;
    }
    

    上传测试

    • 上传

    在这里插入图片描述

    • 文件存储位置为当前项目/assets目录

    在这里插入图片描述


    文件访问

    配置类

    • SpringBoot访问静态资源有两种方式:模板引擎和改变资源映射,这里采用改变资源映射来实现
    • Spring MVC配置类
    import com.coisini.file.util.FileUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import java.nio.file.FileSystems;
    import java.nio.file.Path;
    
    /**
     * @Description Spring MVC 配置
     * @author coisini
     * @date Sep 7, 2021
     * @Version 1.0
     */
    @Configuration(proxyBeanMethods = false)
    @Slf4j
    public class WebConfiguration implements WebMvcConfigurer {
    
        @Value("${file.store-dir:assets/}")
        private String dir;
    
        @Value("${file.serve-path:assets/**}")
        private String servePath;
    
        /**
         * 跨域设置
         */
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOriginPatterns("*")
                    .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowCredentials(true)
                    .maxAge(3600)
                    .allowedHeaders("*");
        }
    
        /**
         * 拦截处理请求信息
         * 添加文件真实地址
         * @param registry
         */
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler(getDirServePath())
                    .addResourceLocations("file:" + getAbsDir() + "/");
        }
    
        /**
         * 获取服务器url
         * @return
         */
        private String getDirServePath() {
            return servePath;
        }
    
        /**
         * 获得文件夹的绝对路径
         */
        private String getAbsDir() {
            if (FileUtil.isAbsolutePath(dir)) {
                return dir;
            }
            String cmd = System.getProperty("user.dir");
            Path path = FileSystems.getDefault().getPath(cmd, dir);
            return path.toAbsolutePath().toString();
        }
    }
    
    • 访问结果

    在这里插入图片描述


    项目源码

    Github: https://github.com/Maggieq8324/java-learn-demo/tree/master/springboot-file-simple
    Gitee: https://gitee.com/maggieq8324/java-learn-demo/tree/master/springboot-file-simple


    - End -
    梦想是咸鱼
    关注一下吧
    以上为本篇文章的主要内容,希望大家多提意见,如果喜欢记得点个推荐哦
    作者:Maggieq8324
    本文版权归作者和博客园共有,欢迎转载,转载时保留原作者和文章地址即可。
  • 相关阅读:
    在ubuntu9中安装配置java postgresql pljava tsearch2
    Zend中配置Memcached
    PHP 截取字符串专题(转)
    网络执法官.P2P终结者等监控网络流量控制软件的原理和防范
    C++中的浮点数格式控制
    HTML教程
    网络执法官.P2P终结者等监控网络流量控制软件的原理和防范
    CodeGear RADstudio 2007+Sp3下载地址及破解
    HTML教程
    CodeGear RADstudio 2007+Sp3下载地址及破解
  • 原文地址:https://www.cnblogs.com/maggieq8324/p/15240901.html
Copyright © 2020-2023  润新知