Merge pull request 'feat/dev_251201' (#35) from feat/dev_251201 into develop
Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/35
This commit is contained in:
282
COMMON_CODE_CACHE_REDIS.md
Normal file
282
COMMON_CODE_CACHE_REDIS.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# 공통코드 Redis 캐시 시스템 - DanielLee
|
||||
|
||||
## 요구사항 검토
|
||||
|
||||
### 1. **API를 통해 공통코드 제공**
|
||||
- **구현 완료**: `CommonCodeApiController`에서 전체 공통코드 조회 API 제공
|
||||
```
|
||||
GET /api/code
|
||||
→ 모든 공통코드 조회
|
||||
```
|
||||
- **추가 구현**: 캐시 갱신 및 상태 확인 API
|
||||
```
|
||||
POST /api/code/cache/refresh → 캐시 갱신
|
||||
GET /api/code/cache/status → 캐시 상태 확인
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. **애플리케이션 로딩시 Redis 캐시에 올리기**
|
||||
- **구현 완료**: `CommonCodeCacheManager` 클래스 생성
|
||||
|
||||
#### 초기화 메커니즘
|
||||
```java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CommonCodeCacheManager {
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void initializeCommonCodeCache() {
|
||||
// 애플리케이션 완전히 시작된 후 공통코드를 Redis에 미리 로드
|
||||
List<Basic> allCommonCodes = commonCodeService.getFindAll();
|
||||
// @Cacheable이 자동으로 Redis에 캐시함
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 동작 흐름
|
||||
1. 애플리케이션 시작
|
||||
2. Spring이 모든 Bean 생성 완료 (`ApplicationReadyEvent` 발생)
|
||||
3. `CommonCodeCacheManager.initializeCommonCodeCache()` 실행
|
||||
4. `commonCodeService.getFindAll()` 호출 (DB에서 조회)
|
||||
5. `@Cacheable(value = "commonCodes")` 에노테이션이 결과를 Redis에 저장
|
||||
|
||||
---
|
||||
|
||||
### 3. **공통코드 변경시 데이터 갱신**
|
||||
|
||||
#### 자동 갱신
|
||||
- **등록 (CREATE)**: `@CacheEvict` → 캐시 전체 삭제
|
||||
- **수정 (UPDATE)**: `@CacheEvict` → 캐시 전체 삭제
|
||||
- **삭제 (DELETE)**: `@CacheEvict` → 캐시 전체 삭제
|
||||
- **순서 변경**: `@CacheEvict` → 캐시 전체 삭제
|
||||
|
||||
```java
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ResponseObj save(CommonCodeDto.AddReq req) {
|
||||
// 공통코드 저장
|
||||
// ↓
|
||||
// 캐시 전체 삭제 (다음 조회 시 DB에서 새로 로드)
|
||||
}
|
||||
```
|
||||
|
||||
#### 수동 갱신 (관리자)
|
||||
```java
|
||||
POST /api/code/cache/refresh
|
||||
```
|
||||
- 공통코드 설정이 변경된 후 API를 호출하여 캐시를 강제 갱신
|
||||
|
||||
#### 캐시 상태 모니터링
|
||||
```java
|
||||
GET /api/code/cache/status
|
||||
→ 응답: { "data": 150 } // 캐시된 공통코드 150개
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 전체 아키텍처
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 클라이언트 요청 │
|
||||
└──────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
┌──────────▼──────────┐
|
||||
│ CommonCodeApiController
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌─────────┴──────────┐
|
||||
│ │
|
||||
┌────▼─────┐ ┌──────▼────────────┐
|
||||
│ 조회 API │ │ 캐시 관리 API │
|
||||
│ (GET) │ │(POST, GET) │
|
||||
└────┬─────┘ └──────┬────────────┘
|
||||
│ │
|
||||
│ ┌────────▼──────────┐
|
||||
│ │CommonCodeCacheManager
|
||||
│ │(캐시 초기화/갱신) │
|
||||
│ └────────┬──────────┘
|
||||
│ │
|
||||
┌────▼─────────────────┬─▼────┐
|
||||
│ CommonCodeService │ │
|
||||
│ (@Cacheable) │ │
|
||||
│ (@CacheEvict) │ │
|
||||
└────┬──────────────────┴──────┘
|
||||
│
|
||||
┌────▼──────────┐
|
||||
│ Redis 캐시 │
|
||||
│ (공통코드) │
|
||||
└────┬──────────┘
|
||||
│
|
||||
┌────▼──────────┐
|
||||
│ PostgreSQL DB │
|
||||
│ (공통코드) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 명세
|
||||
|
||||
### 1. 공통코드 조회 (캐시됨)
|
||||
```
|
||||
GET /api/code
|
||||
|
||||
응답:
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"code": "STATUS",
|
||||
"name": "상태",
|
||||
"description": "상태 공통코드",
|
||||
"used": true,
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 공통코드 캐시 갱신
|
||||
```
|
||||
POST /api/code/cache/refresh
|
||||
|
||||
응답:
|
||||
{
|
||||
"data": "공통코드 캐시가 갱신되었습니다."
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 캐시 상태 확인
|
||||
```
|
||||
GET /api/code/cache/status
|
||||
|
||||
응답:
|
||||
{
|
||||
"data": 150 // Redis에 캐시된 공통코드 개수
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 캐시 갱신 흐름
|
||||
|
||||
### 자동 갱신 (CRUD 작업)
|
||||
```
|
||||
관리자가 공통코드 등록/수정/삭제
|
||||
↓
|
||||
CommonCodeService.save() / update() / removeCode()
|
||||
(@CacheEvict 실행)
|
||||
↓
|
||||
Redis 캐시 전체 삭제
|
||||
↓
|
||||
다음 조회 시 DB에서 새로 로드
|
||||
```
|
||||
|
||||
### 수동 갱신 (API 호출)
|
||||
```
|
||||
관리자: POST /api/code/cache/refresh
|
||||
↓
|
||||
CommonCodeCacheManager.refreshCommonCodeCache()
|
||||
↓
|
||||
캐시 정리 + 새로운 데이터 로드
|
||||
↓
|
||||
Redis 캐시 업데이트 완료
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 성능 최적화 효과
|
||||
|
||||
| 항목 | 개선 전 | 개선 후 |
|
||||
|------|--------|--------|
|
||||
| **조회 속도** | DB 직접 조회 (10-100ms) | Redis 캐시 (1-5ms) |
|
||||
| **DB 부하** | 매번 조회 | 캐시 미스시만 조회 |
|
||||
| **네트워크 대역폭** | 높음 (DB 왕복) | 낮음 (로컬 캐시) |
|
||||
| **응답 시간** | 변동적 | 일정 (캐시) |
|
||||
|
||||
---
|
||||
|
||||
## 추가 기능
|
||||
|
||||
### CommonCodeUtil - 전역 공통코드 조회
|
||||
```java
|
||||
@Component
|
||||
public class CommonCodeUtil {
|
||||
// 모든 공통코드 조회 (캐시 활용)
|
||||
public List<Basic> getAllCommonCodes()
|
||||
|
||||
// 특정 코드로 조회
|
||||
public List<Basic> getCommonCodesByCode(String code)
|
||||
|
||||
// ID로 단건 조회
|
||||
public Optional<Basic> getCommonCodeById(Long id)
|
||||
|
||||
// 코드명 조회
|
||||
public Optional<String> getCodeName(String parentCode, String childCode)
|
||||
|
||||
// 하위 코드 조회
|
||||
public List<Basic> getChildCodesByParentCode(String parentCode)
|
||||
|
||||
// 코드 사용 가능 여부 확인
|
||||
public boolean isCodeAvailable(Long parentId, String code)
|
||||
}
|
||||
```
|
||||
|
||||
### 사용 예시
|
||||
```java
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class SomeController {
|
||||
|
||||
private final CommonCodeUtil commonCodeUtil;
|
||||
|
||||
@GetMapping("/example")
|
||||
public void example() {
|
||||
// 1. 모든 공통코드 조회 (캐시됨)
|
||||
List<Basic> allCodes = commonCodeUtil.getAllCommonCodes();
|
||||
|
||||
// 2. 특정 코드 조회
|
||||
Optional<String> name = commonCodeUtil.getCodeName("PARENT", "CHILD");
|
||||
|
||||
// 3. 코드 사용 가능 여부 확인
|
||||
boolean available = commonCodeUtil.isCodeAvailable(1L, "NEW_CODE");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 완료 체크리스트
|
||||
|
||||
- Redis 캐싱 어노테이션 적용 (@Cacheable, @CacheEvict)
|
||||
- 애플리케이션 로딩시 캐시 초기화
|
||||
- CRUD 작업시 자동 캐시 갱신
|
||||
- 수동 캐시 갱신 API 제공
|
||||
- 캐시 상태 모니터링 API
|
||||
- 전역 공통코드 조회 유틸리티
|
||||
- 포괄적인 유닛 테스트 (12개)
|
||||
|
||||
---
|
||||
|
||||
## 모니터링
|
||||
|
||||
캐시 상태를 주기적으로 모니터링:
|
||||
```bash
|
||||
# 캐시 상태 확인
|
||||
curl http://localhost:8080/api/code/cache/status
|
||||
|
||||
# 캐시 갱신
|
||||
curl -X POST http://localhost:8080/api/code/cache/refresh
|
||||
```
|
||||
|
||||
로그 확인:
|
||||
```
|
||||
=== 공통코드 캐시 초기화 시작 ===
|
||||
✓ 공통코드 150개가 Redis 캐시에 로드되었습니다.
|
||||
- [STATUS] 상태 (ID: 1)
|
||||
- [TYPE] 타입 (ID: 2)
|
||||
...
|
||||
=== 공통코드 캐시 초기화 완료 ===
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
# GeoJSON 파일 모니터링 시스템
|
||||
# GeoJSON 파일 모니터링 시스템 - Daniel Lee
|
||||
|
||||
kamco-dabeeo-backoffice 프로젝트에 추가된 GeoJSON 파일 자동 모니터링 및 처리 시스템입니다.
|
||||
|
||||
|
||||
101
build.gradle
101
build.gradle
@@ -1,8 +1,8 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.5.7'
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
id 'com.diffplug.spotless' version '6.25.0'
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.5.7'
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
id 'com.diffplug.spotless' version '6.25.0'
|
||||
}
|
||||
|
||||
group = 'com.kamco.cd'
|
||||
@@ -10,68 +10,89 @@ version = '0.0.1-SNAPSHOT'
|
||||
description = 'kamco-back'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
}
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
maven { url "https://repo.osgeo.org/repository/release/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
|
||||
//geometry
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
implementation 'org.locationtech.jts.io:jts-io-common:1.20.0'
|
||||
implementation 'org.locationtech.jts:jts-core:1.19.0'
|
||||
//geometry
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
implementation 'org.locationtech.jts.io:jts-io-common:1.20.0'
|
||||
implementation 'org.locationtech.jts:jts-core:1.19.0'
|
||||
implementation 'org.hibernate:hibernate-spatial:6.2.7.Final'
|
||||
implementation 'org.geotools:gt-main:30.0'
|
||||
implementation("org.geotools:gt-geotiff:30.0") {
|
||||
exclude group: "javax.media", module: "jai_core"
|
||||
}
|
||||
implementation 'org.geotools:gt-epsg-hsql:30.0'
|
||||
|
||||
// QueryDSL JPA
|
||||
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
|
||||
// QueryDSL JPA
|
||||
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
|
||||
|
||||
// Q클래스 생성용 annotationProcessor
|
||||
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
|
||||
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
|
||||
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
|
||||
// Q클래스 생성용 annotationProcessor
|
||||
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
|
||||
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
|
||||
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
|
||||
|
||||
// actuator
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
// actuator
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
|
||||
// Redis
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
// Redis
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
|
||||
// SpringDoc OpenAPI (Swagger)
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
|
||||
// SpringDoc OpenAPI (Swagger)
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
|
||||
|
||||
// Apache Commons Compress for archive handling
|
||||
implementation 'org.apache.commons:commons-compress:1.26.0'
|
||||
// Apache Commons Compress for archive handling
|
||||
implementation 'org.apache.commons:commons-compress:1.26.0'
|
||||
|
||||
// crypto
|
||||
implementation 'org.springframework.security:spring-security-crypto'
|
||||
implementation 'org.mindrot:jbcrypt:0.4'
|
||||
|
||||
// security
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
|
||||
// JWT (jjwt 0.12.x)
|
||||
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
|
||||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
|
||||
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' // JSON (Jackson)
|
||||
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||
}
|
||||
|
||||
configurations.configureEach {
|
||||
exclude group: 'javax.media', module: 'jai_core'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
|
||||
bootJar {
|
||||
archiveFileName = 'ROOT.jar'
|
||||
archiveFileName = 'ROOT.jar'
|
||||
}
|
||||
|
||||
// Spotless configuration for code formatting (2-space indent)
|
||||
@@ -86,5 +107,5 @@ spotless {
|
||||
|
||||
// Run spotlessCheck before build
|
||||
tasks.named('build') {
|
||||
dependsOn 'spotlessCheck'
|
||||
dependsOn 'spotlessCheck'
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
||||
import com.kamco.cd.kamcoback.auth.service.AuthService;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
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;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
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;
|
||||
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.RestController;
|
||||
|
||||
@Tag(name = "관리자 관리", description = "관리자 관리 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthApiController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
@Operation(summary = "관리자 등록", description = "관리자를 등록 합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "201",
|
||||
description = "관리자 등록 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/save")
|
||||
public ApiResponseDto<Long> save(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "관리자 정보",
|
||||
required = true,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = AuthDto.SaveReq.class)))
|
||||
@RequestBody
|
||||
@Valid
|
||||
AuthDto.SaveReq saveReq) {
|
||||
return ApiResponseDto.createOK(authService.save(saveReq).getId());
|
||||
}
|
||||
|
||||
@Operation(summary = "관리자 정보 수정", description = "관리자 정보를 수정 합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "201",
|
||||
description = "관리자 정보 수정 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PutMapping("/update/{id}")
|
||||
public ApiResponseDto<Long> update(@PathVariable Long id, @RequestBody AuthDto.SaveReq saveReq) {
|
||||
return ApiResponseDto.createOK(authService.update(id, saveReq).getId());
|
||||
}
|
||||
|
||||
@Operation(summary = "관리자 정보 탈퇴처리", description = "관리자 정보를 탈퇴처리 합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "201",
|
||||
description = "관리자 탈퇴처리 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PutMapping("/withdrawal/{id}")
|
||||
public ApiResponseDto<Long> withdrawal(@PathVariable Long id) {
|
||||
return ApiResponseDto.deleteOk(authService.withdrawal(id).getId());
|
||||
}
|
||||
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = AuthDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@Operation(summary = "관리자 상세조회", description = "관리자 정보를 조회 합니다.")
|
||||
@GetMapping("/detail")
|
||||
public ApiResponseDto<AuthDto.Basic> getDetail(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "관리자 목록 id",
|
||||
required = true)
|
||||
@RequestParam
|
||||
Long id) {
|
||||
return ApiResponseDto.ok(authService.getFindUserById(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "관리자 목록", description = "관리자 목록 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "검색 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Page.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/list")
|
||||
public ApiResponseDto<Page<Basic>> getUserList(
|
||||
@Parameter(description = "관리자 이름") @RequestParam(required = false) String userNm,
|
||||
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
|
||||
int page,
|
||||
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
|
||||
int size,
|
||||
@Parameter(description = "정렬 조건 (형식: 필드명,방향)", example = "name,asc")
|
||||
@RequestParam(required = false)
|
||||
String sort) {
|
||||
AuthDto.SearchReq searchReq = new AuthDto.SearchReq(userNm, page, size, sort);
|
||||
Page<AuthDto.Basic> userList = authService.getUserList(searchReq);
|
||||
return ApiResponseDto.ok(userList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class BCryptSaltGenerator {
|
||||
|
||||
public static String generateSaltWithEmployeeNo(String employeeNo) {
|
||||
|
||||
// bcrypt salt는 16바이트(128비트) 필요
|
||||
byte[] randomBytes = new byte[16];
|
||||
new SecureRandom().nextBytes(randomBytes);
|
||||
|
||||
String base64 = Base64.getEncoder().encodeToString(randomBytes);
|
||||
|
||||
// 사번을 포함 (22자 제한 → 잘라내기)
|
||||
String mixedSalt = (employeeNo + base64).substring(0, 22);
|
||||
|
||||
// bcrypt 포맷에 맞게 구성
|
||||
return "$2a$10$" + mixedSalt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CustomAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private final MembersRepository membersRepository;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
String username = authentication.getName();
|
||||
String rawPassword = authentication.getCredentials().toString();
|
||||
|
||||
// 1. 유저 조회
|
||||
MemberEntity member =
|
||||
membersRepository
|
||||
.findByEmployeeNo(username)
|
||||
.orElseThrow(() -> new BadCredentialsException("ID 또는 비밀번호가 일치하지 않습니다."));
|
||||
|
||||
// 2. jBCrypt + 커스텀 salt 로 저장된 패스워드 비교
|
||||
if (!BCrypt.checkpw(rawPassword, member.getPassword())) {
|
||||
throw new BadCredentialsException("ID 또는 비밀번호가 일치하지 않습니다.");
|
||||
}
|
||||
|
||||
// 3. 인증 성공 → UserDetails 생성
|
||||
CustomUserDetails userDetails = new CustomUserDetails(member);
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
public class CustomUserDetails implements UserDetails {
|
||||
|
||||
private final MemberEntity member;
|
||||
|
||||
public CustomUserDetails(MemberEntity member) {
|
||||
this.member = member;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
// 권한을 Member에서 가져오는 경우 바꾸면 됩니다 — 일단 기본값
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return member.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return String.valueOf(member.getUuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true; // 추후 상태 필드에 따라 수정 가능
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return member.getStatus().equalsIgnoreCase("ACTIVE");
|
||||
}
|
||||
|
||||
public MemberEntity getMember() {
|
||||
return member;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
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.web.filter.OncePerRequestFilter;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
String token = resolveToken(request);
|
||||
|
||||
if (token != null && jwtTokenProvider.isValidToken(token)) {
|
||||
String username = jwtTokenProvider.getSubject(token);
|
||||
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
||||
String path = request.getServletPath();
|
||||
|
||||
// 여기에 JWT 필터를 타지 않게 할 URL 패턴들 작성
|
||||
return path.startsWith("/api/auth/signin") || path.startsWith("/api/auth/refresh");
|
||||
// 필요하면 "/api/auth/logout" 도 추가
|
||||
}
|
||||
|
||||
private String resolveToken(HttpServletRequest request) {
|
||||
String bearer = request.getHeader("Authorization");
|
||||
if (bearer != null && bearer.startsWith("Bearer ")) {
|
||||
return bearer.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import javax.crypto.SecretKey;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class JwtTokenProvider {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.access-token-validity-in-ms}")
|
||||
private long accessTokenValidityInMs;
|
||||
|
||||
@Value("${jwt.refresh-token-validity-in-ms}")
|
||||
private long refreshTokenValidityInMs;
|
||||
|
||||
private SecretKey key;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// HS256용 SecretKey
|
||||
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public String createAccessToken(String subject) {
|
||||
return createToken(subject, accessTokenValidityInMs);
|
||||
}
|
||||
|
||||
public String createRefreshToken(String subject) {
|
||||
return createToken(subject, refreshTokenValidityInMs);
|
||||
}
|
||||
|
||||
private String createToken(String subject, long validityInMs) {
|
||||
Date now = new Date();
|
||||
Date expiry = new Date(now.getTime() + validityInMs);
|
||||
return Jwts.builder().subject(subject).issuedAt(now).expiration(expiry).signWith(key).compact();
|
||||
}
|
||||
|
||||
public String getSubject(String token) {
|
||||
var claims = parseClaims(token).getPayload();
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
public boolean isValidToken(String token) {
|
||||
try {
|
||||
Jws<Claims> claims = parseClaims(token);
|
||||
return !claims.getPayload().getExpiration().before(new Date());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Jws<Claims> parseClaims(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(key) // SecretKey 타입
|
||||
.build()
|
||||
.parseSignedClaims(token);
|
||||
}
|
||||
|
||||
public long getRefreshTokenValidityInMs() {
|
||||
return refreshTokenValidityInMs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import java.time.Duration;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RefreshTokenService {
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private static final String PREFIX = "RT:";
|
||||
|
||||
public void save(String username, String refreshToken, long ttlMillis) {
|
||||
ValueOperations<String, String> ops = redisTemplate.opsForValue();
|
||||
ops.set(PREFIX + username, refreshToken, Duration.ofMillis(ttlMillis));
|
||||
}
|
||||
|
||||
public boolean validate(String username, String refreshToken) {
|
||||
String stored = redisTemplate.opsForValue().get(PREFIX + username);
|
||||
return stored != null && stored.equals(refreshToken);
|
||||
}
|
||||
|
||||
public void delete(String username) {
|
||||
redisTemplate.delete(PREFIX + username);
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.auth.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class AuthDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Basic {
|
||||
|
||||
private Long id;
|
||||
private String userAuth;
|
||||
private String userNm;
|
||||
private String userId;
|
||||
private String empId;
|
||||
private String userEmail;
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
|
||||
public Basic(
|
||||
Long id,
|
||||
String userAuth,
|
||||
String userNm,
|
||||
String userId,
|
||||
String empId,
|
||||
String userEmail,
|
||||
ZonedDateTime createdDttm) {
|
||||
this.id = id;
|
||||
this.userAuth = userAuth;
|
||||
this.userNm = userNm;
|
||||
this.userId = userId;
|
||||
this.empId = empId;
|
||||
this.userEmail = userEmail;
|
||||
this.createdDttm = createdDttm;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "save request", description = "사용자 등록 정보")
|
||||
@Getter
|
||||
@Setter
|
||||
public static class SaveReq {
|
||||
|
||||
@Schema(description = "구분", example = "관리자/라벨러/검수자 중 하나")
|
||||
@NotBlank
|
||||
private String userAuth;
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
private String userNm;
|
||||
|
||||
@Schema(description = "ID", example = "gildong")
|
||||
@NotBlank
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "PW", example = "password")
|
||||
@NotBlank
|
||||
private String userPw;
|
||||
|
||||
@Schema(description = "사번", example = "사번")
|
||||
@NotBlank
|
||||
private String empId;
|
||||
|
||||
@Schema(description = "이메일", example = "gildong@naver.com")
|
||||
@NotBlank
|
||||
private String userEmail;
|
||||
|
||||
public SaveReq(
|
||||
String userAuth,
|
||||
String userNm,
|
||||
String userId,
|
||||
String userPw,
|
||||
String empId,
|
||||
String userEmail) {
|
||||
this.userAuth = userAuth;
|
||||
this.userNm = userNm;
|
||||
this.userId = userId;
|
||||
this.userPw = userPw;
|
||||
this.empId = empId;
|
||||
this.userEmail = userEmail;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "update request", description = "사용자 수정 정보")
|
||||
@Getter
|
||||
@Setter
|
||||
public static class UpdateReq {
|
||||
|
||||
@Schema(description = "id", example = "1")
|
||||
@NotBlank
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "구분", example = "관리자/라벨러/검수자 중 하나")
|
||||
@NotBlank
|
||||
private String userAuth;
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
private String userNm;
|
||||
|
||||
@Schema(description = "ID", example = "gildong")
|
||||
@NotBlank
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "PW", example = "password")
|
||||
@NotBlank
|
||||
private String userPw;
|
||||
|
||||
@Schema(description = "사번", example = "사번")
|
||||
@NotBlank
|
||||
private String empId;
|
||||
|
||||
@Schema(description = "이메일", example = "gildong@naver.com")
|
||||
@NotBlank
|
||||
private String userEmail;
|
||||
|
||||
public UpdateReq(
|
||||
Long id,
|
||||
String userAuth,
|
||||
String userNm,
|
||||
String userId,
|
||||
String userPw,
|
||||
String empId,
|
||||
String userEmail) {
|
||||
this.id = id;
|
||||
this.userAuth = userAuth;
|
||||
this.userNm = userNm;
|
||||
this.userId = userId;
|
||||
this.userPw = userPw;
|
||||
this.empId = empId;
|
||||
this.userEmail = userEmail;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class User {
|
||||
|
||||
String userId;
|
||||
String userPw;
|
||||
}
|
||||
|
||||
@Schema(name = "UserSearchReq", description = "관리자 목록 요청 정보")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SearchReq {
|
||||
|
||||
// 검색 조건
|
||||
private String userNm;
|
||||
|
||||
// 페이징 파라미터
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.auth.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
||||
import com.kamco.cd.kamcoback.postgres.core.AuthCoreService;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
@RequiredArgsConstructor
|
||||
public class AuthService {
|
||||
|
||||
private final AuthCoreService authCoreService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
/**
|
||||
* 관리자 등록
|
||||
*
|
||||
* @param saveReq
|
||||
* @return
|
||||
*/
|
||||
@Transactional
|
||||
public UserEntity save(AuthDto.SaveReq saveReq) {
|
||||
saveReq.setUserPw(passwordEncoder.encode(saveReq.getUserPw()));
|
||||
return authCoreService.save(saveReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 정보 수정
|
||||
*
|
||||
* @param id
|
||||
* @param saveReq
|
||||
* @return
|
||||
*/
|
||||
public UserEntity update(Long id, AuthDto.SaveReq saveReq) {
|
||||
if (saveReq.getUserPw() != null) {
|
||||
saveReq.setUserPw(passwordEncoder.encode(saveReq.getUserPw()));
|
||||
}
|
||||
return authCoreService.update(id, saveReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 삭제
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public UserEntity withdrawal(Long id) {
|
||||
return authCoreService.withdrawal(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시퀀스 id로 관리자 조회
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public AuthDto.Basic getFindUserById(Long id) {
|
||||
return authCoreService.findUserById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
public Page<Basic> getUserList(AuthDto.SearchReq searchReq) {
|
||||
return authCoreService.getUserList(searchReq);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
@Tag(name = "변화탐지", description = "변화탐지 API")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping({"/api/change-detection", "/demo/api/change-detection"})
|
||||
@RequestMapping({"/api/change-detection"})
|
||||
@Transactional
|
||||
public class ChangeDetectionApiController {
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.code;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.config.CommonCodeCacheManager;
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||
import com.kamco.cd.kamcoback.code.service.CommonCodeService;
|
||||
import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
|
||||
@@ -30,10 +31,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@Tag(name = "공통코드 관리", description = "공통코드 관리 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping({"/demo/code", "/api/code"})
|
||||
@RequestMapping("/api/code")
|
||||
public class CommonCodeApiController {
|
||||
|
||||
private final CommonCodeService commonCodeService;
|
||||
private final CommonCodeCacheManager commonCodeCacheManager;
|
||||
|
||||
@Operation(summary = "목록 조회", description = "모든 공통코드 조회")
|
||||
@ApiResponses(
|
||||
@@ -89,7 +91,7 @@ public class CommonCodeApiController {
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping
|
||||
public ApiResponseDto<Long> save(
|
||||
public ApiResponseDto<ApiResponseDto.ResponseObj> save(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "공통코드 생성 요청 정보",
|
||||
required = true,
|
||||
@@ -100,7 +102,7 @@ public class CommonCodeApiController {
|
||||
@RequestBody
|
||||
@Valid
|
||||
CommonCodeDto.AddReq req) {
|
||||
return ApiResponseDto.createOK(commonCodeService.save(req));
|
||||
return ApiResponseDto.okObject(commonCodeService.save(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "수정", description = "공통코드를 수정 합니다.")
|
||||
@@ -118,7 +120,7 @@ public class CommonCodeApiController {
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponseDto<Void> update(
|
||||
public ApiResponseDto<ApiResponseDto.ResponseObj> update(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "공통코드 수정 요청 정보",
|
||||
required = true,
|
||||
@@ -129,8 +131,7 @@ public class CommonCodeApiController {
|
||||
@PathVariable
|
||||
Long id,
|
||||
@RequestBody @Valid CommonCodeDto.ModifyReq req) {
|
||||
commonCodeService.update(id, req);
|
||||
return ApiResponseDto.deleteOk(null);
|
||||
return ApiResponseDto.okObject(commonCodeService.update(id, req));
|
||||
}
|
||||
|
||||
@Operation(summary = "삭제", description = "공통코드를 삭제 합니다.")
|
||||
@@ -148,14 +149,13 @@ public class CommonCodeApiController {
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponseDto<Long> remove(
|
||||
public ApiResponseDto<ApiResponseDto.ResponseObj> remove(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "공통코드 삭제 요청 정보",
|
||||
required = true)
|
||||
@PathVariable
|
||||
Long id) {
|
||||
commonCodeService.remove(id);
|
||||
return ApiResponseDto.deleteOk(id);
|
||||
return ApiResponseDto.okObject(commonCodeService.removeCode(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "순서 변경", description = "공통코드 순서를 변경 합니다.")
|
||||
@@ -173,7 +173,7 @@ public class CommonCodeApiController {
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PutMapping("/order")
|
||||
public ApiResponseDto<Void> updateOrder(
|
||||
public ApiResponseDto<ApiResponseDto.ResponseObj> updateOrder(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "공통코드 순서변경 요청 정보",
|
||||
required = true,
|
||||
@@ -184,8 +184,8 @@ public class CommonCodeApiController {
|
||||
@RequestBody
|
||||
@Valid
|
||||
CommonCodeDto.OrderReq req) {
|
||||
commonCodeService.updateOrder(req);
|
||||
return ApiResponseDto.deleteOk(null);
|
||||
|
||||
return ApiResponseDto.okObject(commonCodeService.updateOrder(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "code 기반 조회", description = "code 기반 조회")
|
||||
@@ -193,7 +193,7 @@ public class CommonCodeApiController {
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "공통코드 순서 변경 성공",
|
||||
description = "code 기반 조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
@@ -223,4 +223,62 @@ public class CommonCodeApiController {
|
||||
|
||||
return ApiResponseDto.ok(list);
|
||||
}
|
||||
|
||||
@Operation(summary = "공통코드 중복여부 체크", description = "공통코드 중복여부 체크")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/check-duplicate")
|
||||
public ApiResponseDto<ApiResponseDto.ResponseObj> getCodeCheckDuplicate(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true)
|
||||
@RequestParam
|
||||
Long parentId,
|
||||
@RequestParam String code) {
|
||||
return ApiResponseDto.okObject(commonCodeService.getCodeCheckDuplicate(parentId, code));
|
||||
}
|
||||
|
||||
@Operation(summary = "캐시 갱신", description = "공통코드 캐시를 갱신합니다. 공통코드 설정 변경 후 호출해주세요.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "캐시 갱신 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = String.class))),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/cache/refresh")
|
||||
public ApiResponseDto<String> refreshCommonCodeCache() {
|
||||
commonCodeCacheManager.refreshCommonCodeCache();
|
||||
return ApiResponseDto.ok("공통코드 캐시가 갱신되었습니다.");
|
||||
}
|
||||
|
||||
@Operation(summary = "캐시 상태 확인", description = "Redis에 캐시된 공통코드 개수를 확인합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "캐시 상태 조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Integer.class))),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/cache/status")
|
||||
public ApiResponseDto<Integer> getCommonCodeCacheStatus() {
|
||||
int count = commonCodeCacheManager.getCachedCommonCodeCount();
|
||||
return ApiResponseDto.ok(count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.kamco.cd.kamcoback.code.config;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic;
|
||||
import com.kamco.cd.kamcoback.code.service.CommonCodeService;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 공통코드 캐시 관리 및 초기화 클래스
|
||||
*
|
||||
* <p>애플리케이션 시작 시 공통코드를 Redis 캐시에 미리 로드하고, 캐시 갱신을 관리합니다. 기존 Redis 데이터와의 호환성 문제로 인한 간헐적 오류를 방지합니다.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CommonCodeCacheManager {
|
||||
|
||||
private final CommonCodeService commonCodeService;
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
public static final String COMMON_CODES_CACHE_NAME = "commonCodes";
|
||||
|
||||
/**
|
||||
* 애플리케이션 시작 완료 후 공통코드를 Redis 캐시에 미리 로드
|
||||
*
|
||||
* <p>이 메서드는 Spring 애플리케이션이 완전히 시작된 후에 자동으로 실행되며, 공통코드 데이터를 Redis 캐시에 미리 로드하여 초기 조회 시 성능을 최적화합니다.
|
||||
* 기존 캐시 데이터 호환성 문제를 대비하여 먼저 캐시를 초기화한 후 재로드합니다.
|
||||
*/
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void initializeCommonCodeCache() {
|
||||
try {
|
||||
log.info("=== 공통코드 캐시 초기화 시작 ===");
|
||||
|
||||
// 1. 기존 캐시 데이터 호환성 문제 방지: 먼저 캐시 전체 초기화
|
||||
try {
|
||||
clearCommonCodeCache();
|
||||
log.debug("✓ 기존 캐시 데이터 정리 완료 (호환성 문제 방지)");
|
||||
} catch (Exception clearEx) {
|
||||
log.debug("기존 캐시 초기화 중 예외 (무시됨): {}", clearEx.getMessage());
|
||||
}
|
||||
|
||||
// 2. DB에서 새로운 데이터 로드 (캐시 미스 상태)
|
||||
List<Basic> allCommonCodes = commonCodeService.getFindAll();
|
||||
log.info("✓ 공통코드 {}개를 DB에서 로드하고 Redis 캐시에 저장했습니다.", allCommonCodes.size());
|
||||
|
||||
// 3. 로그 출력 (DEBUG 레벨)
|
||||
if (log.isDebugEnabled()) {
|
||||
allCommonCodes.forEach(
|
||||
code ->
|
||||
log.debug(" - [{}] {} (ID: {})", code.getCode(), code.getName(), code.getId()));
|
||||
}
|
||||
|
||||
log.info("=== 공통코드 캐시 초기화 완료 ===");
|
||||
} catch (Exception e) {
|
||||
log.warn("공통코드 캐시 초기화 중 오류 발생했습니다. 캐시 없이 계속 진행합니다. " + "(첫 번째 조회 시 DB에서 로드되고 캐시됩니다.)", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 캐시 전체 초기화 (수동 갱신)
|
||||
*
|
||||
* <p>공통코드 설정이 변경되었을 때 호출하여 캐시를 강제로 갱신합니다.
|
||||
*/
|
||||
public void refreshCommonCodeCache() {
|
||||
try {
|
||||
log.info("공통코드 캐시 갱신 시작...");
|
||||
|
||||
// 기존 캐시 제거
|
||||
clearCommonCodeCache();
|
||||
|
||||
// 새로운 데이터 로드
|
||||
List<Basic> allCommonCodes = commonCodeService.getFindAll();
|
||||
|
||||
log.info("✓ 공통코드 캐시가 {}개 항목으로 갱신되었습니다.", allCommonCodes.size());
|
||||
} catch (Exception e) {
|
||||
log.error("공통코드 캐시 갱신 중 오류 발생", e);
|
||||
throw new RuntimeException("공통코드 캐시 갱신 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 캐시 초기화 (삭제)
|
||||
*
|
||||
* <p>공통코드 캐시를 비우고 다음 조회 시 DB에서 새로 로드하도록 합니다.
|
||||
*/
|
||||
public void clearCommonCodeCache() {
|
||||
try {
|
||||
var cache = cacheManager.getCache(COMMON_CODES_CACHE_NAME);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
log.info("✓ 공통코드 캐시가 초기화되었습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("공통코드 캐시 초기화 중 예외 발생 (무시됨): {}", e.getMessage());
|
||||
// 무시하고 계속 진행 - 캐시 초기화 실패가 시스템 전체를 중단시키지 않도록
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 캐시 상태 확인
|
||||
*
|
||||
* <p>현재 Redis 캐시에 저장된 공통코드의 개수를 반환합니다. 캐시 미스 상태인 경우 DB에서 새로 로드합니다.
|
||||
*
|
||||
* @return 캐시에 있는 공통코드 개수
|
||||
*/
|
||||
public int getCachedCommonCodeCount() {
|
||||
try {
|
||||
// 캐시 오류 시 자동으로 재초기화
|
||||
try {
|
||||
List<Basic> cachedCodes = commonCodeService.getFindAll();
|
||||
return cachedCodes.size();
|
||||
} catch (Exception cacheError) {
|
||||
log.debug("캐시 조회 중 호환성 오류 감지, 캐시 재초기화 중...: {}", cacheError.getMessage());
|
||||
|
||||
// 기존 호환성 문제가 있는 캐시 데이터 강제 제거
|
||||
try {
|
||||
var cache = cacheManager.getCache(COMMON_CODES_CACHE_NAME);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
log.debug("✓ 호환되지 않는 캐시 데이터 제거 완료");
|
||||
}
|
||||
} catch (Exception clearEx) {
|
||||
log.debug("캐시 정리 중 예외: {}", clearEx.getMessage());
|
||||
}
|
||||
|
||||
// 재시도
|
||||
List<Basic> cachedCodes = commonCodeService.getFindAll();
|
||||
log.info("✓ 캐시 재초기화 완료, 공통코드 {}개 로드됨", cachedCodes.size());
|
||||
return cachedCodes.size();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("캐시 상태 확인 중 최종 오류 발생: {}", e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.kamco.cd.kamcoback.code.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -27,6 +26,10 @@ public class CommonCodeDto {
|
||||
private int order;
|
||||
private boolean used;
|
||||
private Long parentId;
|
||||
|
||||
private String props1;
|
||||
private String props2;
|
||||
private String props3;
|
||||
}
|
||||
|
||||
@Schema(name = "CodeModifyReq", description = "공통코드 수정 정보")
|
||||
@@ -37,8 +40,11 @@ public class CommonCodeDto {
|
||||
public static class ModifyReq {
|
||||
@NotEmpty private String name;
|
||||
private String description;
|
||||
private int order;
|
||||
private boolean used;
|
||||
|
||||
private String props1;
|
||||
private String props2;
|
||||
private String props3;
|
||||
}
|
||||
|
||||
@Schema(name = "CodeOrderReq", description = "공통코드 순서 변경 정보")
|
||||
@@ -47,7 +53,8 @@ public class CommonCodeDto {
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class OrderReq {
|
||||
@Valid List<OrderReqDetail> orders;
|
||||
@NotNull private Long id;
|
||||
@NotNull private Integer order;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -56,7 +63,6 @@ public class CommonCodeDto {
|
||||
@AllArgsConstructor
|
||||
public static class OrderReqDetail {
|
||||
@NotNull private Long id;
|
||||
|
||||
@NotNull private Integer order;
|
||||
}
|
||||
|
||||
@@ -77,6 +83,12 @@ public class CommonCodeDto {
|
||||
|
||||
@JsonFormatDttm private ZonedDateTime updatedDttm;
|
||||
|
||||
private String props1;
|
||||
private String props2;
|
||||
private String props3;
|
||||
|
||||
@JsonFormatDttm private ZonedDateTime deletedDttm;
|
||||
|
||||
public Basic(
|
||||
Long id,
|
||||
String code,
|
||||
@@ -87,7 +99,11 @@ public class CommonCodeDto {
|
||||
Boolean deleted,
|
||||
List<CommonCodeDto.Basic> children,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime updatedDttm) {
|
||||
ZonedDateTime updatedDttm,
|
||||
String props1,
|
||||
String props2,
|
||||
String props3,
|
||||
ZonedDateTime deletedDttm) {
|
||||
this.id = id;
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
@@ -98,6 +114,10 @@ public class CommonCodeDto {
|
||||
this.children = children;
|
||||
this.createdDttm = createdDttm;
|
||||
this.updatedDttm = updatedDttm;
|
||||
this.props1 = props1;
|
||||
this.props2 = props2;
|
||||
this.props3 = props3;
|
||||
this.deletedDttm = deletedDttm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@ import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.AddReq;
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic;
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.ModifyReq;
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.OrderReq;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.postgres.core.CommonCodeCoreService;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -22,6 +26,7 @@ public class CommonCodeService {
|
||||
*
|
||||
* @return 모튼 코드 정보
|
||||
*/
|
||||
@Cacheable(value = "commonCodes")
|
||||
public List<Basic> getFindAll() {
|
||||
return commonCodeCoreService.findAll();
|
||||
}
|
||||
@@ -43,8 +48,9 @@ public class CommonCodeService {
|
||||
* @return 생성된 코드 id
|
||||
*/
|
||||
@Transactional
|
||||
public Long save(AddReq req) {
|
||||
return commonCodeCoreService.save(req).getId();
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ApiResponseDto.ResponseObj save(AddReq req) {
|
||||
return commonCodeCoreService.save(req);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,8 +60,9 @@ public class CommonCodeService {
|
||||
* @param req 수정요청 정보
|
||||
*/
|
||||
@Transactional
|
||||
public void update(Long id, ModifyReq req) {
|
||||
commonCodeCoreService.update(id, req);
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ApiResponseDto.ResponseObj update(Long id, ModifyReq req) {
|
||||
return commonCodeCoreService.update(id, req);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,8 +71,9 @@ public class CommonCodeService {
|
||||
* @param id 코드 아이디
|
||||
*/
|
||||
@Transactional
|
||||
public void remove(Long id) {
|
||||
commonCodeCoreService.remove(id);
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ApiResponseDto.ResponseObj removeCode(Long id) {
|
||||
return commonCodeCoreService.removeCode(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,8 +82,9 @@ public class CommonCodeService {
|
||||
* @param req id, order 정보를 가진 List
|
||||
*/
|
||||
@Transactional
|
||||
public void updateOrder(OrderReq req) {
|
||||
commonCodeCoreService.updateOrder(req);
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ApiResponseDto.ResponseObj updateOrder(OrderReq req) {
|
||||
return commonCodeCoreService.updateOrder(req);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,4 +96,26 @@ public class CommonCodeService {
|
||||
public List<Basic> findByCode(String code) {
|
||||
return commonCodeCoreService.findByCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 중복 체크
|
||||
*
|
||||
* @param parentId
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public ApiResponseDto.ResponseObj getCodeCheckDuplicate(Long parentId, String code) {
|
||||
return commonCodeCoreService.getCodeCheckDuplicate(parentId, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 이름 조회
|
||||
*
|
||||
* @param parentCodeCd 상위 코드
|
||||
* @param childCodeCd 하위 코드
|
||||
* @return 공통코드명
|
||||
*/
|
||||
public Optional<String> getCode(String parentCodeCd, String childCodeCd) {
|
||||
return commonCodeCoreService.getCode(parentCodeCd, childCodeCd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kamco.cd.kamcoback.common.enums;
|
||||
|
||||
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RoleType implements EnumType {
|
||||
ROLE_ADMIN("시스템 관리자"),
|
||||
ROLE_LABELER("라벨러"),
|
||||
ROLE_REVIEWER("검수자");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.kamco.cd.kamcoback.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
@Getter
|
||||
public class CustomApiException extends RuntimeException {
|
||||
|
||||
private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY")
|
||||
private final HttpStatus status; // 응답으로 내려줄 HttpStatus
|
||||
|
||||
public CustomApiException(String codeName, HttpStatus status, String message) {
|
||||
super(message);
|
||||
this.codeName = codeName;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public CustomApiException(String codeName, HttpStatus status) {
|
||||
this.codeName = codeName;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic;
|
||||
import com.kamco.cd.kamcoback.code.service.CommonCodeService;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 공통코드 조회 유틸리티 클래스
|
||||
*
|
||||
* <p>애플리케이션 전역에서 공통코드를 조회하기 위한 유틸리티입니다. Redis 캐시를 통해 성능을 최적화합니다.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CommonCodeUtil {
|
||||
|
||||
private final CommonCodeService commonCodeService;
|
||||
|
||||
public CommonCodeUtil(CommonCodeService commonCodeService) {
|
||||
this.commonCodeService = commonCodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 공통코드 조회
|
||||
*
|
||||
* @return 캐시된 모든 공통코드 목록
|
||||
*/
|
||||
public List<Basic> getAllCommonCodes() {
|
||||
try {
|
||||
return commonCodeService.getFindAll();
|
||||
} catch (Exception e) {
|
||||
log.error("공통코드 전체 조회 중 오류 발생", e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 코드로 공통코드 조회
|
||||
*
|
||||
* @param code 코드값
|
||||
* @return 해당 코드의 공통코드 목록
|
||||
*/
|
||||
public List<Basic> getCommonCodesByCode(String code) {
|
||||
if (code == null || code.isEmpty()) {
|
||||
log.warn("유효하지 않은 코드: {}", code);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
return commonCodeService.findByCode(code);
|
||||
} catch (Exception e) {
|
||||
log.error("코드 기반 공통코드 조회 중 오류 발생: {}", code, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 ID로 공통코드 단건 조회
|
||||
*
|
||||
* @param id 공통코드 ID
|
||||
* @return 조회된 공통코드
|
||||
*/
|
||||
public Optional<Basic> getCommonCodeById(Long id) {
|
||||
if (id == null || id <= 0) {
|
||||
log.warn("유효하지 않은 ID: {}", id);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(commonCodeService.getOneById(id));
|
||||
} catch (Exception e) {
|
||||
log.error("ID 기반 공통코드 조회 중 오류 발생: {}", id, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상위 코드와 하위 코드로 공통코드명 조회
|
||||
*
|
||||
* @param parentCode 상위 코드
|
||||
* @param childCode 하위 코드
|
||||
* @return 공통코드명
|
||||
*/
|
||||
public Optional<String> getCodeName(String parentCode, String childCode) {
|
||||
if (parentCode == null || parentCode.isEmpty() || childCode == null || childCode.isEmpty()) {
|
||||
log.warn("유효하지 않은 코드: parentCode={}, childCode={}", parentCode, childCode);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return commonCodeService.getCode(parentCode, childCode);
|
||||
} catch (Exception e) {
|
||||
log.error("코드명 조회 중 오류 발생: parentCode={}, childCode={}", parentCode, childCode, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상위 코드를 기반으로 하위 코드 조회
|
||||
*
|
||||
* @param parentCode 상위 코드
|
||||
* @return 해당 상위 코드의 하위 공통코드 목록
|
||||
*/
|
||||
public List<Basic> getChildCodesByParentCode(String parentCode) {
|
||||
if (parentCode == null || parentCode.isEmpty()) {
|
||||
log.warn("유효하지 않은 상위 코드: {}", parentCode);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
List<Basic> allCodes = commonCodeService.getFindAll();
|
||||
return allCodes.stream()
|
||||
.filter(code -> parentCode.equals(code.getCode()))
|
||||
.findFirst()
|
||||
.map(Basic::getChildren)
|
||||
.orElse(List.of());
|
||||
} catch (Exception e) {
|
||||
log.error("상위 코드 기반 하위 코드 조회 중 오류 발생: {}", parentCode, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 코드 사용 가능 여부 확인
|
||||
*
|
||||
* @param parentId 상위 코드 ID
|
||||
* @param code 확인할 코드값
|
||||
* @return 사용 가능 여부 (true: 사용 가능, false: 중복 또는 오류)
|
||||
*/
|
||||
public boolean isCodeAvailable(Long parentId, String code) {
|
||||
if (parentId == null || parentId <= 0 || code == null || code.isEmpty()) {
|
||||
log.warn("유효하지 않은 입력: parentId={}, code={}", parentId, code);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
ApiResponseDto.ResponseObj response = commonCodeService.getCodeCheckDuplicate(parentId, code);
|
||||
// ResponseObj의 flag 필드를 통해 SUCCESS/FAIL 확인
|
||||
return response.getCode() != null
|
||||
&& response.getCode().equals(ApiResponseDto.ApiResponseCode.OK);
|
||||
} catch (Exception e) {
|
||||
log.error("코드 중복 확인 중 오류 발생: parentId={}, code={}", parentId, code, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EnumValidator implements ConstraintValidator<EnumValid, String> {
|
||||
|
||||
private Set<String> acceptedValues;
|
||||
|
||||
@Override
|
||||
public void initialize(EnumValid constraintAnnotation) {
|
||||
acceptedValues =
|
||||
Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
|
||||
.map(Enum::name)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
return value != null && acceptedValues.contains(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.geotools.coverage.grid.GridCoverage2D;
|
||||
import org.geotools.gce.geotiff.GeoTiffReader;
|
||||
|
||||
public class FIleChecker {
|
||||
|
||||
public static boolean isValidFile(String pathStr) {
|
||||
|
||||
Path path = Paths.get(pathStr);
|
||||
|
||||
if (!Files.exists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Files.isRegularFile(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Files.isReadable(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Files.size(path) <= 0) {
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean verifyFileIntegrity(Path path, String expectedHash)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
|
||||
// 1. 알고리즘 선택 (SHA-256 권장, MD5는 보안상 비추천)
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try (InputStream fis = Files.newInputStream(path)) {
|
||||
byte[] buffer = new byte[8192]; // 8KB 버퍼
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 계산된 바이트 배열을 16진수 문자열로 변환
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest.digest()) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
String actualHash = sb.toString();
|
||||
|
||||
return actualHash.equalsIgnoreCase(expectedHash);
|
||||
}
|
||||
|
||||
public static boolean checkTfw(String filePath) {
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 파일의 모든 라인을 읽어옴
|
||||
List<Double> lines = new ArrayList<>();
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (!line.trim().isEmpty()) { // 빈 줄 제외
|
||||
lines.add(Double.parseDouble(line.trim()));
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 6줄이 맞는지 확인
|
||||
if (lines.size() < 6) {
|
||||
// System.out.println("유효하지 않은 TFW 파일입니다. (데이터 부족)");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean checkGeoTiff(String filePath) {
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GeoTiffReader reader = null;
|
||||
try {
|
||||
// 1. 파일 포맷 및 헤더 확인
|
||||
reader = new GeoTiffReader(file);
|
||||
|
||||
// 2. 실제 데이터 로딩 (여기서 파일 깨짐 여부 확인됨)
|
||||
// null을 넣으면 전체 영역을 읽지 않고 메타데이터 위주로 체크하여 빠름
|
||||
GridCoverage2D coverage = reader.read(null);
|
||||
|
||||
if (coverage == null) return false;
|
||||
|
||||
// 3. GIS 필수 정보(좌표계)가 있는지 확인
|
||||
// if (coverage.getCoordinateReferenceSystem() == null) {
|
||||
// GeoTIFF가 아니라 일반 TIFF일 수도 있음(이미지는 정상이지만, 좌표계(CRS) 정보가 없습니다.)
|
||||
// }
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("손상된 TIF 파일입니다: " + e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
// 리소스 해제 (필수)
|
||||
if (reader != null) reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean cmmndGdalInfo(String filePath) {
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
if (!file.exists()) {
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return hasDriver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.kamco.cd.kamcoback.common.utils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NameValidator {
|
||||
|
||||
private static final String HANGUL_REGEX = ".*\\p{IsHangul}.*";
|
||||
private static final Pattern HANGUL_PATTERN = Pattern.compile(HANGUL_REGEX);
|
||||
|
||||
private static final String WHITESPACE_REGEX = ".*\\s.*";
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX);
|
||||
|
||||
public static boolean containsKorean(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Matcher matcher = HANGUL_PATTERN.matcher(str);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
public static boolean containsWhitespaceRegex(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Matcher matcher = WHITESPACE_PATTERN.matcher(str);
|
||||
// find()를 사용하여 문자열 내에서 패턴이 일치하는 부분이 있는지 확인
|
||||
return matcher.find();
|
||||
}
|
||||
|
||||
public static boolean isNullOrEmpty(String str) {
|
||||
if (str == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.kamco.cd.kamcoback.common.utils.interfaces;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.EnumValidator;
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = EnumValidator.class)
|
||||
public @interface EnumValid {
|
||||
|
||||
String message() default "올바르지 않은 값입니다.";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
Class<? extends Enum<?>> enumClass();
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
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.config.api.ApiLogFunction;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
|
||||
import com.kamco.cd.kamcoback.log.dto.ErrorLogDto;
|
||||
import com.kamco.cd.kamcoback.members.exception.MemberException;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.ErrorLogEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.log.ErrorLogRepository;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
@@ -12,13 +15,16 @@ import java.nio.file.AccessDeniedException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
@@ -43,7 +49,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[EntityNotFoundException] resource :{} ", e.getMessage());
|
||||
String codeName = "NOT_FOUND_DATA";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf("UNPROCESSABLE_ENTITY"),
|
||||
@@ -64,7 +70,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[HttpMessageNotReadableException] resource :{} ", e.getMessage());
|
||||
String codeName = "BAD_REQUEST";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -85,7 +91,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[NoSuchElementException] resource :{} ", e.getMessage());
|
||||
String codeName = "NOT_FOUND_DATA";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -106,7 +112,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[handlerIllegalArgumentException] resource :{} ", e.getMessage());
|
||||
String codeName = "BAD_REQUEST";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -127,7 +133,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[DataIntegrityViolationException] resource :{} ", e.getMessage());
|
||||
String codeName = "DATA_INTEGRITY_ERROR";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf("UNPROCESSABLE_ENTITY"),
|
||||
@@ -148,7 +154,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[MethodArgumentNotValidException] resource :{} ", e.getMessage());
|
||||
String codeName = "BAD_REQUEST";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -169,7 +175,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[AccessDeniedException] resource :{} ", e.getMessage());
|
||||
String codeName = "UNAUTHORIZED";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -190,7 +196,7 @@ public class GlobalExceptionHandler {
|
||||
log.warn("[HttpServerErrorException] resource :{} ", e.getMessage());
|
||||
String codeName = "BAD_GATEWAY";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -212,7 +218,7 @@ public class GlobalExceptionHandler {
|
||||
|
||||
String codeName = "UNPROCESSABLE_ENTITY";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -226,6 +232,113 @@ public class GlobalExceptionHandler {
|
||||
errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MemberException.DuplicateMemberException.class)
|
||||
public ApiResponseDto<String> handlerDuplicateMemberException(
|
||||
MemberException.DuplicateMemberException e, HttpServletRequest request) {
|
||||
log.warn("[DuplicateMemberException] resource :{} ", e.getMessage());
|
||||
|
||||
String codeName = "";
|
||||
|
||||
switch (e.getField()) {
|
||||
case EMPLOYEE_NO -> {
|
||||
codeName = "DUPLICATE_EMPLOYEEID";
|
||||
}
|
||||
case EMAIL -> {
|
||||
codeName = "DUPLICATE_EMAIL";
|
||||
}
|
||||
default -> {
|
||||
codeName = "DUPLICATE_DATA";
|
||||
}
|
||||
}
|
||||
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf("BAD_REQUEST"),
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
return ApiResponseDto.createException(
|
||||
ApiResponseCode.getCode(codeName),
|
||||
ApiResponseCode.getMessage(codeName),
|
||||
HttpStatus.valueOf("BAD_REQUEST"),
|
||||
errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MemberException.MemberNotFoundException.class)
|
||||
public ApiResponseDto<String> handlerMemberNotFoundException(
|
||||
MemberException.MemberNotFoundException e, HttpServletRequest request) {
|
||||
log.warn("[MemberNotFoundException] resource :{} ", e.getMessage());
|
||||
|
||||
String codeName = "NOT_FOUND_USER";
|
||||
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf("BAD_REQUEST"),
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
return ApiResponseDto.createException(
|
||||
ApiResponseCode.getCode(codeName),
|
||||
ApiResponseCode.getMessage(codeName),
|
||||
HttpStatus.valueOf("BAD_REQUEST"),
|
||||
errorLog.getId());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public ApiResponseDto<String> handlerDuplicateKeyException(
|
||||
DuplicateKeyException e, HttpServletRequest request) {
|
||||
log.warn("[DuplicateKeyException] resource :{} ", e.getMessage());
|
||||
|
||||
String codeName = "DUPLICATE_DATA";
|
||||
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf("CONFLICT"),
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
return ApiResponseDto.createException(
|
||||
ApiResponseCode.getCode(codeName),
|
||||
ApiResponseCode.getMessage(codeName),
|
||||
HttpStatus.valueOf("CONFLICT"),
|
||||
errorLog.getId());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BadCredentialsException.class)
|
||||
public ResponseEntity<ApiResponseDto<String>> handleBadCredentials(
|
||||
BadCredentialsException e, HttpServletRequest request) {
|
||||
log.warn("[BadCredentialsException] resource : {} ", e.getMessage());
|
||||
|
||||
String codeName = "UNAUTHORIZED";
|
||||
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
ErrorLogDto.LogErrorLevel.WARNING,
|
||||
e.getStackTrace());
|
||||
|
||||
ApiResponseDto<String> body =
|
||||
ApiResponseDto.createException(
|
||||
ApiResponseCode.getCode(codeName),
|
||||
ApiResponseCode.getMessage(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
errorLog.getId());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED) // 🔥 여기서 401 지정
|
||||
.body(body);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ApiResponseDto<String> handlerRuntimeException(
|
||||
@@ -234,7 +347,7 @@ public class GlobalExceptionHandler {
|
||||
|
||||
String codeName = "INTERNAL_SERVER_ERROR";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -255,7 +368,7 @@ public class GlobalExceptionHandler {
|
||||
|
||||
String codeName = "INTERNAL_SERVER_ERROR";
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrerLogData(
|
||||
saveErrorLogData(
|
||||
request,
|
||||
ApiResponseCode.getCode(codeName),
|
||||
HttpStatus.valueOf(codeName),
|
||||
@@ -279,14 +392,30 @@ public class GlobalExceptionHandler {
|
||||
* @param stackTrace : 에러 내용
|
||||
* @return : insert하고 결과로 받은 Entity
|
||||
*/
|
||||
private ErrorLogEntity saveErrerLogData(
|
||||
private ErrorLogEntity saveErrorLogData(
|
||||
HttpServletRequest request,
|
||||
ApiResponseCode errorCode,
|
||||
HttpStatus httpStatus,
|
||||
ErrorLogDto.LogErrorLevel logErrorLevel,
|
||||
StackTraceElement[] stackTrace) {
|
||||
// TODO : 로그인 개발되면 이것도 연결해야 함
|
||||
Long userid = Long.valueOf(Optional.ofNullable(ApiLogFunction.getUserId(request)).orElse("1"));
|
||||
|
||||
Long userid = null;
|
||||
|
||||
/**
|
||||
* servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth 이 요청이
|
||||
* JWT 인증을 통과한 요청인가? 그리고 Spring Security Authentication 객체가 UsernamePasswordAuthenticationToken
|
||||
* 타입인가? 체크
|
||||
*/
|
||||
/**
|
||||
* auth.getPrincipal() instanceof CustomUserDetails customUserDetails principal 안에 들어있는 객체가 내가
|
||||
* 만든 CustomUserDetails 타입인가? 체크
|
||||
*/
|
||||
if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth
|
||||
&& auth.getPrincipal() instanceof CustomUserDetails customUserDetails) {
|
||||
|
||||
// audit 에는 long 타입 user_id가 들어가지만 토큰 sub은 uuid여서 user_id 가져오기
|
||||
userid = customUserDetails.getMember().getId();
|
||||
}
|
||||
|
||||
String stackTraceStr =
|
||||
Arrays.stream(stackTrace)
|
||||
@@ -307,4 +436,25 @@ public class GlobalExceptionHandler {
|
||||
|
||||
return errorLogRepository.save(errorLogEntity);
|
||||
}
|
||||
|
||||
@ExceptionHandler(CustomApiException.class)
|
||||
public ResponseEntity<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);
|
||||
|
||||
ErrorLogEntity errorLog =
|
||||
saveErrorLogData(
|
||||
request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace());
|
||||
|
||||
ApiResponseDto<String> body =
|
||||
ApiResponseDto.createException(apiCode, message, status, errorLog.getId());
|
||||
|
||||
return new ResponseEntity<>(body, status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.kamco.cd.kamcoback.config;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
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.List;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -13,6 +15,21 @@ public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI kamcoOpenAPI() {
|
||||
// 🔹 1) SecurityScheme 정의 (Bearer JWT)
|
||||
SecurityScheme bearerAuth =
|
||||
new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.name("Authorization");
|
||||
|
||||
// 🔹 2) SecurityRequirement (기본으로 BearerAuth 사용)
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth");
|
||||
|
||||
// 🔹 3) Components 에 SecurityScheme 등록
|
||||
Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth);
|
||||
|
||||
return new OpenAPI()
|
||||
.info(
|
||||
new Info()
|
||||
@@ -32,6 +49,8 @@ public class OpenApiConfig {
|
||||
new Server().url("https://kamco.dev-api.gs.dabeeo.com").description("개발 서버")
|
||||
// , new Server().url("https://api.kamco.com").description("운영 서버")
|
||||
))
|
||||
.components(new Components());
|
||||
.components(new Components())
|
||||
// 🔥 여기 한 줄이 "모든 API 기본적으로 BearerAuth 요구" 의미
|
||||
.addSecurityItem(securityRequirement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class PasswordConfig {
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// strength 기본값 10, 필요하면 조절 가능
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,15 @@ package com.kamco.cd.kamcoback.config;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
|
||||
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import java.time.Duration;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
@@ -44,7 +44,9 @@ public class RedisConfig {
|
||||
return new LettuceConnectionFactory(redisConfig);
|
||||
}
|
||||
|
||||
@Bean
|
||||
/** Redis에서 사용할 공통 ObjectMapper */
|
||||
@Bean(name = "redisObjectMapper")
|
||||
@Primary
|
||||
public ObjectMapper redisObjectMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
@@ -55,46 +57,35 @@ public class RedisConfig {
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
/** 공통 Serializer (Template + Cache 둘 다 이거 사용) */
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
public GenericJackson2JsonRedisSerializer redisSerializer(
|
||||
@Qualifier("redisObjectMapper") ObjectMapper redisObjectMapper) {
|
||||
return new GenericJackson2JsonRedisSerializer(redisObjectMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(
|
||||
RedisConnectionFactory connectionFactory,
|
||||
GenericJackson2JsonRedisSerializer redisSerializer) {
|
||||
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// Key는 String으로 직렬화
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// Value는 JSON으로 직렬화 (JavaTimeModule 포함)
|
||||
GenericJackson2JsonRedisSerializer serializer =
|
||||
new GenericJackson2JsonRedisSerializer(redisObjectMapper());
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
template.setValueSerializer(redisSerializer);
|
||||
template.setHashValueSerializer(redisSerializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
// 기본 레디스 캐시 세팅
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
|
||||
ObjectMapper cacheObjectMapper = new ObjectMapper();
|
||||
cacheObjectMapper.registerModule(new JavaTimeModule());
|
||||
cacheObjectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
cacheObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
cacheObjectMapper.findAndRegisterModules();
|
||||
|
||||
// 타입 정보 포함 - JAVA_LANG_OBJECT로 제한적으로 적용
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder()
|
||||
.allowIfSubType("com.kamco.cd.kamcoback")
|
||||
.allowIfSubType("org.springframework.data.domain")
|
||||
.allowIfSubType("java.util")
|
||||
.allowIfSubType("java.time")
|
||||
.build();
|
||||
cacheObjectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
|
||||
|
||||
GenericJackson2JsonRedisSerializer serializer =
|
||||
new GenericJackson2JsonRedisSerializer(cacheObjectMapper);
|
||||
public CacheManager cacheManager(
|
||||
RedisConnectionFactory connectionFactory,
|
||||
GenericJackson2JsonRedisSerializer redisSerializer) {
|
||||
|
||||
RedisCacheConfiguration config =
|
||||
RedisCacheConfiguration.defaultCacheConfig()
|
||||
@@ -103,7 +94,7 @@ public class RedisConfig {
|
||||
RedisSerializationContext.SerializationPair.fromSerializer(
|
||||
new StringRedisSerializer()))
|
||||
.serializeValuesWith(
|
||||
RedisSerializationContext.SerializationPair.fromSerializer(serializer));
|
||||
RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
|
||||
|
||||
return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.CustomAuthenticationProvider;
|
||||
import com.kamco.cd.kamcoback.auth.JwtAuthenticationFilter;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
private final CustomAuthenticationProvider customAuthenticationProvider;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
http.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 사용안함 커스텀 사용
|
||||
.authorizeHttpRequests(
|
||||
auth ->
|
||||
auth.requestMatchers(
|
||||
"/api/auth/signin",
|
||||
"/api/auth/refresh",
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated())
|
||||
.addFilterBefore(
|
||||
jwtAuthenticationFilter,
|
||||
UsernamePasswordAuthenticationFilter
|
||||
.class) // 요청 들어오면 먼저 JWT 토큰 검사 후 security context 에 사용자 정보 저장.
|
||||
;
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration)
|
||||
throws Exception {
|
||||
return configuration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* CORS 설정
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성
|
||||
config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header
|
||||
config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
/** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */
|
||||
source.registerCorsConfiguration("/**", config); // CORS 정책을 등록
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@SecurityScheme(
|
||||
name = "BearerAuth",
|
||||
type = SecuritySchemeType.HTTP,
|
||||
scheme = "bearer",
|
||||
bearerFormat = "JWT")
|
||||
public class SwaggerConfig {}
|
||||
@@ -10,22 +10,11 @@ import org.locationtech.jts.geom.Polygon;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry
|
||||
.addMapping("/**") // 모든 URL 허용
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("*")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
SimpleModule module = new SimpleModule();
|
||||
|
||||
@@ -2,13 +2,16 @@ package com.kamco.cd.kamcoback.config.api;
|
||||
|
||||
import com.kamco.cd.kamcoback.log.dto.EventStatus;
|
||||
import com.kamco.cd.kamcoback.log.dto.EventType;
|
||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
||||
public class ApiLogFunction {
|
||||
|
||||
// 클라이언트 IP 추출
|
||||
public static String getClientIp(HttpServletRequest request) {
|
||||
String[] headers = {
|
||||
@@ -45,8 +48,12 @@ public class ApiLogFunction {
|
||||
String uri = request.getRequestURI().toLowerCase();
|
||||
|
||||
// URL 기반 DOWNLOAD/PRINT 분류
|
||||
if (uri.contains("/download") || uri.contains("/export")) return EventType.DOWNLOAD;
|
||||
if (uri.contains("/print")) return EventType.PRINT;
|
||||
if (uri.contains("/download") || uri.contains("/export")) {
|
||||
return EventType.DOWNLOAD;
|
||||
}
|
||||
if (uri.contains("/print")) {
|
||||
return EventType.PRINT;
|
||||
}
|
||||
|
||||
// 일반 CRUD
|
||||
return switch (method) {
|
||||
@@ -97,7 +104,9 @@ public class ApiLogFunction {
|
||||
// JSON Body 읽기
|
||||
public static String getBodyData(ContentCachingRequestWrapper request) {
|
||||
byte[] buf = request.getContentAsByteArray();
|
||||
if (buf.length == 0) return null;
|
||||
if (buf.length == 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new String(buf, request.getCharacterEncoding());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
@@ -109,4 +118,15 @@ public class ApiLogFunction {
|
||||
public static EventStatus isSuccessFail(ApiResponseDto<?> apiResponse) {
|
||||
return apiResponse.getHttpStatus().is2xxSuccessful() ? EventStatus.SUCCESS : EventStatus.FAILED;
|
||||
}
|
||||
|
||||
public static String getUriMenuInfo(List<MenuDto.Basic> menuList, String uri) {
|
||||
|
||||
MenuDto.Basic m =
|
||||
menuList.stream()
|
||||
.filter(menu -> menu.getMenuApiUrl() != null && uri.contains(menu.getMenuApiUrl()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
return m != null ? m.getMenuUid() : "SYSTEM";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
package com.kamco.cd.kamcoback.config.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
|
||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||
import com.kamco.cd.kamcoback.menu.service.MenuService;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
@@ -23,9 +30,13 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
private final AuditLogRepository auditLogRepository;
|
||||
private final MenuService menuService;
|
||||
|
||||
public ApiResponseAdvice(AuditLogRepository auditLogRepository) {
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
|
||||
public ApiResponseAdvice(AuditLogRepository auditLogRepository, MenuService menuService) {
|
||||
this.auditLogRepository = auditLogRepository;
|
||||
this.menuService = menuService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,26 +63,79 @@ public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
response.setStatusCode(apiResponse.getHttpStatus());
|
||||
|
||||
String ip = ApiLogFunction.getClientIp(servletRequest);
|
||||
// TODO : userid 가 계정명인지, uid 인지 확인 후 로직 수정 필요함
|
||||
Long userid =
|
||||
Long.valueOf(Optional.ofNullable(ApiLogFunction.getUserId(servletRequest)).orElse("1"));
|
||||
|
||||
// TODO: menuUid 를 동적으로 가져오게끔 해야함
|
||||
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); // 로그 저장전에 중요정보 마스킹
|
||||
|
||||
List<?> list = menuService.getFindAll();
|
||||
|
||||
List<MenuDto.Basic> result =
|
||||
list.stream()
|
||||
.map(
|
||||
item -> {
|
||||
if (item instanceof LinkedHashMap<?, ?> map) {
|
||||
return objectMapper.convertValue(map, MenuDto.Basic.class);
|
||||
} else if (item instanceof MenuDto.Basic dto) {
|
||||
return dto;
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported cache type: " + item.getClass());
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
|
||||
AuditLogEntity log =
|
||||
new AuditLogEntity(
|
||||
userid,
|
||||
ApiLogFunction.getEventType(servletRequest),
|
||||
ApiLogFunction.isSuccessFail(apiResponse),
|
||||
"MU_01_01",
|
||||
ApiLogFunction.getUriMenuInfo(result, servletRequest.getRequestURI()),
|
||||
ip,
|
||||
servletRequest.getRequestURI(),
|
||||
ApiLogFunction.getRequestBody(servletRequest, contentWrapper),
|
||||
requestBody,
|
||||
apiResponse.getErrorLogUid());
|
||||
|
||||
// tb_audit_log 테이블 저장
|
||||
auditLogRepository.save(log);
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 마스킹
|
||||
*
|
||||
* @param json
|
||||
* @return
|
||||
*/
|
||||
private String maskSensitiveFields(String json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// password 마스킹
|
||||
json = json.replaceAll("\"password\"\\s*:\\s*\"[^\"]*\"", "\"password\":\"****\"");
|
||||
|
||||
// 토큰 마스킹
|
||||
json = json.replaceAll("\"accessToken\"\\s*:\\s*\"[^\"]*\"", "\"accessToken\":\"****\"");
|
||||
json = json.replaceAll("\"refreshToken\"\\s*:\\s*\"[^\"]*\"", "\"refreshToken\":\"****\"");
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,14 @@ public class ApiResponseDto<T> {
|
||||
return new ApiResponseDto<>(data, HttpStatus.OK);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<ResponseObj> okObject(ResponseObj data) {
|
||||
if (data.getCode().equals(ApiResponseCode.OK)) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
|
||||
} else {
|
||||
return new ApiResponseDto<>(data.getCode(), data.getMessage(), HttpStatus.CONFLICT);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<T> deleteOk(T data) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
@@ -106,6 +114,19 @@ public class ApiResponseDto<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Error가 아닌 Business상 성공이거나 실패인 경우, 메세지 함께 전달하기 위한 object */
|
||||
@Getter
|
||||
public static class ResponseObj {
|
||||
|
||||
private final ApiResponseCode code;
|
||||
private final String message;
|
||||
|
||||
public ResponseObj(ApiResponseCode code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum ApiResponseCode implements EnumType {
|
||||
@@ -134,7 +155,7 @@ public class ApiResponseDto<T> {
|
||||
FAIL_VERIFICATION("인증에 실패하였습니다."),
|
||||
INVALID_EMAIL("잘못된 형식의 이메일입니다."),
|
||||
REQUIRED_EMAIL("이메일은 필수 항목입니다."),
|
||||
WRONG_PASSWORD("잘못된 패스워드입니다.."),
|
||||
WRONG_PASSWORD("잘못된 패스워드입니다."),
|
||||
DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
|
||||
DUPLICATE_DATA("이미 등록되어 있습니다."),
|
||||
DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."),
|
||||
|
||||
@@ -22,8 +22,8 @@ public class GeoJsonMonitorConfig {
|
||||
/** 처리 실패 파일을 이동할 폴더 경로 */
|
||||
private String errorDirectory = "~/geojson/error";
|
||||
|
||||
/** 파일 모니터링 스케줄 (cron 표현식) 기본값: 매 30초마다 실행 */
|
||||
private String cronExpression = "0/30 * * * * *";
|
||||
/** 파일 모니터링 스케줄 (cron 표현식) 기본값: 매 1분마다 실행 */
|
||||
private String cronExpression = "0 * * * * *";
|
||||
|
||||
/** 지원하는 압축파일 확장자 */
|
||||
private String[] supportedExtensions = {"zip", "tar", "tar.gz", "tgz"};
|
||||
|
||||
@@ -27,32 +27,30 @@ public class GeoJsonDataService {
|
||||
private final GeoJsonReader geoJsonReader = new GeoJsonReader();
|
||||
|
||||
/** GeoJSON 파일들을 데이터베이스에 저장 */
|
||||
@Transactional
|
||||
public List<Long> processGeoJsonFiles(
|
||||
Map<String, String> geoJsonContents, String archiveFileName) {
|
||||
List<Long> savedIds = new ArrayList<>();
|
||||
|
||||
log.info("GeoJSON 파일 처리 시작: {} ({}개 파일)", archiveFileName, geoJsonContents.size());
|
||||
|
||||
// 개별 파일별로 독립적인 트랜잭션으로 처리
|
||||
for (Map.Entry<String, String> entry : geoJsonContents.entrySet()) {
|
||||
String fileName = entry.getKey();
|
||||
String geoJsonContent = entry.getValue();
|
||||
|
||||
try {
|
||||
Long savedId = processGeoJsonFile(fileName, geoJsonContent, archiveFileName);
|
||||
Long savedId = processGeoJsonFileWithTransaction(fileName, geoJsonContent, archiveFileName);
|
||||
if (savedId != null) {
|
||||
savedIds.add(savedId);
|
||||
log.debug("GeoJSON 파일 저장 성공: {} (ID: {})", fileName, savedId);
|
||||
|
||||
// 학습 모델 결과 파일인지 확인하여 geometry 데이터 처리
|
||||
if (isLearningModelResult(fileName, geoJsonContent)) {
|
||||
processLearningModelGeometry(savedId, geoJsonContent, fileName);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("GeoJSON 파일 처리 실패: {}", fileName, e);
|
||||
// 개별 파일 처리 실패는 전체 처리를 중단시키지 않음
|
||||
}
|
||||
|
||||
// 메모리 정리
|
||||
System.gc();
|
||||
}
|
||||
|
||||
log.info(
|
||||
@@ -64,6 +62,22 @@ public class GeoJsonDataService {
|
||||
return savedIds;
|
||||
}
|
||||
|
||||
/** 개별 파일을 별도 트랜잭션으로 처리 */
|
||||
@Transactional
|
||||
public Long processGeoJsonFileWithTransaction(
|
||||
String fileName, String geoJsonContent, String archiveFileName) {
|
||||
try {
|
||||
Long savedId = processGeoJsonFile(fileName, geoJsonContent, archiveFileName);
|
||||
if (savedId != null && isLearningModelResult(fileName, geoJsonContent)) {
|
||||
processLearningModelGeometryOptimized(savedId, geoJsonContent, fileName);
|
||||
}
|
||||
return savedId;
|
||||
} catch (Exception e) {
|
||||
log.error("파일 처리 중 트랜잭션 에러: {}", fileName, e);
|
||||
throw e; // 트랜잭션 롤백을 위해 재throw
|
||||
}
|
||||
}
|
||||
|
||||
/** 개별 GeoJSON 파일을 MapSheetLearnDataEntity로 변환하여 저장 */
|
||||
private Long processGeoJsonFile(String fileName, String geoJsonContent, String archiveFileName) {
|
||||
try {
|
||||
@@ -285,9 +299,9 @@ public class GeoJsonDataService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 학습 모델 결과의 geometry 데이터 처리 */
|
||||
@Transactional
|
||||
public void processLearningModelGeometry(Long dataUid, String geoJsonContent, String fileName) {
|
||||
/** 학습 모델 결과의 geometry 데이터 처리 - 최적화된 배치 처리 */
|
||||
public void processLearningModelGeometryOptimized(
|
||||
Long dataUid, String geoJsonContent, String fileName) {
|
||||
try {
|
||||
log.info("학습 모델 geometry 데이터 처리 시작: {} (dataUid: {})", fileName, dataUid);
|
||||
|
||||
@@ -318,30 +332,69 @@ public class GeoJsonDataService {
|
||||
return;
|
||||
}
|
||||
|
||||
List<MapSheetLearnDataGeomEntity> geomEntities = new ArrayList<>();
|
||||
// 소규모 배치로 나누어 처리
|
||||
int totalFeatures = features.size();
|
||||
int batchSize = 10; // 작은 배치 크기
|
||||
int processedCount = 0;
|
||||
|
||||
for (JsonNode feature : features) {
|
||||
try {
|
||||
MapSheetLearnDataGeomEntity geomEntity =
|
||||
createGeometryEntity(feature, dataUid, beforeYear, afterYear, mapSheetNum);
|
||||
if (geomEntity != null) {
|
||||
geomEntities.add(geomEntity);
|
||||
processedCount++;
|
||||
log.info("총 {}개 feature를 {}개씩 배치로 나누어 처리", totalFeatures, batchSize);
|
||||
|
||||
for (int i = 0; i < totalFeatures; i += batchSize) {
|
||||
int endIndex = Math.min(i + batchSize, totalFeatures);
|
||||
List<MapSheetLearnDataGeomEntity> batch = new ArrayList<>();
|
||||
|
||||
for (int j = i; j < endIndex; j++) {
|
||||
try {
|
||||
JsonNode feature = features.get(j);
|
||||
MapSheetLearnDataGeomEntity geomEntity =
|
||||
createGeometryEntity(feature, dataUid, beforeYear, afterYear, mapSheetNum);
|
||||
if (geomEntity != null) {
|
||||
batch.add(geomEntity);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Feature geometry 처리 실패 (feature {}): {}", j + 1, e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Feature geometry 처리 실패 (feature {}): {}", processedCount, e.getMessage());
|
||||
}
|
||||
|
||||
// 배치별 저장
|
||||
if (!batch.isEmpty()) {
|
||||
saveBatchGeometry(batch);
|
||||
processedCount += batch.size();
|
||||
log.debug("배치 {}-{} 처리 완료 ({}개)", i + 1, endIndex, batch.size());
|
||||
}
|
||||
|
||||
// 메모리 정리
|
||||
batch.clear();
|
||||
}
|
||||
|
||||
// 배치 저장
|
||||
if (!geomEntities.isEmpty()) {
|
||||
mapSheetLearnDataGeomRepository.saveAll(geomEntities);
|
||||
log.info("학습 모델 geometry 데이터 저장 완료: {} ({}개 feature)", fileName, geomEntities.size());
|
||||
}
|
||||
log.info("학습 모델 geometry 데이터 저장 완료: {} ({}개 feature)", fileName, processedCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("학습 모델 geometry 데이터 처리 실패: {}", fileName, e);
|
||||
throw new RuntimeException("Geometry 처리 실패: " + fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 배치별 geometry 저장 - 별도 트랜잭션 */
|
||||
@Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
|
||||
public void saveBatchGeometry(List<MapSheetLearnDataGeomEntity> batch) {
|
||||
try {
|
||||
if (batch == null || batch.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
mapSheetLearnDataGeomRepository.saveAll(batch);
|
||||
} catch (Exception e) {
|
||||
log.error("배치 geometry 저장 실패: {}", e.getMessage());
|
||||
// 개별 저장 시도
|
||||
for (MapSheetLearnDataGeomEntity entity : batch) {
|
||||
try {
|
||||
if (entity != null) {
|
||||
mapSheetLearnDataGeomRepository.save(entity);
|
||||
}
|
||||
} catch (Exception individualError) {
|
||||
log.warn("개별 geometry 저장 실패: {}", individualError.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,12 +59,9 @@ public class InferenceResultApiController {
|
||||
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
|
||||
int page,
|
||||
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
|
||||
int size,
|
||||
@Parameter(description = "정렬 조건 (형식: 필드명,방향)", example = "name,asc")
|
||||
@RequestParam(required = false)
|
||||
String sort) {
|
||||
int size) {
|
||||
InferenceResultDto.SearchReq searchReq =
|
||||
new InferenceResultDto.SearchReq(statCode, title, page, size, sort);
|
||||
new InferenceResultDto.SearchReq(statCode, title, page, size);
|
||||
Page<InferenceResultDto.AnalResList> analResList =
|
||||
inferenceResultService.getInferenceResultList(searchReq);
|
||||
return ApiResponseDto.ok(analResList);
|
||||
|
||||
@@ -345,16 +345,8 @@ public class InferenceResultDto {
|
||||
// 페이징 파라미터
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.kamco.cd.kamcoback.mapsheet;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||
import com.kamco.cd.kamcoback.code.service.CommonCodeService;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.service.MapSheetMngService;
|
||||
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 jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "영상 관리", description = "영상 관리 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping({"/api/mapsheet"})
|
||||
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 = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/mng-list")
|
||||
public ApiResponseDto<Page<MapSheetMngDto.MngDto>> findMapSheetMngList(
|
||||
@RequestBody @Valid MapSheetMngDto.searchReq searchReq) {
|
||||
return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngList(searchReq));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hstUidList
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary = "오류데이터 팝업 > 업로드 처리", description = "오류데이터 팝업 > 업로드 처리")
|
||||
@PutMapping("/upload-process")
|
||||
public ApiResponseDto<MapSheetMngDto.DmlReturn> uploadProcess(
|
||||
@RequestBody @Valid List<Long> hstUidList) {
|
||||
return ApiResponseDto.ok(mapSheetMngService.uploadProcess(hstUidList));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hstUidList
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary = "오류데이터 팝업 > 추론 제외", description = "오류데이터 팝업 > 추론 제외")
|
||||
@PutMapping("/except-inference")
|
||||
public ApiResponseDto<MapSheetMngDto.DmlReturn> updateExceptUseInference(
|
||||
@RequestBody @Valid List<Long> hstUidList) {
|
||||
return ApiResponseDto.ok(mapSheetMngService.updateExceptUseInference(hstUidList));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.kamco.cd.kamcoback.mapsheet;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||
import com.kamco.cd.kamcoback.code.service.CommonCodeService;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDepthDto;
|
||||
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.mapsheet.service.MapSheetMngFileCheckerService;
|
||||
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 jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.RestController;
|
||||
|
||||
@Tag(name = "영상 관리", description = "영상 관리 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping({"/api/mapsheet"})
|
||||
public class MapSheetMngFileCheckerApiController {
|
||||
|
||||
private final CommonCodeService commonCodeService;
|
||||
private final MapSheetMngFileCheckerService mapSheetMngFileCheckerService;
|
||||
|
||||
@Operation(summary = "폴더 조회", description = "폴더 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/folder-list")
|
||||
public ApiResponseDto<FoldersDto> getDir(@RequestBody SrchFoldersDto srchDto) {
|
||||
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.getFolderAll(srchDto));
|
||||
}
|
||||
|
||||
@Operation(summary = "지정폴더내 파일목록 조회", description = "지정폴더내 파일목록 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/file-list")
|
||||
public ApiResponseDto<FilesDto> getFiles(@RequestBody SrchFilesDto srchDto) {
|
||||
|
||||
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.getFilesAll(srchDto));
|
||||
}
|
||||
|
||||
@Operation(summary = "지정폴더(하위폴더포함) 파일목록 조회", description = "지정폴더(하위폴더포함) 파일목록 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/file-all-list")
|
||||
public ApiResponseDto<FilesDto> getAllFiles(@RequestBody SrchFilesDepthDto srchDto) {
|
||||
|
||||
return ApiResponseDto.createOK(mapSheetMngFileCheckerService.getFilesDepthAll(srchDto));
|
||||
}
|
||||
|
||||
@Operation(summary = "영상데이터관리 > 영상파일 동기화", description = "영상파일 동기화")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "동기화 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "동기화 할수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/sync-process")
|
||||
public ApiResponseDto<ImageryDto.SyncReturn> syncProcess(
|
||||
@RequestBody @Valid ImageryDto.searchReq searchReq) {
|
||||
return ApiResponseDto.ok(mapSheetMngFileCheckerService.syncProcess(searchReq));
|
||||
}
|
||||
}
|
||||
155
src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/FileDto.java
Normal file
155
src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/FileDto.java
Normal file
@@ -0,0 +1,155 @@
|
||||
package com.kamco.cd.kamcoback.mapsheet.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
public class FileDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SrchFoldersDto {
|
||||
@Schema(description = "디렉토리경로", example = "D:\\kamco")
|
||||
@NotNull
|
||||
private String dirPath;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SrchFilesDto {
|
||||
@Schema(description = "디렉토리경로", example = "D:\\kamco\\2022\\캠코_2021_2022_34602060_D1")
|
||||
@NotNull
|
||||
private String dirPath;
|
||||
|
||||
@Schema(description = "전체(*), cpg,dbf,geojson등", example = "*")
|
||||
@NotNull
|
||||
private String extension;
|
||||
|
||||
@Schema(description = "파일명(name), 최종수정일(date)", example = "name")
|
||||
@NotNull
|
||||
private String sortType;
|
||||
|
||||
@Schema(description = "파일시작위치", example = "1")
|
||||
@NotNull
|
||||
private Integer startPos;
|
||||
|
||||
@Schema(description = "파일종료위치", example = "100")
|
||||
@NotNull
|
||||
private Integer endPos;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SrchFilesDepthDto extends SrchFilesDto {
|
||||
@Schema(description = "최대폴더Depth", example = "5")
|
||||
@NotNull
|
||||
private Integer maxDepth;
|
||||
}
|
||||
|
||||
@Schema(name = "FolderDto", description = "폴더 정보")
|
||||
@Getter
|
||||
public static class FolderDto {
|
||||
private final String folderNm;
|
||||
private final String parentFolderNm;
|
||||
private final String parentPath;
|
||||
private final String fullPath;
|
||||
private final int depth;
|
||||
private final long childCnt;
|
||||
private final String lastModified;
|
||||
private final Boolean isValid;
|
||||
|
||||
public FolderDto(
|
||||
String folderNm,
|
||||
String parentFolderNm,
|
||||
String parentPath,
|
||||
String fullPath,
|
||||
int depth,
|
||||
long childCnt,
|
||||
String lastModified,
|
||||
Boolean isValid) {
|
||||
this.folderNm = folderNm;
|
||||
this.parentFolderNm = parentFolderNm;
|
||||
this.parentPath = parentPath;
|
||||
this.fullPath = fullPath;
|
||||
this.depth = depth;
|
||||
this.childCnt = childCnt;
|
||||
this.lastModified = lastModified;
|
||||
this.isValid = isValid;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "FoldersDto", description = "폴더목록 정보")
|
||||
@Getter
|
||||
public static class FoldersDto {
|
||||
private final String dirPath;
|
||||
private final int folderTotCnt;
|
||||
private final int folderErrTotCnt;
|
||||
private final List<FolderDto> folders;
|
||||
|
||||
public FoldersDto(
|
||||
String dirPath, int folderTotCnt, int folderErrTotCnt, List<FolderDto> folders) {
|
||||
|
||||
this.dirPath = dirPath;
|
||||
this.folderTotCnt = folderTotCnt;
|
||||
this.folderErrTotCnt = folderErrTotCnt;
|
||||
this.folders = folders;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "File Basic", description = "파일 기본 정보")
|
||||
@Getter
|
||||
public static class Basic {
|
||||
|
||||
private final String fileNm;
|
||||
private final String parentFolderNm;
|
||||
private final String parentPath;
|
||||
private final String fullPath;
|
||||
private final String extension;
|
||||
private final long fileSize;
|
||||
private final String lastModified;
|
||||
|
||||
public Basic(
|
||||
String fileNm,
|
||||
String parentFolderNm,
|
||||
String parentPath,
|
||||
String fullPath,
|
||||
String extension,
|
||||
long fileSize,
|
||||
String lastModified) {
|
||||
this.fileNm = fileNm;
|
||||
this.parentFolderNm = parentFolderNm;
|
||||
this.parentPath = parentPath;
|
||||
this.fullPath = fullPath;
|
||||
this.extension = extension;
|
||||
this.fileSize = fileSize;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "FilesDto", description = "파일 목록 정보")
|
||||
@Getter
|
||||
public static class FilesDto {
|
||||
private final String dirPath;
|
||||
private final int fileTotCnt;
|
||||
private final long fileTotSize;
|
||||
private final List<Basic> files;
|
||||
|
||||
public FilesDto(String dirPath, int fileTotCnt, long fileTotSize, List<Basic> files) {
|
||||
|
||||
this.dirPath = dirPath;
|
||||
this.fileTotCnt = fileTotCnt;
|
||||
this.fileTotSize = fileTotSize;
|
||||
this.files = files;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.kamco.cd.kamcoback.mapsheet.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
public class ImageryDto {
|
||||
|
||||
@Schema(name = "searchReq", description = "영상관리 Sync대상 검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class searchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
|
||||
@NotNull
|
||||
private int page = 0;
|
||||
|
||||
@Schema(description = "페이지 크기", example = "20000")
|
||||
@NotNull
|
||||
private int size = 20000;
|
||||
|
||||
@Schema(description = "정렬", example = "id desc")
|
||||
private String sort;
|
||||
|
||||
@Schema(description = "검색어(36809 또는 36809010)", example = "36809010")
|
||||
private String searchValue;
|
||||
|
||||
@Schema(description = "년도", example = "2025")
|
||||
@NotNull
|
||||
private Integer mngYyyy;
|
||||
|
||||
public Pageable toPageable() {
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "ImageryDto", description = "영상관리파일 검색 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class BasicDto {
|
||||
private Long id;
|
||||
private UUID uuid;
|
||||
private Integer year;
|
||||
private String scene50k;
|
||||
private String scene5k;
|
||||
private Integer sceneId50k;
|
||||
private Integer sceneId5k;
|
||||
private ZonedDateTime createdDate;
|
||||
private String middlePath;
|
||||
private String cogMiddlePath;
|
||||
private String filename;
|
||||
private String cogFilename;
|
||||
}
|
||||
|
||||
@Schema(name = "ImagerySyncDto", description = "영상싱크파일 검색 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SyncDto {
|
||||
private Long id;
|
||||
private Integer year;
|
||||
private String scene50k;
|
||||
private String scene5k;
|
||||
private String middlePath;
|
||||
private String cogMiddlePath;
|
||||
private String filename;
|
||||
private String cogFilename;
|
||||
private Long hstUid;
|
||||
}
|
||||
|
||||
@Schema(name = "SyncReturn", description = "영상파일싱크 수행 후 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SyncReturn {
|
||||
private String flag;
|
||||
private int syncCnt;
|
||||
private int tfwErrCnt;
|
||||
private int tifErrCnt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.kamco.cd.kamcoback.mapsheet.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
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 MapSheetMngDto {
|
||||
|
||||
@Schema(name = "searchReq", description = "영상관리 오류데이터 검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class searchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
|
||||
private int page = 0;
|
||||
|
||||
@Schema(description = "페이지 크기", example = "20")
|
||||
private int size = 20;
|
||||
|
||||
@Schema(description = "정렬", example = "id desc")
|
||||
private String sort;
|
||||
|
||||
@Schema(description = "검색어", example = "부산3959")
|
||||
private String searchValue;
|
||||
|
||||
@Schema(description = "년도", example = "2025")
|
||||
private Integer mngYyyy;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "ErrorDataDto", description = "영상관리 오류데이터 검색 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ErrorDataDto {
|
||||
private Long hstUid;
|
||||
private Integer rowNum;
|
||||
private String map50kName;
|
||||
private String map5kName;
|
||||
private Integer mapCodeSrc;
|
||||
private String createdDttm;
|
||||
private DataState dataState;
|
||||
}
|
||||
|
||||
@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 mngStateDttm;
|
||||
private String syncStateDttm;
|
||||
// private int sheetCnt;
|
||||
// private int exceptCnt;
|
||||
private String mngPath;
|
||||
private String createdDttm;
|
||||
private Long createdUid;
|
||||
private String updatedDttm;
|
||||
private Long updatedUid;
|
||||
}
|
||||
|
||||
@Schema(name = "DmlReturn", description = "영상관리 DML 수행 후 리턴")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class DmlReturn {
|
||||
private String flag;
|
||||
private String message;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MngState implements EnumType {
|
||||
COMPLETE("업로드 완료"),
|
||||
IN_PROGRESS("진행중"),
|
||||
FAILED("오류");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SyncState implements EnumType {
|
||||
COMPLETE("동기화 완료"),
|
||||
IN_PROGRESS("진행중"),
|
||||
FAIL("오류"),
|
||||
NONE("미진행");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DataState implements EnumType {
|
||||
SUCCESS("정상"),
|
||||
FAIL("데이터 없음");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
package com.kamco.cd.kamcoback.mapsheet.service;
|
||||
|
||||
import static java.lang.String.CASE_INSENSITIVE_ORDER;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.NameValidator;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FolderDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDepthDto;
|
||||
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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class MapSheetMngFileCheckerService {
|
||||
|
||||
private final MapSheetMngFileCheckerCoreService mapSheetMngFileCheckerCoreService;
|
||||
|
||||
public FoldersDto getFolderAll(SrchFoldersDto srchDto) {
|
||||
|
||||
Path startPath = Paths.get(srchDto.getDirPath());
|
||||
String dirPath = srchDto.getDirPath();
|
||||
|
||||
int maxDepth = 1;
|
||||
|
||||
int folderTotCnt = 0;
|
||||
int folderErrTotCnt = 0;
|
||||
|
||||
List<FolderDto> folderDtoList = List.of();
|
||||
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
|
||||
|
||||
folderDtoList =
|
||||
stream
|
||||
// 1. 디렉토리만 필터링
|
||||
.filter(Files::isDirectory)
|
||||
.filter(p -> !p.toString().equals(dirPath))
|
||||
.map(
|
||||
path -> {
|
||||
int depth = path.getNameCount();
|
||||
|
||||
String folderNm = path.getFileName().toString();
|
||||
String parentFolderNm = path.getParent().getFileName().toString();
|
||||
String parentPath = path.getParent().toString();
|
||||
String fullPath = path.toAbsolutePath().toString();
|
||||
|
||||
boolean isValid =
|
||||
!NameValidator.containsKorean(folderNm)
|
||||
&& !NameValidator.containsWhitespaceRegex(folderNm);
|
||||
|
||||
File directory = new File(fullPath);
|
||||
File[] childFolders = directory.listFiles(File::isDirectory);
|
||||
|
||||
long childCnt = 0;
|
||||
if (childFolders != null) {
|
||||
childCnt = childFolders.length;
|
||||
}
|
||||
|
||||
FileTime time = null;
|
||||
String lastModified = "";
|
||||
try {
|
||||
time = Files.getLastModifiedTime(path);
|
||||
lastModified = dttmFormat.format(new Date(time.toMillis()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return new FolderDto(
|
||||
folderNm,
|
||||
parentFolderNm,
|
||||
parentPath,
|
||||
fullPath,
|
||||
depth,
|
||||
childCnt,
|
||||
lastModified,
|
||||
isValid);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
folderDtoList.sort(
|
||||
Comparator.comparing(
|
||||
FolderDto::getFolderNm, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이
|
||||
)
|
||||
.reversed());
|
||||
|
||||
folderTotCnt = folderDtoList.size();
|
||||
folderErrTotCnt =
|
||||
(int)
|
||||
folderDtoList.stream()
|
||||
.filter(dto -> dto.getIsValid().toString().equals("false"))
|
||||
.count();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return new FoldersDto(dirPath, folderTotCnt, folderErrTotCnt, folderDtoList);
|
||||
}
|
||||
|
||||
public FilesDto getFilesAll(SrchFilesDto srchDto) {
|
||||
|
||||
String dirPath = srchDto.getDirPath();
|
||||
String extension = srchDto.getExtension();
|
||||
String sortType = srchDto.getSortType();
|
||||
int startPos = srchDto.getStartPos();
|
||||
int endPos = srchDto.getEndPos();
|
||||
File dir = new File(dirPath);
|
||||
File[] fileList = dir.listFiles();
|
||||
|
||||
List<FileDto.Basic> files = new ArrayList<>();
|
||||
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
int fileListPos = 0;
|
||||
int fileTotCnt = 0;
|
||||
long fileTotSize = 0;
|
||||
|
||||
if (fileList != null) {
|
||||
if (sortType.equals("name")) {
|
||||
Arrays.sort(fileList);
|
||||
} else if (sortType.equals("date")) {
|
||||
Arrays.sort(fileList, Comparator.comparingLong(File::lastModified));
|
||||
}
|
||||
|
||||
for (File file : fileList) {
|
||||
if (file.isFile()) { // 파일인 경우만
|
||||
if (extension.equals("*") || file.getName().endsWith("." + extension)) {
|
||||
|
||||
fileListPos = fileListPos + 1;
|
||||
|
||||
if (startPos <= fileListPos && endPos >= fileListPos) {
|
||||
|
||||
// 생성자를 통해 객체를 만들고 리스트에 추가
|
||||
String fileName = file.getName();
|
||||
String parentPath = file.getParent();
|
||||
String fullPath = file.getAbsolutePath();
|
||||
String ext = FilenameUtils.getExtension(fileName);
|
||||
|
||||
Path path = Paths.get(parentPath);
|
||||
String parentFolderNm = path.getFileName().toString();
|
||||
|
||||
long fileSize = file.length();
|
||||
String lastModified = dttmFormat.format(new Date(file.lastModified()));
|
||||
|
||||
files.add(
|
||||
new FileDto.Basic(
|
||||
fileName, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified));
|
||||
|
||||
fileTotCnt = fileTotCnt + 1;
|
||||
fileTotSize = fileTotSize + fileSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new FilesDto(dirPath, fileTotCnt, fileTotSize, files);
|
||||
}
|
||||
|
||||
public FilesDto getFilesDepthAll(SrchFilesDepthDto srchDto) {
|
||||
|
||||
Path startPath = Paths.get(srchDto.getDirPath());
|
||||
int maxDepth = srchDto.getMaxDepth();
|
||||
String dirPath = srchDto.getDirPath();
|
||||
String extension = srchDto.getExtension();
|
||||
String sortType = srchDto.getSortType();
|
||||
|
||||
int startPos = srchDto.getStartPos();
|
||||
int endPos = srchDto.getEndPos();
|
||||
int limit = endPos - startPos + 1;
|
||||
|
||||
Set<String> targetExtensions = createExtensionSet(extension);
|
||||
|
||||
List<FileDto.Basic> fileDtoList = new ArrayList<>();
|
||||
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
int fileTotCnt = 0;
|
||||
long fileTotSize = 0;
|
||||
|
||||
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
|
||||
|
||||
fileDtoList =
|
||||
stream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(
|
||||
p ->
|
||||
extension == null
|
||||
|| extension.equals("")
|
||||
|| extension.equals("*")
|
||||
|| targetExtensions.contains(extractExtension(p)))
|
||||
.sorted(getFileComparator(sortType))
|
||||
.skip(startPos)
|
||||
.limit(limit)
|
||||
.map(
|
||||
path -> {
|
||||
int depth = path.getNameCount();
|
||||
|
||||
String fileNm = path.getFileName().toString();
|
||||
String ext = FilenameUtils.getExtension(fileNm);
|
||||
String parentFolderNm = path.getParent().getFileName().toString();
|
||||
String parentPath = path.getParent().toString();
|
||||
String fullPath = path.toAbsolutePath().toString();
|
||||
|
||||
File file = new File(fullPath);
|
||||
long fileSize = file.length();
|
||||
String lastModified = dttmFormat.format(new Date(file.lastModified()));
|
||||
|
||||
return new FileDto.Basic(
|
||||
fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
fileTotCnt = fileDtoList.size();
|
||||
fileTotSize = fileDtoList.stream().mapToLong(FileDto.Basic::getFileSize).sum();
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("파일 I/O 오류 발생: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new FilesDto(dirPath, fileTotCnt, fileTotSize, fileDtoList);
|
||||
}
|
||||
|
||||
public Set<String> createExtensionSet(String extensionString) {
|
||||
if (extensionString == null || extensionString.isBlank()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
// "java, class" -> ["java", " class"] -> [".java", ".class"]
|
||||
return Arrays.stream(extensionString.split(","))
|
||||
.map(ext -> ext.trim())
|
||||
.filter(ext -> !ext.isEmpty())
|
||||
.map(ext -> "." + ext.toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public String extractExtension(Path path) {
|
||||
String filename = path.getFileName().toString();
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
|
||||
// 확장자가 없거나 파일명이 .으로 끝나는 경우
|
||||
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
|
||||
return ""; // 빈 문자열 반환
|
||||
}
|
||||
|
||||
// 확장자 추출 및 소문자 변환
|
||||
return filename.substring(lastDotIndex).toLowerCase();
|
||||
}
|
||||
|
||||
public Comparator<Path> getFileComparator(String sortType) {
|
||||
|
||||
// 파일 이름 비교 기본 Comparator (대소문자 무시)
|
||||
Comparator<Path> nameComparator =
|
||||
Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER);
|
||||
|
||||
Comparator<Path> dateComparator =
|
||||
Comparator.comparing(
|
||||
path -> {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path);
|
||||
} catch (IOException e) {
|
||||
return FileTime.fromMillis(0);
|
||||
}
|
||||
});
|
||||
|
||||
if ("name desc".equalsIgnoreCase(sortType)) {
|
||||
return nameComparator.reversed();
|
||||
} else if ("date".equalsIgnoreCase(sortType)) {
|
||||
return dateComparator;
|
||||
} else if ("date desc".equalsIgnoreCase(sortType)) {
|
||||
return dateComparator.reversed();
|
||||
} else {
|
||||
return nameComparator;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageryDto.SyncReturn syncProcess(ImageryDto.searchReq searchReq) {
|
||||
return mapSheetMngFileCheckerCoreService.syncProcess(searchReq);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package com.kamco.cd.kamcoback.mapsheet.service;
|
||||
|
||||
import static java.lang.String.CASE_INSENSITIVE_ORDER;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDepthDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDto;
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
|
||||
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService;
|
||||
import jakarta.validation.Valid;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class MapSheetMngService {
|
||||
|
||||
private final MapSheetMngCoreService mapSheetMngCoreService;
|
||||
|
||||
public FilesDto getFilesAll(SrchFilesDto srchDto) {
|
||||
|
||||
String dirPath = srchDto.getDirPath();
|
||||
String extension = srchDto.getExtension();
|
||||
String sortType = srchDto.getSortType();
|
||||
int startPos = srchDto.getStartPos();
|
||||
int endPos = srchDto.getEndPos();
|
||||
File dir = new File(dirPath);
|
||||
File[] fileList = dir.listFiles();
|
||||
|
||||
List<FileDto.Basic> files = new ArrayList<>();
|
||||
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
int fileListPos = 0;
|
||||
int fileTotCnt = 0;
|
||||
long fileTotSize = 0;
|
||||
|
||||
if (fileList != null) {
|
||||
if (sortType.equals("name")) {
|
||||
Arrays.sort(fileList);
|
||||
} else if (sortType.equals("date")) {
|
||||
Arrays.sort(fileList, Comparator.comparingLong(File::lastModified));
|
||||
}
|
||||
|
||||
for (File file : fileList) {
|
||||
if (file.isFile()) { // 파일인 경우만
|
||||
if (extension.equals("*") || file.getName().endsWith("." + extension)) {
|
||||
|
||||
fileListPos = fileListPos + 1;
|
||||
|
||||
if (startPos <= fileListPos && endPos >= fileListPos) {
|
||||
|
||||
// 생성자를 통해 객체를 만들고 리스트에 추가
|
||||
String fileName = file.getName();
|
||||
String parentPath = file.getParent();
|
||||
String fullPath = file.getAbsolutePath();
|
||||
String ext = FilenameUtils.getExtension(fileName);
|
||||
|
||||
Path path = Paths.get(parentPath);
|
||||
String parentFolderNm = path.getFileName().toString();
|
||||
|
||||
long fileSize = file.length();
|
||||
String lastModified = dttmFormat.format(new Date(file.lastModified()));
|
||||
|
||||
files.add(
|
||||
new FileDto.Basic(
|
||||
fileName, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified));
|
||||
|
||||
fileTotCnt = fileTotCnt + 1;
|
||||
fileTotSize = fileTotSize + fileSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new FilesDto(dirPath, fileTotCnt, fileTotSize, files);
|
||||
}
|
||||
|
||||
public FilesDto getFilesDepthAll(SrchFilesDepthDto srchDto) {
|
||||
|
||||
Path startPath = Paths.get(srchDto.getDirPath());
|
||||
int maxDepth = srchDto.getMaxDepth();
|
||||
String dirPath = srchDto.getDirPath();
|
||||
String extension = srchDto.getExtension();
|
||||
String sortType = srchDto.getSortType();
|
||||
|
||||
int startPos = srchDto.getStartPos();
|
||||
int endPos = srchDto.getEndPos();
|
||||
int limit = endPos - startPos + 1;
|
||||
|
||||
Set<String> targetExtensions = createExtensionSet(extension);
|
||||
|
||||
List<FileDto.Basic> fileDtoList = new ArrayList<>();
|
||||
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
int fileTotCnt = 0;
|
||||
long fileTotSize = 0;
|
||||
|
||||
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
|
||||
|
||||
fileDtoList =
|
||||
stream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(
|
||||
p ->
|
||||
extension == null
|
||||
|| extension.equals("")
|
||||
|| extension.equals("*")
|
||||
|| targetExtensions.contains(extractExtension(p)))
|
||||
.sorted(getFileComparator(sortType))
|
||||
.skip(startPos)
|
||||
.limit(limit)
|
||||
.map(
|
||||
path -> {
|
||||
int depth = path.getNameCount();
|
||||
|
||||
String fileNm = path.getFileName().toString();
|
||||
String ext = FilenameUtils.getExtension(fileNm);
|
||||
String parentFolderNm = path.getParent().getFileName().toString();
|
||||
String parentPath = path.getParent().toString();
|
||||
String fullPath = path.toAbsolutePath().toString();
|
||||
|
||||
File file = new File(fullPath);
|
||||
long fileSize = file.length();
|
||||
String lastModified = dttmFormat.format(new Date(file.lastModified()));
|
||||
|
||||
return new FileDto.Basic(
|
||||
fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
fileTotCnt = fileDtoList.size();
|
||||
fileTotSize = fileDtoList.stream().mapToLong(FileDto.Basic::getFileSize).sum();
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("파일 I/O 오류 발생: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new FilesDto(dirPath, fileTotCnt, fileTotSize, fileDtoList);
|
||||
}
|
||||
|
||||
public Set<String> createExtensionSet(String extensionString) {
|
||||
if (extensionString == null || extensionString.isBlank()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
// "java, class" -> ["java", " class"] -> [".java", ".class"]
|
||||
return Arrays.stream(extensionString.split(","))
|
||||
.map(ext -> ext.trim())
|
||||
.filter(ext -> !ext.isEmpty())
|
||||
.map(ext -> "." + ext.toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public String extractExtension(Path path) {
|
||||
String filename = path.getFileName().toString();
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
|
||||
// 확장자가 없거나 파일명이 .으로 끝나는 경우
|
||||
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
|
||||
return ""; // 빈 문자열 반환
|
||||
}
|
||||
|
||||
// 확장자 추출 및 소문자 변환
|
||||
return filename.substring(lastDotIndex).toLowerCase();
|
||||
}
|
||||
|
||||
public Comparator<Path> getFileComparator(String sortType) {
|
||||
|
||||
// 파일 이름 비교 기본 Comparator (대소문자 무시)
|
||||
Comparator<Path> nameComparator =
|
||||
Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER);
|
||||
|
||||
Comparator<Path> dateComparator =
|
||||
Comparator.comparing(
|
||||
path -> {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path);
|
||||
} catch (IOException e) {
|
||||
return FileTime.fromMillis(0);
|
||||
}
|
||||
});
|
||||
|
||||
if ("name desc".equalsIgnoreCase(sortType)) {
|
||||
return nameComparator.reversed();
|
||||
} else if ("date".equalsIgnoreCase(sortType)) {
|
||||
return dateComparator;
|
||||
} else if ("date desc".equalsIgnoreCase(sortType)) {
|
||||
return dateComparator.reversed();
|
||||
} else {
|
||||
return nameComparator;
|
||||
}
|
||||
}
|
||||
|
||||
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
return mapSheetMngCoreService.findMapSheetErrorList(searchReq);
|
||||
}
|
||||
|
||||
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
return mapSheetMngCoreService.findMapSheetMngList(searchReq);
|
||||
}
|
||||
|
||||
public MapSheetMngDto.DmlReturn uploadProcess(@Valid List<Long> hstUidList) {
|
||||
return mapSheetMngCoreService.uploadProcess(hstUidList);
|
||||
}
|
||||
|
||||
public MapSheetMngDto.DmlReturn updateExceptUseInference(@Valid List<Long> hstUidList) {
|
||||
return mapSheetMngCoreService.updateExceptUseInference(hstUidList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.kamco.cd.kamcoback.members;
|
||||
|
||||
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.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 jakarta.validation.Valid;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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.RestController;
|
||||
|
||||
@Tag(name = "회원정보 관리자 관리", description = "회원정보 관리자 관리 API")
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/members")
|
||||
@RequiredArgsConstructor
|
||||
public class AdminApiController {
|
||||
|
||||
private final AdminService adminService;
|
||||
|
||||
@Operation(summary = "회원가입", description = "회원가입")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "201",
|
||||
description = "회원가입 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/join")
|
||||
public ApiResponseDto<Long> saveMember(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "회원가입",
|
||||
required = true,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = MembersDto.AddReq.class)))
|
||||
@RequestBody
|
||||
@Valid
|
||||
MembersDto.AddReq addReq) {
|
||||
|
||||
return ApiResponseDto.createOK(adminService.saveMember(addReq));
|
||||
}
|
||||
|
||||
@Operation(summary = "역할 추가", description = "uuid 기준으로 역할 추가")
|
||||
@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)
|
||||
})
|
||||
@PostMapping("/roles/add")
|
||||
public ApiResponseDto<UUID> saveRoles(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "역할 추가",
|
||||
required = true,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = MembersDto.RolesDto.class)))
|
||||
@RequestBody
|
||||
@Valid
|
||||
MembersDto.RolesDto rolesDto) {
|
||||
adminService.saveRoles(rolesDto);
|
||||
return ApiResponseDto.createOK(rolesDto.getUuid());
|
||||
}
|
||||
|
||||
@Operation(summary = "역할 삭제", description = "uuid 기준으로 역할 삭제")
|
||||
@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("/roles/rm")
|
||||
public ApiResponseDto<UUID> deleteRoles(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "역할 삭제",
|
||||
required = true,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = MembersDto.RolesDto.class)))
|
||||
@RequestBody
|
||||
@Valid
|
||||
MembersDto.RolesDto rolesDto) {
|
||||
adminService.deleteRoles(rolesDto);
|
||||
return ApiResponseDto.createOK(rolesDto.getUuid());
|
||||
}
|
||||
|
||||
@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)
|
||||
})
|
||||
@PatchMapping("{uuid}/status")
|
||||
public ApiResponseDto<UUID> updateStatus(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "상태 수정",
|
||||
required = true,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = MembersDto.StatusDto.class)))
|
||||
@PathVariable
|
||||
UUID uuid,
|
||||
@RequestBody @Valid MembersDto.StatusDto statusDto) {
|
||||
adminService.updateStatus(uuid, statusDto);
|
||||
return ApiResponseDto.createOK(uuid);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Operation(summary = "비밀번호 초기화", description = "비밀번호 초기화")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "201",
|
||||
description = "비밀번호 초기화",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PatchMapping("/{memberId}/password")
|
||||
public ApiResponseDto<Long> resetPassword(@PathVariable Long memberId) {
|
||||
adminService.resetPassword(memberId);
|
||||
return ApiResponseDto.createOK(memberId);
|
||||
}
|
||||
}
|
||||
170
src/main/java/com/kamco/cd/kamcoback/members/AuthController.java
Normal file
170
src/main/java/com/kamco/cd/kamcoback/members/AuthController.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package com.kamco.cd.kamcoback.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.JwtTokenProvider;
|
||||
import com.kamco.cd.kamcoback.auth.RefreshTokenService;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.SignInRequest;
|
||||
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 jakarta.servlet.http.HttpServletResponse;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.time.Duration;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
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.RestController;
|
||||
|
||||
@Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API")
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthController {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final RefreshTokenService refreshTokenService;
|
||||
|
||||
@Value("${token.refresh-cookie-name}")
|
||||
private String refreshCookieName;
|
||||
|
||||
@Value("${token.refresh-cookie-secure:true}")
|
||||
private boolean refreshCookieSecure;
|
||||
|
||||
@PostMapping("/signin")
|
||||
@Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "로그인 성공",
|
||||
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
||||
@ApiResponse(
|
||||
responseCode = "401",
|
||||
description = "ID 또는 비밀번호 불일치",
|
||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||
})
|
||||
public ApiResponseDto<TokenResponse> signin(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "로그인 요청 정보",
|
||||
required = true)
|
||||
@RequestBody
|
||||
SignInRequest request,
|
||||
HttpServletResponse response) {
|
||||
Authentication authentication =
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
||||
|
||||
String username = authentication.getName(); // UserDetailsService 에서 사용한 username
|
||||
|
||||
String accessToken = jwtTokenProvider.createAccessToken(username);
|
||||
String refreshToken = jwtTokenProvider.createRefreshToken(username);
|
||||
|
||||
// Redis에 RefreshToken 저장 (TTL = 7일)
|
||||
refreshTokenService.save(
|
||||
username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
||||
|
||||
// HttpOnly + Secure 쿠키에 RefreshToken 저장
|
||||
ResponseCookie cookie =
|
||||
ResponseCookie.from(refreshCookieName, refreshToken)
|
||||
.httpOnly(true)
|
||||
.secure(refreshCookieSecure)
|
||||
.path("/")
|
||||
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
||||
.sameSite("Strict")
|
||||
.build();
|
||||
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||
return ApiResponseDto.ok(new TokenResponse(accessToken));
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
@Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "재발급 성공",
|
||||
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
||||
@ApiResponse(
|
||||
responseCode = "401",
|
||||
description = "만료되었거나 유효하지 않은 리프레시 토큰",
|
||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||
})
|
||||
public ResponseEntity<TokenResponse> refresh(String refreshToken, HttpServletResponse response)
|
||||
throws AccessDeniedException {
|
||||
if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
|
||||
throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
|
||||
}
|
||||
|
||||
String username = jwtTokenProvider.getSubject(refreshToken);
|
||||
|
||||
// Redis에 저장된 RefreshToken과 일치하는지 확인
|
||||
if (!refreshTokenService.validate(username, refreshToken)) {
|
||||
throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
|
||||
}
|
||||
|
||||
// 새 토큰 발급
|
||||
String newAccessToken = jwtTokenProvider.createAccessToken(username);
|
||||
String newRefreshToken = jwtTokenProvider.createRefreshToken(username);
|
||||
|
||||
// Redis 갱신
|
||||
refreshTokenService.save(
|
||||
username, newRefreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
||||
|
||||
// 쿠키 갱신
|
||||
ResponseCookie cookie =
|
||||
ResponseCookie.from(refreshCookieName, newRefreshToken)
|
||||
.httpOnly(true)
|
||||
.secure(refreshCookieSecure)
|
||||
.path("/")
|
||||
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
||||
.sameSite("Strict")
|
||||
.build();
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||
|
||||
return ResponseEntity.ok(new TokenResponse(newAccessToken));
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.")
|
||||
@ApiResponses({
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "로그아웃 성공",
|
||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||
})
|
||||
public ApiResponseDto<ResponseEntity<Object>> logout(
|
||||
Authentication authentication, HttpServletResponse response) {
|
||||
if (authentication != null) {
|
||||
String username = authentication.getName();
|
||||
// Redis에서 RefreshToken 삭제
|
||||
refreshTokenService.delete(username);
|
||||
}
|
||||
|
||||
// 쿠키 삭제 (Max-Age=0)
|
||||
ResponseCookie cookie =
|
||||
ResponseCookie.from(refreshCookieName, "")
|
||||
.httpOnly(true)
|
||||
.secure(refreshCookieSecure)
|
||||
.path("/")
|
||||
.maxAge(0)
|
||||
.sameSite("Strict")
|
||||
.build();
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||
|
||||
return ApiResponseDto.createOK(ResponseEntity.noContent().build());
|
||||
}
|
||||
|
||||
public record TokenResponse(String accessToken) {}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.kamco.cd.kamcoback.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
||||
import com.kamco.cd.kamcoback.members.service.MembersService;
|
||||
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.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "회원정보 관리", description = "회원정보 관리 API")
|
||||
@RestController
|
||||
@RequestMapping("/api/members")
|
||||
@RequiredArgsConstructor
|
||||
public class MembersApiController {
|
||||
|
||||
private final MembersService membersService;
|
||||
|
||||
@Operation(summary = "회원정보 목록", description = "회원정보 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "검색 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Page.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping
|
||||
public ApiResponseDto<Page<Basic>> getMemberList(
|
||||
@ParameterObject MembersDto.SearchReq searchReq) {
|
||||
return ApiResponseDto.ok(membersService.findByMembers(searchReq));
|
||||
}
|
||||
|
||||
@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)
|
||||
})
|
||||
@PutMapping("/{uuid}")
|
||||
public ApiResponseDto<UUID> updateMember(
|
||||
@PathVariable UUID uuid, @RequestBody MembersDto.UpdateReq updateReq) {
|
||||
membersService.updateMember(uuid, updateReq);
|
||||
return ApiResponseDto.createOK(uuid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.kamco.cd.kamcoback.members.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MemberDetails implements UserDetails {
|
||||
|
||||
private final MemberEntity member;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
// TODO: tb_member_role 에서 역할 꺼내서 권한으로 변환하고 싶으면 여기 구현
|
||||
// 예시 (나중에 MemberRoleEntity 보고 수정):
|
||||
// return member.getTbMemberRoles().stream()
|
||||
// .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleName()))
|
||||
// .toList();
|
||||
|
||||
return List.of(); // 일단 빈 권한 리스트
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return member.getPassword(); // 암호화된 비밀번호
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
// 로그인 ID 로 무엇을 쓸지 선택
|
||||
// 1) 이메일 로그인:
|
||||
return member.getEmail();
|
||||
|
||||
// 2) 사번으로 로그인하고 싶으면:
|
||||
// return member.getEmployeeNo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
// status 가 ACTIVE 일 때만 로그인 허용
|
||||
return "ACTIVE".equalsIgnoreCase(member.getStatus());
|
||||
}
|
||||
|
||||
public MemberEntity getMember() {
|
||||
return member;
|
||||
}
|
||||
}
|
||||
173
src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java
Normal file
173
src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java
Normal file
@@ -0,0 +1,173 @@
|
||||
package com.kamco.cd.kamcoback.members.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.enums.RoleType;
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
public class MembersDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Basic {
|
||||
|
||||
private Long id;
|
||||
private UUID uuid;
|
||||
private String employeeNo;
|
||||
private String name;
|
||||
private String email;
|
||||
private String status;
|
||||
private String roleName;
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
@JsonFormatDttm private ZonedDateTime updatedDttm;
|
||||
|
||||
public Basic(
|
||||
Long id,
|
||||
UUID uuid,
|
||||
String employeeNo,
|
||||
String name,
|
||||
String email,
|
||||
String status,
|
||||
String roleName,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime updatedDttm) {
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
this.employeeNo = employeeNo;
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.status = status;
|
||||
this.roleName = roleName;
|
||||
this.createdDttm = createdDttm;
|
||||
this.updatedDttm = updatedDttm;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SearchReq {
|
||||
|
||||
@Schema(description = "이름(name), 이메일(email), 사번(employeeNo)", example = "name")
|
||||
private String field;
|
||||
|
||||
@Schema(description = "키워드", example = "홍길동")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "라벨러 포함 여부", example = "true")
|
||||
private boolean labeler = true;
|
||||
|
||||
@Schema(description = "검수자 포함 여부", example = "true")
|
||||
private boolean reviewer = true;
|
||||
|
||||
@Schema(description = "운영자 포함 여부", example = "true")
|
||||
private boolean admin = true;
|
||||
|
||||
// 페이징 파라미터
|
||||
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
|
||||
private int page = 0;
|
||||
|
||||
@Schema(description = "페이지 크기", example = "20")
|
||||
private int size = 20;
|
||||
|
||||
public Pageable toPageable() {
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class AddReq {
|
||||
|
||||
@Schema(description = "사번", example = "11111")
|
||||
@NotBlank
|
||||
@Size(max = 50)
|
||||
private String employeeNo;
|
||||
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
@NotBlank
|
||||
@Size(min = 2, max = 100)
|
||||
private String name;
|
||||
|
||||
@Schema(hidden = true)
|
||||
private String password;
|
||||
|
||||
@Schema(description = "이메일", example = "gildong@daum.net")
|
||||
@Size(max = 100)
|
||||
private String email;
|
||||
|
||||
public AddReq(String employeeNo, String name, String password, String email) {
|
||||
this.employeeNo = employeeNo;
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@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 = "")
|
||||
@Size(max = 255)
|
||||
private String password;
|
||||
|
||||
@Schema(description = "이메일", example = "gildong@daum.net")
|
||||
@Size(max = 100)
|
||||
private String email;
|
||||
|
||||
public UpdateReq(String employeeNo, String name, String password, String email) {
|
||||
this.employeeNo = employeeNo;
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class RolesDto {
|
||||
|
||||
@Schema(description = "UUID", example = "4e89e487-c828-4a34-a7fc-0d5b0e3b53b5")
|
||||
private UUID uuid;
|
||||
|
||||
@Schema(description = "역할 ROLE_ADMIN, ROLE_LABELER, ROLE_REVIEWER", example = "ROLE_ADMIN")
|
||||
@EnumValid(enumClass = RoleType.class)
|
||||
private String roleName;
|
||||
|
||||
public RolesDto(UUID uuid, String roleName) {
|
||||
this.uuid = uuid;
|
||||
this.roleName = roleName;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class StatusDto {
|
||||
|
||||
@Schema(description = "변경할 상태값 ACTIVE, INACTIVE, ARCHIVED", example = "ACTIVE")
|
||||
@NotBlank
|
||||
private String status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.kamco.cd.kamcoback.members.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString(exclude = "password")
|
||||
public class SignInRequest {
|
||||
|
||||
@Schema(description = "사번", example = "11111")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "비밀번호", example = "kamco1234!")
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.kamco.cd.kamcoback.members.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class MemberException {
|
||||
|
||||
// *** Duplicate Member Exception ***
|
||||
@Getter
|
||||
public static class DuplicateMemberException extends RuntimeException {
|
||||
|
||||
public enum Field {
|
||||
EMPLOYEE_NO,
|
||||
EMAIL,
|
||||
DEFAULT
|
||||
}
|
||||
|
||||
private final Field field;
|
||||
private final String value;
|
||||
|
||||
public DuplicateMemberException(Field field, String value) {
|
||||
super(field.name() + " duplicate: " + value);
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// *** Member Not Found Exception ***
|
||||
public static class MemberNotFoundException extends RuntimeException {
|
||||
|
||||
public MemberNotFoundException() {
|
||||
super("Member not found");
|
||||
}
|
||||
|
||||
public MemberNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.kamco.cd.kamcoback.members.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
|
||||
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.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
@RequiredArgsConstructor
|
||||
public class AdminService {
|
||||
|
||||
private final MembersCoreService membersCoreService;
|
||||
|
||||
@Value("${member.init_password}")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 회원가입
|
||||
*
|
||||
* @param addReq
|
||||
* @return
|
||||
*/
|
||||
@Transactional
|
||||
public Long saveMember(MembersDto.AddReq addReq) {
|
||||
// salt 생성, 사번이 salt
|
||||
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim());
|
||||
|
||||
// 패스워드 암호화, 초기 패스워드 고정
|
||||
String hashedPassword = BCrypt.hashpw(password, salt);
|
||||
addReq.setPassword(hashedPassword);
|
||||
return membersCoreService.saveMembers(addReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 역할 추가
|
||||
*
|
||||
* @param rolesDto
|
||||
*/
|
||||
@Transactional
|
||||
public void saveRoles(MembersDto.RolesDto rolesDto) {
|
||||
membersCoreService.saveRoles(rolesDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 역할 삭제
|
||||
*
|
||||
* @param rolesDto
|
||||
*/
|
||||
public void deleteRoles(MembersDto.RolesDto rolesDto) {
|
||||
membersCoreService.deleteRoles(rolesDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 역할 수정
|
||||
*
|
||||
* @param statusDto
|
||||
*/
|
||||
public void updateStatus(UUID uuid, MembersDto.StatusDto statusDto) {
|
||||
membersCoreService.updateStatus(uuid, statusDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 탈퇴
|
||||
*
|
||||
* @param uuid
|
||||
*/
|
||||
public void deleteAccount(UUID uuid) {
|
||||
membersCoreService.deleteAccount(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 초기화
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
public void resetPassword(Long id) {
|
||||
membersCoreService.resetPassword(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.kamco.cd.kamcoback.members.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MemberDetailsService implements UserDetailsService {
|
||||
|
||||
private final MembersRepository membersRepository;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
UUID uuid = UUID.fromString(username);
|
||||
MemberEntity member =
|
||||
membersRepository
|
||||
.findByUUID(uuid)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("USER NOT FOUND"));
|
||||
|
||||
return new CustomUserDetails(member);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.kamco.cd.kamcoback.members.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
|
||||
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||
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.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
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;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
@RequiredArgsConstructor
|
||||
public class MembersService {
|
||||
|
||||
private final MembersCoreService membersCoreService;
|
||||
|
||||
/**
|
||||
* 회원목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
public Page<Basic> findByMembers(MembersDto.SearchReq searchReq) {
|
||||
return membersCoreService.findByMembers(searchReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원정보 수정
|
||||
*
|
||||
* @param uuid
|
||||
* @param updateReq
|
||||
*/
|
||||
public void updateMember(UUID uuid, MembersDto.UpdateReq updateReq) {
|
||||
|
||||
if (StringUtils.isNotBlank(updateReq.getPassword())) {
|
||||
|
||||
if (!this.isValidPassword(updateReq.getPassword())) {
|
||||
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(updateReq.getEmployeeNo())) {
|
||||
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// salt 생성, 사번이 salt
|
||||
String salt =
|
||||
BCryptSaltGenerator.generateSaltWithEmployeeNo(updateReq.getEmployeeNo().trim());
|
||||
|
||||
// 패스워드 암호화, 초기 패스워드 고정
|
||||
String hashedPassword = BCrypt.hashpw(updateReq.getPassword(), salt);
|
||||
updateReq.setPassword(hashedPassword);
|
||||
}
|
||||
|
||||
membersCoreService.updateMembers(uuid, updateReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 대문자 1개 이상 소문자 1개 이상 숫자 1개 이상 특수문자(!@#$) 1개 이상
|
||||
*
|
||||
* @param password
|
||||
* @return
|
||||
*/
|
||||
private boolean isValidPassword(String password) {
|
||||
String regex = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[!@#$]).{8,20}$";
|
||||
return Pattern.matches(regex, password);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.kamco.cd.kamcoback.menu;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||
import com.kamco.cd.kamcoback.menu.service.MenuService;
|
||||
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.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "메뉴 관리", description = "메뉴 관리 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/menu")
|
||||
public class MenuApiController {
|
||||
|
||||
private final MenuService menuService;
|
||||
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
|
||||
@Operation(summary = "목록 조회", description = "모든 메뉴 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = MenuDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping
|
||||
public ApiResponseDto<List<MenuDto.Basic>> getFindAll() {
|
||||
return ApiResponseDto.ok(menuService.getFindAll());
|
||||
}
|
||||
|
||||
@GetMapping("/find-uri")
|
||||
public ApiResponseDto<String> getFindAllByUid(@RequestParam String apiUri) {
|
||||
List<?> list = menuService.getFindAll();
|
||||
|
||||
List<MenuDto.Basic> result =
|
||||
list.stream()
|
||||
.map(
|
||||
item -> {
|
||||
if (item instanceof LinkedHashMap<?, ?> map) {
|
||||
return objectMapper.convertValue(map, MenuDto.Basic.class);
|
||||
} else if (item instanceof MenuDto.Basic dto) {
|
||||
return dto;
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported cache type: " + item.getClass());
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
|
||||
return ApiResponseDto.ok(ApiLogFunction.getUriMenuInfo(result, apiUri));
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/kamco/cd/kamcoback/menu/dto/MenuDto.java
Normal file
64
src/main/java/com/kamco/cd/kamcoback/menu/dto/MenuDto.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.kamco.cd.kamcoback.menu.dto;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
public class MenuDto {
|
||||
|
||||
@Schema(name = "Menu Basic", description = "메뉴 기본 정보")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public static class Basic {
|
||||
|
||||
private String menuUid;
|
||||
private String menuNm;
|
||||
private String menuUrl;
|
||||
private String description;
|
||||
private Long menuOrder;
|
||||
private Boolean isUse;
|
||||
private Boolean deleted;
|
||||
private Long createdUid;
|
||||
private Long updatedUid;
|
||||
|
||||
private List<MenuDto.Basic> children;
|
||||
|
||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||
|
||||
@JsonFormatDttm private ZonedDateTime updatedDttm;
|
||||
|
||||
private String menuApiUrl;
|
||||
|
||||
public Basic(
|
||||
String menuUid,
|
||||
String menuNm,
|
||||
String menuUrl,
|
||||
String description,
|
||||
Long menuOrder,
|
||||
Boolean isUse,
|
||||
Boolean deleted,
|
||||
Long createdUid,
|
||||
Long updatedUid,
|
||||
List<MenuDto.Basic> children,
|
||||
ZonedDateTime createdDttm,
|
||||
ZonedDateTime updatedDttm,
|
||||
String menuApiUrl) {
|
||||
this.menuUid = menuUid;
|
||||
this.menuNm = menuNm;
|
||||
this.menuUrl = menuUrl;
|
||||
this.description = description;
|
||||
this.menuOrder = menuOrder;
|
||||
this.isUse = isUse;
|
||||
this.deleted = deleted;
|
||||
this.createdUid = createdUid;
|
||||
this.updatedUid = updatedUid;
|
||||
this.children = children;
|
||||
this.createdDttm = createdDttm;
|
||||
this.updatedDttm = updatedDttm;
|
||||
this.menuApiUrl = menuApiUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.kamco.cd.kamcoback.menu.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||
import com.kamco.cd.kamcoback.postgres.core.MenuCoreService;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MenuService {
|
||||
private final MenuCoreService menuCoreService;
|
||||
|
||||
@Cacheable(value = "menuFindAll")
|
||||
public List<MenuDto.Basic> getFindAll() {
|
||||
return menuCoreService.getFindAll();
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.SaveReq;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.auth.AuthRepository;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthCoreService {
|
||||
|
||||
private final AuthRepository authRepository;
|
||||
|
||||
/**
|
||||
* 관리자 등록
|
||||
*
|
||||
* @param saveReq
|
||||
* @return
|
||||
*/
|
||||
public UserEntity save(SaveReq saveReq) {
|
||||
if (authRepository.findByUserId(saveReq.getUserId()).isPresent()) {
|
||||
new EntityNotFoundException("중복된 아이디가 있습니다. " + saveReq.getUserId());
|
||||
}
|
||||
|
||||
UserEntity userEntity =
|
||||
new UserEntity(
|
||||
null,
|
||||
saveReq.getUserAuth(),
|
||||
saveReq.getUserNm(),
|
||||
saveReq.getUserId(),
|
||||
saveReq.getEmpId(),
|
||||
saveReq.getUserEmail(),
|
||||
saveReq.getUserPw());
|
||||
|
||||
return authRepository.save(userEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 정보 수정
|
||||
*
|
||||
* @param id
|
||||
* @param saveReq
|
||||
* @return
|
||||
*/
|
||||
public UserEntity update(Long id, AuthDto.SaveReq saveReq) {
|
||||
UserEntity userEntity =
|
||||
authRepository.findById(id).orElseThrow(() -> new RuntimeException("유저가 존재하지 않습니다."));
|
||||
|
||||
if (saveReq.getUserAuth() != null) {
|
||||
userEntity.setUserAuth(saveReq.getUserAuth());
|
||||
}
|
||||
|
||||
if (saveReq.getUserNm() != null) {
|
||||
userEntity.setUserNm(saveReq.getUserNm());
|
||||
}
|
||||
|
||||
if (saveReq.getUserId() != null) {
|
||||
userEntity.setUserId(saveReq.getUserId());
|
||||
}
|
||||
|
||||
if (saveReq.getEmpId() != null) {
|
||||
userEntity.setEmpId(saveReq.getEmpId());
|
||||
}
|
||||
|
||||
if (saveReq.getUserEmail() != null) {
|
||||
userEntity.setUserEmail(saveReq.getUserEmail());
|
||||
}
|
||||
|
||||
if (saveReq.getUserPw() != null) {
|
||||
userEntity.setUserPw(saveReq.getUserPw());
|
||||
}
|
||||
|
||||
return authRepository.save(userEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 삭제
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public UserEntity withdrawal(Long id) {
|
||||
UserEntity userEntity =
|
||||
authRepository.findById(id).orElseThrow(() -> new RuntimeException("유저가 존재하지 않습니다."));
|
||||
|
||||
userEntity.setId(id);
|
||||
userEntity.setDateWithdrawal(ZonedDateTime.now());
|
||||
userEntity.setState("WITHDRAWAL");
|
||||
|
||||
return authRepository.save(userEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시퀀스 id로 관리자 조회
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public AuthDto.Basic findUserById(Long id) {
|
||||
UserEntity entity =
|
||||
authRepository
|
||||
.findUserById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException("관리자를 찾을 수 없습니다. " + id));
|
||||
return entity.toDto();
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
public Page<AuthDto.Basic> getUserList(AuthDto.SearchReq searchReq) {
|
||||
return authRepository.getUserList(searchReq);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ 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.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;
|
||||
@@ -10,6 +12,8 @@ import jakarta.persistence.EntityNotFoundException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -20,11 +24,48 @@ public class CommonCodeCoreService
|
||||
|
||||
private final CommonCodeRepository commonCodeRepository;
|
||||
|
||||
/**
|
||||
* 모두 조회
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Cacheable(value = "commonCodes")
|
||||
public List<CommonCodeDto.Basic> findAll() {
|
||||
return commonCodeRepository.findByAll().stream().map(CommonCodeEntity::toDto).toList();
|
||||
}
|
||||
|
||||
public CommonCodeDto.Basic save(CommonCodeDto.AddReq req) {
|
||||
/**
|
||||
* 등록
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ResponseObj save(CommonCodeDto.AddReq req) {
|
||||
|
||||
String regex = "^([A-Z]+|[0-9]+|[A-Z0-9]+(_[A-Z0-9]+)+)$";
|
||||
boolean isValid = req.getCode().matches(regex);
|
||||
if (!isValid) {
|
||||
return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다.");
|
||||
}
|
||||
|
||||
Long existsCount =
|
||||
commonCodeRepository.findByParentIdCodeExists(req.getParentId(), req.getCode());
|
||||
if (existsCount > 0) {
|
||||
return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다.");
|
||||
}
|
||||
|
||||
CommonCodeEntity entity =
|
||||
new CommonCodeEntity(
|
||||
req.getCode(),
|
||||
req.getName(),
|
||||
req.getDescription(),
|
||||
req.getOrder(),
|
||||
req.isUsed(),
|
||||
req.getProps1(),
|
||||
req.getProps2(),
|
||||
req.getProps3());
|
||||
|
||||
if (req.getParentId() != null) {
|
||||
CommonCodeEntity parentCommonCodeEntity =
|
||||
commonCodeRepository
|
||||
@@ -34,39 +75,56 @@ public class CommonCodeCoreService
|
||||
new EntityNotFoundException(
|
||||
"parent id 를 찾을 수 없습니다. id : " + req.getParentId()));
|
||||
|
||||
CommonCodeEntity entity =
|
||||
new CommonCodeEntity(
|
||||
req.getCode(), req.getName(), req.getDescription(), req.getOrder(), req.isUsed());
|
||||
entity.addParent(parentCommonCodeEntity);
|
||||
return commonCodeRepository.save(entity).toDto();
|
||||
}
|
||||
|
||||
CommonCodeEntity entity =
|
||||
new CommonCodeEntity(
|
||||
req.getCode(), req.getName(), req.getDescription(), req.getOrder(), req.isUsed());
|
||||
return commonCodeRepository.save(entity).toDto();
|
||||
commonCodeRepository.save(entity).toDto();
|
||||
return new ResponseObj(ApiResponseCode.OK, "");
|
||||
}
|
||||
|
||||
public CommonCodeDto.Basic update(Long id, CommonCodeDto.ModifyReq req) {
|
||||
/**
|
||||
* 수정
|
||||
*
|
||||
* @param id
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ResponseObj update(Long id, CommonCodeDto.ModifyReq req) {
|
||||
CommonCodeEntity found =
|
||||
commonCodeRepository
|
||||
.findByCodeId(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + id));
|
||||
|
||||
CommonCodeEntity entity =
|
||||
new CommonCodeEntity(
|
||||
id,
|
||||
req.getName(),
|
||||
req.getDescription(),
|
||||
req.getOrder(),
|
||||
req.isUsed(),
|
||||
found.getDeleted());
|
||||
found.update(
|
||||
req.getName(),
|
||||
req.getDescription(),
|
||||
req.isUsed(),
|
||||
req.getProps1(),
|
||||
req.getProps2(),
|
||||
req.getProps3());
|
||||
|
||||
return commonCodeRepository.save(entity).toDto();
|
||||
return new ResponseObj(ApiResponseCode.OK, "");
|
||||
}
|
||||
|
||||
public void updateOrder(CommonCodeDto.OrderReq req) {
|
||||
commonCodeRepository.updateOrder(req);
|
||||
/**
|
||||
* 순서 변경
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ResponseObj updateOrder(CommonCodeDto.OrderReq req) {
|
||||
|
||||
CommonCodeEntity found =
|
||||
commonCodeRepository
|
||||
.findByCodeId(req.getId())
|
||||
.orElseThrow(
|
||||
() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + req.getId()));
|
||||
|
||||
found.updateOrder(req.getOrder());
|
||||
|
||||
return new ResponseObj(ApiResponseCode.OK, "");
|
||||
}
|
||||
|
||||
public List<CommonCodeDto.Basic> findByCode(String code) {
|
||||
@@ -84,19 +142,42 @@ public class CommonCodeCoreService
|
||||
return commonCodeRepository.getCode(parentCodeCd, childCodeCd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Long id) {
|
||||
/**
|
||||
* 공통코드 삭제
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@CacheEvict(value = "commonCodes", allEntries = true)
|
||||
public ResponseObj removeCode(Long id) {
|
||||
CommonCodeEntity entity =
|
||||
commonCodeRepository
|
||||
.findByCodeId(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id));
|
||||
|
||||
// 하위 코드 deleted = false 업데이트
|
||||
entity.getChildren().forEach(CommonCodeEntity::deleted);
|
||||
// 하위코드가 있으면 삭제 불가
|
||||
if (!entity.getChildren().isEmpty()) {
|
||||
return new ResponseObj(
|
||||
ApiResponseCode.UNPROCESSABLE_ENTITY,
|
||||
"하위에 다른 공통코드를 가지고 있습니다.<br/>하위공통 코드를 이동한 후 삭제할 수 있습니다.");
|
||||
}
|
||||
|
||||
// id 코드 deleted = false 업데이트
|
||||
entity.deleted();
|
||||
return new ResponseObj(ApiResponseCode.OK, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Long aLong) {
|
||||
// 미사용
|
||||
}
|
||||
|
||||
/**
|
||||
* id 로 단건 조회
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Basic getOneById(Long id) {
|
||||
CommonCodeEntity entity =
|
||||
@@ -110,4 +191,27 @@ public class CommonCodeCoreService
|
||||
public Page<Basic> search(SearchReq searchReq) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 중복 체크
|
||||
*
|
||||
* @param parentId
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public ResponseObj getCodeCheckDuplicate(Long parentId, String code) {
|
||||
Long existsCount = commonCodeRepository.findByParentIdCodeExists(parentId, code);
|
||||
|
||||
String regex = "^([A-Z]+|[0-9]+|[A-Z0-9]+(_[A-Z0-9]+)+)$";
|
||||
boolean isValid = code.matches(regex);
|
||||
if (!isValid) {
|
||||
return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다.");
|
||||
}
|
||||
|
||||
if (existsCount > 0) {
|
||||
return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다.");
|
||||
}
|
||||
|
||||
return new ResponseObj(ApiResponseCode.OK, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngRepository;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MapSheetMngCoreService {
|
||||
|
||||
private final MapSheetMngRepository mapSheetMngRepository;
|
||||
|
||||
private static final String ORIGINAL_IMAGES_PATH = "/app/original-images";
|
||||
|
||||
@Value("{spring.profiles.active}")
|
||||
private String activeEnv;
|
||||
|
||||
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
return mapSheetMngRepository.findMapSheetErrorList(searchReq);
|
||||
}
|
||||
|
||||
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(
|
||||
MapSheetMngDto.@Valid searchReq searchReq) {
|
||||
return mapSheetMngRepository.findMapSheetMngList(searchReq);
|
||||
}
|
||||
|
||||
public MapSheetMngDto.DmlReturn uploadProcess(@Valid List<Long> hstUidList) {
|
||||
int count = 0;
|
||||
if (!Objects.isNull(hstUidList) && !hstUidList.isEmpty()) {
|
||||
for (Long hstUid : hstUidList) {
|
||||
Optional<MapSheetMngHstEntity> entity =
|
||||
Optional.ofNullable(
|
||||
mapSheetMngRepository
|
||||
.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;
|
||||
}
|
||||
|
||||
String filename = entity.get().getMapSheetNum();
|
||||
String[] extensions = {"tif", "tfw"};
|
||||
boolean flag = allExtensionsExist(rootDir, filename, extensions);
|
||||
if (flag) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
MapSheetMngDto.DataState dataState =
|
||||
flag ? MapSheetMngDto.DataState.SUCCESS : MapSheetMngDto.DataState.FAIL;
|
||||
entity.get().updateDataState(dataState);
|
||||
}
|
||||
}
|
||||
return new MapSheetMngDto.DmlReturn("success", count + "개 업로드 성공하였습니다.");
|
||||
}
|
||||
|
||||
public MapSheetMngDto.DmlReturn updateExceptUseInference(@Valid List<Long> hstUidList) {
|
||||
if (!Objects.isNull(hstUidList) && !hstUidList.isEmpty()) {
|
||||
for (Long hstUid : hstUidList) {
|
||||
Optional<MapSheetMngHstEntity> entity =
|
||||
Optional.ofNullable(
|
||||
mapSheetMngRepository
|
||||
.findMapSheetMngHstInfo(hstUid)
|
||||
.orElseThrow(EntityNotFoundException::new));
|
||||
|
||||
entity.get().updateUseInference(true);
|
||||
}
|
||||
}
|
||||
return new MapSheetMngDto.DmlReturn("success", hstUidList.size() + "개 추론제외 업데이트 하였습니다.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 파일명 + 여러 확장자가 모두 존재하는지 확인
|
||||
*
|
||||
* @param rootDir 검색할 최상위 디렉토리
|
||||
* @param filename 파일명 (확장자 제외)
|
||||
* @param extensions 확인할 확장자 배열 (예: {"tif", "tfw"})
|
||||
* @return 모든 확장자가 존재하면 true, 하나라도 없으면 false
|
||||
*/
|
||||
public static boolean allExtensionsExist(String rootDir, String filename, String... extensions) {
|
||||
try (Stream<Path> paths = Files.walk(Paths.get(rootDir))) {
|
||||
|
||||
// 모든 파일명을 Set으로 저장
|
||||
Set<String> fileNames =
|
||||
paths
|
||||
.filter(Files::isRegularFile)
|
||||
.map(p -> p.getFileName().toString())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 모든 확장자 파일 존재 여부 확인
|
||||
for (String ext : extensions) {
|
||||
String target = filename + "." + ext;
|
||||
if (!fileNames.contains(target)) {
|
||||
return false; // 하나라도 없으면 false
|
||||
}
|
||||
}
|
||||
|
||||
return true; // 모두 존재하면 true
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("File search error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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.repository.mapsheet.MapSheetMngFileCheckerRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MapSheetMngFileCheckerCoreService {
|
||||
|
||||
private final MapSheetMngFileCheckerRepository mapSheetMngFileCheckerRepository;
|
||||
|
||||
private static final String ORIGINAL_IMAGES_PATH = "/app/original-images";
|
||||
|
||||
@Value("{spring.profiles.active}")
|
||||
private String activeEnv;
|
||||
|
||||
public ImageryDto.SyncReturn syncProcess(ImageryDto.searchReq searchReq) {
|
||||
String flag = "SUCCESS";
|
||||
int syncCnt = 0;
|
||||
int tfwErrCnt = 0;
|
||||
int tifErrCnt = 0;
|
||||
|
||||
// 대상파일목록 가저오기
|
||||
Page<ImageryDto.SyncDto> pageImagerySyncDto =
|
||||
mapSheetMngFileCheckerRepository.findImagerySyncList(searchReq);
|
||||
|
||||
for (ImageryDto.SyncDto dto : pageImagerySyncDto.getContent()) {
|
||||
|
||||
boolean isTfwFile = true;
|
||||
isTfwFile = FIleChecker.checkTfw(dto.getMiddlePath() + dto.getFilename());
|
||||
|
||||
boolean isGdalInfoTiffFile = true;
|
||||
isGdalInfoTiffFile = FIleChecker.cmmndGdalInfo(dto.getCogMiddlePath() + dto.getCogFilename());
|
||||
// isGdalInfoTiffFile = FIleChecker.cmmndGdalInfo("D:/kamco_cog/36713/36713073_cog.tif");
|
||||
|
||||
syncCnt = syncCnt + 1;
|
||||
if (!isTfwFile) tfwErrCnt = tfwErrCnt + 1;
|
||||
if (!isGdalInfoTiffFile) tifErrCnt = tifErrCnt + 1;
|
||||
|
||||
// 예: 특정 작업 수행
|
||||
// someService.process(dto);
|
||||
}
|
||||
|
||||
if (tfwErrCnt > 0 || tifErrCnt > 0) flag = "ERROR";
|
||||
|
||||
return new ImageryDto.SyncReturn(flag, syncCnt, tfwErrCnt, tifErrCnt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.members.exception.MemberException;
|
||||
import com.kamco.cd.kamcoback.members.exception.MemberException.MemberNotFoundException;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberArchivedEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberArchivedEntityId;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberRoleEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberRoleEntityId;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.members.MembersArchivedRepository;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRoleRepository;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MembersCoreService {
|
||||
|
||||
private final MembersRepository membersRepository;
|
||||
private final MembersRoleRepository memberRoleRepository;
|
||||
private final MembersArchivedRepository memberArchivedRepository;
|
||||
|
||||
@Value("${member.init_password}")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 회원가입
|
||||
*
|
||||
* @param addReq
|
||||
* @return
|
||||
*/
|
||||
public Long saveMembers(MembersDto.AddReq addReq) {
|
||||
if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) {
|
||||
throw new MemberException.DuplicateMemberException(
|
||||
MemberException.DuplicateMemberException.Field.EMPLOYEE_NO, addReq.getEmployeeNo());
|
||||
}
|
||||
|
||||
if (membersRepository.existsByEmail(addReq.getEmail())) {
|
||||
throw new MemberException.DuplicateMemberException(
|
||||
MemberException.DuplicateMemberException.Field.EMAIL, addReq.getEmail());
|
||||
}
|
||||
|
||||
MemberEntity memberEntity = new MemberEntity();
|
||||
memberEntity.setEmployeeNo(addReq.getEmployeeNo());
|
||||
memberEntity.setName(addReq.getName());
|
||||
memberEntity.setPassword(addReq.getPassword());
|
||||
memberEntity.setEmail(addReq.getEmail());
|
||||
|
||||
return membersRepository.save(memberEntity).getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원정보 수정
|
||||
*
|
||||
* @param uuid
|
||||
* @param updateReq
|
||||
*/
|
||||
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
|
||||
MemberEntity memberEntity =
|
||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
if (StringUtils.isNotBlank(memberEntity.getEmployeeNo())) {
|
||||
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(updateReq.getName())) {
|
||||
memberEntity.setName(updateReq.getName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(updateReq.getPassword())) {
|
||||
memberEntity.setPassword(updateReq.getPassword());
|
||||
}
|
||||
if (StringUtils.isNotBlank(updateReq.getEmail())) {
|
||||
memberEntity.setEmail(updateReq.getEmail());
|
||||
}
|
||||
|
||||
membersRepository.save(memberEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 역할 추가
|
||||
*
|
||||
* @param rolesDto
|
||||
*/
|
||||
public void saveRoles(MembersDto.RolesDto rolesDto) {
|
||||
|
||||
MemberEntity memberEntity =
|
||||
membersRepository
|
||||
.findByUUID(rolesDto.getUuid())
|
||||
.orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
if (memberRoleRepository.findByUuidAndRoleName(rolesDto)) {
|
||||
throw new MemberException.DuplicateMemberException(
|
||||
MemberException.DuplicateMemberException.Field.DEFAULT, "중복된 역할이 있습니다.");
|
||||
}
|
||||
|
||||
MemberRoleEntityId memberRoleEntityId = new MemberRoleEntityId();
|
||||
memberRoleEntityId.setMemberUuid(rolesDto.getUuid());
|
||||
memberRoleEntityId.setRoleName(rolesDto.getRoleName());
|
||||
|
||||
MemberRoleEntity memberRoleEntity = new MemberRoleEntity();
|
||||
memberRoleEntity.setId(memberRoleEntityId);
|
||||
memberRoleEntity.setMemberUuid(memberEntity);
|
||||
memberRoleEntity.setCreatedDttm(ZonedDateTime.now());
|
||||
memberRoleRepository.save(memberRoleEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 역할 삭제
|
||||
*
|
||||
* @param rolesDto
|
||||
*/
|
||||
public void deleteRoles(MembersDto.RolesDto rolesDto) {
|
||||
MemberEntity memberEntity =
|
||||
membersRepository
|
||||
.findByUUID(rolesDto.getUuid())
|
||||
.orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
MemberRoleEntityId memberRoleEntityId = new MemberRoleEntityId();
|
||||
memberRoleEntityId.setMemberUuid(rolesDto.getUuid());
|
||||
memberRoleEntityId.setRoleName(rolesDto.getRoleName());
|
||||
|
||||
MemberRoleEntity memberRoleEntity = new MemberRoleEntity();
|
||||
memberRoleEntity.setId(memberRoleEntityId);
|
||||
memberRoleEntity.setMemberUuid(memberEntity);
|
||||
|
||||
memberRoleRepository.delete(memberRoleEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 수정
|
||||
*
|
||||
* @param statusDto
|
||||
*/
|
||||
public void updateStatus(UUID uuid, MembersDto.StatusDto statusDto) {
|
||||
MemberEntity memberEntity =
|
||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
memberEntity.setStatus(statusDto.getStatus());
|
||||
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
||||
membersRepository.save(memberEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 탈퇴
|
||||
*
|
||||
* @param uuid
|
||||
*/
|
||||
public void deleteAccount(UUID uuid) {
|
||||
MemberEntity memberEntity =
|
||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
MemberArchivedEntityId memberArchivedEntityId = new MemberArchivedEntityId();
|
||||
memberArchivedEntityId.setUserId(memberEntity.getId());
|
||||
memberArchivedEntityId.setUuid(memberEntity.getUuid());
|
||||
|
||||
MemberArchivedEntity memberArchivedEntity = new MemberArchivedEntity();
|
||||
memberArchivedEntity.setId(memberArchivedEntityId);
|
||||
memberArchivedEntity.setEmployeeNo(memberEntity.getEmployeeNo());
|
||||
memberArchivedEntity.setName(memberEntity.getName());
|
||||
memberArchivedEntity.setPassword(memberEntity.getPassword());
|
||||
memberArchivedEntity.setEmail(memberEntity.getEmail());
|
||||
memberArchivedEntity.setStatus(memberEntity.getStatus());
|
||||
memberArchivedEntity.setCreatedDttm(memberEntity.getCreatedDttm());
|
||||
memberArchivedEntity.setArchivedDttm(ZonedDateTime.now());
|
||||
memberArchivedRepository.save(memberArchivedEntity);
|
||||
|
||||
memberEntity.setStatus("ARCHIVED");
|
||||
memberEntity.setName("**********");
|
||||
memberEntity.setEmployeeNo("**********");
|
||||
memberEntity.setPassword("**********");
|
||||
memberEntity.setEmail("**********");
|
||||
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
||||
membersRepository.save(memberEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 초기화
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
public void resetPassword(Long id) {
|
||||
MemberEntity memberEntity =
|
||||
membersRepository.findById(id).orElseThrow(() -> new MemberNotFoundException());
|
||||
|
||||
String salt =
|
||||
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());
|
||||
// 패스워드 암호화, 초기 패스워드 고정
|
||||
String hashedPassword = BCrypt.hashpw(password, salt);
|
||||
|
||||
memberEntity.setPassword(hashedPassword);
|
||||
memberEntity.setStatus("INACTIVE");
|
||||
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
||||
membersRepository.save(memberEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
public Page<MembersDto.Basic> findByMembers(MembersDto.SearchReq searchReq) {
|
||||
return membersRepository.findByMembers(searchReq);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MenuEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.menu.MenuRepository;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MenuCoreService {
|
||||
|
||||
private final MenuRepository menuRepository;
|
||||
|
||||
public List<MenuDto.Basic> getFindAll() {
|
||||
return menuRepository.getFindAll().stream().map(MenuEntity::toDto).toList();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AccessLevel;
|
||||
@@ -50,9 +51,6 @@ public class CommonCodeEntity extends CommonDateEntity {
|
||||
@Column(name = "used")
|
||||
private Boolean used;
|
||||
|
||||
@Column(name = "misc_cd")
|
||||
private String miscCd;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "deleted", nullable = false)
|
||||
private Boolean deleted = false;
|
||||
@@ -64,23 +62,38 @@ public class CommonCodeEntity extends CommonDateEntity {
|
||||
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
private List<CommonCodeEntity> children = new ArrayList<>();
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "props1")
|
||||
private String props1;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "props2")
|
||||
private String props2;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "props3")
|
||||
private String props3;
|
||||
|
||||
@Column(name = "deleted_dttm")
|
||||
private ZonedDateTime deletedDttm;
|
||||
|
||||
public CommonCodeEntity(
|
||||
String code, String name, String description, Integer order, Boolean used) {
|
||||
String code,
|
||||
String name,
|
||||
String description,
|
||||
Integer order,
|
||||
Boolean used,
|
||||
String props1,
|
||||
String props2,
|
||||
String props3) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.order = order;
|
||||
this.used = used;
|
||||
}
|
||||
|
||||
public CommonCodeEntity(
|
||||
Long id, String name, String description, Integer order, Boolean used, Boolean deleted) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.order = order;
|
||||
this.used = used;
|
||||
this.deleted = deleted;
|
||||
this.props1 = props1;
|
||||
this.props2 = props2;
|
||||
this.props3 = props3;
|
||||
}
|
||||
|
||||
public CommonCodeDto.Basic toDto() {
|
||||
@@ -94,7 +107,11 @@ public class CommonCodeEntity extends CommonDateEntity {
|
||||
this.deleted,
|
||||
this.children.stream().map(CommonCodeEntity::toDto).toList(),
|
||||
super.getCreatedDate(),
|
||||
super.getModifiedDate());
|
||||
super.getModifiedDate(),
|
||||
this.props1,
|
||||
this.props2,
|
||||
this.props3,
|
||||
this.deletedDttm);
|
||||
}
|
||||
|
||||
public void addParent(CommonCodeEntity parent) {
|
||||
@@ -107,9 +124,20 @@ public class CommonCodeEntity extends CommonDateEntity {
|
||||
|
||||
public void deleted() {
|
||||
this.deleted = true;
|
||||
this.deletedDttm = ZonedDateTime.now();
|
||||
}
|
||||
|
||||
public void updateOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public void update(
|
||||
String name, String description, boolean used, String props1, String props2, String props3) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.used = used;
|
||||
this.props1 = props1;
|
||||
this.props2 = props2;
|
||||
this.props3 = props3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ 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.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -20,6 +23,11 @@ import org.hibernate.type.SqlTypes;
|
||||
public class MapSheetLearnDataEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_sheet_learn_data_id_gen")
|
||||
@SequenceGenerator(
|
||||
name = "tb_map_sheet_learn_data_id_gen",
|
||||
sequenceName = "tb_map_sheet_learn_data_data_uid",
|
||||
allocationSize = 1)
|
||||
@Column(name = "data_uid", nullable = false)
|
||||
private Long id;
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ 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.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -17,6 +20,13 @@ import org.locationtech.jts.geom.Geometry;
|
||||
public class MapSheetLearnDataGeomEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(
|
||||
strategy = GenerationType.SEQUENCE,
|
||||
generator = "tb_map_sheet_learn_data_geom_id_gen")
|
||||
@SequenceGenerator(
|
||||
name = "tb_map_sheet_learn_data_geom_id_gen",
|
||||
sequenceName = "tb_map_sheet_learn_data_geom_geom_uid",
|
||||
allocationSize = 1)
|
||||
@Column(name = "geo_uid", nullable = false)
|
||||
private Long id;
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_map_sheet_mng")
|
||||
public class MapSheetMngEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "mng_yyyy", nullable = false)
|
||||
private Integer id;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "mng_state", length = 20)
|
||||
private String mngState;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "sync_state", length = 20)
|
||||
private String syncState;
|
||||
|
||||
@Column(name = "mng_state_dttm")
|
||||
private OffsetDateTime mngStateDttm;
|
||||
|
||||
@Column(name = "sync_state_dttm")
|
||||
private OffsetDateTime syncStateDttm;
|
||||
|
||||
@Column(name = "created_dttm")
|
||||
private OffsetDateTime createdDttm;
|
||||
|
||||
@Column(name = "created_uid")
|
||||
private Long createdUid;
|
||||
|
||||
@Column(name = "updated_dttm")
|
||||
private OffsetDateTime updatedDttm;
|
||||
|
||||
@Column(name = "updated_uid")
|
||||
private Long updatedUid;
|
||||
|
||||
@Size(max = 255)
|
||||
@ColumnDefault("'NULL::character varying'")
|
||||
@Column(name = "mng_path")
|
||||
private String mngPath;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
|
||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import jakarta.persistence.*;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_map_sheet_mng_hst")
|
||||
public class MapSheetMngHstEntity extends CommonDateEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "hst_uid")
|
||||
private Long hstUid;
|
||||
|
||||
@Column(name = "mng_yyyy")
|
||||
private Integer mngYyyy;
|
||||
|
||||
@Column(name = "map_sheet_code")
|
||||
private Integer mapSheetCode;
|
||||
|
||||
@Column(name = "map_sheet_num")
|
||||
private String mapSheetNum;
|
||||
|
||||
@Column(name = "map_sheet_name")
|
||||
private String mapSheetName;
|
||||
|
||||
@Column(name = "map_sheet_code_src")
|
||||
private Integer mapSheetCodeSrc;
|
||||
|
||||
@Column(name = "scale_ratio")
|
||||
private Integer scaleRatio;
|
||||
|
||||
@Column(name = "data_state")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private MapSheetMngDto.DataState dataState;
|
||||
|
||||
@Column(name = "data_state_dttm")
|
||||
private ZonedDateTime dataStateDttm;
|
||||
|
||||
@Column(name = "use_inference")
|
||||
private Boolean useInference;
|
||||
|
||||
@Column(name = "use_inference_dttm")
|
||||
private ZonedDateTime useInferenceDttm;
|
||||
|
||||
@Column(name = "map_sheet_path")
|
||||
private String mapSheetPath;
|
||||
|
||||
@Column(name = "ref_map_sheet_num")
|
||||
private Long refMapSheetNum;
|
||||
|
||||
@Column(name = "created_uid")
|
||||
private Long createdUid;
|
||||
|
||||
@Column(name = "updated_uid")
|
||||
private Long updatedUid;
|
||||
|
||||
public void updateDataState(MapSheetMngDto.DataState dataState) {
|
||||
this.dataState = dataState;
|
||||
this.dataStateDttm = ZonedDateTime.now();
|
||||
}
|
||||
|
||||
public void updateUseInference(Boolean useInference) {
|
||||
this.useInference = useInference;
|
||||
this.useInferenceDttm = ZonedDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_member")
|
||||
public class MemberEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "uuid", nullable = false, insertable = false)
|
||||
private UUID uuid;
|
||||
|
||||
@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)
|
||||
@Column(name = "email", length = 100)
|
||||
private String email;
|
||||
|
||||
@Size(max = 20)
|
||||
@ColumnDefault("'INACTIVE'")
|
||||
@Column(name = "status", length = 20)
|
||||
private String status = "INACTIVE";
|
||||
|
||||
@Column(name = "created_dttm", nullable = false, insertable = false)
|
||||
private ZonedDateTime createdDttm;
|
||||
|
||||
@Column(name = "updated_dttm", nullable = false, insertable = false)
|
||||
private ZonedDateTime updatedDttm;
|
||||
|
||||
@OneToMany(mappedBy = "memberUuid")
|
||||
private Set<MemberRoleEntity> tbMemberRoles = new LinkedHashSet<>();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EmbeddedId;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.hibernate.annotations.OnDelete;
|
||||
import org.hibernate.annotations.OnDeleteAction;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_member_role")
|
||||
public class MemberRoleEntity {
|
||||
|
||||
@EmbeddedId private MemberRoleEntityId id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||
@JoinColumn(
|
||||
name = "member_uuid",
|
||||
referencedColumnName = "uuid",
|
||||
insertable = false,
|
||||
updatable = false)
|
||||
private MemberEntity memberUuid;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "created_dttm")
|
||||
private ZonedDateTime createdDttm;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
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 MemberRoleEntityId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 9130416001060414347L;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "member_uuid", nullable = false)
|
||||
private UUID memberUuid;
|
||||
|
||||
@Size(max = 50)
|
||||
@NotNull
|
||||
@Column(name = "role_name", nullable = false, length = 50)
|
||||
private String roleName;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
|
||||
return false;
|
||||
}
|
||||
MemberRoleEntityId entity = (MemberRoleEntityId) o;
|
||||
return Objects.equals(this.memberUuid, entity.memberUuid)
|
||||
&& Objects.equals(this.roleName, entity.roleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(memberUuid, roleName);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -47,4 +48,24 @@ public class MenuEntity extends CommonDateEntity {
|
||||
|
||||
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
private List<MenuEntity> children = new ArrayList<>();
|
||||
|
||||
@Column(name = "menu_api_uri")
|
||||
private String menuApiUri;
|
||||
|
||||
public MenuDto.Basic toDto() {
|
||||
return new MenuDto.Basic(
|
||||
this.menuUid,
|
||||
this.menuNm,
|
||||
this.menuUrl,
|
||||
this.description,
|
||||
this.menuOrder,
|
||||
this.isUse,
|
||||
this.deleted,
|
||||
this.createdUid,
|
||||
this.updatedUid,
|
||||
this.children.stream().map(MenuEntity::toDto).toList(),
|
||||
this.getCreatedDate(),
|
||||
this.getModifiedDate(),
|
||||
this.menuApiUri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
@@ -12,6 +13,7 @@ import org.hibernate.annotations.ColumnDefault;
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "tb_model_mng")
|
||||
@NoArgsConstructor
|
||||
public class ModelMngEntity extends CommonDateEntity {
|
||||
|
||||
@Id
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@Table(
|
||||
name = "tb_user",
|
||||
uniqueConstraints = {@UniqueConstraint(name = "ux_tb_user_user_id", columnNames = "user_id")})
|
||||
public class UserEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_user_id_gen")
|
||||
@SequenceGenerator(
|
||||
name = "tb_user_id_gen",
|
||||
sequenceName = "tb_user_user_uid_seq",
|
||||
allocationSize = 1)
|
||||
@Column(name = "user_uid", nullable = false)
|
||||
private Long id;
|
||||
|
||||
@Size(max = 255)
|
||||
@NotNull
|
||||
@Column(name = "user_nm", nullable = false)
|
||||
private String userNm;
|
||||
|
||||
@Size(max = 255)
|
||||
@NotNull
|
||||
@Column(name = "user_id", nullable = false)
|
||||
private String userId;
|
||||
|
||||
@Size(max = 255)
|
||||
@NotNull
|
||||
@Column(name = "user_pw", nullable = false)
|
||||
private String userPw;
|
||||
|
||||
@Size(max = 255)
|
||||
@NotNull
|
||||
@ColumnDefault("'ACTIVE'")
|
||||
@Column(name = "state", nullable = false)
|
||||
private String state = "ACTIVE";
|
||||
|
||||
@Column(name = "date_withdrawal")
|
||||
private ZonedDateTime dateWithdrawal;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "created_dttm")
|
||||
private ZonedDateTime createdDttm;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "updated_dttm")
|
||||
private ZonedDateTime updatedDttm;
|
||||
|
||||
@Column(name = "created_uid")
|
||||
private Long createdUid;
|
||||
|
||||
@Column(name = "updated_uid")
|
||||
private Long updatedUid;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "user_email")
|
||||
private String userEmail;
|
||||
|
||||
@Size(max = 20)
|
||||
@NotNull
|
||||
@Column(name = "user_auth", nullable = false, length = 20)
|
||||
private String userAuth;
|
||||
|
||||
@Size(max = 255)
|
||||
@NotNull
|
||||
@Column(name = "emp_id", nullable = false)
|
||||
private String empId;
|
||||
|
||||
public UserEntity(
|
||||
Long id,
|
||||
String userAuth,
|
||||
String userNm,
|
||||
String userId,
|
||||
String empId,
|
||||
String userEmail,
|
||||
String userPw) {
|
||||
this.id = id;
|
||||
this.userAuth = userAuth;
|
||||
this.userNm = userNm;
|
||||
this.userId = userId;
|
||||
this.empId = empId;
|
||||
this.userEmail = userEmail;
|
||||
this.userPw = userPw;
|
||||
}
|
||||
|
||||
public AuthDto.Basic toDto() {
|
||||
return new AuthDto.Basic(
|
||||
this.id,
|
||||
this.userAuth,
|
||||
this.userNm,
|
||||
this.userId,
|
||||
this.empId,
|
||||
this.userEmail,
|
||||
this.createdDttm);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.auth;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface AuthRepository extends JpaRepository<UserEntity, Long>, AuthRepositoryCustom {}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.auth;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
public interface AuthRepositoryCustom {
|
||||
|
||||
Optional<UserEntity> findByUserId(String userId);
|
||||
|
||||
Optional<UserEntity> findUserById(Long id);
|
||||
|
||||
Page<Basic> getUserList(AuthDto.SearchReq searchReq);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.auth;
|
||||
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.QUserEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class AuthRepositoryImpl implements AuthRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final QUserEntity userEntity = QUserEntity.userEntity;
|
||||
|
||||
/**
|
||||
* 유저 아이디로 조회
|
||||
*
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Optional<UserEntity> findByUserId(String userId) {
|
||||
return Optional.ofNullable(
|
||||
queryFactory.selectFrom(userEntity).where(userEntity.userId.eq(userId)).fetchOne());
|
||||
}
|
||||
|
||||
/**
|
||||
* 유저 시퀀스 id로 조회
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Optional<UserEntity> findUserById(Long id) {
|
||||
return Optional.ofNullable(
|
||||
queryFactory.selectFrom(userEntity).where(userEntity.id.eq(id)).fetchOne());
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Page<Basic> getUserList(AuthDto.SearchReq searchReq) {
|
||||
Pageable pageable = searchReq.toPageable();
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
if (searchReq.getUserNm() != null && !searchReq.getUserNm().isEmpty()) {
|
||||
builder.and(likeName(userEntity, searchReq.getUserNm()));
|
||||
}
|
||||
|
||||
List<Basic> content =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
AuthDto.Basic.class,
|
||||
userEntity.id,
|
||||
userEntity.userAuth,
|
||||
userEntity.userNm,
|
||||
userEntity.userId,
|
||||
userEntity.empId,
|
||||
userEntity.userEmail,
|
||||
userEntity.createdDttm))
|
||||
.from(userEntity)
|
||||
.where(builder)
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(userEntity.userId.asc())
|
||||
.fetch();
|
||||
|
||||
long total = queryFactory.select(userEntity.id).from(userEntity).where(builder).fetchCount();
|
||||
return new PageImpl<>(content, pageable, total);
|
||||
}
|
||||
|
||||
private BooleanExpression likeName(QUserEntity entity, String nameStr) {
|
||||
if (nameStr == null || nameStr.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// ex) WHERE LOWER(name) LIKE LOWER('%입력값%')
|
||||
return entity.userNm.containsIgnoreCase(nameStr.trim());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.code;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -12,7 +12,7 @@ public interface CommonCodeRepositoryCustom {
|
||||
|
||||
List<CommonCodeEntity> findByAll();
|
||||
|
||||
void updateOrder(CommonCodeDto.OrderReq req);
|
||||
|
||||
Optional<String> getCode(String parentCodeCd, String childCodeCd);
|
||||
|
||||
Long findByParentIdCodeExists(Long parentId, @NotEmpty String code);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,12 @@ package com.kamco.cd.kamcoback.postgres.repository.code;
|
||||
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QCommonCodeEntity.commonCodeEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.OrderReqDetail;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.QCommonCodeEntity;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -29,7 +25,10 @@ public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom {
|
||||
.selectFrom(commonCodeEntity)
|
||||
.leftJoin(commonCodeEntity.children, child)
|
||||
.fetchJoin()
|
||||
.where(commonCodeEntity.id.eq(id), commonCodeEntity.deleted.isFalse())
|
||||
.where(
|
||||
commonCodeEntity.id.eq(id),
|
||||
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()),
|
||||
child.deleted.isFalse().or(child.deleted.isNull()))
|
||||
.orderBy(commonCodeEntity.order.asc(), child.order.asc())
|
||||
.fetchOne());
|
||||
}
|
||||
@@ -46,7 +45,8 @@ public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom {
|
||||
commonCodeEntity.parent.isNull(),
|
||||
commonCodeEntity.code.eq(code),
|
||||
commonCodeEntity.used.isTrue(),
|
||||
commonCodeEntity.deleted.isFalse())
|
||||
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()),
|
||||
child.deleted.isFalse().or(child.deleted.isNull()))
|
||||
.orderBy(child.order.asc())
|
||||
.fetchOne());
|
||||
}
|
||||
@@ -58,27 +58,30 @@ public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom {
|
||||
.selectFrom(commonCodeEntity)
|
||||
.leftJoin(commonCodeEntity.children, child)
|
||||
.fetchJoin()
|
||||
.where(commonCodeEntity.parent.isNull(), commonCodeEntity.deleted.isFalse())
|
||||
.where(
|
||||
commonCodeEntity.parent.isNull(),
|
||||
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()),
|
||||
child.deleted.isFalse().or(child.deleted.isNull()))
|
||||
.orderBy(commonCodeEntity.order.asc(), child.order.asc())
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOrder(CommonCodeDto.OrderReq req) {
|
||||
Map<Long, Integer> orderMap =
|
||||
req.getOrders().stream()
|
||||
.collect(Collectors.toMap(OrderReqDetail::getId, OrderReqDetail::getOrder));
|
||||
|
||||
List<CommonCodeEntity> entity = findAllByIds(orderMap.keySet());
|
||||
|
||||
entity.forEach(
|
||||
commonCodeEntity -> {
|
||||
Integer order = orderMap.get(commonCodeEntity.getId());
|
||||
if (order != null) {
|
||||
commonCodeEntity.updateOrder(order);
|
||||
}
|
||||
});
|
||||
}
|
||||
// @Override
|
||||
// public void updateOrder(CommonCodeDto.OrderReq req) {
|
||||
// Map<Long, Integer> orderMap =
|
||||
// req.getOrders().stream()
|
||||
// .collect(Collectors.toMap(OrderReqDetail::getId, OrderReqDetail::getOrder));
|
||||
//
|
||||
// List<CommonCodeEntity> entity = findAllByIds(orderMap.keySet());
|
||||
//
|
||||
// entity.forEach(
|
||||
// commonCodeEntity -> {
|
||||
// Integer order = orderMap.get(commonCodeEntity.getId());
|
||||
// if (order != null) {
|
||||
// commonCodeEntity.updateOrder(order);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
@Override
|
||||
public Optional<String> getCode(String parentCodeCd, String childCodeCd) {
|
||||
@@ -90,16 +93,29 @@ public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom {
|
||||
.select(child.name)
|
||||
.from(child)
|
||||
.join(child.parent, parent)
|
||||
.where(parent.code.eq(parentCodeCd).and(child.code.eq(childCodeCd)))
|
||||
.where(
|
||||
parent.code.eq(parentCodeCd).and(child.code.eq(childCodeCd)),
|
||||
child.deleted.isFalse().or(child.deleted.isNull()))
|
||||
.fetchFirst(); // 단일 결과만
|
||||
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long findByParentIdCodeExists(Long parentId, String code) {
|
||||
return queryFactory
|
||||
.select(commonCodeEntity.code.count())
|
||||
.from(commonCodeEntity)
|
||||
.where(commonCodeEntity.parent.id.eq(parentId), commonCodeEntity.code.eq(code))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
private List<CommonCodeEntity> findAllByIds(Set<Long> ids) {
|
||||
return queryFactory
|
||||
.selectFrom(commonCodeEntity)
|
||||
.where(commonCodeEntity.id.in(ids), commonCodeEntity.deleted.isFalse())
|
||||
.where(
|
||||
commonCodeEntity.id.in(ids),
|
||||
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()))
|
||||
.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.kamco.cd.kamcoback.postgres.repository.log;
|
||||
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QAuditLogEntity.auditLogEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QErrorLogEntity.errorLogEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMenuEntity.menuEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QUserEntity.userEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.log.dto.AuditLogDto;
|
||||
import com.kamco.cd.kamcoback.log.dto.ErrorLogDto;
|
||||
@@ -12,7 +12,11 @@ import com.kamco.cd.kamcoback.log.dto.EventType;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.QMenuEntity;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.*;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.core.types.dsl.CaseBuilder;
|
||||
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 io.micrometer.common.util.StringUtils;
|
||||
import java.time.LocalDate;
|
||||
@@ -27,6 +31,7 @@ import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
|
||||
|
||||
public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
implements AuditLogRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||
|
||||
@@ -121,18 +126,18 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
Projections.constructor(
|
||||
AuditLogDto.UserAuditList.class,
|
||||
auditLogEntity.userUid.as("accountId"),
|
||||
userEntity.userId.as("loginId"),
|
||||
userEntity.userNm.as("username"),
|
||||
memberEntity.employeeNo.as("loginId"),
|
||||
memberEntity.name.as("username"),
|
||||
readCount().as("readCount"),
|
||||
cudCount().as("cudCount"),
|
||||
printCount().as("printCount"),
|
||||
downloadCount().as("downloadCount"),
|
||||
auditLogEntity.count().as("totalCount")))
|
||||
.from(auditLogEntity)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(loginIdOrUsernameContains(searchValue))
|
||||
.groupBy(auditLogEntity.userUid, userEntity.userId, userEntity.userNm)
|
||||
.groupBy(auditLogEntity.userUid, memberEntity.employeeNo, memberEntity.name)
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
// .orderBy(auditLogEntity.eventEndedAt.max().desc())
|
||||
@@ -142,8 +147,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
queryFactory
|
||||
.select(auditLogEntity.userUid.countDistinct())
|
||||
.from(auditLogEntity)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(loginIdOrUsernameContains(searchValue))
|
||||
.fetchOne();
|
||||
|
||||
@@ -175,8 +180,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
Projections.constructor(
|
||||
AuditLogDto.DailyDetail.class,
|
||||
auditLogEntity.id.as("logId"),
|
||||
userEntity.userNm.as("userName"),
|
||||
userEntity.userId.as("loginId"),
|
||||
memberEntity.name.as("userName"),
|
||||
memberEntity.employeeNo.as("loginId"),
|
||||
menuEntity.menuNm.as("menuName"),
|
||||
auditLogEntity.eventType.as("eventType"),
|
||||
Projections.constructor(
|
||||
@@ -192,8 +197,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(menuEntity.parent, parent)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(eventEndedAtEqDate(logDate))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
@@ -207,8 +212,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(menuEntity.parent, parent)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(eventEndedAtEqDate(logDate))
|
||||
.fetchOne();
|
||||
|
||||
@@ -243,8 +248,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
Expressions.stringTemplate(
|
||||
"to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate)
|
||||
.as("logDateTime"), // ??
|
||||
userEntity.userNm.as("userName"),
|
||||
userEntity.userId.as("loginId"),
|
||||
memberEntity.name.as("userName"),
|
||||
memberEntity.employeeNo.as("loginId"),
|
||||
auditLogEntity.eventType.as("eventType"),
|
||||
Projections.constructor(
|
||||
AuditLogDto.LogDetail.class,
|
||||
@@ -259,8 +264,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(menuEntity.parent, parent)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(menuUidEq(menuUid))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
@@ -274,8 +279,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(menuEntity.parent, parent)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(menuUidEq(menuUid))
|
||||
.fetchOne();
|
||||
|
||||
@@ -325,8 +330,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(menuEntity.parent, parent)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(userUidEq(userUid))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
@@ -340,8 +345,8 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(menuEntity.parent, parent)
|
||||
.leftJoin(userEntity)
|
||||
.on(auditLogEntity.userUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(auditLogEntity.userUid.eq(memberEntity.id))
|
||||
.where(userUidEq(userUid))
|
||||
.fetchOne();
|
||||
|
||||
@@ -371,7 +376,10 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
if (StringUtils.isBlank(searchValue)) {
|
||||
return null;
|
||||
}
|
||||
return userEntity.userId.contains(searchValue).or(userEntity.userNm.contains(searchValue));
|
||||
return memberEntity
|
||||
.employeeNo
|
||||
.contains(searchValue)
|
||||
.or(memberEntity.name.contains(searchValue));
|
||||
}
|
||||
|
||||
private BooleanExpression eventStatusEqFailed() {
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.kamco.cd.kamcoback.postgres.repository.log;
|
||||
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QAuditLogEntity.auditLogEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QErrorLogEntity.errorLogEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMenuEntity.menuEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QUserEntity.userEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.log.dto.ErrorLogDto;
|
||||
import com.kamco.cd.kamcoback.log.dto.EventStatus;
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
|
||||
|
||||
public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
implements ErrorLogRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||
|
||||
@@ -45,8 +46,8 @@ public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
errorLogEntity.id.as("logId"),
|
||||
Expressions.stringTemplate("{0}", "한국자산관리공사"), // serviceName
|
||||
menuEntity.menuNm.as("menuName"),
|
||||
userEntity.userId.as("loginId"),
|
||||
userEntity.userNm.as("userName"),
|
||||
memberEntity.employeeNo.as("loginId"),
|
||||
memberEntity.name.as("userName"),
|
||||
errorLogEntity.errorType.as("eventType"),
|
||||
errorLogEntity.errorMessage.as(
|
||||
"errorName"), // 기존에는 errorName 값이 있었는데 신규 테이블에는 없음. 에러 메세지와 동일
|
||||
@@ -61,8 +62,8 @@ public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.on(errorLogEntity.id.eq(auditLogEntity.errorLogUid))
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(userEntity)
|
||||
.on(errorLogEntity.handlerUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(errorLogEntity.handlerUid.eq(memberEntity.id))
|
||||
.where(
|
||||
eventStatusEqFailed(),
|
||||
eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()),
|
||||
@@ -81,8 +82,8 @@ public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport
|
||||
.on(errorLogEntity.id.eq(auditLogEntity.errorLogUid))
|
||||
.leftJoin(menuEntity)
|
||||
.on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
|
||||
.leftJoin(userEntity)
|
||||
.on(errorLogEntity.handlerUid.eq(userEntity.id))
|
||||
.leftJoin(memberEntity)
|
||||
.on(errorLogEntity.handlerUid.eq(memberEntity.id))
|
||||
.where(
|
||||
eventStatusEqFailed(),
|
||||
eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MapSheetMngFileCheckerRepository
|
||||
extends JpaRepository<MapSheetMngEntity, Long>, MapSheetMngFileCheckerRepositoryCustom {}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.ImageryDto;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
public interface MapSheetMngFileCheckerRepositoryCustom {
|
||||
|
||||
Page<ImageryDto.SyncDto> findImagerySyncList(ImageryDto.@Valid searchReq searchReq);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
|
||||
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QImageryEntity.imageryEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.ImageryDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.Projections;
|
||||
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.validation.Valid;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
|
||||
|
||||
public class MapSheetMngFileCheckerRepositoryImpl extends QuerydslRepositorySupport
|
||||
implements MapSheetMngFileCheckerRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||
|
||||
public MapSheetMngFileCheckerRepositoryImpl(JPAQueryFactory queryFactory) {
|
||||
super(MapSheetMngHstEntity.class);
|
||||
this.queryFactory = queryFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ImageryDto.SyncDto> findImagerySyncList(ImageryDto.@Valid searchReq searchReq) {
|
||||
|
||||
Pageable pageable = searchReq.toPageable();
|
||||
BooleanBuilder whereBuilder = new BooleanBuilder();
|
||||
|
||||
if (searchReq.getMngYyyy() != null) {
|
||||
whereBuilder.and(imageryEntity.year.eq(searchReq.getMngYyyy()));
|
||||
}
|
||||
|
||||
List<ImageryDto.SyncDto> foundContent =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
ImageryDto.SyncDto.class,
|
||||
imageryEntity.id,
|
||||
imageryEntity.year,
|
||||
imageryEntity.scene50k,
|
||||
imageryEntity.scene5k,
|
||||
imageryEntity.middlePath,
|
||||
imageryEntity.cogMiddlePath,
|
||||
imageryEntity.filename,
|
||||
imageryEntity.cogFilename,
|
||||
mapSheetMngHstEntity.hstUid))
|
||||
.from(imageryEntity)
|
||||
.leftJoin(mapSheetMngHstEntity)
|
||||
.on(
|
||||
imageryEntity
|
||||
.year
|
||||
.eq(mapSheetMngHstEntity.mngYyyy)
|
||||
.and(imageryEntity.scene5k.eq(mapSheetMngHstEntity.mapSheetNum.stringValue())))
|
||||
.where(whereBuilder)
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
// .orderBy(mapSheetMngEntity.createdDttm.desc())
|
||||
.fetch();
|
||||
|
||||
Long countQuery =
|
||||
queryFactory
|
||||
.select(imageryEntity.id.count())
|
||||
.from(imageryEntity)
|
||||
.where(whereBuilder)
|
||||
.fetchOne();
|
||||
|
||||
return new PageImpl<>(foundContent, pageable, countQuery);
|
||||
}
|
||||
|
||||
private NumberExpression<Integer> rowNum() {
|
||||
return Expressions.numberTemplate(
|
||||
Integer.class, "row_number() over(order by {0} desc)", mapSheetMngHstEntity.createdDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MapSheetMngRepository
|
||||
extends JpaRepository<MapSheetMngEntity, Long>, MapSheetMngRepositoryCustom {}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Optional;
|
||||
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);
|
||||
|
||||
Optional<MapSheetMngHstEntity> findMapSheetMngHstInfo(Long hstUid);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
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.QMapSheetMngHstEntity.mapSheetMngHstEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
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.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
|
||||
|
||||
public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
|
||||
implements MapSheetMngRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||
|
||||
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) {
|
||||
|
||||
Pageable pageable = searchReq.toPageable();
|
||||
BooleanBuilder whereBuilder = new BooleanBuilder();
|
||||
|
||||
if (searchReq.getMngYyyy() != null) {
|
||||
whereBuilder.and(mapSheetMngEntity.id.eq(searchReq.getMngYyyy()));
|
||||
}
|
||||
|
||||
List<MapSheetMngDto.MngDto> foundContent =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
MapSheetMngDto.MngDto.class,
|
||||
Expressions.numberTemplate(
|
||||
Integer.class,
|
||||
"row_number() over(order by {0} desc)",
|
||||
mapSheetMngEntity.createdDttm),
|
||||
mapSheetMngEntity.id,
|
||||
mapSheetMngEntity.mngState,
|
||||
mapSheetMngEntity.syncState,
|
||||
Expressions.stringTemplate(
|
||||
"to_char({0}, 'YYYY-MM-DD HH24:MI:SS')", mapSheetMngEntity.mngStateDttm),
|
||||
Expressions.stringTemplate(
|
||||
"to_char({0}, 'YYYY-MM-DD HH24:MI:SS')", mapSheetMngEntity.syncStateDttm),
|
||||
mapSheetMngEntity.mngPath,
|
||||
Expressions.stringTemplate(
|
||||
"to_char({0}, 'YYYY-MM-DD HH24:MI:SS')", mapSheetMngEntity.createdDttm),
|
||||
mapSheetMngEntity.createdUid,
|
||||
Expressions.stringTemplate(
|
||||
"to_char({0}, 'YYYY-MM-DD HH24:MI:SS')", mapSheetMngEntity.updatedDttm),
|
||||
mapSheetMngEntity.updatedUid))
|
||||
.from(mapSheetMngEntity)
|
||||
.where(whereBuilder)
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(mapSheetMngEntity.createdDttm.desc())
|
||||
.fetch();
|
||||
|
||||
Long countQuery =
|
||||
queryFactory
|
||||
.select(mapSheetMngEntity.id.count())
|
||||
.from(mapSheetMngEntity)
|
||||
.where(whereBuilder)
|
||||
.fetchOne();
|
||||
|
||||
return new PageImpl<>(foundContent, pageable, countQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MapSheetMngHstEntity> findMapSheetMngHstInfo(Long hstUid) {
|
||||
return Optional.ofNullable(
|
||||
queryFactory
|
||||
.selectFrom(mapSheetMngHstEntity)
|
||||
.where(mapSheetMngHstEntity.hstUid.eq(hstUid))
|
||||
.fetchOne());
|
||||
}
|
||||
|
||||
private NumberExpression<Integer> rowNum() {
|
||||
return Expressions.numberTemplate(
|
||||
Integer.class, "row_number() over(order by {0} desc)", mapSheetMngHstEntity.createdDate);
|
||||
}
|
||||
|
||||
private BooleanExpression mapSheetErrorSearchValue(MapSheetMngDto.searchReq searchReq) {
|
||||
if (Objects.isNull(searchReq.getSearchValue())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 검색어 1개 값이 도엽명 or 도엽번호 like 검색
|
||||
return Expressions.booleanTemplate(
|
||||
"{0} like '%" + searchReq.getSearchValue() + "%'", mapSheetMngHstEntity.mapSheetName)
|
||||
.or(
|
||||
Expressions.booleanTemplate(
|
||||
"{0} like '%" + searchReq.getSearchValue() + "%'",
|
||||
mapSheetMngHstEntity.mapSheetNum));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
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 {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
public interface MembersArchivedRepositoryCustom {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class MembersArchivedRepositoryImpl implements MembersArchivedRepositoryCustom {}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MembersRepository
|
||||
extends JpaRepository<MemberEntity, Long>, MembersRepositoryCustom {}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
public interface MembersRepositoryCustom {
|
||||
|
||||
boolean existsByEmployeeNo(String employeeNo);
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
Page<Basic> findByMembers(MembersDto.SearchReq searchReq);
|
||||
|
||||
Optional<MemberEntity> findByUUID(UUID uuid);
|
||||
|
||||
Optional<MemberEntity> findByEmployeeNo(String employeeNo);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.enums.RoleType;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.QMemberRoleEntity;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final QMemberEntity memberEntity = QMemberEntity.memberEntity;
|
||||
private final QMemberRoleEntity memberRoleEntity = QMemberRoleEntity.memberRoleEntity;
|
||||
|
||||
/**
|
||||
* 사원번호 조회
|
||||
*
|
||||
* @param employeeNo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean existsByEmployeeNo(String employeeNo) {
|
||||
return queryFactory
|
||||
.selectOne()
|
||||
.from(memberEntity)
|
||||
.where(memberEntity.employeeNo.eq(employeeNo))
|
||||
.fetchFirst()
|
||||
!= null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일 조회
|
||||
*
|
||||
* @param email
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean existsByEmail(String email) {
|
||||
return queryFactory
|
||||
.selectOne()
|
||||
.from(memberEntity)
|
||||
.where(memberEntity.email.eq(email))
|
||||
.fetchFirst()
|
||||
!= null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원정보 목록 조회
|
||||
*
|
||||
* @param searchReq
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Page<Basic> findByMembers(MembersDto.SearchReq searchReq) {
|
||||
Pageable pageable = searchReq.toPageable();
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
BooleanBuilder leftBuilder = new BooleanBuilder();
|
||||
|
||||
if (StringUtils.isNotBlank(searchReq.getField())) {
|
||||
switch (searchReq.getField()) {
|
||||
case "name" ->
|
||||
builder.and(memberEntity.name.containsIgnoreCase(searchReq.getKeyword().trim()));
|
||||
case "email" ->
|
||||
builder.and(memberEntity.email.containsIgnoreCase(searchReq.getKeyword().trim()));
|
||||
case "employeeNo" ->
|
||||
builder.and(memberEntity.employeeNo.containsIgnoreCase(searchReq.getKeyword().trim()));
|
||||
}
|
||||
}
|
||||
|
||||
List<String> roles = new ArrayList<>();
|
||||
// 라벨러
|
||||
if (searchReq.isLabeler()) {
|
||||
roles.add(RoleType.ROLE_LABELER.getId());
|
||||
}
|
||||
|
||||
// 시스템 전체 관리자
|
||||
if (searchReq.isAdmin()) {
|
||||
roles.add(RoleType.ROLE_ADMIN.getId());
|
||||
}
|
||||
|
||||
// 검수자
|
||||
if (searchReq.isReviewer()) {
|
||||
roles.add(RoleType.ROLE_REVIEWER.getId());
|
||||
}
|
||||
|
||||
// 역할 in 조건 추가
|
||||
if (!roles.isEmpty()) {
|
||||
leftBuilder.and(memberRoleEntity.id.roleName.in(roles));
|
||||
}
|
||||
|
||||
List<MembersDto.Basic> content =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
MembersDto.Basic.class,
|
||||
memberEntity.id,
|
||||
memberEntity.uuid,
|
||||
memberEntity.employeeNo,
|
||||
memberEntity.name,
|
||||
memberEntity.email,
|
||||
memberEntity.status,
|
||||
memberRoleEntity.id.roleName,
|
||||
memberEntity.createdDttm,
|
||||
memberEntity.updatedDttm))
|
||||
.from(memberEntity)
|
||||
.leftJoin(memberRoleEntity)
|
||||
.on(memberRoleEntity.memberUuid.uuid.eq(memberEntity.uuid).and(leftBuilder))
|
||||
.where(builder)
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(memberEntity.createdDttm.desc())
|
||||
.fetch();
|
||||
|
||||
long total =
|
||||
queryFactory
|
||||
.select(memberEntity)
|
||||
.from(memberEntity)
|
||||
.leftJoin(memberRoleEntity)
|
||||
.on(memberRoleEntity.memberUuid.uuid.eq(memberEntity.uuid).and(leftBuilder))
|
||||
.fetchCount();
|
||||
|
||||
return new PageImpl<>(content, pageable, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MemberEntity> findByUUID(UUID uuid) {
|
||||
return Optional.ofNullable(
|
||||
queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MemberEntity> findByEmployeeNo(String employeeNo) {
|
||||
return Optional.ofNullable(
|
||||
queryFactory
|
||||
.selectFrom(memberEntity)
|
||||
.where(memberEntity.employeeNo.eq(employeeNo))
|
||||
.fetchOne());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MemberRoleEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MembersRoleRepository
|
||||
extends JpaRepository<MemberRoleEntity, Long>, MembersRoleRepositoryCutom {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
|
||||
public interface MembersRoleRepositoryCutom {
|
||||
|
||||
boolean findByUuidAndRoleName(MembersDto.RolesDto rolesDto);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||
|
||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.QMemberRoleEntity;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Repository
|
||||
public class MembersRoleRepositoryImpl implements MembersRoleRepositoryCutom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final QMemberRoleEntity memberRoleEntity = QMemberRoleEntity.memberRoleEntity;
|
||||
|
||||
@Override
|
||||
public boolean findByUuidAndRoleName(MembersDto.RolesDto rolesDto) {
|
||||
return queryFactory
|
||||
.select(memberRoleEntity)
|
||||
.from(memberRoleEntity)
|
||||
.where(
|
||||
memberRoleEntity
|
||||
.id
|
||||
.memberUuid
|
||||
.eq(rolesDto.getUuid())
|
||||
.and(memberRoleEntity.id.roleName.eq(rolesDto.getRoleName())))
|
||||
.fetchOne()
|
||||
!= null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.menu;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MenuEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MenuRepository extends JpaRepository<MenuEntity, String>, MenuRepositoryCustom {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user