[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.annotation.web.builders.HttpSecurity;
|
||||
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.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
@@ -90,4 +90,28 @@ public class JwtTokenProvider {
|
||||
.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
|
||||
private String userApiKey;
|
||||
@JsonIgnore
|
||||
private int userTokenIdx;
|
||||
private Integer userTokenIdx;
|
||||
@JsonIgnore
|
||||
private Instant expiresAt;
|
||||
@JsonIgnore
|
||||
private int userIdx;
|
||||
private Integer userIdx;
|
||||
@JsonIgnore
|
||||
private int resultCode;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class TestLoginTokenVo {
|
||||
private int userTokenIdx;
|
||||
private Integer userTokenIdx;
|
||||
private String userApiKey;
|
||||
private String userRole;
|
||||
private String refreshToken;
|
||||
|
||||
@@ -4,7 +4,7 @@ import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class TestLoginVo {
|
||||
private int userIdx;
|
||||
private Integer userIdx;
|
||||
private String id;
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import lombok.Setter;
|
||||
@Setter
|
||||
@Getter
|
||||
public class TestUserTokenDto {
|
||||
private int userTokenIdx;
|
||||
private int userIdx;
|
||||
private Integer userTokenIdx;
|
||||
private Integer userIdx;
|
||||
private String userApiKey;
|
||||
private String userRole;
|
||||
private int resultCode;
|
||||
|
||||
@@ -42,6 +42,11 @@ swagger:
|
||||
id: alist
|
||||
password: "1qaz2wsx!@"
|
||||
|
||||
file:
|
||||
upload:
|
||||
tus-endpoint: https://file-alist.pjt.kr/tus/files/
|
||||
public-base-url: https://file-alist.pjt.kr
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
|
||||
@@ -44,6 +44,11 @@ swagger:
|
||||
id: ${SWAGGER_ID}
|
||||
password: ${SWAGGER_PASSWORD}
|
||||
|
||||
file:
|
||||
upload:
|
||||
tus-endpoint: https://file-alist.pjt.kr/tus/files/
|
||||
public-base-url: https://file-alist.pjt.kr
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
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