diff --git a/build.gradle.kts b/build.gradle.kts index fc60a1f..a4255d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,6 +50,11 @@ dependencies { implementation("net.ttddyy:datasource-proxy:1.9") + +// // 로그백 의존성 + implementation(group = "ca.pjer", name = "logback-awslogs-appender", version = "1.6.0") + // 프로퍼티 제어 in xml + implementation("org.codehaus.janino:janino:3.1.7") } tasks.withType { diff --git a/src/main/kotlin/com/hanghea99/commerce/CommerceApplication.kt b/src/main/kotlin/com/hanghea99/commerce/CommerceApplication.kt index a1ad237..4771484 100644 --- a/src/main/kotlin/com/hanghea99/commerce/CommerceApplication.kt +++ b/src/main/kotlin/com/hanghea99/commerce/CommerceApplication.kt @@ -1,7 +1,6 @@ package com.hanghea99.commerce import org.slf4j.LoggerFactory -import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.data.jpa.repository.config.EnableJpaAuditing diff --git a/src/main/kotlin/com/hanghea99/commerce/api/Manager/Domain/PostManagerLoginRequest.kt b/src/main/kotlin/com/hanghea99/commerce/api/Manager/Domain/PostManagerLoginRequest.kt new file mode 100644 index 0000000..945b9de --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/api/Manager/Domain/PostManagerLoginRequest.kt @@ -0,0 +1,8 @@ +package com.hanghea99.commerce.api.manager.domain + +import com.hanghea99.commerce.api.common.domain.core.CoreRequest + +data class PostManagerLoginRequest( + val id: String, + val password: String +) : CoreRequest() \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/api/Manager/Domain/PostManagerSellerPermitRequest.kt b/src/main/kotlin/com/hanghea99/commerce/api/Manager/Domain/PostManagerSellerPermitRequest.kt new file mode 100644 index 0000000..3efea33 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/api/Manager/Domain/PostManagerSellerPermitRequest.kt @@ -0,0 +1,9 @@ +package com.hanghea99.commerce.api.manager.domain + +import com.hanghea99.commerce.api.common.domain.core.CoreRequest +import com.hanghea99.commerce.api.common.domain.seller.SellerVo + +data class PostManagerSellerPermitRequest ( + val sellers: List +): CoreRequest() + diff --git a/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/ManagerManager.kt b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/ManagerManager.kt new file mode 100644 index 0000000..d0048b3 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/ManagerManager.kt @@ -0,0 +1,24 @@ +package com.hanghea99.commerce.api.common.comp.impl + +import com.hanghea99.commerce.api.common.comp.ManagerComponent +import com.hanghea99.commerce.database.entity.ManagerEntity +import com.hanghea99.commerce.database.entity.StoreEntity +import com.hanghea99.commerce.database.repository.ManagerRepository +import com.hanghea99.commerce.database.repository.StoreRepository +import org.springframework.stereotype.Component + +@Component +class ManagerManager(var managerRepository: ManagerRepository) : ManagerComponent() { + override fun update(entities: List): Long { + return managerRepository.saveAllAndFlush(entities).count().toLong() + } + + override fun delete(entityIds: List) { + managerRepository.deleteAllById(entityIds) + } + + override fun create(entities: List): List { + return managerRepository.saveAllAndFlush(entities) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/ManagerReader.kt b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/ManagerReader.kt new file mode 100644 index 0000000..6a0a7f8 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/ManagerReader.kt @@ -0,0 +1,66 @@ +package com.hanghea99.commerce.api.common.comp.impl + +import com.hanghea99.commerce.api.common.comp.ReaderComponent +import com.hanghea99.commerce.database.entity.ManagerEntity +import com.hanghea99.commerce.database.entity.QManagerEntity +import com.hanghea99.commerce.database.repository.ManagerRepository +import com.querydsl.core.BooleanBuilder +import com.querydsl.core.types.OrderSpecifier +import com.querydsl.jpa.impl.JPAQueryFactory +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class ManagerReader( + val managerRepository: ManagerRepository, + val jpaQueryFactory: JPAQueryFactory, +) : + ReaderComponent( + ) { + private val log = LoggerFactory.getLogger("StoreReader") + + val qManagerEntity: QManagerEntity = QManagerEntity("m1") + + override fun read(id: String): ManagerEntity { + TODO("Not yet implemented") + } + + override fun readAll(ids: List): List { + TODO("Not yet implemented") + } + + override fun readAllCount(ids: List): Long { + TODO("Not yet implemented") + } + + override fun read(where: BooleanBuilder): ManagerEntity? { + val orders: MutableList> = mutableListOf() + return jpaQueryFactory.selectFrom(qManagerEntity) + .where(where) + .fetchFirst() + } + + override fun readAll( + where: BooleanBuilder, + offset: Long, + count: Long, + orders: MutableList>, + ): List { + val orders: MutableList> = mutableListOf() + return jpaQueryFactory.selectFrom(qManagerEntity) + .where(where) + .orderBy(*orders.toTypedArray()) + .offset(offset) + .limit(count) + .fetch() + } + + override fun readAllCount( + where: BooleanBuilder, + ): Long { + return jpaQueryFactory.selectFrom(qManagerEntity) + .where(where) + .fetchCount() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/SellerManager.kt b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/SellerManager.kt new file mode 100644 index 0000000..cbcf663 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/SellerManager.kt @@ -0,0 +1,23 @@ +package com.hanghea99.commerce.api.common.comp.impl + +import com.hanghea99.commerce.api.common.comp.ManagerComponent +import com.hanghea99.commerce.database.entity.SellerEntity +import com.hanghea99.commerce.database.repository.SellerRepository +import org.springframework.stereotype.Component + +@Component +class SellerManager(var sellerRepository: SellerRepository) : + ManagerComponent() { + override fun update(entities: List): Long { + return sellerRepository.saveAllAndFlush(entities).count().toLong() + } + + override fun delete(entityIds: List) { + sellerRepository.deleteAllById(entityIds) + } + + override fun create(entities: List): List { + return sellerRepository.saveAllAndFlush(entities) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/SellerReader.kt b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/SellerReader.kt new file mode 100644 index 0000000..a2dd40e --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/api/common/comp/impl/SellerReader.kt @@ -0,0 +1,63 @@ +package com.hanghea99.commerce.api.common.comp.impl + +import com.hanghea99.commerce.api.common.comp.ReaderComponent +import com.hanghea99.commerce.database.entity.QSellerEntity +import com.hanghea99.commerce.database.entity.SellerEntity +import com.hanghea99.commerce.database.repository.SellerRepository +import com.querydsl.core.BooleanBuilder +import com.querydsl.core.types.OrderSpecifier +import com.querydsl.jpa.impl.JPAQueryFactory +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class SellerReader( + val selerRepository: SellerRepository, + val jpaQueryFactory: JPAQueryFactory, +) : + ReaderComponent( + ) { + private val log = LoggerFactory.getLogger("StoreReader") + + val qSellerEntity: QSellerEntity = QSellerEntity("m1") + + override fun read(id: Long): SellerEntity { + TODO("Not yet implemented") + } + + override fun readAll(ids: List): List { + TODO("Not yet implemented") + } + + override fun readAllCount(ids: List): Long { + TODO("Not yet implemented") + } + + override fun read(where: BooleanBuilder): SellerEntity { + TODO("Not yet implemented") + } + + override fun readAll( + where: BooleanBuilder, + offset: Long, + count: Long, + orders: MutableList>, + ): List { + val orders: MutableList> = mutableListOf() + return jpaQueryFactory.selectFrom(qSellerEntity) + .where(where) + .orderBy(*orders.toTypedArray()) + .offset(offset) + .limit(count) + .fetch() + } + + override fun readAllCount( + where: BooleanBuilder, + ): Long { + return jpaQueryFactory.selectFrom(qSellerEntity) + .where(where) + .fetchCount() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/api/common/domain/seller/SellerVo.kt b/src/main/kotlin/com/hanghea99/commerce/api/common/domain/seller/SellerVo.kt new file mode 100644 index 0000000..4f78caa --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/api/common/domain/seller/SellerVo.kt @@ -0,0 +1,25 @@ +package com.hanghea99.commerce.api.common.domain.seller + +import java.time.Instant + +data class SellerVo( + var sellerId: String?, + var status: String?, + var password: String?, + var name: String?, + var ssn: String?, + var telecomName: String?, + var phoneNumber: String?, + var companyName: String?, + var businessRegestraionNumber: String?, + var representativeName: String?, + var representativeTelephoneNumber: String?, + var faxNumber: String?, + var businessZipCode: String?, + var businiessAddress: String?, + var createdAt: Instant?, + var updatedAt: Instant?, + var deletedAt: Instant?, + var allowedAt: Instant?, + var blockedAt: Instant?, +) diff --git a/src/main/kotlin/com/hanghea99/commerce/common/HttpLogMessage.kt b/src/main/kotlin/com/hanghea99/commerce/common/HttpLogMessage.kt new file mode 100644 index 0000000..ecbced4 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/common/HttpLogMessage.kt @@ -0,0 +1,65 @@ +package com.hanghea99.commerce.common + +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.http.HttpStatus +import org.springframework.web.util.ContentCachingRequestWrapper +import org.springframework.web.util.ContentCachingResponseWrapper +import java.lang.StringBuilder +import java.nio.charset.Charset + +data class HttpLogMessage( + val httpMethod: String, + val requestUri: String, + val httpStatus: HttpStatus, + val clientIp: String, + val elapsedTime: Double, + val headers: String?, + val requestParam: String?, + val requestBody: String?, + val responseBody: String?, +) { + companion object { + val objectMapper = ObjectMapper() + + fun createInstance( + requestWrapper: ContentCachingRequestWrapper, + responseWrapper: ContentCachingResponseWrapper, + elapsedTime: Double + ): HttpLogMessage { + val headerMap = mutableMapOf() + val sb = StringBuilder() + for( headerName in requestWrapper.headerNames){ + sb.append("${headerName}=${requestWrapper.getHeader(headerName)} ") +// headerMap[headerName] = requestWrapper.getHeader(headerName) + } + + + + return HttpLogMessage( + httpMethod = requestWrapper.method, + requestUri = requestWrapper.requestURI, + httpStatus = HttpStatus.valueOf(responseWrapper.status), + clientIp = requestWrapper.remoteAddr, + elapsedTime = elapsedTime, +// headers = objectMapper.writeValueAsString(headerMap), + headers = sb.toString(), + requestParam = objectMapper.writeValueAsString(requestWrapper.parameterMap), + requestBody = requestWrapper.contentAsByteArray.toString(charset = Charset.defaultCharset()), + responseBody = responseWrapper.contentAsByteArray.toString(charset = Charset.defaultCharset()), + ) + } + } + + // 이부분은 각자 취향대로 포멧 정하는 것으로,,, + fun toPrettierLog(): String { + return """ + | + |[REQUEST] ${this.httpMethod} ${this.requestUri} ${this.httpStatus} (${this.elapsedTime}) + |>> CLIENT_IP: ${this.clientIp} + |>> HEADERS: ${this.headers} + |>> REQUEST_PARAM: ${this.requestParam} + |>> REQUEST_BODY: ${this.requestBody} + |>> RESPONSE_BODY: ${this.responseBody} + """.trimMargin() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/common/ReqResLogginFilter.kt b/src/main/kotlin/com/hanghea99/commerce/common/ReqResLogginFilter.kt new file mode 100644 index 0000000..a8ab007 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/common/ReqResLogginFilter.kt @@ -0,0 +1,52 @@ +package com.hanghea99.commerce.common + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.slf4j.MDC +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.util.ContentCachingRequestWrapper +import org.springframework.web.util.ContentCachingResponseWrapper +import java.util.* + +@Component +class ReqResLogginFilter : OncePerRequestFilter() { + private val log = logger + + companion object { + const val REQUEST_ID = "request_id" + } + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val cachingRequestWrapper = ContentCachingRequestWrapper(request) + val cachingResponseWrapper = ContentCachingResponseWrapper(response) + val requestId = UUID.randomUUID().toString().substring(0, 8) + + MDC.put(REQUEST_ID, requestId)// -> MCD 저장시 멀티 스레드 스레드가 바뀌면서 MDC가 날아갈수 있다 req id 꼬일수 있음 + // 올바르게 잘 전달 되는지 테스트 및 확인 필요 + + + val startTime = System.currentTimeMillis() + filterChain.doFilter(cachingRequestWrapper, cachingResponseWrapper) + val end = System.currentTimeMillis() + + try { + log.info(HttpLogMessage.createInstance( + requestWrapper = cachingRequestWrapper, + responseWrapper = cachingResponseWrapper, + elapsedTime = (end - startTime) / 1000.0 + ).toPrettierLog()) + + cachingResponseWrapper.copyBodyToResponse() + } catch (e: Exception) { + log.error("Logging 실패", e) + } + + MDC.remove(REQUEST_ID) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/configuration/database/DataSourceConfig.kt b/src/main/kotlin/com/hanghea99/commerce/configuration/database/DataSourceConfig.kt index 4c36cf3..9c18e42 100644 --- a/src/main/kotlin/com/hanghea99/commerce/configuration/database/DataSourceConfig.kt +++ b/src/main/kotlin/com/hanghea99/commerce/configuration/database/DataSourceConfig.kt @@ -1,9 +1,8 @@ package com.hanghea99.commerce.configuration.database +import com.hanghea99.commerce.logger import jakarta.persistence.EntityManagerFactory -import net.ttddyy.dsproxy.listener.logging.DefaultQueryLogEntryCreator -import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener -import net.ttddyy.dsproxy.listener.logging.SystemOutQueryLoggingListener +import net.ttddyy.dsproxy.listener.logging.* import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder import org.hibernate.cfg.AvailableSettings import org.hibernate.engine.jdbc.internal.FormatStyle @@ -36,6 +35,8 @@ import javax.sql.DataSource ) class DataSourceConfig(var env: Environment) { + val log = logger() + @Primary @Bean(name = ["masterDataSource"]) @ConfigurationProperties(prefix = "spring.datasource.write") @@ -55,14 +56,22 @@ class DataSourceConfig(var env: Environment) { @Qualifier("masterDataSource") masterDataSource: DataSource?, @Qualifier("slaveDataSource") slaveDataSource: DataSource? ): DataSource { - return ReplicationRoutingDataSource(masterDataSource!!, slaveDataSource!!) - } - - private class PrettyQueryEntryCreator : DefaultQueryLogEntryCreator() { - private val formatter = FormatStyle.HIGHLIGHT.formatter - override fun formatQuery(query: String): String { - return formatter.format(query) - } + return ReplicationRoutingDataSource( + ProxyDataSourceBuilder + .create(masterDataSource) + .name("masterDataSource") + .countQuery() + .logQueryBySlf4j(SLF4JLogLevel.ERROR, this.javaClass.name) + .logSlowQueryToSysOut(1, TimeUnit.MINUTES) + .build(), + ProxyDataSourceBuilder + .create(slaveDataSource) + .name("slaveDataSource") + .countQuery() + .logQueryBySlf4j(SLF4JLogLevel.ERROR, this.javaClass.name) + .logSlowQueryToSysOut(1, TimeUnit.MINUTES) + .build() + ) } @Profile("dev|default") @@ -71,25 +80,19 @@ class DataSourceConfig(var env: Environment) { @Qualifier("masterDataSource") masterDataSource: DataSource?, @Qualifier("slaveDataSource") slaveDataSource: DataSource? ): DataSource { - val creator = PrettyQueryEntryCreator() - creator.isMultiline = true - val listener = SystemOutQueryLoggingListener() - listener.queryLogEntryCreator = creator return ReplicationRoutingDataSource( ProxyDataSourceBuilder .create(masterDataSource) .name("masterDataSource") .countQuery() - .multiline() - .listener(listener) + .logQueryBySlf4j(SLF4JLogLevel.INFO, this.javaClass.name) .logSlowQueryToSysOut(1, TimeUnit.MINUTES) .build(), ProxyDataSourceBuilder .create(slaveDataSource) .name("slaveDataSource") .countQuery() - .multiline() - .listener(listener) + .logQueryBySlf4j(SLF4JLogLevel.INFO, this.javaClass.name) .logSlowQueryToSysOut(1, TimeUnit.MINUTES) .build() ) diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/CartEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/CartEntity.kt index 0132f35..0019a90 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/CartEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/CartEntity.kt @@ -30,7 +30,7 @@ open class CartEntity( @MapsId("userId") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "USER_ID", nullable = false) + @JoinColumn(name = "USER_ID", nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var userEntity: UserEntity? = userEntity @OneToMany(mappedBy = "cartEntity") diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/CartItemEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/CartItemEntity.kt index a59d2d4..03506ee 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/CartItemEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/CartItemEntity.kt @@ -35,11 +35,11 @@ open class CartItemEntity( @MapsId("cartKey") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "CART_KEY", nullable = false, referencedColumnName = "CART_KEY") + @JoinColumn(name = "CART_KEY", nullable = false, referencedColumnName = "CART_KEY", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var cartEntity: CartEntity? = cartEntity @MapsId("optionKey") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "OPTION_KEY", nullable = false, referencedColumnName = "OPTION_KEY") + @JoinColumn(name = "OPTION_KEY", nullable = false, referencedColumnName = "OPTION_KEY", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var storeItemOptionEntity: StoreItemOptionEntity? = storeItemOptionEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryAddressEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryAddressEntity.kt index 9122f7b..a93d108 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryAddressEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryAddressEntity.kt @@ -37,7 +37,7 @@ open class DeliveryAddressEntity( @MapsId("userId") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "USER_ID", nullable = false) + @JoinColumn(name = "USER_ID", nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var userEntity: UserEntity? = userEntity @Size(max = 20) diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryEntity.kt index 27efabb..640b6ed 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/DeliveryEntity.kt @@ -35,7 +35,8 @@ open class DeliveryEntity( @JoinColumn( name = "PURCHASE_ITEM_KEY", nullable = false, - referencedColumnName = "PURCHASE_ITEM_KEY" + referencedColumnName = "PURCHASE_ITEM_KEY", + foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT), ) open var purchaseItemEntity: PurchaseItemEntity? = purchaseItemEntity diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteEntity.kt index 38fde7d..2cf485b 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteEntity.kt @@ -30,7 +30,7 @@ open class FavoriteEntity( @MapsId("userId") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "USER_ID", nullable = false) + @JoinColumn(name = "USER_ID", nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var userEntity: UserEntity? = userEntity @OneToMany(mappedBy = "favoriteEntity") diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteItemEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteItemEntity.kt index f35c21b..25f9a44 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteItemEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/FavoriteItemEntity.kt @@ -35,11 +35,11 @@ open class FavoriteItemEntity( @MapsId("itemKey") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "ITEM_KEY", nullable = false, referencedColumnName = "ITEM_KEY") + @JoinColumn(name = "ITEM_KEY", nullable = false, referencedColumnName = "ITEM_KEY", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var storeItemEntity: StoreItemEntity? = storeItemEntity @MapsId("favoriteKey") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "FAVORITE_KEY", nullable = false, referencedColumnName = "FAVORITE_KEY") + @JoinColumn(name = "FAVORITE_KEY", nullable = false, referencedColumnName = "FAVORITE_KEY", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var favoriteEntity: FavoriteEntity? = favoriteEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/PaymentEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/PaymentEntity.kt index 2b10cfc..cea5f4a 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/PaymentEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/PaymentEntity.kt @@ -31,7 +31,7 @@ open class PaymentEntity( @MapsId("purchaseKey") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "PURCHASE_KEY", nullable = false, referencedColumnName = "PURCHASE_KEY") + @JoinColumn(name = "PURCHASE_KEY", nullable = false, referencedColumnName = "PURCHASE_KEY", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var purchaseEntity: PurchaseEntity? = purchaseEntity @Size(max = 20) diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseEntity.kt index 5f25706..287aa53 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseEntity.kt @@ -38,7 +38,7 @@ open class PurchaseEntity( @MapsId("userId") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "USER_ID", nullable = false) + @JoinColumn(name = "USER_ID", nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var userEntity: UserEntity? = userEntity @Column(name = "TOTAL_PRICE", precision = 20, scale = 2) diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseItemEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseItemEntity.kt index 0f6c53d..d74c87c 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseItemEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/PurchaseItemEntity.kt @@ -42,17 +42,17 @@ open class PurchaseItemEntity( @MapsId("reviewId") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "REVIEW_ID", nullable = false) + @JoinColumn(name = "REVIEW_ID", nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var reviewEntity: ReviewEntity? = reviewEntity @MapsId("purchaseKey") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "PURCHASE_KEY", nullable = false, referencedColumnName = "PURCHASE_KEY") + @JoinColumn(name = "PURCHASE_KEY", nullable = false, referencedColumnName = "PURCHASE_KEY", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var purchaseEntity: PurchaseEntity? = purchaseEntity @MapsId("optionKey") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "OPTION_KEY", nullable = false, referencedColumnName = "OPTION_KEY") + @JoinColumn(name = "OPTION_KEY", nullable = false, referencedColumnName = "OPTION_KEY", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var storeItemoptionEntity: StoreItemOptionEntity? = storeItemoptionEntity @OneToMany(mappedBy = "purchaseItemEntity") diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/SellerRequestRegistrationEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/SellerRequestRegistrationEntity.kt index 1426304..689f767 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/SellerRequestRegistrationEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/SellerRequestRegistrationEntity.kt @@ -35,7 +35,7 @@ open class SellerRequestRegistrationEntity( @MapsId("sellerId") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "SELLER_ID", nullable = false) + @JoinColumn(name = "SELLER_ID", nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var sellerEntity: SellerEntity? = sellerEntity @Size(max = 10) diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/StoreEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/StoreEntity.kt index 4faa4b2..ae5bf33 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/StoreEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/StoreEntity.kt @@ -52,7 +52,7 @@ open class StoreEntity( @MapsId("sellerId") @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "SELLER_ID", nullable = false) + @JoinColumn(name = "SELLER_ID", nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT)) open var sellerEntity: SellerEntity? = sellerEntity @Size(max = 10) diff --git a/src/main/kotlin/com/hanghea99/commerce/database/entity/UserEntity.kt b/src/main/kotlin/com/hanghea99/commerce/database/entity/UserEntity.kt index 2c7dd9a..fb512be 100644 --- a/src/main/kotlin/com/hanghea99/commerce/database/entity/UserEntity.kt +++ b/src/main/kotlin/com/hanghea99/commerce/database/entity/UserEntity.kt @@ -7,7 +7,7 @@ import org.hibernate.annotations.UpdateTimestamp import java.time.Instant @Entity -@Table(name = "USER") +@Table(name = "`USER`") open class UserEntity( id: Long? = null, email: Long? = null, diff --git a/src/main/kotlin/com/hanghea99/commerce/exception/BusinessException.kt b/src/main/kotlin/com/hanghea99/commerce/exception/BusinessException.kt new file mode 100644 index 0000000..a334c1d --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/exception/BusinessException.kt @@ -0,0 +1,14 @@ +package com.hanghea99.commerce.exception + +open class BusinessException:RuntimeException { + var errorCode: ErrorCode + + private set + constructor(message: String?, errorCode: ErrorCode) : super(message) { + this.errorCode = errorCode + } + + constructor(errorCode: ErrorCode) : super(errorCode.message) { + this.errorCode = errorCode + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/exception/EntityNotFoundException.kt b/src/main/kotlin/com/hanghea99/commerce/exception/EntityNotFoundException.kt new file mode 100644 index 0000000..794abae --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/exception/EntityNotFoundException.kt @@ -0,0 +1,3 @@ +package com.hanghea99.commerce.exception + +class EntityNotFoundException(message: String?) : BusinessException(message, ErrorCode.ENTITY_NOT_FOUND) \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/exception/ErrorCode.kt b/src/main/kotlin/com/hanghea99/commerce/exception/ErrorCode.kt new file mode 100644 index 0000000..bb5861a --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/exception/ErrorCode.kt @@ -0,0 +1,29 @@ +package com.hanghea99.commerce.exception + +import com.fasterxml.jackson.annotation.JsonFormat + +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +enum class ErrorCode(val status: Int, val code: String, val message: String) { + // Common + INVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"), METHOD_NOT_ALLOWED( + 405, + "C002", + " Invalid Input Value" + ), + ENTITY_NOT_FOUND(400, "C003", " Entity Not Found"), INTERNAL_SERVER_ERROR( + 500, + "C004", + "Server Error" + ), + INVALID_TYPE_VALUE(400, "C005", " Invalid Type Value"), HANDLE_ACCESS_DENIED( + 403, + "C006", + "Access is Denied" + ), // User + EMAIL_DUPLICATION(400, "M001", "Email is Duplication"), LOGIN_INPUT_INVALID( + 400, + "M002", + "Login input is invalid" + ), + +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/exception/ErrorResponse.kt b/src/main/kotlin/com/hanghea99/commerce/exception/ErrorResponse.kt new file mode 100644 index 0000000..2c21035 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/exception/ErrorResponse.kt @@ -0,0 +1,72 @@ +package com.hanghea99.commerce.exception + +import org.springframework.validation.BindingResult +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException +import java.util.stream.Collectors +import java.util.ArrayList; + +class ErrorResponse { + private var message: String + private var status: Int + private var errors: List + private var code: String + + private constructor(code: ErrorCode, errors: List) { + this.message = code.message + this.status = code.status + this.errors = errors + this.code = code.code + } + + private constructor(code: ErrorCode) { + this.message = code.message + this.status = code.status + this.code = code.code + this.errors = ArrayList() + } + class FieldError private constructor( + private val field: String, + private val value: String, + private val reason: String? + ) { + companion object { + fun of(field: String, value: String, reason: String): List { + val fieldErrors: MutableList = ArrayList() + fieldErrors.add(FieldError(field, value, reason)) + return fieldErrors + } + fun of(bindingResult: BindingResult): List { + val fieldErrors: List = bindingResult.getFieldErrors() + return fieldErrors.stream() + .map { error -> + FieldError( + error.field, + if (error.rejectedValue == null) "" else error.rejectedValue.toString(), + error.defaultMessage + ) + } + .collect(Collectors.toList()) + } + } + } + + companion object { + fun of(code: ErrorCode, bindingResult: BindingResult): ErrorResponse { + return ErrorResponse(code, FieldError.of(bindingResult)) + } + + fun of(code: ErrorCode): ErrorResponse { + return ErrorResponse(code) + } + + fun of(code: ErrorCode, errors: List): ErrorResponse { + return ErrorResponse(code, errors) + } + + fun of(e: MethodArgumentTypeMismatchException): ErrorResponse { + val value = if (e.getValue() == null) "" else e.getValue().toString() + val errors = FieldError.of(e.getName(), value, e.getErrorCode()) + return ErrorResponse(ErrorCode.INVALID_TYPE_VALUE, errors) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/exception/GlobalExceptionHandler.kt b/src/main/kotlin/com/hanghea99/commerce/exception/GlobalExceptionHandler.kt new file mode 100644 index 0000000..ee4740b --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/exception/GlobalExceptionHandler.kt @@ -0,0 +1,87 @@ +package com.hanghea99.commerce.exception + +import com.hanghea99.commerce.logger + +import java.nio.file.AccessDeniedException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +@ControllerAdvice +class GlobalExceptionHandler { + + private val log = logger() + /** + * javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다. + * HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생 + * 주로 @RequestBody, @RequestPart 어노테이션에서 발생 + */ + @ExceptionHandler(MethodArgumentNotValidException::class) + protected fun handleMethodArgumentNotValidException(e: MethodArgumentNotValidException): ResponseEntity { + log.error("handleMethodArgumentNotValidException", e) + val response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.bindingResult) + return ResponseEntity(response, HttpStatus.BAD_REQUEST) + } + + /** + * @ModelAttribut 으로 binding error 발생시 BindException 발생한다. + * ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args + */ + @ExceptionHandler(BindException::class) + protected fun handleBindException(e: BindException): ResponseEntity { + log.error("handleBindException", e) + val response: ErrorResponse = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult()) + return ResponseEntity(response, HttpStatus.BAD_REQUEST) + } + + /** + * enum type 일치하지 않아 binding 못할 경우 발생 + * 주로 @RequestParam enum으로 binding 못했을 경우 발생 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException::class) + protected fun handleMethodArgumentTypeMismatchException(e: MethodArgumentTypeMismatchException?): ResponseEntity { + log.error("handleMethodArgumentTypeMismatchException", e) + val response: ErrorResponse = ErrorResponse.of(ErrorCode.INVALID_TYPE_VALUE) + return ResponseEntity(response, HttpStatus.BAD_REQUEST) + } + + /** + * 지원하지 않은 HTTP method 호출 할 경우 발생 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException::class) + protected fun handleHttpRequestMethodNotSupportedException(e: HttpRequestMethodNotSupportedException?): ResponseEntity { + log.error("handleHttpRequestMethodNotSupportedException", e) + val response = ErrorResponse.of(ErrorCode.METHOD_NOT_ALLOWED) + return ResponseEntity(response, HttpStatus.METHOD_NOT_ALLOWED) + } + + /** + * Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생합 + */ + @ExceptionHandler(AccessDeniedException::class) + protected fun handleAccessDeniedException(e: AccessDeniedException?): ResponseEntity { + log.error("handleAccessDeniedException", e) + val response = ErrorResponse.of(ErrorCode.HANDLE_ACCESS_DENIED) + return ResponseEntity(response, HttpStatus.valueOf(ErrorCode.HANDLE_ACCESS_DENIED.status)) + } + + @ExceptionHandler(BusinessException::class) + protected fun handleBusinessException(e: BusinessException): ResponseEntity { + log.error("handleEntityNotFoundException", e) + val errorCode: ErrorCode = e.errorCode + val response = ErrorResponse.of(errorCode) + return ResponseEntity(response, HttpStatus.valueOf(errorCode.status)) + } + + @ExceptionHandler(Exception::class) + protected fun handleException(e: Exception?): ResponseEntity { + log.error("handleEntityNotFoundException", e) + val response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR) + return ResponseEntity(response, HttpStatus.INTERNAL_SERVER_ERROR) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/hanghea99/commerce/exception/InvalidValueException.kt b/src/main/kotlin/com/hanghea99/commerce/exception/InvalidValueException.kt new file mode 100644 index 0000000..aa10ac5 --- /dev/null +++ b/src/main/kotlin/com/hanghea99/commerce/exception/InvalidValueException.kt @@ -0,0 +1,6 @@ +package com.hanghea99.commerce.exception + +class InvalidValueException : BusinessException { + constructor(value: String?) : super(value, ErrorCode.INVALID_INPUT_VALUE) + constructor(value: String?, errorCode: ErrorCode?) : super(value, errorCode!!) +} \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..084b216 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,61 @@ + + + + + INFO + + + %d{yyyyMMdd'T'HHmmss} %thread %level %logger{15} %msg%n + + + + + ${Log_Group_Name:local} + + + + + + ${Log_Group_Name:dev} + + + + + + ${Log_Group_Name:prod} + + + HANGHAE-99 + error- + ap-northeast-2 + 50 + 30000 + 5000 + 0 + ${AWS_ACCESS_KEY} + ${AWS_SECRET_KEY} + + + + + + + %d{yyyyMMdd'T'HHmmss} %thread %level %logger{15} %msg%n + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index e0fd717..98cccc0 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -6,7 +6,7 @@ spring: jpa: hibernate: default_batch_fetch_size: 500 - ddl-auto: update + ddl-auto: create open-in-view: false properties: hibernate.jdbc.batch_size: 100