Merge pull request 'feat/dev_251201' (#52) from feat/dev_251201 into develop
Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/52
This commit is contained in:
0
login.json
Normal file
0
login.json
Normal file
@@ -11,7 +11,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -19,7 +18,6 @@ import org.springframework.stereotype.Component;
|
||||
public class CustomAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private final MembersRepository membersRepository;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
@@ -32,24 +30,21 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
|
||||
.findByUserId(username)
|
||||
.orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND));
|
||||
|
||||
// 미사용 상태
|
||||
if (member.getStatus().equals(StatusType.INACTIVE.getId())) {
|
||||
throw new CustomApiException(AuthErrorCode.INACTIVE_ID);
|
||||
}
|
||||
|
||||
// jBCrypt + 커스텀 salt 로 저장된 패스워드 비교
|
||||
if (!BCrypt.checkpw(rawPassword, member.getPassword())) {
|
||||
// 실패 카운트 저장
|
||||
int cnt = member.getLoginFailCount() + 1;
|
||||
if (cnt >= 5) {
|
||||
member.setStatus(StatusType.INACTIVE.getId());
|
||||
}
|
||||
member.setLoginFailCount(cnt);
|
||||
membersRepository.save(member);
|
||||
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
|
||||
}
|
||||
|
||||
// 삭제 상태
|
||||
if (member.getStatus().equals(StatusType.DELETED.getId())) {
|
||||
throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 패스워드 실패 횟수 체크
|
||||
// 로그인 실패 체크
|
||||
if (member.getLoginFailCount() >= 5) {
|
||||
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -15,6 +15,9 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
public class CommonCodeDto {
|
||||
|
||||
@@ -132,6 +135,33 @@ public class CommonCodeDto {
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "SearchReq", description = "검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SearchReq {
|
||||
|
||||
// 검색 조건
|
||||
private String name;
|
||||
|
||||
// 페이징 파라미터
|
||||
private int page = 0;
|
||||
private int size = 20;
|
||||
private String sort;
|
||||
|
||||
public Pageable toPageable() {
|
||||
if (sort != null && !sort.isEmpty()) {
|
||||
String[] sortParams = sort.split(",");
|
||||
String property = sortParams[0];
|
||||
Sort.Direction direction =
|
||||
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
|
||||
return PageRequest.of(page, size, Sort.by(direction, property));
|
||||
}
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Clazzes {
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import lombok.Getter;
|
||||
@AllArgsConstructor
|
||||
public enum StatusType implements EnumType {
|
||||
ACTIVE("활성"),
|
||||
INACTIVE("비활성"),
|
||||
DELETED("삭제");
|
||||
INACTIVE("미사용"),
|
||||
PENDING("보류");
|
||||
|
||||
private final String desc;
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ public enum AuthErrorCode implements ErrorCode {
|
||||
|
||||
LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED);
|
||||
LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
INACTIVE_ID("INACTIVE_ID", HttpStatus.UNAUTHORIZED);
|
||||
|
||||
private final String code;
|
||||
private final HttpStatus status;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
|
||||
import java.util.regex.Pattern;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
public class CommonStringUtils {
|
||||
|
||||
/**
|
||||
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
|
||||
*
|
||||
* @param password 벨리데이션 필요한 패스워드
|
||||
* @return
|
||||
*/
|
||||
public static boolean isValidPassword(String password) {
|
||||
String passwordPattern =
|
||||
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
|
||||
return Pattern.matches(passwordPattern, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 암호화
|
||||
*
|
||||
* @param password 암호화 필요한 패스워드
|
||||
* @param employeeNo salt 생성에 필요한 사원번호
|
||||
* @return
|
||||
*/
|
||||
public static String hashPassword(String password, String employeeNo) {
|
||||
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(employeeNo.trim());
|
||||
return BCrypt.hashpw(password.trim(), salt);
|
||||
}
|
||||
}
|
||||
@@ -142,34 +142,11 @@ public class FIleChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
String resStr = "";
|
||||
boolean hasDriver = false;
|
||||
|
||||
// 리눅스/맥용
|
||||
// ProcessBuilder pb = new ProcessBuilder("sh", "-c", "gdalinfo "+filePath+" | grep -i 'Geo'");
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
|
||||
// 윈도우용
|
||||
/*
|
||||
command.add("cmd.exe"); // 윈도우 명령 프롬프트 실행
|
||||
command.add("/c"); // 명령어를 수행하고 종료한다는 옵션
|
||||
command.add("gdalinfo");
|
||||
command.add(filePath);
|
||||
command.add("|");
|
||||
command.add("findstr");
|
||||
command.add("/i");
|
||||
command.add("Geo");
|
||||
*/
|
||||
|
||||
command.add("sh"); // 리눅스,맥 명령 프롬프트 실행
|
||||
command.add("-c"); // 명령어를 수행하고 종료한다는 옵션
|
||||
command.add("gdalinfo");
|
||||
command.add(filePath);
|
||||
command.add("|");
|
||||
command.add("grep");
|
||||
command.add("-i");
|
||||
command.add("Geo");
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||
processBuilder.redirectErrorStream(true);
|
||||
@@ -177,19 +154,17 @@ public class FIleChecker {
|
||||
try {
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// 인코딩은 윈도우 한글 환경에 맞게 MS949로 지정
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// System.out.println(line);
|
||||
if (line.contains("Driver: GTiff/GeoTIFF")) {
|
||||
hasDriver = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int exitCode = process.waitFor();
|
||||
process.waitFor();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -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,27 +161,6 @@ 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)
|
||||
public ApiResponseDto<String> handlerDataIntegrityViolationException(
|
||||
@@ -435,23 +470,38 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
|
||||
@ExceptionHandler(CustomApiException.class)
|
||||
public ResponseEntity<ApiResponseDto<String>> handleCustomApiException(
|
||||
public ApiResponseDto<String> handleCustomApiException(
|
||||
CustomApiException e, HttpServletRequest request) {
|
||||
log.warn("[CustomApiException] resource : {}", e.getMessage());
|
||||
|
||||
String codeName = e.getCodeName();
|
||||
HttpStatus status = e.getStatus();
|
||||
String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) : e.getMessage();
|
||||
|
||||
ApiResponseCode apiCode = ApiResponseCode.getCode(codeName);
|
||||
// String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) :
|
||||
// e.getMessage();
|
||||
//
|
||||
// ApiResponseCode apiCode = ApiResponseCode.getCode(codeName);
|
||||
//
|
||||
// ErrorLogEntity errorLog =
|
||||
// saveErrorLogData(
|
||||
// request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace());
|
||||
//
|
||||
// ApiResponseDto<String> body =
|
||||
// ApiResponseDto.createException(apiCode, message, status, errorLog.getId());
|
||||
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace());
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(status.value()),
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
ApiResponseDto<String> body =
|
||||
ApiResponseDto.createException(apiCode, message, status, errorLog.getId());
|
||||
return ApiResponseDto.createException(
|
||||
ApiResponseCode.getCode(codeName),
|
||||
ApiResponseCode.getMessage(codeName),
|
||||
HttpStatus.valueOf(status.value()),
|
||||
errorLog.getId());
|
||||
|
||||
return new ResponseEntity<>(body, status);
|
||||
// return new ResponseEntity<>(body, status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
@@ -54,6 +62,8 @@ public class SecurityConfig {
|
||||
// ADMIN, REVIEWER 접근
|
||||
.requestMatchers("/api/test/review")
|
||||
.hasAnyRole("ADMIN", "REVIEWER")
|
||||
.requestMatchers("/error")
|
||||
.permitAll()
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**")
|
||||
.permitAll() // preflight 허용
|
||||
.requestMatchers(
|
||||
@@ -98,4 +108,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ public class ApiResponseDto<T> {
|
||||
LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."),
|
||||
LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."),
|
||||
LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."),
|
||||
INACTIVE_ID("미사용 아이디 입니다."),
|
||||
INVALID_EMAIL_TOKEN(
|
||||
"You can only reset your password within 24 hours from when the email was sent.\n"
|
||||
+ "To reset your password again, please submit a new request through \"Forgot"
|
||||
|
||||
@@ -26,18 +26,6 @@ public class MapSheetMngApiController {
|
||||
private final CommonCodeService commonCodeService;
|
||||
private final MapSheetMngService mapSheetMngService;
|
||||
|
||||
/**
|
||||
* 오류데이터 목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/error-list")
|
||||
public ApiResponseDto<Page<MapSheetMngDto.ErrorDataDto>> findMapSheetErrorList(
|
||||
@RequestBody @Valid MapSheetMngDto.searchReq searchReq) {
|
||||
return ApiResponseDto.ok(mapSheetMngService.findMapSheetErrorList(searchReq));
|
||||
}
|
||||
|
||||
@Operation(summary = "영상데이터관리목록 조회", description = "영상데이터관리목록 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@@ -53,7 +41,7 @@ public class MapSheetMngApiController {
|
||||
})
|
||||
@PostMapping("/mng-list")
|
||||
public ApiResponseDto<Page<MapSheetMngDto.MngDto>> findMapSheetMngList(
|
||||
@RequestBody @Valid MapSheetMngDto.searchReq searchReq) {
|
||||
@RequestBody MapSheetMngDto.MngSearchReq searchReq) {
|
||||
return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngList(searchReq));
|
||||
}
|
||||
|
||||
@@ -77,6 +65,18 @@ public class MapSheetMngApiController {
|
||||
return ApiResponseDto.ok(mapSheetMngService.mngDataSave(AddReq));
|
||||
}
|
||||
|
||||
/**
|
||||
* 오류데이터 목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/error-list")
|
||||
public ApiResponseDto<Page<MapSheetMngDto.ErrorDataDto>> findMapSheetErrorList(
|
||||
@RequestBody @Valid MapSheetMngDto.ErrorSearchReq searchReq) {
|
||||
return ApiResponseDto.ok(mapSheetMngService.findMapSheetErrorList(searchReq));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hstUidList
|
||||
* @return
|
||||
@@ -86,7 +86,7 @@ public class MapSheetMngApiController {
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "201",
|
||||
description = "공통코드 저장 성공",
|
||||
description = "업로드 처리 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
|
||||
@@ -15,10 +15,14 @@ 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 lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
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;
|
||||
|
||||
@Tag(name = "영상 관리", description = "영상 관리 API")
|
||||
@RestController
|
||||
@@ -67,6 +71,45 @@ public class MapSheetMngFileCheckerApiController {
|
||||
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.getFilesAll(srchDto));
|
||||
}
|
||||
|
||||
@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,
|
||||
@RequestParam(value = "overwrite", required = false, defaultValue = "false")
|
||||
boolean overwrite,
|
||||
@RequestParam(value = "hstUid", required = false) Long hstUid) {
|
||||
return ApiResponseDto.createOK(
|
||||
mapSheetMngFileCheckerService.uploadFile(file, targetPath, overwrite, hstUid));
|
||||
}
|
||||
|
||||
@Operation(summary = "페어 파일 업로드", description = "TFW/TIF 두 파일을 쌍으로 업로드 및 검증")
|
||||
@PostMapping(value = "/upload-pair", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ApiResponseDto<String> uploadPair(
|
||||
@RequestPart("tfw") MultipartFile tfwFile,
|
||||
@RequestPart("tif") MultipartFile tifFile,
|
||||
@RequestParam("targetPath") String targetPath,
|
||||
@RequestParam(value = "overwrite", required = false, defaultValue = "false")
|
||||
boolean overwrite,
|
||||
@RequestParam(value = "hstUid", required = false) Long hstUid) {
|
||||
return ApiResponseDto.createOK(
|
||||
mapSheetMngFileCheckerService.uploadPair(tfwFile, tifFile, targetPath, overwrite, hstUid));
|
||||
}
|
||||
|
||||
@Operation(summary = "파일 삭제", description = "중복 파일 등 파일 삭제")
|
||||
@PostMapping("/delete")
|
||||
public ApiResponseDto<Boolean> deleteFile(@RequestBody SrchFoldersDto dto) {
|
||||
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.deleteFile(dto.getDirPath()));
|
||||
}
|
||||
|
||||
@Operation(summary = "중복 파일 삭제", description = "중복 데이터 발견 시 기존 데이터를 삭제")
|
||||
@PostMapping(value = "/delete-file")
|
||||
public ApiResponseDto<String> deleteDuplicateFile(
|
||||
@RequestParam("filePath") String filePath, @RequestParam("fileName") String fileName) {
|
||||
return ApiResponseDto.createOK(
|
||||
mapSheetMngFileCheckerService.deleteDuplicate(filePath, fileName));
|
||||
}
|
||||
|
||||
/*
|
||||
@Operation(summary = "지정폴더(하위폴더포함) 파일목록 조회", description = "지정폴더(하위폴더포함) 파일목록 조회")
|
||||
@ApiResponses(
|
||||
|
||||
@@ -14,12 +14,78 @@ import org.springframework.data.domain.Sort;
|
||||
|
||||
public class MapSheetMngDto {
|
||||
|
||||
@Schema(name = "searchReq", description = "영상관리 오류데이터 검색 요청")
|
||||
@Schema(name = "MngSearchReq", description = "영상관리 검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class searchReq {
|
||||
public static class MngSearchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
|
||||
private int page = 0;
|
||||
|
||||
@Schema(description = "페이지 크기", example = "20")
|
||||
private int size = 20;
|
||||
|
||||
@Schema(description = "년도", example = "2025")
|
||||
private Integer mngYyyy;
|
||||
|
||||
public Pageable toPageable() {
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "MngAddReq", description = "영상관리 생성 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AddReq {
|
||||
@Schema(description = "관리년도", example = "2022")
|
||||
private int mngYyyy;
|
||||
|
||||
@Schema(description = "선택폴더경로", example = "D:\\app\\original-images\\2022")
|
||||
private String mngPath;
|
||||
}
|
||||
|
||||
@Schema(name = "DeleteFileReq", description = "파일 삭제 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class DeleteFileReq {
|
||||
@Schema(description = "파일 경로", example = "/app/original-images/2024/00000001.tif")
|
||||
private String filePath;
|
||||
}
|
||||
|
||||
@Schema(name = "MngDto", description = "영상관리 검색 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class MngDto {
|
||||
private int rowNum;
|
||||
private int mngYyyy;
|
||||
private String mngState;
|
||||
private String syncState;
|
||||
private String syncCheckState;
|
||||
private Long syncTotCnt;
|
||||
private Long syncStateDoneCnt;
|
||||
private Long syncCheckStateDoneCnt;
|
||||
private Long syncNotFileCnt;
|
||||
private Long syncTypeErrorCnt;
|
||||
private Long syncSizeErrorCnt;
|
||||
@JsonFormatDttm private ZonedDateTime rgstStrtDttm;
|
||||
@JsonFormatDttm private ZonedDateTime rgstEndDttm;
|
||||
}
|
||||
|
||||
@Schema(name = "ErrorSearchReq", description = "영상관리 오류데이터 검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ErrorSearchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
|
||||
@@ -64,40 +130,6 @@ public class MapSheetMngDto {
|
||||
private DataState dataState;
|
||||
}
|
||||
|
||||
@Schema(name = "MngAddReq", description = "영상관리 생성 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AddReq {
|
||||
@Schema(description = "관리년도", example = "2022")
|
||||
private int mngYyyy;
|
||||
|
||||
@Schema(description = "선택폴더경로", example = "D:\\app\\original-images\\2022")
|
||||
private String mngPath;
|
||||
}
|
||||
|
||||
@Schema(name = "MngDto", description = "영상관리 검색 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class MngDto {
|
||||
private int rowNum;
|
||||
private int mngYyyy;
|
||||
private String mngState;
|
||||
private String syncState;
|
||||
private String syncCheckState;
|
||||
private Long syncTotCnt;
|
||||
private Long syncStateDoneCnt;
|
||||
private Long syncCheckStateDoneCnt;
|
||||
private Long syncNotFileCnt;
|
||||
private Long syncTypeErrorCnt;
|
||||
private Long syncSizeErrorCnt;
|
||||
@JsonFormatDttm private ZonedDateTime rgstStrtDttm;
|
||||
@JsonFormatDttm private ZonedDateTime rgstEndDttm;
|
||||
}
|
||||
|
||||
@Schema(name = "DmlReturn", description = "영상관리 DML 수행 후 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
@@ -2,6 +2,9 @@ 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;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto;
|
||||
@@ -13,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;
|
||||
@@ -32,6 +37,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -40,6 +46,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) {
|
||||
|
||||
@@ -304,4 +314,283 @@ public class MapSheetMngFileCheckerService {
|
||||
public ImageryDto.SyncReturn syncProcess(ImageryDto.searchReq searchReq) {
|
||||
return mapSheetMngFileCheckerCoreService.syncProcess(searchReq);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String uploadFile(MultipartFile file, String targetPath, boolean overwrite, Long hstUid) {
|
||||
try {
|
||||
Path path = Paths.get(targetPath);
|
||||
if (Files.isDirectory(path)) {
|
||||
path = path.resolve(file.getOriginalFilename());
|
||||
}
|
||||
if (path.getParent() != null) {
|
||||
Files.createDirectories(path.getParent());
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
// DB 중복 체크
|
||||
String parentPathStr = path.getParent() != null ? path.getParent().toString() : "";
|
||||
boolean dbExists =
|
||||
mapSheetMngFileRepository.existsByFileNameAndFilePath(filename, parentPathStr);
|
||||
// boolean fileExists = Files.exists(path); // 파일 시스템 존재 여부는 체크하지 않음 (DB 기준)
|
||||
|
||||
// 이미 존재하는 경우 처리 (DB에만 있는 경우 체크)
|
||||
if (!overwrite && dbExists) {
|
||||
throw new DuplicateFileException("동일한 파일이 이미 존재합니다 (DB): " + path.getFileName());
|
||||
}
|
||||
|
||||
// 덮어쓰기인 경우 기존 DB 데이터 삭제 (새로 저장하기 위함)
|
||||
if (overwrite && dbExists) {
|
||||
mapSheetMngFileRepository.deleteByFileNameAndFilePath(filename, parentPathStr);
|
||||
}
|
||||
|
||||
// 업로드 파일 저장(덮어쓰기 허용 시 replace)
|
||||
file.transferTo(path.toFile());
|
||||
|
||||
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, hstUid);
|
||||
return "TFW 업로드 성공 (매칭되는 TIF가 아직 없습니다).";
|
||||
}
|
||||
// TIF가 존재하면 쌍 요건 충족
|
||||
saveUploadMeta(path, hstUid);
|
||||
return "TFW 업로드 성공";
|
||||
}
|
||||
|
||||
if ("tif".equals(ext) || "tiff".equals(ext)) {
|
||||
// GDAL 검증 (항상 수행)
|
||||
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, hstUid);
|
||||
return "TIF 업로드 성공";
|
||||
}
|
||||
|
||||
// 기타 확장자: 저장만 하고 메타 기록
|
||||
saveUploadMeta(path, hstUid);
|
||||
|
||||
return "업로드 성공";
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String uploadPair(
|
||||
MultipartFile tfwFile,
|
||||
MultipartFile tifFile,
|
||||
String targetPath,
|
||||
boolean overwrite,
|
||||
Long hstUid) {
|
||||
try {
|
||||
Path basePath = Paths.get(targetPath);
|
||||
if (Files.isDirectory(basePath)) {
|
||||
// 디렉토리인 경우 파일명 기준으로 경로 생성
|
||||
Path tfwPath = basePath.resolve(tfwFile.getOriginalFilename());
|
||||
Path tifPath = basePath.resolve(tifFile.getOriginalFilename());
|
||||
// 동일 베이스명 확인
|
||||
String tfwBase = FilenameUtils.getBaseName(tfwPath.getFileName().toString());
|
||||
String tifBase = FilenameUtils.getBaseName(tifPath.getFileName().toString());
|
||||
if (!tfwBase.equalsIgnoreCase(tifBase)) {
|
||||
throw new ValidationException("TFW/TIF 파일명이 동일한 베이스가 아닙니다.");
|
||||
}
|
||||
// 디렉토리 생성
|
||||
if (tfwPath.getParent() != null) Files.createDirectories(tfwPath.getParent());
|
||||
if (tifPath.getParent() != null) Files.createDirectories(tifPath.getParent());
|
||||
|
||||
// DB 중복 체크 및 overwrite 처리 (각 파일별)
|
||||
String parentPathStr = basePath.toString();
|
||||
String tfwName = tfwPath.getFileName().toString();
|
||||
String tifName = tifPath.getFileName().toString();
|
||||
boolean tfwDbExists =
|
||||
mapSheetMngFileRepository.existsByFileNameAndFilePath(tfwName, parentPathStr);
|
||||
boolean tifDbExists =
|
||||
mapSheetMngFileRepository.existsByFileNameAndFilePath(tifName, parentPathStr);
|
||||
if (!overwrite && (tfwDbExists || tifDbExists)) {
|
||||
throw new DuplicateFileException("동일한 파일이 이미 존재합니다 (DB): " + tfwName + ", " + tifName);
|
||||
}
|
||||
if (overwrite) {
|
||||
if (tfwDbExists)
|
||||
mapSheetMngFileRepository.deleteByFileNameAndFilePath(tfwName, parentPathStr);
|
||||
if (tifDbExists)
|
||||
mapSheetMngFileRepository.deleteByFileNameAndFilePath(tifName, parentPathStr);
|
||||
}
|
||||
|
||||
// 파일 저장
|
||||
tfwFile.transferTo(tfwPath.toFile());
|
||||
tifFile.transferTo(tifPath.toFile());
|
||||
|
||||
// 검증
|
||||
boolean tfwOk = FIleChecker.checkTfw(tfwPath.toString());
|
||||
if (!tfwOk) {
|
||||
Files.deleteIfExists(tfwPath);
|
||||
Files.deleteIfExists(tifPath);
|
||||
throw new ValidationException("유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwName);
|
||||
}
|
||||
boolean isValidTif = FIleChecker.cmmndGdalInfo(tifPath.toString());
|
||||
if (!isValidTif) {
|
||||
Files.deleteIfExists(tfwPath);
|
||||
Files.deleteIfExists(tifPath);
|
||||
throw new ValidationException("유효하지 않은 TIF 파일입니다 (GDAL 검증 실패): " + tifName);
|
||||
}
|
||||
|
||||
// 메타 저장 (두 파일 각각 저장)
|
||||
saveUploadMeta(tfwPath, hstUid);
|
||||
saveUploadMeta(tifPath, hstUid);
|
||||
return "TFW/TIF 페어 업로드 성공";
|
||||
} else {
|
||||
throw new ValidationException("targetPath는 디렉토리여야 합니다.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveUploadMeta(Path savedPath, Long hstUid) {
|
||||
String fullPath = savedPath.toAbsolutePath().toString();
|
||||
String fileName = savedPath.getFileName().toString();
|
||||
String ext = FilenameUtils.getExtension(fileName);
|
||||
|
||||
MapSheetMngFileEntity entity = new MapSheetMngFileEntity();
|
||||
|
||||
if (hstUid != null) {
|
||||
// 히스토리에서 메타 가져오기
|
||||
var hstOpt = mapSheetMngFileCheckerCoreService.findHstByUid(hstUid);
|
||||
hstOpt.ifPresent(
|
||||
hst -> {
|
||||
entity.setHstUid(hst.getHstUid());
|
||||
entity.setMngYyyy(hst.getMngYyyy());
|
||||
entity.setMapSheetNum(hst.getMapSheetNum());
|
||||
entity.setRefMapSheetNum(hst.getRefMapSheetNum());
|
||||
});
|
||||
} else {
|
||||
// 기존 추정 로직 유지
|
||||
Integer mngYyyy = extractYearFromPath(fullPath);
|
||||
String mapSheetNum = extractMapSheetNumFromFileName(fileName);
|
||||
String refMapSheetNum = null;
|
||||
if (mapSheetNum != null && !mapSheetNum.isEmpty()) {
|
||||
try {
|
||||
long num = Long.parseLong(mapSheetNum);
|
||||
refMapSheetNum = String.valueOf(num / 1000);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
entity.setMngYyyy(mngYyyy);
|
||||
entity.setMapSheetNum(mapSheetNum);
|
||||
entity.setRefMapSheetNum(refMapSheetNum);
|
||||
}
|
||||
|
||||
entity.setFilePath(savedPath.getParent() != null ? savedPath.getParent().toString() : "");
|
||||
entity.setFileName(fileName);
|
||||
entity.setFileExt(ext);
|
||||
|
||||
// 파일 크기 설정
|
||||
try {
|
||||
long size = Files.size(savedPath);
|
||||
entity.setFileSize(size);
|
||||
} catch (IOException e) {
|
||||
entity.setFileSize(0L);
|
||||
}
|
||||
|
||||
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 {
|
||||
Path path = Paths.get(filePath);
|
||||
return Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String deleteDuplicate(String filePath, String fileName) {
|
||||
try {
|
||||
Path path = Paths.get(filePath, fileName);
|
||||
boolean deleted = Files.deleteIfExists(path);
|
||||
// DB에서도 삭제
|
||||
mapSheetMngFileRepository.deleteByFileNameAndFilePath(fileName, filePath);
|
||||
return deleted ? "파일 및 DB 레코드 삭제 완료" : "DB 레코드 삭제 완료 (파일 미존재)";
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("중복 파일 삭제 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,19 +213,30 @@ public class MapSheetMngService {
|
||||
}
|
||||
|
||||
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
MapSheetMngDto.@Valid ErrorSearchReq searchReq) {
|
||||
return mapSheetMngCoreService.findMapSheetErrorList(searchReq);
|
||||
}
|
||||
|
||||
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(MapSheetMngDto.MngSearchReq searchReq) {
|
||||
return mapSheetMngCoreService.findMapSheetMngList(searchReq);
|
||||
}
|
||||
|
||||
public MapSheetMngDto.DmlReturn mngDataSave(@Valid MapSheetMngDto.AddReq AddReq) {
|
||||
@Transactional
|
||||
public MapSheetMngDto.DmlReturn mngDataSave(MapSheetMngDto.AddReq AddReq) {
|
||||
return mapSheetMngCoreService.mngDataSave(AddReq);
|
||||
}
|
||||
|
||||
/*
|
||||
public MapSheetMngDto.DmlReturn uploadFile(MultipartFile file, Long hstUid) {
|
||||
return mapSheetMngCoreService.uploadFile(file, hstUid);
|
||||
}
|
||||
|
||||
public MapSheetMngDto.DmlReturn deleteFile(MapSheetMngDto.DeleteFileReq req) {
|
||||
return mapSheetMngCoreService.deleteFile(req);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
public MapSheetMngDto.DmlReturn uploadProcess(@Valid List<Long> hstUidList) {
|
||||
return mapSheetMngCoreService.uploadProcess(hstUidList);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
@@ -87,24 +86,4 @@ public class AdminApiController {
|
||||
adminService.updateMembers(uuid, updateReq);
|
||||
return ApiResponseDto.createOK(UUID.randomUUID());
|
||||
}
|
||||
|
||||
@Operation(summary = "관리자 계정 미사용 처리", description = "관리자 계정 미사용 처리")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "201",
|
||||
description = "관리자 계정 미사용 처리",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = UUID.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@DeleteMapping("/delete/{uuid}")
|
||||
public ApiResponseDto<UUID> deleteAccount(@PathVariable UUID uuid) {
|
||||
adminService.deleteAccount(uuid);
|
||||
return ApiResponseDto.createOK(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import com.kamco.cd.kamcoback.auth.CustomUserDetails;
|
||||
import com.kamco.cd.kamcoback.auth.JwtTokenProvider;
|
||||
import com.kamco.cd.kamcoback.auth.RefreshTokenService;
|
||||
import com.kamco.cd.kamcoback.common.enums.StatusType;
|
||||
import com.kamco.cd.kamcoback.common.enums.error.AuthErrorCode;
|
||||
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.SignInRequest;
|
||||
@@ -112,20 +110,13 @@ public class AuthController {
|
||||
Authentication authentication = null;
|
||||
MembersDto.Member member = new MembersDto.Member();
|
||||
|
||||
// 비활성 상태면 임시패스워드를 비교함
|
||||
if (StatusType.INACTIVE.getId().equals(status)) {
|
||||
if (!authService.isTempPasswordValid(request)) {
|
||||
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
|
||||
}
|
||||
} else {
|
||||
authentication =
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
request.getUsername(), request.getPassword()));
|
||||
}
|
||||
authentication =
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
||||
|
||||
// INACTIVE 비활성 상태(새로운 패스워드 입력 해야함), DELETED 탈퇴
|
||||
if (!StatusType.ACTIVE.getId().equals(status)) {
|
||||
// PENDING 비활성 상태(새로운 패스워드 입력 해야함)
|
||||
if (StatusType.PENDING.getId().equals(status)) {
|
||||
member.setEmployeeNo(request.getUsername());
|
||||
return ApiResponseDto.ok(new TokenResponse(status, null, null, member));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -70,10 +69,6 @@ public class MembersApiController {
|
||||
@PatchMapping("/{memberId}/password")
|
||||
public ApiResponseDto<String> resetPassword(
|
||||
@PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) {
|
||||
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(memberId, initReq.getTempPassword()));
|
||||
|
||||
membersService.resetPassword(memberId, initReq);
|
||||
return ApiResponseDto.createOK(memberId);
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@ public class MembersDto {
|
||||
private String userRole;
|
||||
private String userRoleName;
|
||||
private String name;
|
||||
private String userId;
|
||||
private String employeeNo;
|
||||
private String tempPassword;
|
||||
private String status;
|
||||
private String statusName;
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
@@ -43,9 +41,7 @@ public class MembersDto {
|
||||
UUID uuid,
|
||||
String userRole,
|
||||
String name,
|
||||
String userId,
|
||||
String employeeNo,
|
||||
String tempPassword,
|
||||
String status,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime updatedDttm,
|
||||
@@ -56,9 +52,7 @@ public class MembersDto {
|
||||
this.userRole = userRole;
|
||||
this.userRoleName = getUserRoleName(userRole);
|
||||
this.name = name;
|
||||
this.userId = userId;
|
||||
this.employeeNo = employeeNo;
|
||||
this.tempPassword = tempPassword;
|
||||
this.status = status;
|
||||
this.statusName = getStatusName(status);
|
||||
this.createdDttm = createdDttm;
|
||||
@@ -108,32 +102,28 @@ public class MembersDto {
|
||||
|
||||
@Schema(description = "관리자 유형", example = "ADMIN")
|
||||
@NotBlank
|
||||
@EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER만 가능합니다.")
|
||||
@EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "사번", example = "K20251212001")
|
||||
@Size(max = 50)
|
||||
private String employeeNo;
|
||||
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
@NotBlank
|
||||
@Size(min = 2, max = 100)
|
||||
private String name;
|
||||
|
||||
@Schema(description = "ID", example = "gildong")
|
||||
@NotBlank
|
||||
@Size(min = 2, max = 50)
|
||||
private String userId;
|
||||
@Schema(description = "패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
private String password;
|
||||
|
||||
@Schema(description = "임시 비밀번호", example = "q!w@e#r4")
|
||||
private String tempPassword;
|
||||
|
||||
@Schema(description = "사번", example = "123456")
|
||||
private String employeeNo;
|
||||
|
||||
public AddReq(
|
||||
String userRole, String name, String userId, String tempPassword, String employeeNo) {
|
||||
public AddReq(String userRole, String employeeNo, String name, String password) {
|
||||
this.userRole = userRole;
|
||||
this.name = name;
|
||||
this.userId = userId;
|
||||
this.tempPassword = tempPassword;
|
||||
this.employeeNo = employeeNo;
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,22 +131,22 @@ public class MembersDto {
|
||||
@Setter
|
||||
public static class UpdateReq {
|
||||
|
||||
@Schema(description = "사번, 패스워드 변경시 필수 값", example = "11111")
|
||||
@Size(max = 50)
|
||||
private String employeeNo;
|
||||
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
@Size(min = 2, max = 100)
|
||||
private String name;
|
||||
|
||||
@Schema(description = "상태", example = "ACTIVE")
|
||||
@EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
private String tempPassword;
|
||||
private String password;
|
||||
|
||||
public UpdateReq(String employeeNo, String name, String tempPassword) {
|
||||
this.employeeNo = employeeNo;
|
||||
public UpdateReq(String name, String status, String password) {
|
||||
this.name = name;
|
||||
this.tempPassword = tempPassword;
|
||||
this.status = status;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,14 +154,15 @@ public class MembersDto {
|
||||
@Setter
|
||||
public static class InitReq {
|
||||
|
||||
@Schema(description = "변경 패스워드", example = "")
|
||||
@Schema(description = "기존 패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
@NotBlank
|
||||
private String password;
|
||||
private String oldPassword;
|
||||
|
||||
@Schema(description = "초기 패스워드", example = "")
|
||||
@Schema(description = "신규 패스워드", example = "")
|
||||
@Size(max = 255)
|
||||
@NotBlank
|
||||
private String tempPassword;
|
||||
private String newPassword;
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
||||
@@ -11,6 +11,7 @@ public class MemberException {
|
||||
|
||||
public enum Field {
|
||||
USER_ID,
|
||||
EMPLOYEE_NO,
|
||||
DEFAULT
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.kamco.cd.kamcoback.members.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||
import com.kamco.cd.kamcoback.common.utils.CommonStringUtils;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -22,6 +25,10 @@ public class AdminService {
|
||||
*/
|
||||
@Transactional
|
||||
public Long saveMember(MembersDto.AddReq addReq) {
|
||||
if (!CommonStringUtils.isValidPassword(addReq.getPassword())) {
|
||||
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return membersCoreService.saveMembers(addReq);
|
||||
}
|
||||
|
||||
@@ -35,14 +42,4 @@ public class AdminService {
|
||||
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
|
||||
membersCoreService.updateMembers(uuid, updateReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 계정 미사용 처리
|
||||
*
|
||||
* @param uuid
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteAccount(UUID uuid) {
|
||||
membersCoreService.deleteAccount(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.kamco.cd.kamcoback.members.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||
import com.kamco.cd.kamcoback.common.utils.CommonStringUtils;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
||||
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -37,21 +37,9 @@ public class MembersService {
|
||||
@Transactional
|
||||
public void resetPassword(String id, MembersDto.InitReq initReq) {
|
||||
|
||||
if (!isValidPassword(initReq.getPassword())) {
|
||||
if (!CommonStringUtils.isValidPassword(initReq.getNewPassword())) {
|
||||
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
membersCoreService.resetPassword(id, initReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
|
||||
*
|
||||
* @param password
|
||||
* @return
|
||||
*/
|
||||
private boolean isValidPassword(String password) {
|
||||
String passwordPattern =
|
||||
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
|
||||
return Pattern.matches(passwordPattern, password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic;
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.SearchReq;
|
||||
import com.kamco.cd.kamcoback.common.service.BaseCoreService;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.code.CommonCodeRepository;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.SearchReq;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -33,12 +33,12 @@ public class MapSheetMngCoreService {
|
||||
private String activeEnv;
|
||||
|
||||
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
MapSheetMngDto.@Valid ErrorSearchReq searchReq) {
|
||||
return mapSheetMngRepository.findMapSheetErrorList(searchReq);
|
||||
}
|
||||
|
||||
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
MapSheetMngDto.@Valid MngSearchReq searchReq) {
|
||||
return mapSheetMngRepository.findMapSheetMngList(searchReq);
|
||||
}
|
||||
|
||||
@@ -52,10 +52,7 @@ public class MapSheetMngCoreService {
|
||||
.findMapSheetMngHstInfo(hstUid)
|
||||
.orElseThrow(EntityNotFoundException::new));
|
||||
|
||||
// TODO: local TEST 시 각자 경로 수정하기
|
||||
// TODO: application.yml 에 active profile : local 로 임시 변경하여 테스트
|
||||
String localPath = "";
|
||||
// String localPath = "C:\\Users\\gypark\\Desktop\\file";
|
||||
String rootDir = ORIGINAL_IMAGES_PATH + "/" + entity.get().getMngYyyy();
|
||||
if (activeEnv.equals("local")) {
|
||||
rootDir = localPath + rootDir;
|
||||
@@ -68,6 +65,59 @@ public class MapSheetMngCoreService {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// 파일 크기 계산 및 저장
|
||||
try (Stream<Path> paths = Files.walk(Paths.get(rootDir))) {
|
||||
List<Path> matched =
|
||||
paths
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(
|
||||
p -> {
|
||||
String name = p.getFileName().toString();
|
||||
return name.equals(filename + ".tif") || name.equals(filename + ".tfw");
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long tifSize =
|
||||
matched.stream()
|
||||
.filter(p -> p.getFileName().toString().endsWith(".tif"))
|
||||
.mapToLong(
|
||||
p -> {
|
||||
try {
|
||||
return Files.size(p);
|
||||
} catch (IOException e) {
|
||||
return 0L;
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
|
||||
long tfwSize =
|
||||
matched.stream()
|
||||
.filter(p -> p.getFileName().toString().endsWith(".tfw"))
|
||||
.mapToLong(
|
||||
p -> {
|
||||
try {
|
||||
return Files.size(p);
|
||||
} catch (IOException e) {
|
||||
return 0L;
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
|
||||
entity.get().setTifSizeBytes(tifSize);
|
||||
entity.get().setTfwSizeBytes(tfwSize);
|
||||
entity.get().setTotalSizeBytes(tifSize + tfwSize);
|
||||
|
||||
// 엔터티 저장 -> 커스텀 업데이트로 변경
|
||||
mapSheetMngRepository.updateHstFileSizes(
|
||||
entity.get().getHstUid(), tifSize, tfwSize, tifSize + tfwSize);
|
||||
} catch (IOException e) {
|
||||
// 크기 계산 실패 시 0으로 저장
|
||||
entity.get().setTifSizeBytes(0L);
|
||||
entity.get().setTfwSizeBytes(0L);
|
||||
entity.get().setTotalSizeBytes(0L);
|
||||
mapSheetMngRepository.updateHstFileSizes(entity.get().getHstUid(), 0L, 0L, 0L);
|
||||
}
|
||||
|
||||
/*
|
||||
MapSheetMngDto.DataState dataState =
|
||||
flag ? MapSheetMngDto.DataState.SUCCESS : MapSheetMngDto.DataState.FAIL;
|
||||
@@ -132,7 +182,10 @@ public class MapSheetMngCoreService {
|
||||
entity.setMngYyyy(addReq.getMngYyyy());
|
||||
entity.setMngPath(addReq.getMngPath());
|
||||
|
||||
mapSheetMngRepository.deleteByMngYyyyMngAll(addReq.getMngYyyy());
|
||||
|
||||
MapSheetMngEntity saved = mapSheetMngRepository.save(entity);
|
||||
int hstCnt = mapSheetMngRepository.insertMapSheetOrgDataToMapSheetMngHst(saved.getMngYyyy());
|
||||
|
||||
return new MapSheetMngDto.DmlReturn("success", saved.getMngYyyy().toString());
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.FIleChecker;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.ImageryDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngFileCheckerRepository;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngRepository;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -13,6 +16,7 @@ import org.springframework.stereotype.Service;
|
||||
public class MapSheetMngFileCheckerCoreService {
|
||||
|
||||
private final MapSheetMngFileCheckerRepository mapSheetMngFileCheckerRepository;
|
||||
private final MapSheetMngRepository mapSheetMngRepository;
|
||||
|
||||
private static final String ORIGINAL_IMAGES_PATH = "/app/original-images";
|
||||
|
||||
@@ -50,4 +54,8 @@ public class MapSheetMngFileCheckerCoreService {
|
||||
|
||||
return new ImageryDto.SyncReturn(flag, syncCnt, tfwErrCnt, tifErrCnt);
|
||||
}
|
||||
|
||||
public Optional<MapSheetMngHstEntity> findHstByUid(Long hstUid) {
|
||||
return mapSheetMngRepository.findMapSheetMngHstInfo(hstUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
|
||||
import com.kamco.cd.kamcoback.common.enums.StatusType;
|
||||
import com.kamco.cd.kamcoback.common.enums.error.AuthErrorCode;
|
||||
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||
import com.kamco.cd.kamcoback.common.utils.CommonStringUtils;
|
||||
import com.kamco.cd.kamcoback.common.utils.UserUtil;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.AddReq;
|
||||
@@ -18,6 +21,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@@ -34,23 +38,23 @@ public class MembersCoreService {
|
||||
* @return
|
||||
*/
|
||||
public Long saveMembers(AddReq addReq) {
|
||||
if (membersRepository.existsByUserId(addReq.getUserId())) {
|
||||
throw new DuplicateMemberException(Field.USER_ID, addReq.getUserId());
|
||||
if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) {
|
||||
throw new DuplicateMemberException(Field.EMPLOYEE_NO, addReq.getEmployeeNo());
|
||||
}
|
||||
|
||||
// salt 생성, 사번이 salt
|
||||
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim());
|
||||
// 패스워드 암호화, 초기 패스워드 고정
|
||||
String hashedPassword = BCrypt.hashpw(addReq.getTempPassword(), salt);
|
||||
String hashedPassword = BCrypt.hashpw(addReq.getPassword(), salt);
|
||||
|
||||
MemberEntity memberEntity = new MemberEntity();
|
||||
memberEntity.setUserId(addReq.getUserId());
|
||||
memberEntity.setUserId(addReq.getEmployeeNo());
|
||||
memberEntity.setUserRole(addReq.getUserRole());
|
||||
memberEntity.setTempPassword(addReq.getTempPassword().trim()); // 임시 패스워드는 암호화 하지 않음
|
||||
memberEntity.setPassword(hashedPassword);
|
||||
memberEntity.setName(addReq.getName());
|
||||
memberEntity.setEmployeeNo(addReq.getEmployeeNo());
|
||||
memberEntity.setRgstrUidl(userUtil.getId());
|
||||
memberEntity.setStatus(StatusType.PENDING.getId());
|
||||
|
||||
return membersRepository.save(memberEntity).getId();
|
||||
}
|
||||
@@ -69,38 +73,25 @@ public class MembersCoreService {
|
||||
memberEntity.setName(updateReq.getName());
|
||||
}
|
||||
|
||||
// 임시 패스워드는 암호화 하지 않음
|
||||
if (StringUtils.isNotBlank(updateReq.getTempPassword())) {
|
||||
// 임시 패스워드가 기존과 다르면 패스워드 변경으로 처리함
|
||||
// 상태 INACTIVE로 변경하여 사용자가 로그인할때 패스워드 변경하게함
|
||||
// 패스워드 리셋이므로 로그인 실패카운트 초기화처리함
|
||||
if (!memberEntity.getTempPassword().equals(updateReq.getTempPassword().trim())) {
|
||||
memberEntity.setStatus(StatusType.INACTIVE.getId());
|
||||
memberEntity.setLoginFailCount(0);
|
||||
if (StringUtils.isNotBlank(updateReq.getStatus())) {
|
||||
memberEntity.changeStatus(updateReq.getStatus());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(updateReq.getPassword())) {
|
||||
|
||||
// 패스워드 유효성 검사
|
||||
if (!CommonStringUtils.isValidPassword(updateReq.getPassword())) {
|
||||
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
memberEntity.setTempPassword(updateReq.getTempPassword().trim());
|
||||
|
||||
String password =
|
||||
CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo());
|
||||
|
||||
memberEntity.setStatus(StatusType.PENDING.getId());
|
||||
memberEntity.setLoginFailCount(0);
|
||||
memberEntity.setPassword(password);
|
||||
memberEntity.setPwdResetYn("Y");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(memberEntity.getEmployeeNo())) {
|
||||
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
|
||||
}
|
||||
|
||||
memberEntity.setUpdtrUid(userUtil.getId());
|
||||
|
||||
membersRepository.save(memberEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 계정 삭제 처리
|
||||
*
|
||||
* @param uuid
|
||||
*/
|
||||
public void deleteAccount(UUID uuid) {
|
||||
MemberEntity memberEntity =
|
||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
memberEntity.setStatus(StatusType.DELETED.getId());
|
||||
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
||||
memberEntity.setUpdtrUid(userUtil.getId());
|
||||
membersRepository.save(memberEntity);
|
||||
}
|
||||
@@ -112,17 +103,21 @@ public class MembersCoreService {
|
||||
*/
|
||||
public void resetPassword(String id, MembersDto.InitReq initReq) {
|
||||
MemberEntity memberEntity =
|
||||
membersRepository.findByUserId(id).orElseThrow(() -> new MemberNotFoundException());
|
||||
membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
String salt =
|
||||
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());
|
||||
// 패스워드 암호화
|
||||
String hashedPassword = BCrypt.hashpw(initReq.getPassword(), salt);
|
||||
// 기존 패스워드 확인
|
||||
if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) {
|
||||
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
|
||||
}
|
||||
|
||||
memberEntity.setPassword(hashedPassword);
|
||||
memberEntity.setStatus("ACTIVE");
|
||||
String password =
|
||||
CommonStringUtils.hashPassword(initReq.getOldPassword(), memberEntity.getEmployeeNo());
|
||||
|
||||
memberEntity.setPassword(password);
|
||||
memberEntity.setStatus(StatusType.ACTIVE.getId());
|
||||
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
||||
memberEntity.setUpdtrUid(memberEntity.getId());
|
||||
memberEntity.setPwdResetYn("N");
|
||||
membersRepository.save(memberEntity);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_map_sheet_mng_files")
|
||||
public class MapSheetMngFileEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "file_uid", nullable = false)
|
||||
private Long fileUid;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "mng_yyyy", nullable = false)
|
||||
private Integer mngYyyy;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "map_sheet_num", nullable = false)
|
||||
private String mapSheetNum;
|
||||
|
||||
@Column(name = "ref_map_sheet_num")
|
||||
private String refMapSheetNum;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "file_path")
|
||||
private String filePath;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "file_name", length = 100)
|
||||
private String fileName;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "file_ext", length = 20)
|
||||
private String fileExt;
|
||||
|
||||
@Column(name = "hst_uid")
|
||||
private Long hstUid;
|
||||
|
||||
@Column(name = "file_size")
|
||||
private Long fileSize;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -78,4 +78,13 @@ public class MapSheetMngHstEntity extends CommonDateEntity {
|
||||
|
||||
@Column(name = "sync_check_end_dttm")
|
||||
private ZonedDateTime syncCheckEndDttm;
|
||||
|
||||
@Column(name = "tif_size_bytes")
|
||||
private Long tifSizeBytes;
|
||||
|
||||
@Column(name = "tfw_size_bytes")
|
||||
private Long tfwSizeBytes;
|
||||
|
||||
@Column(name = "total_size_bytes")
|
||||
private Long totalSizeBytes;
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_member_archived")
|
||||
public class MemberArchivedEntity {
|
||||
|
||||
@EmbeddedId private MemberArchivedEntityId id;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "employee_no", length = 50)
|
||||
private String employeeNo;
|
||||
|
||||
@Size(max = 100)
|
||||
@NotNull
|
||||
@Column(name = "name", nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
@Size(max = 255)
|
||||
@NotNull
|
||||
@Column(name = "password", nullable = false)
|
||||
private String password;
|
||||
|
||||
@Size(max = 100)
|
||||
@NotNull
|
||||
@Column(name = "email", nullable = false, length = 100)
|
||||
private String email;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "status", length = 20)
|
||||
private String status;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "created_dttm", nullable = false)
|
||||
private ZonedDateTime createdDttm;
|
||||
|
||||
@NotNull
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "archived_dttm", nullable = false)
|
||||
private ZonedDateTime archivedDttm;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Embeddable
|
||||
public class MemberArchivedEntityId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -7102800377481389036L;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "user_id", nullable = false)
|
||||
private Long userId;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "uuid", nullable = false)
|
||||
private UUID uuid;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
|
||||
return false;
|
||||
}
|
||||
MemberArchivedEntityId entity = (MemberArchivedEntityId) o;
|
||||
return Objects.equals(this.userId, entity.userId) && Objects.equals(this.uuid, entity.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(userId, uuid);
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,14 @@ import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.sql.Types;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -62,9 +65,8 @@ public class MemberEntity {
|
||||
private String password;
|
||||
|
||||
@Size(max = 20)
|
||||
@ColumnDefault("'INACTIVE'")
|
||||
@Column(name = "status", length = 20)
|
||||
private String status = StatusType.INACTIVE.getId();
|
||||
private String status = StatusType.PENDING.getId();
|
||||
|
||||
@NotNull
|
||||
@ColumnDefault("now()")
|
||||
@@ -90,4 +92,20 @@ public class MemberEntity {
|
||||
|
||||
@Column(name = "updtr_uid")
|
||||
private Long updtrUid;
|
||||
|
||||
@Column(name = "status_chg_dttm")
|
||||
private ZonedDateTime statusChgDttm;
|
||||
|
||||
@JdbcTypeCode(Types.CHAR)
|
||||
@Column(name = "pwd_reset_yn", columnDefinition = "CHAR(1)")
|
||||
private String pwdResetYn;
|
||||
|
||||
public void changeStatus(String newStatus) {
|
||||
// 같은 값 보내도 무시
|
||||
if (Objects.equals(this.status, newStatus)) {
|
||||
return;
|
||||
}
|
||||
this.status = newStatus;
|
||||
this.statusChgDttm = ZonedDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
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> {
|
||||
boolean existsByFileNameAndFilePath(String fileName, String filePath);
|
||||
|
||||
void deleteByFileNameAndFilePath(String fileName, String filePath);
|
||||
}
|
||||
@@ -8,10 +8,22 @@ import org.springframework.data.domain.Page;
|
||||
|
||||
public interface MapSheetMngRepositoryCustom {
|
||||
|
||||
Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq);
|
||||
|
||||
Page<MapSheetMngDto.MngDto> findMapSheetMngList(MapSheetMngDto.@Valid searchReq searchReq);
|
||||
Page<MapSheetMngDto.MngDto> findMapSheetMngList(MapSheetMngDto.MngSearchReq searchReq);
|
||||
|
||||
Optional<MapSheetMngHstEntity> findMapSheetMngHstInfo(Long hstUid);
|
||||
|
||||
int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy);
|
||||
|
||||
void deleteByMngYyyyMngAll(int mngYyyy);
|
||||
|
||||
void deleteByMngYyyyMng(int mngYyyy);
|
||||
|
||||
void deleteByMngYyyyMngHst(int mngYyyy);
|
||||
|
||||
void deleteByMngYyyyMngFile(int mngYyyy);
|
||||
|
||||
Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid ErrorSearchReq searchReq);
|
||||
|
||||
void updateHstFileSizes(Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx50kEntity.mapInkx50kEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity.mapSheetMngFileEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
|
||||
@@ -15,10 +16,13 @@ import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.core.types.dsl.StringExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.hibernate.query.Query;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -31,67 +35,15 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||
|
||||
@PersistenceContext private EntityManager em;
|
||||
|
||||
public MapSheetMngRepositoryImpl(JPAQueryFactory queryFactory) {
|
||||
super(MapSheetMngHstEntity.class);
|
||||
this.queryFactory = queryFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
|
||||
Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize());
|
||||
List<MapSheetMngDto.ErrorDataDto> foundContent =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
MapSheetMngDto.ErrorDataDto.class,
|
||||
mapSheetMngHstEntity.hstUid,
|
||||
rowNum(),
|
||||
Expressions.stringTemplate(
|
||||
"concat({0}, {1})",
|
||||
mapSheetMngHstEntity.mapSheetName, mapInkx50kEntity.mapidcdNo),
|
||||
Expressions.stringTemplate(
|
||||
"concat({0}, substring({1}, {2}, {3}))",
|
||||
mapSheetMngHstEntity.mapSheetName, mapSheetMngHstEntity.mapSheetNum, 6, 8),
|
||||
mapSheetMngHstEntity.mapSheetCodeSrc,
|
||||
Expressions.stringTemplate(
|
||||
"to_char({0}, 'YYYY-MM-DD')", mapSheetMngHstEntity.createdDate),
|
||||
mapSheetMngHstEntity.dataState))
|
||||
.from(mapSheetMngHstEntity)
|
||||
.innerJoin(mapInkx5kEntity)
|
||||
.on(mapSheetMngHstEntity.mapSheetCode.eq(mapInkx5kEntity.fid))
|
||||
.leftJoin(mapInkx50kEntity)
|
||||
.on(mapInkx5kEntity.fidK50.eq(mapInkx50kEntity.fid.longValue()))
|
||||
.where(
|
||||
mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy()),
|
||||
// mapSheetMngHstEntity.dataState.eq(MapSheetMngDto.DataState.FAIL), // 오류만 검색
|
||||
mapSheetErrorSearchValue(searchReq))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(mapSheetMngHstEntity.createdDate.desc())
|
||||
.fetch();
|
||||
|
||||
Long countQuery =
|
||||
queryFactory
|
||||
.select(mapSheetMngHstEntity.hstUid.count())
|
||||
.from(mapSheetMngHstEntity)
|
||||
.innerJoin(mapInkx5kEntity)
|
||||
.on(mapSheetMngHstEntity.mapSheetCode.eq(mapInkx5kEntity.fid))
|
||||
.leftJoin(mapInkx50kEntity)
|
||||
.on(mapInkx5kEntity.fidK50.eq(mapInkx50kEntity.fid.longValue()))
|
||||
.where(
|
||||
mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy()),
|
||||
// mapSheetMngHstEntity.dataState.eq(MapSheetMngDto.DataState.FAIL), // 오류만 검색
|
||||
mapSheetErrorSearchValue(searchReq))
|
||||
.fetchOne();
|
||||
|
||||
return new PageImpl<>(foundContent, pageable, countQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(MapSheetMngDto.MngSearchReq searchReq) {
|
||||
|
||||
Pageable pageable = searchReq.toPageable();
|
||||
BooleanBuilder whereBuilder = new BooleanBuilder();
|
||||
@@ -100,27 +52,6 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
|
||||
whereBuilder.and(mapSheetMngEntity.mngYyyy.eq(searchReq.getMngYyyy()));
|
||||
}
|
||||
|
||||
/*
|
||||
QMapSheetMngEntity m = mapSheetMngEntity;
|
||||
QMapSheetMngHstEntity h = mapSheetMngHstEntity;
|
||||
|
||||
List<MapSheetSummaryDto> summaryContent =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
MapSheetSummaryDto.class,
|
||||
mapSheetMngHstEntity.mngYyyy,
|
||||
mapSheetMngHstEntity.mngYyyy.count().as("syncTotCnt"),
|
||||
new CaseBuilder()
|
||||
.when(mapSheetMngHstEntity.syncState.eq("DONE")).then(1L).otherwise(0L)
|
||||
.sum().as("syncStateDoneCnt")
|
||||
))
|
||||
.from(mapSheetMngHstEntity)
|
||||
.groupBy(mapSheetMngHstEntity.mngYyyy) // mng_yyyy 별로 그룹핑
|
||||
.fetch();
|
||||
|
||||
*/
|
||||
|
||||
List<MapSheetMngDto.MngDto> foundContent =
|
||||
queryFactory
|
||||
.select(
|
||||
@@ -183,6 +114,149 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
|
||||
return new PageImpl<>(foundContent, pageable, countQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid ErrorSearchReq searchReq) {
|
||||
|
||||
Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize());
|
||||
List<MapSheetMngDto.ErrorDataDto> foundContent =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
MapSheetMngDto.ErrorDataDto.class,
|
||||
mapSheetMngHstEntity.hstUid,
|
||||
rowNum(),
|
||||
Expressions.stringTemplate(
|
||||
"concat({0}, {1})",
|
||||
mapSheetMngHstEntity.mapSheetName, mapInkx50kEntity.mapidcdNo),
|
||||
Expressions.stringTemplate(
|
||||
"concat({0}, substring({1}, {2}, {3}))",
|
||||
mapSheetMngHstEntity.mapSheetName, mapSheetMngHstEntity.mapSheetNum, 6, 8),
|
||||
mapSheetMngHstEntity.mapSheetCodeSrc,
|
||||
Expressions.stringTemplate(
|
||||
"to_char({0}, 'YYYY-MM-DD')", mapSheetMngHstEntity.createdDate),
|
||||
mapSheetMngHstEntity.dataState))
|
||||
.from(mapSheetMngHstEntity)
|
||||
.innerJoin(mapInkx5kEntity)
|
||||
.on(mapSheetMngHstEntity.mapSheetCode.eq(mapInkx5kEntity.fid))
|
||||
.leftJoin(mapInkx50kEntity)
|
||||
.on(mapInkx5kEntity.fidK50.eq(mapInkx50kEntity.fid.longValue()))
|
||||
.where(
|
||||
mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy()),
|
||||
// mapSheetMngHstEntity.dataState.eq(MapSheetMngDto.DataState.FAIL), // 오류만 검색
|
||||
mapSheetErrorSearchValue(searchReq))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(mapSheetMngHstEntity.createdDate.desc())
|
||||
.fetch();
|
||||
|
||||
Long countQuery =
|
||||
queryFactory
|
||||
.select(mapSheetMngHstEntity.hstUid.count())
|
||||
.from(mapSheetMngHstEntity)
|
||||
.innerJoin(mapInkx5kEntity)
|
||||
.on(mapSheetMngHstEntity.mapSheetCode.eq(mapInkx5kEntity.fid))
|
||||
.leftJoin(mapInkx50kEntity)
|
||||
.on(mapInkx5kEntity.fidK50.eq(mapInkx50kEntity.fid.longValue()))
|
||||
.where(
|
||||
mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy()),
|
||||
// mapSheetMngHstEntity.dataState.eq(MapSheetMngDto.DataState.FAIL), // 오류만 검색
|
||||
mapSheetErrorSearchValue(searchReq))
|
||||
.fetchOne();
|
||||
|
||||
return new PageImpl<>(foundContent, pageable, countQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByMngYyyyMngAll(int mngYyyy) {
|
||||
|
||||
long deletedFileCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngFileEntity)
|
||||
.where(mapSheetMngFileEntity.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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByMngYyyyMng(int mngYyyy) {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByMngYyyyMngFile(int mngYyyy) {
|
||||
|
||||
long deletedFileCount =
|
||||
queryFactory
|
||||
.delete(mapSheetMngFileEntity)
|
||||
.where(mapSheetMngFileEntity.mngYyyy.eq(mngYyyy))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy) {
|
||||
|
||||
String sql =
|
||||
"""
|
||||
INSERT INTO tb_map_sheet_mng_hst
|
||||
(
|
||||
mng_yyyy
|
||||
,map_sheet_code
|
||||
,map_sheet_num
|
||||
,map_sheet_name
|
||||
,map_sheet_code_src
|
||||
,scale_ratio
|
||||
,ref_map_sheet_num
|
||||
,use_inference
|
||||
)
|
||||
select
|
||||
:mngYyyy as mng_yyyy
|
||||
,fid as map_sheet_code
|
||||
,mapidcd_no::INTEGER as map_sheet_num
|
||||
,mapid_nm as map_sheet_name
|
||||
,fid as map_sheet_code_src
|
||||
,5000 as scale_ratio
|
||||
,((mapidcd_no::INTEGER)/1000) as ref_map_sheet_num
|
||||
,use_inference
|
||||
from
|
||||
tb_map_inkx_5k
|
||||
""";
|
||||
|
||||
// Native Query 생성 및 실행
|
||||
Query query = (Query) em.createNativeQuery(sql);
|
||||
query.setParameter("mngYyyy", mngYyyy);
|
||||
|
||||
int exeCnt = query.executeUpdate(); // 실행 (영향받은 행의 개수 반환)
|
||||
|
||||
return exeCnt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MapSheetMngHstEntity> findMapSheetMngHstInfo(Long hstUid) {
|
||||
return Optional.ofNullable(
|
||||
@@ -192,12 +266,25 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
|
||||
.fetchOne());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHstFileSizes(
|
||||
Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes) {
|
||||
String sql =
|
||||
"UPDATE tb_map_sheet_mng_hst SET tif_size_bytes = :tif, tfw_size_bytes = :tfw, total_size_bytes = :tot WHERE hst_uid = :uid";
|
||||
Query query = (Query) em.createNativeQuery(sql);
|
||||
query.setParameter("tif", tifSizeBytes);
|
||||
query.setParameter("tfw", tfwSizeBytes);
|
||||
query.setParameter("tot", totalSizeBytes);
|
||||
query.setParameter("uid", hstUid);
|
||||
query.executeUpdate();
|
||||
}
|
||||
|
||||
private NumberExpression<Integer> rowNum() {
|
||||
return Expressions.numberTemplate(
|
||||
Integer.class, "row_number() over(order by {0} desc)", mapSheetMngHstEntity.createdDate);
|
||||
}
|
||||
|
||||
private BooleanExpression mapSheetErrorSearchValue(MapSheetMngDto.searchReq searchReq) {
|
||||
private BooleanExpression mapSheetErrorSearchValue(MapSheetMngDto.ErrorSearchReq searchReq) {
|
||||
if (Objects.isNull(searchReq.getSearchValue())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberArchivedEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MembersArchivedRepository
|
||||
extends JpaRepository<MemberArchivedEntity, Long>, MembersArchivedRepositoryCustom {}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
public interface MembersArchivedRepositoryCustom {}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class MembersArchivedRepositoryImpl implements MembersArchivedRepositoryCustom {}
|
||||
@@ -11,6 +11,10 @@ public interface MembersRepositoryCustom {
|
||||
|
||||
boolean existsByUserId(String userId);
|
||||
|
||||
boolean existsByEmployeeNo(String employeeNo);
|
||||
|
||||
Optional<MemberEntity> findByEmployeeNo(String employeeNo);
|
||||
|
||||
Optional<MemberEntity> findByUserId(String userId);
|
||||
|
||||
Optional<MemberEntity> findByUUID(UUID uuid);
|
||||
|
||||
@@ -40,6 +40,22 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
||||
!= null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 사번 조회
|
||||
*
|
||||
* @param employeeNo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean existsByEmployeeNo(String employeeNo) {
|
||||
return queryFactory
|
||||
.selectOne()
|
||||
.from(memberEntity)
|
||||
.where(memberEntity.employeeNo.eq(employeeNo))
|
||||
.fetchFirst()
|
||||
!= null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 조회 user id
|
||||
*
|
||||
@@ -52,6 +68,21 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
||||
queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne());
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 조회 employeed no
|
||||
*
|
||||
* @param employeeNo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Optional<MemberEntity> findByEmployeeNo(String employeeNo) {
|
||||
return Optional.ofNullable(
|
||||
queryFactory
|
||||
.selectFrom(memberEntity)
|
||||
.where(memberEntity.employeeNo.eq(employeeNo))
|
||||
.fetchOne());
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원정보 목록 조회
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ spring:
|
||||
jpa:
|
||||
show-sql: true
|
||||
hibernate:
|
||||
ddl-auto: validate # 스키마 검증만 수행, 자동 변경하지 않음
|
||||
ddl-auto: validate # 로컬만 완화(시킬려면 update으로 변경)
|
||||
properties:
|
||||
hibernate:
|
||||
default_batch_fetch_size: 100 # ✅ 성능 - N+1 쿼리 방지
|
||||
@@ -16,7 +16,7 @@ spring:
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
||||
#url: jdbc:postgresql://localhost:5432/kamco_cds
|
||||
#url: jdbc:postgresql://localhost:15432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
hikari:
|
||||
|
||||
@@ -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,66 +0,0 @@
|
||||
-- Fix timestamp column type conversion issue
|
||||
-- Run this if the Hibernate automatic schema update still fails
|
||||
|
||||
-- For tb_map_sheet_anal_data
|
||||
ALTER TABLE tb_map_sheet_anal_data
|
||||
ALTER COLUMN anal_end_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING anal_end_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_anal_data
|
||||
ALTER COLUMN anal_strt_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING anal_strt_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_anal_data
|
||||
ALTER COLUMN created_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING created_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_anal_data
|
||||
ALTER COLUMN updated_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING updated_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_anal_data
|
||||
ALTER COLUMN data_state_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING data_state_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
-- For tb_map_sheet_learn_data
|
||||
ALTER TABLE tb_map_sheet_learn_data
|
||||
ALTER COLUMN anal_end_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING anal_end_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_learn_data
|
||||
ALTER COLUMN anal_strt_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING anal_strt_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_learn_data
|
||||
ALTER COLUMN created_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING created_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_learn_data
|
||||
ALTER COLUMN updated_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING updated_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_learn_data
|
||||
ALTER COLUMN data_state_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING data_state_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_learn_data
|
||||
ALTER COLUMN gukuin_used_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING gukuin_used_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
-- For tb_map_sheet_learn_data_geom
|
||||
ALTER TABLE tb_map_sheet_learn_data_geom
|
||||
ALTER COLUMN created_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING created_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_learn_data_geom
|
||||
ALTER COLUMN updated_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING updated_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
-- For tb_map_sheet_anal_data_geom
|
||||
ALTER TABLE tb_map_sheet_anal_data_geom
|
||||
ALTER COLUMN created_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING created_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE tb_map_sheet_anal_data_geom
|
||||
ALTER COLUMN updated_dttm SET DATA TYPE TIMESTAMP WITH TIME ZONE
|
||||
USING updated_dttm::TIMESTAMP WITH TIME ZONE;
|
||||
@@ -1,97 +0,0 @@
|
||||
-- GeoJSON 모니터링 시스템을 위한 필수 테이블 생성 스크립트
|
||||
-- dump-kamco_cds-202511201730.sql에서 추출
|
||||
|
||||
-- 1. 시퀀스 생성
|
||||
CREATE SEQUENCE IF NOT EXISTS public.tb_map_sheet_learn_data_data_uid
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
-- 2. tb_map_sheet_learn_data 테이블 생성
|
||||
CREATE TABLE IF NOT EXISTS public.tb_map_sheet_learn_data (
|
||||
data_uid bigint DEFAULT nextval('public.tb_map_sheet_learn_data_data_uid'::regclass) NOT NULL,
|
||||
data_name character varying(128),
|
||||
data_path character varying(255),
|
||||
data_type character varying(128),
|
||||
data_crs_type character varying(128),
|
||||
data_crs_type_name character varying(255),
|
||||
created_dttm timestamp without time zone DEFAULT now(),
|
||||
created_uid bigint,
|
||||
updated_dttm timestamp without time zone DEFAULT now(),
|
||||
updated_uid bigint,
|
||||
compare_yyyy integer,
|
||||
data_yyyy integer,
|
||||
data_json json,
|
||||
data_state character varying(20),
|
||||
data_state_dttm timestamp without time zone DEFAULT now(),
|
||||
data_title character varying(255),
|
||||
anal_map_sheet character varying(255),
|
||||
anal_strt_dttm timestamp without time zone,
|
||||
anal_end_dttm time without time zone,
|
||||
anal_sec bigint,
|
||||
gukuin_used character varying(20),
|
||||
gukuin_used_dttm timestamp without time zone,
|
||||
anal_state character varying(20),
|
||||
CONSTRAINT tb_map_sheet_learn_data_pkey PRIMARY KEY (data_uid)
|
||||
);
|
||||
|
||||
-- 3. 시퀀스 생성 (Geometry용)
|
||||
CREATE SEQUENCE IF NOT EXISTS public.tb_map_sheet_learn_data_geom_geom_uid
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
-- 4. tb_map_sheet_learn_data_geom 테이블 생성
|
||||
CREATE TABLE IF NOT EXISTS public.tb_map_sheet_learn_data_geom (
|
||||
geo_uid bigint DEFAULT nextval('public.tb_map_sheet_learn_data_geom_geom_uid'::regclass) NOT NULL,
|
||||
cd_prob double precision,
|
||||
class_before_name character varying(100),
|
||||
class_before_prob double precision,
|
||||
class_after_name character varying(100),
|
||||
class_after_prob double precision,
|
||||
map_sheet_num bigint,
|
||||
before_yyyy integer,
|
||||
after_yyyy integer,
|
||||
area double precision,
|
||||
geom public.geometry,
|
||||
geo_type character varying(100),
|
||||
data_uid bigint,
|
||||
created_dttm timestamp without time zone,
|
||||
created_uid bigint,
|
||||
updated_dttm timestamp without time zone,
|
||||
updated_uid bigint,
|
||||
CONSTRAINT tb_map_sheet_learn_data_geom_pkey PRIMARY KEY (geo_uid)
|
||||
);
|
||||
|
||||
-- 5. 외래 키 제약 조건
|
||||
ALTER TABLE ONLY public.tb_map_sheet_learn_data_geom
|
||||
ADD CONSTRAINT fk_learn_data_geom_data_uid
|
||||
FOREIGN KEY (data_uid) REFERENCES public.tb_map_sheet_learn_data(data_uid) ON DELETE CASCADE;
|
||||
|
||||
-- 6. 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_tb_map_sheet_learn_data_data_state ON public.tb_map_sheet_learn_data(data_state);
|
||||
CREATE INDEX IF NOT EXISTS idx_tb_map_sheet_learn_data_anal_state ON public.tb_map_sheet_learn_data(anal_state);
|
||||
CREATE INDEX IF NOT EXISTS idx_tb_map_sheet_learn_data_data_path ON public.tb_map_sheet_learn_data(data_path);
|
||||
CREATE INDEX IF NOT EXISTS idx_tb_map_sheet_learn_data_geom_data_uid ON public.tb_map_sheet_learn_data_geom(data_uid);
|
||||
CREATE INDEX IF NOT EXISTS idx_tb_map_sheet_learn_data_geom_geo_type ON public.tb_map_sheet_learn_data_geom(geo_type);
|
||||
|
||||
-- 7. 테이블 코멘트
|
||||
COMMENT ON TABLE public.tb_map_sheet_learn_data IS '학습데이터';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data.data_uid IS '식별키';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data.data_name IS '데이타명';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data.data_path IS '경로';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data.data_type IS '타입';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data.data_state IS '처리상태';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data.anal_state IS '분석상태';
|
||||
|
||||
COMMENT ON TABLE public.tb_map_sheet_learn_data_geom IS '학습데이터GEOM정보';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data_geom.geo_uid IS '식별키';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data_geom.geom IS 'geometry정보';
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data_geom.data_uid IS '데이터식별키';
|
||||
|
||||
-- 완료 메시지
|
||||
SELECT 'GeoJSON 모니터링 시스템 테이블 생성 완료' as message;
|
||||
@@ -1,32 +0,0 @@
|
||||
-- PostGIS extension 및 기본 설정 확인
|
||||
-- 이 스크립트를 PostgreSQL에서 실행하여 PostGIS가 설치되어 있는지 확인
|
||||
|
||||
-- 1. PostGIS extension 설치 (이미 설치되어 있다면 무시됨)
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
CREATE EXTENSION IF NOT EXISTS postgis_topology;
|
||||
|
||||
-- 2. 현재 설치된 확장 확인
|
||||
SELECT name, default_version, installed_version
|
||||
FROM pg_available_extensions
|
||||
WHERE name LIKE '%postgis%';
|
||||
|
||||
-- 3. Geometry 타입이 사용 가능한지 확인
|
||||
SELECT typname
|
||||
FROM pg_type
|
||||
WHERE typname = 'geometry';
|
||||
|
||||
-- 4. 테스트용 geometry 컬럼 생성 확인
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 임시 테스트 테이블로 geometry 타입 확인
|
||||
DROP TABLE IF EXISTS temp_geom_test;
|
||||
CREATE TEMP TABLE temp_geom_test (
|
||||
id serial,
|
||||
test_geom geometry(Point, 4326)
|
||||
);
|
||||
RAISE NOTICE 'PostGIS geometry 타입이 정상적으로 작동합니다.';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE 'PostGIS 설정에 문제가 있습니다: %', SQLERRM;
|
||||
END
|
||||
$$;
|
||||
@@ -1,41 +0,0 @@
|
||||
-- Fix geometry column type in tb_map_sheet_learn_data_geom table
|
||||
-- The table was incorrectly created with 'bytea' type instead of 'geometry' type
|
||||
|
||||
-- 1. First ensure PostGIS is enabled
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
|
||||
-- 2. Check if column needs to be recreated (only if it's bytea type)
|
||||
-- Only clear data if the column type is incorrect and needs conversion
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Only delete data if geom column exists and is bytea type
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'tb_map_sheet_learn_data_geom'
|
||||
AND column_name = 'geom'
|
||||
AND data_type = 'bytea'
|
||||
) THEN
|
||||
DELETE FROM public.tb_map_sheet_learn_data_geom WHERE geom IS NOT NULL;
|
||||
RAISE NOTICE 'Cleared incorrect bytea geometry data for conversion';
|
||||
ELSE
|
||||
RAISE NOTICE 'Geometry column is already correct type, skipping data deletion';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 3. Drop and recreate the geom column with correct PostGIS geometry type (only if needed)
|
||||
ALTER TABLE public.tb_map_sheet_learn_data_geom DROP COLUMN IF EXISTS geom;
|
||||
ALTER TABLE public.tb_map_sheet_learn_data_geom ADD COLUMN geom geometry(Polygon, 5186);
|
||||
|
||||
-- 4. Create spatial index for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_tb_map_sheet_learn_data_geom_spatial
|
||||
ON public.tb_map_sheet_learn_data_geom USING GIST (geom);
|
||||
|
||||
-- 5. Update column comment
|
||||
COMMENT ON COLUMN public.tb_map_sheet_learn_data_geom.geom IS 'PostGIS geometry 정보 (Polygon, EPSG:5186)';
|
||||
|
||||
-- 6. Verify the column type is correct
|
||||
SELECT column_name, data_type, udt_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'tb_map_sheet_learn_data_geom' AND column_name = 'geom';
|
||||
|
||||
SELECT 'Geometry column type fixed successfully' as message;
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user