以往开发的时候都是使用的Apache服务器进行文件上传,后面改用了Ftp,现在开始接触FastDFS了,感觉还是FastDFS要高级一点,毕竟是分布式文件系统,从性能和稳定性上都有很高的保证。
搭建docker版本的FastDFS可以参考前面的文章
简介
FastDFS 是一个开源的高性能分布式文件系统(DFS)。 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。
FastDFS 系统有三个角色:跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)。
Tracker Server:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。
Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。
Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。
话不多说了,直接上代码了。我的框架是springboot 。
pom.xml
<!--FastDFS-->
<dependency>
<groupId>com.luhuiguo</groupId>
<artifactId>fastdfs-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
application.yml
fdfs:
#回显时的服务器地址,端口是fdfs默认的8888,这里我没有进行修改哦,注意哦~
file-host: http://172.16.24.203:8888/
tracker-list:
- 172.16.24.203:22122 #这里是上传的服务器地址,配置的是22122端口
so-timeout: 5000
pool:
max-total: 200
max-total-per-key: 50
max-wait-millis: 5000
Java代码
controller
package com.github.edu.modular.edu.controller;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.github.edu.core.base.controller.BaseController;
import com.github.edu.modular.edu.model.Attachment;
import com.github.edu.modular.edu.service.IAttachmentService;
import com.github.edu.modular.edu.util.ExceptionUtil;
import com.github.edu.modular.edu.util.FileUtil;
import com.github.edu.modular.edu.util.MultipartFileUtil;
import com.github.edu.modular.edu.util.R;
import com.luhuiguo.fastdfs.service.FastFileStorageClient;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.*;
/**
* attachment控制器
*
* @author Monkey
* @Date 2020-04-19 13:20:14
*/
@RestController
@RequestMapping("/attachment")
@Api(value = "Attachment-controller", description = "附件(完成)")
public class AttachmentController extends BaseController {
@Autowired
private IAttachmentService attachmentService;
@Autowired
private FastFileStorageClient fastFileStorageClient;
/**
* 下载,只支持小文件,适合播放类接口
* @param response
* @param filecode
* @throws Exception
*/
@GetMapping("/play/download/filecode/{filecode}")
@ApiOperation(value = "下载附件(完成)", notes = "下载附件(完成)")
public void downloadAttachment(HttpServletResponse response, @PathVariable String filecode) throws Exception{
Attachment attachment = attachmentService.selectOne(new EntityWrapper<Attachment>()
.eq("attachment_file_code", filecode)
);
if(Objects.isNull(attachment)){
throw new Exception("不是有效的文件地址");
}
String attachmentUrl = attachment.getAttachmentUrl();
String group = attachmentUrl.substring(0, attachmentUrl.indexOf("/"));
String path = attachmentUrl.substring(attachmentUrl.indexOf("/") + 1);
//通过直接下载(大文件会导致超时)
FileUtil.downloadFile(response, attachment.getAttachmentName(), fastFileStorageClient.downloadFile(group, path));
}
@RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
@ResponseBody
@ApiOperation(value = "上传工单附件(完成)", notes = "上传工单附件(完成)")
public Object uploadFile( @ApiParam("工单附件") MultipartFile file,
@ApiParam(value = "附件类型:1.工单 2.题目")@RequestParam(value = "attachmentType", defaultValue = "2", required = false)String attachmentType){
R r = new R();
try {
if (file != null) {
List<Attachment> attachments = new ArrayList<>();
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
File file1 = MultipartFileUtil.multipartFileToFile(file);
Attachment attachment = attachmentService.fileSave(file1, uuid);
attachment.setAttachmentType(attachmentType);
attachments.add(attachment);
//删除临时文件
MultipartFileUtil.delteTempFile(file1);
attachmentService.insertBatch(attachments);
Map map = new HashMap<>();
map.put("attachmentCode", uuid);
r.setData(map);
} else {
r = new R(null,"附件不能为空!", R.FAIL);
}
} catch (Exception e) {
r = ExceptionUtil.getException(e);
}
return r;
}
}
service
package com.github.edu.modular.edu.service.impl;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.github.edu.modular.edu.model.Attachment;
import com.github.edu.modular.edu.service.IAttachmentService;
import com.luhuiguo.fastdfs.domain.StorePath;
import com.luhuiguo.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.util.UUID;
/**
* attachmentService
*
* @author Monkey
* @Date 2020-04-19 13:20:14
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class AttachmentServiceImpl extends ServiceImpl<BaseMapper<Attachment>,Attachment> implements IAttachmentService {
@Autowired
private FastFileStorageClient fastFileStorageClient;
@Override
public Attachment fileSave(File f, String code) throws Exception {
String fileCode = UUID.randomUUID().toString();
String newFilename = fileCode + "." + FilenameUtils.getExtension(f.getName());
StorePath storePath = fastFileStorageClient.uploadFile(FileUtils.readFileToByteArray(f), newFilename);
Attachment attachment1 = new Attachment();
attachment1.setAttachmentUrl(storePath.getFullPath());
attachment1.setAttachmentCode(code);
attachment1.setAttachmentFileCode(fileCode.replaceAll("-", ""));
attachment1.setAttachmentDownloadUrl(("attachment/play/download/filecode/" + fileCode).replaceAll("-", ""));
attachment1.setAttachmentName(f.getName());
attachment1.setAttachmentType("1");
return attachment1;
}
}
util
package com.github.edu.modular.edu.util; /**
* Created by TongGuoBo on 2019/6/19.
*/
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @ClassName MultipartFileToFile
* @Description MultipartFile转fie
* @Author Monkey
* @Date 2019/6/19 13:48
**/
public class MultipartFileUtil {
/**
* MultipartFile 转 File
*
* @param file
* @throws Exception
*/
public static File multipartFileToFile(MultipartFile file) throws Exception {
File toFile = null;
if (file.equals("") || file.getSize() <= 0) {
file = null;
} else {
InputStream ins = null;
ins = file.getInputStream();
toFile = new File(file.getOriginalFilename());
inputStreamToFile(ins, toFile);
ins.close();
}
return toFile;
}
//获取流文件
private static void inputStreamToFile(InputStream ins, File file) {
try {
OutputStream os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
ins.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 删除本地临时文件
* @param file
*/
public static void delteTempFile(File file) {
if (file != null) {
File del = new File(file.toURI());
del.delete();
}
}
}
package com.github.edu.modular.edu.util;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
public class FileUtil {
/**
* 下载公共配置
* @param response
* @param fileName
* @throws Exception
*/
public static void downloadSetting(HttpServletResponse response, String fileName) throws Exception{
// 配置文件下载
response.setHeader("content-type", "application/octet-stream");
response.setContentType("application/octet-stream");
// 下载文件能正常显示中文
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
}
/**
* 下载文件
* @param response
* @param fileName
* 文件名
* @param data
* 下载数据
* @throws Exception
*/
public static void downloadFile(HttpServletResponse response, String fileName, byte[] data) throws Exception{
downloadSetting(response,fileName);
//实现下载
OutputStream os = response.getOutputStream();
os.write(data);
os.flush();
}
}
db
CREATE TABLE `attachment` (
`attachment_id` int(11) NOT NULL AUTO_INCREMENT,
`attachment_task_id` int(11) DEFAULT NULL COMMENT '问题分类id',
`attachment_code` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '附件code',
`attachment_file_code` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '文件编号',
`attachment_url` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT '请求地址',
`attachment_name` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT '附件名字',
`attachment_type` varchar(2) COLLATE utf8_bin DEFAULT '1' COMMENT '附件类型:1.工单 2.题目',
`attachment_download_url` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT '下载地址',
`attachment_create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`attachment_update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`attachment_create_by` int(11) DEFAULT NULL COMMENT '创建人',
`attachment_update_by` int(11) DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`attachment_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='附件';
swagger
通过code码查询到下载地址,即是接口地址