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