[api] tus 수정

This commit is contained in:
2026-05-27 15:18:03 +09:00
parent 85d593fb9d
commit cbea6bc2af
40 changed files with 1016 additions and 770 deletions
@@ -6,6 +6,7 @@ import com.alist.api.common.utils.SecurityUtil;
import com.alist.api.modules.tusFile.dto.*;
import com.alist.api.modules.tusFile.form.*;
import com.alist.api.modules.tusFile.service.TusFileService;
import com.alist.api.modules.tusFile.vo.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
@@ -43,29 +44,29 @@ public class TusFileController {
, description = "업로드 대상 파일 정보를 DB에 등록하고 fileUuid/uploadToken을 발급합니다."
)
@PostMapping("/upload/init")
public ResponseEntity<ApiResponse<FileUploadDto>> tusFileUploadInit(
@Valid @RequestBody FileUploadForm fileUploadForm
public ResponseEntity<ApiResponse<TusFileUploadInitVo>> tusFileUploadInit(
@Valid @RequestBody TusFileUploadInitForm tusFileUploadInitForm
) {
FileUploadDto fileUploadDto = fileUploadForm.toFileUploadDto();
TusFileUploadInitDto tusFileUploadInitDto = tusFileUploadInitForm.toDto();
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
if (userTokenIdx == null) {
return ApiResponse.entity(new FileUploadDto(), ApiResponseCode.CODE_401);
return ApiResponse.entity(new TusFileUploadInitVo(), ApiResponseCode.CODE_401);
}
Integer userIdx = tusFileService.selectUserIdxByUserTokenIdx(userTokenIdx);
if (userIdx == null) {
return ApiResponse.entity(new FileUploadDto(), ApiResponseCode.CODE_401);
return ApiResponse.entity(new TusFileUploadInitVo(), ApiResponseCode.CODE_401);
}
fileUploadDto.setUserIdx(userIdx);
fileUploadDto.setUserTokenIdx(userTokenIdx);
tusFileUploadInitDto.setUserIdx(userIdx);
tusFileUploadInitDto.setUserTokenIdx(userTokenIdx);
FileUploadDto fileUploadResult = tusFileService.insertFileInit(fileUploadDto);
TusFileUploadInitVo tusFileUploadInitVo = tusFileService.insertTusFileUploadInit(tusFileUploadInitDto);
return ApiResponse.entity(fileUploadResult, ApiResponseCode.CODE_200);
return ApiResponse.entity(tusFileUploadInitVo, ApiResponseCode.CODE_200);
}
@Operation(
@@ -86,17 +87,17 @@ public class TusFileController {
return ApiResponse.entity("", ApiResponseCode.CODE_403);
}
UploadTokenDto uploadTokenDto = new UploadTokenDto();
uploadTokenDto.setUploadToken(uploadToken.substring(7).trim());
uploadTokenDto.setFileUuid(fileUuid.trim());
uploadTokenDto.setUploadMetadataRaw(uploadMetadata);
uploadTokenDto.setUploadLengthRaw(uploadLength);
TusFileUploadAuthDto tusFileUploadAuthDto = new TusFileUploadAuthDto();
tusFileUploadAuthDto.setUploadToken(uploadToken.substring(7).trim());
tusFileUploadAuthDto.setFileUuid(fileUuid.trim());
tusFileUploadAuthDto.setUploadMetadataRaw(uploadMetadata);
tusFileUploadAuthDto.setUploadLengthRaw(uploadLength);
UploadTokenDto uploadTokenCheck = tusFileService.isUploadTokenCheck(uploadTokenDto);
TusFileUploadAuthVo tusFileUploadAuthVo = tusFileService.selectTusFileUploadAuth(tusFileUploadAuthDto);
if (uploadTokenCheck.getResultCode() == 401) {
if (tusFileUploadAuthVo.getResultCode() == 401) {
return ApiResponse.entity("", ApiResponseCode.CODE_401);
} else if (uploadTokenCheck.getResultCode() == 403) {
} else if (tusFileUploadAuthVo.getResultCode() == 403) {
return ApiResponse.entity("", ApiResponseCode.CODE_403);
}
@@ -108,14 +109,16 @@ public class TusFileController {
, description = "fileUuid 기준으로 Redis/DB 업로드 상태(진행률, 상태값)를 조회합니다."
)
@PostMapping("/upload/status")
public ResponseEntity<ApiResponse<UploadStatusDto>> tusFileUploadStatus(
@Valid @RequestBody UploadStatusForm uploadStatusForm
public ResponseEntity<ApiResponse<TusFileUploadStatusVo>> tusFileUploadStatus(
@Valid @RequestBody TusFileUploadStatusForm tusFileUploadStatusForm
) {
UploadStatusDto status = tusFileService.getUploadStatus(uploadStatusForm.toUploadStatusDto());
if (status == null) {
return ApiResponse.entity(new UploadStatusDto(), ApiResponseCode.CODE_2003);
TusFileUploadStatusVo tusFileUploadStatusVo = tusFileService.loadTusFileUploadStatus(tusFileUploadStatusForm.toDto());
if (tusFileUploadStatusVo == null) {
return ApiResponse.entity(new TusFileUploadStatusVo(), ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(status, ApiResponseCode.CODE_200);
return ApiResponse.entity(tusFileUploadStatusVo, ApiResponseCode.CODE_200);
}
@Operation(
@@ -123,11 +126,13 @@ public class TusFileController {
, description = "tusd에서 전달되는 pre-create/post-receive/post-finish/post-terminate 이벤트를 반영합니다."
)
@PostMapping("/hook")
public ResponseEntity<ApiResponse<String>> tusFileHook(@RequestBody TusHookForm tusHookForm) {
boolean accepted = tusFileService.updateFileUploadStatus(tusHookForm.toTusHookDto());
public ResponseEntity<ApiResponse<String>> tusFileHook(@RequestBody TusFileHookForm tusFileHookForm) {
TusFileHookDto tusFileHookDto = tusFileHookForm.toDto();
boolean accepted = tusFileService.updateTusFileHook(tusFileHookDto);
if (accepted) {
tusFileService.saveUploadStatusRedisTusHook(tusHookForm.toTusHookDto());
tusFileService.saveTusFileHookStatusRedis(tusFileHookDto);
}
return ApiResponse.entity("", ApiResponseCode.CODE_200);
@@ -139,7 +144,7 @@ public class TusFileController {
)
@PostMapping("/upload/cancel")
public ResponseEntity<ApiResponse<String>> tusFileUploadCancel(
@Valid @RequestBody UploadCancelForm uploadCancelForm
@Valid @RequestBody TusFileUploadCancelForm tusFileUploadCancelForm
) {
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
@@ -147,14 +152,12 @@ public class TusFileController {
return ApiResponse.entity("", ApiResponseCode.CODE_401);
}
UploadCancelDto uploadCancelDto = new UploadCancelDto();
TusFileUploadCancelDto tusFileUploadCancelDto = tusFileUploadCancelForm.toDto();
tusFileUploadCancelDto.setUserTokenIdx(userTokenIdx);
uploadCancelDto.setFileUuid(uploadCancelForm.getFileUuid().trim());
uploadCancelDto.setUserTokenIdx(userTokenIdx);
TusFileUploadCancelVo tusFileUploadCancelVo = tusFileService.updateTusFileUploadCancel(tusFileUploadCancelDto);
boolean canceled = tusFileService.updateUploadCancel(uploadCancelDto);
if (!canceled) {
if (!tusFileUploadCancelVo.isCanceled()) {
return ApiResponse.entity("", ApiResponseCode.CODE_2005, "업로드 취소");
}
@@ -163,62 +166,63 @@ public class TusFileController {
@Operation(
summary = "파일 목록 조회"
, description = "FILE_MASTER_IDX 기준으로 완료된 파일 목록을 조회합니다.")
, description = "FILE_MASTER_IDX 기준으로 완료된 파일 목록을 조회합니다."
)
@GetMapping("/list/{fileMasterIdx}")
public ResponseEntity<ApiResponse<FileDownloadListDto>> tusFileList(
public ResponseEntity<ApiResponse<TusFileListVo>> tusFileList(
@PathVariable Long fileMasterIdx
) {
TusFileListDto tusFileListDto = new TusFileListDto();
tusFileListDto.setFileMasterIdx(fileMasterIdx);
FileDownloadDto fileDownloadDto = new FileDownloadDto();
fileDownloadDto.setFileMasterIdx(fileMasterIdx);
TusFileListVo tusFileListVo = tusFileService.selectTusFileList(tusFileListDto);
FileDownloadListDto fileDownloadList = tusFileService.selectFileDownloadListByFileMasterIdx(fileDownloadDto);
if (fileDownloadList.getResultCode() == 401) {
return ApiResponse.entity(fileDownloadList, ApiResponseCode.CODE_401);
if (tusFileListVo.getResultCode() == 401) {
return ApiResponse.entity(tusFileListVo, ApiResponseCode.CODE_401);
}
return ApiResponse.entity(fileDownloadList, ApiResponseCode.CODE_2001, "파일");
return ApiResponse.entity(tusFileListVo, ApiResponseCode.CODE_2001, "파일");
}
@Operation(
summary = "파일 보기"
, description = "권한 확인 후 Nginx X-Accel-Redirect로 파일을 inline 조회합니다.")
, description = "권한 확인 후 Nginx X-Accel-Redirect로 파일을 inline 조회합니다."
)
@GetMapping("/view/{fileUuid}")
public ResponseEntity<Resource> tusFileView(
@PathVariable String fileUuid
, HttpServletRequest request
) throws IOException {
FileDownloadDto fileDownloadDto = new FileDownloadDto();
fileDownloadDto.setFileUuid(fileUuid);
fileDownloadDto.setEventType("VIEW");
fileDownloadDto.setClientIp(request.getRemoteAddr());
fileDownloadDto.setUserAgent(request.getHeader("User-Agent"));
fileDownloadDto.setReferer(request.getHeader("Referer"));
TusFileViewOrDownloadDto tusFileViewOrDownloadDto = new TusFileViewOrDownloadDto();
tusFileViewOrDownloadDto.setFileUuid(fileUuid);
tusFileViewOrDownloadDto.setEventType("VIEW");
tusFileViewOrDownloadDto.setClientIp(request.getRemoteAddr());
tusFileViewOrDownloadDto.setUserAgent(request.getHeader("User-Agent"));
tusFileViewOrDownloadDto.setReferer(request.getHeader("Referer"));
FileDownloadDto target = tusFileService.selectFileViewOrDownload(fileDownloadDto);
TusFileViewOrDownloadVo tusFileViewOrDownloadVo = tusFileService.selectTusFileViewOrDownload(tusFileViewOrDownloadDto);
if (target == null) {
if (tusFileViewOrDownloadVo == null) {
return ResponseEntity.notFound().build();
}
Path filePath = tusFileService.resolveStoredFilePath(target.getSavePath());
Path filePath = tusFileService.resolveStoredFilePath(tusFileViewOrDownloadVo.getSavePath());
if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
return ResponseEntity.notFound().build();
}
Resource resource = new InputStreamResource(Files.newInputStream(filePath));
String contentType = target.getContentType() == null
String contentType = tusFileViewOrDownloadVo.getContentType() == null
? "application/octet-stream"
: target.getContentType();
: tusFileViewOrDownloadVo.getContentType();
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.contentLength(Files.size(filePath))
.header(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.inline()
.filename(target.getOriginName(), StandardCharsets.UTF_8)
.filename(tusFileViewOrDownloadVo.getOriginName(), StandardCharsets.UTF_8)
.build()
.toString())
.body(resource);
@@ -226,58 +230,59 @@ public class TusFileController {
@Operation(
summary = "파일 다운로드"
, description = "권한 확인 후 파일 다운로드 로그를 남기고 redirect 또는 Nginx X-Accel-Redirect로 파일 다운로드 합니다.")
, description = "권한 확인 후 파일 다운로드 로그를 남기고 redirect 또는 Nginx X-Accel-Redirect로 파일 다운로드 합니다."
)
@GetMapping("/download/{fileUuid}")
public ResponseEntity<Resource> tusFileDownload(
@PathVariable String fileUuid
, HttpServletRequest request
) throws IOException {
FileDownloadDto fileDownloadDto = new FileDownloadDto();
fileDownloadDto.setFileUuid(fileUuid);
fileDownloadDto.setEventType("DOWNLOAD");
fileDownloadDto.setClientIp(request.getRemoteAddr());
fileDownloadDto.setUserAgent(request.getHeader("User-Agent"));
fileDownloadDto.setReferer(request.getHeader("Referer"));
TusFileViewOrDownloadDto tusFileViewOrDownloadDto = new TusFileViewOrDownloadDto();
tusFileViewOrDownloadDto.setFileUuid(fileUuid);
tusFileViewOrDownloadDto.setEventType("DOWNLOAD");
tusFileViewOrDownloadDto.setClientIp(request.getRemoteAddr());
tusFileViewOrDownloadDto.setUserAgent(request.getHeader("User-Agent"));
tusFileViewOrDownloadDto.setReferer(request.getHeader("Referer"));
FileDownloadDto target = tusFileService.selectFileViewOrDownload(fileDownloadDto);
TusFileViewOrDownloadVo tusFileViewOrDownloadVo = tusFileService.selectTusFileViewOrDownload(tusFileViewOrDownloadDto);
if (target == null) {
if (tusFileViewOrDownloadVo == null) {
return ResponseEntity.notFound().build();
}
Path filePath = tusFileService.resolveStoredFilePath(target.getSavePath());
Path filePath = tusFileService.resolveStoredFilePath(tusFileViewOrDownloadVo.getSavePath());
if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
return ResponseEntity.notFound().build();
}
Resource resource = new InputStreamResource(Files.newInputStream(filePath));
String contentType = target.getContentType() == null
String contentType = tusFileViewOrDownloadVo.getContentType() == null
? "application/octet-stream"
: target.getContentType();
: tusFileViewOrDownloadVo.getContentType();
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.contentLength(Files.size(filePath))
.header(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.attachment()
.filename(target.getOriginName(), StandardCharsets.UTF_8)
.filename(tusFileViewOrDownloadVo.getOriginName(), StandardCharsets.UTF_8)
.build()
.toString())
.body(resource);
}
@Operation(
summary = "파일 삭제 처리",
description = "fileUuid 기준으로 완료된 파일(STATUS = 3)을 논리 삭제 처리합니다. 실제 파일은 삭제하지 않고 DEL_YN = 'Y'만 반영합니다."
summary = "파일 삭제 처리"
, description = "fileUuid 기준으로 완료된 파일(STATUS = 3)을 논리 삭제 처리합니다. 실제 파일은 삭제하지 않고 DEL_YN = 'Y'만 반영합니다."
)
@PostMapping("/delete")
public ResponseEntity<ApiResponse<String>> tusFileDelete(
@Valid @RequestBody FileDeleteForm fileDeleteForm
@Valid @RequestBody TusFileDeleteForm tusFileDeleteForm
) {
boolean deleted = tusFileService.updateFileDelete(fileDeleteForm.toFileDeleteDto());
TusFileDeleteVo tusFileDeleteVo = tusFileService.updateTusFileDelete(tusFileDeleteForm.toDto());
if (!deleted) {
if (!tusFileDeleteVo.isDeleted()) {
return ApiResponse.entity("", ApiResponseCode.CODE_2005, "파일 삭제");
}
@@ -1,41 +0,0 @@
package com.alist.api.modules.tusFile.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "파일 다운로드 처리 DTO")
@Getter
@Setter
public class FileDownloadDto {
@Schema(description = "파일 상세 PK", example = "1")
private Long fileDetailIdx;
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "원본 파일명", example = "sample.png")
private String originName;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long sizeBytes;
private String savePath;
private Integer userIdx;
private Integer userTokenIdx;
private String eventType;
private String clientIp;
private String userAgent;
private String referer;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -0,0 +1,16 @@
package com.alist.api.modules.tusFile.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 파일 삭제 DTO")
@Getter
@Setter
public class TusFileDeleteDto {
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(hidden = true)
private Integer userTokenIdx;
}
@@ -5,7 +5,7 @@ import lombok.Setter;
@Getter
@Setter
public class TusHookDto {
public class TusFileHookDto {
private String type;
private String uploadId;
private Long size;
@@ -0,0 +1,16 @@
package com.alist.api.modules.tusFile.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 파일 목록 조회 DTO")
@Getter
@Setter
public class TusFileListDto {
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(hidden = true)
private Integer userTokenIdx;
}
@@ -5,7 +5,7 @@ import lombok.Setter;
@Getter
@Setter
public class FileMoveTaskDto {
public class TusFileMoveTaskDto {
private Long fileDetailIdx;
private Long fileMasterIdx;
private String fileUuid;
@@ -1,6 +1,5 @@
package com.alist.api.modules.tusFile.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -8,7 +7,7 @@ import lombok.Setter;
@Schema(description = "업로드 인증 토큰 정보")
@Getter
@Setter
public class UploadTokenDto {
public class TusFileUploadAuthDto {
private Integer userTokenIdx;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
@@ -28,8 +27,4 @@ public class UploadTokenDto {
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -5,7 +5,10 @@ import lombok.Setter;
@Getter
@Setter
public class FileDeleteDto {
public class TusFileUploadCancelDto {
private Integer userTokenIdx;
private String fileUuid;
private Long uploadedBytes;
private Long sizeBytes;
}
@@ -10,7 +10,7 @@ import java.util.List;
@Schema(description = "TUS 파일 업로드 초기화 응답")
@Getter
@Setter
public class FileUploadDto {
public class TusFileUploadInitDto {
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@@ -42,5 +42,5 @@ public class FileUploadDto {
private Integer totalCount;
@Schema(description = "업로드 파일 항목 목록")
private List<FileUploadItemDto> itemList;
private List<TusFileUploadInitItemDto> itemList;
}
@@ -8,7 +8,7 @@ import lombok.Setter;
@Schema(description = "TUS 업로드 파일 항목 응답")
@Getter
@Setter
public class FileUploadItemDto {
public class TusFileUploadInitItemDto {
@Schema(description = "파일 순번", example = "1")
private Integer fileSeq;
@@ -5,7 +5,7 @@ import lombok.Setter;
@Getter
@Setter
public class FileUploadMetaDto {
public class TusFileUploadMetaDto {
private String fileUuid;
private String originName;
private Long sizeBytes;
@@ -5,10 +5,6 @@ import lombok.Setter;
@Getter
@Setter
public class UploadCancelDto {
private Integer userTokenIdx;
public class TusFileUploadStatusDto {
private String fileUuid;
private Long uploadedBytes;
private Long sizeBytes;
}
@@ -0,0 +1,28 @@
package com.alist.api.modules.tusFile.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 파일 보기/다운로드 조회 DTO")
@Getter
@Setter
public class TusFileViewOrDownloadDto {
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(hidden = true)
private Integer userTokenIdx;
@Schema(hidden = true)
private String eventType;
@Schema(hidden = true)
private String clientIp;
@Schema(hidden = true)
private String userAgent;
@Schema(hidden = true)
private String referer;
}
@@ -1,28 +0,0 @@
package com.alist.api.modules.tusFile.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 상태 조회 응답")
@Getter
@Setter
public class UploadStatusDto {
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "업로드 상태. PENDING, UPLOADING, DONE, FAILED", example = "UPLOADING")
private String status; // PENDING, UPLOADING, DONE, FAILED
@Schema(description = "업로드 완료 byte", example = "51200")
private Long uploadedBytes;
@Schema(description = "전체 파일 크기 byte", example = "102400")
private Long totalBytes;
@Schema(description = "업로드 진행률", example = "50")
private Integer percent;
@Schema(description = "상태 갱신 시각", example = "2026-05-19T10:30:00")
private String updatedAt;
}
@@ -1,26 +0,0 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.FileDeleteDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "파일 삭제 요청 폼")
@Getter
@Setter
public class FileDeleteForm {
@Schema(
description = "파일 UUID",
example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
public FileDeleteDto toFileDeleteDto() {
FileDeleteDto fileDeleteDto = new FileDeleteDto();
fileDeleteDto.setFileUuid(fileUuid == null ? null : fileUuid.trim());
return fileDeleteDto;
}
}
@@ -1,62 +0,0 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.FileUploadDto;
import com.alist.api.modules.tusFile.dto.FileUploadItemDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "TUS 파일 업로드 초기화 요청 폼")
@Getter
@Setter
public class FileUploadForm {
@Schema(
description = "파일 카테고리",
example = "BOARD",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 카테고리는 필수입니다.")
private String fileCategory;
@Schema(description = "업로드 폴더 경로. 미전달 시 / 로 처리됩니다.", example = "/board")
private String folderPath;
@Schema(
description = "업로드 파일 목록",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotEmpty(message = "업로드 파일 목록은 필수입니다.")
@Valid
private List<FileUploadItemForm> itemList;
public FileUploadDto toFileUploadDto() {
FileUploadDto fileUploadDto = new FileUploadDto();
fileUploadDto.setFileCategory(this.fileCategory.trim());
fileUploadDto.setFolderPath((this.folderPath == null || this.folderPath.isBlank()) ? "/" : this.folderPath.trim());
List<FileUploadItemDto> fileUploadItemDtoList = new ArrayList<>();
int i = 1;
for (FileUploadItemForm item : this.itemList) {
FileUploadItemDto fileUploadItemDto = new FileUploadItemDto();
fileUploadItemDto.setFileSeq(i++);
fileUploadItemDto.setOriginName(item.getOriginName());
fileUploadItemDto.setSizeBytes(item.getSizeBytes());
fileUploadItemDto.setContentType(item.getContentType());
fileUploadItemDtoList.add(fileUploadItemDto);
}
fileUploadDto.setItemList(fileUploadItemDtoList);
return fileUploadDto;
}
}
@@ -0,0 +1,26 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.TusFileDeleteDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "파일 삭제 요청 폼")
@Getter
@Setter
public class TusFileDeleteForm {
@Schema(
description = "파일 UUID"
, example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
public TusFileDeleteDto toDto() {
TusFileDeleteDto tusFileDeleteDto = new TusFileDeleteDto();
tusFileDeleteDto.setFileUuid(fileUuid == null ? null : fileUuid.trim());
return tusFileDeleteDto;
}
}
@@ -10,8 +10,8 @@ import lombok.Setter;
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class TusHookEventForm {
public class TusFileHookEventForm {
@Schema(description = "TUS 업로드 정보")
@JsonProperty("Upload")
private TusHookUploadForm tusHookUploadForm;
private TusFileHookUploadForm tusFileHookUploadForm;
}
@@ -0,0 +1,45 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.TusFileHookDto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 훅 요청 폼")
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class TusFileHookForm {
@Schema(description = "TUS 훅 이벤트 타입", example = "post-finish")
@JsonProperty("Type")
private String type;
@Schema(description = "TUS 훅 이벤트 정보")
@JsonProperty("Event")
private TusFileHookEventForm tusFileHookEventForm;
public TusFileHookDto toDto() {
TusFileHookDto tusFileHookDto = new TusFileHookDto();
tusFileHookDto.setType(this.type);
if (this.tusFileHookEventForm == null || this.tusFileHookEventForm.getTusFileHookUploadForm() == null) {
return tusFileHookDto;
}
TusFileHookUploadForm upload = this.tusFileHookEventForm.getTusFileHookUploadForm();
tusFileHookDto.setUploadId(upload.getId());
tusFileHookDto.setSize(upload.getSize());
tusFileHookDto.setOffset(upload.getOffset());
if (upload.getMetaData() != null) {
tusFileHookDto.setFileUuid(upload.getMetaData().get("fileUuid"));
tusFileHookDto.setFilename(upload.getMetaData().get("filename"));
tusFileHookDto.setFiletype(upload.getMetaData().get("filetype"));
}
return tusFileHookDto;
}
}
@@ -12,7 +12,7 @@ import java.util.Map;
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class TusHookUploadForm {
public class TusFileHookUploadForm {
@Schema(description = "TUS 업로드 ID", example = "018f6f2d7b5c7d499c3a8c0f4f9f9f10")
@JsonProperty("ID")
private String id;
@@ -0,0 +1,26 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.TusFileUploadCancelDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 취소 요청 폼")
@Getter
@Setter
public class TusFileUploadCancelForm {
@Schema(
description = "파일 UUID"
, example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
public TusFileUploadCancelDto toDto() {
TusFileUploadCancelDto tusFileUploadCancelDto = new TusFileUploadCancelDto();
tusFileUploadCancelDto.setFileUuid(this.fileUuid == null ? null : this.fileUuid.trim());
return tusFileUploadCancelDto;
}
}
@@ -0,0 +1,62 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.TusFileUploadInitDto;
import com.alist.api.modules.tusFile.dto.TusFileUploadInitItemDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "TUS 파일 업로드 초기화 요청 폼")
@Getter
@Setter
public class TusFileUploadInitForm {
@Schema(
description = "파일 카테고리"
, example = "BOARD"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 카테고리는 필수입니다.")
private String fileCategory;
@Schema(description = "업로드 폴더 경로. 미전달 시 / 로 처리됩니다.", example = "/board")
private String folderPath;
@Schema(
description = "업로드 파일 목록"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotEmpty(message = "업로드 파일 목록은 필수입니다.")
@Valid
private List<TusFileUploadInitItemForm> itemList;
public TusFileUploadInitDto toDto() {
TusFileUploadInitDto tusFileUploadInitDto = new TusFileUploadInitDto();
tusFileUploadInitDto.setFileCategory(this.fileCategory.trim());
tusFileUploadInitDto.setFolderPath((this.folderPath == null || this.folderPath.isBlank()) ? "/" : this.folderPath.trim());
List<TusFileUploadInitItemDto> tusFileUploadInitItemDtoList = new ArrayList<>();
int i = 1;
for (TusFileUploadInitItemForm item : this.itemList) {
TusFileUploadInitItemDto tusFileUploadInitItemDto = new TusFileUploadInitItemDto();
tusFileUploadInitItemDto.setFileSeq(i++);
tusFileUploadInitItemDto.setOriginName(item.getOriginName());
tusFileUploadInitItemDto.setSizeBytes(item.getSizeBytes());
tusFileUploadInitItemDto.setContentType(item.getContentType());
tusFileUploadInitItemDtoList.add(tusFileUploadInitItemDto);
}
tusFileUploadInitDto.setItemList(tusFileUploadInitItemDtoList);
return tusFileUploadInitDto;
}
}
@@ -10,19 +10,19 @@ import lombok.Setter;
@Schema(description = "TUS 업로드 파일 항목")
@Getter
@Setter
public class FileUploadItemForm {
public class TusFileUploadInitItemForm {
@Schema(
description = "원본 파일명",
example = "sample.png",
requiredMode = Schema.RequiredMode.REQUIRED
description = "원본 파일명"
, example = "sample.png"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "원본 파일명은 필수입니다.")
private String originName;
@Schema(
description = "파일 크기(byte)",
example = "102400",
requiredMode = Schema.RequiredMode.REQUIRED
description = "파일 크기(byte)"
, example = "102400"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotNull(message = "파일 크기는 필수입니다.")
@Positive(message = "파일 크기는 0보다 커야 합니다.")
@@ -0,0 +1,26 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.TusFileUploadStatusDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 상태 조회 요청 폼")
@Getter
@Setter
public class TusFileUploadStatusForm {
@Schema(
description = "파일 UUID"
, example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
public TusFileUploadStatusDto toDto() {
TusFileUploadStatusDto tusFileUploadStatusDto = new TusFileUploadStatusDto();
tusFileUploadStatusDto.setFileUuid(this.fileUuid == null ? null : this.fileUuid.trim());
return tusFileUploadStatusDto;
}
}
@@ -1,45 +0,0 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.TusHookDto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 훅 요청 폼")
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class TusHookForm {
@Schema(description = "TUS 훅 이벤트 타입", example = "post-finish")
@JsonProperty("Type")
private String type;
@Schema(description = "TUS 훅 이벤트 정보")
@JsonProperty("Event")
private TusHookEventForm tusHookEventForm;
public TusHookDto toTusHookDto() {
TusHookDto tusHookDto = new TusHookDto();
tusHookDto.setType(this.type);
if (this.tusHookEventForm == null || this.tusHookEventForm.getTusHookUploadForm() == null) {
return tusHookDto;
}
TusHookUploadForm upload = this.tusHookEventForm.getTusHookUploadForm();
tusHookDto.setUploadId(upload.getId());
tusHookDto.setSize(upload.getSize());
tusHookDto.setOffset(upload.getOffset());
if (upload.getMetaData() != null) {
tusHookDto.setFileUuid(upload.getMetaData().get("fileUuid"));
tusHookDto.setFilename(upload.getMetaData().get("filename"));
tusHookDto.setFiletype(upload.getMetaData().get("filetype"));
}
return tusHookDto;
}
}
@@ -1,19 +0,0 @@
package com.alist.api.modules.tusFile.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 취소 요청 폼")
@Getter
@Setter
public class UploadCancelForm {
@Schema(
description = "파일 UUID",
example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
}
@@ -1,26 +0,0 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.UploadStatusDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 상태 조회 요청 폼")
@Getter
@Setter
public class UploadStatusForm {
@Schema(
description = "파일 UUID",
example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
public UploadStatusDto toUploadStatusDto() {
UploadStatusDto uploadStatusDto = new UploadStatusDto();
uploadStatusDto.setFileUuid(this.fileUuid == null ? null : this.fileUuid.trim());
return uploadStatusDto;
}
}
@@ -1,53 +1,56 @@
package com.alist.api.modules.tusFile.mapper;
import com.alist.api.modules.tusFile.dto.*;
import com.alist.api.modules.tusFile.vo.TusFileListItemVo;
import com.alist.api.modules.tusFile.vo.TusFileUploadCancelProgressVo;
import com.alist.api.modules.tusFile.vo.TusFileViewOrDownloadVo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface TusFileMapper {
void insertFileMasterInit(FileUploadDto fileUploadDto);
void insertTusFileUploadInitMaster(TusFileUploadInitDto tusFileUploadInitDto);
void insertFileDetailInit(FileUploadItemDto item);
void insertTusFileUploadInitDetail(TusFileUploadInitItemDto tusFileUploadInitItemDto);
int selectFileDetailCountByFileUuidUserToKenIdx(UploadTokenDto uploadTokenDto);
int selectTusFileUploadAuthCount(TusFileUploadAuthDto tusFileUploadAuthDto);
void insertFileUploadEventLog(TusHookDto tusHookDto);
void insertTusFileHookEventLog(TusFileHookDto tusFileHookDto);
void updateFileDetailStatus(TusHookDto tusHookDto);
void updateTusFileHookDetailStatus(TusFileHookDto tusFileHookDto);
void updateFileMasterAggregateByFileUuid(String fileUuid);
FileUploadMetaDto selectFileUploadMetaByFileUuid(String fileUuid);
TusFileUploadMetaDto selectTusFileUploadMeta(String fileUuid);
void updateTusUploadIdIfNull(FileUploadMetaDto fileUploadMetaDto);
void updateTusFileUploadIdIfNull(TusFileUploadMetaDto tusFileUploadMetaDto);
void updateFileDetailCanceledByFileUuid(String fileUuid);
int selectFileDetailCountByFileUuidAndUserToKenIdx(UploadCancelDto uploadCancelDto);
int selectTusFileUploadCancelCount(TusFileUploadCancelDto tusFileUploadCancelDto);
int updateFileDetailCanceledByFileUuidAndUserTokenIdx(UploadCancelDto uploadCancelDto);
TusFileUploadCancelProgressVo selectTusFileUploadCancelProgress(TusFileUploadCancelDto tusFileUploadCancelDto);
UploadCancelDto selectUploadProgressByFileUuid(UploadCancelDto uploadCancelDto);
TusFileMoveTaskDto selectTusFileMoveTask(String fileUuid);
FileMoveTaskDto selectMoveTaskByFileUuid(String fileUuid);
int updateTusFileMoveSuccess(TusFileMoveTaskDto tusFileMoveTaskDto);
int updateFileMoveSuccess(FileMoveTaskDto task);
int updateTusFileMovePending(TusFileMoveTaskDto tusFileMoveTaskDto);
int updateFileMovePendingByFileUuid(FileMoveTaskDto task);
List<TusFileListItemVo> selectTusFileList(TusFileListDto tusFileListDto);
List<FileDownloadItemDto> selectFileDownloadListByFileMasterIdx(FileDownloadDto fileDownloadDto);
TusFileViewOrDownloadVo selectTusFileViewOrDownload(TusFileViewOrDownloadDto tusFileViewOrDownloadDto);
FileDownloadDto selectFileDetailByFileUuid(FileDownloadDto fileDownloadDto);
void insertTusFileViewOrDownloadEventLog(TusFileViewOrDownloadVo tusFileViewOrDownloadVo);
void insertFileDownloadEventLog(FileDownloadDto fileDetailInfo);
int selectTusFileDeleteTargetCount(TusFileDeleteDto tusFileDeleteDto);
int selectFileDeleteTargetCount(FileDeleteDto fileDeleteDto);
int updateTusFileDetailDelete(TusFileDeleteDto tusFileDeleteDto);
int updateFileDetailDeleteByFileUuid(FileDeleteDto fileDeleteDto);
int updateFileMasterDeleteByFileUuid(FileDeleteDto fileDeleteDto);
int updateTusFileMasterDelete(TusFileDeleteDto tusFileDeleteDto);
Integer selectUserIdxByUserTokenIdx(Integer userTokenIdx);
int updateTusFileUploadCancel(TusFileUploadCancelDto tusFileUploadCancelDto);
}
@@ -4,6 +4,7 @@ import com.alist.api.common.utils.SecurityUtil;
import com.alist.api.config.jwt.JwtTokenProvider;
import com.alist.api.modules.tusFile.dto.*;
import com.alist.api.modules.tusFile.mapper.TusFileMapper;
import com.alist.api.modules.tusFile.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
@@ -19,10 +20,7 @@ import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
@Slf4j
@Service
@@ -57,70 +55,102 @@ public class TusFileService {
/*파일 초기화 작업*/
@Transactional
public FileUploadDto insertFileInit(FileUploadDto fileUploadDto) {
fileUploadDto.setStatus(0);
fileUploadDto.setTotalCount(fileUploadDto.getItemList().size());
public TusFileUploadInitVo insertTusFileUploadInit(TusFileUploadInitDto tusFileUploadInitDto) {
tusFileUploadInitDto.setStatus(0);
tusFileUploadInitDto.setTotalCount(tusFileUploadInitDto.getItemList().size());
tusFileMapper.insertFileMasterInit(fileUploadDto);
fileUploadDto.setTusEndpoint(tusEndpoint);
tusFileMapper.insertTusFileUploadInitMaster(tusFileUploadInitDto);
tusFileUploadInitDto.setTusEndpoint(tusEndpoint);
if (fileUploadDto.getItemList() == null || fileUploadDto.getItemList().isEmpty()) {
return fileUploadDto;
if (tusFileUploadInitDto.getItemList() == null || tusFileUploadInitDto.getItemList().isEmpty()) {
return toTusFileUploadInitVo(tusFileUploadInitDto);
}
for (FileUploadItemDto item : fileUploadDto.getItemList()) {
item.setFileMasterIdx(fileUploadDto.getFileMasterIdx());
item.setFileUuid(UUID.randomUUID().toString());
item.setUploadToken(jwtTokenProvider.createUploadToken(fileUploadDto.getUserTokenIdx()));
item.setUserIdx(fileUploadDto.getUserIdx());
item.setStatus(0); // PENDING
tusFileMapper.insertFileDetailInit(item);
for (TusFileUploadInitItemDto tusFileUploadInitItemDto : tusFileUploadInitDto.getItemList()) {
tusFileUploadInitItemDto.setFileMasterIdx(tusFileUploadInitDto.getFileMasterIdx());
tusFileUploadInitItemDto.setFileUuid(UUID.randomUUID().toString());
tusFileUploadInitItemDto.setUploadToken(jwtTokenProvider.createUploadToken(tusFileUploadInitDto.getUserTokenIdx()));
tusFileUploadInitItemDto.setUserIdx(tusFileUploadInitDto.getUserIdx());
tusFileUploadInitItemDto.setStatus(0);
tusFileMapper.insertTusFileUploadInitDetail(tusFileUploadInitItemDto);
}
return fileUploadDto;
return toTusFileUploadInitVo(tusFileUploadInitDto);
}
private TusFileUploadInitVo toTusFileUploadInitVo(TusFileUploadInitDto tusFileUploadInitDto) {
TusFileUploadInitVo tusFileUploadInitVo = new TusFileUploadInitVo();
tusFileUploadInitVo.setFileMasterIdx(tusFileUploadInitDto.getFileMasterIdx());
tusFileUploadInitVo.setTusEndpoint(tusFileUploadInitDto.getTusEndpoint());
List<TusFileUploadInitItemVo> tusFileUploadInitItemVoList = new ArrayList<>();
if (tusFileUploadInitDto.getItemList() != null) {
for (TusFileUploadInitItemDto tusFileUploadInitItemDto : tusFileUploadInitDto.getItemList()) {
TusFileUploadInitItemVo tusFileUploadInitItemVo = new TusFileUploadInitItemVo();
tusFileUploadInitItemVo.setFileSeq(tusFileUploadInitItemDto.getFileSeq());
tusFileUploadInitItemVo.setFileUuid(tusFileUploadInitItemDto.getFileUuid());
tusFileUploadInitItemVo.setUploadToken(tusFileUploadInitItemDto.getUploadToken());
tusFileUploadInitItemVo.setOriginName(tusFileUploadInitItemDto.getOriginName());
tusFileUploadInitItemVo.setSizeBytes(tusFileUploadInitItemDto.getSizeBytes());
tusFileUploadInitItemVo.setContentType(tusFileUploadInitItemDto.getContentType());
tusFileUploadInitItemVoList.add(tusFileUploadInitItemVo);
}
}
tusFileUploadInitVo.setItemList(tusFileUploadInitItemVoList);
return tusFileUploadInitVo;
}
/*업로드 권한 체크*/
public UploadTokenDto isUploadTokenCheck(UploadTokenDto uploadTokenDto) {
if (!jwtTokenProvider.validateToken(uploadTokenDto.getUploadToken())) {
uploadTokenDto.setResultCode(401);
return uploadTokenDto;
public TusFileUploadAuthVo selectTusFileUploadAuth(TusFileUploadAuthDto tusFileUploadAuthDto) {
TusFileUploadAuthVo tusFileUploadAuthVo = new TusFileUploadAuthVo();
if (!jwtTokenProvider.validateToken(tusFileUploadAuthDto.getUploadToken())) {
tusFileUploadAuthVo.setResultCode(401);
return tusFileUploadAuthVo;
}
if (!jwtTokenProvider.hasUploadScope(uploadTokenDto.getUploadToken())) {
uploadTokenDto.setResultCode(403);
return uploadTokenDto;
if (!jwtTokenProvider.hasUploadScope(tusFileUploadAuthDto.getUploadToken())) {
tusFileUploadAuthVo.setResultCode(403);
return tusFileUploadAuthVo;
}
try {
uploadTokenDto.setUserTokenIdx(Integer.parseInt(jwtTokenProvider.getUserTokenIdx(uploadTokenDto.getUploadToken())));
tusFileUploadAuthDto.setUserTokenIdx(Integer.parseInt(jwtTokenProvider.getUserTokenIdx(tusFileUploadAuthDto.getUploadToken())));
} catch (NumberFormatException e) {
uploadTokenDto.setResultCode(401);
return uploadTokenDto;
tusFileUploadAuthVo.setResultCode(401);
return tusFileUploadAuthVo;
}
applyTusMeta(uploadTokenDto);
applyTusMeta(tusFileUploadAuthDto);
String cacheKey = buildAuthCacheKey(uploadTokenDto);
String cacheKey = buildAuthCacheKey(tusFileUploadAuthDto);
try {
String cached = stringRedisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
try {
uploadTokenDto.setResultCode(Integer.parseInt(cached));
return uploadTokenDto;
tusFileUploadAuthVo.setResultCode(Integer.parseInt(cached));
return tusFileUploadAuthVo;
} catch (NumberFormatException parseEx) {
log.warn("uploadAuth cache parse fail. key={}, value={}", cacheKey, cached);
stringRedisTemplate.delete(cacheKey); // 오염 캐시 정리
stringRedisTemplate.delete(cacheKey);
}
}
} catch (Exception redisGetEx) {
log.warn("uploadAuth cache get fail. fallback DB. key={}", cacheKey, redisGetEx);
}
int isOk = tusFileMapper.selectFileDetailCountByFileUuidUserToKenIdx(uploadTokenDto);
int resultCode = (isOk > 0) ? 200 : 403;
uploadTokenDto.setResultCode(resultCode);
int tusFileUploadAuthCount = tusFileMapper.selectTusFileUploadAuthCount(tusFileUploadAuthDto);
int resultCode = (tusFileUploadAuthCount > 0) ? 200 : 403;
tusFileUploadAuthVo.setResultCode(resultCode);
try {
stringRedisTemplate.opsForValue().set(
@@ -132,16 +162,16 @@ public class TusFileService {
log.warn("uploadAuth cache set fail. key={}, resultCode={}", cacheKey, resultCode, redisSetEx);
}
return uploadTokenDto;
return tusFileUploadAuthVo;
}
private void applyTusMeta(UploadTokenDto dto) {
String len = dto.getUploadLengthRaw();
private void applyTusMeta(TusFileUploadAuthDto tusFileUploadAuthDto) {
String len = tusFileUploadAuthDto.getUploadLengthRaw();
if (len != null && !len.isBlank()) {
try { dto.setSizeBytes(Long.parseLong(len.trim())); } catch (Exception ignored) {}
try { tusFileUploadAuthDto.setSizeBytes(Long.parseLong(len.trim())); } catch (Exception ignored) {}
}
String raw = dto.getUploadMetadataRaw();
String raw = tusFileUploadAuthDto.getUploadMetadataRaw();
if (raw == null || raw.isBlank()) return;
for (String part : raw.split(",")) {
@@ -158,20 +188,22 @@ public class TusFileService {
continue;
}
if ("filename".equals(key)) dto.setOriginName(decoded);
else if ("filetype".equals(key)) dto.setContentType(decoded);
if ("filename".equals(key)) tusFileUploadAuthDto.setOriginName(decoded);
else if ("filetype".equals(key)) tusFileUploadAuthDto.setContentType(decoded);
}
}
public UploadStatusDto getUploadStatus(UploadStatusDto uploadStatusDto) {
String key = buildUploadStatusKey(uploadStatusDto.getFileUuid());
public TusFileUploadStatusVo loadTusFileUploadStatus(TusFileUploadStatusDto tusFileUploadStatusDto) {
String key = buildUploadStatusKey(tusFileUploadStatusDto.getFileUuid());
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(key);
if (map.isEmpty()) {
return null;
}
String fileUuid = (String) map.getOrDefault("fileUuid", uploadStatusDto.getFileUuid());
TusFileUploadStatusVo tusFileUploadStatusVo = new TusFileUploadStatusVo();
String fileUuid = (String) map.getOrDefault("fileUuid", tusFileUploadStatusDto.getFileUuid());
String status = (String) map.getOrDefault("status", "PENDING");
long uploadedBytes = parseLong(map.get("uploadedBytes"));
long totalBytes = parseLong(map.get("totalBytes"));
@@ -184,19 +216,19 @@ public class TusFileService {
updatedAt = Instant.now().toString();
saveCanceledStatusRedis(fileUuid, uploadedBytes, totalBytes);
fileMapper.updateFileDetailCanceledByFileUuid(fileUuid);
fileMapper.updateFileMasterAggregateByFileUuid(fileUuid);
tusFileMapper.updateFileDetailCanceledByFileUuid(fileUuid);
tusFileMapper.updateFileMasterAggregateByFileUuid(fileUuid);
}
*/
uploadStatusDto.setFileUuid(fileUuid);
uploadStatusDto.setStatus(status);
uploadStatusDto.setUploadedBytes(uploadedBytes);
uploadStatusDto.setTotalBytes(totalBytes);
uploadStatusDto.setPercent(percent);
uploadStatusDto.setUpdatedAt(updatedAt);
tusFileUploadStatusVo.setFileUuid(fileUuid);
tusFileUploadStatusVo.setStatus(status);
tusFileUploadStatusVo.setUploadedBytes(uploadedBytes);
tusFileUploadStatusVo.setTotalBytes(totalBytes);
tusFileUploadStatusVo.setPercent(percent);
tusFileUploadStatusVo.setUpdatedAt(updatedAt);
return uploadStatusDto;
return tusFileUploadStatusVo;
}
private boolean isStaleUploading(String status, String updatedAt) {
@@ -222,23 +254,23 @@ public class TusFileService {
/*db에 상태값 저장*/
@Transactional
public boolean updateFileUploadStatus(TusHookDto tusHookDto) {
if (tusHookDto == null || tusHookDto.getFileUuid() == null || tusHookDto.getFileUuid().isBlank()) {
public boolean updateTusFileHook(TusFileHookDto tusFileHookDto) {
if (tusFileHookDto == null || tusFileHookDto.getFileUuid() == null || tusFileHookDto.getFileUuid().isBlank()) {
return true;
}
String fileUuid = tusHookDto.getFileUuid().trim();
String type = tusHookDto.getType() == null ? "" : tusHookDto.getType().trim().toLowerCase();
String uploadId = tusHookDto.getUploadId() == null ? "" : tusHookDto.getUploadId().trim();
long offset = tusHookDto.getOffset() == null ? 0L : tusHookDto.getOffset();
long size = tusHookDto.getSize() == null ? 0L : tusHookDto.getSize();
String fileUuid = tusFileHookDto.getFileUuid().trim();
String type = tusFileHookDto.getType() == null ? "" : tusFileHookDto.getType().trim().toLowerCase();
String uploadId = tusFileHookDto.getUploadId() == null ? "" : tusFileHookDto.getUploadId().trim();
long offset = tusFileHookDto.getOffset() == null ? 0L : tusFileHookDto.getOffset();
long size = tusFileHookDto.getSize() == null ? 0L : tusFileHookDto.getSize();
tusFileMapper.insertFileUploadEventLog(tusHookDto);
tusFileMapper.insertTusFileHookEventLog(tusFileHookDto);
FileUploadMetaDto fileUploadMetaInfo = tusFileMapper.selectFileUploadMetaByFileUuid(fileUuid);
TusFileUploadMetaDto fileUploadMetaInfo = tusFileMapper.selectTusFileUploadMeta(fileUuid);
if (fileUploadMetaInfo == null) return true;
if (isMetadataMismatch(fileUploadMetaInfo, tusHookDto)) {
if (isMetadataMismatch(fileUploadMetaInfo, tusFileHookDto)) {
tusFileMapper.updateFileDetailCanceledByFileUuid(fileUuid);
tusFileMapper.updateFileMasterAggregateByFileUuid(fileUuid);
saveCanceledStatusRedis(fileUuid, offset, size);
@@ -247,12 +279,12 @@ public class TusFileService {
if (!uploadId.isBlank()) {
if (fileUploadMetaInfo.getTusUploadId() == null || fileUploadMetaInfo.getTusUploadId().isBlank()) {
FileUploadMetaDto fileUploadMetaDto = new FileUploadMetaDto();
fileUploadMetaDto.setFileUuid(fileUuid);
fileUploadMetaDto.setTusUploadId(uploadId);
tusFileMapper.updateTusUploadIdIfNull(fileUploadMetaDto);
TusFileUploadMetaDto tusFileUploadMetaDto = new TusFileUploadMetaDto();
tusFileUploadMetaDto.setFileUuid(fileUuid);
tusFileUploadMetaDto.setTusUploadId(uploadId);
tusFileMapper.updateTusFileUploadIdIfNull(tusFileUploadMetaDto);
fileUploadMetaInfo = tusFileMapper.selectFileUploadMetaByFileUuid(fileUuid);
fileUploadMetaInfo = tusFileMapper.selectTusFileUploadMeta(fileUuid);
}
if (fileUploadMetaInfo.getTusUploadId() == null || fileUploadMetaInfo.getTusUploadId().isBlank()
|| !fileUploadMetaInfo.getTusUploadId().equals(uploadId)) {
@@ -280,8 +312,8 @@ public class TusFileService {
return false;
}
tusHookDto.setUploadedBytes(null);
tusHookDto.setStatus(1); // UPLOADING
tusFileHookDto.setUploadedBytes(null);
tusFileHookDto.setStatus(1); // UPLOADING
break;
case "post-receive":
@@ -290,13 +322,13 @@ public class TusFileService {
return false;
}
tusHookDto.setUploadedBytes(null);
tusHookDto.setStatus(1); // UPLOADING
tusFileHookDto.setUploadedBytes(null);
tusFileHookDto.setStatus(1); // UPLOADING
break;
case "post-finish":
tusHookDto.setUploadedBytes(size);
tusHookDto.setStatus(3); // DONE
tusFileHookDto.setUploadedBytes(size);
tusFileHookDto.setStatus(3); // DONE
break;
case "post-terminate":
@@ -304,31 +336,31 @@ public class TusFileService {
if (currentStatus != null && currentStatus == 3) {
return false;
}
tusHookDto.setUploadedBytes(offset);
tusHookDto.setStatus(5); // CANCELED
tusFileHookDto.setUploadedBytes(offset);
tusFileHookDto.setStatus(5); // CANCELED
break;
default:
return false;
}
if (tusHookDto.getStatus() == 1 && currentStatus != null && currentStatus >= 3) {
if (tusFileHookDto.getStatus() == 1 && currentStatus != null && currentStatus >= 3) {
return false;
}
boolean shouldUpdateDetail = !"post-receive".equals(type);
if (shouldUpdateDetail) {
tusFileMapper.updateFileDetailStatus(tusHookDto);
tusFileMapper.updateTusFileHookDetailStatus(tusFileHookDto);
}
if ("post-finish".equals(type) || "post-terminate".equals(type)) {
tusFileMapper.updateFileMasterAggregateByFileUuid(tusHookDto.getFileUuid());
tusFileMapper.updateFileMasterAggregateByFileUuid(tusFileHookDto.getFileUuid());
}
if ("post-finish".equals(type)) {
try {
tryMoveNowByFileUuid(tusHookDto.getFileUuid());
tryMoveNowByFileUuid(tusFileHookDto.getFileUuid());
} catch (NoSuchFileException e) {
try {
Thread.sleep(200);
@@ -336,14 +368,14 @@ public class TusFileService {
Thread.currentThread().interrupt();
}
try {
tryMoveNowByFileUuid(tusHookDto.getFileUuid()); // 1회 재시도
tryMoveNowByFileUuid(tusFileHookDto.getFileUuid()); // 1회 재시도
} catch (Exception ex) {
log.error("Move failed after retry. fileUuid={}", tusHookDto.getFileUuid(), ex);
markMovePending(tusHookDto.getFileUuid(), truncateErr(ex.toString()));
log.error("Move failed after retry. fileUuid={}", tusFileHookDto.getFileUuid(), ex);
markMovePending(tusFileHookDto.getFileUuid(), truncateErr(ex.toString()));
}
} catch (Exception moveEx) {
log.error("Move failed. fileUuid={}", tusHookDto.getFileUuid(), moveEx);
markMovePending(tusHookDto.getFileUuid(), truncateErr(moveEx.toString()));
log.error("Move failed. fileUuid={}", tusFileHookDto.getFileUuid(), moveEx);
markMovePending(tusFileHookDto.getFileUuid(), truncateErr(moveEx.toString()));
}
}
@@ -352,7 +384,7 @@ public class TusFileService {
@Transactional
public void tryMoveNowByFileUuid(String fileUuid) throws IOException {
FileMoveTaskDto task = tusFileMapper.selectMoveTaskByFileUuid(fileUuid);
TusFileMoveTaskDto task = tusFileMapper.selectTusFileMoveTask(fileUuid);
if (task == null) return;
if (task.getMoveYn() != null && !"N".equalsIgnoreCase(task.getMoveYn())) return;
@@ -373,15 +405,15 @@ public class TusFileService {
task.setSavePath(toSavePath(finalPath));
task.setSaveName(finalPath.getFileName().toString());
task.setExt(extractExt(task.getSaveName()));
tusFileMapper.updateFileMoveSuccess(task);
tusFileMapper.updateTusFileMoveSuccess(task);
}
@Transactional
public void markMovePending(String fileUuid, String errMsg) {
FileMoveTaskDto task = new FileMoveTaskDto();
task.setFileUuid(fileUuid);
task.setMoveLastError(errMsg == null ? "move failed" : errMsg);
tusFileMapper.updateFileMovePendingByFileUuid(task); // MOVE_YN='N', TRY_COUNT+1, LAST_ERROR
TusFileMoveTaskDto tusFileMoveTaskDto = new TusFileMoveTaskDto();
tusFileMoveTaskDto.setFileUuid(fileUuid);
tusFileMoveTaskDto.setMoveLastError(errMsg == null ? "move failed" : errMsg);
tusFileMapper.updateTusFileMovePending(tusFileMoveTaskDto); // MOVE_YN='N', TRY_COUNT+1, LAST_ERROR
}
private String truncateErr(String s) {
@@ -389,7 +421,7 @@ public class TusFileService {
return s.length() > 500 ? s.substring(0, 500) : s;
}
private Path resolveTmpPath(FileMoveTaskDto task) {
private Path resolveTmpPath(TusFileMoveTaskDto task) {
// 예: /srv/project/alist/uploads/tmp/{tusUploadId}
if (task.getTusUploadId() == null || task.getTusUploadId().isBlank()) {
throw new IllegalStateException("tusUploadId is empty");
@@ -397,7 +429,7 @@ public class TusFileService {
return Paths.get(uploadTmpRoot, task.getTusUploadId());
}
private Path buildFinalPath(FileMoveTaskDto task) {
private Path buildFinalPath(TusFileMoveTaskDto task) {
String ext = extractExt(task.getOriginName());
String fileName = (task.getFileUuid() == null ? UUID.randomUUID().toString() : task.getFileUuid())
+ (ext.isBlank() ? "" : "." + ext);
@@ -449,15 +481,15 @@ public class TusFileService {
}
/*레이디스에 상태값 저장*/
public void saveUploadStatusRedisTusHook(TusHookDto tusHookDto) {
if (tusHookDto == null || tusHookDto.getFileUuid() == null || tusHookDto.getFileUuid().isBlank()) {
public void saveTusFileHookStatusRedis(TusFileHookDto tusFileHookDto) {
if (tusFileHookDto == null || tusFileHookDto.getFileUuid() == null || tusFileHookDto.getFileUuid().isBlank()) {
return;
}
String key = buildUploadStatusKey(tusHookDto.getFileUuid().trim());
String type = tusHookDto.getType() == null ? "" : tusHookDto.getType().trim().toLowerCase();
long total = tusHookDto.getSize() == null ? 0L : tusHookDto.getSize();
long uploaded = tusHookDto.getOffset() == null ? 0L : tusHookDto.getOffset();
String key = buildUploadStatusKey(tusFileHookDto.getFileUuid().trim());
String type = tusFileHookDto.getType() == null ? "" : tusFileHookDto.getType().trim().toLowerCase();
long total = tusFileHookDto.getSize() == null ? 0L : tusFileHookDto.getSize();
long uploaded = tusFileHookDto.getOffset() == null ? 0L : tusFileHookDto.getOffset();
String status = "UPLOADING";
if ("post-finish".equals(type)) {
@@ -472,7 +504,7 @@ public class TusFileService {
int percent = (total > 0) ? (int) Math.min(100, (uploaded * 100) / total) : 0;
HashOperations<String, Object, Object> ops = stringRedisTemplate.opsForHash();
ops.put(key, "fileUuid", tusHookDto.getFileUuid());
ops.put(key, "fileUuid", tusFileHookDto.getFileUuid());
ops.put(key, "status", status);
ops.put(key, "uploadedBytes", String.valueOf(uploaded));
ops.put(key, "totalBytes", String.valueOf(total));
@@ -510,14 +542,14 @@ public class TusFileService {
}
}
private String buildAuthCacheKey(UploadTokenDto dto) {
String normalizedName = normalize(dto.getOriginName());
String normalizedType = normalize(dto.getContentType());
String normalizedSize = (dto.getSizeBytes() == null) ? "" : String.valueOf(dto.getSizeBytes());
private String buildAuthCacheKey(TusFileUploadAuthDto tusFileUploadAuthDto) {
String normalizedName = normalize(tusFileUploadAuthDto.getOriginName());
String normalizedType = normalize(tusFileUploadAuthDto.getContentType());
String normalizedSize = (tusFileUploadAuthDto.getSizeBytes() == null) ? "" : String.valueOf(tusFileUploadAuthDto.getSizeBytes());
String raw = String.join("|",
normalize(dto.getFileUuid()),
dto.getUploadToken() == null ? "" : dto.getUploadToken(),
normalize(tusFileUploadAuthDto.getFileUuid()),
tusFileUploadAuthDto.getUploadToken() == null ? "" : tusFileUploadAuthDto.getUploadToken(),
normalizedName,
normalizedType,
normalizedSize
@@ -545,96 +577,117 @@ public class TusFileService {
return (uploadAuthCacheTtlSeconds > 0) ? uploadAuthCacheTtlSeconds : 20L;
}
private boolean isMetadataMismatch(FileUploadMetaDto fileUploadMetaDto, TusHookDto tusHookDto) {
if (tusHookDto.getSize() != null && fileUploadMetaDto.getSizeBytes() != null && !tusHookDto.getSize().equals(fileUploadMetaDto.getSizeBytes())) {
private boolean isMetadataMismatch(TusFileUploadMetaDto tusFileUploadMetaDto, TusFileHookDto tusFileHookDto) {
if (tusFileHookDto.getSize() != null && tusFileUploadMetaDto.getSizeBytes() != null && !tusFileHookDto.getSize().equals(tusFileUploadMetaDto.getSizeBytes())) {
return true;
}
if (tusHookDto.getFilename() != null && !tusHookDto.getFilename().isBlank()
&& fileUploadMetaDto.getOriginName() != null && !fileUploadMetaDto.getOriginName().equals(tusHookDto.getFilename())) {
if (tusFileHookDto.getFilename() != null && !tusFileHookDto.getFilename().isBlank()
&& tusFileUploadMetaDto.getOriginName() != null && !tusFileUploadMetaDto.getOriginName().equals(tusFileHookDto.getFilename())) {
return true;
}
if (tusHookDto.getFiletype() != null && !tusHookDto.getFiletype().isBlank()
&& fileUploadMetaDto.getContentType() != null && !fileUploadMetaDto.getContentType().isBlank()
&& !fileUploadMetaDto.getContentType().equalsIgnoreCase(tusHookDto.getFiletype())) {
if (tusFileHookDto.getFiletype() != null && !tusFileHookDto.getFiletype().isBlank()
&& tusFileUploadMetaDto.getContentType() != null && !tusFileUploadMetaDto.getContentType().isBlank()
&& !tusFileUploadMetaDto.getContentType().equalsIgnoreCase(tusFileHookDto.getFiletype())) {
return true;
}
return false;
}
@Transactional
public boolean updateUploadCancel(UploadCancelDto uploadCancelDto) {
if (uploadCancelDto.getFileUuid() == null || uploadCancelDto.getFileUuid().isBlank() || uploadCancelDto.getUserTokenIdx() == null) {
return false;
public TusFileUploadCancelVo updateTusFileUploadCancel(TusFileUploadCancelDto tusFileUploadCancelDto) {
TusFileUploadCancelVo tusFileUploadCancelVo = new TusFileUploadCancelVo();
if (tusFileUploadCancelDto.getFileUuid() == null
|| tusFileUploadCancelDto.getFileUuid().isBlank()
|| tusFileUploadCancelDto.getUserTokenIdx() == null) {
tusFileUploadCancelVo.setCanceled(false);
tusFileUploadCancelVo.setResultCode(4003);
return tusFileUploadCancelVo;
}
int allowed = tusFileMapper.selectFileDetailCountByFileUuidAndUserToKenIdx(uploadCancelDto);
if (allowed <= 0) {
return false;
int tusFileUploadCancelCount = tusFileMapper.selectTusFileUploadCancelCount(tusFileUploadCancelDto);
if (tusFileUploadCancelCount <= 0) {
tusFileUploadCancelVo.setCanceled(false);
tusFileUploadCancelVo.setResultCode(2003);
return tusFileUploadCancelVo;
}
UploadCancelDto progress = tusFileMapper.selectUploadProgressByFileUuid(uploadCancelDto);
long uploaded = (progress != null && progress.getUploadedBytes() != null) ? progress.getUploadedBytes() : 0L;
long total = (progress != null && progress.getSizeBytes() != null) ? progress.getSizeBytes() : 0L;
TusFileUploadCancelProgressVo tusFileUploadCancelProgressVo = tusFileMapper.selectTusFileUploadCancelProgress(tusFileUploadCancelDto);
int updated = tusFileMapper.updateFileDetailCanceledByFileUuidAndUserTokenIdx(uploadCancelDto);
if (updated <= 0) {
return false;
long uploadedBytes = (tusFileUploadCancelProgressVo != null && tusFileUploadCancelProgressVo.getUploadedBytes() != null)
? tusFileUploadCancelProgressVo.getUploadedBytes()
: 0L;
long totalBytes = (tusFileUploadCancelProgressVo != null && tusFileUploadCancelProgressVo.getSizeBytes() != null)
? tusFileUploadCancelProgressVo.getSizeBytes()
: 0L;
int updateCount = tusFileMapper.updateTusFileUploadCancel(tusFileUploadCancelDto);
if (updateCount <= 0) {
tusFileUploadCancelVo.setCanceled(false);
tusFileUploadCancelVo.setResultCode(2003);
return tusFileUploadCancelVo;
}
tusFileMapper.updateFileMasterAggregateByFileUuid(uploadCancelDto.getFileUuid());
saveCanceledStatusRedis(uploadCancelDto.getFileUuid(), uploaded, total); // 필요하면 기존 bytes 조회해서 넣어도 됨
return true;
tusFileMapper.updateFileMasterAggregateByFileUuid(tusFileUploadCancelDto.getFileUuid());
saveCanceledStatusRedis(tusFileUploadCancelDto.getFileUuid(), uploadedBytes, totalBytes);
tusFileUploadCancelVo.setCanceled(true);
tusFileUploadCancelVo.setResultCode(200);
return tusFileUploadCancelVo;
}
@Transactional(readOnly = true)
public FileDownloadListDto selectFileDownloadListByFileMasterIdx(FileDownloadDto fileDownloadDto) {
FileDownloadListDto fileDownloadList = new FileDownloadListDto();
public TusFileListVo selectTusFileList(TusFileListDto tusFileListDto) {
TusFileListVo tusFileListVo = new TusFileListVo();
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
if (userTokenIdx == null) {
fileDownloadList.setResultCode(401);
return fileDownloadList;
tusFileListVo.setResultCode(401);
return tusFileListVo;
}
fileDownloadDto.setUserTokenIdx(userTokenIdx);
tusFileListDto.setUserTokenIdx(userTokenIdx);
fileDownloadList.setFileMasterIdx(fileDownloadDto.getFileMasterIdx());
tusFileListVo.setFileMasterIdx(tusFileListDto.getFileMasterIdx());
List<FileDownloadItemDto> itemList = tusFileMapper.selectFileDownloadListByFileMasterIdx(fileDownloadDto);
List<TusFileListItemVo> tusFileList = tusFileMapper.selectTusFileList(tusFileListDto);
for (FileDownloadItemDto item : itemList) {
for (TusFileListItemVo item : tusFileList) {
item.setViewUrl("/tus/file/view/" + item.getFileUuid());
item.setDownloadUrl("/tus/file/download/" + item.getFileUuid());
}
fileDownloadList.setItemList(itemList);
fileDownloadList.setResultCode(200);
tusFileListVo.setItemList(tusFileList);
tusFileListVo.setResultCode(200);
return fileDownloadList;
return tusFileListVo;
}
public FileDownloadDto selectFileViewOrDownload(FileDownloadDto fileDownloadDto) {
@Transactional
public TusFileViewOrDownloadVo selectTusFileViewOrDownload(TusFileViewOrDownloadDto tusFileViewOrDownloadDto) {
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
if (userTokenIdx == null) {
return null;
}
fileDownloadDto.setUserTokenIdx(userTokenIdx);
tusFileViewOrDownloadDto.setUserTokenIdx(userTokenIdx);
FileDownloadDto fileDetailInfo = tusFileMapper.selectFileDetailByFileUuid(fileDownloadDto);
TusFileViewOrDownloadVo tusFileViewOrDownloadVo = tusFileMapper.selectTusFileViewOrDownload(tusFileViewOrDownloadDto);
if (fileDetailInfo == null) {
if (tusFileViewOrDownloadVo == null) {
return null;
}
fileDetailInfo.setUserTokenIdx(fileDownloadDto.getUserTokenIdx());
fileDetailInfo.setEventType(fileDownloadDto.getEventType());
fileDetailInfo.setClientIp(fileDownloadDto.getClientIp());
fileDetailInfo.setUserAgent(fileDownloadDto.getUserAgent());
fileDetailInfo.setReferer(fileDownloadDto.getReferer());
tusFileViewOrDownloadVo.setUserTokenIdx(tusFileViewOrDownloadDto.getUserTokenIdx());
tusFileViewOrDownloadVo.setEventType(tusFileViewOrDownloadDto.getEventType());
tusFileViewOrDownloadVo.setClientIp(tusFileViewOrDownloadDto.getClientIp());
tusFileViewOrDownloadVo.setUserAgent(tusFileViewOrDownloadDto.getUserAgent());
tusFileViewOrDownloadVo.setReferer(tusFileViewOrDownloadDto.getReferer());
tusFileMapper.insertFileDownloadEventLog(fileDetailInfo);
return fileDetailInfo;
tusFileMapper.insertTusFileViewOrDownloadEventLog(tusFileViewOrDownloadVo);
return tusFileViewOrDownloadVo;
}
public Path resolveStoredFilePath(String savePath) {
@@ -655,31 +708,46 @@ public class TusFileService {
}
@Transactional
public boolean updateFileDelete(FileDeleteDto fileDeleteDto) {
if (fileDeleteDto == null || fileDeleteDto.getFileUuid() == null || fileDeleteDto.getFileUuid().isBlank()) {
return false;
public TusFileDeleteVo updateTusFileDelete(TusFileDeleteDto tusFileDeleteDto) {
TusFileDeleteVo tusFileDeleteVo = new TusFileDeleteVo();
if (tusFileDeleteDto == null
|| tusFileDeleteDto.getFileUuid() == null
|| tusFileDeleteDto.getFileUuid().isBlank()) {
tusFileDeleteVo.setDeleted(false);
tusFileDeleteVo.setResultCode(4003);
return tusFileDeleteVo;
}
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
if (userTokenIdx == null) {
return false;
tusFileDeleteVo.setDeleted(false);
tusFileDeleteVo.setResultCode(401);
return tusFileDeleteVo;
}
fileDeleteDto.setUserTokenIdx(userTokenIdx);
fileDeleteDto.setFileUuid(fileDeleteDto.getFileUuid().trim());
tusFileDeleteDto.setUserTokenIdx(userTokenIdx);
tusFileDeleteDto.setFileUuid(tusFileDeleteDto.getFileUuid().trim());
int allowed = tusFileMapper.selectFileDeleteTargetCount(fileDeleteDto);
if (allowed <= 0) {
return false;
int tusFileDeleteTargetCount = tusFileMapper.selectTusFileDeleteTargetCount(tusFileDeleteDto);
if (tusFileDeleteTargetCount <= 0) {
tusFileDeleteVo.setDeleted(false);
tusFileDeleteVo.setResultCode(2003);
return tusFileDeleteVo;
}
int detailUpdated = tusFileMapper.updateFileDetailDeleteByFileUuid(fileDeleteDto);
if (detailUpdated <= 0) {
return false;
int tusFileDetailDeleteCount = tusFileMapper.updateTusFileDetailDelete(tusFileDeleteDto);
if (tusFileDetailDeleteCount <= 0) {
tusFileDeleteVo.setDeleted(false);
tusFileDeleteVo.setResultCode(2003);
return tusFileDeleteVo;
}
tusFileMapper.updateFileMasterDeleteByFileUuid(fileDeleteDto);
return true;
tusFileMapper.updateTusFileMasterDelete(tusFileDeleteDto);
tusFileDeleteVo.setDeleted(true);
tusFileDeleteVo.setResultCode(200);
return tusFileDeleteVo;
}
@Transactional(readOnly = true)
@@ -0,0 +1,18 @@
package com.alist.api.modules.tusFile.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 파일 삭제 응답")
@Getter
@Setter
public class TusFileDeleteVo {
@Schema(description = "파일 삭제 처리 여부", example = "true")
private boolean deleted;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -1,4 +1,4 @@
package com.alist.api.modules.tusFile.dto;
package com.alist.api.modules.tusFile.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
@@ -7,7 +7,7 @@ import lombok.Setter;
@Schema(description = "다운로드 파일 항목")
@Getter
@Setter
public class FileDownloadItemDto {
public class TusFileListItemVo {
@Schema(description = "파일 상세 PK", example = "1")
private Long fileDetailIdx;
@@ -1,4 +1,4 @@
package com.alist.api.modules.tusFile.dto;
package com.alist.api.modules.tusFile.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -10,12 +10,12 @@ import java.util.List;
@Schema(description = "파일 다운로드 목록 응답")
@Getter
@Setter
public class FileDownloadListDto {
public class TusFileListVo {
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "다운로드 파일 목록")
private List<FileDownloadItemDto> itemList;
private List<TusFileListItemVo> itemList;
@JsonIgnore
@Schema(hidden = true)
@@ -0,0 +1,15 @@
package com.alist.api.modules.tusFile.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 업로드 권한 검증 응답")
@Getter
@Setter
public class TusFileUploadAuthVo {
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -0,0 +1,12 @@
package com.alist.api.modules.tusFile.vo;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TusFileUploadCancelProgressVo {
private String fileUuid;
private Long uploadedBytes;
private Long sizeBytes;
}
@@ -0,0 +1,19 @@
package com.alist.api.modules.tusFile.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 업로드 취소 응답")
@Getter
@Setter
public class TusFileUploadCancelVo {
@JsonIgnore
@Schema(hidden = true)
private boolean canceled;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -0,0 +1,28 @@
package com.alist.api.modules.tusFile.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 파일 업로드 초기화 파일 항목 응답")
@Getter
@Setter
public class TusFileUploadInitItemVo {
@Schema(description = "파일 순번", example = "1")
private Integer fileSeq;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "TUS 업로드 토큰")
private String uploadToken;
@Schema(description = "원본 파일명", example = "sample.png")
private String originName;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long sizeBytes;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
}
@@ -0,0 +1,21 @@
package com.alist.api.modules.tusFile.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "TUS 파일 업로드 초기화 응답")
@Getter
@Setter
public class TusFileUploadInitVo {
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "TUS 업로드 엔드포인트", example = "https://file-alist.pjt.kr/tus/file/")
private String tusEndpoint;
@Schema(description = "업로드 파일 항목 목록")
private List<TusFileUploadInitItemVo> itemList;
}
@@ -0,0 +1,28 @@
package com.alist.api.modules.tusFile.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 업로드 상태 조회 응답")
@Getter
@Setter
public class TusFileUploadStatusVo {
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "업로드 상태. PENDING, UPLOADING, DONE, CANCELED", example = "UPLOADING")
private String status;
@Schema(description = "업로드 완료 byte", example = "51200")
private Long uploadedBytes;
@Schema(description = "전체 파일 크기 byte", example = "102400")
private Long totalBytes;
@Schema(description = "업로드 진행률", example = "50")
private Integer percent;
@Schema(description = "상태 갱신 시각", example = "2026-05-19T10:30:00")
private String updatedAt;
}
@@ -0,0 +1,49 @@
package com.alist.api.modules.tusFile.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 파일 보기/다운로드 조회 결과")
@Getter
@Setter
public class TusFileViewOrDownloadVo {
@Schema(description = "파일 상세 PK", example = "1")
private Long fileDetailIdx;
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "원본 파일명", example = "sample.png")
private String originName;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long sizeBytes;
@Schema(hidden = true)
private String savePath;
@Schema(hidden = true)
private Integer userIdx;
@Schema(hidden = true)
private Integer userTokenIdx;
@Schema(hidden = true)
private String eventType;
@Schema(hidden = true)
private String clientIp;
@Schema(hidden = true)
private String userAgent;
@Schema(hidden = true)
private String referer;
}
@@ -2,9 +2,147 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alist.api.modules.tusFile.mapper.TusFileMapper">
<select id="selectTusFileUploadAuthCount" resultType="java.lang.Integer">
/*TusFileMapper.selectTusFileUploadAuthCount*/
SELECT COUNT(*)
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFD.CREATE_MEMBER
WHERE LFD.DEL_YN = 'N'
AND LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.STATUS IN (0,1,2,3)
AND (#{sizeBytes} IS NULL OR LFD.SIZE_BYTES = #{sizeBytes})
AND (#{originName} IS NULL OR #{originName} = '' OR LFD.ORIGIN_NAME = #{originName})
AND (
#{contentType} IS NULL OR #{contentType} = ''
OR LFD.CONTENT_TYPE IS NULL OR LFD.CONTENT_TYPE = ''
OR LOWER(LFD.CONTENT_TYPE) = LOWER(#{contentType})
)
</select>
<insert id="insertFileMasterInit" useGeneratedKeys="true" keyProperty="fileMasterIdx">
/*FileMapper.insertFileMasterInit*/
<select id="selectTusFileUploadMeta" resultType="com.alist.api.modules.tusFile.dto.TusFileUploadMetaDto">
/*TusFileMapper.selectTusFileUploadMeta*/
SELECT FILE_UUID, ORIGIN_NAME, SIZE_BYTES, CONTENT_TYPE, TUS_UPLOAD_ID, STATUS
FROM ALISTLMS.FILE_DETAIL
WHERE FILE_UUID = #{fileUuid}
AND DEL_YN = 'N'
LIMIT 1
</select>
<select id="selectTusFileUploadCancelCount" resultType="java.lang.Integer">
/*TusFileMapper.selectTusFileUploadCancelCount*/
SELECT COUNT(*)
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFD.CREATE_MEMBER
WHERE LFD.DEL_YN = 'N'
AND LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.STATUS IN (0,1,2)
</select>
<select id="selectTusFileUploadCancelProgress" resultType="com.alist.api.modules.tusFile.vo.TusFileUploadCancelProgressVo">
/*TusFileMapper.selectTusFileUploadCancelProgress*/
SELECT LFD.FILE_UUID
, COALESCE(LFD.UPLOADED_BYTES, 0) AS UPLOADED_BYTES
, COALESCE(LFD.SIZE_BYTES, 0) AS SIZE_BYTES
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFD.CREATE_MEMBER
WHERE LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
LIMIT 1
</select>
<select id="selectTusFileMoveTask" resultType="com.alist.api.modules.tusFile.dto.TusFileMoveTaskDto">
/*TusFileMapper.selectTusFileMoveTask*/
SELECT LFD.FILE_DETAIL_IDX
, LFD.FILE_MASTER_IDX
, LFD.FILE_UUID
, LFD.TUS_UPLOAD_ID
, LFD.ORIGIN_NAME
, LFD.CONTENT_TYPE
, LFD.SIZE_BYTES
, LFD.UPLOADED_BYTES
, LFM.FOLDER_PATH
, LFD.SAVE_NAME
, LFD.SAVE_PATH
, LFD.`EXT`
, LFD.MOVE_YN
, LFD.MOVE_TRY_COUNT
, LFD.MOVE_LAST_ERROR
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
WHERE LFD.FILE_UUID = #{fileUuid}
AND LFD.DEL_YN = 'N'
LIMIT 1
</select>
<select id="selectTusFileList" resultType="com.alist.api.modules.tusFile.vo.TusFileListItemVo">
/*TusFileMapper.selectTusFileList*/
SELECT LFD.FILE_DETAIL_IDX
, LFD.FILE_MASTER_IDX
, LFD.FILE_UUID
, LFD.ORIGIN_NAME
, LFD.CONTENT_TYPE
, LFD.SIZE_BYTES
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFM.USER_IDX
WHERE LFD.FILE_MASTER_IDX = #{fileMasterIdx}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFD.MOVE_YN = 'Y'
ORDER BY LFD.FILE_SEQ ASC, LFD.FILE_DETAIL_IDX ASC
</select>
<select id="selectTusFileViewOrDownload" resultType="com.alist.api.modules.tusFile.vo.TusFileViewOrDownloadVo">
/*TusFileMapper.selectTusFileViewOrDownload*/
SELECT LFD.FILE_DETAIL_IDX
, LFD.FILE_MASTER_IDX
, LFD.FILE_UUID
, LFD.ORIGIN_NAME
, LFD.CONTENT_TYPE
, LFD.SIZE_BYTES
, LFD.SAVE_PATH
, LTUT.user_idx
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFM.USER_IDX
WHERE LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFD.MOVE_YN = 'Y'
LIMIT 1
</select>
<select id="selectTusFileDeleteTargetCount" resultType="java.lang.Integer">
/*TusFileMapper.selectTusFileDeleteTargetCount*/
SELECT COUNT(*)
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFM.USER_IDX
WHERE LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFM.STATUS = 3
</select>
<select id="selectUserIdxByUserTokenIdx" resultType="java.lang.Integer">
/*TusFileMapper.selectUserIdxByUserTokenIdx*/
SELECT user_idx
FROM test_user_token
WHERE user_token_idx = #{userTokenIdx}
</select>
<insert id="insertTusFileUploadInitMaster" useGeneratedKeys="true" keyProperty="fileMasterIdx">
/*TusFileMapper.insertTusFileUploadInitMaster*/
INSERT INTO ALISTLMS.FILE_MASTER (
USER_IDX, FILE_CATEGORY, FOLDER_PATH, TOTAL_COUNT, DONE_COUNT, FAIL_COUNT, STATUS, DEL_YN, CREATE_MEMBER, UPDATE_MEMBER
) VALUES (
@@ -12,8 +150,8 @@
)
</insert>
<insert id="insertFileDetailInit">
/*FileMapper.insertFileDetailInit*/
<insert id="insertTusFileUploadInitDetail">
/*TusFileMapper.insertTusFileUploadInitDetail*/
INSERT INTO ALISTLMS.FILE_DETAIL (
FILE_MASTER_IDX, FILE_SEQ, FILE_UUID, ORIGIN_NAME, CONTENT_TYPE, SIZE_BYTES, UPLOADED_BYTES, TUS_UPLOAD_ID, STATUS, DEL_YN, CREATE_MEMBER, UPDATE_MEMBER
) VALUES (
@@ -21,13 +159,12 @@
)
</insert>
<insert id="insertFileUploadEventLog">
/*FileMapper.insertFileUploadEventLog*/
<insert id="insertTusFileHookEventLog">
/*TusFileMapper.insertTusFileHookEventLog*/
INSERT INTO ALISTLMS.FILE_UPLOAD_EVENT_LOG (
FILE_DETAIL_IDX, EVENT_TYPE, OFFSET_BYTES, BYTES_WRITTEN, HTTP_STATUS, MESSAGE, RAW_JSON
)
SELECT
LFD.FILE_DETAIL_IDX
SELECT LFD.FILE_DETAIL_IDX
, #{type}
, #{offset}
, NULL
@@ -38,8 +175,9 @@
WHERE LFD.FILE_UUID = #{fileUuid}
LIMIT 1
</insert>
<insert id="insertFileDownloadEventLog">
/*FileMapper.insertFileDownloadEventLog*/
<insert id="insertTusFileViewOrDownloadEventLog">
/*TusFileMapper.insertTusFileViewOrDownloadEventLog*/
INSERT INTO ALISTLMS.FILE_DOWNLOAD_EVENT_LOG (
FILE_MASTER_IDX
, FILE_DETAIL_IDX
@@ -67,8 +205,8 @@
)
</insert>
<update id="updateFileDetailStatus">
/*FileMapper.updateFileDetailStatus*/
<update id="updateTusFileHookDetailStatus">
/*TusFileMapper.updateTusFileHookDetailStatus*/
UPDATE ALISTLMS.FILE_DETAIL
<set>
<if test="uploadId != null and uploadId != ''">
@@ -87,193 +225,56 @@
</update>
<update id="updateFileMasterAggregateByFileUuid">
/*FileMapper.updateFileMasterAggregateByFileUuid*/
/*TusFileMapper.updateFileMasterAggregateByFileUuid*/
UPDATE ALISTLMS.FILE_MASTER LFM
JOIN (
SELECT
LLFD.FILE_MASTER_IDX,
COUNT(*) AS total_count,
SUM(CASE WHEN LLFD.STATUS = 3 THEN 1 ELSE 0 END) AS done_count,
SUM(CASE WHEN LLFD.STATUS IN (4,5) THEN 1 ELSE 0 END) AS fail_count
FROM ALISTLMS.FILE_DETAIL LLFD
WHERE LLFD.FILE_MASTER_IDX = (
SELECT FILE_MASTER_IDX
FROM ALISTLMS.FILE_DETAIL
WHERE FILE_UUID = #{fileUuid}
LIMIT 1
)
AND LLFD.DEL_YN = 'N'
SELECT LLFD.FILE_MASTER_IDX
, COUNT(*) AS total_count
, SUM(CASE WHEN LLFD.STATUS = 3 THEN 1 ELSE 0 END) AS done_count
, SUM(CASE WHEN LLFD.STATUS IN (4,5) THEN 1 ELSE 0 END) AS fail_count
FROM ALISTLMS.FILE_DETAIL LLFD
WHERE LLFD.FILE_MASTER_IDX = (
SELECT FILE_MASTER_IDX
FROM ALISTLMS.FILE_DETAIL
WHERE FILE_UUID = #{fileUuid}
LIMIT 1
)
AND LLFD.DEL_YN = 'N'
) A ON A.FILE_MASTER_IDX = LFM.FILE_MASTER_IDX
SET LFM.TOTAL_COUNT = A.total_count
, LFM.DONE_COUNT = A.done_count
, LFM.FAIL_COUNT = A.fail_count
, LFM.STATUS = CASE
WHEN A.done_count = A.total_count THEN 3
WHEN A.done_count = 0 AND A.fail_count = A.total_count THEN 4
WHEN A.done_count > 0 AND A.fail_count > 0 THEN 5
ELSE 1
END
WHEN A.done_count = A.total_count THEN 3
WHEN A.done_count = 0 AND A.fail_count = A.total_count THEN 4
WHEN A.done_count > 0 AND A.fail_count > 0 THEN 5
ELSE 1
END
, LFM.UPDATE_DATE = NOW()
</update>
<update id="updateTusUploadIdIfNull" parameterType="com.alist.api.modules.tusFile.dto.FileUploadMetaDto">
/*FileMapper.updateTusUploadIdIfNull*/
<update id="updateTusFileUploadIdIfNull" parameterType="com.alist.api.modules.tusFile.dto.TusFileUploadMetaDto">
/*TusFileMapper.updateTusFileUploadIdIfNull*/
UPDATE ALISTLMS.FILE_DETAIL
SET TUS_UPLOAD_ID = #{tusUploadId}
, UPDATE_DATE = NOW()
WHERE FILE_UUID = #{fileUuid}
AND DEL_YN = 'N'
AND TUS_UPLOAD_ID IS NULL
AND DEL_YN = 'N'
AND TUS_UPLOAD_ID IS NULL
</update>
<update id="updateFileDetailCanceledByFileUuid">
/*FileMapper.updateFileDetailCanceledByFileUuid*/
/*TusFileMapper.updateFileDetailCanceledByFileUuid*/
UPDATE ALISTLMS.FILE_DETAIL
SET STATUS = 5
, UPDATE_DATE = NOW()
WHERE FILE_UUID = #{fileUuid}
AND DEL_YN = 'N'
AND STATUS IN (0,1,2)
</update>
<update id="updateFileDetailCanceledByFileUuidAndUserTokenIdx">
/*FileMapper.updateFileDetailCanceledByFileUuidAndUserTokenIdx*/
UPDATE ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFD.CREATE_MEMBER
SET LFD.STATUS = 5
, LFD.UPDATE_DATE = NOW()
WHERE LFD.DEL_YN = 'N'
AND LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.STATUS IN (0,1,2)
AND DEL_YN = 'N'
AND STATUS IN (0,1,2)
</update>
<select id="selectFileDetailCountByFileUuidUserToKenIdx" resultType="java.lang.Integer">
/*FileMapper.selectFileDetailCountByFileUuidUserToKenIdx*/
SELECT COUNT(*)
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFD.CREATE_MEMBER
WHERE LFD.DEL_YN = 'N'
AND LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.STATUS IN (0,1,2,3)
AND (#{sizeBytes} IS NULL OR LFD.SIZE_BYTES = #{sizeBytes})
AND (#{originName} IS NULL OR #{originName} = '' OR LFD.ORIGIN_NAME = #{originName})
AND (
#{contentType} IS NULL OR #{contentType} = ''
OR LFD.CONTENT_TYPE IS NULL OR LFD.CONTENT_TYPE = ''
OR LOWER(LFD.CONTENT_TYPE) = LOWER(#{contentType})
)
</select>
<select id="selectFileUploadMetaByFileUuid" resultType="com.alist.api.modules.tusFile.dto.FileUploadMetaDto">
/*FileMapper.selectFileUploadMetaByFileUuid*/
SELECT FILE_UUID, ORIGIN_NAME, SIZE_BYTES, CONTENT_TYPE, TUS_UPLOAD_ID, STATUS
FROM ALISTLMS.FILE_DETAIL
WHERE FILE_UUID = #{fileUuid}
AND DEL_YN = 'N'
LIMIT 1
</select>
<select id="selectFileDetailCountByFileUuidAndUserToKenIdx" resultType="java.lang.Integer">
/*FileMapper.selectFileDetailCountByFileUuidAndUserToKenIdx*/
SELECT COUNT(*)
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFD.CREATE_MEMBER
WHERE LFD.DEL_YN = 'N'
AND LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.STATUS IN (0,1,2)
</select>
<select id="selectUploadProgressByFileUuid" resultType="com.alist.api.modules.tusFile.dto.UploadCancelDto">
/*FileMapper.selectUploadProgressByFileUuid*/
SELECT LFD.FILE_UUID
, COALESCE(LFD.UPLOADED_BYTES, 0) AS UPLOADED_BYTES
, COALESCE(LFD.SIZE_BYTES, 0) AS SIZE_BYTES
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFD.CREATE_MEMBER
WHERE LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
LIMIT 1
</select>
<select id="selectMoveTaskByFileUuid" resultType="com.alist.api.modules.tusFile.dto.FileMoveTaskDto">
/*FileMapper.selectMoveTaskByFileUuid*/
SELECT LFD.FILE_DETAIL_IDX
, LFD.FILE_MASTER_IDX
, LFD.FILE_UUID
, LFD.TUS_UPLOAD_ID
, LFD.ORIGIN_NAME
, LFD.CONTENT_TYPE
, LFD.SIZE_BYTES
, LFD.UPLOADED_BYTES
, LFM.FOLDER_PATH
, LFD.SAVE_NAME
, LFD.SAVE_PATH
, LFD.`EXT`
, LFD.MOVE_YN
, LFD.MOVE_TRY_COUNT
, LFD.MOVE_LAST_ERROR
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM
ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
WHERE LFD.FILE_UUID = #{fileUuid}
AND LFD.DEL_YN = 'N'
LIMIT 1
</select>
<select id="selectFileDownloadListByFileMasterIdx" resultType="com.alist.api.modules.tusFile.dto.FileDownloadItemDto">
/*FileMapper.selectFileDownloadListByFileMasterIdx*/
SELECT LFD.FILE_DETAIL_IDX
, LFD.FILE_MASTER_IDX
, LFD.FILE_UUID
, LFD.ORIGIN_NAME
, LFD.CONTENT_TYPE
, LFD.SIZE_BYTES
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM
ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFM.USER_IDX
WHERE LFD.FILE_MASTER_IDX = #{fileMasterIdx}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFD.MOVE_YN = 'Y'
ORDER BY LFD.FILE_SEQ ASC, LFD.FILE_DETAIL_IDX ASC
</select>
<select id="selectFileDetailByFileUuid" resultType="com.alist.api.modules.tusFile.dto.FileDownloadDto">
/*FileMapper.selectFileDetailByFileUuid*/
SELECT LFD.FILE_DETAIL_IDX
, LFD.FILE_MASTER_IDX
, LFD.FILE_UUID
, LFD.ORIGIN_NAME
, LFD.CONTENT_TYPE
, LFD.SIZE_BYTES
, LFD.SAVE_PATH
, LTUT.user_idx
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM
ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFM.USER_IDX
WHERE LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFD.MOVE_YN = 'Y'
LIMIT 1
</select>
<update id="updateFileMoveSuccess" parameterType="com.alist.api.modules.tusFile.dto.FileMoveTaskDto">
/*FileMapper.updateFileMoveSuccess*/
<update id="updateTusFileMoveSuccess" parameterType="com.alist.api.modules.tusFile.dto.TusFileMoveTaskDto">
/*TusFileMapper.updateTusFileMoveSuccess*/
UPDATE ALISTLMS.FILE_DETAIL
SET SAVE_NAME = #{saveName}
, SAVE_PATH = #{savePath}
@@ -285,84 +286,71 @@
, MOVE_LOCK_OWNER = NULL
, UPDATE_DATE = NOW()
WHERE FILE_DETAIL_IDX = #{fileDetailIdx}
AND DEL_YN = 'N'
AND DEL_YN = 'N'
</update>
<update id="updateFileMovePendingByFileUuid" parameterType="com.alist.api.modules.tusFile.dto.FileMoveTaskDto">
/*FileMapper.updateFileMovePendingByFileUuid*/
<update id="updateTusFileMovePending" parameterType="com.alist.api.modules.tusFile.dto.TusFileMoveTaskDto">
/*TusFileMapper.updateTusFileMovePending*/
UPDATE ALISTLMS.FILE_DETAIL
SET MOVE_YN = 'N'
, MOVE_TRY_COUNT = COALESCE(MOVE_TRY_COUNT, 0) + 1
, MOVE_LAST_ERROR = #{moveLastError}
, UPDATE_DATE = NOW()
WHERE FILE_UUID = #{fileUuid}
AND DEL_YN = 'N'
AND STATUS = 3
AND DEL_YN = 'N'
AND STATUS = 3
</update>
<select id="selectFileDeleteTargetCount" resultType="java.lang.Integer">
/*FileMapper.selectFileDeleteTargetCount*/
SELECT COUNT(*)
FROM ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM
ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFM.USER_IDX
WHERE LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFM.STATUS = 3
</select>
<select id="selectUserIdxByUserTokenIdx" resultType="java.lang.Integer">
/*FileMapper.selectUserIdxByUserTokenIdx*/
SELECT user_idx
FROM test_user_token
WHERE user_token_idx = #{userTokenIdx}
</select>
<update id="updateFileDetailDeleteByFileUuid">
/*FileMapper.updateFileDetailDeleteByFileUuid*/
<update id="updateTusFileDetailDelete">
/*TusFileMapper.updateTusFileDetailDelete*/
UPDATE ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.FILE_MASTER LFM
ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFM.USER_IDX
INNER JOIN ALISTLMS.FILE_MASTER LFM ON LFM.FILE_MASTER_IDX = LFD.FILE_MASTER_IDX
AND LFM.DEL_YN = 'N'
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFM.USER_IDX
SET LFD.DEL_YN = 'Y'
, LFD.UPDATE_DATE = NOW()
WHERE LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFM.STATUS = 3
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.DEL_YN = 'N'
AND LFD.STATUS = 3
AND LFM.STATUS = 3
</update>
<update id="updateFileMasterDeleteByFileUuid">
/*FileMapper.updateFileMasterDeleteByFileUuid*/
<update id="updateTusFileMasterDelete">
/*TusFileMapper.updateTusFileMasterDelete*/
UPDATE ALISTLMS.FILE_MASTER LFM
INNER JOIN (
SELECT LFD.FILE_MASTER_IDX
FROM ALISTLMS.FILE_DETAIL LFD
WHERE LFD.FILE_UUID = #{fileUuid}
AND LFD.DEL_YN = 'Y'
AND LFD.DEL_YN = 'Y'
LIMIT 1
) TARGET
ON TARGET.FILE_MASTER_IDX = LFM.FILE_MASTER_IDX
) TARGET ON TARGET.FILE_MASTER_IDX = LFM.FILE_MASTER_IDX
INNER JOIN ALISTLMS.test_user_token LTUT
ON LTUT.user_idx = LFM.USER_IDX
ON LTUT.user_idx = LFM.USER_IDX
SET LFM.DEL_YN = 'Y'
, LFM.UPDATE_DATE = NOW()
WHERE LTUT.user_token_idx = #{userTokenIdx}
AND LFM.DEL_YN = 'N'
AND LFM.STATUS = 3
AND NOT EXISTS (
SELECT 1
FROM ALISTLMS.FILE_DETAIL CHK
WHERE CHK.FILE_MASTER_IDX = LFM.FILE_MASTER_IDX
AND CHK.DEL_YN = 'N'
)
AND LFM.DEL_YN = 'N'
AND LFM.STATUS = 3
AND NOT EXISTS (
SELECT 1
FROM ALISTLMS.FILE_DETAIL CHK
WHERE CHK.FILE_MASTER_IDX = LFM.FILE_MASTER_IDX
AND CHK.DEL_YN = 'N'
)
</update>
<update id="updateTusFileUploadCancel">
/*TusFileMapper.updateTusFileUploadCancel*/
UPDATE ALISTLMS.FILE_DETAIL LFD
INNER JOIN ALISTLMS.test_user_token LTUT ON LTUT.user_idx = LFD.CREATE_MEMBER
SET LFD.STATUS = 5
, LFD.UPDATE_DATE = NOW()
WHERE LFD.DEL_YN = 'N'
AND LFD.FILE_UUID = #{fileUuid}
AND LTUT.user_token_idx = #{userTokenIdx}
AND LFD.STATUS IN (0,1,2)
</update>
</mapper>