Feature/init - 프로젝트 설정 추가 진행 (#14)

* feat: jasypt 적용

* refactor: data-source-properties 제거

* feat: BaseEntity add

* feat: User 패키지 구조 및 파일 세팅

* feat: Payment 패키지 구조 및 파일 세팅

* feat: Movie 패키지 구조 및 파일 세팅

* refactor: security 주석 및 actuator 추가

security 사용 시 주석 해제 (아직 사용하지 않아 주석처리)
actuator 을 통해 health check 구현

* feat: 헬스 상태 수동 변경 컨트롤러 구현

* refactor: @Component 등록

* test: health check test 작성

* refactor: intellij warring 해결

* feat: log4j2 설정

* feat: @EnableJpaAuditing 추가

Jpa Auditing 활성화

* feat: user insert db 연결 테스트

* refactor: @Transactional 추가

* refactor: import 정리 및 하단 공백 추가
This commit is contained in:
Kim DongHyo
2022-05-11 00:03:33 +09:00
committed by GitHub
parent 2028f3e508
commit b36a58f965
51 changed files with 726 additions and 15 deletions

View File

@@ -23,13 +23,23 @@ repositories {
mavenCentral()
}
dependencies {
implementation ("org.springframework.boot:spring-boot-starter-data-jpa")
implementation ("org.springframework.boot:spring-boot-starter-security")
// implementation ("org.springframework.boot:spring-boot-starter-security")
implementation ("org.springframework.boot:spring-boot-starter-validation")
implementation ("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation ("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4")
implementation ("org.springframework.boot:spring-boot-starter-log4j2")
implementation ("com.lmax:disruptor:3.4.2")
modules {
module("org.springframework.boot:spring-boot-starter-logging") {
replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
}
}
compileOnly ("org.projectlombok:lombok")
runtimeOnly ("mysql:mysql-connector-java")

View File

@@ -2,7 +2,9 @@ package com.ticketing.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing
@SpringBootApplication
public class ServerApplication {

View File

@@ -0,0 +1,31 @@
package com.ticketing.server.global.dto.repository;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, updatable = false)
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,43 @@
package com.ticketing.server.global.health;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/l7check")
@RequiredArgsConstructor
public class L7checkController {
private final MutableHealthIndicator indicator;
@GetMapping
public ResponseEntity<Object> health() {
Health health = indicator.health();
boolean isUp = health.getStatus().equals(Status.UP);
return ResponseEntity
.status(isUp ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE)
.build();
}
@DeleteMapping
@ResponseStatus(HttpStatus.NO_CONTENT)
public void down() {
indicator.setHealth(Health.down().build());
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void up() {
indicator.setHealth(Health.up().build());
}
}

View File

@@ -0,0 +1,22 @@
package com.ticketing.server.global.health;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;
@Component
public class ManualHealthIndicator implements MutableHealthIndicator {
private final AtomicReference<Health> healthRef = new AtomicReference<>(Health.up().build());
@Override
public void setHealth(Health health) {
healthRef.set(health);
}
@Override
public Health health() {
return healthRef.get();
}
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.global.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
public interface MutableHealthIndicator extends HealthIndicator {
void setHealth(Health health);
}

View File

@@ -0,0 +1,8 @@
package com.ticketing.server.movie.application;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MovieController {
}

View File

@@ -0,0 +1,8 @@
package com.ticketing.server.movie.application;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MovieTimesController {
}

View File

@@ -0,0 +1,8 @@
package com.ticketing.server.movie.application;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SeatController {
}

View File

@@ -0,0 +1,8 @@
package com.ticketing.server.movie.application;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TheaterController {
}

View File

@@ -0,0 +1,8 @@
package com.ticketing.server.movie.application;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TicketController {
}

View File

@@ -0,0 +1,18 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;
import lombok.Getter;
@Entity
@Getter
public class Movie extends AbstractEntity {
@NotNull
private String title;
@NotNull
private Integer runningTime;
}

View File

@@ -0,0 +1,38 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import java.time.LocalDate;
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import lombok.Getter;
@Entity
@Getter
public class MovieTimes extends AbstractEntity {
@NotNull
@ManyToOne
@JoinColumn(name = "movie_id", referencedColumnName = "id", updatable = false)
private Movie movie;
@NotNull
@ManyToOne
@JoinColumn(name = "theater_id", referencedColumnName = "id", updatable = false)
private Theater theater;
@NotNull
private LocalDate runningDate;
@NotNull
private Integer round;
@NotNull
private LocalDateTime startAt;
@NotNull
private LocalDateTime endAt;
}

View File

@@ -0,0 +1,25 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import lombok.Getter;
@Entity
@Getter
public class Seat extends AbstractEntity {
@NotNull
@ManyToOne
@JoinColumn(name = "theater_id", referencedColumnName = "id", updatable = false)
private Theater theater;
@NotNull
private Integer seatColumn;
@NotNull
private Integer seatRow;
}

View File

@@ -0,0 +1,17 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;
import lombok.Getter;
@Entity
@Getter
public class Theater extends AbstractEntity {
@NotNull
private Integer theaterNumber;
private Integer seatCount;
}

View File

@@ -0,0 +1,35 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import com.ticketing.server.payment.domain.Payment;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import lombok.Getter;
@Entity
@Getter
public class Ticket extends AbstractEntity {
@NotNull
@ManyToOne
@JoinColumn(name = "seat_id", referencedColumnName = "id", updatable = false)
private Seat seat;
@NotNull
@ManyToOne
@JoinColumn(name = "movie_times_id", referencedColumnName = "id", updatable = false)
private MovieTimes movieTimes;
@ManyToOne
@JoinColumn(name = "payment_id", referencedColumnName = "id", updatable = false)
private Payment payment;
@NotNull
private TicketStatus status;
@NotNull
private Integer ticketPrice;
}

View File

@@ -0,0 +1,15 @@
package com.ticketing.server.movie.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TicketStatus {
SALE("판매가능"),
SCHEDULED("환불"),
SOLD("판매완료");
private String name;
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.Movie;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MovieRepository extends JpaRepository<Movie, Long> {
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.MovieTimes;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MovieTimesRepository extends JpaRepository<MovieTimes, Long> {
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.Seat;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SeatRepository extends JpaRepository<Seat, Long> {
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.Theater;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TheaterRepository extends JpaRepository<Theater, Long> {
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.Ticket;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TicketRepository extends JpaRepository<Ticket, Long> {
}

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.movie.service;
import com.ticketing.server.movie.service.interfaces.MovieService;
import org.springframework.stereotype.Service;
@Service
public class MovieServiceImpl implements MovieService {
}

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.movie.service;
import com.ticketing.server.movie.service.interfaces.MovieTimesService;
import org.springframework.stereotype.Service;
@Service
public class MovieTimesServiceImpl implements MovieTimesService {
}

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.movie.service;
import com.ticketing.server.movie.service.interfaces.SeatService;
import org.springframework.stereotype.Service;
@Service
public class SeatServiceImpl implements SeatService {
}

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.movie.service;
import com.ticketing.server.movie.service.interfaces.TheaterService;
import org.springframework.stereotype.Service;
@Service
public class TheaterServiceImpl implements TheaterService {
}

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.movie.service;
import com.ticketing.server.movie.service.interfaces.TicketService;
import org.springframework.stereotype.Service;
@Service
public class TicketServiceImpl implements TicketService {
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.movie.service.interfaces;
public interface MovieService {
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.movie.service.interfaces;
public interface MovieTimesService {
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.movie.service.interfaces;
public interface SeatService {
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.movie.service.interfaces;
public interface TheaterService {
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.movie.service.interfaces;
public interface TicketService {
}

View File

@@ -0,0 +1,8 @@
package com.ticketing.server.payment.application;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
}

View File

@@ -0,0 +1,38 @@
package com.ticketing.server.payment.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import com.ticketing.server.user.domain.User;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import lombok.Getter;
@Entity
@Getter
public class Payment extends AbstractEntity {
@NotNull
@ManyToOne
@JoinColumn(name = "user_id", referencedColumnName = "id", updatable = false)
private User user;
@NotNull
@Enumerated(value = EnumType.STRING)
private PaymentType type;
@NotNull
@Enumerated(value = EnumType.STRING)
private PaymentStatus status;
private String failedMessage;
@NotNull
private String paymentNumber;
@NotNull
private Integer totalPrice;
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.payment.domain;
public enum PaymentStatus {
COMPLETED, REFUNDED, FAILED
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.payment.domain;
public enum PaymentType {
KAKAO_PAY
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.payment.domain.repository;
import com.ticketing.server.payment.domain.Payment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> {
}

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.payment.service;
import com.ticketing.server.payment.service.interfaces.PaymentService;
import org.springframework.stereotype.Service;
@Service
public class PaymentServiceImpl implements PaymentService {
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.payment.service.interfaces;
public interface PaymentService {
}

View File

@@ -0,0 +1,8 @@
package com.ticketing.server.user.application;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
}

View File

@@ -0,0 +1,47 @@
package com.ticketing.server.user.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
public class User extends AbstractEntity {
@NotNull
private String name;
@NotNull
private String email;
@NotNull
private String password;
@NotNull
@Enumerated(value = EnumType.STRING)
private UserGrade grade;
@NotNull
private String phone;
private boolean isDeleted = false;
private LocalDateTime deletedAt;
@Builder
protected User(String name, String email, String password, UserGrade grade, String phone) {
this.name = name;
this.email = email;
this.password = password;
this.grade = grade;
this.phone = phone;
}
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.user.domain;
public enum UserGrade {
GUEST, STAFF
}

View File

@@ -0,0 +1,10 @@
package com.ticketing.server.user.domain.repository;
import com.ticketing.server.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.user.service;
import com.ticketing.server.user.service.interfaces.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
}

View File

@@ -0,0 +1,5 @@
package com.ticketing.server.user.service.interfaces;
public interface UserService {
}

View File

@@ -14,17 +14,6 @@ spring:
connection-timeout: 30000 # default 30s
maximum-pool-size: 10 # default 10
max-lifetime: 1800000 # default 30 minutes
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
useLocalSessionState: true
rewriteBatchedStatements: true
cacheResultSetMetadata: true
cacheServerConfiguration: true
elideSetAutoCommits: true
maintainTimeStats: false
jasypt:
encryptor:

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="INFO" monitorInterval="30">
<Properties>
<Property name="LOG_PATH">./logs/log</Property>
<Property name="LOG_PATTERN">%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
</Properties>
<Appenders>
<Console name="ConsoleLog" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
</Console>
<RollingFile name="FileLog"
fileName="${LOG_PATH}.log"
filePattern="${LOG_PATH}-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8" />
<Policies>
<SizeBasedTriggeringPolicy size="15MB" />
<TimeBasedTriggeringPolicy modulate="true" interval="1" />
</Policies>
<DefaultRolloverStrategy>
<Delete basePath="${LOG_PATH}" maxDepth="1">
<IfLastModified age="30d"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="ConsoleLog" />
<AppenderRef ref="FileLog" />
</Root>
</Loggers>
</Configuration>

View File

@@ -2,8 +2,10 @@ package com.ticketing.server;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootTest
@EnableJpaAuditing
class ServerApplicationTests {
@Test

View File

@@ -0,0 +1,51 @@
package com.ticketing.server.global.health;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class L7checkControllerTest {
private static final String L7CHECK = "/l7check";
private static final String HEALTH = "/actuator/health";
@Autowired
TestRestTemplate restTemplate;
@Test
void downAndUp() throws InterruptedException {
// before
expectUrlStatus(L7CHECK, HttpStatus.OK);
expectUrlStatus(HEALTH, HttpStatus.OK);
// down
restTemplate.delete(L7CHECK);
// then down
TimeUnit.MILLISECONDS.sleep(1000);
expectUrlStatus(L7CHECK, HttpStatus.SERVICE_UNAVAILABLE);
expectUrlStatus(HEALTH, HttpStatus.SERVICE_UNAVAILABLE);
// up
restTemplate.postForEntity(L7CHECK, null, Object.class);
// then up
TimeUnit.MILLISECONDS.sleep(1000);
expectUrlStatus(L7CHECK, HttpStatus.OK);
expectUrlStatus(HEALTH, HttpStatus.OK);
}
private void expectUrlStatus(String url, HttpStatus status) {
ResponseEntity<Object> res = restTemplate.getForEntity(url, Object.class);
assertThat(res.getStatusCode()).isEqualTo(status);
}
}

View File

@@ -0,0 +1,37 @@
package com.ticketing.server.user.domain.repository;
import static org.assertj.core.api.Assertions.assertThat;
import com.ticketing.server.user.domain.User;
import com.ticketing.server.user.domain.UserGrade;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
@Transactional
class UserRepositoryTest {
@Autowired
UserRepository userRepository;
@Test
void 유저레포지토리테스트() {
// given
User user = User.builder()
.name("동효")
.password("test")
.email("test@test.com")
.grade(UserGrade.GUEST)
.phone("010-1234-5678")
.build();
// when
userRepository.save(user);
// then
assertThat(user).isNotNull();
}
}

View File

@@ -1,8 +1,8 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/ticketing_test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: ticketing
password: ticketing
username: ENC(LowN1n4w0Ep/DqLD8+q5Bq6AXM4b8e3V)
password: ENC(OMvGcpZLpggFTiGNkqNe66Zq/SmJXF6o)
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
@@ -12,3 +12,7 @@ spring:
format_sql: true
hibernate:
ddl-auto: create
jasypt:
encryptor:
bean: jasyptStringEncryptor