api sample
This commit is contained in:
@@ -6,8 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class KamcoBackApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(KamcoBackApplication.class, args);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(KamcoBackApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,25 @@
|
||||
package com.kamco.cd.kamcoback.common.api;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.api.HelloDto.Res;
|
||||
import com.kamco.cd.kamcoback.common.service.HelloService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/hello")
|
||||
public class HelloApiController {
|
||||
|
||||
private final HelloService helloService;
|
||||
|
||||
@GetMapping
|
||||
public HelloDto.Res hello(HelloDto.Req req) {
|
||||
req.valid();
|
||||
|
||||
Res res = helloService.sayHello(req);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
package com.kamco.cd.kamcoback.common.api;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
public class HelloDto {
|
||||
|
||||
public static class Req{
|
||||
private String id;
|
||||
}
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Req {
|
||||
private String id;
|
||||
|
||||
public void valid() {
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Res {
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
@Builder
|
||||
public Res(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.kamco.cd.kamcoback.common.service;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Base Core Service Interface
|
||||
*
|
||||
* <p>CRUD operations를 정의하는 기본 서비스 인터페이스
|
||||
*
|
||||
* @param <T> Entity 타입
|
||||
* @param <ID> Entity의 ID 타입
|
||||
* @param <S> Search Request 타입
|
||||
*/
|
||||
public interface BaseCoreService<T, ID, S> {
|
||||
|
||||
/**
|
||||
* ID로 엔티티를 삭제합니다.
|
||||
*
|
||||
* @param id 삭제할 엔티티의 ID
|
||||
*/
|
||||
void remove(ID id);
|
||||
|
||||
/**
|
||||
* ID로 단건 조회합니다.
|
||||
*
|
||||
* @param id 조회할 엔티티의 ID
|
||||
* @return 조회된 엔티티
|
||||
*/
|
||||
T getOneById(ID id);
|
||||
|
||||
/**
|
||||
* 검색 조건과 페이징으로 조회합니다.
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 검색 결과
|
||||
*/
|
||||
Page<T> search(S searchReq);
|
||||
}
|
||||
@@ -1,4 +1,17 @@
|
||||
package com.kamco.cd.kamcoback.common.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.api.HelloDto;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class HelloService {
|
||||
|
||||
public HelloDto.Res sayHello(HelloDto.Req req) {
|
||||
log.info("hello");
|
||||
String name = UUID.randomUUID().toString();
|
||||
return HelloDto.Res.builder().id(req.getId()).name(name).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import java.io.IOException;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonReader;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> {
|
||||
|
||||
public GeometryDeserializer(Class<T> targetType) {
|
||||
@@ -20,7 +19,7 @@ public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T>
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||
throws IOException, JacksonException {
|
||||
throws IOException, JacksonException {
|
||||
String json = jsonParser.readValueAsTree().toString();
|
||||
|
||||
if (!StringUtils.hasText(json)) {
|
||||
|
||||
@@ -3,11 +3,10 @@ package com.kamco.cd.kamcoback.common.utils.geometry;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonWriter;
|
||||
|
||||
public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> {
|
||||
|
||||
@@ -17,8 +16,9 @@ public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
public void serialize(
|
||||
T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
if (Objects.nonNull(geometry)) {
|
||||
// default: 8자리 강제로 반올림시킴. 16자리로 늘려줌
|
||||
GeoJsonWriter writer = new GeoJsonWriter(16);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.kamco.cd.kamcoback.config;
|
||||
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import javax.sql.DataSource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -15,85 +14,85 @@ import org.springframework.stereotype.Component;
|
||||
@RequiredArgsConstructor
|
||||
public class StartupLogger {
|
||||
|
||||
private final Environment environment;
|
||||
private final DataSource dataSource;
|
||||
private final Environment environment;
|
||||
private final DataSource dataSource;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void logStartupInfo() {
|
||||
String[] activeProfiles = environment.getActiveProfiles();
|
||||
String profileInfo =
|
||||
activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default";
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void logStartupInfo() {
|
||||
String[] activeProfiles = environment.getActiveProfiles();
|
||||
String profileInfo =
|
||||
activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default";
|
||||
|
||||
// Database connection information
|
||||
String dbUrl = environment.getProperty("spring.datasource.url");
|
||||
String dbUsername = environment.getProperty("spring.datasource.username");
|
||||
String dbDriver = environment.getProperty("spring.datasource.driver-class-name");
|
||||
// Database connection information
|
||||
String dbUrl = environment.getProperty("spring.datasource.url");
|
||||
String dbUsername = environment.getProperty("spring.datasource.username");
|
||||
String dbDriver = environment.getProperty("spring.datasource.driver-class-name");
|
||||
|
||||
// HikariCP pool settings
|
||||
String poolInfo = "";
|
||||
if (dataSource instanceof HikariDataSource hikariDs) {
|
||||
poolInfo =
|
||||
String.format(
|
||||
"""
|
||||
│ Pool Size : min=%d, max=%d
|
||||
│ Connection Timeout: %dms
|
||||
│ Idle Timeout : %dms
|
||||
│ Max Lifetime : %dms""",
|
||||
hikariDs.getMinimumIdle(),
|
||||
hikariDs.getMaximumPoolSize(),
|
||||
hikariDs.getConnectionTimeout(),
|
||||
hikariDs.getIdleTimeout(),
|
||||
hikariDs.getMaxLifetime());
|
||||
}
|
||||
// HikariCP pool settings
|
||||
String poolInfo = "";
|
||||
if (dataSource instanceof HikariDataSource hikariDs) {
|
||||
poolInfo =
|
||||
String.format(
|
||||
"""
|
||||
│ Pool Size : min=%d, max=%d
|
||||
│ Connection Timeout: %dms
|
||||
│ Idle Timeout : %dms
|
||||
│ Max Lifetime : %dms""",
|
||||
hikariDs.getMinimumIdle(),
|
||||
hikariDs.getMaximumPoolSize(),
|
||||
hikariDs.getConnectionTimeout(),
|
||||
hikariDs.getIdleTimeout(),
|
||||
hikariDs.getMaxLifetime());
|
||||
}
|
||||
|
||||
// JPA/Hibernate settings
|
||||
String showSql = environment.getProperty("spring.jpa.show-sql", "false");
|
||||
String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none");
|
||||
String batchSize =
|
||||
environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A");
|
||||
String batchFetchSize =
|
||||
environment.getProperty(
|
||||
"spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A");
|
||||
// JPA/Hibernate settings
|
||||
String showSql = environment.getProperty("spring.jpa.show-sql", "false");
|
||||
String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none");
|
||||
String batchSize =
|
||||
environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A");
|
||||
String batchFetchSize =
|
||||
environment.getProperty(
|
||||
"spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A");
|
||||
|
||||
String startupMessage =
|
||||
String.format(
|
||||
"""
|
||||
String startupMessage =
|
||||
String.format(
|
||||
"""
|
||||
|
||||
╔════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ 🚀 APPLICATION STARTUP INFORMATION ║
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ PROFILE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Active Profile(s): %s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ DATABASE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Database URL : %s
|
||||
│ Username : %s
|
||||
│ Driver : %s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ HIKARICP CONNECTION POOL ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
%s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ JPA/HIBERNATE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Show SQL : %s
|
||||
│ DDL Auto : %s
|
||||
│ JDBC Batch Size : %s
|
||||
│ Fetch Batch Size : %s
|
||||
╚════════════════════════════════════════════════════════════════════════════════╝
|
||||
""",
|
||||
profileInfo,
|
||||
dbUrl != null ? dbUrl : "N/A",
|
||||
dbUsername != null ? dbUsername : "N/A",
|
||||
dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)",
|
||||
poolInfo,
|
||||
showSql,
|
||||
ddlAuto,
|
||||
batchSize,
|
||||
batchFetchSize);
|
||||
╔════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ 🚀 APPLICATION STARTUP INFORMATION ║
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ PROFILE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Active Profile(s): %s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ DATABASE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Database URL : %s
|
||||
│ Username : %s
|
||||
│ Driver : %s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ HIKARICP CONNECTION POOL ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
%s
|
||||
╠════════════════════════════════════════════════════════════════════════════════╣
|
||||
║ JPA/HIBERNATE CONFIGURATION ║
|
||||
╠────────────────────────────────────────────────────────────────────────────────╣
|
||||
│ Show SQL : %s
|
||||
│ DDL Auto : %s
|
||||
│ JDBC Batch Size : %s
|
||||
│ Fetch Batch Size : %s
|
||||
╚════════════════════════════════════════════════════════════════════════════════╝
|
||||
""",
|
||||
profileInfo,
|
||||
dbUrl != null ? dbUrl : "N/A",
|
||||
dbUsername != null ? dbUsername : "N/A",
|
||||
dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)",
|
||||
poolInfo,
|
||||
showSql,
|
||||
ddlAuto,
|
||||
batchSize,
|
||||
batchFetchSize);
|
||||
|
||||
log.info(startupMessage);
|
||||
}
|
||||
log.info(startupMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ public class WebConfig {
|
||||
module.addSerializer(Point.class, new GeometrySerializer<>(Point.class));
|
||||
module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class));
|
||||
|
||||
return Jackson2ObjectMapperBuilder.json()
|
||||
.modulesToInstall(module)
|
||||
.build();
|
||||
return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,120 @@
|
||||
package com.kamco.cd.kamcoback.config.api;
|
||||
|
||||
public class ApiResponseDto {
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class ApiResponseDto<T> {
|
||||
|
||||
private T data;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Error error;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private T errorData;
|
||||
|
||||
public ApiResponseDto(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ApiResponseDto(ApiResponseCode code) {
|
||||
this.error = new Error(code.getId(), code.getMessage());
|
||||
}
|
||||
|
||||
public ApiResponseDto(ApiResponseCode code, String message) {
|
||||
this.error = new Error(code.getId(), message);
|
||||
}
|
||||
|
||||
public ApiResponseDto(ApiResponseCode code, String message, T errorData) {
|
||||
this.error = new Error(code.getId(), message);
|
||||
this.errorData = errorData;
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<T> createOK(T data) {
|
||||
return new ApiResponseDto<>(data);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(ApiResponseCode code) {
|
||||
return new ApiResponseDto<>(code);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(ApiResponseCode code, String message) {
|
||||
return new ApiResponseDto<>(code, message);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<T> createException(
|
||||
ApiResponseCode code, String message, T data) {
|
||||
return new ApiResponseDto<>(code, message, data);
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Error {
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
public Error(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum ApiResponseCode implements EnumType {
|
||||
|
||||
// @formatter:off
|
||||
OK("요청이 성공하였습니다."),
|
||||
BAD_REQUEST("요청 파라미터가 잘못되었습니다."),
|
||||
ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."),
|
||||
NOT_FOUND_MAP("지도를 찾을 수 없습니다."),
|
||||
UNAUTHORIZED("권한이 없습니다."),
|
||||
CONFLICT("이미 등록된 컨텐츠입니다."),
|
||||
NOT_FOUND("Resource를 찾을 수 없습니다."),
|
||||
NOT_FOUND_DATA("데이터를 찾을 수 없습니다."),
|
||||
NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."),
|
||||
FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."),
|
||||
TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."),
|
||||
UNAUTHENTICATED("인증에 실패하였습니다."),
|
||||
INVALID_TOKEN("잘못된 토큰입니다."),
|
||||
EXPIRED_TOKEN("만료된 토큰입니다."),
|
||||
INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."),
|
||||
FORBIDDEN("권한을 확인해주세요."),
|
||||
INVALID_PASSWORD("잘못된 비밀번호 입니다."),
|
||||
NOT_FOUND_CAR_IN("입차정보가 없습니다."),
|
||||
WRONG_STATUS("잘못된 상태입니다."),
|
||||
FAIL_VERIFICATION("인증에 실패하였습니다."),
|
||||
INVALID_EMAIL("잘못된 형식의 이메일입니다."),
|
||||
REQUIRED_EMAIL("이메일은 필수 항목입니다."),
|
||||
WRONG_PASSWORD("잘못된 패스워드입니다.."),
|
||||
DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
|
||||
DUPLICATE_DATA("이미 등록되여 있습니다."),
|
||||
DATA_INTEGRITY_ERROR("요청을 처리할수 없습니다."),
|
||||
FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."),
|
||||
DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."),
|
||||
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
|
||||
NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
|
||||
INVALID_EMAIL_TOKEN(
|
||||
"You can only reset your password within 24 hours from when the email was sent.\n"
|
||||
+ "To reset your password again, please submit a new request through \"Forgot"
|
||||
+ " Password.\""),
|
||||
;
|
||||
// @formatter:on
|
||||
private final String message;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
package com.kamco.cd.kamcoback.config.api;
|
||||
package com.kamco.cd.kamcoback.config.enums;
|
||||
|
||||
public interface EnumType {
|
||||
|
||||
String getId();
|
||||
|
||||
String getText();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.kamco.cd.kamcoback.postgres;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
|
||||
@Getter
|
||||
@MappedSuperclass
|
||||
public class CommonDateEntity {
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_date", updatable = false, nullable = false)
|
||||
private ZonedDateTime createdDate;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "modified_date", nullable = false)
|
||||
private ZonedDateTime modifiedDate;
|
||||
|
||||
@PrePersist
|
||||
protected void onPersist() {
|
||||
this.createdDate = ZonedDateTime.now();
|
||||
this.modifiedDate = ZonedDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
this.modifiedDate = ZonedDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.kamco.cd.kamcoback.postgres;
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Configuration
|
||||
public class QueryDslConfig {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
@Bean
|
||||
public JPAQueryFactory jpaQueryFactory() {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.common.service.BaseCoreService;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.repository.AnimalRepository;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AnimalCoreService
|
||||
implements BaseCoreService<AnimalDto.Basic, Long, AnimalDto.SearchReq> {
|
||||
|
||||
private final AnimalRepository animalRepository;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public AnimalDto.Basic getDataByUuid(String uuid) {
|
||||
AnimalEntity getZoo =
|
||||
animalRepository
|
||||
.getAnimalByUuid(uuid)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new EntityNotFoundException(
|
||||
"Zoo not found with uuid: " + uuid));
|
||||
return getZoo.toDto();
|
||||
}
|
||||
|
||||
// AddReq를 받는 추가 메서드
|
||||
@Transactional
|
||||
public AnimalDto.Basic create(AnimalDto.AddReq req) {
|
||||
AnimalEntity entity = new AnimalEntity(req.getCategory(), req.getSpecies(), req.getName());
|
||||
AnimalEntity saved = animalRepository.save(entity);
|
||||
return saved.toDto();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void remove(Long id) {
|
||||
AnimalEntity getZoo =
|
||||
animalRepository
|
||||
.getAnimalByUid(id)
|
||||
.orElseThrow(
|
||||
() -> new EntityNotFoundException("Zoo not found with id: " + id));
|
||||
getZoo.deleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimalDto.Basic getOneById(Long id) {
|
||||
AnimalEntity getZoo =
|
||||
animalRepository
|
||||
.getAnimalByUid(id)
|
||||
.orElseThrow(
|
||||
() -> new EntityNotFoundException("Zoo not found with id: " + id));
|
||||
return getZoo.toDto();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) {
|
||||
|
||||
Page<AnimalEntity> zooEntities = animalRepository.listAnimal(searchReq);
|
||||
return zooEntities.map(AnimalEntity::toDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.kamco.cd.kamcoback.postgres.entity;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Category;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Species;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.UUID;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
// 기본구조 관련
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@Table(name = "tb_animal")
|
||||
public class AnimalEntity extends CommonDateEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long uid;
|
||||
|
||||
@Column(unique = true)
|
||||
private UUID uuid;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Category category;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Species species;
|
||||
|
||||
private String name;
|
||||
private Boolean isDeleted;
|
||||
|
||||
// Construct
|
||||
public AnimalEntity(Category category, Species species, String name) {
|
||||
this.uuid = UUID.randomUUID();
|
||||
this.category = category;
|
||||
this.species = species;
|
||||
this.name = name;
|
||||
this.isDeleted = false;
|
||||
}
|
||||
|
||||
public AnimalDto.Basic toDto() {
|
||||
return new AnimalDto.Basic(
|
||||
this.uid,
|
||||
this.uuid.toString(),
|
||||
this.name,
|
||||
this.category,
|
||||
this.species,
|
||||
super.getCreatedDate(),
|
||||
super.getModifiedDate());
|
||||
}
|
||||
|
||||
public void deleted() {
|
||||
this.isDeleted = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface AnimalRepository
|
||||
extends JpaRepository<AnimalEntity, Long>, AnimalRepositoryCustom {}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
public interface AnimalRepositoryCustom {
|
||||
|
||||
Optional<AnimalEntity> getAnimalByUid(Long uid);
|
||||
|
||||
Optional<AnimalEntity> getAnimalByUuid(String uuid);
|
||||
|
||||
Page<AnimalEntity> listAnimal(AnimalDto.SearchReq req);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.QAnimalEntity;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Category;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Species;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
|
||||
|
||||
public class AnimalRepositoryImpl extends QuerydslRepositorySupport
|
||||
implements AnimalRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
|
||||
public AnimalRepositoryImpl(JPAQueryFactory queryFactory) {
|
||||
super(AnimalEntity.class);
|
||||
this.queryFactory = queryFactory;
|
||||
}
|
||||
|
||||
public Optional<AnimalEntity> getAnimalByUid(Long uid) {
|
||||
QAnimalEntity animal = QAnimalEntity.animalEntity;
|
||||
|
||||
return Optional.ofNullable(
|
||||
queryFactory.selectFrom(animal).where(animal.uid.eq(uid)).fetchFirst());
|
||||
}
|
||||
|
||||
public Optional<AnimalEntity> getAnimalByUuid(String uuid) {
|
||||
QAnimalEntity animal = QAnimalEntity.animalEntity;
|
||||
|
||||
return Optional.ofNullable(
|
||||
queryFactory
|
||||
.selectFrom(animal)
|
||||
.where(animal.uuid.eq(UUID.fromString(uuid)))
|
||||
.fetchFirst());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<AnimalEntity> listAnimal(AnimalDto.SearchReq req) {
|
||||
QAnimalEntity animal = QAnimalEntity.animalEntity;
|
||||
|
||||
Pageable pageable = req.toPageable();
|
||||
|
||||
List<AnimalEntity> content =
|
||||
queryFactory
|
||||
.selectFrom(animal)
|
||||
.where(
|
||||
animal.isDeleted.eq(false),
|
||||
eqCategory(animal, req.getCategory()),
|
||||
eqSpecies(animal, req.getSpecies()),
|
||||
likeName(animal, req.getName()))
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(animal.createdDate.desc())
|
||||
.fetch();
|
||||
|
||||
// count 쿼리
|
||||
Long total =
|
||||
queryFactory
|
||||
.select(animal.count())
|
||||
.from(animal)
|
||||
.where(
|
||||
animal.isDeleted.eq(false),
|
||||
eqCategory(animal, req.getCategory()),
|
||||
eqSpecies(animal, req.getSpecies()),
|
||||
likeName(animal, req.getName()))
|
||||
.fetchOne();
|
||||
|
||||
return new PageImpl<>(content, pageable, total);
|
||||
}
|
||||
|
||||
private BooleanExpression likeName(QAnimalEntity animal, String nameStr) {
|
||||
if (nameStr == null || nameStr.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return animal.name.containsIgnoreCase(nameStr.trim());
|
||||
}
|
||||
|
||||
private BooleanExpression eqCategory(QAnimalEntity animal, Category category) {
|
||||
if (category == null) {
|
||||
return null;
|
||||
}
|
||||
return animal.category.eq(category);
|
||||
}
|
||||
|
||||
private BooleanExpression eqSpecies(QAnimalEntity animal, Species species) {
|
||||
if (species == null) {
|
||||
return null;
|
||||
}
|
||||
return animal.species.eq(species);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.kamco.cd.kamcoback.zoo;
|
||||
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Category;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Species;
|
||||
import com.kamco.cd.kamcoback.zoo.service.AnimalService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping({"/api/animals", "/v1/api/animals"})
|
||||
public class AnimalApiController {
|
||||
|
||||
private final AnimalService animalService;
|
||||
|
||||
/**
|
||||
* 동물 생성
|
||||
*
|
||||
* @param req 동물 생성 요청
|
||||
* @return 생성된 동물 정보
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<AnimalDto.Basic> createAnimal(@RequestBody AnimalDto.AddReq req) {
|
||||
AnimalDto.Basic created = animalService.createAnimal(req);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID로 동물 조회
|
||||
*
|
||||
* @param uuid 동물 UUID
|
||||
* @return 동물 정보
|
||||
*/
|
||||
@GetMapping("/{uuid}")
|
||||
public ResponseEntity<AnimalDto.Basic> getAnimal(@PathVariable String uuid) {
|
||||
Long id = animalService.getAnimalByUuid(uuid);
|
||||
AnimalDto.Basic animal = animalService.getAnimal(id);
|
||||
return ResponseEntity.ok(animal);
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID로 동물 삭제 (논리 삭제)
|
||||
*
|
||||
* @param uuid 동물 UUID
|
||||
* @return 삭제 성공 메시지
|
||||
*/
|
||||
@DeleteMapping("/{uuid}")
|
||||
public ResponseEntity<Void> deleteAnimal(@PathVariable String uuid) {
|
||||
Long id = animalService.getAnimalByUuid(uuid);
|
||||
animalService.deleteZoo(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 동물 검색 (페이징)
|
||||
*
|
||||
* @param name 동물 이름 (선택)
|
||||
* @param category 서식지 타입 (선택)
|
||||
* @param species 동물종 (선택) 개, 고양이등.
|
||||
* @param page 페이지 번호 (기본값: 0)
|
||||
* @param size 페이지 크기 (기본값: 20)
|
||||
* @param sort 정렬 조건 (예: "name,asc")
|
||||
* @return 페이징 처리된 동물 목록
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<AnimalDto.Basic>> searchAnimals(
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) Category category,
|
||||
@RequestParam(required = false) Species species,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(required = false) String sort) {
|
||||
AnimalDto.SearchReq searchReq =
|
||||
new AnimalDto.SearchReq(name, category, species, page, size, sort);
|
||||
Page<AnimalDto.Basic> animals = animalService.search(searchReq);
|
||||
return ResponseEntity.ok(animals);
|
||||
}
|
||||
}
|
||||
129
src/main/java/com/kamco/cd/kamcoback/zoo/dto/AnimalDto.java
Normal file
129
src/main/java/com/kamco/cd/kamcoback/zoo/dto/AnimalDto.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package com.kamco.cd.kamcoback.zoo.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
||||
import java.time.ZonedDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
public class AnimalDto {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AddReq {
|
||||
|
||||
private Category category;
|
||||
private Species species;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Basic {
|
||||
|
||||
@JsonIgnore private Long id;
|
||||
private String uuid;
|
||||
private Category category;
|
||||
private Species species;
|
||||
private String name;
|
||||
|
||||
@JsonFormat(
|
||||
shape = JsonFormat.Shape.STRING,
|
||||
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
|
||||
timezone = "Asia/Seoul")
|
||||
private ZonedDateTime createdDate;
|
||||
|
||||
@JsonFormat(
|
||||
shape = JsonFormat.Shape.STRING,
|
||||
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
|
||||
timezone = "Asia/Seoul")
|
||||
private ZonedDateTime modifiedDate;
|
||||
|
||||
public Basic(
|
||||
Long id,
|
||||
String uuid,
|
||||
String name,
|
||||
Category category,
|
||||
Species species,
|
||||
ZonedDateTime createdDate,
|
||||
ZonedDateTime modifiedDate) {
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.category = category;
|
||||
this.species = species;
|
||||
this.createdDate = createdDate;
|
||||
this.modifiedDate = modifiedDate;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum Category implements EnumType {
|
||||
// @formatter:off
|
||||
MAMMALS("100", "포유류"), // 땅에 사는 동물
|
||||
BIRDS("200", "조류"), // 하늘을 나는 동물
|
||||
FISH("300", "어류"),
|
||||
AMPHIBIANS("400", "양서류"),
|
||||
REPTILES("500", "파충류"),
|
||||
INSECTS("500", "곤충"),
|
||||
INVERTEBRATES("500", "무척추동물"),
|
||||
;
|
||||
// @formatter:on
|
||||
private final String id;
|
||||
private final String text;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum Species implements EnumType {
|
||||
// @formatter:off
|
||||
DOG("101", "개"),
|
||||
CAT("102", "강아지"),
|
||||
DOVE("201", "비둘기"),
|
||||
EAGLE("202", "독수리"),
|
||||
SALMON("301", "연어"),
|
||||
TUNA("302", "참치"),
|
||||
;
|
||||
// @formatter:on
|
||||
private final String id;
|
||||
private final String text;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SearchReq {
|
||||
|
||||
// 검색 조건
|
||||
private String name;
|
||||
private Category category;
|
||||
private Species species;
|
||||
|
||||
// 페이징 파라미터
|
||||
private int page = 0;
|
||||
private int size = 20;
|
||||
private String sort;
|
||||
|
||||
public Pageable toPageable() {
|
||||
if (sort != null && !sort.isEmpty()) {
|
||||
String[] sortParams = sort.split(",");
|
||||
String property = sortParams[0];
|
||||
Sort.Direction direction =
|
||||
sortParams.length > 1
|
||||
? Sort.Direction.fromString(sortParams[1])
|
||||
: Sort.Direction.ASC;
|
||||
return PageRequest.of(page, size, Sort.by(direction, property));
|
||||
}
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.kamco.cd.kamcoback.zoo.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.core.AnimalCoreService;
|
||||
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
public class AnimalService {
|
||||
private final AnimalCoreService zooCoreService;
|
||||
|
||||
// 동물의 UUID로 id조회
|
||||
public Long getAnimalByUuid(String uuid) {
|
||||
return zooCoreService.getDataByUuid(uuid).getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 동물 생성
|
||||
*
|
||||
* @param req 동물 생성 요청
|
||||
* @return 생성된 동물 정보
|
||||
*/
|
||||
@Transactional
|
||||
public AnimalDto.Basic createAnimal(AnimalDto.AddReq req) {
|
||||
return zooCoreService.create(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* 동물 삭제 (논리 삭제)
|
||||
*
|
||||
* @param id 동물 ID
|
||||
*/
|
||||
@Transactional
|
||||
public void deleteZoo(Long id) {
|
||||
zooCoreService.remove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 동물 단건 조회
|
||||
*
|
||||
* @param id 동물 ID
|
||||
* @return 동물 정보
|
||||
*/
|
||||
public AnimalDto.Basic getAnimal(Long id) {
|
||||
return zooCoreService.getOneById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 동물 검색 (페이징)
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 동물 목록
|
||||
*/
|
||||
public Page<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) {
|
||||
return zooCoreService.search(searchReq);
|
||||
}
|
||||
}
|
||||
@@ -23,3 +23,5 @@ spring:
|
||||
maximum-pool-size: 20
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,11 +15,10 @@ spring:
|
||||
format_sql: true # ⚠️ 선택 - SQL 포맷팅 (가독성)
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://10.100.0.10:25432/temp
|
||||
username: temp
|
||||
password: temp123!
|
||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
||||
username: kamco_cds
|
||||
password: kamco_cds_Q!W@E#R$
|
||||
hikari:
|
||||
minimum-idle: 1
|
||||
maximum-pool-size: 5
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
@SpringBootTest
|
||||
class KamcoBackApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextLoads() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user