diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index f5d1d319..2239f9e3 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -8,6 +8,7 @@ object Dependencies { const val SPRING_BOOT_STARTER_WEB = "org.springframework.boot:spring-boot-starter-web" const val SPRING_BOOT_STARTER_TEST = "org.springframework.boot:spring-boot-starter-test" const val SPRING_BOOT_STARTER_ACTUATOR = "org.springframework.boot:spring-boot-starter-actuator" + const val SPRING_CONTEXT = "org.springframework:spring-context" //jexl const val APACHE_COMMONS_JEXL = "org.apache.commons:commons-jexl3:${DependencyVersions.APACHE_COMMONS_JEXL_VERSION}" @@ -35,6 +36,12 @@ object Dependencies { //commons io const val COMMONS_IO = "commons-io:commons-io:${DependencyVersions.COMMONS_IO}" + // Feign Client + const val OPEN_FEIGN = "org.springframework.cloud:spring-cloud-starter-openfeign:${DependencyVersions.OPEN_FEIGN_VERSION}" + + // Spring Cloud BOM + const val SPRING_CLOUD = "org.springframework.cloud:spring-cloud-dependencies:${DependencyVersions.SPRING_CLOUD_VERSION}" + // WebFlux const val WEB_FLUX = "org.springframework.boot:spring-boot-starter-webflux" @@ -82,5 +89,13 @@ object Dependencies { const val SPRING_BOOT_STARTER_DATA_JPA = "org.springframework.boot:spring-boot-starter-data-jpa" // transaction - const val SPRING_TRANSACTION = "org.springframework:spring-tx:${DependencyVersions.SPRING_TRANSACTION}" -} \ No newline at end of file + const val SPRING_TRANSACTION = "org.springframework:spring-tx" + + //spring cache + // Redis (캐시) + const val REDIS = "org.springframework.boot:spring-boot-starter-data-redis" + + // Cache (스프링 캐시) + const val SPRING_CACHE = "org.springframework.boot:spring-boot-starter-cache" + +} diff --git a/buildSrc/src/main/kotlin/DependencyVersions.kt b/buildSrc/src/main/kotlin/DependencyVersions.kt index aa5a58b8..0218932e 100644 --- a/buildSrc/src/main/kotlin/DependencyVersions.kt +++ b/buildSrc/src/main/kotlin/DependencyVersions.kt @@ -1,31 +1,47 @@ object DependencyVersions { // JEXL const val APACHE_COMMONS_JEXL_VERSION = "3.5.0" - + // Kotlinx Serialization const val KOTLINX_SERIALIZATION_VERSION = "1.6.3" - + // Kotlinx Coroutines const val KOTLINX_COROUTINES_VERSION = "1.8.1" + // PDF const val PDF_ITEXT = "7.2.0" const val PDF_HTML = "3.0.3" + + // Apache POI const val POI_VERSION = "5.2.3" + + // Commons IO const val COMMONS_IO = "2.11.0" - const val GRPC = "1.61.1" - const val GRPC_KOTLIN = "1.4.1" - const val PROTOBUF = "3.25.3" - const val GRPC_CLIENT = "2.15.0.RELEASE" - const val GOOGLE_PROTOBUF = "3.25.3" + // Spring Cloud (Feign 포함) + const val SPRING_CLOUD_VERSION = "2024.0.0" + const val OPEN_FEIGN_VERSION = "3.1.4" + + // gRPC + const val GRPC = "1.58.0" // 1.61.1에서 변경 + const val GRPC_KOTLIN = "1.4.0" // 1.4.1에서 변경 + const val GRPC_CLIENT = "2.15.0.RELEASE" // 유지 + const val PROTOBUF = "3.24.0" // 3.25.3에서 변경 + const val GOOGLE_PROTOBUF = "3.24.0" // 3.25.3에서 변경 + // Coroutines (main 쪽 버전, KOTLINX_COROUTINES_VERSION 과 구분됨) const val COROUTINES = "1.8.0" + + // MapStruct const val MAPSTRUCT = "1.6.0" + // QueryDSL const val QUERYDSL = "5.0.0" + + // Jakarta const val JAKARTA_PERSISTENCE = "3.1.0" const val JAKARTA_ANNOTATION = "2.1.1" - const val CAFFEINE = "3.1.8" - const val SPRING_TRANSACTION = "5.3.22" -} \ No newline at end of file + // Caffeine + const val CAFFEINE = "3.1.8" +} diff --git a/buildSrc/src/main/kotlin/Plugins.kt b/buildSrc/src/main/kotlin/Plugins.kt index c06bb982..0bf2e97d 100644 --- a/buildSrc/src/main/kotlin/Plugins.kt +++ b/buildSrc/src/main/kotlin/Plugins.kt @@ -8,7 +8,12 @@ object Plugins { const val SPRING_DEPENDENCY_MANAGEMENT = "io.spring.dependency-management" const val KTLINT = "org.jlleitschuh.gradle.ktlint" const val CASPER_CONVENTION = "casper.documentation-convention" + + // feature/28-school-info + const val KOTLIN_ALL_OPEN = "org.jetbrains.kotlin.plugin.spring" + + // main 브랜치 const val KAPT = "kapt" const val PROTOBUF = "com.google.protobuf" const val ALL_OPEN = "plugin.allopen" -} \ No newline at end of file +} diff --git a/casper-application-domain/build.gradle.kts b/casper-application-domain/build.gradle.kts index e5758b8d..180a4c6e 100644 --- a/casper-application-domain/build.gradle.kts +++ b/casper-application-domain/build.gradle.kts @@ -1,11 +1,13 @@ plugins { kotlin(Plugins.KOTLIN_JVM) version PluginVersions.KOTLIN_VERSION kotlin(Plugins.KOTLIN_SERIALIZATION) version PluginVersions.KOTLIN_VERSION + id(Plugins.KOTLIN_ALL_OPEN) version PluginVersions.KOTLIN_VERSION } version = Projects.APPLICATION_DOMAIN_VERSION dependencies { + implementation(Dependencies.KOTLINX_SERIALIZATION_JSON) implementation(Dependencies.KOTLINX_COROUTINES_CORE) diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/aggregate/School.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/aggregate/School.kt new file mode 100644 index 00000000..6d9c6605 --- /dev/null +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/aggregate/School.kt @@ -0,0 +1,28 @@ +package hs.kr.entrydsm.domain.school.aggregate + +import hs.kr.entrydsm.domain.school.exception.SchoolException + +/** + * 학교 정보를 담는 데이터 클래스 입니다. + * + * @property code 학교 코드 + * @property name 학교 이름 + * @property tel 학교 전화번호 + * @property type 학교 타입 + * @property address 학교 주소 + * @property regionName 지역 이름 + */ +data class School( + val code: String, + val name: String, + val tel: String, + val type: String, + val address: String, + val regionName: String +) { + init { + check(type == "중학교") { + throw SchoolException.InvalidSchoolTypeException(type) + } + } +} diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/dto/QuerySchoolResponse.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/dto/QuerySchoolResponse.kt new file mode 100644 index 00000000..53acb6a8 --- /dev/null +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/dto/QuerySchoolResponse.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.domain.school.dto + +/** + * 학교 검색 결과를 담는 데이터 클래스 입니다. + * + * @property content 학교 검색 결과 리스트 + */ +data class QuerySchoolResponse( + val content: List +) diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/dto/SchoolResponse.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/dto/SchoolResponse.kt new file mode 100644 index 00000000..0bcdf429 --- /dev/null +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/dto/SchoolResponse.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.domain.school.dto + +/** + * 학교 정보를 담는 데이터 클래스 입니다. + * + * @property code 학교 코드 + * @property name 학교 이름 + * @property information 학교 정보 + * @property address 학교 주소 + */ +data class SchoolResponse( + val code: String, + val name: String, + val information: String, + val address: String +) diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/exception/SchoolException.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/exception/SchoolException.kt new file mode 100644 index 00000000..d523fdf2 --- /dev/null +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/exception/SchoolException.kt @@ -0,0 +1,22 @@ +package hs.kr.entrydsm.domain.school.exception + +import hs.kr.entrydsm.global.exception.DomainException +import hs.kr.entrydsm.global.exception.ErrorCode + +/** + * 학교 관련 최상위 예외 클래스 입니다. + */ +sealed class SchoolException( + errorCode: ErrorCode, // override 제거 + message: String +) : DomainException(errorCode, message) { + + + /** + * 유효하지 않은 학교 타입일 경우 발생하는 예외입니다. + */ + class InvalidSchoolTypeException(schoolType: String) : SchoolException( + errorCode = ErrorCode.SCHOOL_INVALID_TYPE, + message = "Invalid school type: $schoolType" + ) +} \ No newline at end of file diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/QuerySchoolContract.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/QuerySchoolContract.kt new file mode 100644 index 00000000..231517db --- /dev/null +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/QuerySchoolContract.kt @@ -0,0 +1,24 @@ +package hs.kr.entrydsm.domain.school.interfaces + +import hs.kr.entrydsm.domain.school.aggregate.School + +/** + * 학교 정보를 조회하는 Contract 입니다. + */ +interface QuerySchoolContract { + /** + * 학교 이름으로 학교 리스트를 조회합니다. + * + * @param schoolName 학교 이름 + * @return 학교 리스트 + */ + fun querySchoolListBySchoolName(schoolName: String): List + + /** + * 학교 코드로 학교를 조회합니다. + * + * @param schoolCode 학교 코드 + * @return 학교 정보 + */ + fun querySchoolBySchoolCode(schoolCode: String): School? +} \ No newline at end of file diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/QuerySchoolUseCaseContract.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/QuerySchoolUseCaseContract.kt new file mode 100644 index 00000000..d4fd4415 --- /dev/null +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/QuerySchoolUseCaseContract.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.domain.school.interfaces + +import hs.kr.entrydsm.domain.school.dto.QuerySchoolResponse + +/** + * 학교 정보를 조회하는 UseCase Contract 입니다. + */ +interface QuerySchoolUseCaseContract { + /** + * 학교 이름으로 학교를 조회합니다. + * + * @param name 학교 이름 + * @return 학교 검색 결과 + */ + fun querySchool(name: String): QuerySchoolResponse +} \ No newline at end of file diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/SchoolContract.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/SchoolContract.kt new file mode 100644 index 00000000..2db96bb6 --- /dev/null +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/school/interfaces/SchoolContract.kt @@ -0,0 +1,7 @@ +package hs.kr.entrydsm.domain.school.interfaces + +/** + * School 도메인의 Persistence를 관리하는 Contract 입니다. + */ +interface SchoolContract : QuerySchoolContract { +} \ No newline at end of file diff --git a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/global/exception/ErrorCode.kt b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/global/exception/ErrorCode.kt index fc04eee7..c804abde 100644 --- a/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/global/exception/ErrorCode.kt +++ b/casper-application-domain/src/main/kotlin/hs/kr/entrydsm/global/exception/ErrorCode.kt @@ -392,8 +392,14 @@ enum class ErrorCode(val code: String, val description: String) { ENTITY_CONTRACT_NOT_IMPLEMENTED("ANT002", "EntityCont,ract 미구현"), INVALID_AGGREGATE_ROOT("ANT003", "유효하지 않은 Aggregate Root"), - // ExamCode 오류 (EXP) - FAILED_GEOCODE_CONVERSION("EXA001", "주소를 변환할 수 없습니다."); + // School 도메인 오류 (SCH) + SCHOOL_INVALID_TYPE("SCH001", "유효하지 않은 학교 유형입니다"), + + //feign error + FEIGN_SERVER_ERROR("FGN001", "외부 API 서버 오류가 발생했습니다"), + + // ExamCode 오류 (EXA) + FAILED_GEOCODE_CONVERSION("EXA001", "주소를 변환할 수 없습니다"); /** * 오류 코드의 도메인 접두사를 반환합니다. diff --git a/casper-application-infrastructure/build.gradle.kts b/casper-application-infrastructure/build.gradle.kts index 6a340d86..ad2be732 100644 --- a/casper-application-infrastructure/build.gradle.kts +++ b/casper-application-infrastructure/build.gradle.kts @@ -14,64 +14,80 @@ repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom(Dependencies.SPRING_CLOUD) + } +} + dependencies { + implementation(project(":casper-application-domain")) + + // Spring Boot implementation(Dependencies.SPRING_BOOT_STARTER) implementation(Dependencies.SPRING_BOOT_STARTER_WEB) - implementation(Dependencies.SPRING_BOOT_STARTER_TEST) implementation(Dependencies.SPRING_BOOT_STARTER_ACTUATOR) + implementation(Dependencies.SPRING_BOOT_STARTER_TEST) implementation(Dependencies.SPRING_BOOT_STARTER_DATA_JPA) + implementation(Dependencies.SPRING_CACHE) - implementation(Dependencies.APACHE_COMMONS_JEXL) + //redis + implementation(Dependencies.REDIS) + // Kotlin implementation(Dependencies.KOTLIN_REFLECT) testImplementation(Dependencies.KOTLIN_TEST) - // itext + // Utilities + implementation(Dependencies.APACHE_COMMONS_JEXL) + implementation(Dependencies.COMMONS_IO) + + // PDF / Template implementation(Dependencies.PDF_HTML) - implementation (Dependencies.THYMELEAF) + implementation(Dependencies.THYMELEAF) - //read-file - implementation(Dependencies.COMMONS_IO) + // Excel implementation(Dependencies.POI) implementation(Dependencies.POI_OOXML) - // grpc + // Feign + implementation(Dependencies.OPEN_FEIGN) + + // Jackson + implementation(Dependencies.JACKSON_MODULE_KOTLIN) + + // gRPC implementation(Dependencies.GRPC_NETTY_SHADED) implementation(Dependencies.GRPC_PROTOBUF) implementation(Dependencies.GRPC_STUB) implementation(Dependencies.GRPC_KOTLIN_STUB) implementation(Dependencies.PROTOBUF_KOTLIN) - testImplementation(Dependencies.GRPC_TESTING) implementation(Dependencies.GRPC_CLIENT) + testImplementation(Dependencies.GRPC_TESTING) implementation(Dependencies.GOOGLE_PROTOBUF) - // coroutines + // Coroutines implementation(Dependencies.COROUTINES) + implementation(Dependencies.COROUTINES_REACTOR) - // mapstruct + // MapStruct implementation(Dependencies.MAPSTRUCT) kapt(Dependencies.MAPSTRUCT_PROCESSOR) + // QueryDSL implementation(Dependencies.QUERYDSL_JPA) kapt(Dependencies.QUERYDSL_APT) kapt(Dependencies.JAKARTA_PERSISTENCE_API) kapt(Dependencies.JAKARTA_ANNOTATION_API) - // web flux + // WebFlux implementation(Dependencies.WEB_FLUX) - // mysql - runtimeOnly(Dependencies.MYSQL_CONNECTOR) - - // cache / jackson / reactor + // Cache implementation(Dependencies.CAFFEINE) - implementation(Dependencies.JACKSON_MODULE_KOTLIN) - implementation(Dependencies.COROUTINES_REACTOR) - runtimeOnly(Dependencies.REACTOR_NETTY) - implementation(Dependencies.SPRING_TRANSACTION) - - implementation(project(":casper-application-domain")) + // MySQL + runtimeOnly(Dependencies.MYSQL_CONNECTOR) } protobuf { @@ -127,7 +143,6 @@ kapt { ktlint { ignoreFailures.set(true) filter { - // 넓게 막습니다: build 전부 + generated 전부 exclude("**/build/**") exclude("**/generated/**") } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/SchoolPersistenceAdapter.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/SchoolPersistenceAdapter.kt new file mode 100644 index 00000000..1e96c521 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/SchoolPersistenceAdapter.kt @@ -0,0 +1,108 @@ +package hs.kr.entrydsm.application.domain.school.domain + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import hs.kr.entrydsm.application.domain.school.domain.entity.SchoolCacheRedisEntity +import hs.kr.entrydsm.application.domain.school.domain.repository.SchoolCacheRepository +import hs.kr.entrydsm.application.global.feign.client.SchoolClient +import hs.kr.entrydsm.application.global.feign.client.dto.SchoolInfoElement +import hs.kr.entrydsm.domain.school.aggregate.School +import hs.kr.entrydsm.domain.school.interfaces.SchoolContract +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component + +/** + * School 도메인의 영속성을 관리하는 Adapter 입니다. + */ +@Component +class SchoolPersistenceAdapter( + private val schoolClient: SchoolClient, + private val schoolCacheRepository: SchoolCacheRepository, +) : SchoolContract { + @Value("\${neis.key}") + lateinit var apiKey: String + + /** + * 학교 코드로 학교를 조회합니다. + * + * @param schoolCode 학교 코드 + * @return 학교 정보 + */ + override fun querySchoolBySchoolCode(schoolCode: String): School? { + if (schoolCacheRepository.existsById(schoolCode)) { + val schoolCache = schoolCacheRepository.findById(schoolCode).get() + schoolCache.run { + return School( + code = code, + name = name, + tel = tel, + type = type, + address = address, + regionName = regionName, + ) + } + } + + val school = + schoolClient.getSchoolBySchoolCode(schoolCode = schoolCode, key = apiKey)?.let { response -> + val mapper = ObjectMapper().registerKotlinModule() + val responseObject = mapper.readValue(response) + responseObject.schoolInfo?.getOrNull(1)?.row?.map { + School( + code = it.sdSchulCode, + name = it.schulNm, + tel = it.orgTelno, + type = it.schulKndScNm, + address = it.orgRdnma, + regionName = it.lctnScNm, + ) + }?.firstOrNull() + } + return school?.let { saveInCache(it) } + } + + /** + * 학교 이름으로 학교 리스트를 조회합니다. + * + * @param schoolName 학교 이름 + * @return 학교 리스트 + */ + override fun querySchoolListBySchoolName(schoolName: String): List { + return schoolClient.getSchoolListBySchoolName(schoolName = schoolName, key = apiKey)?.let { response -> + val mapper = ObjectMapper().registerKotlinModule() + val responseObject = mapper.readValue(response) + responseObject.schoolInfo?.getOrNull(1)?.row?.map { + School( + code = it.sdSchulCode, + name = it.schulNm, + tel = it.orgTelno, + type = it.schulKndScNm, + address = it.orgRdnma, + regionName = it.lctnScNm, + ) + } + } ?: emptyList() + } + + /** + * 학교 정보를 캐시에 저장합니다. + * + * @param school 학교 정보 + * @return 저장된 학교 정보 + */ + private fun saveInCache(school: School): School { + val schoolCache = + SchoolCacheRedisEntity( + code = school.code, + name = school.name, + tel = school.tel, + type = school.type, + address = school.address, + regionName = school.regionName, + ) + + schoolCacheRepository.save(schoolCache) + return school + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/entity/SchoolCacheRedisEntity.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/entity/SchoolCacheRedisEntity.kt new file mode 100644 index 00000000..baed10de --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/entity/SchoolCacheRedisEntity.kt @@ -0,0 +1,25 @@ +package hs.kr.entrydsm.application.domain.school.domain.entity + +import org.springframework.data.annotation.Id +import org.springframework.data.redis.core.RedisHash + +/** + * 학교 정보를 캐시하는 Redis Entity 입니다. + * + * @property code 학교 코드 + * @property name 학교 이름 + * @property tel 학교 전화번호 + * @property type 학교 타입 + * @property address 학교 주소 + * @property regionName 지역 이름 + */ +@RedisHash("school_cache") +data class SchoolCacheRedisEntity( + @Id + val code: String, + val name: String, + val tel: String, + val type: String, + val address: String, + val regionName: String, +) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/SchoolWebAdapter.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/SchoolWebAdapter.kt new file mode 100644 index 00000000..4de4659c --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/SchoolWebAdapter.kt @@ -0,0 +1,43 @@ +package hs.kr.entrydsm.application.domain.school.domain.presentation + +import hs.kr.entrydsm.application.domain.school.domain.presentation.dto.QuerySchoolWebResponse +import hs.kr.entrydsm.application.domain.school.domain.presentation.dto.SchoolWebResponse +import hs.kr.entrydsm.application.domain.school.domain.usecase.QuerySchoolUseCase +import org.springframework.cache.annotation.Cacheable +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 + +@RestController +@RequestMapping("/schools") +/** + * 학교 정보 API를 제공하는 WebAdapter 입니다. + */ +class SchoolWebAdapter( + private val querySchoolUseCase: QuerySchoolUseCase, +) { + /** + * 학교 이름으로 학교를 검색합니다. + * + * @param name 학교 이름 + * @return 학교 검색 결과 + */ + @Cacheable(value = ["school_info"], key = "#name") + @GetMapping + fun querySchool( + @RequestParam(value = "school_name") name: String, + ): QuerySchoolWebResponse { + return QuerySchoolWebResponse( + content = + querySchoolUseCase.querySchool(name).content.map { + SchoolWebResponse( + code = it.code, + name = it.name, + information = it.information, + address = it.address, + ) + }, + ) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/TestSchoolController.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/TestSchoolController.kt new file mode 100644 index 00000000..13ce9274 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/TestSchoolController.kt @@ -0,0 +1,46 @@ +package hs.kr.entrydsm.application.domain.school.domain.presentation + +import hs.kr.entrydsm.application.global.feign.client.SchoolClient +import org.springframework.beans.factory.annotation.Value +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 + +/** + * School API 테스트를 위한 컨트롤러입니다. + */ +@RestController +@RequestMapping("/test/schools") +class TestSchoolController( + private val schoolClient: SchoolClient, +) { + @Value("\${neis.key}") + lateinit var apiKey: String + + /** + * 학교 코드로 학교 정보를 조회합니다. + * + * @param schoolCode 학교 코드 + * @return 학교 정보 + */ + @GetMapping("/code") + fun getSchoolByCode( + @RequestParam("school_code") schoolCode: String, + ): String? { + return schoolClient.getSchoolBySchoolCode(schoolCode = schoolCode, key = apiKey) + } + + /** + * 학교 이름으로 학교 리스트를 조회합니다. + * + * @param schoolName 학교 이름 + * @return 학교 리스트 + */ + @GetMapping("/name") + fun getSchoolByName( + @RequestParam("school_name") schoolName: String, + ): String? { + return schoolClient.getSchoolListBySchoolName(schoolName = schoolName, key = apiKey) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/dto/QuerySchoolWebResponse.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/dto/QuerySchoolWebResponse.kt new file mode 100644 index 00000000..0c17ad14 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/dto/QuerySchoolWebResponse.kt @@ -0,0 +1,10 @@ +package hs.kr.entrydsm.application.domain.school.domain.presentation.dto + +/** + * 학교 정보 검색 응답을 위한 데이터 클래스 입니다. + * + * @property content 학교 정보 리스트 + */ +data class QuerySchoolWebResponse( + val content: List? = null, +) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/dto/SchoolWebResponse.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/dto/SchoolWebResponse.kt new file mode 100644 index 00000000..35e85cc4 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/presentation/dto/SchoolWebResponse.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.application.domain.school.domain.presentation.dto + +/** + * 학교 정보 응답을 위한 데이터 클래스 입니다. + * + * @property code 학교 코드 + * @property name 학교 이름 + * @property information 학교 정보 + * @property address 학교 주소 + */ +data class SchoolWebResponse( + val code: String? = null, + val name: String? = null, + val information: String? = null, + val address: String? = null, +) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/repository/SchoolCacheRepository.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/repository/SchoolCacheRepository.kt new file mode 100644 index 00000000..646db8a5 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/repository/SchoolCacheRepository.kt @@ -0,0 +1,9 @@ +package hs.kr.entrydsm.application.domain.school.domain.repository + +import hs.kr.entrydsm.application.domain.school.domain.entity.SchoolCacheRedisEntity +import org.springframework.data.repository.CrudRepository + +/** + * 학교 정보를 캐시하는 Repository 입니다. + */ +interface SchoolCacheRepository : CrudRepository diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/usecase/QuerySchoolUseCase.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/usecase/QuerySchoolUseCase.kt new file mode 100644 index 00000000..feab2b3c --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/school/domain/usecase/QuerySchoolUseCase.kt @@ -0,0 +1,37 @@ +package hs.kr.entrydsm.application.domain.school.domain.usecase + +import hs.kr.entrydsm.domain.school.interfaces.QuerySchoolContract +import hs.kr.entrydsm.domain.school.dto.QuerySchoolResponse +import hs.kr.entrydsm.domain.school.dto.SchoolResponse +import hs.kr.entrydsm.application.global.annotation.usecase.ReadOnlyUseCase +import hs.kr.entrydsm.domain.school.interfaces.QuerySchoolUseCaseContract + +/** + * 학교 정보를 조회하는 UseCase 입니다. + */ +@ReadOnlyUseCase +class QuerySchoolUseCase( + private val querySchoolContract: QuerySchoolContract, +): QuerySchoolUseCaseContract { + + /** + * 학교 이름으로 학교를 조회합니다. + * + * @param name 학교 이름 + * @return 학교 검색 결과 + */ + override fun querySchool(name: String): QuerySchoolResponse { + val schoolList = querySchoolContract.querySchoolListBySchoolName(name) + return QuerySchoolResponse( + content = + schoolList.map { + SchoolResponse( + code = it.code, + name = it.name, + information = it.tel, + address = it.address, + ) + }, + ) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/annotation/usecase/ReadOnlyUseCase.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/annotation/usecase/ReadOnlyUseCase.kt new file mode 100644 index 00000000..d508a90f --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/annotation/usecase/ReadOnlyUseCase.kt @@ -0,0 +1,16 @@ +package hs.kr.entrydsm.application.global.annotation.usecase + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +/** + * + * 조회 기능을 담당하는 사용자 UseCase를 나타내는 어노테이션 + * + * @author 박주원 + **/ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +@Transactional(readOnly = true) +@Service +annotation class ReadOnlyUseCase() diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/ConverterPropertiesCreator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/ConverterPropertiesCreator.kt index baff0308..2503d313 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/ConverterPropertiesCreator.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/ConverterPropertiesCreator.kt @@ -8,7 +8,6 @@ import java.io.IOException @Component class ConverterPropertiesCreator { - private var fontPath: String = "/fonts/" fun createConverterProperties(): ConverterProperties { diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/Font.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/Font.kt index 85c5dde7..3362a036 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/Font.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/Font.kt @@ -1,10 +1,11 @@ package hs.kr.entrydsm.application.global.document.pdf.config object Font { - val fonts = listOf( - "KoPubWorld Dotum Light.ttf", - "KoPubWorld Dotum Bold.ttf", - "KoPubWorld Dotum Medium.ttf", - "DejaVuSans.ttf" - ) + val fonts = + listOf( + "KoPubWorld Dotum Light.ttf", + "KoPubWorld Dotum Bold.ttf", + "KoPubWorld Dotum Medium.ttf", + "DejaVuSans.ttf", + ) } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/PdfConfig.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/PdfConfig.kt index 27d088b7..11eb3f78 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/PdfConfig.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/config/PdfConfig.kt @@ -8,7 +8,6 @@ import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver @Configuration class PdfConfig { - @Bean fun templateEngine(): TemplateEngine { val templateResolver = ClassLoaderTemplateResolver().apply { @@ -16,8 +15,8 @@ class PdfConfig { suffix = ".html" templateMode = TemplateMode.HTML } - return TemplateEngine().also { - it.setTemplateResolver(templateResolver) + return TemplateEngine().apply { + setTemplateResolver(templateResolver) } } } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/IntroductionPdfConverter.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/IntroductionPdfConverter.kt index ab9e0a77..a00104cb 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/IntroductionPdfConverter.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/IntroductionPdfConverter.kt @@ -5,7 +5,6 @@ import java.util.HashMap @Component class IntroductionPdfConverter { - fun execute(application: Any): PdfData { val values: MutableMap = HashMap() setIntroduction(application, values) @@ -16,25 +15,37 @@ class IntroductionPdfConverter { return PdfData(values) } - private fun setPersonalInfo(application: Any, values: MutableMap) { + private fun setPersonalInfo( + application: Any, + values: MutableMap, + ) { // TODO: Application 도메인 모델 연동 필요 values["userName"] = "더미사용자명" values["address"] = "더미주소" values["detailAddress"] = "더미상세주소" } - - private fun setSchoolInfo(application: Any, values: MutableMap) { + + private fun setSchoolInfo( + application: Any, + values: MutableMap, + ) { // TODO: 교육상태 및 졸업정보 연동 필요 // 현재는 더미 데이터로 설정 values["schoolName"] = "더미중학교" } - private fun setReceiptCode(application: Any, values: MutableMap) { + private fun setReceiptCode( + application: Any, + values: MutableMap, + ) { // TODO: Application 도메인 모델 연동 필요 values["receiptCode"] = "더미수험번호" } - private fun setPhoneNumber(application: Any, values: MutableMap) { + private fun setPhoneNumber( + application: Any, + values: MutableMap, + ) { values["applicantTel"] = toFormattedPhoneNumber("01012345678") } @@ -45,7 +56,10 @@ class IntroductionPdfConverter { return phoneNumber.replace("(\\d{2,3})(\\d{3,4})(\\d{4})".toRegex(), "$1-$2-$3") } - private fun setIntroduction(application: Any, values: MutableMap) { + private fun setIntroduction( + application: Any, + values: MutableMap, + ) { values["selfIntroduction"] = "더미 자기소개 내용" values["studyPlan"] = "더미 학업계획 내용" values["newLineChar"] = "\n" diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfData.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfData.kt index bd9fd6f8..02c70045 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfData.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfData.kt @@ -1,13 +1,16 @@ package hs.kr.entrydsm.application.global.document.pdf.data data class PdfData( - private val values: MutableMap + private val values: MutableMap, ) { fun toMap(): MutableMap = values - + fun getValue(key: String): Any? = values[key] - - fun setValue(key: String, value: Any) { + + fun setValue( + key: String, + value: Any, + ) { values[key] = value } } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfDataConverter.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfDataConverter.kt index c23316c0..58c7949a 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfDataConverter.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/data/PdfDataConverter.kt @@ -4,12 +4,13 @@ import org.springframework.stereotype.Component import java.time.LocalDate import java.time.LocalDateTime import java.time.YearMonth -import java.util.* @Component class PdfDataConverter { - - fun applicationToInfo(application: Any, score: Any): PdfData { + fun applicationToInfo( + application: Any, + score: Any, + ): PdfData { val values: MutableMap = HashMap() setReceiptCode(application, values) setEntranceYear(values) @@ -41,7 +42,10 @@ class PdfDataConverter { return PdfData(values) } - private fun setReceiptCode(application: Any, values: MutableMap) { + private fun setReceiptCode( + application: Any, + values: MutableMap, + ) { // TODO: Application 도메인 모델 연동 필요 values["receiptCode"] = "더미데이터" } @@ -51,12 +55,18 @@ class PdfDataConverter { values["entranceYear"] = entranceYear.toString() } - private fun setVeteransNumber(application: Any, values: MutableMap) { + private fun setVeteransNumber( + application: Any, + values: MutableMap, + ) { // TODO: Application 도메인 모델 연동 필요 values["veteransNumber"] = "" } - private fun setPersonalInfo(application: Any, values: MutableMap) { + private fun setPersonalInfo( + application: Any, + values: MutableMap, + ) { // TODO: Application 도메인 모델 연동 필요 values["userName"] = setBlankIfNull("더미사용자명") values["isMale"] = toBallotBox(true) @@ -70,7 +80,10 @@ class PdfDataConverter { values["applicationRemark"] = "해당없음" } - private fun setAttendanceAndVolunteer(application: Any, values: MutableMap) { + private fun setAttendanceAndVolunteer( + application: Any, + values: MutableMap, + ) { // TODO: ApplicationCase 도메인 모델 연동 필요 values["absenceDayCount"] = 0 values["latenessCount"] = 0 @@ -79,11 +92,17 @@ class PdfDataConverter { values["volunteerTime"] = 0 } - private fun setGenderInfo(application: Any, values: MutableMap) { + private fun setGenderInfo( + application: Any, + values: MutableMap, + ) { values["gender"] = setBlankIfNull("남") } - private fun setSchoolInfo(application: Any, values: MutableMap) { + private fun setSchoolInfo( + application: Any, + values: MutableMap, + ) { // TODO: 졸업정보 및 학교정보 연동 필요 values["schoolCode"] = "더미학교코드" values["schoolRegion"] = "더미지역" @@ -92,14 +111,20 @@ class PdfDataConverter { values["schoolName"] = "더미중학교" } - private fun setPhoneNumber(application: Any, values: MutableMap) { + private fun setPhoneNumber( + application: Any, + values: MutableMap, + ) { values["applicantTel"] = toFormattedPhoneNumber("01012345678") values["parentTel"] = toFormattedPhoneNumber("01087654321") } - private fun setGraduationClassification(application: Any, values: MutableMap) { + private fun setGraduationClassification( + application: Any, + values: MutableMap, + ) { values.putAll(emptyGraduationClassification()) - + // TODO: 졸업정보 연동 필요 val yearMonth = YearMonth.now() values["graduateYear"] = yearMonth.year.toString() @@ -107,39 +132,50 @@ class PdfDataConverter { values["educationalStatus"] = "${yearMonth.year}년 ${yearMonth.monthValue}월 중학교 졸업" } - private fun setUserType(application: Any, values: MutableMap) { - val list = listOf( - "isQualificationExam" to false, - "isGraduate" to true, - "isProspectiveGraduate" to false, - "isDaejeon" to true, - "isNotDaejeon" to false, - "isBasicLiving" to false, - "isFromNorth" to false, - "isLowestIncome" to false, - "isMulticultural" to false, - "isOneParent" to false, - "isTeenHouseholder" to false, - "isPrivilegedAdmission" to false, - "isNationalMerit" to false, - "isProtectedChildren" to false, - "isCommon" to true, - "isMeister" to false, - "isSocialMerit" to false - ) + private fun setUserType( + application: Any, + values: MutableMap, + ) { + val list = + listOf( + "isQualificationExam" to false, + "isGraduate" to true, + "isProspectiveGraduate" to false, + "isDaejeon" to true, + "isNotDaejeon" to false, + "isBasicLiving" to false, + "isFromNorth" to false, + "isLowestIncome" to false, + "isMulticultural" to false, + "isOneParent" to false, + "isTeenHouseholder" to false, + "isPrivilegedAdmission" to false, + "isNationalMerit" to false, + "isProtectedChildren" to false, + "isCommon" to true, + "isMeister" to false, + "isSocialMerit" to false, + ) list.forEach { (key, value) -> values[key] = toBallotBox(value) } } - private fun setExtraScore(application: Any, values: MutableMap) { + private fun setExtraScore( + application: Any, + values: MutableMap, + ) { // TODO: ApplicationCase 연동 필요 values["hasCompetitionPrize"] = toCircleBallotbox(false) values["hasCertificate"] = toCircleBallotbox(false) } - private fun setGradeScore(application: Any, score: Any, values: MutableMap) { + private fun setGradeScore( + application: Any, + score: Any, + values: MutableMap, + ) { // TODO: Score 도메인 모델 연동 필요 with(values) { put("conversionScore1st", "80.0") @@ -152,21 +188,25 @@ class PdfDataConverter { } } - private fun setAllSubjectScores(application: Any, values: MutableMap) { + private fun setAllSubjectScores( + application: Any, + values: MutableMap, + ) { // TODO: ApplicationCase 연동 필요 - 일반졸업 케이스로 더미 데이터 val subjects = listOf("국어", "사회", "역사", "수학", "과학", "영어", "기술가정") subjects.forEach { subject -> - val subjectPrefix = when (subject) { - "국어" -> "korean" - "사회" -> "social" - "역사" -> "history" - "수학" -> "math" - "과학" -> "science" - "영어" -> "english" - "기술가정" -> "techAndHome" - else -> subject.lowercase() - } + val subjectPrefix = + when (subject) { + "국어" -> "korean" + "사회" -> "social" + "역사" -> "history" + "수학" -> "math" + "과학" -> "science" + "영어" -> "english" + "기술가정" -> "techAndHome" + else -> subject.lowercase() + } with(values) { put("applicationCase", "기술∙가정") @@ -187,31 +227,46 @@ class PdfDataConverter { } } - private fun setIntroduction(application: Any, values: MutableMap) { + private fun setIntroduction( + application: Any, + values: MutableMap, + ) { values["selfIntroduction"] = setBlankIfNull("더미 자기소개") values["studyPlan"] = setBlankIfNull("더미 학업계획") values["newLineChar"] = "\n" } - private fun setTeacherInfo(application: Any, values: MutableMap) { + private fun setTeacherInfo( + application: Any, + values: MutableMap, + ) { // TODO: 졸업정보 연동 필요 values["teacherName"] = "더미선생님" values["teacherTel"] = toFormattedPhoneNumber("0421234567") } - private fun setParentInfo(application: Any, values: MutableMap) { + private fun setParentInfo( + application: Any, + values: MutableMap, + ) { values["parentName"] = "더미학부모" values["parentRelation"] = "부" } - private fun setRecommendations(application: Any, values: MutableMap) { + private fun setRecommendations( + application: Any, + values: MutableMap, + ) { values["isDaejeonAndMeister"] = markIfTrue(false) values["isDaejeonAndSocialMerit"] = markIfTrue(false) values["isNotDaejeonAndMeister"] = markIfTrue(false) values["isNotDaejeonAndSocialMerit"] = markIfTrue(false) } - private fun setBase64Image(application: Any, values: MutableMap) { + private fun setBase64Image( + application: Any, + values: MutableMap, + ) { // TODO: 이미지 파일 연동 필요 values["base64Image"] = "" } @@ -225,7 +280,7 @@ class PdfDataConverter { "schoolCode" to "", "schoolClass" to "", "schoolTel" to "", - "schoolName" to "" + "schoolName" to "", ) } @@ -236,7 +291,7 @@ class PdfDataConverter { "graduateYear" to "20__", "graduateMonth" to "__", "prospectiveGraduateYear" to "20__", - "prospectiveGraduateMonth" to "__" + "prospectiveGraduateMonth" to "__", ) } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/facade/PdfDocumentFacade.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/facade/PdfDocumentFacade.kt index 048c7cf4..d260c02c 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/facade/PdfDocumentFacade.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/facade/PdfDocumentFacade.kt @@ -8,7 +8,6 @@ import java.io.ByteArrayOutputStream @Component class PdfDocumentFacade { - fun getPdfDocument(pdfStream: ByteArrayOutputStream): PdfDocument? { return try { val inputStream = ByteArrayInputStream(pdfStream.toByteArray()) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/ApplicationPdfGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/ApplicationPdfGenerator.kt index bc72993c..96f42ee3 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/ApplicationPdfGenerator.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/ApplicationPdfGenerator.kt @@ -1,7 +1,6 @@ package hs.kr.entrydsm.application.global.document.pdf.generator import com.itextpdf.kernel.pdf.PdfDocument -import com.itextpdf.kernel.pdf.PdfReader import com.itextpdf.kernel.pdf.PdfWriter import com.itextpdf.kernel.utils.PdfMerger import com.itextpdf.layout.Document @@ -9,36 +8,39 @@ import hs.kr.entrydsm.application.global.document.pdf.data.PdfDataConverter import hs.kr.entrydsm.application.global.document.pdf.data.TemplateFileName import hs.kr.entrydsm.application.global.document.pdf.facade.PdfDocumentFacade import org.springframework.stereotype.Component -import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream -import java.util.* +import java.util.LinkedList @Component class ApplicationPdfGenerator( private val pdfProcessor: PdfProcessor, private val pdfDataConverter: PdfDataConverter, private val templateProcessor: TemplateProcessor, - private val pdfDocumentFacade: PdfDocumentFacade + private val pdfDocumentFacade: PdfDocumentFacade, ) { - - fun generate(application: Any, score: Any): ByteArray { + fun generate( + application: Any, + score: Any, + ): ByteArray { return generateApplicationPdf(application, score) } - private fun generateApplicationPdf(application: Any, score: Any): ByteArray { + private fun generateApplicationPdf( + application: Any, + score: Any, + ): ByteArray { val data = pdfDataConverter.applicationToInfo(application, score) val templates = getTemplateFileNames(application) - val outStream = templates.stream() - .map { template -> - templateProcessor.convertTemplateIntoHtmlString(template, data.toMap()) - } - .map { html -> - pdfProcessor.convertHtmlToPdf(html) - } - .toArray { size -> arrayOfNulls(size) } + val outStream = + templates.stream() + .map { template -> + templateProcessor.convertTemplateIntoHtmlString(template, data.toMap()) + } + .map { html -> + pdfProcessor.convertHtmlToPdf(html) + } + .toArray { size -> arrayOfNulls(size) } val outputStream = ByteArrayOutputStream() val mergedDocument = PdfDocument(PdfWriter(outputStream)) @@ -55,7 +57,10 @@ class ApplicationPdfGenerator( return outputStream.toByteArray() } - private fun mergeDocument(merger: PdfMerger, document: PdfDocument?) { + private fun mergeDocument( + merger: PdfMerger, + document: PdfDocument?, + ) { if (document != null) { merger.merge(document, 1, document.numberOfPages) document.close() @@ -63,15 +68,16 @@ class ApplicationPdfGenerator( } private fun getTemplateFileNames(application: Any): MutableList { - val result = LinkedList( - listOf( - TemplateFileName.APPLICATION_FOR_ADMISSION, - TemplateFileName.PRIVACY_AGREEMENT, - TemplateFileName.INTRODUCTION, - TemplateFileName.NON_SMOKING, - TemplateFileName.SMOKING_EXAMINE + val result = + LinkedList( + listOf( + TemplateFileName.APPLICATION_FOR_ADMISSION, + TemplateFileName.PRIVACY_AGREEMENT, + TemplateFileName.INTRODUCTION, + TemplateFileName.NON_SMOKING, + TemplateFileName.SMOKING_EXAMINE, + ), ) - ) // TODO: 조건부 추천서 추가 로직 // if (!application.isQualificationExam() && !application.isCommonApplicationType()) { diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/IntroductionPdfGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/IntroductionPdfGenerator.kt index 73604cd9..3279f79b 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/IntroductionPdfGenerator.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/IntroductionPdfGenerator.kt @@ -8,15 +8,15 @@ import org.springframework.stereotype.Component class IntroductionPdfGenerator( private val pdfProcessor: PdfProcessor, private val introductionPdfConverter: IntroductionPdfConverter, - private val templateProcessor: TemplateProcessor + private val templateProcessor: TemplateProcessor, ) { - fun generate(application: Any): ByteArray { val data = introductionPdfConverter.execute(application) - val html = templateProcessor.convertTemplateIntoHtmlString( - TemplateFileName.ADMIN_INTRODUCTION, - data.toMap() - ) + val html = + templateProcessor.convertTemplateIntoHtmlString( + TemplateFileName.ADMIN_INTRODUCTION, + data.toMap(), + ) val pdfStream = pdfProcessor.convertHtmlToPdf(html) return pdfStream.toByteArray() } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/PdfProcessor.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/PdfProcessor.kt index dac284d0..155d14df 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/PdfProcessor.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/PdfProcessor.kt @@ -9,7 +9,6 @@ import java.io.ByteArrayOutputStream class PdfProcessor( private val converterPropertiesCreator: ConverterPropertiesCreator, ) { - fun convertHtmlToPdf(html: String): ByteArrayOutputStream { val outputStream = ByteArrayOutputStream() HtmlConverter.convertToPdf(html, outputStream, converterPropertiesCreator.createConverterProperties()) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/TemplateProcessor.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/TemplateProcessor.kt index c30e2cbb..b32c21d3 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/TemplateProcessor.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/generator/TemplateProcessor.kt @@ -6,10 +6,12 @@ import org.thymeleaf.context.Context @Component class TemplateProcessor( - private val templateEngine: TemplateEngine + private val templateEngine: TemplateEngine, ) { - - fun convertTemplateIntoHtmlString(template: String?, data: MutableMap?): String { + fun convertTemplateIntoHtmlString( + template: String?, + data: MutableMap?, + ): String { val context = Context() context.setVariables(data) return templateEngine.process(template, context) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/presentation/PdfTestController.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/presentation/PdfTestController.kt index 3199fd80..2f87434c 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/presentation/PdfTestController.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/document/pdf/presentation/PdfTestController.kt @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/pdf") class PdfTestController( private val applicationPdfGenerator: ApplicationPdfGenerator, - private val introductionPdfGenerator: IntroductionPdfGenerator + private val introductionPdfGenerator: IntroductionPdfGenerator, ) { @GetMapping("/test") fun testPdf(): ResponseEntity { @@ -38,4 +38,4 @@ class PdfTestController( .header("Content-Disposition", "inline; filename=test-introduction.pdf") .body(pdfBytes) } -} \ No newline at end of file +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintAdmissionTicketGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintAdmissionTicketGenerator.kt index 3a633ff5..f4d8fed4 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintAdmissionTicketGenerator.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintAdmissionTicketGenerator.kt @@ -1,6 +1,12 @@ package hs.kr.entrydsm.application.global.excel.generator -import org.apache.poi.ss.usermodel.* +import jakarta.servlet.http.HttpServletResponse +import org.apache.poi.ss.usermodel.Cell +import org.apache.poi.ss.usermodel.CellStyle +import org.apache.poi.ss.usermodel.CellType +import org.apache.poi.ss.usermodel.Row +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.ss.util.CellReference import org.apache.poi.xssf.usermodel.XSSFClientAnchor @@ -11,7 +17,6 @@ import org.springframework.stereotype.Component import java.io.IOException import java.time.LocalDateTime import java.time.format.DateTimeFormatter -import jakarta.servlet.http.HttpServletResponse @Component class PrintAdmissionTicketGenerator { @@ -46,24 +51,25 @@ class PrintAdmissionTicketGenerator { targetSheet.setDefaultColumnWidth(13) // 더미 데이터 - val dummyApplications = listOf( - mapOf( - "receiptCode" to 1001L, - "examCode" to "DUMMY001", - "applicantName" to "홍길동", - "schoolName" to "더미고등학교", - "isDaejeon" to "대전", - "applicationType" to "일반전형" - ), - mapOf( - "receiptCode" to 1002L, - "examCode" to "DUMMY002", - "applicantName" to "김철수", - "schoolName" to "테스트고등학교", - "isDaejeon" to "전국", - "applicationType" to "마이스터전형" + val dummyApplications = + listOf( + mapOf( + "receiptCode" to 1001L, + "examCode" to "DUMMY001", + "applicantName" to "홍길동", + "schoolName" to "더미고등학교", + "isDaejeon" to "대전", + "applicationType" to "일반전형", + ), + mapOf( + "receiptCode" to 1002L, + "examCode" to "DUMMY002", + "applicantName" to "김철수", + "schoolName" to "테스트고등학교", + "isDaejeon" to "전국", + "applicationType" to "마이스터전형", + ), ) - ) var currentRowIndex = 0 dummyApplications.forEach { dummyApp -> @@ -82,20 +88,31 @@ class PrintAdmissionTicketGenerator { return resource.inputStream.use { XSSFWorkbook(it) } } - fun createStyleMap(sourceWorkbook: Workbook, targetWorkbook: Workbook): Map { + fun createStyleMap( + sourceWorkbook: Workbook, + targetWorkbook: Workbook, + ): Map { val styleCache = mutableMapOf() return (0 until sourceWorkbook.numCellStyles).associate { i -> val sourceStyle = sourceWorkbook.getCellStyleAt(i) - val targetStyle = styleCache.getOrPut(sourceStyle.index) { - val newStyle = targetWorkbook.createCellStyle() - newStyle.cloneStyleFrom(sourceStyle) - newStyle - } + val targetStyle = + styleCache.getOrPut(sourceStyle.index) { + val newStyle = targetWorkbook.createCellStyle() + newStyle.cloneStyleFrom(sourceStyle) + newStyle + } sourceStyle to targetStyle } } - fun copyRows(sourceSheet: Sheet, targetSheet: Sheet, sourceStartRow: Int, sourceEndRow: Int, targetStartRow: Int, styleMap: Map) { + fun copyRows( + sourceSheet: Sheet, + targetSheet: Sheet, + sourceStartRow: Int, + sourceEndRow: Int, + targetStartRow: Int, + styleMap: Map, + ) { for (i in sourceStartRow..sourceEndRow) { val sourceRow = sourceSheet.getRow(i) val targetRow = targetSheet.createRow(targetStartRow + i - sourceStartRow) @@ -105,7 +122,13 @@ class PrintAdmissionTicketGenerator { } } - fun copyRow(sourceSheet: Sheet, targetSheet: Sheet, sourceRow: Row, targetRow: Row, styleMap: Map) { + fun copyRow( + sourceSheet: Sheet, + targetSheet: Sheet, + sourceRow: Row, + targetRow: Row, + styleMap: Map, + ) { targetRow.height = sourceRow.height for (i in 0 until sourceRow.lastCellNum) { @@ -122,18 +145,23 @@ class PrintAdmissionTicketGenerator { for (i in 0 until sourceSheet.numMergedRegions) { val mergedRegion = sourceSheet.getMergedRegion(i) if (mergedRegion.firstRow == sourceRow.rowNum) { - val newMergedRegion = CellRangeAddress( - targetRow.rowNum, - targetRow.rowNum + (mergedRegion.lastRow - mergedRegion.firstRow), - mergedRegion.firstColumn, - mergedRegion.lastColumn - ) + val newMergedRegion = + CellRangeAddress( + targetRow.rowNum, + targetRow.rowNum + (mergedRegion.lastRow - mergedRegion.firstRow), + mergedRegion.firstColumn, + mergedRegion.lastColumn, + ) targetSheet.addMergedRegion(newMergedRegion) } } } - fun copyCell(oldCell: Cell, newCell: Cell, styleMap: Map) { + fun copyCell( + oldCell: Cell, + newCell: Cell, + styleMap: Map, + ) { val newStyle = styleMap[oldCell.cellStyle] if (newStyle != null) { newCell.cellStyle = newStyle @@ -150,7 +178,12 @@ class PrintAdmissionTicketGenerator { } } - fun fillApplicationData(sheet: Sheet, startRowIndex: Int, dummyApp: Map, workbook: Workbook) { + fun fillApplicationData( + sheet: Sheet, + startRowIndex: Int, + dummyApp: Map, + workbook: Workbook, + ) { setValue(sheet, "E4", dummyApp["examCode"].toString()) setValue(sheet, "E6", dummyApp["applicantName"].toString()) setValue(sheet, "E8", dummyApp["schoolName"].toString()) @@ -159,10 +192,13 @@ class PrintAdmissionTicketGenerator { setValue(sheet, "E14", dummyApp["receiptCode"].toString()) } - fun copyDummyImage(targetSheet: Sheet, targetRowIndex: Int) { + fun copyDummyImage( + targetSheet: Sheet, + targetRowIndex: Int, + ) { // 더미 이미지 데이터 (빈 바이트 배열) val dummyImageBytes = ByteArray(100) { 0 } - + try { val workbook = targetSheet.workbook val pictureId = workbook.addPicture(dummyImageBytes, Workbook.PICTURE_TYPE_PNG) @@ -179,7 +215,11 @@ class PrintAdmissionTicketGenerator { } } - fun setValue(sheet: Sheet, position: String, value: String) { + fun setValue( + sheet: Sheet, + position: String, + value: String, + ) { val ref = CellReference(position) val r = sheet.getRow(ref.row) if (r != null) { diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicantCodesGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicantCodesGenerator.kt index 4b638deb..371dd289 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicantCodesGenerator.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicantCodesGenerator.kt @@ -14,14 +14,15 @@ class PrintApplicantCodesGenerator { val applicantCode = ApplicantCode() val sheet = applicantCode.getSheet() applicantCode.format() - + // 더미 데이터로 테스트 - val dummyData = listOf( - Triple("DUMMY001", 1001L, "홍길동"), - Triple("DUMMY002", 1002L, "김철수"), - Triple("DUMMY003", 1003L, "이영희") - ) - + val dummyData = + listOf( + Triple("DUMMY001", 1001L, "홍길동"), + Triple("DUMMY002", 1002L, "김철수"), + Triple("DUMMY003", 1003L, "이영희"), + ) + dummyData.forEachIndexed { index, (examCode, receiptCode, name) -> val row = sheet.createRow(index + 1) insertCode(row, examCode, receiptCode, name) @@ -40,7 +41,12 @@ class PrintApplicantCodesGenerator { } } - private fun insertCode(row: Row, examCode: String, receiptCode: Long, name: String) { + private fun insertCode( + row: Row, + examCode: String, + receiptCode: Long, + name: String, + ) { row.createCell(0).setCellValue(examCode) row.createCell(1).setCellValue(receiptCode.toString()) row.createCell(2).setCellValue(name) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationCheckListGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationCheckListGenerator.kt index 2530180e..55dbe3a8 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationCheckListGenerator.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationCheckListGenerator.kt @@ -2,7 +2,11 @@ package hs.kr.entrydsm.application.global.excel.generator import jakarta.servlet.ServletOutputStream import jakarta.servlet.http.HttpServletResponse -import org.apache.poi.ss.usermodel.* +import org.apache.poi.ss.usermodel.BorderStyle +import org.apache.poi.ss.usermodel.Cell +import org.apache.poi.ss.usermodel.Row +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.ss.util.RegionUtil import org.apache.poi.xssf.usermodel.XSSFWorkbook @@ -13,7 +17,6 @@ import java.time.format.DateTimeFormatter @Component class PrintApplicationCheckListGenerator { - private val workbook: Workbook = XSSFWorkbook() private val sheet: Sheet = workbook.createSheet("application Check List") @@ -22,12 +25,13 @@ class PrintApplicationCheckListGenerator { var dh = 0 try { // 더미 데이터 - val dummyApplications = listOf( - createDummyApplication(1001L, "홍길동", "더미고등학교"), - createDummyApplication(1002L, "김철수", "테스트고등학교"), - createDummyApplication(1003L, "이영희", "샘플고등학교") - ) - + val dummyApplications = + listOf( + createDummyApplication(1001L, "홍길동", "더미고등학교"), + createDummyApplication(1002L, "김철수", "테스트고등학교"), + createDummyApplication(1003L, "이영희", "샘플고등학교"), + ) + dummyApplications.forEach { dummyData -> formatSheet(dh) insertDataIntoSheet(dummyData, dh) @@ -35,10 +39,19 @@ class PrintApplicationCheckListGenerator { } httpServletResponse.apply { - contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + contentType = + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + val formatFilename = "attachment;filename=\"점검표" - val time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy년MM월dd일_HH시mm분")) - val fileName = String(("$formatFilename$time.xlsx\"").toByteArray(Charsets.UTF_8), Charsets.ISO_8859_1) + val time = + LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyy년MM월dd일_HH시mm분")) + + val fileName = + String( + ("$formatFilename$time.xlsx\"").toByteArray(Charsets.UTF_8), + Charsets.ISO_8859_1, + ) setHeader("Content-Disposition", fileName) } @@ -60,7 +73,11 @@ class PrintApplicationCheckListGenerator { } } - private fun createDummyApplication(receiptCode: Long, name: String, schoolName: String): Map { + private fun createDummyApplication( + receiptCode: Long, + name: String, + schoolName: String, + ): Map { return mapOf( "receiptCode" to receiptCode, "applicationType" to "일반전형", @@ -76,7 +93,7 @@ class PrintApplicationCheckListGenerator { "graduateYear" to "2024", "studentNumber" to "30315", "phoneNumber" to "010-1234-5678", - "parentPhoneNumber" to "010-9876-5432" + "parentPhoneNumber" to "010-9876-5432", ) } @@ -89,15 +106,16 @@ class PrintApplicationCheckListGenerator { } private fun Sheet.mergeRegions(rowOffset: Int) { - val mergedRegions = arrayOf( - CellRangeAddress(1 + rowOffset, 1 + rowOffset, 3, 5), - CellRangeAddress(3 + rowOffset, 3 + rowOffset, 2, 3), - CellRangeAddress(4 + rowOffset, 4 + rowOffset, 2, 3), - CellRangeAddress(5 + rowOffset, 5 + rowOffset, 2, 3), - CellRangeAddress(4 + rowOffset, 4 + rowOffset, 6, 7), - CellRangeAddress(5 + rowOffset, 5 + rowOffset, 6, 7), - CellRangeAddress(3 + rowOffset, 3 + rowOffset, 6, 7) - ) + val mergedRegions = + arrayOf( + CellRangeAddress(1 + rowOffset, 1 + rowOffset, 3, 5), + CellRangeAddress(3 + rowOffset, 3 + rowOffset, 2, 3), + CellRangeAddress(4 + rowOffset, 4 + rowOffset, 2, 3), + CellRangeAddress(5 + rowOffset, 5 + rowOffset, 2, 3), + CellRangeAddress(4 + rowOffset, 4 + rowOffset, 6, 7), + CellRangeAddress(5 + rowOffset, 5 + rowOffset, 6, 7), + CellRangeAddress(3 + rowOffset, 3 + rowOffset, 6, 7), + ) mergedRegions.forEach { if (!isRegionMerged(it)) { addMergedRegion(it) @@ -106,116 +124,131 @@ class PrintApplicationCheckListGenerator { } private fun Sheet.isRegionMerged(region: CellRangeAddress): Boolean { - return mergedRegions.any { it.firstRow == region.firstRow && it.lastRow == region.lastRow && it.firstColumn == region.firstColumn && it.lastColumn == region.lastColumn } + return mergedRegions.any { + it.firstRow == region.firstRow && + it.lastRow == region.lastRow && + it.firstColumn == region.firstColumn && + it.lastColumn == region.lastColumn + } } private fun Sheet.applyBorderStyles(dh: Int) { - val borderRegionsDashedBottom = arrayOf( - intArrayOf(3 + dh, 3 + dh, 1, 1), - intArrayOf(4 + dh, 4 + dh, 1, 3), - intArrayOf(5 + dh, 5 + dh, 1, 3), - intArrayOf(3 + dh, 3 + dh, 5, 7), - intArrayOf(4 + dh, 4 + dh, 5, 7), - intArrayOf(5 + dh, 5 + dh, 5, 7), - intArrayOf(7 + dh, 7 + dh, 1, 7), - intArrayOf(11 + dh, 11 + dh, 1, 7), - intArrayOf(12 + dh, 12 + dh, 1, 7), - intArrayOf(13 + dh, 13 + dh, 1, 7), - intArrayOf(14 + dh, 14 + dh, 1, 7), - intArrayOf(15 + dh, 15 + dh, 1, 7), - intArrayOf(16 + dh, 16 + dh, 1, 7), - ) + val borderRegionsDashedBottom = + arrayOf( + intArrayOf(3 + dh, 3 + dh, 1, 1), + intArrayOf(4 + dh, 4 + dh, 1, 3), + intArrayOf(5 + dh, 5 + dh, 1, 3), + intArrayOf(3 + dh, 3 + dh, 5, 7), + intArrayOf(4 + dh, 4 + dh, 5, 7), + intArrayOf(5 + dh, 5 + dh, 5, 7), + intArrayOf(7 + dh, 7 + dh, 1, 7), + intArrayOf(11 + dh, 11 + dh, 1, 7), + intArrayOf(12 + dh, 12 + dh, 1, 7), + intArrayOf(13 + dh, 13 + dh, 1, 7), + intArrayOf(14 + dh, 14 + dh, 1, 7), + intArrayOf(15 + dh, 15 + dh, 1, 7), + intArrayOf(16 + dh, 16 + dh, 1, 7), + ) setBorderStyle(borderRegionsDashedBottom, BorderStyle.DASHED, Direction.BOTTOM) - val borderRegionsThin = arrayOf( - intArrayOf(1 + dh, 1 + dh, 1, 7), - intArrayOf(3 + dh, 3 + dh, 1, 1), - intArrayOf(4 + dh, 5 + dh, 1, 3), - intArrayOf(3 + dh, 5 + dh, 5, 7), - intArrayOf(7 + dh, 8 + dh, 1, 7), - intArrayOf(10 + dh, 17 + dh, 1, 7), - intArrayOf(10 + dh, 10 + dh, 1, 5), - intArrayOf(18 + dh, 18 + dh, 1, 5), - ) + val borderRegionsThin = + arrayOf( + intArrayOf(1 + dh, 1 + dh, 1, 7), + intArrayOf(3 + dh, 3 + dh, 1, 1), + intArrayOf(4 + dh, 5 + dh, 1, 3), + intArrayOf(3 + dh, 5 + dh, 5, 7), + intArrayOf(7 + dh, 8 + dh, 1, 7), + intArrayOf(10 + dh, 17 + dh, 1, 7), + intArrayOf(10 + dh, 10 + dh, 1, 5), + intArrayOf(18 + dh, 18 + dh, 1, 5), + ) setBorderStyle(borderRegionsThin, BorderStyle.THIN, Direction.ALL) - val borderRegionsThick = arrayOf( - intArrayOf(18 + dh, 18 + dh, 6, 7), - intArrayOf(10 + dh, 10 + dh, 6, 7), - intArrayOf(1 + dh, 1 + dh, 2, 2), - intArrayOf(3 + dh, 3 + dh, 2, 2), - intArrayOf(18 + dh, 18 + dh, 6, 7), - intArrayOf(19 + dh, 19 + dh, 6, 7) - ) + val borderRegionsThick = + arrayOf( + intArrayOf(18 + dh, 18 + dh, 6, 7), + intArrayOf(10 + dh, 10 + dh, 6, 7), + intArrayOf(1 + dh, 1 + dh, 2, 2), + intArrayOf(3 + dh, 3 + dh, 2, 2), + intArrayOf(18 + dh, 18 + dh, 6, 7), + intArrayOf(19 + dh, 19 + dh, 6, 7), + ) setBorderStyle(borderRegionsThick, BorderStyle.THICK, Direction.ALL) - val borderRegionsDashedRight = arrayOf( - intArrayOf(18 + dh, 18 + dh, 2, 3), - intArrayOf(1 + dh, 1 + dh, 4, 5), - intArrayOf(1 + dh, 1 + dh, 5, 6), - intArrayOf(4 + dh, 5 + dh, 1, 2), - intArrayOf(7 + dh, 8 + dh, 1, 1), - intArrayOf(7 + dh, 8 + dh, 2, 2), - intArrayOf(7 + dh, 8 + dh, 3, 3), - intArrayOf(7 + dh, 8 + dh, 4, 4), - intArrayOf(7 + dh, 8 + dh, 5, 5), - intArrayOf(7 + dh, 8 + dh, 6, 6), - intArrayOf(10 + dh, 18 + dh, 1, 1), - intArrayOf(10 + dh, 18 + dh, 2, 2), - intArrayOf(10 + dh, 18 + dh, 3, 3), - intArrayOf(10 + dh, 18 + dh, 4, 4), - intArrayOf(10 + dh, 18 + dh, 6, 6), - intArrayOf(3 + dh, 5 + dh, 1, 1), - intArrayOf(19 + dh, 19 + dh, 6, 6) - ) + val borderRegionsDashedRight = + arrayOf( + intArrayOf(18 + dh, 18 + dh, 2, 3), + intArrayOf(1 + dh, 1 + dh, 4, 5), + intArrayOf(1 + dh, 1 + dh, 5, 6), + intArrayOf(4 + dh, 5 + dh, 1, 2), + intArrayOf(7 + dh, 8 + dh, 1, 1), + intArrayOf(7 + dh, 8 + dh, 2, 2), + intArrayOf(7 + dh, 8 + dh, 3, 3), + intArrayOf(7 + dh, 8 + dh, 4, 4), + intArrayOf(7 + dh, 8 + dh, 5, 5), + intArrayOf(7 + dh, 8 + dh, 6, 6), + intArrayOf(10 + dh, 18 + dh, 1, 1), + intArrayOf(10 + dh, 18 + dh, 2, 2), + intArrayOf(10 + dh, 18 + dh, 3, 3), + intArrayOf(10 + dh, 18 + dh, 4, 4), + intArrayOf(10 + dh, 18 + dh, 6, 6), + intArrayOf(3 + dh, 5 + dh, 1, 1), + intArrayOf(19 + dh, 19 + dh, 6, 6), + ) setBorderStyle(borderRegionsDashedRight, BorderStyle.DASHED, Direction.RIGHT) - val borderRegionsThinRight = arrayOf( - intArrayOf(11 + dh, 17 + dh, 5, 5), - intArrayOf(3 + dh, 5 + dh, 5, 5) - ) + val borderRegionsThinRight = + arrayOf( + intArrayOf(11 + dh, 17 + dh, 5, 5), + intArrayOf(3 + dh, 5 + dh, 5, 5), + ) setBorderStyle(borderRegionsThinRight, BorderStyle.THIN, Direction.RIGHT) } private fun Sheet.setCellValues(dh: Int) { - val cellValues = mapOf( - Pair(1 + dh, 1) to "접수번호", - Pair(3 + dh, 5) to "학번", - Pair(4 + dh, 5) to "학생", - Pair(5 + dh, 5) to "보호자", - Pair(7 + dh, 1) to "결석", - Pair(7 + dh, 2) to "지각", - Pair(7 + dh, 3) to "조퇴", - Pair(7 + dh, 4) to "결과", - Pair(7 + dh, 5) to "출석점수", - Pair(7 + dh, 6) to "봉사시간", - Pair(7 + dh, 7) to "봉사점수", - Pair(10 + dh, 1) to "과목", - Pair(10 + dh, 2) to "3_2학기", - Pair(10 + dh, 3) to "3_1학기", - Pair(10 + dh, 4) to "직전", - Pair(10 + dh, 5) to "직전전", - Pair(10 + dh, 6) to "교과성적", - Pair(11 + dh, 6) to "대회", - Pair(12 + dh, 6) to "기능사", - Pair(13 + dh, 6) to "가산점", - Pair(19 + dh, 6) to "총점", - Pair(18 + dh, 6) to "환산점수", - Pair(11 + dh, 1) to "국어", - Pair(12 + dh, 1) to "사회", - Pair(13 + dh, 1) to "역사", - Pair(14 + dh, 1) to "수학", - Pair(15 + dh, 1) to "과학", - Pair(16 + dh, 1) to "기술가정", - Pair(17 + dh, 1) to "영어", - Pair(18 + dh, 1) to "점수" - ) + val cellValues = + mapOf( + Pair(1 + dh, 1) to "접수번호", + Pair(3 + dh, 5) to "학번", + Pair(4 + dh, 5) to "학생", + Pair(5 + dh, 5) to "보호자", + Pair(7 + dh, 1) to "결석", + Pair(7 + dh, 2) to "지각", + Pair(7 + dh, 3) to "조퇴", + Pair(7 + dh, 4) to "결과", + Pair(7 + dh, 5) to "출석점수", + Pair(7 + dh, 6) to "봉사시간", + Pair(7 + dh, 7) to "봉사점수", + Pair(10 + dh, 1) to "과목", + Pair(10 + dh, 2) to "3_2학기", + Pair(10 + dh, 3) to "3_1학기", + Pair(10 + dh, 4) to "직전", + Pair(10 + dh, 5) to "직전전", + Pair(10 + dh, 6) to "교과성적", + Pair(11 + dh, 6) to "대회", + Pair(12 + dh, 6) to "기능사", + Pair(13 + dh, 6) to "가산점", + Pair(19 + dh, 6) to "총점", + Pair(18 + dh, 6) to "환산점수", + Pair(11 + dh, 1) to "국어", + Pair(12 + dh, 1) to "사회", + Pair(13 + dh, 1) to "역사", + Pair(14 + dh, 1) to "수학", + Pair(15 + dh, 1) to "과학", + Pair(16 + dh, 1) to "기술가정", + Pair(17 + dh, 1) to "영어", + Pair(18 + dh, 1) to "점수", + ) cellValues.forEach { (cell, value) -> getCell(cell.first, cell.second).setCellValue(value) } } - private fun setBorderStyle(regions: Array, borderStyle: BorderStyle, direction: Direction) { + private fun setBorderStyle( + regions: Array, + borderStyle: BorderStyle, + direction: Direction, + ) { regions.forEach { region -> val address = CellRangeAddress(region[0], region[1], region[2], region[3]) when (direction) { @@ -233,17 +266,26 @@ class PrintApplicationCheckListGenerator { } } - private fun getCell(rowNum: Int, cellNum: Int): Cell { - val row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) + private fun getCell( + rowNum: Int, + cellNum: Int, + ): Cell { + val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) return row.getCell(cellNum) ?: row.createCell(cellNum) } - private fun setRowHeight(rowIndex: Int, height: Int) { - val row = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) + private fun setRowHeight( + rowIndex: Int, + height: Int, + ) { + val row: Row = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) row.heightInPoints = height.toFloat() } - private fun insertDataIntoSheet(dummyData: Map, dh: Int) { + private fun insertDataIntoSheet( + dummyData: Map, + dh: Int, + ) { getCell(dh + 1, 2).setCellValue(dummyData["receiptCode"].toString()) getCell(dh + 1, 3).setCellValue(dummyData["schoolName"].toString()) getCell(dh + 1, 6).setCellValue(dummyData["educationalStatus"].toString()) @@ -271,7 +313,6 @@ class PrintApplicationCheckListGenerator { // 성적 더미 데이터 val subjects = listOf("국어", "사회", "역사", "수학", "과학", "기술가정", "영어") val dummyGrades = listOf("A", "B", "A", "B", "A", "B", "A") - subjects.forEachIndexed { index, subject -> val rowIndex = dh + 11 + index getCell(rowIndex, 1).setCellValue(subject) @@ -302,6 +343,6 @@ class PrintApplicationCheckListGenerator { BOTTOM, LEFT, RIGHT, - ALL + ALL, } } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationInfoGenerator.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationInfoGenerator.kt index 3b23ed22..54e150e3 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationInfoGenerator.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/generator/PrintApplicationInfoGenerator.kt @@ -1,28 +1,27 @@ package hs.kr.entrydsm.application.global.excel.generator import hs.kr.entrydsm.application.global.excel.model.ApplicationInfo +import jakarta.servlet.http.HttpServletResponse import org.apache.poi.ss.usermodel.Row import org.springframework.stereotype.Component import java.io.IOException import java.time.LocalDateTime import java.time.format.DateTimeFormatter -import jakarta.servlet.http.HttpServletResponse @Component class PrintApplicationInfoGenerator { - fun execute(httpServletResponse: HttpServletResponse) { val applicationInfo = ApplicationInfo() val sheet = applicationInfo.getSheet() applicationInfo.format() - + // 더미 데이터로 테스트 val dummyApplications = listOf( createDummyApplication(1001L, "홍길동", "더미고등학교"), createDummyApplication(1002L, "김철수", "테스트고등학교"), createDummyApplication(1003L, "이영희", "샘플고등학교"), ) - + dummyApplications.forEachIndexed { index, dummyData -> val row = sheet.createRow(index + 1) insertCode(row, dummyData) @@ -42,8 +41,12 @@ class PrintApplicationInfoGenerator { throw IllegalArgumentException("Excel 파일 생성 중 오류가 발생했습니다.", e) } } - - private fun createDummyApplication(receiptCode: Long, name: String, schoolName: String): Map { + + private fun createDummyApplication( + receiptCode: Long, + name: String, + schoolName: String, + ): Map { return mapOf( "receiptCode" to receiptCode, "applicationType" to "일반전형", @@ -60,11 +63,14 @@ class PrintApplicationInfoGenerator { "classNumber" to "3", "parentName" to "홍부모", "parentTel" to "010-9876-5432", - "examCode" to "DUMMY${receiptCode.toString().takeLast(3)}" + "examCode" to "DUMMY${receiptCode.toString().takeLast(3)}", ) } - - private fun insertCode(row: Row, dummyData: Map) { + + private fun insertCode( + row: Row, + dummyData: Map, + ) { row.createCell(0).setCellValue(dummyData["receiptCode"].toString()) row.createCell(1).setCellValue(dummyData["applicationType"].toString()) row.createCell(2).setCellValue(dummyData["isDaejeon"].toString()) @@ -80,39 +86,20 @@ class PrintApplicationInfoGenerator { row.createCell(12).setCellValue(dummyData["classNumber"].toString()) row.createCell(13).setCellValue(dummyData["parentName"].toString()) row.createCell(14).setCellValue(dummyData["parentTel"].toString()) - + // 성적 더미 데이터 val dummyGrades = listOf("A", "B", "A", "B", "A", "B", "A") - for (i in 15..21) { - row.createCell(i).setCellValue(dummyGrades[i - 15]) + for (i in 15..42) { + row.createCell(i).setCellValue(dummyGrades[(i - 15) % dummyGrades.size]) } - for (i in 22..28) { - row.createCell(i).setCellValue(dummyGrades[i - 22]) - } - for (i in 29..35) { - row.createCell(i).setCellValue(dummyGrades[i - 29]) - } - for (i in 36..42) { - row.createCell(i).setCellValue(dummyGrades[i - 36]) - } - + // 점수 더미 데이터 - row.createCell(43).setCellValue("180.0") - row.createCell(44).setCellValue("170.0") - row.createCell(45).setCellValue("165.0") - row.createCell(46).setCellValue("170.5") - row.createCell(47).setCellValue("30.0") - row.createCell(48).setCellValue("15.0") - row.createCell(49).setCellValue("0") - row.createCell(50).setCellValue("0") - row.createCell(51).setCellValue("0") - row.createCell(52).setCellValue("0") - row.createCell(53).setCellValue("20.0") - row.createCell(54).setCellValue("O") - row.createCell(55).setCellValue("X") - row.createCell(56).setCellValue("5.0") - row.createCell(57).setCellValue("210.5") - row.createCell(58).setCellValue("200.0") - row.createCell(59).setCellValue(dummyData["examCode"].toString()) + val scores = listOf( + "180.0", "170.0", "165.0", "170.5", "30.0", "15.0", "0", "0", "0", "0", + "20.0", "O", "X", "5.0", "210.5", "200.0", dummyData["examCode"].toString() + ) + for (i in scores.indices) { + row.createCell(43 + i).setCellValue(scores[i]) + } } } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicationInfo.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicationInfo.kt index dae722ec..c4822fde 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicationInfo.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/model/ApplicationInfo.kt @@ -90,5 +90,4 @@ class ApplicationInfo { row.createCell(58).setCellValue("일반전형(170)") row.createCell(59).setCellValue("수험번호") } - } diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/presentation/ExcelTestController.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/presentation/ExcelTestController.kt index c199a9ad..eb6b173e 100644 --- a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/presentation/ExcelTestController.kt +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/excel/presentation/ExcelTestController.kt @@ -15,9 +15,8 @@ class ExcelTestController( private val printApplicantCodesGenerator: PrintApplicantCodesGenerator, private val printApplicationInfoGenerator: PrintApplicationInfoGenerator, private val printAdmissionTicketGenerator: PrintAdmissionTicketGenerator, - private val printApplicationCheckListGenerator: PrintApplicationCheckListGenerator + private val printApplicationCheckListGenerator: PrintApplicationCheckListGenerator, ) { - @GetMapping("/applicant-codes") fun downloadApplicantCodes(response: HttpServletResponse) { printApplicantCodesGenerator.execute(response) @@ -37,4 +36,4 @@ class ExcelTestController( fun downloadCheckList(response: HttpServletResponse) { printApplicationCheckListGenerator.printApplicationCheckList(response) } -} \ No newline at end of file +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/FeignClientErrorDecoder.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/FeignClientErrorDecoder.kt new file mode 100644 index 00000000..19a00580 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/FeignClientErrorDecoder.kt @@ -0,0 +1,27 @@ +package hs.kr.entrydsm.application.global.feign + +import feign.Response +import feign.codec.ErrorDecoder +import hs.kr.entrydsm.application.global.feign.exception.FeignException + +/** + * Feign Client의 에러를 디코딩하는 클래스입니다. + */ +class FeignClientErrorDecoder : ErrorDecoder { + /** + * Feign 요청 중 발생한 에러를 디코딩합니다. + * + * @param methodKey 요청한 메소드 키 + * @param response 응답 + * @return 디코딩된 예외 + */ + override fun decode( + methodKey: String?, + response: Response, + ): Exception? { + if (response.status() >= 400) { + throw FeignException.FeignServerErrorException(response.status(), methodKey) + } + return feign.FeignException.errorStatus(methodKey, response) + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/FeignConfig.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/FeignConfig.kt new file mode 100644 index 00000000..bdd026ea --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/FeignConfig.kt @@ -0,0 +1,35 @@ +package hs.kr.entrydsm.application.global.feign + +import feign.Logger +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.cloud.openfeign.EnableFeignClients +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +/** + * Feign Client 설정을 위한 클래스입니다. + */ +@EnableFeignClients +@Configuration +class FeignConfig { + /** + * Feign Client의 에러 디코더를 설정합니다. + * + * @return FeignClientErrorDecoder + */ + @Bean + @ConditionalOnMissingBean(value = [FeignClientErrorDecoder::class]) + fun commonFeignErrorDecoder(): FeignClientErrorDecoder? { + return FeignClientErrorDecoder() + } + + /** + * Feign Client의 로거 레벨을 설정합니다. + * + * @return Logger.Level + */ + @Bean + fun feignLoggerLevel(): Logger.Level? { + return Logger.Level.FULL + } +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/client/SchoolClient.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/client/SchoolClient.kt new file mode 100644 index 00000000..9606299f --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/client/SchoolClient.kt @@ -0,0 +1,54 @@ +package hs.kr.entrydsm.application.global.feign.client + +import hs.kr.entrydsm.application.global.feign.FeignConfig +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestParam + +/** + * 나이스 교육정보 개방 포털 API SchoolClient 입니다. + */ +@FeignClient(name = "SchoolClient", url = "\${url.school}", configuration = [FeignConfig::class]) +interface SchoolClient { + /** + * 학교 코드로 학교 정보를 조회합니다. + * + * @param schoolCode 학교 코드 + * @param key API KEY + * @param type 응답 타입 + * @param pageIndex 페이지 인덱스 + * @param pageSize 페이지 사이즈 + * @param schoolKind 학교 종류 + * @return 학교 정보 + */ + @GetMapping + fun getSchoolBySchoolCode( + @RequestParam("SD_SCHUL_CODE") schoolCode: String, + @RequestParam("KEY") key: String, + @RequestParam("Type") type: String = "json", + @RequestParam("pIndex") pageIndex: Int = 1, + @RequestParam("pSize") pageSize: Int = 100, + @RequestParam("SCHUL_KND_SC_NM") schoolKind: String = "중학교", + ): String? + + /** + * 학교 이름으로 학교 리스트를 조회합니다. + * + * @param schoolName 학교 이름 + * @param key API KEY + * @param type 응답 타입 + * @param pageIndex 페이지 인덱스 + * @param pageSize 페이지 사이즈 + * @param schoolKind 학교 종류 + * @return 학교 리스트 + */ + @GetMapping + fun getSchoolListBySchoolName( + @RequestParam("SCHUL_NM") schoolName: String, + @RequestParam("KEY") key: String, + @RequestParam("Type") type: String = "json", + @RequestParam("pIndex") pageIndex: Int = 1, + @RequestParam("pSize") pageSize: Int = 100, + @RequestParam("SCHUL_KND_SC_NM") schoolKind: String = "중학교", + ): String? +} diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/client/dto/SchoolInfoElement.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/client/dto/SchoolInfoElement.kt new file mode 100644 index 00000000..775475bb --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/client/dto/SchoolInfoElement.kt @@ -0,0 +1,131 @@ +package hs.kr.entrydsm.application.global.feign.client.dto + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * 나이스 API 학교 정보 응답을 위한 데이터 클래스 입니다. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +data class SchoolInfoElement( + val schoolInfo: List?, +) + +/** + * 학교 정보를 담는 데이터 클래스 입니다. + * + * @property head 헤더 정보 + * @property row 학교 정보 리스트 + */ +data class SchoolInfo( + val head: List?, + val row: List?, +) + +/** + * 나이스 API 응답의 헤더 정보를 담는 데이터 클래스 입니다. + * + * @property listTotalCount 총 아이템 수 + * @property result 응답 결과 + */ +data class Head( + @JsonProperty("list_total_count") + val listTotalCount: Long?, + @JsonProperty("RESULT") + val result: Result?, +) + +/** + * 나이스 API 응답 결과를 담는 데이터 클래스 입니다. + * + * @property code 응답 코드 + * @property message 응답 메시지 + */ +data class Result( + @JsonProperty("CODE") + val code: String, + @JsonProperty("MESSAGE") + val message: String, +) + +/** + * 나이스 API 학교 정보 상세를 담는 데이터 클래스 입니다. + * + * @property atptOfcdcScCode 시도교육청코드 + * @property atptOfcdcScNm 시도교육청명 + * @property sdSchulCode 표준학교코드 + * @property schulNm 학교명 + * @property engSchulNm 영문학교명 + * @property schulKndScNm 학교종류명 + * @property lctnScNm 소재지명 + * @property juOrgNm 관할조직명 + * @property fondScNm 설립명 + * @property orgRdnzc 도로명우편번호 + * @property orgRdnma 도로명주소 + * @property orgRdnda 도로명상세주소 + * @property orgTelno 전화번호 + * @property hmpgAdres 홈페이지주소 + * @property coeduScNm 남녀공학구분명 + * @property orgFaxno 팩с번호 + * @property hsScNm 고등학교구분명 + * @property indstSpeclCccclExstYn 산업체특별학급존재여부 + * @property hsGnrlBusnsScNm 고등학교일반실업구분명 + * @property spclyPurpsHsOrdNm 특수목적고등학교계열명 + * @property eneBfeSehfScNm 입학전형구분명 + * @property dghtScNm 주야구분명 + * @property fondYmd 설립일자 + * @property foasMemrd 개교기념일 + * @property loadDtm 수정일 + */ +data class Row( + @JsonProperty("ATPT_OFCDC_SC_CODE") + val atptOfcdcScCode: String, + @JsonProperty("ATPT_OFCDC_SC_NM") + val atptOfcdcScNm: String, + @JsonProperty("SD_SCHUL_CODE") + val sdSchulCode: String, + @JsonProperty("SCHUL_NM") + val schulNm: String, + @JsonProperty("ENG_SCHUL_NM") + val engSchulNm: String? = null, + @JsonProperty("SCHUL_KND_SC_NM") + val schulKndScNm: String, + @JsonProperty("LCTN_SC_NM") + val lctnScNm: String, + @JsonProperty("JU_ORG_NM") + val juOrgNm: String, + @JsonProperty("FOND_SC_NM") + val fondScNm: String, + @JsonProperty("ORG_RDNZC") + val orgRdnzc: String, + @JsonProperty("ORG_RDNMA") + val orgRdnma: String, + @JsonProperty("ORG_RDNDA") + val orgRdnda: String, + @JsonProperty("ORG_TELNO") + val orgTelno: String, + @JsonProperty("HMPG_ADRES") + val hmpgAdres: String, + @JsonProperty("COEDU_SC_NM") + val coeduScNm: String, + @JsonProperty("ORG_FAXNO") + val orgFaxno: String? = null, + @JsonProperty("HS_SC_NM") + val hsScNm: Any?, + @JsonProperty("INDST_SPECL_CCCCL_EXST_YN") + val indstSpeclCccclExstYn: String, + @JsonProperty("HS_GNRL_BUSNS_SC_NM") + val hsGnrlBusnsScNm: Any?, + @JsonProperty("SPCLY_PURPS_HS_ORD_NM") + val spclyPurpsHsOrdNm: Any?, + @JsonProperty("ENE_BFE_SEHF_SC_NM") + val eneBfeSehfScNm: String, + @JsonProperty("DGHT_SC_NM") + val dghtScNm: String, + @JsonProperty("FOND_YMD") + val fondYmd: String, + @JsonProperty("FOAS_MEMRD") + val foasMemrd: String, + @JsonProperty("LOAD_DTM") + val loadDtm: String, +) diff --git a/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/exception/FeignException.kt b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/exception/FeignException.kt new file mode 100644 index 00000000..ccb0ead7 --- /dev/null +++ b/casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/global/feign/exception/FeignException.kt @@ -0,0 +1,21 @@ +package hs.kr.entrydsm.application.global.feign.exception + +import hs.kr.entrydsm.global.exception.DomainException +import hs.kr.entrydsm.global.exception.ErrorCode + +/** + * Feign 관련 최상위 예외 클래스 입니다. + */ +sealed class FeignException( + errorCode: ErrorCode, + message: String, +) : DomainException(errorCode, message) { + + /** + * Feign 서버 오류시 발생하는 예외입니다. + */ + class FeignServerErrorException(statusCode: Int, methodKey: String?) : FeignException( + errorCode = ErrorCode.FEIGN_SERVER_ERROR, + message = "Feign server error: $statusCode for method: $methodKey", + ) +}