在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目錄和數據表對應的數據空了: