|
@@ -1,18 +1,36 @@
|
|
|
package com.usoftchina.saas.file.controller;
|
|
package com.usoftchina.saas.file.controller;
|
|
|
|
|
|
|
|
|
|
+import com.github.tobato.fastdfs.domain.StorePath;
|
|
|
|
|
+import com.github.tobato.fastdfs.service.FastFileStorageClient;
|
|
|
import com.usoftchina.saas.base.Result;
|
|
import com.usoftchina.saas.base.Result;
|
|
|
|
|
+import com.usoftchina.saas.exception.BizException;
|
|
|
import com.usoftchina.saas.exception.ExceptionCode;
|
|
import com.usoftchina.saas.exception.ExceptionCode;
|
|
|
|
|
+import com.usoftchina.saas.file.constant.FileConstant;
|
|
|
|
|
+import com.usoftchina.saas.file.dto.FileInfoDTO;
|
|
|
import com.usoftchina.saas.file.dto.FolderDTO;
|
|
import com.usoftchina.saas.file.dto.FolderDTO;
|
|
|
-import com.usoftchina.saas.file.dto.FolderToSaveDTO;
|
|
|
|
|
-import com.usoftchina.saas.file.service.FileService;
|
|
|
|
|
|
|
+import com.usoftchina.saas.file.dto.FolderSaveDTO;
|
|
|
|
|
+import com.usoftchina.saas.file.po.FileInfo;
|
|
|
|
|
+import com.usoftchina.saas.file.service.FileInfoService;
|
|
|
|
|
+import com.usoftchina.saas.utils.BeanMapper;
|
|
|
import com.usoftchina.saas.utils.BizAssert;
|
|
import com.usoftchina.saas.utils.BizAssert;
|
|
|
|
|
+import com.usoftchina.saas.utils.CollectionUtils;
|
|
|
|
|
+import com.usoftchina.saas.utils.StringUtils;
|
|
|
import io.swagger.annotations.Api;
|
|
import io.swagger.annotations.Api;
|
|
|
import io.swagger.annotations.ApiOperation;
|
|
import io.swagger.annotations.ApiOperation;
|
|
|
|
|
+import org.apache.commons.compress.archivers.ArchiveOutputStream;
|
|
|
|
|
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
|
|
|
|
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
|
|
|
|
+import org.apache.commons.io.IOUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.web.bind.annotation.PostMapping;
|
|
|
|
|
-import org.springframework.web.bind.annotation.RequestBody;
|
|
|
|
|
-import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
|
|
-import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
+import org.springframework.http.MediaType;
|
|
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
+
|
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.net.URLEncoder;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 文件接口
|
|
* 文件接口
|
|
@@ -22,16 +40,307 @@ import org.springframework.web.bind.annotation.RestController;
|
|
|
*/
|
|
*/
|
|
|
@Api(value = "FileApi", description = "文件管理")
|
|
@Api(value = "FileApi", description = "文件管理")
|
|
|
@RestController
|
|
@RestController
|
|
|
-@RequestMapping("/api/file")
|
|
|
|
|
|
|
+@RequestMapping
|
|
|
public class FileController {
|
|
public class FileController {
|
|
|
|
|
|
|
|
@Autowired
|
|
@Autowired
|
|
|
- private FileService fileService;
|
|
|
|
|
|
|
+ private FileInfoService fileService;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FastFileStorageClient storageClient;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 文件访问服务baseUrl
|
|
|
|
|
+ *
|
|
|
|
|
+ * @Deprecated 文件需要权限限制,用nginx不好处理
|
|
|
|
|
+ */
|
|
|
|
|
+ @Value("${file.base-url}")
|
|
|
|
|
+ private String fileBaseUrl;
|
|
|
|
|
|
|
|
- @ApiOperation(value = "保存文件夹")
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检查文件夹ID的有效性
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param folderId
|
|
|
|
|
+ * @return
|
|
|
|
|
+ */
|
|
|
|
|
+ private FileInfo checkFolder(Long folderId) {
|
|
|
|
|
+ if (null == folderId || folderId == FileConstant.DEFAULT_FOLDER_ID) {
|
|
|
|
|
+ return FileInfo.newFolder(FileConstant.DEFAULT_FOLDER_ID, null, "");
|
|
|
|
|
+ } else if (folderId < 0) {
|
|
|
|
|
+ throw new BizException(ExceptionCode.FOLDER_INVALID);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ FileInfo info = fileService.findByPrimaryKey(folderId);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ if (!info.isFolder()) {
|
|
|
|
|
+ throw new BizException(ExceptionCode.FILE_NOT_FOLDER);
|
|
|
|
|
+ }
|
|
|
|
|
+ return info;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new BizException(ExceptionCode.FOLDER_NOT_EXISTS);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "创建文件夹")
|
|
|
@PostMapping(value = "/folder")
|
|
@PostMapping(value = "/folder")
|
|
|
- public Result<FolderDTO> folder(@RequestBody FolderToSaveDTO newFolder) {
|
|
|
|
|
- BizAssert.notNull(newFolder.getOriginalFilename(), ExceptionCode.FOLDER_NAME_EMPTY);
|
|
|
|
|
|
|
+ public Result<FolderDTO> folder(FolderSaveDTO newFolder) {
|
|
|
|
|
+ BizAssert.notNull(newFolder.getName(), ExceptionCode.FOLDER_NAME_EMPTY);
|
|
|
|
|
+ // 检查父文件夹
|
|
|
|
|
+ FileInfo parent = checkFolder(newFolder.getFolderId());
|
|
|
|
|
+ FileInfo info = FileInfo.newFolder(parent.getId(), newFolder.getName());
|
|
|
|
|
+ fileService.save(info);
|
|
|
|
|
+ return Result.success(BeanMapper.map(info, FolderDTO.class));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "上传文件")
|
|
|
|
|
+ @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
|
|
|
+ public Result<FileInfoDTO> upload(Long folderId, @RequestPart(value = "file") MultipartFile file) throws Exception {
|
|
|
|
|
+ // 检查父文件夹
|
|
|
|
|
+ FileInfo parent = checkFolder(folderId);
|
|
|
|
|
+ FileInfo info = FileInfo.newFile(file).folder(parent.getId())
|
|
|
|
|
+ .storeBy(storageClient).build();
|
|
|
|
|
+ fileService.save(info);
|
|
|
|
|
+ return Result.success(BeanMapper.map(info, FileInfoDTO.class));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "下载文件")
|
|
|
|
|
+ @GetMapping(value = "/download/{id}")
|
|
|
|
|
+ public void download(@PathVariable Long id, HttpServletResponse response) throws Exception {
|
|
|
|
|
+ FileInfo info = fileService.findByPrimaryKey(id);
|
|
|
|
|
+ if (null == info) {
|
|
|
|
|
+ throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
|
|
|
|
|
+ }
|
|
|
|
|
+ download(info, response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void download(FileInfo info, HttpServletResponse response) throws Exception{
|
|
|
|
|
+ response.setContentType("application/force-download");
|
|
|
|
|
+ if (info.isFolder()) {
|
|
|
|
|
+ // 压缩后下载
|
|
|
|
|
+ try (ArchiveOutputStream out = new ZipArchiveOutputStream(response.getOutputStream());) {
|
|
|
|
|
+ cascadeOutput(info, null, out);
|
|
|
|
|
+ out.finish();
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (null != info.getFullPath()) {
|
|
|
|
|
+ response.setHeader("Content-disposition",
|
|
|
|
|
+ "attachment; filename=" + URLEncoder.encode(info.getName(), "UTF-8"));
|
|
|
|
|
+ StorePath path = StorePath.praseFromUrl(info.getFullPath());
|
|
|
|
|
+ storageClient.downloadFile(path.getGroup(), path.getPath(),
|
|
|
|
|
+ in -> IOUtils.copy(in, response.getOutputStream()));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "下载文件")
|
|
|
|
|
+ @GetMapping(value = "/download")
|
|
|
|
|
+ public void download(@RequestParam String path, HttpServletResponse response) throws Exception {
|
|
|
|
|
+ FileInfo info = fileService.findByFullPath(path);
|
|
|
|
|
+ if (null == info) {
|
|
|
|
|
+ throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
|
|
|
|
|
+ }
|
|
|
|
|
+ download(info, response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "查看文件")
|
|
|
|
|
+ @GetMapping(value = "/view/{id}")
|
|
|
|
|
+ public void view(@PathVariable Long id, HttpServletResponse response) throws Exception {
|
|
|
|
|
+ FileInfo info = fileService.findByPrimaryKey(id);
|
|
|
|
|
+ if (null == info) {
|
|
|
|
|
+ throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
|
|
|
|
|
+ }
|
|
|
|
|
+ view(info, response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void view(FileInfo info, HttpServletResponse response) throws Exception{
|
|
|
|
|
+ if (info.isImage() || info.isText()) {
|
|
|
|
|
+ if (!StringUtils.isEmpty(info.getMime())) {
|
|
|
|
|
+ response.setContentType(info.getMime());
|
|
|
|
|
+ }
|
|
|
|
|
+ StorePath path = StorePath.praseFromUrl(info.getFullPath());
|
|
|
|
|
+ storageClient.downloadFile(path.getGroup(), path.getPath(),
|
|
|
|
|
+ in -> IOUtils.copy(in, response.getOutputStream()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 无法直接查看的文件,下载到客户端
|
|
|
|
|
+ download(info.getId(), response);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "查看文件")
|
|
|
|
|
+ @GetMapping(value = "/view")
|
|
|
|
|
+ public void view(@RequestParam String path, HttpServletResponse response) throws Exception {
|
|
|
|
|
+ FileInfo info = fileService.findByFullPath(path);
|
|
|
|
|
+ if (null == info) {
|
|
|
|
|
+ throw new BizException(ExceptionCode.FILE_NOT_EXISTS);
|
|
|
|
|
+ }
|
|
|
|
|
+ view(info, response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "查看文件信息")
|
|
|
|
|
+ @GetMapping(value = "/info/{id}")
|
|
|
|
|
+ public Result<FileInfoDTO> getFileInfo(@PathVariable Long id) {
|
|
|
|
|
+ FileInfo info = fileService.findByPrimaryKey(id);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ return Result.success(BeanMapper.map(info, FileInfoDTO.class));
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.error(ExceptionCode.FILE_NOT_EXISTS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "查看文件信息")
|
|
|
|
|
+ @GetMapping(value = "/info")
|
|
|
|
|
+ public Result<FileInfoDTO> getFileInfo(@RequestParam String path) {
|
|
|
|
|
+ FileInfo info = fileService.findByFullPath(path);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ return Result.success(BeanMapper.map(info, FileInfoDTO.class));
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.error(ExceptionCode.FILE_NOT_EXISTS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "查看子文件")
|
|
|
|
|
+ @GetMapping(value = "/list")
|
|
|
|
|
+ public Result<List<FileInfoDTO>> listFiles(Long folderId) {
|
|
|
|
|
+ List<FileInfo> files = fileService.findByFolderId(checkFolder(folderId).getId());
|
|
|
|
|
+ return Result.success(BeanMapper.mapList(files, FileInfoDTO.class));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 以压缩文件的方式,级联输出
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param info
|
|
|
|
|
+ * @param directory
|
|
|
|
|
+ * @param out
|
|
|
|
|
+ * @throws IOException
|
|
|
|
|
+ */
|
|
|
|
|
+ private void cascadeOutput(FileInfo info, String directory, ArchiveOutputStream out) throws IOException{
|
|
|
|
|
+ if (info.isFolder()) {
|
|
|
|
|
+ if (null == directory) {
|
|
|
|
|
+ directory = "/";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ directory = directory + info.getName() + "/";
|
|
|
|
|
+ out.putArchiveEntry(new ZipArchiveEntry(directory));
|
|
|
|
|
+ out.closeArchiveEntry();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<FileInfo> files = fileService.findByFolderId(info.getId());
|
|
|
|
|
+ if (!CollectionUtils.isEmpty(files)) {
|
|
|
|
|
+ for (FileInfo f : files) {
|
|
|
|
|
+ cascadeOutput(f, directory,out);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ out.putArchiveEntry(new ZipArchiveEntry(directory + info.getName()));
|
|
|
|
|
+ StorePath path = StorePath.praseFromUrl(info.getFullPath());
|
|
|
|
|
+ storageClient.downloadFile(path.getGroup(), path.getPath(), in -> IOUtils.copy(in, out));
|
|
|
|
|
+ out.closeArchiveEntry();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 级联删除
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param info
|
|
|
|
|
+ */
|
|
|
|
|
+ private void cascadeRemove(FileInfo info) {
|
|
|
|
|
+ if (info.isFolder()) {
|
|
|
|
|
+ List<FileInfo> files = fileService.findByFolderId(info.getId());
|
|
|
|
|
+ if (!CollectionUtils.isEmpty(files)) {
|
|
|
|
|
+ files.forEach(this::cascadeRemove);
|
|
|
|
|
+ }
|
|
|
|
|
+ fileService.removeByPrimaryKey(info.getId());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fileService.removeByPrimaryKey(info.getId());
|
|
|
|
|
+ storageClient.deleteFile(info.getFullPath());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 级联回收
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param info
|
|
|
|
|
+ */
|
|
|
|
|
+ private void cascadeRetrieve(FileInfo info) {
|
|
|
|
|
+ if (info.isFolder()) {
|
|
|
|
|
+ List<FileInfo> files = fileService.findByFolderId(info.getId());
|
|
|
|
|
+ if (!CollectionUtils.isEmpty(files)) {
|
|
|
|
|
+ files.forEach(this::cascadeRetrieve);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ info.retrieve();
|
|
|
|
|
+ fileService.updateByPrimaryKey(info);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 级联还原
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param info
|
|
|
|
|
+ */
|
|
|
|
|
+ private void cascadeRestore(FileInfo info) {
|
|
|
|
|
+ info.restore();
|
|
|
|
|
+ fileService.updateByPrimaryKey(info);
|
|
|
|
|
+ if (info.isFolder()) {
|
|
|
|
|
+ List<FileInfo> files = fileService.findByFolderId(info.getId());
|
|
|
|
|
+ if (!CollectionUtils.isEmpty(files)) {
|
|
|
|
|
+ files.forEach(this::cascadeRestore);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "删除文件")
|
|
|
|
|
+ @GetMapping(value = "/delete/{id}")
|
|
|
|
|
+ public Result delete(@PathVariable Long id,
|
|
|
|
|
+ @RequestParam(required = false, defaultValue = "false") Boolean purge) {
|
|
|
|
|
+ FileInfo info = fileService.findByPrimaryKey(id);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ if (purge) {
|
|
|
|
|
+ cascadeRemove(info);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ cascadeRetrieve(info);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "删除文件")
|
|
|
|
|
+ @GetMapping(value = "/delete")
|
|
|
|
|
+ public Result delete(@RequestParam String path,
|
|
|
|
|
+ @RequestParam(required = false, defaultValue = "false") Boolean purge) {
|
|
|
|
|
+ FileInfo info = fileService.findByFullPath(path);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ if (purge) {
|
|
|
|
|
+ cascadeRemove(info);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ cascadeRetrieve(info);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "还原文件")
|
|
|
|
|
+ @GetMapping(value = "/restore/{id}")
|
|
|
|
|
+ public Result restore(@PathVariable Long id) {
|
|
|
|
|
+ FileInfo info = fileService.findByPrimaryKey(id);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ cascadeRestore(info);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "还原文件")
|
|
|
|
|
+ @GetMapping(value = "/restore")
|
|
|
|
|
+ public Result restore(@RequestParam String path) {
|
|
|
|
|
+ FileInfo info = fileService.findByFullPath(path);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ cascadeRestore(info);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ApiOperation(value = "移动文件到文件夹")
|
|
|
|
|
+ @GetMapping(value = "/move")
|
|
|
|
|
+ public Result move(@RequestParam Long fileId, @RequestParam Long folderId) {
|
|
|
|
|
+ FileInfo info = fileService.findByPrimaryKey(fileId);
|
|
|
|
|
+ if (null != info) {
|
|
|
|
|
+ info.setFolderId(folderId);
|
|
|
|
|
+ fileService.updateByPrimaryKey(info);
|
|
|
|
|
+ }
|
|
|
return Result.success();
|
|
return Result.success();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|