[파일 업로드] tusHook 작업
- 여러번 db 호출 redis로 캐싱화
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
package com.alist.api.common.modules.file.dto;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class TusHookEventDto {
|
||||
private String Type;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.alist.api.common.modules.file.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class TusHookUploadDto {
|
||||
private String id;
|
||||
private Long size;
|
||||
private Map<String, String> metaData;
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package com.alist.api.common.modules.file.service;
|
||||
|
||||
import com.alist.api.common.modules.file.dto.TusHookDto;
|
||||
import com.alist.api.common.modules.file.dto.TusHookUploadDto;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.util.HexFormat;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@@ -22,12 +26,17 @@ public class FileService {
|
||||
@Value("${file.upload.public-base-url}")
|
||||
private String publicBaseUrl;
|
||||
|
||||
@Value("${file.upload.auth-cache.ttl-seconds:20}")
|
||||
private long uploadAuthCacheTtlSeconds;
|
||||
|
||||
private final FileMapper fileMapper;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public FileService(FileMapper fileMapper, JwtTokenProvider jwtTokenProvider) {
|
||||
public FileService(FileMapper fileMapper, JwtTokenProvider jwtTokenProvider, StringRedisTemplate stringRedisTemplate) {
|
||||
this.fileMapper = fileMapper;
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
}
|
||||
|
||||
public UploadStartDto insertFileMasterUpload(Integer userIdx, Integer userTokenIdx, UploadStartDto uploadStartDto) {
|
||||
@@ -62,12 +71,35 @@ public class FileService {
|
||||
return uploadTokenDto;
|
||||
}
|
||||
|
||||
int isOk = fileMapper.selectFileMasterCountByFileMasterKeyAndDelYnAndStatus(uploadTokenDto);
|
||||
String cacheKey = buildAuthCacheKey(uploadTokenDto.getFileMasterKey(), uploadTokenDto.getUploadToken());
|
||||
|
||||
if (isOk > 0) {
|
||||
uploadTokenDto.setResultCode(200);
|
||||
} else {
|
||||
uploadTokenDto.setResultCode(403);
|
||||
try {
|
||||
String cached = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
if (cached != null) {
|
||||
try {
|
||||
uploadTokenDto.setResultCode(Integer.parseInt(cached));
|
||||
return uploadTokenDto;
|
||||
} catch (NumberFormatException parseEx) {
|
||||
log.warn("uploadAuth cache parse fail. key={}, value={}", cacheKey, cached);
|
||||
stringRedisTemplate.delete(cacheKey); // 오염 캐시 정리
|
||||
}
|
||||
}
|
||||
} catch (Exception redisGetEx) {
|
||||
log.warn("uploadAuth cache get fail. fallback DB. key={}", cacheKey, redisGetEx);
|
||||
}
|
||||
|
||||
int isOk = fileMapper.selectFileMasterCountByFileMasterKeyAndDelYnAndStatus(uploadTokenDto);
|
||||
int resultCode = (isOk > 0) ? 200 : 403;
|
||||
uploadTokenDto.setResultCode(resultCode);
|
||||
|
||||
try {
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
cacheKey,
|
||||
String.valueOf(resultCode),
|
||||
Duration.ofSeconds(safeTtlSeconds())
|
||||
);
|
||||
} catch (Exception redisSetEx) {
|
||||
log.warn("uploadAuth cache set fail. key={}, resultCode={}", cacheKey, resultCode, redisSetEx);
|
||||
}
|
||||
|
||||
return uploadTokenDto;
|
||||
@@ -76,4 +108,35 @@ public class FileService {
|
||||
public void insertFileAndSave(TusHookDto tusHookDto) {
|
||||
|
||||
}
|
||||
|
||||
private String buildAuthCacheKey(String fileMasterKey, String uploadToken) {
|
||||
return "upload:alist:auth:" + fileMasterKey + ":" + sha256(uploadToken);
|
||||
}
|
||||
|
||||
private String sha256(String value) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hashed = digest.digest(value.getBytes(StandardCharsets.UTF_8));
|
||||
return HexFormat.of().formatHex(hashed);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("SHA-256 algorithm not available", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void evictUploadAuthCacheByFileMasterKey(String fileMasterKey) {
|
||||
try {
|
||||
String pattern = "upload:alist:auth:" + fileMasterKey + ":*";
|
||||
var keys = stringRedisTemplate.keys(pattern);
|
||||
if (!keys.isEmpty()) {
|
||||
stringRedisTemplate.delete(keys);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("uploadAuth cache evict fail. fileMasterKey={}", fileMasterKey, e);
|
||||
}
|
||||
}
|
||||
|
||||
private long safeTtlSeconds() {
|
||||
// 설정 실수 방어: 0/음수면 기본값 20초 사용
|
||||
return (uploadAuthCacheTtlSeconds > 0) ? uploadAuthCacheTtlSeconds : 20L;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public class RedisSessionConfig {
|
||||
@Value("${cookie.secure}")
|
||||
private boolean cookieSecure;
|
||||
|
||||
@Value("${cookie.domain}")
|
||||
@Value("${cookie.domain:}")
|
||||
private String cookieDomain;
|
||||
|
||||
@Value("${cookie.name}")
|
||||
@@ -31,6 +31,10 @@ public class RedisSessionConfig {
|
||||
serializer.setUseSecureCookie(cookieSecure); // 환경별 설정 (local: false, pjt: true)
|
||||
serializer.setUseHttpOnlyCookie(true); // XSS 방지
|
||||
serializer.setSameSite("Lax"); // CSRF 방지
|
||||
|
||||
if (cookieDomain != null && !cookieDomain.isBlank()) {
|
||||
serializer.setDomainName(cookieDomain.trim());
|
||||
}
|
||||
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ jwt:
|
||||
refresh-token-validity-seconds: 2592000
|
||||
|
||||
cookie:
|
||||
secure: true # 로컬 개발 환경 (HTTP)
|
||||
domain: pjt.kr
|
||||
secure: false # 로컬 개발 환경 (HTTP)
|
||||
domain:
|
||||
name: ALIST_SESSION
|
||||
|
||||
swagger:
|
||||
@@ -46,6 +46,8 @@ file:
|
||||
upload:
|
||||
tus-endpoint: https://file-alist.pjt.kr/tus/files/
|
||||
public-base-url: https://file-alist.pjt.kr
|
||||
auth-cache:
|
||||
ttl-seconds: 20
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
|
||||
@@ -48,6 +48,8 @@ file:
|
||||
upload:
|
||||
tus-endpoint: https://file-alist.pjt.kr/tus/files/
|
||||
public-base-url: https://file-alist.pjt.kr
|
||||
auth-cache:
|
||||
ttl-seconds: 20
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
|
||||
Reference in New Issue
Block a user