本地服务器、ftp服务器和seaweedfs服务器之文件操作并整合springboot

在springboot项目中经常会有上传和下载的需求,此文章主要讲述在springboot项目中可能应用到的文件知识!假设我们需要将一些文件保存到服务器并将其对应信息记录到数据库中,接着能够通过前端发送的请求对文件进行相应的上传、下载和删除工作。

一、本地服务器文件的上传、下载和删除(这里说的本地服务器是指项目部署所在服务器且文件存放在与jar包同级的static的File目录下)
1.创建名为File的数据库(这里使用mysql数据库),并添加resource表,此表将记录所有文件信息,其中有主键id,文件名resource_url以及文件描述resource_desc字段。如下图:


截图
截图

2.在springboot中创建对应的Service层、Dao层、Controller层以及Entity层等(这里整合了mybatisplus):
(1)新建resource表中对应的实体类ResourceDO以及文件传输对象ResourceDTO:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("resource")
public class ResourceDO implements Serializable {

    //主键
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    //文件名
    private String resourceUrl;

    //文件描述
    private String resourceDesc;
}
@Data
public class ResourceDTO {

    //文件描述 
    private String resourceDesc;
}

(2)建立数据操作层ResourceMapper

@Repository
public interface ResourceMapper extends BaseMapper<ResourceDO>{

    /**
     * 通过文件id获取文件信息
     * @param resourceId 文件id
     * @return 文件实体类
     */
    ResourceDO getResourceById(Integer resourceId);
}

对应的ResourceMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fileexample.filedemo.mapper.ResourceMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.fileexample.filedemo.entity.ResourceDO">
        <id column="id" property="id" />
        <result column="resource_url" property="resourceUrl" />
        <result column="resource_desc" property="resourceDesc" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, resource_url, resource_desc
    </sql>

    <select id="getResourceById" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from resource
        where id = #{resourceId}
    </select>
</mapper>

(3)新建ResourceService以及其对应的实现类:

public interface ResourceService extends IService<ResourceDO>{

    /**
     * 上传文件到本地服务器
     * @param multipartFile 要上传的文件
     * @param resourceDTO 文件传输对象
     * @return 文件在数据表中的id
     */
    Integer uploadLocalResource(MultipartFile multipartFile, ResourceDTO resourceDTO);

    /**
     * 从本地服务器上下载文件
     * @param LocalFileName 要下载的文件名
     * @param response 服务器的响应
     * @return 是否下载成功(true or false)
     */
    boolean LocalDownload(String LocalFileName, HttpServletResponse response);

    /**
     * 通过文件id删除本地服务器上对应的文件以及对应的数据表信息
     * @param resourceId 文件id
     * @param LocalFileName 要删除的文件名
     * @return 是否删除成功(true or false)
     */
    boolean LocalDelete(Integer resourceId,String LocalFileName);

    /**
     * 根据文件id获取文件信息
     * @param resourceId 文件id
     * @return 文件信息
     */
    ResourceDO getResourceById(Integer resourceId);

@Service
@Slf4j
public class ResourceServiceImpl extends ServiceImpl<ResourceMapper, ResourceDO> implements ResourceService {
    @Autowired
    private ResourceMapper resourceMapper;

    @Override
    @Transactional(rollbackFor=Exception.class)
    public Integer uploadLocalResource(MultipartFile multipartFile, ResourceDTO resourceDTO) {
        File uploadPath = null;
        try {
            // 获取项目根目录
            File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
            if (!rootPath.exists()) {
                rootPath = new File("");
            }
            // 获取上传目录,这里假设文件需要上传到项目根目录的static/File下
            uploadPath = new File(rootPath.getAbsolutePath(), "static/File");
            if (!uploadPath.exists()) {
                uploadPath.mkdirs();
            }
        } catch (FileNotFoundException e) {
            log.info("上传路径不存在");
            return null;
        }
        // 获取上传文件名
        String filename = multipartFile.getOriginalFilename();
        if (StringUtils.isBlank(filename)) {
            log.info("上传文件不合法");
            return null;
        }
        // 获取文件扩展名
        String fileExtensionName = filename.substring(filename.lastIndexOf("."));
        // 获取文件经过UUID.randomUUID()生成的唯一识别码
        String fileNewName = UUID.randomUUID().toString() + fileExtensionName;
        try {
            // 保存文件到项目根目录的static/File下
            multipartFile.transferTo(new File(uploadPath, fileNewName));
        } catch (IOException e) {
            log.info("上传文件错误");
            return null;
        }
        ResourceDO resourceDO = new ResourceDO();
        resourceDO.setResourceUrl(fileNewName);
        resourceDO.setResourceDesc(resourceDTO.getResourceDesc());
        if(resourceMapper.insert(resourceDO) < 1) {
            throw new PersistenceException("插入resource表失败");
        }
        return resourceDO.getId();
    }

    @Override
    public boolean LocalDownload(String LocalFileName, HttpServletResponse response) {
        //告诉客户端所发送的数据属于什么类型
        response.setHeader("content-type", "application/octet-stream");
        response.setContentType("application/octet-stream");
        try {
            //设置响应头,attachment表示以附件的形式下载
            response.setHeader("Content-Disposition", "attachment;filename=" + java.net.URLEncoder.encode(LocalFileName, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        byte[] buff = new byte[1024];
        BufferedInputStream bis = null;
        OutputStream os = null;
        try {
            File uploadPath = null;
            try {
                // 获取项目根目录
                File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
                // 获取上传目录
                uploadPath = new File(rootPath.getAbsolutePath(), "static/File");
            } catch (FileNotFoundException e) {
                log.info("上传路径不存在");
                return false;
            }
            //文件输出流
            os = response.getOutputStream();
            //文件输出流
            bis = new BufferedInputStream(new FileInputStream(new File(uploadPath, LocalFileName)));
            int i = bis.read(buff);
            while (i != -1) {
                os.write(buff, 0, buff.length);
                os.flush();
                i = bis.read(buff);
            }
        } catch (FileNotFoundException e1) {
            log.info("找不到指定的文件");
        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    @Override
    public boolean LocalDelete(Integer resourceId, String LocalFileName) {
        File uploadPath = null;
        try {
            // 获取项目根目录
            File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
            // 获取上传目录
            uploadPath = new File(rootPath.getAbsolutePath(), "static/File");
        } catch (FileNotFoundException e) {
            log.info("上传路径不存在");
            return false;
        }
        File targetFile = new File(uploadPath, LocalFileName);
        try {
            // 删除文件
            targetFile.delete();
            resourceMapper.deleteById(resourceId);
            return true;
        } catch (Exception e) {
            log.info("上传文件出错");
            return false;
        }
    }

    @Override
    public ResourceDO getResourceById(Integer resourceId) {
        return resourceMapper.getResourceById(resourceId);
    }

(4)文件控制器FileController为:

@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private ResourceService resourceService;

    /**
     * 上传单文件到本地服务器
     * @param file 要上传的文件
     * @param resourceDTO 文件传输对象
     * @return 上传结果
     */
    @PostMapping("/local-single-upload")
    public String localSingleUpload(@RequestParam("file") MultipartFile file, ResourceDTO resourceDTO) {
        int resourceId=resourceService.uploadLocalResource(file, resourceDTO);
        return "上传单文件到本地服务器成功,此文件的id为:" + resourceId;
    }

    /**
     * 上传多文件到本地服务器
     * @param files 要上传的文件
     * @param resourceDTO 文件传输对象
     * @return 上传结果
     */
    @PostMapping("/local-Multiple-upload")
    public String localMultipleUpload(@RequestParam("files") MultipartFile[] files, ResourceDTO resourceDTO) {
        for (MultipartFile file : files){
            int resourceId=resourceService.uploadLocalResource(file, resourceDTO);
            System.out.println("上传到本地服务器成功的文件id为:" + resourceId);
        }
        return "上传多文件本地服务器成功";
    }

    /**
     * 从本地服务器上下载文件
     * @param resourceId 要下载的文件id
     * @param response 服务器的响应
     * @return 下载结果
     */
    @GetMapping("/local-download")
    public String localDownload(@RequestParam("resourceId") Integer resourceId, HttpServletResponse response) {
        ResourceDO resourceDO=resourceService.getResourceById(resourceId);
        boolean success=resourceService.LocalDownload(resourceDO.getResourceUrl(),response);
        if(success){
            return "下载本地服务器文件成功";
        }
        return "下载本地服务器文件失败";
    }


    /**
     * 从本地服务器上删除文件
     * @param resourceId 要删除的文件名
     * @return 删除结果
     */
    @GetMapping("/local-delete")
    public String localDelete(@RequestParam("resourceId") Integer resourceId) {
        ResourceDO resourceDO=resourceService.getResourceById(resourceId);
        boolean success=resourceService.LocalDelete(resourceId,resourceDO.getResourceUrl());
        if(success){
            return "删除在本地服务器的文件成功";
        }
        return "删除在本地服务器的文件失败";
    }
}

3.测试
(1)上传单个文件:
先创建好一个文本文件:


截图

postman测试如下:


截图

项目根目录以及数据库有了相应的数据:
截图

截图

(2)上传多个文件
再创建两个文本文件:


截图

截图

postman测试如下:


截图

控制台、项目根目录以及数据库有了相应的数据:
截图

截图

截图

(3)下载文件:
假设要下载id为14的文件:
在浏览器中运行以下路径后id为14的文件会下载到电脑默认的下载路径下


截图

截图

(4)删除文件
假设要删除id为14的文件:


截图

发现以下关于id为14的文件信息已被删除:


截图

截图

截图

二、在遇到大型项目时,可能要建立专门的文件服务器来存放文件,这里介绍ftp服务器
FTP简介:
FTP 是用来传送文件的协议。使用 FTP 实现远程文件传输的同时,还可以保证数据传输的可靠性和高效性。
具体可参考https://blog.csdn.net/u013233097/article/details/89449668

1.安装vsftpd
可参考链接https://www.cnblogs.com/magic-chenyang/p/10383929.html

2.springboot需要引入对应的依赖:

        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.6</version>
        </dependency>

3.新建FtpUtils类

@Component
@Slf4j
public class FtpUtils {

    /**
     * ftp服务器ip
     */
    private static final String ftpIp = "127.0.0.1";

    /**
     * ftp服务器端口号,默认是21
     */
    private static final int ftpPort = 21;

    /**
     * ftp服务器用户名,即安装vsftpd时设置的用户名
     */
    private static final String ftpUser = "uftp";

    /**
     * ftp服务器密码,即安装vsftpd时设置的密码
     */
    private static final String ftpPassword = "password";

    /**
     * ftp服务器文件保存路径,因为我的vsftp设置在/home/uftp目录下,所以这个ftpPath相当于/home/uftp/File
     */
    private static final String ftpPath = "File/";

    /**
     * 从远程服务器下载文件到本地电脑的位置,这里我设置在我的电脑桌面的downloads文件夹下
     */
    private static final String localPath = "/home/zhu/Desktop/downloads/";

    /**
     * 连接FTP服务器
     * @return 连接结果
     */
    private static FTPClient getFTPClient() {
        FTPClient ftpClient = new FTPClient();
        try {
            //连接ftp服务器
            ftpClient = new FTPClient();
            ftpClient.connect(ftpIp, ftpPort);
            ftpClient.login(ftpUser, ftpPassword);
            ftpClient.setControlEncoding("UTF-8");
            //把文件转换为二进制字符流的形式进行上传
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.enterLocalPassiveMode();
            if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
                log.info("未连接到FTP,用户名或密码错误");
                ftpClient.disconnect();
            } else {
                log.info("FTP连接成功");
            }
        } catch (Exception e) {
            log.info("FTP连接错误");
            e.printStackTrace();
        }
        return ftpClient;
    }

    /**
     * 上传单个文件到ftp服务器
     * @param fileName 上传的文件名字
     * @param file 上传的文件
     * @return 是否上传成功(true or false)
     * @throws IOException
     */
    public static boolean uploadFile(String fileName, MultipartFile file) throws IOException {
        boolean success = false;
        InputStream inputStream = file.getInputStream();
        //连接ftp服务器
        FTPClient ftpClient = getFTPClient();
        try {
            //如果ftp服务器不存在对应的目录,则创建
            if(!ftpClient.changeWorkingDirectory(ftpPath)){
                String[] dirs = ftpPath.split("/");
                for(String dir : dirs){
                    if (!ftpClient.changeWorkingDirectory(dir)) {
                        if (!ftpClient.makeDirectory(dir)) {
                            log.info("创建目录" + dir + "失败");
                            return false;
                        }else{
                            ftpClient.changeWorkingDirectory(dir);
                            log.info("创建目录" + dir + "成功");
                        }
                    }
                }
            }
            //设置每次读取文件流时缓存数组的大小
            ftpClient.setBufferSize(1024);
            //上传文件
            ftpClient.storeFile(fileName, inputStream);
            //关闭输入流
            inputStream.close();
            //退出ftp
            ftpClient.logout();
            success = true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftpClient.isConnected()) {
                try {
                    ftpClient.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return success;
    }


    /**
     * 从ftp服务器上下载文件
     * @param ftpFileName 需要从ftp服务器上下载的文件名
     * @return 是否下载成功(true or false)
     */
    public static boolean downloadFile(String ftpFileName) {
        FTPClient ftpClient = null;
        try {
            ftpClient = getFTPClient();
            // 切换工作目录
            ftpClient.changeWorkingDirectory(ftpPath);

            File localFile = new File(localPath + ftpFileName);
            OutputStream outputStream = new FileOutputStream(localFile);
            ftpClient.retrieveFile(ftpFileName, outputStream);
            outputStream.close();
            ftpClient.logout();
            log.info("下载成功");
            return true;
        } catch (FileNotFoundException e) {
            log.error("没有找到" + ftpFileName + "文件");
        } catch (SocketException e) {
            log.error("连接FTP失败");
        } catch (IOException e) {
            log.error("文件读取错误");
        }
        log.info("下载失败");
        return false;
    }


    /**
     * 删除ftp服务器上的文件
     * @param filename 需要删除的文件名
     * @return 是否删除成功(true or false)
     */
    public static boolean deleteFile(String filename) {
        //连接ftp服务器
        FTPClient ftpClient = getFTPClient();
        boolean success = false;
        try {
            ftpClient.changeWorkingDirectory(ftpPath);
            ftpClient.dele(filename);
            success = true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ftpClient.isConnected()) {
                try {
                    ftpClient.disconnect();
                } catch (IOException ioe) {
                    log.error("关闭FTP连接失败");
                }
            }
        }
        return success;
    }

}

4.往ResourceService和ResourceServiceImpl中添加如下代码:

    /**
     * 上传文件到FTP服务器
     * @param multipartFile 需要上传的文件
     * @param resourceDTO 文件传输对象
     * @return
     */
    Integer uploadFtpResource(MultipartFile multipartFile, ResourceDTO resourceDTO);

    /**
     * 从FTP服务器上下载文件
     * @param ftpFileName 需要下载的文件名
     * @return 是否下载成功(true or false)
     */

    boolean ftpDownload(String ftpFileName);

    /**
     * 删除FTP服务器上的文件
     * @param resourceId 需要删除的文件id
     * @param ftpFileName 需要删除的文件名
     * @return 是否删除成功(true or false)
     */
    boolean ftpDelete(Integer resourceId, String ftpFileName);
    @Override
    public Integer uploadFtpResource(MultipartFile multipartFile, ResourceDTO resourceDTO) {
        // 上传文件名
        String filename = multipartFile.getOriginalFilename();
        if (StringUtils.isBlank(filename)) {
            log.info("上传文件不合法");
            return null;
        }
        String fileExtensionName = filename.substring(filename.lastIndexOf("."));
        String fileNewName = UUID.randomUUID().toString() + fileExtensionName;
        try {
             FtpUtils.uploadFile(fileNewName,multipartFile);
        } catch (IOException e) {
            log.info("上传文件出错");
            return null;
        }
        ResourceDO resourceDO = new ResourceDO();
        resourceDO.setResourceUrl(fileNewName);
        resourceDO.setResourceDesc(resourceDTO.getResourceDesc());
        if(resourceMapper.insert(resourceDO) < 1) {
            throw new PersistenceException("插入resource表失败");
        }
        return resourceDO.getId();
    }

    @Override
    public ResourceDO getResourceById(Integer resourceId) {
        return resourceMapper.getResourceById(resourceId);
    }

    @Override
    public boolean ftpDownload(String ftpFileName) {
        return FtpUtils.downloadFile(ftpFileName);
    }


    @Override
    public boolean ftpDelete(Integer resourceId, String ftpFileName) {
        if(FtpUtils.deleteFile(ftpFileName)){
            resourceMapper.deleteById(resourceId);
            return true;
        }else {
            return false;
        }
    }

5.添加如下代码到FileController:

  /**
     * 上传单个文件到FTP服务器
     * @param file 需要上传的文件
     * @param resourceDTO 文件传输对象
     * @return 上传结果
     */
    @PostMapping("/Ftp-single-upload")
    public String FtpSingleUpload(@RequestParam("file") MultipartFile file, ResourceDTO resourceDTO) {
        int resourceId=resourceService.uploadFtpResource(file,resourceDTO);
        return "上传单文件到FTP服务器成功,此文件的id为:" + resourceId;
    }

    /**
     * 上传多个文件到FTP服务器
     * @param files 需要上传的文件
     * @param resourceDTO 文件传输对象
     * @return 上传结果
     */
    @PostMapping("/Ftp-Multiple-upload")
    public String FtpMultipleUpload(@RequestParam("files") MultipartFile[] files, ResourceDTO resourceDTO) {
        for (MultipartFile file : files){
            int resourceId=resourceService.uploadFtpResource(file,resourceDTO);
            System.out.println("上传到FTP服务器成功的文件id为:" + resourceId);
        }
        return "上传多文件FTP服务器成功";
    }

    /**
     * 从FTP服务器上下载文件
     * @param resourceId 需要下载的文件id
     * @return 下载结果
     */
    @PostMapping("/ftp-download")
    public String ftpDownload(Integer resourceId) {
        ResourceDO resourceDO=resourceService.getResourceById(resourceId);
        boolean success=resourceService.ftpDownload(resourceDO.getResourceUrl());
        if(success){
            return "下载ftp服务器文件到本地成功";
        }
        return "下载ftp服务器文件到本地失败";
    }

    /**
     * 删除FTP服务器上的文件
     * @param resourceId 需要删除的文件id
     * @return 删除结果
     */
    @PostMapping("/ftp-delete")
    public String ftpDelete(@RequestParam("resourceId") Integer resourceId) {
        ResourceDO resourceDO=resourceService.getResourceById(resourceId);
        boolean success=resourceService.ftpDelete(resourceId, resourceDO.getResourceUrl());
        if(success){
            return "删除在ftp服务器的文件成功";
        }
        return "删除在ftp服务器的文件失败";
    }

6.测试
(1)上传单个文件
新建一个文本文件ftpfileone:


截图

用postman发送上传请求:


截图

上传成功后:
截图

截图

(2)上传多个文件
新建ftpfiletwo和ftpfilethree文本文件:


截图

截图

用postman发送上传请求:


截图

上传成功后:
截图

截图

截图

(3)下载文件
假设要下载id为17的文件:


截图

发现成功下载到桌面上的downloads文件夹下:


截图

(4)删除文件
假设要删除id为17的文件:


截图

发现id为17的文件已经被删除:


截图

截图

三、最近还接触到了seaweedfs文件存储服务器
简介:seaweedfs是一个非常优秀的由 golang 开发的分布式存储开源项目。它是用来存储文件的系统,并且与使用的语言无关,使得文件储存在云端变得非常方便。
在逻辑上Seaweedfs的几个概念:

  • Node 系统抽象的节点,抽象为DataCenter、Rack、DataNode
  • DataCenter 数据中心,对应现实中的不同机房
  • Rack 机架,对应现实中的机柜
  • Datanode 存储节点,用于管理、存储逻辑卷
  • Volume 逻辑卷,存储的逻辑结构,逻辑卷下存储Needle
  • Needle 逻辑卷中的Object,对应存储的文件
  • Collection 文件集,可以分布在多个逻辑卷上
    参考文章:https://blog.csdn.net/github_37459410/article/details/81141365
    Seaweedfs的官网为:https://github.com/chrislusf/seaweedfs

接下来介绍如何在服务器上搭建seaweedfs(阿里云服务器上搭建):

1.安装go环境:

(1)下载并解压go安装包
cd /usr/local
wget https://storage.googleapis.com/golang/go1.14.linux-amd64.tar.gz
tar -zxvf go1.14.linux-amd64.tar.gz

(2)添加go环境变量
sudo vim /etc/profile
#加入
export GOPATH=/usr/local/go
export PATH=$GOPATH/bin:$PATH
export GOROOT=/usr/local/go
export GOPATH=$PATH:$GOROOT/bin
#使之生效
source /etc/profile

(3)查看go版本
go version

安装完go后版本号如下:


截图

2.安装并运行seaweedfs

(1)下载并解压seaweedfs安装包

cd /usr/local
wget https://github.com/chrislusf/seaweedfs/releases/download/1.57/linux_amd64.tar.gz
tar -zxvf linux_amd64.tar.gz

(2)创建运行目录

cd /usr/local
mkdir data
cd data
mkdir fileData #用来存放master
mkdir volume  #用来存放volume
mkdir logs #用来存放日志
cd volume
mkdir v1
mkdir v2
cd ..
cd logs
touch master.log #用来存放master的日志
touch v1.log #用来存放第一个volume结点的日志
touch v1.log #用来存放第二个volume结点的日志

(3)运行master

sudo nohup /usr/local/weed master -mdir=/usr/local/data/fileData -port=9333 -defaultReplication="001" -ip="ip地址" >>/usr/local/data/logs/master.log &

其中各参数的意思可运行以下命令查看:

/usr/local/weed master -h

运行后可通过运行cat /usr/local/data/logs/master.log查看运行master的日志。
(4)运行volume

sudo /usr/local/weed volume -dir=/usr/local/data/volume/v1 -max=5 -mserver="ip地址:9333" -port=9080 -ip="ip地址" >>/usr/local/data/logs/v1.log &

sudo /usr/local/weed volume -dir=/usr/local/data/volume/v2 -max=5 -mserver="ip地址:9333" -port=9081 -ip="ip地址" >>/usr/local/data/logs/v2.log &

其中各参数的意思可运行以下命令查看:

/usr/local/weed volume -h

3.测试上传文件

(1)需要请求master, 得到分配的逻辑卷和fid

curl http://ip地址:9333/dir/assign

得到返回结果为:

{"fid":"5,020dab8398","url":"ip地址:9080","publicUrl":"ip地址:9080","count":1}

(2)使用返回的url和fid上传文件

curl -F file=@./seafile.txt http://ip地址:9080/5,020dab8398

得到返回结果为:

{"name":"seafile.txt","size":16,"eTag":"8727d9d8"}

(3)访问上传的文件
可通过在浏览器上访问以下网址:

http://ip地址:9080/5,020dab8398

or

http://ip地址:9080/5,020dab8398.txt

4.运行Filer并挂载到本地目录

(1)设置配置文件

mkdir -p /etc/seaweedfs
cd /etc/seaweedfs
touch filer.toml
mkdir -p /usr/local/data/filer_path

接着往filer.toml文件中添加以下内容:

recursive_delete = false
[leveldb2]
enabled = true
dir = "/usr/local/data/filer_path"

由于这里不使用mysql、redis、cassandra等来保存数据所以对应的配置我也没写在里面。
(2)启动filer

/usr/local/weed filer -master=ip地址:9333 -ip=ip地址 -defaultReplicaPlacement='001'&

(3)测试上传文件

curl -F "filename=@./seafile.txt" "http://ip地址:8888/test/"

得到返回结果为:

{"name":"seafile.txt","size":16,"fid":"4,015b48397e","url":"http://ip地址:9080/4,015b48397e"}

可通过输入以下网址来访问上传的文件:

http://ip地址:9080/4,015b48397e

or

http://ip地址:8888/test/seafile.txt

5.mount挂载

(1)设置挂载目录

cd /usr/local/data
mkdir mount

(2)启动挂载

 /usr/local/weed mount -filer=ip地址:8888 -dir=/usr/local/data/mount &

可进入到/usr/local/data/mount目录查看到刚刚上传的seafile.txt文件。

6.seaweedfs文件服务器相关文件操作整合springboot

首先增加有关的pom文件:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

注意第一个pom文件不要换成以下pom依赖,否则在注入webclient时会报No suitable default ClientHttpConnector found……的错误,可能跟springboot的父版本有关:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>

(1)新建Seaweedfs表以及其相应的实体类SeaweedfsDO,用来存放文件信息:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("Seaweedfs")
public class SeaweedfsDO implements Serializable {
    //主键
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * 文件名
     */
    private String fileName;
    /**
     * 文件大小
     */
    private float fileSize;
    /**
     * 文件标识
     */
    private String fileFid;
    /**
     * 文件url
     */
    private String fileUrl;
    /**
     * 文件描述
     */
    private String fileDesc;
}
截图

(2)建立数据操作层SeaweedfsMapper:

@Repository
public interface SeaweedfsMapper extends BaseMapper<SeaweedfsDO> {

    /**
     * 通过文件id获取文件信息
     * @param resourceId 文件id
     * @return 文件实体类
     */
    SeaweedfsDO getResourceById(Integer resourceId);
}

对应的SeaweedfsMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fileexample.filedemo.mapper.SeaweedfsMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.fileexample.filedemo.entity.SeaweedfsDO">
        <id column="id" property="id" />
        <result column="file_name" property="fileName" />
        <result column="file_size" property="fileSize" />
        <result column="file_fid" property="fileFid" />
        <result column="file_url" property="fileUrl" />
        <result column="file_desc" property="fileDesc" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, file_name, file_size, file_fid, file_url, file_desc
    </sql>

    <select id="getResourceById" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from Seaweedfs
        where id = #{resourceId}
    </select>
</mapper>

(3)新建SeaweedfsService以及其对应的实现类:

public interface SeaweedfsService extends IService<SeaweedfsDO>{

    /**
     * 上传文件到seaweedfs服务器
     * @param multipartFile 需要上传的文件
     * @param resourceDTO 文件传输对象
     * @return 文件在数据表里的id
     */
    Integer uploadSeaweedfsResource(MultipartFile multipartFile, ResourceDTO resourceDTO);

    /**
     * 下载seaweedfs服务器上的文件
     * @param fileName 文件名字
     * @param downloadUrl 文件路径
     * @return 是否下载成功(true or false)
     */
    boolean downloadSeaweedfsResource(String fileName, String downloadUrl);

    /**
     * 根据文件id删除seaweedfs服务器上的文件
     * @param downloadUrl 文件路径
     * @param resourceId 文件id
     * @return 是否删除成功(true or false)
     */
    boolean DeleteSeaweedfsResource(String downloadUrl,Integer resourceId);

    /**
     * 根据文件id获取文件信息
     * @param resourceId 文件id
     * @return 文件信息
     */
    SeaweedfsDO getResourceById(Integer resourceId);

}
@Service
@Slf4j
public class SeaweedfsServiceImpl extends ServiceImpl<SeaweedfsMapper, SeaweedfsDO> implements SeaweedfsService {

    @Autowired
    private SeaweedfsMapper seaweedfsMapper;

    /**
     * Seaweedfs服务器ip
     */
    @Value("${seaweedfs.ip}")
    private String seaweedfsIp;

    /**
     * Seaweedfs服务器上传文件端口号
     */
    @Value("${seaweedfs.port}")
    private int seaweedfsPort;

    /**
     * Seaweedfs服务器文件保存路径
     */
    @Value("${seaweedfs.uploadpath}")
    private String seaweedfsUploadPath;

    /**
     * 从远程服务器下载文件到本地电脑的位置,这里我设置在我的电脑桌面的downloads文件夹下
     */
    @Value("${seaweedfs.localPath}")
    private String localPath;


    @Override
    @Transactional(rollbackFor=Exception.class)
    public Integer uploadSeaweedfsResource(MultipartFile multipartFile, ResourceDTO resourceDTO) {
        // 获取上传文件名
        String filename = multipartFile.getOriginalFilename();
        if (StringUtils.isBlank(filename)) {
            log.info("上传文件不合法");
            return null;
        }
        // 获取文件扩展名
        String fileExtensionName = filename.substring(filename.lastIndexOf("."));
        // 获取文件经过UUID.randomUUID()生成的唯一识别码
        String fileNewName = UUID.randomUUID().toString() + fileExtensionName;
        // 文件目标位置
        File file = new File(fileNewName);
        try {
            //将文件内容输入到file中
            FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file);
        }catch (IOException ioe){
            log.info("文件错误");
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<FileSystemResource> entity = new HttpEntity<>(new FileSystemResource(file), headers);
        Mono<UploadResponse> response = WebClient.create().post().uri("http://" + seaweedfsIp + ":" + seaweedfsPort + seaweedfsUploadPath)
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(BodyInserters.fromMultipartData("filename", entity)).retrieve()
                .bodyToMono(UploadResponse.class);
        UploadResponse uploadResponse = response.block();
        uploadResponse.setUrl("http://" + seaweedfsIp + ":" + seaweedfsPort + seaweedfsUploadPath
                + file.getName());
        log.info("文件发送到seaweedfs服务器完成,现在开始将文件记录到数据库里");
        SeaweedfsDO seaweedfsDO = new SeaweedfsDO();
        seaweedfsDO.setFileName(uploadResponse.getName())
                   .setFileUrl(uploadResponse.getUrl())
                   .setFileFid(uploadResponse.getFid())
                   .setFileSize(uploadResponse.getSize())
                   .setFileDesc(resourceDTO.getResourceDesc());
        if(seaweedfsMapper.insert(seaweedfsDO) < 1) {
            throw new PersistenceException("插入seaweedfs表失败");
        }
        return seaweedfsDO.getId();
    }

    @Override
    public boolean downloadSeaweedfsResource(String fileName, String downloadUrl) {
        Mono<ClientResponse> clientResponseMono = WebClient.create().get().uri(downloadUrl)
                .accept(MediaType.APPLICATION_OCTET_STREAM).exchange();
        ClientResponse clientResponse = clientResponseMono.block();
        assert clientResponse != null;
        Resource resource = clientResponse.bodyToMono(Resource.class).block();
        File file = new File(localPath + fileName);
        assert resource != null;
        try {
            FileUtils.copyInputStreamToFile(resource.getInputStream(), file);
        }catch (IOException ioe){
            log.info("文件错误");
        }
        return true;
    }

    @Override
    public boolean DeleteSeaweedfsResource(String downloadUrl,Integer resourceId) {
        Mono<ClientResponse> clientResponseMono = WebClient.create().delete()
                .uri(downloadUrl)
                .accept(MediaType.APPLICATION_JSON).exchange();
        ClientResponse clientResponse = clientResponseMono.block();
        assert clientResponse != null;
        Mono<String> response = clientResponse.bodyToMono(String.class);
        String a= response.block();
        if(StringUtils.isBlank(a)){
            seaweedfsMapper.deleteById(resourceId);
            return true;
        }
        return false;
    }


    @Override
    public SeaweedfsDO getResourceById(Integer resourceId) {
        return seaweedfsMapper.getResourceById(resourceId);
    }
}

(4)向FileController中添加相关代码:

    @PostMapping("/seaweedfs-upload")
    public String seaweedfsUpload(@RequestParam("file") MultipartFile file, ResourceDTO resourceDTO) {
        int resourceId=seaweedfsService.uploadSeaweedfsResource(file,resourceDTO);
        return "上传文件到seaweedfs服务器成功,此文件的id为:" + resourceId;
    }

    @PostMapping("/seaweedfs-download")
    public String seaweedfsDownload(Integer resourceId) {
        SeaweedfsDO seaweedfsDO=seaweedfsService.getResourceById(resourceId);
        boolean success = seaweedfsService.downloadSeaweedfsResource(seaweedfsDO.getFileName(),seaweedfsDO.getFileUrl());
        if(success){
            return "下载seaweedfs服务器文件到本地成功";
        }
        return "下载seaweedfs服务器文件到本地失败";
    }

    @PostMapping("/seaweedfs-delete")
    public String seaweedfsDelete(Integer resourceId) {
        SeaweedfsDO seaweedfsDO=seaweedfsService.getResourceById(resourceId);
        boolean success = seaweedfsService.DeleteSeaweedfsResource(seaweedfsDO.getFileUrl(),resourceId);
        if(success){
            return "删除在seaweedfs服务器的文件成功";
        }
        return "删除在seaweedfs服务器的文件失败";
    }

(5)测试
上传文件:
新建seafileone文本文件:


截图

postman测试上传:


截图

成功上传后:
截图

截图

下载id为4的文件:


截图

下载成功后:


截图

删除id为4的文件:


截图

删除成功后test目录和数据表对应的数据空了:


截图

截图

有问题欢迎指正谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容