diff --git a/module-file/src/main/java/ink/wgink/module/file/controller/app/api/filecenter/FileCenterAppController.java b/module-file/src/main/java/ink/wgink/module/file/controller/app/api/filecenter/FileCenterAppController.java index 754e43dc..ecf3148a 100644 --- a/module-file/src/main/java/ink/wgink/module/file/controller/app/api/filecenter/FileCenterAppController.java +++ b/module-file/src/main/java/ink/wgink/module/file/controller/app/api/filecenter/FileCenterAppController.java @@ -14,6 +14,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + /** * @ClassName: FileAppCenterController * @Description: APP文件远程 @@ -97,7 +100,17 @@ public class FileCenterAppController extends DefaultBaseController { return new SuccessResultData<>(fileCenterService.uploadSingleByUserId(creator, audio, UploadTypeEnum.AUDIO)); } - + @ApiOperation(value = "文件下载", notes = "文件下载(用于直接下载)接口") + @ApiImplicitParams({ + @ApiImplicitParam(name = "fileId", value = "文件ID", paramType = "path") + }) + @ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)}) + @GetMapping("download/{fileId}") + public void download(HttpServletRequest request, + HttpServletResponse response, + @PathVariable("fileId") String fileId) { + fileCenterService.download(request, response, fileId); + } /** * 校验Key与Secret diff --git a/module-file/src/main/java/ink/wgink/module/file/controller/route/v2/FileRouteController.java b/module-file/src/main/java/ink/wgink/module/file/controller/route/v2/FileRouteController.java index f9db53b3..25461fdb 100644 --- a/module-file/src/main/java/ink/wgink/module/file/controller/route/v2/FileRouteController.java +++ b/module-file/src/main/java/ink/wgink/module/file/controller/route/v2/FileRouteController.java @@ -35,7 +35,7 @@ public class FileRouteController { return modelAndView; } - @ApiOperation(value = "文件下载", notes = "文件下载接口") + @ApiOperation(value = "文件重定向下载(浏览器)", notes = "文件重定向下载(适用于浏览器访问图片)接口") @ApiImplicitParams({ @ApiImplicitParam(name = "isOpen", value = "是否打开,true和false", paramType = "path"), @ApiImplicitParam(name = "fileId", value = "文件ID", paramType = "path") @@ -46,7 +46,19 @@ public class FileRouteController { HttpServletResponse response, @PathVariable("isOpen") Boolean isOpen, @PathVariable("fileId") String fileId) { - fileService.download(request, response, isOpen, fileId); + fileService.downloadRedirect(request, response, isOpen, fileId); + } + + @ApiOperation(value = "文件下载", notes = "文件下载(用于直接下载)接口") + @ApiImplicitParams({ + @ApiImplicitParam(name = "fileId", value = "文件ID", paramType = "path") + }) + @ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)}) + @GetMapping("download/{fileId}") + public void download(HttpServletRequest request, + HttpServletResponse response, + @PathVariable("fileId") String fileId) { + fileService.download(request, response, fileId); } } diff --git a/module-file/src/main/java/ink/wgink/module/file/service/filecenter/IFileCenterService.java b/module-file/src/main/java/ink/wgink/module/file/service/filecenter/IFileCenterService.java index 1670bbdf..532fb91a 100644 --- a/module-file/src/main/java/ink/wgink/module/file/service/filecenter/IFileCenterService.java +++ b/module-file/src/main/java/ink/wgink/module/file/service/filecenter/IFileCenterService.java @@ -4,6 +4,9 @@ import ink.wgink.module.file.enums.UploadTypeEnum; import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + /** * @ClassName: IFileCenterService * @Description: 文件中心 @@ -23,5 +26,13 @@ public interface IFileCenterService { */ FileUploadSuccessDTO uploadSingleByUserId(String userId, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum); + /** + * 文件下载 + * + * @param request + * @param response + * @param fileId + */ + void download(HttpServletRequest request, HttpServletResponse response, String fileId); } diff --git a/module-file/src/main/java/ink/wgink/module/file/service/filecenter/impl/FileCenterServiceImpl.java b/module-file/src/main/java/ink/wgink/module/file/service/filecenter/impl/FileCenterServiceImpl.java index 84465286..ef796c12 100644 --- a/module-file/src/main/java/ink/wgink/module/file/service/filecenter/impl/FileCenterServiceImpl.java +++ b/module-file/src/main/java/ink/wgink/module/file/service/filecenter/impl/FileCenterServiceImpl.java @@ -1,18 +1,12 @@ package ink.wgink.module.file.service.filecenter.impl; import ink.wgink.common.base.DefaultBaseService; -import ink.wgink.exceptions.FileException; import ink.wgink.exceptions.SearchException; import ink.wgink.module.file.enums.UploadTypeEnum; import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO; import ink.wgink.module.file.pojo.vos.v2.FileSaveVO; import ink.wgink.module.file.service.filecenter.IFileCenterService; -import ink.wgink.module.file.service.filedownload.IFileDownloadService; import ink.wgink.module.file.service.v2.IFileService; -import ink.wgink.pojo.pos.FilePO; -import ink.wgink.util.request.StaticResourceRequestUtil; -import org.apache.catalina.connector.ClientAbortException; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -21,12 +15,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.net.URLEncoder; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; /** * @ClassName: FileCenterServiceImpl @@ -40,8 +28,6 @@ public class FileCenterServiceImpl extends DefaultBaseService implements IFileCe @Autowired private IFileService fileService; - @Autowired - private IFileDownloadService fileDownloadService; @Override public FileUploadSuccessDTO uploadSingleByUserId(String userId, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum) { @@ -49,50 +35,9 @@ public class FileCenterServiceImpl extends DefaultBaseService implements IFileCe return fileService.saveFile(fileSaveVO); } - private void download(HttpServletRequest request, HttpServletResponse response, String fileId) { - FilePO filePO = fileService.getPO(fileId); - try ( - RandomAccessFile randomAccessFile = new RandomAccessFile(filePO.getFilePath(), "r"); - FileChannel fileChannel = randomAccessFile.getChannel(); - OutputStream outputStream = response.getOutputStream(); - WritableByteChannel writableByteChannel = Channels.newChannel(outputStream); - ) { - response.setHeader("Content-Length", filePO.getFileSize().toString()); - response.setContentType(StaticResourceRequestUtil.getContentType(filePO.getFileType())); - response.setHeader("Content-Disposition", "inline;fileName=" + URLEncoder.encode(filePO.getFileName(), "UTF-8")); - String rangeString = request.getHeader("Range"); - long contentLength = filePO.getFileSize(); - long startRange = 0; - long endRange = contentLength - 1; - if (!StringUtils.isBlank(rangeString)) { - response.setContentType("multipart/byteranges"); - String[] rangeArray = rangeString.substring(rangeString.indexOf("=") + 1).split("-"); - startRange = Long.parseLong(rangeArray[0]); - if (rangeArray.length > 1) { - endRange = Long.parseLong(rangeArray[1]); - } - response.setHeader("Content-Length", String.valueOf(endRange - startRange + 1)); - response.setHeader("Content-Range", String.format("bytes %d-%d/%d", startRange, endRange, contentLength)); - response.setHeader("Accept-Ranges", "bytes"); - response.setHeader("Etag", fileId); - response.setStatus(206); - randomAccessFile.seek(startRange); - } - LOG.debug("startRange: {}, endRange: {}", startRange, endRange); - long totalOutputLength = endRange - startRange + 1; - fileChannel.transferTo(startRange, totalOutputLength, writableByteChannel); - outputStream.flush(); - if (endRange >= contentLength - 1) { - fileDownloadService.handle(request, false, fileId); - } - } catch (Exception e) { - e.printStackTrace(); - if (e instanceof ClientAbortException) { - LOG.debug("客户端断开连接"); - } else { - throw new FileException("文件输出异常", e); - } - } + @Override + public void download(HttpServletRequest request, HttpServletResponse response, String fileId) { + fileService.download(request, response, fileId); } /** diff --git a/module-file/src/main/java/ink/wgink/module/file/service/filedownload/IFileDownloadService.java b/module-file/src/main/java/ink/wgink/module/file/service/filedownload/IFileDownloadService.java index 78dc40ea..c12e56ef 100644 --- a/module-file/src/main/java/ink/wgink/module/file/service/filedownload/IFileDownloadService.java +++ b/module-file/src/main/java/ink/wgink/module/file/service/filedownload/IFileDownloadService.java @@ -3,9 +3,11 @@ package ink.wgink.module.file.service.filedownload; import ink.wgink.module.file.pojo.dtos.filedownload.FileDownloadDTO; import ink.wgink.module.file.pojo.vos.filedownload.FileDownloadVO; import ink.wgink.pojo.ListPage; +import ink.wgink.pojo.pos.FilePO; import ink.wgink.pojo.result.SuccessResultList; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Map; @@ -68,12 +70,22 @@ public interface IFileDownloadService { Integer countByFileId(String fileId); /** - * 下载 + * 文件下载 * - * @param httpServletRequest - * @param isOpen 是否打开 - * @param fileId + * @param request + * @param response + * @param filePO */ - void handle(HttpServletRequest httpServletRequest, boolean isOpen, String fileId); + void download(HttpServletRequest request, HttpServletResponse response, FilePO filePO); + + /** + * 文件重定向下载 + * + * @param request + * @param response + * @param isOpen + * @param filePO + */ + void downloadRedirect(HttpServletRequest request, HttpServletResponse response, boolean isOpen, FilePO filePO); } diff --git a/module-file/src/main/java/ink/wgink/module/file/service/filedownload/impl/FileDownloadServiceImpl.java b/module-file/src/main/java/ink/wgink/module/file/service/filedownload/impl/FileDownloadServiceImpl.java index ee856608..b6dd62fe 100644 --- a/module-file/src/main/java/ink/wgink/module/file/service/filedownload/impl/FileDownloadServiceImpl.java +++ b/module-file/src/main/java/ink/wgink/module/file/service/filedownload/impl/FileDownloadServiceImpl.java @@ -3,22 +3,36 @@ package ink.wgink.module.file.service.filedownload.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import ink.wgink.common.base.DefaultBaseService; +import ink.wgink.exceptions.FileException; import ink.wgink.module.file.dao.filedownload.IFileDownloadDao; +import ink.wgink.module.file.manager.FilesManager; import ink.wgink.module.file.pojo.dtos.filedownload.FileDownloadDTO; import ink.wgink.module.file.pojo.vos.filedownload.FileDownloadVO; import ink.wgink.module.file.service.filedownload.IFileDownloadService; import ink.wgink.pojo.ListPage; +import ink.wgink.pojo.pos.FilePO; import ink.wgink.pojo.result.SuccessResultList; import ink.wgink.util.UUIDUtil; import ink.wgink.util.date.DateUtil; import ink.wgink.util.map.HashMapUtil; import ink.wgink.util.request.RequestUtil; +import ink.wgink.util.request.StaticResourceRequestUtil; import ink.wgink.util.thread.CachedThreadPoolUtil; +import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.net.URLEncoder; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; import java.util.List; import java.util.Map; @@ -77,8 +91,74 @@ public class FileDownloadServiceImpl extends DefaultBaseService implements IFile return fileDownloadDao.count(params); } + @Override - public void handle(HttpServletRequest httpServletRequest, boolean isOpen, String fileId) { + public void download(HttpServletRequest request, HttpServletResponse response, FilePO filePO) { + String fileId = filePO.getFileId(); + try ( + RandomAccessFile randomAccessFile = new RandomAccessFile(filePO.getFilePath(), "r"); + FileChannel fileChannel = randomAccessFile.getChannel(); + OutputStream outputStream = response.getOutputStream(); + WritableByteChannel writableByteChannel = Channels.newChannel(outputStream); + ) { + response.setHeader("Content-Length", filePO.getFileSize().toString()); + response.setContentType(StaticResourceRequestUtil.getContentType(filePO.getFileType())); + response.setHeader("Content-Disposition", "inline;fileName=" + URLEncoder.encode(filePO.getFileName(), "UTF-8")); + String rangeString = request.getHeader("Range"); + long contentLength = filePO.getFileSize(); + long startRange = 0; + long endRange = contentLength - 1; + if (!StringUtils.isBlank(rangeString)) { + response.setContentType("multipart/byteranges"); + String[] rangeArray = rangeString.substring(rangeString.indexOf("=") + 1).split("-"); + startRange = Long.parseLong(rangeArray[0]); + if (rangeArray.length > 1) { + endRange = Long.parseLong(rangeArray[1]); + } + response.setHeader("Content-Length", String.valueOf(endRange - startRange + 1)); + response.setHeader("Content-Range", String.format("bytes %d-%d/%d", startRange, endRange, contentLength)); + response.setHeader("Accept-Ranges", "bytes"); + response.setHeader("Etag", fileId); + response.setStatus(206); + randomAccessFile.seek(startRange); + } + LOG.debug("startRange: {}, endRange: {}", startRange, endRange); + long totalOutputLength = endRange - startRange + 1; + fileChannel.transferTo(startRange, totalOutputLength, writableByteChannel); + outputStream.flush(); + if (endRange >= contentLength - 1) { + handle(request, false, fileId); + } + } catch (Exception e) { + e.printStackTrace(); + if (e instanceof ClientAbortException) { + LOG.debug("客户端断开连接"); + } else { + throw new FileException("文件输出异常", e); + } + } + } + + @Override + public void downloadRedirect(HttpServletRequest request, HttpServletResponse response, boolean isOpen, FilePO filePO) { + if (filePO == null) { + throw new FileException("查询失败"); + } + File file = new File(filePO.getFilePath()); + if (!file.exists()) { + throw new FileException("文件不存在"); + } + String fileId = filePO.getFileId(); + try { + String code = FilesManager.getInstance().generateCode(fileId, filePO.getFileName()); + response.sendRedirect(String.format("%s/%s?file=%s&code=%s&open=%d", request.getContextPath(), filePO.getFileUrl(), fileId, code, isOpen ? 1 : 0)); + handle(request, isOpen, fileId); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void handle(HttpServletRequest httpServletRequest, boolean isOpen, String fileId) { String requestIp = RequestUtil.getRequestIp(httpServletRequest); if (!isOpen && !StringUtils.isBlank(requestIp)) { // 记录下载历史 @@ -88,5 +168,4 @@ public class FileDownloadServiceImpl extends DefaultBaseService implements IFile } } - } diff --git a/module-file/src/main/java/ink/wgink/module/file/service/impl/MinIoFileServiceImpl.java b/module-file/src/main/java/ink/wgink/module/file/service/impl/MinIoFileServiceImpl.java index 3af99cce..69b9a37b 100644 --- a/module-file/src/main/java/ink/wgink/module/file/service/impl/MinIoFileServiceImpl.java +++ b/module-file/src/main/java/ink/wgink/module/file/service/impl/MinIoFileServiceImpl.java @@ -224,7 +224,7 @@ public class MinIoFileServiceImpl extends DefaultBaseService implements IMinIoFi } else { inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(filePO.getFileUrl()).object(objectName).offset(0L).length(contentLength).build()); } - if (endRange == contentLength) { + if (endRange >= (contentLength - 1)) { isDownloadComplete = true; } byte[] readBuf = new byte[IFileService.INPUT_STREAM_SIZE]; diff --git a/module-file/src/main/java/ink/wgink/module/file/service/v2/IFileService.java b/module-file/src/main/java/ink/wgink/module/file/service/v2/IFileService.java index b1356776..f6c5565c 100644 --- a/module-file/src/main/java/ink/wgink/module/file/service/v2/IFileService.java +++ b/module-file/src/main/java/ink/wgink/module/file/service/v2/IFileService.java @@ -129,14 +129,24 @@ public interface IFileService { FilePO getPO(String fileId); /** - * 下载 + * 重定向下载 * * @param request * @param response * @param isOpen 是否打开 * @param fileId 文件ID */ - void download(HttpServletRequest request, HttpServletResponse response, boolean isOpen, String fileId); + void downloadRedirect(HttpServletRequest request, HttpServletResponse response, boolean isOpen, String fileId); + + /** + * 下载 + * + * @param request + * @param response + * @param fileId + */ + void download(HttpServletRequest request, HttpServletResponse response, String fileId); + /** * 文件列表 diff --git a/module-file/src/main/java/ink/wgink/module/file/service/v2/impl/FileServiceImpl.java b/module-file/src/main/java/ink/wgink/module/file/service/v2/impl/FileServiceImpl.java index 3db4ff8d..53cc74a5 100644 --- a/module-file/src/main/java/ink/wgink/module/file/service/v2/impl/FileServiceImpl.java +++ b/module-file/src/main/java/ink/wgink/module/file/service/v2/impl/FileServiceImpl.java @@ -9,7 +9,6 @@ import ink.wgink.exceptions.SearchException; import ink.wgink.module.file.components.FileComponent; import ink.wgink.module.file.dao.IFileDao; import ink.wgink.module.file.enums.UploadTypeEnum; -import ink.wgink.module.file.manager.FilesManager; import ink.wgink.module.file.pojo.dtos.FileInfoDTO; import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO; import ink.wgink.module.file.pojo.vos.v2.FileSaveVO; @@ -169,7 +168,7 @@ public class FileServiceImpl extends DefaultBaseService implements IFileService } @Override - public void download(HttpServletRequest request, HttpServletResponse response, boolean isOpen, String fileId) { + public void downloadRedirect(HttpServletRequest request, HttpServletResponse response, boolean isOpen, String fileId) { FilePO filePO = getPO(fileId); if (filePO == null) { throw new FileException("查询失败"); @@ -178,13 +177,20 @@ public class FileServiceImpl extends DefaultBaseService implements IFileService if (!file.exists()) { throw new FileException("文件不存在"); } - try { - String code = FilesManager.getInstance().generateCode(fileId, filePO.getFileName()); - response.sendRedirect(String.format("%s/%s?file=%s&code=%s&open=%d", request.getContextPath(), filePO.getFileUrl(), fileId, code, isOpen ? 1 : 0)); - fileDownloadService.handle(request, isOpen, fileId); - } catch (IOException e) { - throw new RuntimeException(e); + fileDownloadService.downloadRedirect(request, response, isOpen, filePO); + } + + @Override + public void download(HttpServletRequest request, HttpServletResponse response, String fileId) { + FilePO filePO = getPO(fileId); + if (filePO == null) { + throw new FileException("查询失败"); } + File file = new File(filePO.getFilePath()); + if (!file.exists()) { + throw new FileException("文件不存在"); + } + fileDownloadService.download(request, response, filePO); } @Override diff --git a/module-file/src/main/resources/templates/file-client/list.html b/module-file/src/main/resources/templates/file-client/list.html index 5fd2f124..5041f051 100644 --- a/module-file/src/main/resources/templates/file-client/list.html +++ b/module-file/src/main/resources/templates/file-client/list.html @@ -181,7 +181,7 @@ yes: function (index) { top.dialog.close(index); var layIndex; - top.restAjax.delete(top.restAjax.path('api/file-remote/remove/{ids}', [ids]), {}, null, function (code, data) { + top.restAjax.delete(top.restAjax.path('api/file-client/remove/{ids}', [ids]), {}, null, function (code, data) { top.dialog.msg(top.dataMessage.deleteSuccess, {time: 1000}); reloadTable(); }, function (code, data) { @@ -208,7 +208,7 @@ area: ['100%', '100%'], shadeClose: true, anim: 2, - content: top.restAjax.path('route/file-remote/save', []), + content: top.restAjax.path('route/file-client/save', []), end: function() { reloadTable(); } @@ -226,7 +226,7 @@ area: ['100%', '100%'], shadeClose: true, anim: 2, - content: top.restAjax.path('route/file-remote/update?fileRemoteId={fileRemoteId}', [checkDatas[0].fileRemoteId]), + content: top.restAjax.path('route/file-client/update?fileClientId={fileClientId}', [checkDatas[0].fileClientId]), end: function() { reloadTable(); } @@ -241,7 +241,7 @@ if(i > 1) { ids += '_'; } - ids += item['fileRemoteId']; + ids += item['fileClientId']; } removeData(ids); }