增加文件V2直接下载功能

This commit is contained in:
wanggeng 2022-08-08 16:42:38 +08:00
parent 40d103963b
commit 596fe32678
10 changed files with 171 additions and 83 deletions

View File

@ -14,6 +14,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* @ClassName: FileAppCenterController * @ClassName: FileAppCenterController
* @Description: APP文件远程 * @Description: APP文件远程
@ -97,7 +100,17 @@ public class FileCenterAppController extends DefaultBaseController {
return new SuccessResultData<>(fileCenterService.uploadSingleByUserId(creator, audio, UploadTypeEnum.AUDIO)); 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 * 校验Key与Secret

View File

@ -35,7 +35,7 @@ public class FileRouteController {
return modelAndView; return modelAndView;
} }
@ApiOperation(value = "文件下载", notes = "文件下载接口") @ApiOperation(value = "文件重定向下载(浏览器)", notes = "文件重定向下载(适用于浏览器访问图片)接口")
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(name = "isOpen", value = "是否打开,true和false", paramType = "path"), @ApiImplicitParam(name = "isOpen", value = "是否打开,true和false", paramType = "path"),
@ApiImplicitParam(name = "fileId", value = "文件ID", paramType = "path") @ApiImplicitParam(name = "fileId", value = "文件ID", paramType = "path")
@ -46,7 +46,19 @@ public class FileRouteController {
HttpServletResponse response, HttpServletResponse response,
@PathVariable("isOpen") Boolean isOpen, @PathVariable("isOpen") Boolean isOpen,
@PathVariable("fileId") String fileId) { @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);
} }
} }

View File

@ -4,6 +4,9 @@ import ink.wgink.module.file.enums.UploadTypeEnum;
import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO; import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* @ClassName: IFileCenterService * @ClassName: IFileCenterService
* @Description: 文件中心 * @Description: 文件中心
@ -23,5 +26,13 @@ public interface IFileCenterService {
*/ */
FileUploadSuccessDTO uploadSingleByUserId(String userId, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum); FileUploadSuccessDTO uploadSingleByUserId(String userId, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum);
/**
* 文件下载
*
* @param request
* @param response
* @param fileId
*/
void download(HttpServletRequest request, HttpServletResponse response, String fileId);
} }

View File

@ -1,18 +1,12 @@
package ink.wgink.module.file.service.filecenter.impl; package ink.wgink.module.file.service.filecenter.impl;
import ink.wgink.common.base.DefaultBaseService; import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.FileException;
import ink.wgink.exceptions.SearchException; import ink.wgink.exceptions.SearchException;
import ink.wgink.module.file.enums.UploadTypeEnum; import ink.wgink.module.file.enums.UploadTypeEnum;
import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO; import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO;
import ink.wgink.module.file.pojo.vos.v2.FileSaveVO; import ink.wgink.module.file.pojo.vos.v2.FileSaveVO;
import ink.wgink.module.file.service.filecenter.IFileCenterService; 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.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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -21,12 +15,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; 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 * @ClassName: FileCenterServiceImpl
@ -40,8 +28,6 @@ public class FileCenterServiceImpl extends DefaultBaseService implements IFileCe
@Autowired @Autowired
private IFileService fileService; private IFileService fileService;
@Autowired
private IFileDownloadService fileDownloadService;
@Override @Override
public FileUploadSuccessDTO uploadSingleByUserId(String userId, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum) { public FileUploadSuccessDTO uploadSingleByUserId(String userId, MultipartFile uploadFile, UploadTypeEnum uploadTypeEnum) {
@ -49,50 +35,9 @@ public class FileCenterServiceImpl extends DefaultBaseService implements IFileCe
return fileService.saveFile(fileSaveVO); return fileService.saveFile(fileSaveVO);
} }
private void download(HttpServletRequest request, HttpServletResponse response, String fileId) { @Override
FilePO filePO = fileService.getPO(fileId); public void download(HttpServletRequest request, HttpServletResponse response, String fileId) {
try ( fileService.download(request, response, fileId);
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);
}
}
} }
/** /**

View File

@ -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.dtos.filedownload.FileDownloadDTO;
import ink.wgink.module.file.pojo.vos.filedownload.FileDownloadVO; import ink.wgink.module.file.pojo.vos.filedownload.FileDownloadVO;
import ink.wgink.pojo.ListPage; import ink.wgink.pojo.ListPage;
import ink.wgink.pojo.pos.FilePO;
import ink.wgink.pojo.result.SuccessResultList; import ink.wgink.pojo.result.SuccessResultList;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -68,12 +70,22 @@ public interface IFileDownloadService {
Integer countByFileId(String fileId); Integer countByFileId(String fileId);
/** /**
* 下载 * 文件下载
* *
* @param httpServletRequest * @param request
* @param isOpen 是否打开 * @param response
* @param fileId * @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);
} }

View File

@ -3,22 +3,36 @@ package ink.wgink.module.file.service.filedownload.impl;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import ink.wgink.common.base.DefaultBaseService; import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.FileException;
import ink.wgink.module.file.dao.filedownload.IFileDownloadDao; 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.dtos.filedownload.FileDownloadDTO;
import ink.wgink.module.file.pojo.vos.filedownload.FileDownloadVO; import ink.wgink.module.file.pojo.vos.filedownload.FileDownloadVO;
import ink.wgink.module.file.service.filedownload.IFileDownloadService; import ink.wgink.module.file.service.filedownload.IFileDownloadService;
import ink.wgink.pojo.ListPage; import ink.wgink.pojo.ListPage;
import ink.wgink.pojo.pos.FilePO;
import ink.wgink.pojo.result.SuccessResultList; import ink.wgink.pojo.result.SuccessResultList;
import ink.wgink.util.UUIDUtil; import ink.wgink.util.UUIDUtil;
import ink.wgink.util.date.DateUtil; import ink.wgink.util.date.DateUtil;
import ink.wgink.util.map.HashMapUtil; import ink.wgink.util.map.HashMapUtil;
import ink.wgink.util.request.RequestUtil; import ink.wgink.util.request.RequestUtil;
import ink.wgink.util.request.StaticResourceRequestUtil;
import ink.wgink.util.thread.CachedThreadPoolUtil; import ink.wgink.util.thread.CachedThreadPoolUtil;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest; 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.List;
import java.util.Map; import java.util.Map;
@ -77,8 +91,74 @@ public class FileDownloadServiceImpl extends DefaultBaseService implements IFile
return fileDownloadDao.count(params); return fileDownloadDao.count(params);
} }
@Override @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); String requestIp = RequestUtil.getRequestIp(httpServletRequest);
if (!isOpen && !StringUtils.isBlank(requestIp)) { if (!isOpen && !StringUtils.isBlank(requestIp)) {
// 记录下载历史 // 记录下载历史
@ -88,5 +168,4 @@ public class FileDownloadServiceImpl extends DefaultBaseService implements IFile
} }
} }
} }

View File

@ -224,7 +224,7 @@ public class MinIoFileServiceImpl extends DefaultBaseService implements IMinIoFi
} else { } else {
inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(filePO.getFileUrl()).object(objectName).offset(0L).length(contentLength).build()); inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(filePO.getFileUrl()).object(objectName).offset(0L).length(contentLength).build());
} }
if (endRange == contentLength) { if (endRange >= (contentLength - 1)) {
isDownloadComplete = true; isDownloadComplete = true;
} }
byte[] readBuf = new byte[IFileService.INPUT_STREAM_SIZE]; byte[] readBuf = new byte[IFileService.INPUT_STREAM_SIZE];

View File

@ -129,14 +129,24 @@ public interface IFileService {
FilePO getPO(String fileId); FilePO getPO(String fileId);
/** /**
* 下载 * 重定向下载
* *
* @param request * @param request
* @param response * @param response
* @param isOpen 是否打开 * @param isOpen 是否打开
* @param fileId 文件ID * @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);
/** /**
* 文件列表 * 文件列表

View File

@ -9,7 +9,6 @@ import ink.wgink.exceptions.SearchException;
import ink.wgink.module.file.components.FileComponent; import ink.wgink.module.file.components.FileComponent;
import ink.wgink.module.file.dao.IFileDao; import ink.wgink.module.file.dao.IFileDao;
import ink.wgink.module.file.enums.UploadTypeEnum; 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.FileInfoDTO;
import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO; import ink.wgink.module.file.pojo.dtos.v2.FileUploadSuccessDTO;
import ink.wgink.module.file.pojo.vos.v2.FileSaveVO; import ink.wgink.module.file.pojo.vos.v2.FileSaveVO;
@ -169,7 +168,7 @@ public class FileServiceImpl extends DefaultBaseService implements IFileService
} }
@Override @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); FilePO filePO = getPO(fileId);
if (filePO == null) { if (filePO == null) {
throw new FileException("查询失败"); throw new FileException("查询失败");
@ -178,13 +177,20 @@ public class FileServiceImpl extends DefaultBaseService implements IFileService
if (!file.exists()) { if (!file.exists()) {
throw new FileException("文件不存在"); throw new FileException("文件不存在");
} }
try { fileDownloadService.downloadRedirect(request, response, isOpen, filePO);
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); @Override
} catch (IOException e) { public void download(HttpServletRequest request, HttpServletResponse response, String fileId) {
throw new RuntimeException(e); 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 @Override

View File

@ -181,7 +181,7 @@
yes: function (index) { yes: function (index) {
top.dialog.close(index); top.dialog.close(index);
var layIndex; 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}); top.dialog.msg(top.dataMessage.deleteSuccess, {time: 1000});
reloadTable(); reloadTable();
}, function (code, data) { }, function (code, data) {
@ -208,7 +208,7 @@
area: ['100%', '100%'], area: ['100%', '100%'],
shadeClose: true, shadeClose: true,
anim: 2, anim: 2,
content: top.restAjax.path('route/file-remote/save', []), content: top.restAjax.path('route/file-client/save', []),
end: function() { end: function() {
reloadTable(); reloadTable();
} }
@ -226,7 +226,7 @@
area: ['100%', '100%'], area: ['100%', '100%'],
shadeClose: true, shadeClose: true,
anim: 2, 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() { end: function() {
reloadTable(); reloadTable();
} }
@ -241,7 +241,7 @@
if(i > 1) { if(i > 1) {
ids += '_'; ids += '_';
} }
ids += item['fileRemoteId']; ids += item['fileClientId'];
} }
removeData(ids); removeData(ids);
} }