Merge branch 'feat/dev_251201' of https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice into feat/dev_251201

This commit is contained in:
Moon
2025-12-16 09:20:09 +09:00
46 changed files with 462 additions and 11267 deletions

View File

@@ -32,7 +32,7 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
// 미사용 상태
if (member.getStatus().equals(StatusType.INACTIVE.getId())) {
throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND);
throw new CustomApiException(AuthErrorCode.INACTIVE_ID);
}
// jBCrypt + 커스텀 salt 로 저장된 패스워드 비교

View File

@@ -11,6 +11,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
@@ -19,6 +20,10 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService;
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
private static final String[] EXCLUDE_PATHS = {
"/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password"
};
@Override
protected void doFilterInternal(
@@ -44,9 +49,15 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
String path = request.getServletPath();
// JWT 필터를 타지 않게 할 URL 패턴들
return path.startsWith("/api/auth/signin") || path.startsWith("/api/auth/refresh");
for (String pattern : EXCLUDE_PATHS) {
if (PATH_MATCHER.match(pattern, path)) {
return true;
}
}
return false;
}
// /api/members/{memberId}/password
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (bearer != null && bearer.startsWith("Bearer ")) {

View File

@@ -224,7 +224,8 @@ public class CommonCodeApiController {
commonCodeUtil.getChildCodesByParentCode("0000").stream()
.map(
child ->
new CommonCodeDto.Clazzes(child.getCode(), child.getName(), child.getOrder()))
new CommonCodeDto.Clazzes(
child.getCode(), child.getName(), child.getOrder(), child.getProps2()))
.toList();
return ApiResponseDto.ok(list);

View File

@@ -1,6 +1,5 @@
package com.kamco.cd.kamcoback.code.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.kamco.cd.kamcoback.common.utils.html.HtmlEscapeDeserializer;
@@ -155,7 +154,7 @@ public class CommonCodeDto {
String[] sortParams = sort.split(",");
String property = sortParams[0];
Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
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);
@@ -167,16 +166,14 @@ public class CommonCodeDto {
private String code;
private String name;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Double score;
private Integer order;
private String color;
public Clazzes(String code, String name, Integer order) {
public Clazzes(String code, String name, Integer order, String color) {
this.code = code;
this.name = name;
this.order = order;
this.color = color;
}
}
}

View File

@@ -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;

View File

@@ -1,13 +1,15 @@
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
* @param password 벨리데이션 필요한 패스워드
* @return
*/
public static boolean isValidPassword(String password) {
@@ -15,4 +17,16 @@ public class CommonStringUtils {
"^(?=.*[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);
}
}

View File

@@ -161,8 +161,6 @@ public class GlobalExceptionHandler {
errorLog.getId());
}
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
@ExceptionHandler(DataIntegrityViolationException.class)
public ApiResponseDto<String> handlerDataIntegrityViolationException(
@@ -278,8 +276,8 @@ public class GlobalExceptionHandler {
String codeName = "";
switch (e.getField()) {
case USER_ID -> {
codeName = "DUPLICATE_DATA";
case EMPLOYEE_NO -> {
codeName = "DUPLICATE_EMPLOYEEID";
}
default -> {
codeName = "DUPLICATE_DATA";

View File

@@ -6,16 +6,30 @@ import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
@Value("${server.port}")
private String serverPort;
@Value("${spring.profiles.active:local}")
private String profile;
@Value("${swagger.dev-url:https://kamco.dev-api.gs.dabeeo.com}")
private String devUrl;
@Value("${swagger.prod-url:https://api.kamco.com}")
private String prodUrl;
@Bean
public OpenAPI kamcoOpenAPI() {
// 🔹 1) SecurityScheme 정의 (Bearer JWT)
// 1) SecurityScheme 정의 (Bearer JWT)
SecurityScheme bearerAuth =
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
@@ -24,12 +38,21 @@ public class OpenApiConfig {
.in(SecurityScheme.In.HEADER)
.name("Authorization");
// 🔹 2) SecurityRequirement (기본으로 BearerAuth 사용)
// 2) SecurityRequirement (기본으로 BearerAuth 사용)
SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth");
// 🔹 3) Components 에 SecurityScheme 등록
// 3) Components 에 SecurityScheme 등록
Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth);
// profile 별 server url 분기
List<Server> servers = new ArrayList<>();
switch (profile) {
case "prod" -> servers.add(new Server().url(prodUrl).description("운영 서버"));
case "dev" -> servers.add(new Server().url(devUrl).description("개발 서버"));
default ->
servers.add(new Server().url("http://localhost:" + serverPort).description("로컬 개발 서버"));
}
return new OpenAPI()
.info(
new Info()
@@ -38,19 +61,10 @@ public class OpenApiConfig {
"KAMCO 변화 탐지 시스템 API 문서\n\n"
+ "이 API는 지리공간 데이터를 활용한 변화 탐지 시스템을 제공합니다.\n"
+ "GeoJSON 형식의 공간 데이터를 처리하며, PostgreSQL/PostGIS 기반으로 동작합니다.")
.version("v1.0.0")
// .contact(new Contact().name("KAMCO Development
// Team").email("dev@kamco.com").url("https://kamco.com"))
// .license(new License().name("Proprietary").url("https://kamco.com/license"))
)
.servers(
List.of(
new Server().url("http://localhost:8080").description("로컬 개발 서버"),
new Server().url("https://kamco.dev-api.gs.dabeeo.com").description("개발 서버")
// , new Server().url("https://api.kamco.com").description("운영 서버")
))
.components(new Components())
// 🔥 여기 한 줄이 "모든 API 기본적으로 BearerAuth 요구" 의미
.version("v1.0.0"))
.servers(servers)
// 만들어둔 components를 넣어야 함
.components(components)
.addSecurityItem(securityRequirement);
}
}

View File

@@ -69,6 +69,7 @@ public class SecurityConfig {
.requestMatchers(
"/api/auth/signin",
"/api/auth/refresh",
"/api/auth/logout",
"/swagger-ui/**",
"/api/members/*/password",
"/v3/api-docs/**")
@@ -99,7 +100,7 @@ public class SecurityConfig {
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성
config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header
config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정

View File

@@ -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"

View File

@@ -76,8 +76,24 @@ public class MapSheetMngFileCheckerApiController {
public ApiResponseDto<String> uploadFile(
@RequestPart("file") MultipartFile file,
@RequestParam("targetPath") String targetPath,
@RequestParam(value = "overwrite", required = false, defaultValue = "false") boolean overwrite) {
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.uploadFile(file, targetPath, overwrite));
@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 = "중복 파일 등 파일 삭제")
@@ -86,6 +102,14 @@ public class MapSheetMngFileCheckerApiController {
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(

View File

@@ -316,7 +316,7 @@ public class MapSheetMngFileCheckerService {
}
@Transactional
public String uploadFile(MultipartFile file, String targetPath, boolean overwrite) {
public String uploadFile(MultipartFile file, String targetPath, boolean overwrite, Long hstUid) {
try {
Path path = Paths.get(targetPath);
if (Files.isDirectory(path)) {
@@ -368,11 +368,11 @@ public class MapSheetMngFileCheckerService {
// 안내: 같은 베이스의 TIF가 없으면 추후 TIF 업로드 필요
if (!Files.exists(tifPath)) {
// DB 메타 저장은 진행 (향후 쌍 검증 위해)
saveUploadMeta(path);
saveUploadMeta(path, hstUid);
return "TFW 업로드 성공 (매칭되는 TIF가 아직 없습니다).";
}
// TIF가 존재하면 쌍 요건 충족
saveUploadMeta(path);
saveUploadMeta(path, hstUid);
return "TFW 업로드 성공";
}
@@ -395,12 +395,12 @@ public class MapSheetMngFileCheckerService {
throw new ValidationException(
"유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwPath.getFileName());
}
saveUploadMeta(path);
saveUploadMeta(path, hstUid);
return "TIF 업로드 성공";
}
// 기타 확장자: 저장만 하고 메타 기록
saveUploadMeta(path);
saveUploadMeta(path, hstUid);
return "업로드 성공";
} catch (IOException e) {
@@ -408,35 +408,123 @@ public class MapSheetMngFileCheckerService {
}
}
private void saveUploadMeta(Path savedPath) {
@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);
// 연도(mng_yyyy) 추출: 경로 내의 연도 폴더명을 찾음 (예: .../original-images/2022/2022_25cm/...)
Integer mngYyyy = extractYearFromPath(fullPath);
MapSheetMngFileEntity entity = new MapSheetMngFileEntity();
// 도엽번호(map_sheet_num) 추정: 파일명 내 숫자 연속 부분을 추출해 정수화
String mapSheetNum = extractMapSheetNumFromFileName(fileName);
// ref_map_sheet_num: 1000으로 나눈 값(파일명 규칙에 따라 추정)
String refMapSheetNum = null;
if (mapSheetNum != null && !mapSheetNum.isEmpty()) {
try {
long num = Long.parseLong(mapSheetNum);
refMapSheetNum = String.valueOf(num / 1000);
} catch (NumberFormatException ignored) {
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);
}
MapSheetMngFileEntity entity = new MapSheetMngFileEntity();
entity.setMngYyyy(mngYyyy);
entity.setMapSheetNum(mapSheetNum);
entity.setRefMapSheetNum(refMapSheetNum);
entity.setFilePath(savedPath.getParent() != null ? savedPath.getParent().toString() : "");
entity.setFileName(fileName);
entity.setFileExt(ext);
// 파일 크기 설정
try {
long size = Files.size(savedPath);
entity.setFileSize(size);
} catch (IOException e) {
entity.setFileSize(0L);
}
mapSheetMngFileRepository.save(entity);
}
@@ -492,4 +580,17 @@ public class MapSheetMngFileCheckerService {
}
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());
}
}
}

View File

@@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.members.service.AdminService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -12,6 +13,7 @@ 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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@@ -86,4 +88,25 @@ public class AdminApiController {
adminService.updateMembers(uuid, updateReq);
return ApiResponseDto.createOK(UUID.randomUUID());
}
@Operation(summary = "사번 중복 체크", description = "사번 중복 체크")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Boolean.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/{employeeNo}")
public ApiResponseDto<Boolean> checkEmployeeNo(
@Parameter(description = "중복 체크할 사번", required = true, example = "1234567") @PathVariable
String employeeNo) {
return ApiResponseDto.ok(adminService.existsByEmployeeNo(employeeNo));
}
}

View File

@@ -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,23 +110,9 @@ public class AuthController {
Authentication authentication = null;
MembersDto.Member member = new MembersDto.Member();
// 비활성 상태면 임시패스워드를 비교함
if (StatusType.PENDING.getId().equals(status)) {
if (!authService.isTempPasswordValid(request)) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
}
} else {
authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(), request.getPassword()));
}
// PENDING 비활성 상태(새로운 패스워드 입력 해야함)
if (StatusType.PENDING.getId().equals(status)) {
member.setEmployeeNo(request.getUsername());
return ApiResponseDto.ok(new TokenResponse(status, null, null, member));
}
authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
String username = authentication.getName(); // UserDetailsService 에서 사용한 username
@@ -156,6 +140,12 @@ public class AuthController {
member.setName(user.getMember().getName());
member.setEmployeeNo(user.getMember().getEmployeeNo());
// PENDING 비활성 상태(새로운 패스워드 입력 해야함)
if (StatusType.PENDING.getId().equals(status)) {
member.setEmployeeNo(request.getUsername());
return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member));
}
// 인증 성공 로그인 시간 저장
authService.saveLogin(UUID.fromString(username));
@@ -215,7 +205,7 @@ public class AuthController {
@ApiResponse(
responseCode = "200",
description = "로그아웃 성공",
content = @Content(schema = @Schema(implementation = Void.class)))
content = @Content(schema = @Schema(implementation = Object.class)))
})
public ApiResponseDto<ResponseEntity<Object>> logout(
Authentication authentication, HttpServletResponse response) {

View File

@@ -28,43 +28,37 @@ 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;
@JsonFormatDttm private ZonedDateTime updatedDttm;
@JsonFormatDttm private ZonedDateTime firstLoginDttm;
@JsonFormatDttm private ZonedDateTime lastLoginDttm;
@JsonFormatDttm private ZonedDateTime statusChgDttm;
public Basic(
Long id,
UUID uuid,
String userRole,
String name,
String userId,
String employeeNo,
String tempPassword,
String status,
ZonedDateTime createdDttm,
ZonedDateTime updatedDttm,
ZonedDateTime firstLoginDttm,
ZonedDateTime lastLoginDttm) {
ZonedDateTime lastLoginDttm,
ZonedDateTime statusChgDttm) {
this.id = id;
this.uuid = uuid;
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;
this.updatedDttm = updatedDttm;
this.firstLoginDttm = firstLoginDttm;
this.lastLoginDttm = lastLoginDttm;
this.statusChgDttm = statusChgDttm;
}
private String getUserRoleName(String roleId) {
@@ -120,14 +114,16 @@ public class MembersDto {
@Size(min = 2, max = 100)
private String name;
@Schema(description = "임시 비밀번호", example = "q!w@e#r4")
private String tempPassword;
@NotBlank
@Schema(description = "패스워드", example = "")
@Size(max = 255)
private String password;
public AddReq(String userRole, String employeeNo, String name, String tempPassword) {
public AddReq(String userRole, String employeeNo, String name, String password) {
this.userRole = userRole;
this.employeeNo = employeeNo;
this.name = name;
this.tempPassword = tempPassword;
this.password = password;
}
}
@@ -139,18 +135,18 @@ public class MembersDto {
@Size(min = 2, max = 100)
private String name;
@Schema(description = "패스워드", example = "")
@Size(max = 255)
private String tempPassword;
@Schema(description = "상태", example = "ACTIVE")
@EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.")
private String status;
public UpdateReq(String name, String tempPassword, String status) {
@Schema(description = "패스워드", example = "")
@Size(max = 255)
private String password;
public UpdateReq(String name, String status, String password) {
this.name = name;
this.tempPassword = tempPassword;
this.status = status;
this.password = password;
}
}
@@ -158,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

View File

@@ -1,6 +1,5 @@
package com.kamco.cd.kamcoback.members.service;
import com.kamco.cd.kamcoback.common.enums.StatusType;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.utils.CommonStringUtils;
import com.kamco.cd.kamcoback.members.dto.MembersDto;
@@ -26,7 +25,7 @@ public class AdminService {
*/
@Transactional
public Long saveMember(MembersDto.AddReq addReq) {
if (!CommonStringUtils.isValidPassword(addReq.getTempPassword())) {
if (!CommonStringUtils.isValidPassword(addReq.getPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
}
@@ -41,12 +40,16 @@ public class AdminService {
*/
@Transactional
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
if (StatusType.INACTIVE.getId().equals(updateReq.getStatus())) {
// 미사용 처리
membersCoreService.deleteMember(uuid);
} else {
// 수정
membersCoreService.updateMembers(uuid, updateReq);
}
membersCoreService.updateMembers(uuid, updateReq);
}
/**
* 사번 중복 체크
*
* @param employeeNo
* @return
*/
public boolean existsByEmployeeNo(String employeeNo) {
return membersCoreService.existsByEmployeeNo(employeeNo);
}
}

View File

@@ -33,14 +33,4 @@ public class AuthService {
public String getUserStatus(SignInRequest request) {
return membersCoreService.getUserStatus(request);
}
/**
* 임시 패스워드 비교
*
* @param request
* @return
*/
public boolean isTempPasswordValid(SignInRequest request) {
return membersCoreService.isTempPasswordValid(request);
}
}

View File

@@ -37,7 +37,7 @@ public class MembersService {
@Transactional
public void resetPassword(String id, MembersDto.InitReq initReq) {
if (!CommonStringUtils.isValidPassword(initReq.getPassword())) {
if (!CommonStringUtils.isValidPassword(initReq.getNewPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
}
membersCoreService.resetPassword(id, initReq);

View File

@@ -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.code.dto.CommonCodeDto.SearchReq;
import jakarta.persistence.EntityNotFoundException;
import java.util.List;
import java.util.Optional;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -45,16 +45,16 @@ public class MembersCoreService {
// 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.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();
}
@@ -73,37 +73,26 @@ public class MembersCoreService {
memberEntity.setName(updateReq.getName());
}
// 임시 패스워드는 암호화 하지 않음
if (StringUtils.isNotBlank(updateReq.getTempPassword())) {
/**
* 임시 패스워드가 기존과 다르면 패스워드 변경으로 처리함 상태 PENDING 으로 변경하여 사용자가 로그인할때 패스워드 변경하게함 패스워드 리셋이므로 로그인
* 실패카운트 초기화처리함
*/
if (!memberEntity.getTempPassword().equals(updateReq.getTempPassword().trim())) {
// 패스워드 유효성 검사
if (!CommonStringUtils.isValidPassword(updateReq.getTempPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
}
if (StringUtils.isNotBlank(updateReq.getStatus())) {
memberEntity.changeStatus(updateReq.getStatus());
}
memberEntity.setStatus(StatusType.PENDING.getId());
memberEntity.setLoginFailCount(0);
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");
}
memberEntity.setUpdtrUid(userUtil.getId());
membersRepository.save(memberEntity);
}
/**
* 미사용 처리
*
* @param uuid
*/
public void deleteMember(UUID uuid) {
MemberEntity memberEntity =
membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new);
memberEntity.setStatus(StatusType.INACTIVE.getId());
membersRepository.save(memberEntity);
}
@@ -116,20 +105,19 @@ public class MembersCoreService {
MemberEntity memberEntity =
membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException());
// 임시 패스워드 확인
if (!memberEntity.getTempPassword().equals(initReq.getTempPassword())) {
// 기존 패스워드 확인
if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
}
String salt =
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());
// 패스워드 암호화
String hashedPassword = BCrypt.hashpw(initReq.getPassword(), salt);
String password =
CommonStringUtils.hashPassword(initReq.getNewPassword(), memberEntity.getEmployeeNo());
memberEntity.setPassword(hashedPassword);
memberEntity.setStatus("ACTIVE");
memberEntity.setPassword(password);
memberEntity.setStatus(StatusType.ACTIVE.getId());
memberEntity.setUpdatedDttm(ZonedDateTime.now());
memberEntity.setUpdtrUid(memberEntity.getId());
memberEntity.setPwdResetYn("N");
membersRepository.save(memberEntity);
}
@@ -159,21 +147,6 @@ public class MembersCoreService {
return memberEntity.getStatus();
}
/**
* 임시 패스워드 비교
*
* @param request
* @return
*/
public boolean isTempPasswordValid(SignInRequest request) {
MemberEntity memberEntity =
membersRepository
.findByUserId(request.getUsername())
.orElseThrow(MemberNotFoundException::new);
return memberEntity.getTempPassword().equals(request.getPassword().trim());
}
/**
* 최초 로그인 저장 마지막 로그인 저장
*
@@ -190,4 +163,14 @@ public class MembersCoreService {
memberEntity.setLoginFailCount(0);
membersRepository.save(memberEntity);
}
/**
* 사번 중복체크
*
* @param employeeNo
* @return
*/
public boolean existsByEmployeeNo(String employeeNo) {
return membersRepository.existsByEmployeeNo(employeeNo);
}
}

View File

@@ -15,8 +15,14 @@ import lombok.NoArgsConstructor;
@Table(name = "tb_audit_log")
public class AuditLogEntity extends CommonCreateEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "audit_log_uid")
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "tb_audit_log_audit_log_uid_seq_gen")
@SequenceGenerator(
name = "tb_audit_log_audit_log_uid_seq_gen",
sequenceName = "tb_audit_log_audit_log_uid_seq",
allocationSize = 1)
@Column(name = "audit_log_uid", nullable = false)
private Long id;
@Column(name = "user_uid")

View File

@@ -16,8 +16,14 @@ import lombok.NoArgsConstructor;
@Table(name = "tb_error_log")
public class ErrorLogEntity extends CommonCreateEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "error_log_uid")
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "tb_error_log_error_log_uid_seq_gen")
@SequenceGenerator(
name = "tb_error_log_error_log_uid_seq_gen",
sequenceName = "tb_error_log_error_log_uid_seq",
allocationSize = 1)
@Column(name = "error_log_uid", nullable = false)
private Long id;
@Column(name = "request_id")

View File

@@ -50,10 +50,4 @@ public class MapSheetMngFileEntity {
@Column(name = "file_size")
private Long fileSize;
@Size(max = 20)
@Column(name = "file_state", length = 20)
private String fileState;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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
@@ -51,18 +54,12 @@ public class MemberEntity {
@Column(name = "name", nullable = false, length = 100)
private String name;
@Size(max = 255)
@NotNull
@Column(name = "temp_password", nullable = false)
private String tempPassword;
@Size(max = 255)
@NotNull
@Column(name = "password", nullable = false)
private String password;
@Size(max = 20)
@ColumnDefault("'INACTIVE'")
@Column(name = "status", length = 20)
private String status = StatusType.PENDING.getId();
@@ -90,4 +87,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();
}
}

View File

@@ -24,4 +24,6 @@ public interface MapSheetMngRepositoryCustom {
Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
MapSheetMngDto.@Valid ErrorSearchReq searchReq);
void updateHstFileSizes(Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes);
}

View File

@@ -266,6 +266,19 @@ 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);

View File

@@ -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 {}

View File

@@ -1,3 +0,0 @@
package com.kamco.cd.kamcoback.postgres.repository.members;
public interface MembersArchivedRepositoryCustom {}

View File

@@ -1,6 +0,0 @@
package com.kamco.cd.kamcoback.postgres.repository.members;
import org.springframework.stereotype.Repository;
@Repository
public class MembersArchivedRepositoryImpl implements MembersArchivedRepositoryCustom {}

View File

@@ -120,14 +120,12 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
memberEntity.uuid,
memberEntity.userRole,
memberEntity.name,
memberEntity.userId,
memberEntity.employeeNo,
memberEntity.tempPassword,
memberEntity.status,
memberEntity.createdDttm,
memberEntity.updatedDttm,
memberEntity.firstLoginDttm,
memberEntity.lastLoginDttm))
memberEntity.lastLoginDttm,
memberEntity.statusChgDttm))
.from(memberEntity)
.where(builder)
.offset(pageable.getOffset())

View File

@@ -48,22 +48,27 @@ server:
jwt:
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
#access-token-validity-in-ms: 86400000 # 1일
#refresh-token-validity-in-ms: 604800000 # 7일
access-token-validity-in-ms: 60000 # 1분
refresh-token-validity-in-ms: 300000 # 5분
access-token-validity-in-ms: 86400000 # 1일
refresh-token-validity-in-ms: 604800000 # 7일
#access-token-validity-in-ms: 60000 # 1분
#refresh-token-validity-in-ms: 300000 # 5분
token:
refresh-cookie-name: kamco-dev # 개발용 쿠키 이름
refresh-cookie-secure: false # 로컬 http 테스트면 false
springdoc:
swagger-ui:
persist-authorization: true # 스웨거 새로고침해도 토큰 유지, 로컬스토리지에 저장
logging:
level:
org:
springframework:
security: DEBUG
org.springframework.security: DEBUG
mapsheet:
upload:
skipGdalValidation: true

View File

@@ -38,4 +38,10 @@ token:
refresh-cookie-name: kamco-local # 개발용 쿠키 이름
refresh-cookie-secure: false # 로컬 http 테스트면 false
springdoc:
swagger-ui:
persist-authorization: true # 스웨거 새로고침해도 토큰 유지, 로컬스토리지에 저장

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
$$;

View File

@@ -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;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long