diff --git a/src/main/java/com/alist/api/common/modules/file/FileController.java b/src/main/java/com/alist/api/common/modules/file/FileController.java index 5f9b2c7..9353493 100644 --- a/src/main/java/com/alist/api/common/modules/file/FileController.java +++ b/src/main/java/com/alist/api/common/modules/file/FileController.java @@ -1,9 +1,6 @@ package com.alist.api.common.modules.file; -import com.alist.api.common.modules.file.dto.FileUploadDto; -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.dto.*; 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.UploadCancelForm; @@ -17,13 +14,19 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; 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.web.bind.annotation.*; +import java.net.URI; +import java.nio.charset.StandardCharsets; + @Slf4j @Tag( - name = "99. TUS 업로드", - description = "TUS 기반 파일 업로드 초기화, 권한 검증, 상태 조회, 훅 처리, 취소 API" + name = "99. TUS 업로드" + , description = "TUS 기반 파일 업로드 초기화, 권한 검증, 상태 조회, 훅 처리, 취소 API" ) @RestController @RequestMapping("/files") @@ -35,8 +38,8 @@ public class FileController { } @Operation( - summary = "파일 업로드 초기화", - description = "업로드 대상 파일 정보를 DB에 등록하고 fileUuid/uploadToken을 발급합니다." + summary = "파일 업로드 초기화" + , description = "업로드 대상 파일 정보를 DB에 등록하고 fileUuid/uploadToken을 발급합니다." ) @PostMapping("/uploadInit") public ResponseEntity> uploadInit( @@ -62,8 +65,8 @@ public class FileController { } @Operation( - summary = "업로드 권한 검증", - description = "Bearer 업로드 토큰과 fileUuid(및 TUS 메타데이터)를 검증해 업로드 가능 여부를 확인합니다." + summary = "업로드 권한 검증" + , description = "Bearer 업로드 토큰과 fileUuid(및 TUS 메타데이터)를 검증해 업로드 가능 여부를 확인합니다." ) @GetMapping("/uploadAuth") public ResponseEntity> uploadAuth( @@ -97,8 +100,8 @@ public class FileController { } @Operation( - summary = "업로드 상태 조회", - description = "fileUuid 기준으로 Redis/DB 업로드 상태(진행률, 상태값)를 조회합니다." + summary = "업로드 상태 조회" + , description = "fileUuid 기준으로 Redis/DB 업로드 상태(진행률, 상태값)를 조회합니다." ) @PostMapping("/uploadStatus") public ResponseEntity> uploadStatus( @@ -112,8 +115,8 @@ public class FileController { } @Operation( - summary = "TUS 훅 수신", - description = "tusd에서 전달되는 pre-create/post-receive/post-finish/post-terminate 이벤트를 반영합니다." + summary = "TUS 훅 수신" + , description = "tusd에서 전달되는 pre-create/post-receive/post-finish/post-terminate 이벤트를 반영합니다." ) @PostMapping("/tusHook") public ResponseEntity> tusHook(@RequestBody TusHookForm tusHookForm) { @@ -127,8 +130,8 @@ public class FileController { } @Operation( - summary = "업로드 취소", - description = "현재 로그인 사용자의 fileUuid 업로드를 취소(CANCELED) 처리합니다." + summary = "업로드 취소" + , description = "현재 로그인 사용자의 fileUuid 업로드를 취소(CANCELED) 처리합니다." ) @PostMapping("/uploadCancel") public ResponseEntity> uploadCancel( @@ -155,4 +158,104 @@ public class FileController { return ApiResponse.entity("", ApiResponseCode.CODE_200); } + + @Operation( + summary = "파일 목록 조회" + , description = "FILE_MASTER_IDX 기준으로 완료된 파일 목록을 조회합니다.") + @GetMapping("/list/{fileMasterIdx}") + public ResponseEntity> 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 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 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); + } } diff --git a/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadDto.java b/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadDto.java new file mode 100644 index 0000000..9591167 --- /dev/null +++ b/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadDto.java @@ -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; +} diff --git a/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadItemDto.java b/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadItemDto.java new file mode 100644 index 0000000..7f8a47e --- /dev/null +++ b/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadItemDto.java @@ -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; +} diff --git a/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadListDto.java b/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadListDto.java new file mode 100644 index 0000000..1f1b977 --- /dev/null +++ b/src/main/java/com/alist/api/common/modules/file/dto/FileDownloadListDto.java @@ -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 itemList; + + @JsonIgnore + private Integer resultCode; +} diff --git a/src/main/java/com/alist/api/common/modules/file/dto/TusHookDto.java b/src/main/java/com/alist/api/common/modules/file/dto/TusHookDto.java index 16a53fc..d13123a 100644 --- a/src/main/java/com/alist/api/common/modules/file/dto/TusHookDto.java +++ b/src/main/java/com/alist/api/common/modules/file/dto/TusHookDto.java @@ -13,6 +13,6 @@ public class TusHookDto { private String fileUuid; private String filename; private String filetype; - private long uploadedBytes; - private int status; + private Long uploadedBytes; + private Integer status; } diff --git a/src/main/java/com/alist/api/common/modules/file/dto/UploadStartDto.java b/src/main/java/com/alist/api/common/modules/file/dto/UploadStartDto.java deleted file mode 100644 index c248a09..0000000 --- a/src/main/java/com/alist/api/common/modules/file/dto/UploadStartDto.java +++ /dev/null @@ -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; -} diff --git a/src/main/java/com/alist/api/common/modules/file/dto/UploadStatusDto.java b/src/main/java/com/alist/api/common/modules/file/dto/UploadStatusDto.java index a30d34f..e603e15 100644 --- a/src/main/java/com/alist/api/common/modules/file/dto/UploadStatusDto.java +++ b/src/main/java/com/alist/api/common/modules/file/dto/UploadStatusDto.java @@ -8,8 +8,8 @@ import lombok.Setter; public class UploadStatusDto { private String fileUuid; private String status; // PENDING, UPLOADING, DONE, FAILED - private long uploadedBytes; - private long totalBytes; - private int percent; + private Long uploadedBytes; + private Long totalBytes; + private Integer percent; private String updatedAt; } diff --git a/src/main/java/com/alist/api/common/modules/file/dto/UploadTokenDto.java b/src/main/java/com/alist/api/common/modules/file/dto/UploadTokenDto.java index 3151163..8761714 100644 --- a/src/main/java/com/alist/api/common/modules/file/dto/UploadTokenDto.java +++ b/src/main/java/com/alist/api/common/modules/file/dto/UploadTokenDto.java @@ -19,5 +19,5 @@ public class UploadTokenDto { private String contentType; @JsonIgnore - private int resultCode; + private Integer resultCode; } diff --git a/src/main/java/com/alist/api/common/modules/file/form/UploadCompleteForm.java b/src/main/java/com/alist/api/common/modules/file/form/UploadCompleteForm.java deleted file mode 100644 index 7fff8e2..0000000 --- a/src/main/java/com/alist/api/common/modules/file/form/UploadCompleteForm.java +++ /dev/null @@ -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; -} diff --git a/src/main/java/com/alist/api/common/modules/file/form/UploadStartForm.java b/src/main/java/com/alist/api/common/modules/file/form/UploadStartForm.java deleted file mode 100644 index e1c99d8..0000000 --- a/src/main/java/com/alist/api/common/modules/file/form/UploadStartForm.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/com/alist/api/common/modules/file/form/UploadTokenForm.java b/src/main/java/com/alist/api/common/modules/file/form/UploadTokenForm.java deleted file mode 100644 index b3c56d0..0000000 --- a/src/main/java/com/alist/api/common/modules/file/form/UploadTokenForm.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/com/alist/api/common/modules/file/mapper/FileMapper.java b/src/main/java/com/alist/api/common/modules/file/mapper/FileMapper.java index e176087..85601ef 100644 --- a/src/main/java/com/alist/api/common/modules/file/mapper/FileMapper.java +++ b/src/main/java/com/alist/api/common/modules/file/mapper/FileMapper.java @@ -3,12 +3,10 @@ package com.alist.api.common.modules.file.mapper; import com.alist.api.common.modules.file.dto.*; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + @Mapper public interface FileMapper { - void insertFileMasterUpload(UploadStartDto uploadStartDto); - - int selectFileMasterCountByFileMasterKeyAndDelYnAndStatus(UploadTokenDto uploadTokenDto); - void insertFileMasterInit(FileUploadDto fileUploadDto); void insertFileDetailInit(FileUploadItemDto item); @@ -38,4 +36,10 @@ public interface FileMapper { int updateFileMoveSuccess(FileMoveTaskDto task); int updateFileMovePendingByFileUuid(FileMoveTaskDto task); + + List selectFileDownloadListByFileMasterIdx(FileDownloadDto fileDownloadDto); + + FileDownloadDto selectFileDetailByFileUuid(FileDownloadDto fileDownloadDto); + + void insertFileDownloadEventLog(FileDownloadDto fileDetailInfo); } diff --git a/src/main/java/com/alist/api/common/modules/file/service/FileService.java b/src/main/java/com/alist/api/common/modules/file/service/FileService.java index ca9f999..18cbefc 100644 --- a/src/main/java/com/alist/api/common/modules/file/service/FileService.java +++ b/src/main/java/com/alist/api/common/modules/file/service/FileService.java @@ -7,6 +7,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.HashOperations; 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.transaction.annotation.Transactional; @@ -19,6 +21,7 @@ import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.util.HexFormat; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -43,6 +46,12 @@ public class FileService { @Value("${file.upload.final-root}") 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 JwtTokenProvider jwtTokenProvider; private final StringRedisTemplate stringRedisTemplate; @@ -259,7 +268,7 @@ public class FileService { } } - tusHookDto.setUploadedBytes(offset); + //tusHookDto.setUploadedBytes(offset); // 전체 사이즈 업데이트 삭제 Integer currentStatus = fileUploadMetaInfo.getStatus(); if (currentStatus != null && (currentStatus == 3 || currentStatus == 5)) { @@ -276,6 +285,7 @@ public class FileService { return false; } + tusHookDto.setUploadedBytes(null); tusHookDto.setStatus(1); // UPLOADING break; @@ -285,12 +295,13 @@ public class FileService { return false; } + tusHookDto.setUploadedBytes(null); tusHookDto.setStatus(1); // UPLOADING break; case "post-finish": - tusHookDto.setStatus(3); // DONE tusHookDto.setUploadedBytes(size); + tusHookDto.setStatus(3); // DONE break; case "post-terminate": @@ -298,6 +309,7 @@ public class FileService { if (currentStatus != null && currentStatus == 3) { return false; } + tusHookDto.setUploadedBytes(offset); tusHookDto.setStatus(5); // CANCELED break; @@ -309,7 +321,11 @@ public class FileService { 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)) { fileMapper.updateFileMasterAggregateByFileUuid(tusHookDto.getFileUuid()); @@ -319,7 +335,11 @@ public class FileService { try { tryMoveNowByFileUuid(tusHookDto.getFileUuid()); } catch (NoSuchFileException e) { - try { Thread.sleep(200); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } + try { + Thread.sleep(200); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } try { tryMoveNowByFileUuid(tusHookDto.getFileUuid()); // 1회 재시도 } catch (Exception ex) { @@ -570,4 +590,109 @@ public class FileService { saveCanceledStatusRedis(uploadCancelDto.getFileUuid(), uploaded, total); // 필요하면 기존 bytes 조회해서 넣어도 됨 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 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; + } } diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 8e1fcfd..355e3fd 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -51,6 +51,9 @@ file: interrupt-seconds: 30 auth-cache: ttl-seconds: 20 + download: + mode: redirect + redirect-prefix: /public-test-files springdoc: api-docs: diff --git a/src/main/resources/application-pjt.yaml b/src/main/resources/application-pjt.yaml index c6b1b51..2a536bd 100644 --- a/src/main/resources/application-pjt.yaml +++ b/src/main/resources/application-pjt.yaml @@ -53,6 +53,9 @@ file: interrupt-seconds: 30 auth-cache: ttl-seconds: 20 + download: + mode: accel + redirect-prefix: /protected-files springdoc: api-docs: diff --git a/src/main/resources/mapper/common/file/FileMapper.xml b/src/main/resources/mapper/common/file/FileMapper.xml index 8fbe4f7..a7d62c9 100644 --- a/src/main/resources/mapper/common/file/FileMapper.xml +++ b/src/main/resources/mapper/common/file/FileMapper.xml @@ -21,15 +21,6 @@ ) - - /*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} - ) - - /*FileMapper.insertFileUploadEventLog*/ INSERT INTO ALISTLMS.FILE_UPLOAD_EVENT_LOG ( @@ -47,6 +38,34 @@ WHERE LFD.FILE_UUID = #{fileUuid} LIMIT 1 + + /*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} + ) + /*FileMapper.updateFileDetailStatus*/ @@ -127,18 +146,6 @@ AND LFD.STATUS IN (0,1,2) - - + + + + /*FileMapper.updateFileMoveSuccess*/ UPDATE ALISTLMS.FILE_DETAIL