File Management API Upload ALL Success
This commit is contained in:
@@ -43,9 +43,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
||||
String path = request.getServletPath();
|
||||
|
||||
// 여기에 JWT 필터를 타지 않게 할 URL 패턴들 작성
|
||||
// JWT 필터를 타지 않게 할 URL 패턴들
|
||||
return path.startsWith("/api/auth/signin") || path.startsWith("/api/auth/refresh");
|
||||
// 필요하면 "/api/auth/logout" 도 추가
|
||||
}
|
||||
|
||||
private String resolveToken(HttpServletRequest request) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.common.exception;
|
||||
|
||||
public class DuplicateFileException extends RuntimeException {
|
||||
public DuplicateFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.common.exception;
|
||||
|
||||
public class ValidationException extends RuntimeException {
|
||||
public ValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
|
||||
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||
import com.kamco.cd.kamcoback.common.exception.DuplicateFileException;
|
||||
import com.kamco.cd.kamcoback.common.exception.ValidationException;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
|
||||
@@ -42,6 +44,60 @@ public class GlobalExceptionHandler {
|
||||
this.errorLogRepository = errorLogRepository;
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
@ExceptionHandler(DuplicateFileException.class)
|
||||
public ApiResponseDto<String> handleDuplicateFileException(
|
||||
DuplicateFileException e, HttpServletRequest request) {
|
||||
log.warn("[DuplicateFileException] resource :{} ", e.getMessage());
|
||||
ApiResponseCode code = ApiResponseCode.CONFLICT;
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
code,
|
||||
HttpStatus.CONFLICT,
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
return ApiResponseDto.createException(
|
||||
code, e.getMessage(), HttpStatus.CONFLICT, errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
|
||||
@ExceptionHandler(ValidationException.class)
|
||||
public ApiResponseDto<String> handleValidationException(
|
||||
ValidationException e, HttpServletRequest request) {
|
||||
log.warn("[ValidationException] resource :{} ", e.getMessage());
|
||||
ApiResponseCode code = ApiResponseCode.UNPROCESSABLE_ENTITY;
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
code,
|
||||
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
return ApiResponseDto.createException(
|
||||
code, e.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY, errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ApiResponseDto<String> handleIllegalArgumentException(
|
||||
IllegalArgumentException e, HttpServletRequest request) {
|
||||
log.warn("[IllegalArgumentException] resource :{} ", e.getMessage());
|
||||
ApiResponseCode code = ApiResponseCode.BAD_REQUEST;
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
code,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
return ApiResponseDto.createException(
|
||||
code, e.getMessage(), HttpStatus.BAD_REQUEST, errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ApiResponseDto<String> handlerEntityNotFoundException(
|
||||
@@ -105,26 +161,7 @@ public class GlobalExceptionHandler {
|
||||
errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ApiResponseDto<String> handlerIllegalArgumentException(
|
||||
IllegalArgumentException e, HttpServletRequest request) {
|
||||
log.warn("[handlerIllegalArgumentException] resource :{} ", e.getMessage());
|
||||
String codeName = "BAD_REQUEST";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
return ApiResponseDto.createException(
|
||||
ApiResponseCode.getCode(codeName),
|
||||
ApiResponseCode.getMessage(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
|
||||
@@ -11,9 +11,13 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
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.WebSecurityCustomizer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.firewall.HttpFirewall;
|
||||
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
@@ -30,19 +34,23 @@ public class SecurityConfig {
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.csrf(csrf -> csrf.disable()) // CSRF 보안 기능 비활성화
|
||||
.sessionManagement(
|
||||
sm ->
|
||||
sm.sessionCreationPolicy(
|
||||
SessionCreationPolicy.STATELESS)) // 서버 세션 만들지 않음, 요청은 JWT 인증
|
||||
.formLogin(form -> form.disable()) // react에서 로그인 요청 관리
|
||||
.httpBasic(basic -> basic.disable()) // 기본 basic 인증 비활성화 JWT 인증사용
|
||||
.logout(logout -> logout.disable()) // 기본 로그아웃 비활성화 JWT는 서버 상태가 없으므로 로그아웃 처리 필요 없음
|
||||
.authenticationProvider(
|
||||
customAuthenticationProvider) // 로그인 패스워드 비교방식 스프링 기본 Provider 사용안함 커스텀 사용
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.formLogin(form -> form.disable())
|
||||
.httpBasic(basic -> basic.disable())
|
||||
.logout(logout -> logout.disable())
|
||||
.authenticationProvider(customAuthenticationProvider)
|
||||
.authorizeHttpRequests(
|
||||
auth ->
|
||||
auth
|
||||
// 맵시트 영역 전체 허용 (우선순위 최상단)
|
||||
.requestMatchers("/api/mapsheet/**")
|
||||
.permitAll()
|
||||
|
||||
// 업로드 명시적 허용
|
||||
.requestMatchers(HttpMethod.POST, "/api/mapsheet/upload")
|
||||
.permitAll()
|
||||
|
||||
// ADMIN만 접근
|
||||
.requestMatchers("/api/test/admin")
|
||||
.hasRole("ADMIN")
|
||||
@@ -98,4 +106,19 @@ public class SecurityConfig {
|
||||
source.registerCorsConfiguration("/**", config); // CORS 정책을 등록
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpFirewall httpFirewall() {
|
||||
StrictHttpFirewall firewall = new StrictHttpFirewall();
|
||||
firewall.setAllowUrlEncodedSlash(true);
|
||||
firewall.setAllowUrlEncodedDoubleSlash(true);
|
||||
firewall.setAllowUrlEncodedPercent(true);
|
||||
firewall.setAllowSemicolon(true);
|
||||
return firewall;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/api/mapsheet/**"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,37 +56,38 @@ public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
ServerHttpResponse response) {
|
||||
|
||||
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
|
||||
ContentCachingRequestWrapper contentWrapper = (ContentCachingRequestWrapper) servletRequest;
|
||||
ContentCachingRequestWrapper contentWrapper = null;
|
||||
if (servletRequest instanceof ContentCachingRequestWrapper wrapper) {
|
||||
contentWrapper = wrapper;
|
||||
}
|
||||
|
||||
if (body instanceof ApiResponseDto<?> apiResponse) {
|
||||
// ApiResponseDto에 설정된 httpStatus를 실제 HTTP 응답에 적용
|
||||
response.setStatusCode(apiResponse.getHttpStatus());
|
||||
|
||||
String ip = ApiLogFunction.getClientIp(servletRequest);
|
||||
|
||||
Long userid = null;
|
||||
|
||||
/**
|
||||
* servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth 이 요청이
|
||||
* JWT 인증을 통과한 요청인가? 그리고 Spring Security Authentication 객체가
|
||||
* UsernamePasswordAuthenticationToken 타입인가? 체크
|
||||
*/
|
||||
/**
|
||||
* auth.getPrincipal() instanceof CustomUserDetails customUserDetails principal 안에 들어있는 객체가 내가
|
||||
* 만든 CustomUserDetails 타입인가? 체크
|
||||
*/
|
||||
if (servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth
|
||||
&& auth.getPrincipal() instanceof CustomUserDetails customUserDetails) {
|
||||
|
||||
// audit 에는 long 타입 user_id가 들어가지만 토큰 sub은 uuid여서 user_id 가져오기
|
||||
userid = customUserDetails.getMember().getId();
|
||||
}
|
||||
|
||||
String requestBody = ApiLogFunction.getRequestBody(servletRequest, contentWrapper);
|
||||
requestBody = maskSensitiveFields(requestBody); // 로그 저장전에 중요정보 마스킹
|
||||
String requestBody;
|
||||
// 멀티파트 요청은 바디 로깅을 생략 (파일 바이너리로 인한 문제 예방)
|
||||
MediaType reqContentType = null;
|
||||
try {
|
||||
String ct = servletRequest.getContentType();
|
||||
reqContentType = ct != null ? MediaType.valueOf(ct) : null;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
if (reqContentType != null && MediaType.MULTIPART_FORM_DATA.includes(reqContentType)) {
|
||||
requestBody = "(multipart omitted)";
|
||||
} else {
|
||||
requestBody = ApiLogFunction.getRequestBody(servletRequest, contentWrapper);
|
||||
requestBody = maskSensitiveFields(requestBody);
|
||||
}
|
||||
|
||||
List<?> list = menuService.getFindAll();
|
||||
|
||||
List<MenuDto.Basic> result =
|
||||
list.stream()
|
||||
.map(
|
||||
@@ -111,7 +112,6 @@ public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
servletRequest.getRequestURI(),
|
||||
requestBody,
|
||||
apiResponse.getErrorLogUid());
|
||||
// tb_audit_log 테이블 저장
|
||||
auditLogRepository.save(log);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,14 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -74,9 +75,12 @@ public class MapSheetMngFileCheckerApiController {
|
||||
@Operation(summary = "파일 업로드", description = "파일 업로드 및 TIF 검증")
|
||||
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ApiResponseDto<String> uploadFile(
|
||||
@RequestPart("file") MultipartFile file,
|
||||
@RequestParam("targetPath") String targetPath) {
|
||||
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.uploadFile(file, targetPath));
|
||||
@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("targetPath") String targetPath,
|
||||
@RequestParam(name = "overwrite", required = false, defaultValue = "true")
|
||||
boolean overwrite) {
|
||||
return ApiResponseDto.createOK(
|
||||
mapSheetMngFileCheckerService.uploadFile(file, targetPath, overwrite));
|
||||
}
|
||||
|
||||
@Operation(summary = "파일 삭제", description = "중복 파일 등 파일 삭제")
|
||||
@@ -126,4 +130,8 @@ public class MapSheetMngFileCheckerApiController {
|
||||
|
||||
|
||||
*/
|
||||
@PostMapping("/upload-test")
|
||||
public String uploadTest(@RequestParam("name") String name) {
|
||||
return "RECV:" + name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.mapsheet.service;
|
||||
|
||||
import static java.lang.String.CASE_INSENSITIVE_ORDER;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.exception.DuplicateFileException;
|
||||
import com.kamco.cd.kamcoback.common.exception.ValidationException;
|
||||
import com.kamco.cd.kamcoback.common.utils.FIleChecker;
|
||||
import com.kamco.cd.kamcoback.common.utils.NameValidator;
|
||||
import com.kamco.cd.kamcoback.config.FileConfig;
|
||||
@@ -14,6 +16,8 @@ import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFoldersDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.ImageryDto;
|
||||
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngFileCheckerCoreService;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngFileEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngFileRepository;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -31,6 +35,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -42,6 +47,10 @@ public class MapSheetMngFileCheckerService {
|
||||
|
||||
private final MapSheetMngFileCheckerCoreService mapSheetMngFileCheckerCoreService;
|
||||
private final FileConfig fileConfig;
|
||||
private final MapSheetMngFileRepository mapSheetMngFileRepository;
|
||||
|
||||
@Value("${mapsheet.upload.skipGdalValidation:false}")
|
||||
private boolean skipGdalValidation;
|
||||
|
||||
public FoldersDto getFolderAll(SrchFoldersDto srchDto) {
|
||||
|
||||
@@ -308,41 +317,145 @@ public class MapSheetMngFileCheckerService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String uploadFile(MultipartFile file, String targetPath) {
|
||||
public String uploadFile(MultipartFile file, String targetPath, boolean overwrite) {
|
||||
try {
|
||||
Path path = Paths.get(targetPath);
|
||||
// If targetPath is a directory, append the original filename
|
||||
if (Files.isDirectory(path)) {
|
||||
path = path.resolve(file.getOriginalFilename());
|
||||
} else if (Files.notExists(path) && Files.isDirectory(path.getParent())) {
|
||||
// If path doesn't exist but parent is a directory, assume it's the full path
|
||||
// (No change needed)
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
if (path.getParent() != null) {
|
||||
Files.createDirectories(path.getParent());
|
||||
}
|
||||
|
||||
// Save file
|
||||
String filename = path.getFileName().toString();
|
||||
String ext = FilenameUtils.getExtension(filename).toLowerCase();
|
||||
String baseName = FilenameUtils.getBaseName(filename);
|
||||
Path tfwPath =
|
||||
path.getParent() == null
|
||||
? Paths.get(baseName + ".tfw")
|
||||
: path.getParent().resolve(baseName + ".tfw");
|
||||
Path tifPath =
|
||||
path.getParent() == null
|
||||
? Paths.get(baseName + ".tif")
|
||||
: path.getParent().resolve(baseName + ".tif");
|
||||
|
||||
// 이미 존재하는 경우 처리
|
||||
if (Files.exists(path) && !overwrite) {
|
||||
throw new DuplicateFileException("동일한 파일이 이미 존재합니다: " + path.getFileName());
|
||||
}
|
||||
|
||||
// 업로드 파일 저장(덮어쓰기 허용 시 replace)
|
||||
file.transferTo(path.toFile());
|
||||
|
||||
// Check TIF using Gdal functionality
|
||||
String ext = FilenameUtils.getExtension(path.getFileName().toString());
|
||||
if ("tif".equalsIgnoreCase(ext) || "tiff".equalsIgnoreCase(ext)) {
|
||||
boolean isValid = FIleChecker.cmmndGdalInfo(path.toString());
|
||||
if (!isValid) {
|
||||
Files.delete(path); // Delete invalid file
|
||||
throw new RuntimeException("유효하지 않은 TIF 파일입니다 (Gdal 검증 실패).");
|
||||
if ("tfw".equals(ext)) {
|
||||
// TFW 검증
|
||||
boolean tfwOk = FIleChecker.checkTfw(path.toString());
|
||||
if (!tfwOk) {
|
||||
Files.deleteIfExists(path);
|
||||
throw new ValidationException(
|
||||
"유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + path.getFileName());
|
||||
}
|
||||
// 안내: 같은 베이스의 TIF가 없으면 추후 TIF 업로드 필요
|
||||
if (!Files.exists(tifPath)) {
|
||||
// DB 메타 저장은 진행 (향후 쌍 검증 위해)
|
||||
saveUploadMeta(path);
|
||||
return "TFW 업로드 성공 (매칭되는 TIF가 아직 없습니다).";
|
||||
}
|
||||
// TIF가 존재하면 쌍 요건 충족
|
||||
saveUploadMeta(path);
|
||||
return "TFW 업로드 성공";
|
||||
}
|
||||
|
||||
if ("tif".equals(ext) || "tiff".equals(ext)) {
|
||||
// GDAL 검증 (플래그에 따라 스킵)
|
||||
if (!skipGdalValidation) {
|
||||
boolean isValidTif = FIleChecker.cmmndGdalInfo(path.toString());
|
||||
if (!isValidTif) {
|
||||
Files.deleteIfExists(path);
|
||||
throw new ValidationException("유효하지 않은 TIF 파일입니다 (GDAL 검증 실패): " + path.getFileName());
|
||||
}
|
||||
}
|
||||
// TFW 존재/검증
|
||||
if (!Files.exists(tfwPath)) {
|
||||
Files.deleteIfExists(path);
|
||||
throw new ValidationException("TFW 파일이 존재하지 않습니다: " + tfwPath.getFileName());
|
||||
}
|
||||
boolean tfwOk = FIleChecker.checkTfw(tfwPath.toString());
|
||||
if (!tfwOk) {
|
||||
Files.deleteIfExists(path);
|
||||
throw new ValidationException(
|
||||
"유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwPath.getFileName());
|
||||
}
|
||||
saveUploadMeta(path);
|
||||
return skipGdalValidation ? "TIF 업로드 성공(GDAL 검증 스킵)" : "TIF 업로드 성공";
|
||||
}
|
||||
|
||||
// 기타 확장자: 저장만 하고 메타 기록
|
||||
saveUploadMeta(path);
|
||||
|
||||
return "업로드 성공";
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("파일 업로드 실패: " + e.getMessage());
|
||||
throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveUploadMeta(Path savedPath) {
|
||||
String fullPath = savedPath.toAbsolutePath().toString();
|
||||
String fileName = savedPath.getFileName().toString();
|
||||
String ext = FilenameUtils.getExtension(fileName);
|
||||
|
||||
// 연도(mng_yyyy) 추출: 경로 내의 연도 폴더명을 찾음 (예: .../original-images/2022/2022_25cm/...)
|
||||
Integer mngYyyy = extractYearFromPath(fullPath);
|
||||
|
||||
// 도엽번호(map_sheet_num) 추정: 파일명 내 숫자 연속 부분을 추출해 정수화
|
||||
String mapSheetNum = extractMapSheetNumFromFileName(fileName);
|
||||
|
||||
// ref_map_sheet_num: 1000으로 나눈 값(파일명 규칙에 따라 추정)
|
||||
String refMapSheetNum = null;
|
||||
if (mapSheetNum != null && !mapSheetNum.isEmpty()) {
|
||||
try {
|
||||
long num = Long.parseLong(mapSheetNum);
|
||||
refMapSheetNum = String.valueOf(num / 1000);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
MapSheetMngFileEntity entity = new MapSheetMngFileEntity();
|
||||
entity.setMngYyyy(mngYyyy);
|
||||
entity.setMapSheetNum(mapSheetNum);
|
||||
entity.setRefMapSheetNum(refMapSheetNum);
|
||||
entity.setFilePath(savedPath.getParent() != null ? savedPath.getParent().toString() : "");
|
||||
entity.setFileName(fileName);
|
||||
entity.setFileExt(ext);
|
||||
|
||||
mapSheetMngFileRepository.save(entity);
|
||||
}
|
||||
|
||||
private Integer extractYearFromPath(String fullPath) {
|
||||
// 경로에서 4자리 연도를 찾아 가장 근접한 값을 사용
|
||||
// 예시 경로: /Users/.../original-images/2022/2022_25cm/1/34602
|
||||
String[] parts = fullPath.split("/");
|
||||
for (String p : parts) {
|
||||
if (p.matches("\\d{4}")) {
|
||||
try {
|
||||
return Integer.parseInt(p);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String extractMapSheetNumFromFileName(String fileName) {
|
||||
// 파일명에서 연속된 숫자를 최대한 찾아 사용 (예: 34602027.tif -> 34602027)
|
||||
String base = FilenameUtils.getBaseName(fileName);
|
||||
String digits = base.replaceAll("[^0-9]", "");
|
||||
if (!digits.isEmpty()) {
|
||||
return digits;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Boolean deleteFile(String filePath) {
|
||||
try {
|
||||
@@ -352,4 +465,22 @@ public class MapSheetMngFileCheckerService {
|
||||
throw new RuntimeException("파일 삭제 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<MapSheetMngFileEntity> findRecentFiles(int limit) {
|
||||
// 간단히 전체를 불러 정렬/제한 (운영에선 Page 요청으로 변경 권장)
|
||||
List<MapSheetMngFileEntity> all = new ArrayList<>();
|
||||
mapSheetMngFileRepository.findAll().forEach(all::add);
|
||||
all.sort(
|
||||
(a, b) -> {
|
||||
// fileUid 기준 내림차순
|
||||
long av = a.getFileUid() == null ? 0L : a.getFileUid();
|
||||
long bv = b.getFileUid() == null ? 0L : b.getFileUid();
|
||||
return Long.compare(bv, av);
|
||||
});
|
||||
if (all.size() > limit) {
|
||||
return all.subList(0, limit);
|
||||
}
|
||||
return all;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -28,10 +28,10 @@ public class MapSheetMngFileEntity {
|
||||
|
||||
@NotNull
|
||||
@Column(name = "map_sheet_num", nullable = false)
|
||||
private Integer mapSheetNum;
|
||||
private String mapSheetNum;
|
||||
|
||||
@Column(name = "ref_map_sheet_num")
|
||||
private Integer refMapSheetNum;
|
||||
private String refMapSheetNum;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "file_path")
|
||||
@@ -45,9 +45,9 @@ public class MapSheetMngFileEntity {
|
||||
@Column(name = "file_ext", length = 20)
|
||||
private String fileExt;
|
||||
|
||||
@Column(name = "mng_uid")
|
||||
private Long mngUid;
|
||||
// @Column(name = "mng_uid")
|
||||
// private Long mngUid;
|
||||
|
||||
@Column(name = "hst_uid")
|
||||
private Long hstUid;
|
||||
// @Column(name = "hst_uid")
|
||||
// private Long hstUid;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class MapSheetMngHstEntity extends CommonDateEntity {
|
||||
private String mapSheetPath;
|
||||
|
||||
@Column(name = "ref_map_sheet_num")
|
||||
private Long refMapSheetNum;
|
||||
private String refMapSheetNum;
|
||||
|
||||
@Column(name = "created_uid")
|
||||
private Long createdUid;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngFileEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MapSheetMngFileRepository extends JpaRepository<MapSheetMngFileEntity, Long> {}
|
||||
@@ -170,48 +170,53 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
|
||||
@Override
|
||||
public void deleteByMngYyyyMngAll(int mngYyyy) {
|
||||
|
||||
long deletedFileCount = queryFactory
|
||||
.delete(mapSheetMngFileEntity)
|
||||
.where(mapSheetMngFileEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
long deletedFileCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngFileEntity)
|
||||
.where(mapSheetMngFileEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
|
||||
long deletedHisCount = queryFactory
|
||||
.delete(mapSheetMngHstEntity)
|
||||
.where(mapSheetMngHstEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
long deletedHisCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngHstEntity)
|
||||
.where(mapSheetMngHstEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
|
||||
long deletedMngCount = queryFactory
|
||||
.delete(mapSheetMngEntity)
|
||||
.where(mapSheetMngEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
long deletedMngCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngEntity)
|
||||
.where(mapSheetMngEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void deleteByMngYyyyMng(int mngYyyy) {
|
||||
|
||||
long deletedMngCount = queryFactory
|
||||
.delete(mapSheetMngEntity)
|
||||
.where(mapSheetMngEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
long deletedMngCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngEntity)
|
||||
.where(mapSheetMngEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByMngYyyyMngHst(int mngYyyy) {
|
||||
|
||||
long deletedHisCount = queryFactory
|
||||
.delete(mapSheetMngHstEntity)
|
||||
.where(mapSheetMngHstEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
long deletedHisCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngHstEntity)
|
||||
.where(mapSheetMngHstEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByMngYyyyMngFile(int mngYyyy) {
|
||||
|
||||
long deletedFileCount = queryFactory
|
||||
.delete(mapSheetMngFileEntity)
|
||||
.where(mapSheetMngFileEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
long deletedFileCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngFileEntity)
|
||||
.where(mapSheetMngFileEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,6 +35,16 @@ spring:
|
||||
port: 6379
|
||||
password: kamco
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 1024MB
|
||||
max-request-size: 2048MB
|
||||
file-size-threshold: 10MB
|
||||
|
||||
server:
|
||||
tomcat:
|
||||
max-swallow-size: 2097152000 # 약 2GB
|
||||
|
||||
jwt:
|
||||
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
|
||||
@@ -47,3 +57,15 @@ token:
|
||||
refresh-cookie-name: kamco-dev # 개발용 쿠키 이름
|
||||
refresh-cookie-secure: false # 로컬 http 테스트면 false
|
||||
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
springframework:
|
||||
security: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
mapsheet:
|
||||
upload:
|
||||
skipGdalValidation: true
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,17 @@ spring:
|
||||
port: 6379
|
||||
password: 1234
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 1024MB
|
||||
max-request-size: 2048MB
|
||||
file-size-threshold: 10MB
|
||||
|
||||
server:
|
||||
tomcat:
|
||||
max-swallow-size: 2097152000 # 약 2GB
|
||||
|
||||
jwt:
|
||||
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
|
||||
access-token-validity-in-ms: 86400000 # 1일
|
||||
@@ -38,4 +49,13 @@ token:
|
||||
refresh-cookie-name: kamco-local # 개발용 쿠키 이름
|
||||
refresh-cookie-secure: false # 로컬 http 테스트면 false
|
||||
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
springframework:
|
||||
security: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
mapsheet:
|
||||
upload:
|
||||
skipGdalValidation: true
|
||||
|
||||
@@ -41,6 +41,7 @@ logging:
|
||||
web: DEBUG
|
||||
security: DEBUG
|
||||
root: INFO
|
||||
org.springframework.security: DEBUG
|
||||
# actuator
|
||||
management:
|
||||
health:
|
||||
@@ -77,4 +78,3 @@ geojson:
|
||||
- tar.gz
|
||||
- tgz
|
||||
max-file-size: 104857600 # 100MB
|
||||
|
||||
|
||||
1
test_data/fake.tif
Normal file
1
test_data/fake.tif
Normal file
@@ -0,0 +1 @@
|
||||
fake tif content
|
||||
Reference in New Issue
Block a user