spring集成tus的简单总结

spring集成tus的简单总结

背景

项目需要一个可靠、稳定的页面大文件上传实现,故选择了tus。

一、tus

  • tus协议是一个基于http的断点续传的开放协议。
  • 它提供了多种功能,有很好的官方和非官方实现示例,并且支持多种语言。
  • 官方:https://tus.io/

核心协议

下面列出的为核心协议,其余可选的协议可点击此处

标头 简介
Upload-Offset 当前传输的偏移量
Upload-Length 文件总大小
Tus-Version 支持的协议版本列表
Tus-Resumable 当前使用的协议版本
Tus-Extension 服务器支持的扩展列表
Tus-Max-Size 允许的文件大小
X-HTTP-Method-Override http 方法覆盖
  1. 创建上传资源

    • 首次上传时创建上传资源

    请求头

    POST /files HTTP/1.1 Host: tus.example.org Content-Length: 0 Upload-Length: 100 Tus-Resumable: 1.0.0 Upload-Metadata: filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential

    响应

    HTTP/1.1 201 Created Location: https://tus.example.org/files/24e533e02ec3bc40c387f1a0e460e216 Tus-Resumable: 1.0.0
  2. 偏移量查询

    请求头

    • 检查由 HEAD 方法请求中断的上传的偏移量
    HEAD /files/24e533e02ec3bc40c387f1a0e460e216 HTTP/1.1 Host: tus.example.org Tus-Resumable: 1.0.0

    响应

    • 如果没有中断上传,则返回 404 响应
    HTTP/1.1 200 OK Upload-Offset: 70 Tus-Resumable: 1.0.0
  3. 继传

    请求头

    • 使用 PATCH 方法请求恢复上传
    PATCH /files/24e533e02ec3bc40c387f1a0e460e216 HTTP/1.1 Host: tus.example.org Content-Type: application/offset+octet-stream Content-Length: 30 Upload-Offset: 70 Tus-Resumable: 1.0.0

    响应

    HTTP/1.1 204 No Content Tus-Resumable: 1.0.0 Upload-Offset: 100
  4. 检查服务器配置信息

    请求头

    OPTIONS /files HTTP/1.1 Host: tus.example.org

    响应

    HTTP/1.1 204 No Content Tus-Resumable: 1.0.0 Tus-Version: 1.0.0,0.2.2,0.2.1 Tus-Max-Size: 1073741824 Tus-Extension: creation,expiration

二、库的选择以及使用

添加依赖项

<dependency> <groupId>me.desair.tus</groupId> <artifactId>tus-java-server</artifactId> <version>1.0.0-2.1</version> </dependency>

配置

配置项 简介
withUploadURI 用作上传端点的 uri
withMaxUploadSize 上传的最大字节数(默认Long.MAX_VALUE)
withStoragePath 存放上传信息的路径
withChunkedTransferDecoding 是否启用分块上传(默认为false)
withThreadLocalCache 是否启用ThreadLocal缓存(默认为false)
withUploadExpirationPeriod 过期时间(毫秒)
@Configuration @Slf4j public class TusConfig { @Value("${store.path}") private String tusDataPath; @Value("${upload.expiration.period}") private Long expirationPeriod; @PreDestroy public void exit() throws IOException { // cleanup any expired uploads and stale locks tusFileUploadService().cleanup(); } @Bean public TusFileUploadService tusFileUploadService() { return new TusFileUploadService() .withStoragePath(tusDataPath + "/tus") .withUploadURI("/__URL__") .withThreadLocalCache(true) //停止某个extension .disableTusExtension("creation") //添加自定义的extension实现 .addTusExtension(new MyCreationExtension()) .addTusExtension(new AddPathExtension(tusDataPath)) .withUploadExpirationPeriod(expirationPeriod * 1000 * 60 * 60 * 24); } }

控制器

@RestController @RequestMapping(value = "/__URL__") @Slf4j @Api(tags = "文件上传管理") @CrossOrigin(exposedHeaders = {"Location", "Upload-Offset", "Upload-Length"}) public class FileUploadController { @Value("${store.path}") private String storePath; @Resource private TusFileUploadService tusFileUploadService; @ApiOperation("文件上传") @RequestMapping(value = {"/**"}, method = {RequestMethod.POST, RequestMethod.PATCH, RequestMethod.HEAD, RequestMethod.DELETE, RequestMethod.OPTIONS, RequestMethod.GET}) public ResponseEntity<String> processUpload(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse) { log.info("processUpload------start"); try { tusFileUploadService.process(servletRequest, servletResponse); } catch (Exception e) { return ResponseEntity.badRequest(); } //access response header Location,Upload-Offset,Upload-length servletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Location,Upload-Offset,Upload-Length"); String uploadUrl = servletRequest.getRequestURI(); log.info("uploadUrl:{}", uploadUrl); UploadInfo info; try { info = this.tusFileUploadService.getUploadInfo(uploadUrl); } catch (IOException | TusException e) { log.error("get upload info", e); return ResponseEntity.ok(); } Path tusDataPath = Paths.get(storePath).resolve("tus"); //上传完成后,将上传完成的data文件重命名为原文件 if (info != null && !info.isUploadInProgress()) { Path parPath = tusDataPath.resolve("uploads").resolve(info.getId().getOriginalObject().toString()); Path filePath = parPath.resolve(info.getFileName()); try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(uploadUrl, null)) { FileUtils.copyToFile(uploadedBytes, filePath.toFile()); } catch (Exception e) { try { this.tusFileUploadService.deleteUpload(uploadUrl); } catch (IOException | TusException ee) { log.error("delete upload", ee); return ResponseEntity.badRequest().body("删除错误的文件失败"); } return ResponseEntity.badRequest().body("重命名文件失败"); } } return ResponseEntity.ok(); } }

定时任务

tusFileUploadService.cleanup()通过该方法可以轻松清理停止上传的数据,以及超过config中设置的过期时间的数据。
创建定时任务以定期删除过期数据以保护服务器空间。

@Service @Slf4j public class FileOptSchedule { @Value("${store.path}") private String storePath; @Resource private TusFileUploadService tusFileUploadService; @Scheduled(fixedDelayString = "PT24H") public void cleanup() { Path path = new File(storePath).toPath(); Path locksDir = path.resolve("locks"); if (Files.exists(locksDir)) { try { tusFileUploadService.cleanup(); } catch (IOException e) { log.error("清理文件上传目录:", e); } } } }

自定义extension

  1. 将文件地址存储在uploadInfo中

    @Slf4j public class AddPathExtension extends AbstractTusExtension { private final Path tusDataPath; public AddPathExtension(String path) { this.tusDataPath = Paths.get(path); } @Override public String getName() { return "addPath"; } @Override public Collection<HttpMethod> getMinimalSupportedHttpMethods() { return Collections.singletonList(HttpMethod.PATCH); } @Override protected void initValidators(List<RequestValidator> requestValidators) { } @Override protected void initRequestHandlers(List<RequestHandler> requestHandlers) { requestHandlers.add(new AbstractExtensionRequestHandler() { @Override protected void appendExtensions(StringBuilder extensionBuilder) { addExtension(extensionBuilder, "addPath"); } }); requestHandlers.add(new AbstractRequestHandler() { @Override public boolean supports(HttpMethod method) { return HttpMethod.PATCH.equals(method); } @Override public void process(HttpMethod method, TusServletRequest servletRequest, TusServletResponse servletResponse, UploadStorageService uploadStorageService, String ownerKey) throws IOException, TusException { log.info("AddPathRequestHandlers======start"); String uploadUrl = servletRequest.getRequestURI(); UploadInfo info = uploadStorageService.getUploadInfo(uploadUrl, ownerKey); if (info == null) { throw new UploadNotFoundException("Upload " + uploadUrl + " is not found"); } else if (!info.isUploadInProgress()) { String encodedMetadata = info.getEncodedMetadata(); Path parPath = tusDataPath.resolve("tus").resolve("uploads").resolve(info.getId().getOriginalObject().toString()); Path filePath = parPath.resolve(info.getFileName()); encodedMetadata = encodedMetadata + ",filePath " + new String(Base64.encodeBase64(filePath.toString().getBytes(StandardCharsets.UTF_8)), Charsets.UTF_8); info.setEncodedMetadata(encodedMetadata); uploadStorageService.update(info); } } }); } }
  2. 通过网关转发的url和后端获取的url不一致,导致无法正常上传

    解决方法是重写CreationExtension,扩展一下CreationExtension的initRequestHandlers的CreationPostRequestHandler即可

    @Override public void process(HttpMethod method, TusServletRequest servletRequest, TusServletResponse servletResponse, UploadStorageService uploadStorageService, String ownerKey) throws IOException { UploadInfo info = buildUploadInfo(servletRequest); info = uploadStorageService.create(info, ownerKey); //We've already validated that the current request URL matches our upload URL so we can safely use it. String uploadURI = servletRequest.getRequestURI(); //It's important to return relative UPLOAD URLs in the Location header in order to support HTTPS proxies //that sit in front of the web app String url = "这里是你自己的服务地址" + uploadURI + (StringUtils.endsWith(uploadURI, "/") ? "" : "/") + info.getId(); servletResponse.setHeader(HttpHeader.LOCATION, url); servletResponse.setStatus(HttpServletResponse.SC_CREATED); log.info("Created upload with ID {} at {} for ip address {} with location {}", info.getId(), info.getCreationTimestamp(), info.getCreatorIpAddresses(), url); }

其他的一些方法

{ //获取上传的文件详情 UploadInfo uploadInfo = tusFileUploadService.getUploadInfo(uploadUrl); //删除指定文件 tusFileUploadService.deleteUpload(uploadUrl); }

__EOF__

本文作者CrossAutomaton
本文链接https://www.cnblogs.com/CrossAutomaton/p/18304813.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   CrossAutomaton  阅读(290)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示