首先这盘博客是参考SpringBoot Mongodb文件存储服务器,并且修改了原来博客中过时的废弃的方法,并把我实际做的公布出来。
一、首先提醒
因为我做的是多模块项目,用dubbo,zk实现的微服务,但是因为上传的文件过大,所以肯定不能通过微服务传输数据再到服务的实现类里去上传文件,所以我就在
webapp
(应该都知道是哪个模块)这个模块直接和mongoDB连接传输文件。
二、依赖(都是webapp模块下的)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
application.properties
spring.data.mongodb.uri=mongodb://user:123456@127.0.0.1:27017/first?authSource=admin&authMechanism=SCRAM-SHA-256
spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=50MB
三、用到的POJO
@Data
public class FileDocument implements Serializable {
/**
* id
*/
private String id;
/**
* 文件名称
*/
private String name;
/**
* 文件大小
*/
private long size;
/**
* 上传时间
*/
private Date uploadDate;
/**
* 文件的md5值
*/
private String md5;
/**
* 文件内容
*/
private byte[] content;
/**
* 文件类型
*/
private String contentType;
/**
* 文件后缀
*/
private String suffix;
/**
* 文件描述
*/
private String description;
/**
* 大文件管理GridFS的ID
*/
private String gridfsId;
}
四、服务和实现类
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Optional;
/**
* @author 孔超
* @date 2020/5/11 15:20
*/
public interface FileService {
/**
* 保存信息的文件
*
* @param md5
* @param file
* @return
*/
public FileDocument saveFile(String md5, MultipartFile file);
/**
* 移除文件
*
* @param id
* @param isDeleteFile
*/
public void removeFile(String id, boolean isDeleteFile);
/**
* 根据id查询附件
*
* @param id
* @return
*/
public Optional<FileDocument> getById(String id);
/**
* 根据md5获取文件对象
*
* @param md5
* @return
*/
public FileDocument getByMd5(String md5);
/**
* 文件分页
*
* @param pageIndex
* @param pageSize
* @return
*/
public List<FileDocument> listFilesByPage(int pageIndex, int pageSize);
}
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.mongodb.client.result.DeleteResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Field;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* @author 孔超
* @date 2020/5/11 15:18
*/
@Slf4j
@Service("fileService")
public class FileServiceImpl implements FileService {
private static String collectionName = "fileDate";
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private GridFsTemplate gridFsTemplate;
@Autowired
private GridFSBucket gridFSBucket;
/**
* 表单上传附件
*
* @param md5
* @param file
* @return
*/
@Override
public FileDocument saveFile(String md5, MultipartFile file) {
//已存在该文件,则实现秒传
FileDocument fileDocument = getByMd5(md5);
if (fileDocument != null) {
return fileDocument;
}
fileDocument = new FileDocument();
fileDocument.setName(file.getOriginalFilename());
fileDocument.setSize(file.getSize());
fileDocument.setContentType(file.getContentType());
fileDocument.setUploadDate(new Date());
fileDocument.setMd5(md5);
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
fileDocument.setSuffix(suffix);
try {
//文件存入gridfs
String gridfsId = uploadFileToGridFS(file.getInputStream(), file.getContentType());
fileDocument.setGridfsId(gridfsId);
//上传的信息保存在mongodb
fileDocument = mongoTemplate.save(fileDocument, collectionName);
} catch (IOException ex) {
ex.printStackTrace();
}
return fileDocument;
}
/**
* 上传文件到Mongodb的GridFs中
*
* @param in
* @param contentType
* @return
*/
private String uploadFileToGridFS(InputStream in, String contentType) {
String gridfsId = UUID.randomUUID().toString();
//文件,存储在GridFS中
gridFsTemplate.store(in, gridfsId, contentType);
return gridfsId;
}
/**
* 删除附件
*
* @param id 文件id
* @param isDeleteFile 是否删除文件
*/
@Override
public void removeFile(String id, boolean isDeleteFile) {
FileDocument fileDocument = mongoTemplate.findById(id, FileDocument.class, collectionName);
if (fileDocument != null) {
Query query = new Query().addCriteria(Criteria.where("_id").is(id));
DeleteResult result = mongoTemplate.remove(query, collectionName);
System.out.println("result:" + result.getDeletedCount());
if (isDeleteFile) {
Query deleteQuery = new Query().addCriteria(Criteria.where("filename").is(fileDocument.getGridfsId()));
gridFsTemplate.delete(deleteQuery);
}
}
}
/**
* 查询附件
*
* @param id 文件id
* @return
* @throws IOException
*/
@Override
public Optional<FileDocument> getById(String id) {
FileDocument fileDocument = mongoTemplate.findById(id, FileDocument.class, collectionName);
if (fileDocument != null) {
Query gridQuery = new Query().addCriteria(Criteria.where("filename").is(fileDocument.getGridfsId()));
try {
GridFSFile fsFile = gridFsTemplate.findOne(gridQuery);
GridFSDownloadStream in = gridFSBucket.openDownloadStream(fsFile.getObjectId());
if (in.getGridFSFile().getLength() > 0) {
GridFsResource resource = new GridFsResource(fsFile, in);
fileDocument.setContent(inputStreamToByte(resource.getInputStream()));
return Optional.of(fileDocument);
} else {
fileDocument = null;
return Optional.empty();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return Optional.empty();
}
/**
* 流转换成byte数组
*
* @param inStream
* @return
* @throws IOException
*/
private byte[] inputStreamToByte(InputStream inStream) throws IOException {
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
//buff用于存放循环读取的临时数据
byte[] buff = new byte[100];
int rc = 0;
while ((rc = inStream.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
return swapStream.toByteArray();
}
/**
* 根据md5获取文件对象
*
* @param md5
* @return
*/
@Override
public FileDocument getByMd5(String md5) {
Query query = new Query().addCriteria(Criteria.where("md5").is(md5));
return mongoTemplate.findOne(query, FileDocument.class, collectionName);
}
/**
* 文件分页
*
* @param pageIndex
* @param pageSize
* @return
*/
@Override
public List<FileDocument> listFilesByPage(int pageIndex, int pageSize) {
Sort sort = Sort.by(Sort.Order.desc("uploadDate"));
Query query = new Query().with(sort);
long skip = (pageIndex - 1) * pageSize;
query.skip(skip);
query.limit(pageSize);
Field field = query.fields();
field.exclude("content");
List<FileDocument> files = mongoTemplate.find(query, FileDocument.class, collectionName);
return files;
}
}
五、FileController
1.返回值
Result
是我自己的工具类,用于返回信息的,你可以自己编
2.这里面和上传实现类是有重复的,我没整理,应该整理成工具类的
3.下面在线预览的前端需要进行处理,这里的简单例子不支持
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
/**
* @author 孔超
* @date 2020/5/11 15:17
*/
@Slf4j
@RestController
@RequestMapping("files")
public class FileController {
@Resource
FileService fileService;
/**
* 上传文件列表
*
* @param pageIndex
* @param pageSize
* @return
*/
@RequestMapping("/list")
public List<FileDocument> list(int pageIndex, int pageSize) {
return fileService.listFilesByPage(pageIndex, pageSize);
}
/**
* 在线显示文件:目前不能实现在线预览,需要前端进行处理,这个简单例子是不行
*
* @param id 文件id
* @return
*/
@GetMapping("/view")
public ResponseEntity<Object> serveFileOnline(String id) {
Optional<FileDocument> file = fileService.getById(id);
if (file.isPresent()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "fileName=" + file.get().getName())
.header(HttpHeaders.CONTENT_TYPE, file.get().getContentType())
.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
.body(file.get().getContent());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not found");
}
}
/**
* 下载文件
*
* @param id
* @return
* @throws UnsupportedEncodingException
*/
@GetMapping("/down")
public ResponseEntity<Object> downloadFileById(String id) throws UnsupportedEncodingException {
Optional<FileDocument> file = fileService.getById(id);
if (file.isPresent()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; fileName=" + URLEncoder.encode(file.get().getName(), "utf-8"))
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
.body(file.get().getContent());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not found");
}
}
/**
* 表单上传文件
* 当数据库中存在该md5值时,可以实现秒传功能
*
* @param file 文件
* @return
*/
@PostMapping("/upload")
public Result formUpload(@RequestParam("file") MultipartFile file) {
Result result = null;
try {
if (file != null && !file.isEmpty()) {
String fileMd5 = getMD5(file.getInputStream());
FileDocument fileDocument = fileService.saveFile(fileMd5, file);
System.out.println(fileDocument);
result = new Result(1, "上传成功", fileDocument.getId());
} else {
result = new Result(0, "请上传文件");
}
} catch (IOException e) {
log.error("IOE异常", e);
result = new Result(-1, "系统异常");
} catch (Exception e) {
log.error("上传文件系统异常,", e);
result = new Result(-1, "系统异常");
}
return result;
}
/**
* 删除附件
*
* @param id
* @return
*/
@GetMapping("/delete")
public Result deleteFileByGetMethod(String id) {
Result result = null;
if (id != null) {
fileService.removeFile(id, true);
result = new Result(1, "删除成功");
} else {
result = new Result(0, "请传输文件");
}
return result;
}
/**
* 流转换成MD5字符串
*
* @param inputStream
* @return
*/
private String getMD5(InputStream inputStream) {
BigInteger md5 = null;
try {
byte[] buffer = inputStreamToByte(inputStream);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(buffer, 0, buffer.length);
byte[] b = md.digest();
md5 = new BigInteger(1, b);
} catch (NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
}
assert md5 != null;
return md5.toString(16);
}
/**
* 流转换成byte数组
*
* @param inStream
* @return
* @throws IOException
*/
private byte[] inputStreamToByte(InputStream inStream) throws IOException {
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
//buff用于存放循环读取的临时数据
byte[] buff = new byte[100];
int rc = 0;
while ((rc = inStream.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
return swapStream.toByteArray();
}
}
六 简单的例子
单独的Controller
这个和上面的FileController是不一样的,当然也可以在一起
@RequestMapping("/gotoTest.do")
public String gotoTest(Model model) {
//这里我定死了,没有根据前端提供的分页数据的来,这里的list是mongoDB里的数据的信息,
//如果想根据前端提供的数据来的话上面的参数加上@RequestParam(value = "pageIndex" , defaultValue = "1") int pageIndex,@RequestParam(value = "pageSize" , defaultValue = "10") int pageSize
List<FileDocument> fileDocumentList = fileService.listFilesByPage(1, 10);
model.addAttribute("fileDocumentList", fileDocumentList);
return "text";
}
@RequestMapping("/upload")
public String upload() {
return "upload";
}
test.ftl
我用的freemarker
<div class="find-top1">
<form action="" id="form_Recruitment">
<table class="top-table">
<tr>
<td>id</td>
<td>名字</td>
<td>大小</td>
<td>操作</td>
</tr>
<#list fileDocumentList as fileDocument>
<tr>
<td>${fileDocument.id}</td>
<td>${fileDocument.name}</td>
<td>${fileDocument.size}</td>
<td><a href="/files/view?id=${fileDocument.id}">预览</a><a>下载</a> <a>删除</a></td>
</tr>
</#list>
</table>
</form>
</div>
效果图
upload.ftl
<form method="POST" enctype="multipart/form-data" action="/files/upload">
<p>
文件:<input type="file" name="file"/>
</p>
<p>
<input type="submit" value="上传"/>
</p>
</form>
效果图
上面就是特别简单的实例
七 实际的例子
文档数据库就当好它的数据库,它记录的是文档,一般我们创建时很多时候是很多input和文件一起提交到后端,而关系型数据库记录的是文档在文档数据库中的id或者唯一标识符。
主要在于怎么提交既有文件又有input的form表单
前端:
<form name="form_recruiters">
<table class="top-table">
<tr>
<td class="top-table-label">姓名:</td>
<td><input type="text" name="name"></td>
<td class="top-table-label">应聘岗位:</td>
<td><input type="text" name="positions"></td>
<td class="top-table-label">工作类型:</td>
<td>
<select name="type">
<option>实习</option>
<option>全职</option>
</select>
</td>
</tr>
<tr>
<td class="top-table-label">上传简历</td>
<td colspan="5"><input type="file" name="file"></td>
</tr>
<tr>
<td colspan="6" style="text-align: center">
<i class="glyphicon glyphicon-edit"></i><input type="button" value="提交" data-dismiss="modal" onclick="submitRecruiters()"/>
</td>
</tr>
</table>
</form>
js
function submitRecruiters() {
//创建一个formdate对象传输
var form = document.forms.namedItem("form_recruiters");
var formData = new FormData(form);
$.ajax({
url: "/saveRecruiters.do",
type: "POST",
data: formData,
dataType: "json",
contentType: false, //必须
processData: false, //必须
cache: false,
success: function (result) {
if (result.code === 1) {
location.reload();
}
alert(result.msg);
},
error: function () {
alert("连接服务器异常,请刷新后重试")
}
});
}
后端
@PostMapping("/saveRecruiters.do")
@ResponseBody
public Result saveRecruiters(@RequestParam("file") MultipartFile file, @Valid Recruiters recruiters) {
if (file == null || file.isEmpty()) {
return new Result(0, "请上传简历");
}
//uploadFile这个就是FileController中的上传文件的代码,返回的是文档数据库中的id
String resumeId = uploadFile(file);
if (resumeId == null) {
return new Result(0, "简历上传失败,请重新上传");
}
recruiters.setResumeId(resumeId);
Result result = null;
try {
Long isSuc = recruitersService.insert(recruiters);
if (isSuc != 1) {
result = new Result(0, "添加失败,请重新添加");
} else {
result = new Result(1, "添加成功");
}
} catch (Exception e) {
log.error("保存应聘者信息出现系统异常recruiters{}", JSON.toJSONString(recruiters), e);
result = new Result(1,"出现系统异常");
}
return result;
}
这样就完成了