[tus] 파일업로드 추가
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
package com.alist.api.common.modules.file;
|
||||||
|
|
||||||
|
import com.alist.api.common.modules.file.dto.UploadTokenDto;
|
||||||
|
import com.alist.api.common.modules.file.form.UploadTokenForm;
|
||||||
|
import com.alist.api.common.response.ApiResponse;
|
||||||
|
import com.alist.api.common.response.ApiResponseCode;
|
||||||
|
import com.alist.api.common.modules.file.dto.UploadStartDto;
|
||||||
|
import com.alist.api.common.modules.file.form.UploadStartForm;
|
||||||
|
import com.alist.api.common.modules.file.service.FileService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "99. 파일 관리", description = "파일 관리")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/files")
|
||||||
|
public class FileController {
|
||||||
|
private final FileService fileService;
|
||||||
|
|
||||||
|
public FileController(FileService fileService) {
|
||||||
|
this.fileService = fileService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "파일 업로드 시작",
|
||||||
|
description = "업로드 토큰 획득 및 업로드 정보 db 기록"
|
||||||
|
)
|
||||||
|
@PostMapping("/uploadStart")
|
||||||
|
public ResponseEntity<ApiResponse<UploadStartDto>> uploadStart(@Valid @RequestBody UploadStartForm uploadStartForm, HttpServletRequest request) {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
Integer userTokenIdx = (session == null) ? null : (Integer) session.getAttribute("userTokenIdx");
|
||||||
|
Integer userIdx = (session == null) ? null : (Integer) session.getAttribute("userIdx");
|
||||||
|
|
||||||
|
if (userTokenIdx == null || userIdx == null) {
|
||||||
|
return ApiResponse.entity(new UploadStartDto(), ApiResponseCode.CODE_401);
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadStartDto uploadInitDto = fileService.insertFileMasterUpload(userIdx, userTokenIdx, uploadStartForm.uploadStartDto());
|
||||||
|
|
||||||
|
return ApiResponse.entity(uploadInitDto, ApiResponseCode.CODE_200);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "업로드 권한 체크",
|
||||||
|
description = "업로드 토큰 발급 후 실제 업로드 권한이 있는지 확인 과정입니다."
|
||||||
|
)
|
||||||
|
@PostMapping("/uploadAuthCheck")
|
||||||
|
public ResponseEntity<ApiResponse<String>> uploadAuthCheck(@RequestHeader(value = "Authorization", required = false) String uploadToken, UploadTokenForm uploadTokenForm) {
|
||||||
|
uploadTokenForm.setUploadToken(uploadToken.substring(7).trim());
|
||||||
|
|
||||||
|
UploadTokenDto uploadTokenInfo = fileService.isUploadTokenCheck(uploadTokenForm.uploadTokenDto());
|
||||||
|
|
||||||
|
if (uploadTokenInfo.getResultCode() == 401) {
|
||||||
|
return ApiResponse.entity("", ApiResponseCode.CODE_401);
|
||||||
|
} else if (uploadTokenInfo.getResultCode() == 403) {
|
||||||
|
return ApiResponse.entity("", ApiResponseCode.CODE_403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.entity("", ApiResponseCode.CODE_200);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
String originName;
|
||||||
|
@JsonIgnore
|
||||||
|
String contentType;
|
||||||
|
@JsonIgnore
|
||||||
|
Long sizeBytes;
|
||||||
|
@JsonIgnore
|
||||||
|
String fileCategory;
|
||||||
|
@JsonIgnore
|
||||||
|
String folderPath;
|
||||||
|
@JsonIgnore
|
||||||
|
Integer userIdx;
|
||||||
|
@JsonIgnore
|
||||||
|
int status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.alist.api.common.modules.file.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class UploadTokenDto {
|
||||||
|
private Integer userTokenIdx;
|
||||||
|
private String fileMasterKey;
|
||||||
|
private String uploadToken;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private int resultCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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 fileMasterKey;
|
||||||
|
private String uploadToken;
|
||||||
|
|
||||||
|
public UploadTokenDto uploadTokenDto() {
|
||||||
|
UploadTokenDto uploadTokenDto = new UploadTokenDto();
|
||||||
|
uploadTokenDto.setFileMasterKey(this.fileMasterKey);
|
||||||
|
uploadTokenDto.setUploadToken(this.uploadToken);
|
||||||
|
|
||||||
|
return uploadTokenDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.alist.api.common.modules.file.mapper;
|
||||||
|
|
||||||
|
import com.alist.api.common.modules.file.dto.UploadStartDto;
|
||||||
|
import com.alist.api.common.modules.file.dto.UploadTokenDto;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface FileMapper {
|
||||||
|
void insertFileMasterUpload(UploadStartDto uploadStartDto);
|
||||||
|
|
||||||
|
int selectFileMasterCountByFileMasterKeyAndDelYnAndStatus(UploadTokenDto uploadTokenDto);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.alist.api.common.modules.file.service;
|
||||||
|
|
||||||
|
import com.alist.api.common.modules.file.dto.UploadStartDto;
|
||||||
|
import com.alist.api.common.modules.file.dto.UploadTokenDto;
|
||||||
|
import com.alist.api.common.modules.file.mapper.FileMapper;
|
||||||
|
import com.alist.api.config.jwt.JwtTokenProvider;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FileService {
|
||||||
|
@Value("${file.upload.tus-endpoint}")
|
||||||
|
private String tusEndpoint;
|
||||||
|
|
||||||
|
@Value("${file.upload.public-base-url}")
|
||||||
|
private String publicBaseUrl;
|
||||||
|
|
||||||
|
private final FileMapper fileMapper;
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
|
||||||
|
public FileService(FileMapper fileMapper, JwtTokenProvider jwtTokenProvider) {
|
||||||
|
this.fileMapper = fileMapper;
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadStartDto insertFileMasterUpload(Integer userIdx, Integer userTokenIdx, UploadStartDto uploadStartDto) {
|
||||||
|
uploadStartDto.setFileMasterKey(UUID.randomUUID().toString());
|
||||||
|
uploadStartDto.setUserIdx(userIdx);
|
||||||
|
uploadStartDto.setStatus(1);
|
||||||
|
uploadStartDto.setFolderPath(uploadStartDto.getFolderPath() == null ? "/" : uploadStartDto.getFolderPath());
|
||||||
|
|
||||||
|
fileMapper.insertFileMasterUpload(uploadStartDto);
|
||||||
|
|
||||||
|
uploadStartDto.setUploadToken(jwtTokenProvider.createUploadToken(userTokenIdx));
|
||||||
|
uploadStartDto.setTusEndpoint(tusEndpoint);
|
||||||
|
|
||||||
|
return uploadStartDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadTokenDto isUploadTokenCheck(UploadTokenDto uploadTokenDto) {
|
||||||
|
if (!jwtTokenProvider.validateToken(uploadTokenDto.getUploadToken())) {
|
||||||
|
uploadTokenDto.setResultCode(401);
|
||||||
|
return uploadTokenDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jwtTokenProvider.hasUploadScope(uploadTokenDto.getUploadToken())) {
|
||||||
|
uploadTokenDto.setResultCode(403);
|
||||||
|
return uploadTokenDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
uploadTokenDto.setUserTokenIdx(Integer.parseInt(jwtTokenProvider.getUserId(uploadTokenDto.getUploadToken())));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
uploadTokenDto.setResultCode(401);
|
||||||
|
return uploadTokenDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
int isOk = fileMapper.selectFileMasterCountByFileMasterKeyAndDelYnAndStatus(uploadTokenDto);
|
||||||
|
|
||||||
|
if (isOk > 0) {
|
||||||
|
uploadTokenDto.setResultCode(200);
|
||||||
|
} else {
|
||||||
|
uploadTokenDto.setResultCode(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uploadTokenDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import org.springframework.core.annotation.Order;
|
|||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
|||||||
@@ -90,4 +90,28 @@ public class JwtTokenProvider {
|
|||||||
.getBody();
|
.getBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 업로드 전용 토큰*/
|
||||||
|
public String createUploadToken(long userTokenIdx) {
|
||||||
|
Instant now = Instant.now();
|
||||||
|
Instant expiry = now.plusSeconds(300); // 5분
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.setSubject(String.valueOf(userTokenIdx))
|
||||||
|
.claim("scope", "UPLOAD")
|
||||||
|
.setIssuedAt(Date.from(now))
|
||||||
|
.setExpiration(Date.from(expiry))
|
||||||
|
.signWith(secretKey, SignatureAlgorithm.HS256)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasUploadScope(String token) {
|
||||||
|
try {
|
||||||
|
Claims claims = parseClaims(token);
|
||||||
|
return "UPLOAD".equals(claims.get("scope", String.class));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ public class TestLoginDto {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private String userApiKey;
|
private String userApiKey;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private int userTokenIdx;
|
private Integer userTokenIdx;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Instant expiresAt;
|
private Instant expiresAt;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private int userIdx;
|
private Integer userIdx;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private int resultCode;
|
private int resultCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import lombok.Getter;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class TestLoginTokenVo {
|
public class TestLoginTokenVo {
|
||||||
private int userTokenIdx;
|
private Integer userTokenIdx;
|
||||||
private String userApiKey;
|
private String userApiKey;
|
||||||
private String userRole;
|
private String userRole;
|
||||||
private String refreshToken;
|
private String refreshToken;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import lombok.Getter;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class TestLoginVo {
|
public class TestLoginVo {
|
||||||
private int userIdx;
|
private Integer userIdx;
|
||||||
private String id;
|
private String id;
|
||||||
private String password;
|
private String password;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import lombok.Setter;
|
|||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
public class TestUserTokenDto {
|
public class TestUserTokenDto {
|
||||||
private int userTokenIdx;
|
private Integer userTokenIdx;
|
||||||
private int userIdx;
|
private Integer userIdx;
|
||||||
private String userApiKey;
|
private String userApiKey;
|
||||||
private String userRole;
|
private String userRole;
|
||||||
private int resultCode;
|
private int resultCode;
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ swagger:
|
|||||||
id: alist
|
id: alist
|
||||||
password: "1qaz2wsx!@"
|
password: "1qaz2wsx!@"
|
||||||
|
|
||||||
|
file:
|
||||||
|
upload:
|
||||||
|
tus-endpoint: https://file-alist.pjt.kr/tus/files/
|
||||||
|
public-base-url: https://file-alist.pjt.kr
|
||||||
|
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ swagger:
|
|||||||
id: ${SWAGGER_ID}
|
id: ${SWAGGER_ID}
|
||||||
password: ${SWAGGER_PASSWORD}
|
password: ${SWAGGER_PASSWORD}
|
||||||
|
|
||||||
|
file:
|
||||||
|
upload:
|
||||||
|
tus-endpoint: https://file-alist.pjt.kr/tus/files/
|
||||||
|
public-base-url: https://file-alist.pjt.kr
|
||||||
|
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
|
||||||
|
<mapper namespace="com.alist.api.common.modules.file.mapper.FileMapper">
|
||||||
|
<insert id="insertFileMasterUpload">
|
||||||
|
/*FileMapper.insertFileMasterUpload*/
|
||||||
|
INSERT INTO ALISTLMS.FILE_MASTER (
|
||||||
|
FILE_MASTER_KEY, USER_IDX, FILE_CATEGORY, FOLDER_PATH, STATUS, CREATE_MEMBER, UPDATE_MEMBER
|
||||||
|
) values (
|
||||||
|
#{fileMasterKey}, #{userIdx}, #{fileCategory}, #{folderPath}, #{status}, #{userIdx}, #{userIdx}
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="selectFileMasterCountByFileMasterKeyAndDelYnAndStatus" resultType="int">
|
||||||
|
/*FileMapper.selectFileMasterCountByFileMasterKeyAndDelYnAndStatus*/
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM ALISTLMS.FILE_MASTER 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
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user