학습데이터 관리 목록 순서 변경, 주석추가

This commit is contained in:
2026-02-27 18:54:03 +09:00
parent 0acaeaac09
commit 086eb20e8d
17 changed files with 256 additions and 11 deletions

View File

@@ -19,6 +19,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
// JWT 필터를 타지 않게 할 URL 패턴들
private static final String[] EXCLUDE_PATHS = {
"/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password"
};
@@ -30,8 +31,10 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// HTTP 요청 헤더에서 JWT 토큰 꺼내기
String token = resolveToken(request);
// JWT 토큰을 검증하고, 인증된 사용자로 SecurityContext에 등록
if (token != null && jwtTokenProvider.isValidToken(token)) {
String username = jwtTokenProvider.getSubject(token);
@@ -57,7 +60,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
return false;
}
// /api/members/{memberId}/password
// HTTP 요청 헤더에서 JWT 토큰 꺼내기
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (bearer != null && bearer.startsWith("Bearer ")) {

View File

@@ -11,6 +11,7 @@ import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/** 토큰 생성 */
@Component
public class JwtTokenProvider {
@@ -31,10 +32,12 @@ public class JwtTokenProvider {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
// Access Token 생성
public String createAccessToken(String subject) {
return createToken(subject, accessTokenValidityInMs);
}
// Refresh Token 생성
public String createRefreshToken(String subject) {
return createToken(subject, refreshTokenValidityInMs);
}

View File

@@ -6,6 +6,7 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
/** redis token handler */
@Service
@RequiredArgsConstructor
public class RefreshTokenService {
@@ -13,16 +14,33 @@ public class RefreshTokenService {
private final StringRedisTemplate redisTemplate;
private static final String PREFIX = "RT:";
/**
* Refresh Token 저장
*
* @param username 사용자 식별값 (보통 username or userId)
* @param refreshToken 발급된 Refresh Token
* @param ttlMillis 토큰 만료 시간 (밀리초 단위)
*/
public void save(String username, String refreshToken, long ttlMillis) {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.set(PREFIX + username, refreshToken, Duration.ofMillis(ttlMillis));
}
/**
* Refresh Token 검증
*
* <p>1. Redis에 저장된 값 조회 2. 클라이언트가 보낸 refreshToken과 비교 3. 동일하면 true
*/
public boolean validate(String username, String refreshToken) {
String stored = redisTemplate.opsForValue().get(PREFIX + username);
return stored != null && stored.equals(refreshToken);
}
/**
* Refresh Token 삭제
*
* <p>로그아웃 시 호출 Redis에서 해당 사용자 토큰 제거
*/
public void delete(String username) {
redisTemplate.delete(PREFIX + username);
}

View File

@@ -1,10 +1,43 @@
package com.kamco.cd.kamcoback.inference;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.inference.service.InferenceManualService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/inference/manual")
public class InferenceManualApiController {}
public class InferenceManualApiController {
private final InferenceManualService inferenceManualService;
@Operation(summary = "추론 결과로 추론 목록 및 shp 생성", description = "추론 결과로 추론 목록 및 shp 생성")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "데이터 저장 성공",
content =
@Content(
mediaType = "application/json",
schema =
@Schema(implementation = InferenceResultShpDto.InferenceCntDto.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/save")
public void saveTesting(List<Long> batchIds) {
inferenceManualService.saveResultsTesting(batchIds);
}
}

View File

@@ -53,6 +53,7 @@ public class InferenceResultShpApiController {
@PostMapping("/shp/{uuid}")
public ApiResponseDto<Void> createShp(
@Parameter(example = "feb2ec0b-a0f7-49ca-95e4-98b2231bdaae") @PathVariable UUID uuid) {
// shp 파일 수동생성
inferenceResultShpService.createShp(uuid);
return ApiResponseDto.createOK(null);
}

View File

@@ -1,24 +1,61 @@
package com.kamco.cd.kamcoback.inference.service;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class InferenceManualService {
private final InferenceResultCoreService inferenceResultCoreService;
private final ModelMngCoreService modelMngCoreService;
public void getResultsTesting(List<Long> batchIds) {
List<InferenceResultsTestingDto.Basic> resultList =
inferenceResultCoreService.getInferenceResults(batchIds);
public void saveResultsTesting(List<Long> batchIds) {
// 배치 id로 추론 결과 testing 테이블에서 조회
List<InferenceResultsTestingDto.Basic> resultInfoList =
inferenceResultCoreService.getInferenceResultGroupList(batchIds);
if (resultList.isEmpty()) {}
if (resultInfoList.isEmpty()) {
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND);
}
// Long compareYear = resultInfoList.getFirst().getBeforeYear();
// Long targetYear = resultInfoList.getFirst().getAfterYear();
// String title = compareYear + "-" + targetYear + "변화탐지";
//
// InferenceResultDto.RegReq inferenceDto = new InferenceResultDto.RegReq();
// inferenceDto.setTitle(title);
// inferenceDto.setCompareYyyy(Integer.valueOf(compareYear));
// inferenceDto.setTargetYyyy(targetYear);
// // 추론 기본정보 저장
// for (InferenceResultsTestingDto.Basic result : resultInfoList) {
//
// if (result.getModelVersion().startsWith(ModelType.G1.getId()) ||
// result.getModelVersion().startsWith("M1")) {
// ModelMngDto.Basic model =
// modelMngCoreService.findByModelVer(result.getModelVersion());
// inferenceDto.setModel1Uuid(model.getUuid());
//
// } else if (result.getModelVersion().startsWith("G2") ||
// result.getModelVersion().startsWith("M2")) {
// ModelMngDto.Basic model =
// modelMngCoreService.findByModelVer(result.getModelVersion());
// inferenceDto.setModel2Uuid(model.getUuid());
//
// } else if (result.getModelVersion().startsWith("G3") ||
// result.getModelVersion().startsWith("M3")) {
// ModelMngDto.Basic model =
// modelMngCoreService.findByModelVer(result.getModelVersion());
// inferenceDto.setModel3Uuid(model.getUuid());
// }
//
// System.out.println(result);
// }
for (InferenceResultsTestingDto.Basic result : resultList) {
System.out.println(result);
}
}
}

View File

@@ -67,6 +67,7 @@ public class ModelMngDto {
private String clsModelVersion;
private Double priority;
private String memo;
private UUID uuid;
public Basic(
Long modelUid,
@@ -89,7 +90,8 @@ public class ModelMngDto {
String clsModelFileName,
String clsModelVersion,
double priority,
String memo) {
String memo,
UUID uuid) {
this.modelUid = modelUid;
this.modelVer = modelVer;
this.createCompleteDttm = createCompleteDttm;
@@ -111,6 +113,7 @@ public class ModelMngDto {
this.clsModelVersion = clsModelVersion;
this.priority = priority;
this.memo = memo;
this.uuid = uuid;
}
}

View File

@@ -575,11 +575,37 @@ public class InferenceResultCoreService {
return mapSheetLearn5kRepository.getInferenceRunMapId(uuid);
}
/**
* 실패 처리되어야 할 목록 중에 아직 실패로 표시되지 않은 ID 조회
*
* @param uuid 추론 uuid
* @param failMapIds AI API 연결하여 조회한 실패 job id
* @param type 모델 타입
* @return job id
*/
public List<Long> findFail5kList(UUID uuid, List<Long> failMapIds, String type) {
return mapSheetLearn5kRepository.findFail5kList(uuid, failMapIds, type);
}
/**
* 완료된 것으로 들어온 목록 중 실제로 존재하는 5k jobId 조회
*
* @param uuid 추론 uuid
* @param completedIds AI API 연결하여 조회한 성공 job id
* @param type 모델 타입
* @return job id
*/
public List<Long> findCompleted5kList(UUID uuid, List<Long> completedIds, String type) {
return mapSheetLearn5kRepository.findCompleted5kList(uuid, completedIds, type);
}
/**
* testing 테이블 결과로 기본정보 조회
*
* @param batchIds batch id
* @return batch id, model ver, year 정보
*/
public List<InferenceResultsTestingDto.Basic> getInferenceResultGroupList(List<Long> batchIds) {
return inferenceResultsTestingRepository.getInferenceResultGroupList(batchIds);
}
}

View File

@@ -109,4 +109,18 @@ public class ModelMngCoreService {
.orElseThrow(() -> new EntityNotFoundException("모델 정보가 없습니다."));
return entity.toDto();
}
/**
* 모델 버전명으로 조회
*
* @param ver 모델버전
* @return 모델정보
*/
public ModelMngDto.Basic findByModelVer(String ver) {
ModelMngEntity entity =
modelMngRepository
.findByModelVer(ver)
.orElseThrow(() -> new EntityNotFoundException("모델 정보가 없습니다."));
return entity.toDto();
}
}

View File

@@ -117,6 +117,7 @@ public class ModelMngEntity extends CommonDateEntity {
this.clsModelFileName,
this.clsModelVersion,
this.priority,
this.memo);
this.memo,
this.uuid);
}
}

View File

@@ -1,10 +1,17 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto;
import com.kamco.cd.kamcoback.postgres.entity.InferenceResultsTestingEntity;
import java.util.List;
public interface InferenceResultsTestingRepositoryCustom {
/**
* 추론 결과 조회
*
* @param batchIds batch id
* @return 추론 결과 목록
*/
List<InferenceResultsTestingEntity> getInferenceResultList(List<Long> batchIds);
/**
@@ -14,4 +21,12 @@ public interface InferenceResultsTestingRepositoryCustom {
* @return batchIds 조회 count 수
*/
Long getInferenceResultCnt(List<Long> batchIds);
/**
* testing 테이블 결과로 기본정보 조회
*
* @param batchIds batch id
* @return batch id, model ver, year 정보
*/
List<InferenceResultsTestingDto.Basic> getInferenceResultGroupList(List<Long> batchIds);
}

View File

@@ -2,7 +2,9 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference;
import static com.kamco.cd.kamcoback.postgres.entity.QInferenceResultsTestingEntity.inferenceResultsTestingEntity;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto;
import com.kamco.cd.kamcoback.postgres.entity.InferenceResultsTestingEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
@@ -47,4 +49,29 @@ public class InferenceResultsTestingRepositoryImpl
return cnt == null ? 0L : cnt;
}
@Override
public List<InferenceResultsTestingDto.Basic> getInferenceResultGroupList(List<Long> batchIds) {
return queryFactory
.select(
Projections.constructor(
InferenceResultsTestingDto.Basic.class,
inferenceResultsTestingEntity.batchId,
inferenceResultsTestingEntity.modelVersion.max(),
inferenceResultsTestingEntity.beforeYear.max(),
inferenceResultsTestingEntity.afterYear.max()))
.from(inferenceResultsTestingEntity)
.where(
inferenceResultsTestingEntity
.batchId
.in(batchIds)
.and(inferenceResultsTestingEntity.afterC.isNotNull())
.and(inferenceResultsTestingEntity.afterP.isNotNull()))
.groupBy(
inferenceResultsTestingEntity.batchId,
inferenceResultsTestingEntity.modelVersion,
inferenceResultsTestingEntity.beforeYear,
inferenceResultsTestingEntity.afterYear)
.fetch();
}
}

View File

@@ -6,12 +6,42 @@ import java.util.UUID;
public interface MapSheetLearn5kRepositoryCustom {
/**
* 추론 실행 실패 정보 저장
*
* @param uuid 추론 uuid
* @param jobDto AI API에서 조회한 Job 정보
* @param type 모델 타입
*/
void saveFail5k(UUID uuid, JobStatusDto jobDto, String type);
/**
* 추론 실행중인 Job id 저장
*
* @param uuid 추론 uuid
* @param jobDto AI API에서 조회한 Job 정보
* @param type 모델 타입
*/
void saveJobId(UUID uuid, JobStatusDto jobDto, String type);
/**
* 실패 처리되어야 할 목록 중에 아직 실패로 표시되지 않은 ID 조회
*
* @param uuid 추론 uuid
* @param failIds AI API 연결하여 조회한 실패 job id
* @param type 모델 타입
* @return 실패로 표시되지 않은 ID
*/
List<Long> findFail5kList(UUID uuid, List<Long> failIds, String type);
/**
* 완료된 것으로 들어온 목록 중 실제로 존재하는 5k jobId 조회
*
* @param uuid 추론 uuid
* @param completedIds AI API 연결하여 조회한 실패 job id
* @param type 모델 타입
* @return 성공한 job id
*/
List<Long> findCompleted5kList(UUID uuid, List<Long> completedIds, String type);
/**

View File

@@ -4,6 +4,7 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapShe
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.label.dto.LabelWorkDto;
import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng;
@@ -166,6 +167,19 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
.from(labelingAssignmentEntity)
.where(labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id));
/** 순서 ING 진행중 ASSIGNED 작업할당 PENDING 작업대기 FINISH 종료 */
NumberExpression<Integer> stateOrder =
new CaseBuilder()
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.ING.getId()))
.then(0)
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.ASSIGNED.getId()))
.then(1)
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.PENDING.getId()))
.then(2)
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.FINISH.getId()))
.then(3)
.otherwise(99);
List<LabelWorkMng> foundContent =
queryFactory
.select(
@@ -251,6 +265,7 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
mapSheetLearnEntity.uid,
mapSheetLearnEntity.uuid)
.orderBy(
stateOrder.asc(),
mapSheetAnalInferenceEntity.targetYyyy.desc(),
mapSheetAnalInferenceEntity.compareYyyy.desc(),
mapSheetAnalInferenceEntity.stage.desc())

View File

@@ -39,4 +39,12 @@ public interface ModelMngRepositoryCustom {
Optional<ModelMngEntity> findByModelId(Long id);
Optional<ModelMngEntity> findByModelId(UUID id);
/**
* 버전명으로 모델 조회
*
* @param ver 모델버전
* @return 모델정보
*/
Optional<ModelMngEntity> findByModelVer(String ver);
}

View File

@@ -246,4 +246,14 @@ public class ModelMngRepositoryImpl extends QuerydslRepositorySupport
return Optional.ofNullable(
queryFactory.selectFrom(modelMngEntity).where(modelMngEntity.uuid.eq(uuid)).fetchOne());
}
@Override
public Optional<ModelMngEntity> findByModelVer(String ver) {
return Optional.ofNullable(
queryFactory
.selectFrom(modelMngEntity)
.where(modelMngEntity.modelVer.eq(ver))
.limit(1)
.fetchOne());
}
}

View File

@@ -423,6 +423,7 @@ public class MapSheetInferenceJobService {
.map(Long::valueOf)
.toList();
// 추론 실행 도엽별 정보 조회
List<Long> jobIds = inferenceResultCoreService.findFail5kList(uuid, failedIds, type);
Set<Long> jobIdSet = new HashSet<>(jobIds);