[파일 업로드] 파일업로드 뷰 & 다운로드
- 파일 뷰 & 다운로드 작업 커밋
This commit is contained in:
@@ -1,9 +1,6 @@
|
|||||||
package com.alist.api.common.modules.file;
|
package com.alist.api.common.modules.file;
|
||||||
|
|
||||||
import com.alist.api.common.modules.file.dto.FileUploadDto;
|
import com.alist.api.common.modules.file.dto.*;
|
||||||
import com.alist.api.common.modules.file.dto.UploadCancelDto;
|
|
||||||
import com.alist.api.common.modules.file.dto.UploadStatusDto;
|
|
||||||
import com.alist.api.common.modules.file.dto.UploadTokenDto;
|
|
||||||
import com.alist.api.common.modules.file.form.FileUploadForm;
|
import com.alist.api.common.modules.file.form.FileUploadForm;
|
||||||
import com.alist.api.common.modules.file.form.TusHookForm;
|
import com.alist.api.common.modules.file.form.TusHookForm;
|
||||||
import com.alist.api.common.modules.file.form.UploadCancelForm;
|
import com.alist.api.common.modules.file.form.UploadCancelForm;
|
||||||
@@ -17,13 +14,19 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ContentDisposition;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Tag(
|
@Tag(
|
||||||
name = "99. TUS 업로드",
|
name = "99. TUS 업로드"
|
||||||
description = "TUS 기반 파일 업로드 초기화, 권한 검증, 상태 조회, 훅 처리, 취소 API"
|
, description = "TUS 기반 파일 업로드 초기화, 권한 검증, 상태 조회, 훅 처리, 취소 API"
|
||||||
)
|
)
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/files")
|
@RequestMapping("/files")
|
||||||
@@ -35,8 +38,8 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "파일 업로드 초기화",
|
summary = "파일 업로드 초기화"
|
||||||
description = "업로드 대상 파일 정보를 DB에 등록하고 fileUuid/uploadToken을 발급합니다."
|
, description = "업로드 대상 파일 정보를 DB에 등록하고 fileUuid/uploadToken을 발급합니다."
|
||||||
)
|
)
|
||||||
@PostMapping("/uploadInit")
|
@PostMapping("/uploadInit")
|
||||||
public ResponseEntity<ApiResponse<FileUploadDto>> uploadInit(
|
public ResponseEntity<ApiResponse<FileUploadDto>> uploadInit(
|
||||||
@@ -62,8 +65,8 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "업로드 권한 검증",
|
summary = "업로드 권한 검증"
|
||||||
description = "Bearer 업로드 토큰과 fileUuid(및 TUS 메타데이터)를 검증해 업로드 가능 여부를 확인합니다."
|
, description = "Bearer 업로드 토큰과 fileUuid(및 TUS 메타데이터)를 검증해 업로드 가능 여부를 확인합니다."
|
||||||
)
|
)
|
||||||
@GetMapping("/uploadAuth")
|
@GetMapping("/uploadAuth")
|
||||||
public ResponseEntity<ApiResponse<String>> uploadAuth(
|
public ResponseEntity<ApiResponse<String>> uploadAuth(
|
||||||
@@ -97,8 +100,8 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "업로드 상태 조회",
|
summary = "업로드 상태 조회"
|
||||||
description = "fileUuid 기준으로 Redis/DB 업로드 상태(진행률, 상태값)를 조회합니다."
|
, description = "fileUuid 기준으로 Redis/DB 업로드 상태(진행률, 상태값)를 조회합니다."
|
||||||
)
|
)
|
||||||
@PostMapping("/uploadStatus")
|
@PostMapping("/uploadStatus")
|
||||||
public ResponseEntity<ApiResponse<UploadStatusDto>> uploadStatus(
|
public ResponseEntity<ApiResponse<UploadStatusDto>> uploadStatus(
|
||||||
@@ -112,8 +115,8 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "TUS 훅 수신",
|
summary = "TUS 훅 수신"
|
||||||
description = "tusd에서 전달되는 pre-create/post-receive/post-finish/post-terminate 이벤트를 반영합니다."
|
, description = "tusd에서 전달되는 pre-create/post-receive/post-finish/post-terminate 이벤트를 반영합니다."
|
||||||
)
|
)
|
||||||
@PostMapping("/tusHook")
|
@PostMapping("/tusHook")
|
||||||
public ResponseEntity<ApiResponse<String>> tusHook(@RequestBody TusHookForm tusHookForm) {
|
public ResponseEntity<ApiResponse<String>> tusHook(@RequestBody TusHookForm tusHookForm) {
|
||||||
@@ -127,8 +130,8 @@ public class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "업로드 취소",
|
summary = "업로드 취소"
|
||||||
description = "현재 로그인 사용자의 fileUuid 업로드를 취소(CANCELED) 처리합니다."
|
, description = "현재 로그인 사용자의 fileUuid 업로드를 취소(CANCELED) 처리합니다."
|
||||||
)
|
)
|
||||||
@PostMapping("/uploadCancel")
|
@PostMapping("/uploadCancel")
|
||||||
public ResponseEntity<ApiResponse<String>> uploadCancel(
|
public ResponseEntity<ApiResponse<String>> uploadCancel(
|
||||||
@@ -155,4 +158,104 @@ public class FileController {
|
|||||||
|
|
||||||
return ApiResponse.entity("", ApiResponseCode.CODE_200);
|
return ApiResponse.entity("", ApiResponseCode.CODE_200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "파일 목록 조회"
|
||||||
|
, description = "FILE_MASTER_IDX 기준으로 완료된 파일 목록을 조회합니다.")
|
||||||
|
@GetMapping("/list/{fileMasterIdx}")
|
||||||
|
public ResponseEntity<ApiResponse<FileDownloadListDto>> fileListByFileMasterIdx(
|
||||||
|
@PathVariable Long fileMasterIdx
|
||||||
|
) {
|
||||||
|
|
||||||
|
FileDownloadDto fileDownloadDto = new FileDownloadDto();
|
||||||
|
fileDownloadDto.setFileMasterIdx(fileMasterIdx);
|
||||||
|
|
||||||
|
FileDownloadListDto fileDownloadList = fileService.selectFileDownloadListByFileMasterIdx(fileDownloadDto);
|
||||||
|
|
||||||
|
if (fileDownloadList.getResultCode() == 401) {
|
||||||
|
return ApiResponse.entity(fileDownloadList, ApiResponseCode.CODE_401);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.entity(fileDownloadList, ApiResponseCode.CODE_2001, "파일");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "파일 보기"
|
||||||
|
, description = "권한 확인 후 Nginx X-Accel-Redirect로 파일을 inline 조회합니다.")
|
||||||
|
@GetMapping("/view/{fileUuid}")
|
||||||
|
public ResponseEntity<Void> fileView(
|
||||||
|
@PathVariable String fileUuid
|
||||||
|
, HttpServletRequest request
|
||||||
|
) {
|
||||||
|
FileDownloadDto fileDownloadDto = new FileDownloadDto();
|
||||||
|
fileDownloadDto.setFileUuid(fileUuid);
|
||||||
|
fileDownloadDto.setEventType("VIEW");
|
||||||
|
fileDownloadDto.setClientIp(request.getRemoteAddr());
|
||||||
|
fileDownloadDto.setUserAgent(request.getHeader("User-Agent"));
|
||||||
|
fileDownloadDto.setReferer(request.getHeader("Referer"));
|
||||||
|
|
||||||
|
FileDownloadDto target = fileService.selectFileViewOrDownload(fileDownloadDto);
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileService.isRedirectMode()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FOUND)
|
||||||
|
.location(URI.create(fileService.buildPublicViewUrl(target.getSavePath())))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("X-Accel-Redirect", fileService.buildAccelRedirectPath(target.getSavePath()));
|
||||||
|
headers.add(HttpHeaders.CONTENT_TYPE,
|
||||||
|
target.getContentType() == null ? "application/octet-stream" : target.getContentType());
|
||||||
|
headers.add(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
ContentDisposition.inline()
|
||||||
|
.filename(target.getOriginName(), StandardCharsets.UTF_8)
|
||||||
|
.build()
|
||||||
|
.toString());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "파일 다운로드"
|
||||||
|
, description = "권한 확인 후 파일 다운로드 로그를 남기고 redirect 또는 Nginx X-Accel-Redirect로 파일 다운로드 합니다.")
|
||||||
|
@GetMapping("/download/{fileUuid}")
|
||||||
|
public ResponseEntity<Void> fileDownload(
|
||||||
|
@PathVariable String fileUuid
|
||||||
|
, HttpServletRequest request
|
||||||
|
) {
|
||||||
|
FileDownloadDto fileDownloadDto = new FileDownloadDto();
|
||||||
|
fileDownloadDto.setFileUuid(fileUuid);
|
||||||
|
fileDownloadDto.setEventType("DOWNLOAD");
|
||||||
|
fileDownloadDto.setClientIp(request.getRemoteAddr());
|
||||||
|
fileDownloadDto.setUserAgent(request.getHeader("User-Agent"));
|
||||||
|
fileDownloadDto.setReferer(request.getHeader("Referer"));
|
||||||
|
|
||||||
|
FileDownloadDto target = fileService.selectFileViewOrDownload(fileDownloadDto);
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileService.isRedirectMode()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FOUND)
|
||||||
|
.location(URI.create(fileService.buildPublicDownloadUrl(target.getSavePath())))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("X-Accel-Redirect", fileService.buildAccelRedirectPath(target.getSavePath()));
|
||||||
|
headers.add(HttpHeaders.CONTENT_TYPE,
|
||||||
|
target.getContentType() == null ? "application/octet-stream" : target.getContentType());
|
||||||
|
headers.add(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
ContentDisposition.attachment()
|
||||||
|
.filename(target.getOriginName(), StandardCharsets.UTF_8)
|
||||||
|
.build()
|
||||||
|
.toString());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.alist.api.common.modules.file.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class FileDownloadDto {
|
||||||
|
private Long fileDetailIdx;
|
||||||
|
private Long fileMasterIdx;
|
||||||
|
private String fileUuid;
|
||||||
|
private String originName;
|
||||||
|
private String contentType;
|
||||||
|
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
|
||||||
|
private Integer resultCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.alist.api.common.modules.file.dto;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class FileDownloadItemDto {
|
||||||
|
private Long fileDetailIdx;
|
||||||
|
private Long fileMasterIdx;
|
||||||
|
private String fileUuid;
|
||||||
|
private String originName;
|
||||||
|
private String contentType;
|
||||||
|
private Long sizeBytes;
|
||||||
|
private String viewUrl;
|
||||||
|
private String downloadUrl;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.alist.api.common.modules.file.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class FileDownloadListDto {
|
||||||
|
private Long fileMasterIdx;
|
||||||
|
private List<FileDownloadItemDto> itemList;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private Integer resultCode;
|
||||||
|
}
|
||||||
@@ -13,6 +13,6 @@ public class TusHookDto {
|
|||||||
private String fileUuid;
|
private String fileUuid;
|
||||||
private String filename;
|
private String filename;
|
||||||
private String filetype;
|
private String filetype;
|
||||||
private long uploadedBytes;
|
private Long uploadedBytes;
|
||||||
private int status;
|
private Integer status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.alist.api.common.modules.file.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class UploadStartDto {
|
|
||||||
private String fileMasterKey;
|
|
||||||
private String tusEndpoint;
|
|
||||||
private String uploadToken;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private String originName;
|
|
||||||
@JsonIgnore
|
|
||||||
private String contentType;
|
|
||||||
@JsonIgnore
|
|
||||||
private Long sizeBytes;
|
|
||||||
@JsonIgnore
|
|
||||||
private String fileCategory;
|
|
||||||
@JsonIgnore
|
|
||||||
private String folderPath;
|
|
||||||
@JsonIgnore
|
|
||||||
private Integer userIdx;
|
|
||||||
@JsonIgnore
|
|
||||||
private int status;
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,8 @@ import lombok.Setter;
|
|||||||
public class UploadStatusDto {
|
public class UploadStatusDto {
|
||||||
private String fileUuid;
|
private String fileUuid;
|
||||||
private String status; // PENDING, UPLOADING, DONE, FAILED
|
private String status; // PENDING, UPLOADING, DONE, FAILED
|
||||||
private long uploadedBytes;
|
private Long uploadedBytes;
|
||||||
private long totalBytes;
|
private Long totalBytes;
|
||||||
private int percent;
|
private Integer percent;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ public class UploadTokenDto {
|
|||||||
private String contentType;
|
private String contentType;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private int resultCode;
|
private Integer resultCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.alist.api.common.modules.file.form;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import jakarta.validation.constraints.Positive;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class UploadCompleteForm {
|
|
||||||
@NotBlank
|
|
||||||
String fileMastryKey;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
String tusUploadUrl;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
String originName;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
String saveName;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
String savePath;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
String contentType;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Positive
|
|
||||||
Long sizeBytes;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package com.alist.api.common.modules.file.form;
|
|
||||||
|
|
||||||
import com.alist.api.common.modules.file.dto.UploadStartDto;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import jakarta.validation.constraints.Positive;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class UploadStartForm {
|
|
||||||
@NotBlank
|
|
||||||
private String originName;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
private String contentType;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Positive
|
|
||||||
private Long sizeBytes;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
private String fileCategory;
|
|
||||||
|
|
||||||
private String folderPath;
|
|
||||||
|
|
||||||
public UploadStartDto uploadStartDto() {
|
|
||||||
UploadStartDto uploadStartDto = new UploadStartDto();
|
|
||||||
|
|
||||||
uploadStartDto.setOriginName(this.originName);
|
|
||||||
uploadStartDto.setContentType(this.contentType);
|
|
||||||
uploadStartDto.setSizeBytes(this.sizeBytes);
|
|
||||||
uploadStartDto.setFileCategory(this.fileCategory);
|
|
||||||
uploadStartDto.setFolderPath(this.folderPath);
|
|
||||||
|
|
||||||
return uploadStartDto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.alist.api.common.modules.file.form;
|
|
||||||
|
|
||||||
import com.alist.api.common.modules.file.dto.UploadTokenDto;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
@Getter
|
|
||||||
public class UploadTokenForm {
|
|
||||||
private String fileUuid;
|
|
||||||
private String uploadToken;
|
|
||||||
|
|
||||||
public UploadTokenDto uploadTokenDto() {
|
|
||||||
UploadTokenDto uploadTokenDto = new UploadTokenDto();
|
|
||||||
uploadTokenDto.setFileUuid(this.fileUuid);
|
|
||||||
uploadTokenDto.setUploadToken(this.uploadToken);
|
|
||||||
|
|
||||||
return uploadTokenDto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,12 +3,10 @@ package com.alist.api.common.modules.file.mapper;
|
|||||||
import com.alist.api.common.modules.file.dto.*;
|
import com.alist.api.common.modules.file.dto.*;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface FileMapper {
|
public interface FileMapper {
|
||||||
void insertFileMasterUpload(UploadStartDto uploadStartDto);
|
|
||||||
|
|
||||||
int selectFileMasterCountByFileMasterKeyAndDelYnAndStatus(UploadTokenDto uploadTokenDto);
|
|
||||||
|
|
||||||
void insertFileMasterInit(FileUploadDto fileUploadDto);
|
void insertFileMasterInit(FileUploadDto fileUploadDto);
|
||||||
|
|
||||||
void insertFileDetailInit(FileUploadItemDto item);
|
void insertFileDetailInit(FileUploadItemDto item);
|
||||||
@@ -38,4 +36,10 @@ public interface FileMapper {
|
|||||||
int updateFileMoveSuccess(FileMoveTaskDto task);
|
int updateFileMoveSuccess(FileMoveTaskDto task);
|
||||||
|
|
||||||
int updateFileMovePendingByFileUuid(FileMoveTaskDto task);
|
int updateFileMovePendingByFileUuid(FileMoveTaskDto task);
|
||||||
|
|
||||||
|
List<FileDownloadItemDto> selectFileDownloadListByFileMasterIdx(FileDownloadDto fileDownloadDto);
|
||||||
|
|
||||||
|
FileDownloadDto selectFileDetailByFileUuid(FileDownloadDto fileDownloadDto);
|
||||||
|
|
||||||
|
void insertFileDownloadEventLog(FileDownloadDto fileDetailInfo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ 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;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ 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.HexFormat;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -43,6 +46,12 @@ public class FileService {
|
|||||||
@Value("${file.upload.final-root}")
|
@Value("${file.upload.final-root}")
|
||||||
private String uploadFinalRoot;
|
private String uploadFinalRoot;
|
||||||
|
|
||||||
|
@Value("${file.download.redirect-prefix:/protected-files}")
|
||||||
|
private String fileDownloadRedirectPrefix;
|
||||||
|
|
||||||
|
@Value("${file.download.mode:accel}")
|
||||||
|
private String fileDownloadMode;
|
||||||
|
|
||||||
private final FileMapper fileMapper;
|
private final FileMapper fileMapper;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final StringRedisTemplate stringRedisTemplate;
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
@@ -259,7 +268,7 @@ public class FileService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tusHookDto.setUploadedBytes(offset);
|
//tusHookDto.setUploadedBytes(offset); // 전체 사이즈 업데이트 삭제
|
||||||
|
|
||||||
Integer currentStatus = fileUploadMetaInfo.getStatus();
|
Integer currentStatus = fileUploadMetaInfo.getStatus();
|
||||||
if (currentStatus != null && (currentStatus == 3 || currentStatus == 5)) {
|
if (currentStatus != null && (currentStatus == 3 || currentStatus == 5)) {
|
||||||
@@ -276,6 +285,7 @@ public class FileService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tusHookDto.setUploadedBytes(null);
|
||||||
tusHookDto.setStatus(1); // UPLOADING
|
tusHookDto.setStatus(1); // UPLOADING
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -285,12 +295,13 @@ public class FileService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tusHookDto.setUploadedBytes(null);
|
||||||
tusHookDto.setStatus(1); // UPLOADING
|
tusHookDto.setStatus(1); // UPLOADING
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "post-finish":
|
case "post-finish":
|
||||||
tusHookDto.setStatus(3); // DONE
|
|
||||||
tusHookDto.setUploadedBytes(size);
|
tusHookDto.setUploadedBytes(size);
|
||||||
|
tusHookDto.setStatus(3); // DONE
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "post-terminate":
|
case "post-terminate":
|
||||||
@@ -298,6 +309,7 @@ public class FileService {
|
|||||||
if (currentStatus != null && currentStatus == 3) {
|
if (currentStatus != null && currentStatus == 3) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
tusHookDto.setUploadedBytes(offset);
|
||||||
tusHookDto.setStatus(5); // CANCELED
|
tusHookDto.setStatus(5); // CANCELED
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -309,7 +321,11 @@ public class FileService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fileMapper.updateFileDetailStatus(tusHookDto);
|
boolean shouldUpdateDetail = !"post-receive".equals(type);
|
||||||
|
|
||||||
|
if (shouldUpdateDetail) {
|
||||||
|
fileMapper.updateFileDetailStatus(tusHookDto);
|
||||||
|
}
|
||||||
|
|
||||||
if ("post-finish".equals(type) || "post-terminate".equals(type)) {
|
if ("post-finish".equals(type) || "post-terminate".equals(type)) {
|
||||||
fileMapper.updateFileMasterAggregateByFileUuid(tusHookDto.getFileUuid());
|
fileMapper.updateFileMasterAggregateByFileUuid(tusHookDto.getFileUuid());
|
||||||
@@ -319,7 +335,11 @@ public class FileService {
|
|||||||
try {
|
try {
|
||||||
tryMoveNowByFileUuid(tusHookDto.getFileUuid());
|
tryMoveNowByFileUuid(tusHookDto.getFileUuid());
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
try { Thread.sleep(200); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
|
try {
|
||||||
|
Thread.sleep(200);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
tryMoveNowByFileUuid(tusHookDto.getFileUuid()); // 1회 재시도
|
tryMoveNowByFileUuid(tusHookDto.getFileUuid()); // 1회 재시도
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -570,4 +590,109 @@ public class FileService {
|
|||||||
saveCanceledStatusRedis(uploadCancelDto.getFileUuid(), uploaded, total); // 필요하면 기존 bytes 조회해서 넣어도 됨
|
saveCanceledStatusRedis(uploadCancelDto.getFileUuid(), uploaded, total); // 필요하면 기존 bytes 조회해서 넣어도 됨
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer getLoginUserTokenIdx() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication == null || authentication.getPrincipal() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(authentication.getPrincipal().toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public FileDownloadListDto selectFileDownloadListByFileMasterIdx(FileDownloadDto fileDownloadDto) {
|
||||||
|
FileDownloadListDto fileDownloadList = new FileDownloadListDto();
|
||||||
|
|
||||||
|
Integer userTokenIdx = getLoginUserTokenIdx();
|
||||||
|
if (userTokenIdx == null) {
|
||||||
|
fileDownloadList.setResultCode(401);
|
||||||
|
return fileDownloadList;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDownloadDto.setUserTokenIdx(userTokenIdx);
|
||||||
|
|
||||||
|
fileDownloadList.setFileMasterIdx(fileDownloadDto.getFileMasterIdx());
|
||||||
|
|
||||||
|
List<FileDownloadItemDto> itemList = fileMapper.selectFileDownloadListByFileMasterIdx(fileDownloadDto);
|
||||||
|
|
||||||
|
for (FileDownloadItemDto item : itemList) {
|
||||||
|
item.setViewUrl("/files/view/" + item.getFileUuid());
|
||||||
|
item.setDownloadUrl("/files/download/" + item.getFileUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDownloadList.setItemList(itemList);
|
||||||
|
fileDownloadList.setResultCode(200);
|
||||||
|
|
||||||
|
return fileDownloadList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileDownloadDto selectFileViewOrDownload(FileDownloadDto fileDownloadDto) {
|
||||||
|
Integer userTokenIdx = getLoginUserTokenIdx();
|
||||||
|
if (userTokenIdx == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDownloadDto.setUserTokenIdx(userTokenIdx);
|
||||||
|
|
||||||
|
FileDownloadDto fileDetailInfo = fileMapper.selectFileDetailByFileUuid(fileDownloadDto);
|
||||||
|
|
||||||
|
if (fileDetailInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDetailInfo.setUserTokenIdx(fileDownloadDto.getUserTokenIdx());
|
||||||
|
fileDetailInfo.setEventType(fileDownloadDto.getEventType());
|
||||||
|
fileDetailInfo.setClientIp(fileDownloadDto.getClientIp());
|
||||||
|
fileDetailInfo.setUserAgent(fileDownloadDto.getUserAgent());
|
||||||
|
fileDetailInfo.setReferer(fileDownloadDto.getReferer());
|
||||||
|
|
||||||
|
fileMapper.insertFileDownloadEventLog(fileDetailInfo);
|
||||||
|
return fileDetailInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildAccelRedirectPath(String savePath) {
|
||||||
|
if (savePath == null || savePath.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("savePath is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
String prefix = (fileDownloadRedirectPrefix == null || fileDownloadRedirectPrefix.isBlank())
|
||||||
|
? "/protected-files"
|
||||||
|
: fileDownloadRedirectPrefix.trim();
|
||||||
|
|
||||||
|
if (!prefix.startsWith("/")) {
|
||||||
|
prefix = "/" + prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix + savePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRedirectMode() {
|
||||||
|
return "redirect".equalsIgnoreCase(fileDownloadMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildPublicViewUrl(String savePath) {
|
||||||
|
return buildPublicTestUrl("/public-test-view", savePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildPublicDownloadUrl(String savePath) {
|
||||||
|
return buildPublicTestUrl("/public-test-download", savePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPublicTestUrl(String prefix, String savePath) {
|
||||||
|
if (savePath == null || savePath.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("savePath is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
String baseUrl = publicBaseUrl == null ? "" : publicBaseUrl.trim();
|
||||||
|
if (baseUrl.endsWith("/")) {
|
||||||
|
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl + prefix + savePath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ file:
|
|||||||
interrupt-seconds: 30
|
interrupt-seconds: 30
|
||||||
auth-cache:
|
auth-cache:
|
||||||
ttl-seconds: 20
|
ttl-seconds: 20
|
||||||
|
download:
|
||||||
|
mode: redirect
|
||||||
|
redirect-prefix: /public-test-files
|
||||||
|
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ file:
|
|||||||
interrupt-seconds: 30
|
interrupt-seconds: 30
|
||||||
auth-cache:
|
auth-cache:
|
||||||
ttl-seconds: 20
|
ttl-seconds: 20
|
||||||
|
download:
|
||||||
|
mode: accel
|
||||||
|
redirect-prefix: /protected-files
|
||||||
|
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
|
|||||||
@@ -21,15 +21,6 @@
|
|||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<insert id="insertFileMasterUpload">
|
|
||||||
/*FileMapper.insertFileMasterUpload*/
|
|
||||||
INSERT INTO ALISTLMS.FILE_MASTER2 (
|
|
||||||
FILE_MASTER_KEY, USER_IDX, FILE_CATEGORY, FOLDER_PATH, STATUS, CREATE_MEMBER, UPDATE_MEMBER
|
|
||||||
) values (
|
|
||||||
#{fileMasterKey}, #{userIdx}, #{fileCategory}, #{folderPath}, #{status}, #{userIdx}, #{userIdx}
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<insert id="insertFileUploadEventLog">
|
<insert id="insertFileUploadEventLog">
|
||||||
/*FileMapper.insertFileUploadEventLog*/
|
/*FileMapper.insertFileUploadEventLog*/
|
||||||
INSERT INTO ALISTLMS.FILE_UPLOAD_EVENT_LOG (
|
INSERT INTO ALISTLMS.FILE_UPLOAD_EVENT_LOG (
|
||||||
@@ -47,6 +38,34 @@
|
|||||||
WHERE LFD.FILE_UUID = #{fileUuid}
|
WHERE LFD.FILE_UUID = #{fileUuid}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</insert>
|
</insert>
|
||||||
|
<insert id="insertFileDownloadEventLog">
|
||||||
|
/*FileMapper.insertFileDownloadEventLog*/
|
||||||
|
INSERT INTO ALISTLMS.FILE_DOWNLOAD_EVENT_LOG (
|
||||||
|
FILE_MASTER_IDX
|
||||||
|
, FILE_DETAIL_IDX
|
||||||
|
, FILE_UUID
|
||||||
|
, EVENT_TYPE
|
||||||
|
, USER_IDX
|
||||||
|
, USER_TOKEN_IDX
|
||||||
|
, CLIENT_IP
|
||||||
|
, USER_AGENT
|
||||||
|
, REFERER
|
||||||
|
, DEL_YN
|
||||||
|
, CREATE_MEMBER
|
||||||
|
) VALUES (
|
||||||
|
#{fileMasterIdx}
|
||||||
|
, #{fileDetailIdx}
|
||||||
|
, #{fileUuid}
|
||||||
|
, #{eventType}
|
||||||
|
, #{userIdx}
|
||||||
|
, #{userTokenIdx}
|
||||||
|
, #{clientIp}
|
||||||
|
, #{userAgent}
|
||||||
|
, #{referer}
|
||||||
|
, 'N'
|
||||||
|
, #{userIdx}
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
<update id="updateFileDetailStatus">
|
<update id="updateFileDetailStatus">
|
||||||
/*FileMapper.updateFileDetailStatus*/
|
/*FileMapper.updateFileDetailStatus*/
|
||||||
@@ -127,18 +146,6 @@
|
|||||||
AND LFD.STATUS IN (0,1,2)
|
AND LFD.STATUS IN (0,1,2)
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="selectFileMasterCountByFileMasterKeyAndDelYnAndStatus" resultType="int">
|
|
||||||
/*FileMapper.selectFileMasterCountByFileMasterKeyAndDelYnAndStatus*/
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM ALISTLMS.FILE_MASTER2 LFM
|
|
||||||
INNER JOIN ALISTLMS.test_user_token LTUT
|
|
||||||
ON LTUT.user_idx = LFM.USER_IDX
|
|
||||||
WHERE LFM.DEL_YN = 'N'
|
|
||||||
AND LFM.FILE_MASTER_KEY = #{fileMasterKey}
|
|
||||||
AND LFM.STATUS = 1
|
|
||||||
AND LTUT.user_token_idx = #{userTokenIdx}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="selectFileDetailCountByFileUuidUserToKenIdx" resultType="java.lang.Integer">
|
<select id="selectFileDetailCountByFileUuidUserToKenIdx" resultType="java.lang.Integer">
|
||||||
/*FileMapper.selectFileDetailCountByFileUuidUserToKenIdx*/
|
/*FileMapper.selectFileDetailCountByFileUuidUserToKenIdx*/
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
@@ -219,6 +226,52 @@
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectFileDownloadListByFileMasterIdx" resultType="com.alist.api.common.modules.file.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.common.modules.file.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.common.modules.file.dto.FileMoveTaskDto">
|
<update id="updateFileMoveSuccess" parameterType="com.alist.api.common.modules.file.dto.FileMoveTaskDto">
|
||||||
/*FileMapper.updateFileMoveSuccess*/
|
/*FileMapper.updateFileMoveSuccess*/
|
||||||
UPDATE ALISTLMS.FILE_DETAIL
|
UPDATE ALISTLMS.FILE_DETAIL
|
||||||
|
|||||||
Reference in New Issue
Block a user