在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目录和数据表对应的数据空了: