Compare commits

..

17 Commits

Author SHA1 Message Date
손창현
43b34deb3b refactor: MovieTimeRepository - findValidMovieTimes 네이티브 쿼리 -> JPQL로 변경 2022-07-04 01:31:17 +09:00
손창현
730916c365 add: MovieDto - AcessLevel.PRIVATE for static factory method 2022-07-02 03:14:00 +09:00
손창현
c807e71523 refactor: removed unnecessary imports 2022-07-02 03:10:14 +09:00
손창현
1fcae19f32 refactor: removed unnecessary comments 2022-07-02 03:00:50 +09:00
손창현
f4e32dc84c refactor: removed unnecessary comments 2022-07-02 02:41:17 +09:00
손창현
8d3f1b7208 refactor: removed tmdb settings 2022-07-02 02:37:39 +09:00
손창현
f7396d7986 refactor: startOfDay 06:00 설정 2022-07-01 03:55:33 +09:00
손창현
e189d5d7ee add: MovieTimeDto - AccessLevel.PRIVATE for static factory method 2022-07-01 03:27:19 +09:00
손창현
7354f52e75 Merge branch 'develop' of https://github.com/f-lab-edu/Ticketing into feature/getMovieTimesList 2022-07-01 03:21:33 +09:00
손창현
054caef574 add: comment for endOfDay variable 2022-07-01 03:20:48 +09:00
손창현
237c32c323 refactor: find List<MovieTime> with native query 2022-07-01 03:18:57 +09:00
손창현
d731cf0067 refactor: MovieTime start_at, end_at LocalDateTime으로 변경 2022-06-30 20:34:46 +09:00
손창현
7a7da7d9bf fix: MovieTimeRepositoryTest 오타 수정 2022-06-30 20:31:40 +09:00
손창현
59e9f78673 refactor: 영화 시간표 조회 runningDate ApiParam 상영 날짜로 변경 2022-06-28 21:03:33 +09:00
손창현
1dc13c568d feat: getMovieTimes API 2022-06-28 04:11:21 +09:00
손창현
99f996959c refactor: 영화 시간표 테이블 이름 변경 movie_times -> movie_time 2022-06-27 13:25:50 +09:00
손창현
f6cfb80a53 refactor: 영화 시작, 종료 시간 LocalTime 적용 2022-06-27 10:23:36 +09:00
39 changed files with 318 additions and 485 deletions

View File

@@ -1,5 +1,5 @@
# 🍿 Ticketing
영화 예매 사이트를 대용량 트래픽에 대응할 수 있도록 설계.
영화 예매 서비스
<br/>
@@ -8,37 +8,13 @@
- 기획 및 설계 : 22.04.13 ~
- 프로젝트 구현 :
<br/>
## 🎯 프로젝트 주요 관심사
- OOP(객체 지향 프로그래밍)의 장점을 최대한 활용
- 테스트 코드를 통한 믿을 수 있는 코드 작성
- 대용량 트래픽을 고려한 확장 가능한 설계 및 성능 튜닝
- 코드리뷰를 통해 코드 품질 향상
- 코드 컨벤션 준수하여 코드 통일성 유지
<br/><br/>
<br/>
## 🎯 프로젝트 목표
## 🛠 기술스택
- Java 11
- Spring Boot 2.6.7 (당시 최신 GA 버전)
- Gradle Kotlin DSL
- Spring Security
- Junit 5
- Hibernate / SpringJPA
- MySQL 8.0
- Redis
<br/>
## Wiki
- [Git Branch 전략](https://github.com/f-lab-edu/Ticketing/wiki#-git-branch-%EC%A0%84%EB%9E%B5)
- [Code Convention](https://github.com/f-lab-edu/Ticketing/wiki#-code-convention)
- [패키지 구조](https://github.com/f-lab-edu/Ticketing/wiki#-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B5%AC%EC%A1%B0)
- [Use Case](https://github.com/f-lab-edu/Ticketing/wiki/Use-Case)
- [Prototype](https://github.com/f-lab-edu/Ticketing/wiki/Prototype)
<br/>
<br/><br/>
## 🧩 ERD
@@ -75,6 +51,7 @@ erDiagram
bigint id PK "상영시간표 ID"
bigint movie_id FK "영화 ID"
bigint theater_id FK "상영관 ID"
date running_date "상영 날짜"
int round "회차"
time start_at "시작 시간"
time end_at "종료 시간"
@@ -117,3 +94,5 @@ erDiagram
datetime updated_at "수정일시"
}
```
<br/><br/>

View File

@@ -23,6 +23,8 @@ repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
@@ -30,15 +32,14 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-log4j2")
implementation("org.projectlombok:lombok:1.18.24")
implementation("org.projectlombok:lombok:1.18.20")
implementation("io.springfox:springfox-boot-starter:3.0.0")
implementation("io.springfox:springfox-swagger-ui:3.0.0")
implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4")
implementation("com.lmax:disruptor:3.4.4")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
implementation("com.lmax:disruptor:3.4.2")
implementation("io.jsonwebtoken:jjwt-api:0.11.2")
implementation("com.googlecode.json-simple:json-simple:1.1.1")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("com.google.code.findbugs:jsr305:3.0.2")
modules {
module("org.springframework.boot:spring-boot-starter-logging") {
@@ -46,14 +47,18 @@ dependencies {
}
}
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
compileOnly("org.projectlombok:lombok")
runtimeOnly("mysql:mysql-connector-java")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.2")
annotationProcessor("org.projectlombok:lombok")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
@@ -61,28 +66,3 @@ dependencies {
tasks.withType<Test> {
useJUnitPlatform()
}
sourceSets {
create("intTest") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
}
}
val intTestImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}
configurations["intTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
configurations["intTestImplementation"].extendsFrom(configurations.testImplementation.get())
val integrationTest = task<Test>("integrationTest") {
description = "Runs integration tests."
group = "verification"
testClassesDirs = sourceSets["intTest"].output.classesDirs
classpath = sourceSets["intTest"].runtimeClasspath
shouldRunAfter("test")
}
tasks.check { dependsOn(integrationTest) }

View File

@@ -1,5 +0,0 @@
package com.ticketing.server.movie.domain.repository;
public class MovieTimesRepositoryTest {
}

View File

@@ -1,94 +0,0 @@
package com.ticketing.server.movie.service;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
@SpringBootTest
public class TMDBServiceImplTest {
@Value("${tmdb.api-key}")
private String apiKey;
@Value("${tmdb.read-access-token}")
private String readAccessToken;
@Autowired
RestTemplate restTemplate;
@Test
@DisplayName("TMDB Service Test - Get [Now Playing] movies")
void shouldAbleToGetMovieList() throws Exception {
// given
assertNotNull(apiKey);
assertNotNull(readAccessToken);
ArrayList<Charset> acceptCharset = new ArrayList<>();
acceptCharset.add(StandardCharsets.UTF_8);
HttpHeaders headers = new HttpHeaders();
headers.setAcceptCharset(acceptCharset);
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(readAccessToken);
Map<String, String> params = new HashMap<>();
params.put("api_key", apiKey);
params.put("language", "ko");
HttpEntity<?> request = new HttpEntity<>(headers);
// when
ResponseEntity<?> response = restTemplate.exchange(
"https://api.themoviedb.org/3/movie/now_playing?" + mapToUrlParam(params),
HttpMethod.GET,
request,
String.class
);
// JSONParser parser = new JSONParser();
// Object obj = parser.parse(String.valueOf(response));
// Object results = ((JSONObject) obj).get("results");
//
// ArrayList<String> movieList = new ArrayList<>();
//
// ArrayList<JSONObject> jsonMovieList = new ArrayList<>();
// then
assertTrue(response.getStatusCode().is2xxSuccessful());
}
private static String mapToUrlParam(Map<String, String> params) {
StringBuffer paramData = new StringBuffer();
for (Map.Entry<String, String> param : params.entrySet()) {
if (paramData.length() != 0) {
paramData.append('&');
}
paramData.append(param.getKey());
paramData.append('=');
paramData.append(param.getValue());
}
return paramData.toString();
}
}

View File

@@ -1,7 +0,0 @@
package com.ticketing.server.user.application;
import static org.junit.jupiter.api.Assertions.*;
class UserControllerTest {
}

View File

@@ -1,37 +0,0 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/ticketing_test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: ENC(LowN1n4w0Ep/DqLD8+q5Bq6AXM4b8e3V)
password: ENC(OMvGcpZLpggFTiGNkqNe66Zq/SmJXF6o)
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
jpa:
properties:
hibernate:
show_sql: true
format_sql: true
hibernate:
ddl-auto: create
mvc:
pathmatch:
matching-strategy: ant_path_matcher
jasypt:
encryptor:
bean: jasyptStringEncryptor
jwt:
access-header: Authorization
refresh-header: REFRESH_TOKEN
prefix: Bearer
secret-key: Zi1sYWItdGlja2V0aW5nLXByb2plY3Qtc3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXNlY3JldC1rZXktZi1sYWItdGlja2V0aW5nLXByb2plY3Qtc3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LXNlY3JldC1rZXkK
access-token-validity-in-seconds: 60
refresh-token-validity-in-seconds: 259200
tmdb:
api-key: 0d1503b6dcbfe1c514299b5564c649b8
read-access-token: eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIwZDE1MDNiNmRjYmZlMWM1MTQyOTliNTU2NGM2NDliOCIsInN1YiI6IjYyOWYwODRlNzI2ZmIxMTA2NDA4MjI2NCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.rs8KZea8QLyashILiggWFx2s46lgUtzo-xSWoDgE58A

View File

@@ -1,6 +1,7 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import com.ticketing.server.movie.service.dto.MovieDto;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;
@@ -14,11 +15,11 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class Movie extends AbstractEntity {
@NotNull
@Column(unique = true)
private String title;
@NotNull
@Column(unique = true)
private String title;
@NotNull
private Long runningTime;
@NotNull
private Integer runningTime;
}

View File

@@ -1,21 +1,16 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import com.ticketing.server.movie.service.dto.MovieTimeDto;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
public class MovieTime extends AbstractEntity {
@NotNull
@@ -37,26 +32,4 @@ public class MovieTime extends AbstractEntity {
@NotNull
private LocalDateTime endAt;
@OneToMany(mappedBy = "movieTime", cascade = CascadeType.ALL)
private List<Ticket> tickets = new ArrayList<>();
private MovieTime(Movie movie, Theater theater, int round, LocalDateTime startAt, LocalDateTime endAt) {
this.movie = movie;
this.theater = theater;
this.round = round;
this.startAt = startAt;
this.endAt = endAt;
}
public static MovieTime of(Movie movie, Theater theater, int round, LocalDateTime startAt) {
Long runningTime = movie.getRunningTime();
LocalDateTime endAt = startAt.plusMinutes(runningTime);
return new MovieTime(movie, theater, round, startAt, endAt);
}
public List<Seat> getSeats() {
return this.theater.getSeats();
}
}

View File

@@ -6,11 +6,9 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
public class Seat extends AbstractEntity {
@NotNull
@@ -24,14 +22,4 @@ public class Seat extends AbstractEntity {
@NotNull
private Integer seatRow;
public Seat(Integer seatColumn, Integer seatRow, Theater theater) {
this.seatColumn = seatColumn;
this.seatRow = seatRow;
setTheater(theater);
}
private void setTheater(Theater theater) {
this.theater = theater;
theater.addSeat(this);
}
}

View File

@@ -1,32 +1,21 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Theater extends AbstractEntity {
@NotNull
private Integer theaterNumber;
@OneToMany(mappedBy = "theater", cascade = CascadeType.ALL)
private List<Seat> seats = new ArrayList<>();
public Theater(Integer theaterNumber) {
this.theaterNumber = theaterNumber;
}
public void addSeat(Seat seat) {
seats.add(seat);
}
private Integer seatCount;
}

View File

@@ -3,17 +3,13 @@ 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.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
public class Ticket extends AbstractEntity {
@NotNull
@@ -31,21 +27,9 @@ public class Ticket extends AbstractEntity {
private Payment payment;
@NotNull
@Enumerated(EnumType.STRING)
private TicketStatus status;
@NotNull
private Integer ticketPrice;
private Ticket(Seat seat, MovieTime movieTime, Integer ticketPrice) {
this.seat = seat;
this.movieTime = movieTime;
this.ticketPrice = ticketPrice;
this.status = TicketStatus.SALE;
}
public static Ticket of(Seat seat, MovieTime movieTime, Integer ticketPrice) {
return new Ticket(seat, movieTime, ticketPrice);
}
}

View File

@@ -6,11 +6,10 @@ import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TicketStatus {
SALE("판매가능"),
SCHEDULED("환불"),
SOLD("판매완료");
SALE("판매가능"),
SCHEDULED("환불"),
SOLD("판매완료");
private String name;
private String name;
}

View File

@@ -15,7 +15,8 @@ public interface MovieTimeRepository extends JpaRepository<MovieTime, Long> {
+ "FROM MovieTime mt "
+ "JOIN FETCH mt.movie "
+ "WHERE mt.movie = :movie "
+ "AND mt.startAt BETWEEN :startOfDay AND :endOfDay ")
+ "AND mt.startAt "
+ "BETWEEN :startOfDay AND :endOfDay", nativeQuery = false)
List<MovieTime> findValidMovieTimes(Movie movie, LocalDateTime startOfDay, LocalDateTime endOfDay);
}

View File

@@ -10,7 +10,7 @@ import lombok.Getter;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MovieTimeDto {
private Long movieTimeId;
private long movieTimeId;
private Integer theaterNumber;

View File

@@ -1,20 +0,0 @@
package com.ticketing.server.movie.setup;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.RestController;
@Profile(value = {"local"})
@RestController
@RequiredArgsConstructor
public class MovieSetupController {
private final MovieSetupService movieSetupService;
@PostConstruct
public void setup() {
movieSetupService.init();
}
}

View File

@@ -1,101 +0,0 @@
package com.ticketing.server.movie.setup;
import com.ticketing.server.movie.domain.Movie;
import com.ticketing.server.movie.domain.MovieTime;
import com.ticketing.server.movie.domain.Seat;
import com.ticketing.server.movie.domain.Theater;
import com.ticketing.server.movie.domain.Ticket;
import com.ticketing.server.movie.domain.repository.MovieRepository;
import com.ticketing.server.movie.domain.repository.MovieTimeRepository;
import com.ticketing.server.movie.domain.repository.TheaterRepository;
import com.ticketing.server.movie.domain.repository.TicketRepository;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class MovieSetupService {
private final MovieRepository movieRepository;
private final MovieTimeRepository movieTimeRepository;
private final TheaterRepository theaterRepository;
private final TicketRepository ticketRepository;
@Transactional
public void init() {
initMovie();
initTheater();
initMovieTime();
initTicket();
}
private void initMovie() {
List<Movie> movies = Arrays.asList(
new Movie("탑건: 매버릭", 130L),
new Movie("헤어질 결심", 138L),
new Movie("마녀2", 137L),
new Movie("범죄도시2", 106L),
new Movie("버즈 라이트이어", 105L)
);
movieRepository.saveAll(movies);
}
private void initTheater() {
List<Theater> theaters = Arrays.asList(
new Theater(1),
new Theater(2)
);
for (Theater theater : theaters) {
for (int i = 1; i <= 2; i++) {
for (int j = 1; j <= 10; j++) {
new Seat(i, j, theater);
}
}
}
theaterRepository.saveAll(theaters);
}
private void initMovieTime() {
List<Movie> movies = movieRepository.findAll();
List<Theater> theaters = theaterRepository.findAll();
LocalDateTime now = LocalDateTime.now();
List<MovieTime> movieTimes = new ArrayList<>();
for (Theater theater : theaters) {
movieTimes.add(MovieTime.of(movies.get(0), theater, 1, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 8, 0)));
movieTimes.add(MovieTime.of(movies.get(0), theater, 3, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 12, 0)));
movieTimes.add(MovieTime.of(movies.get(1), theater, 2, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 10, 0)));
movieTimes.add(MovieTime.of(movies.get(2), theater, 4, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 14, 0)));
movieTimes.add(MovieTime.of(movies.get(0), theater, 5, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 16, 0)));
movieTimes.add(MovieTime.of(movies.get(3), theater, 6, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 18, 0)));
movieTimes.add(MovieTime.of(movies.get(0), theater, 7, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 21, 0)));
movieTimes.add(MovieTime.of(movies.get(4), theater, 8, LocalDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), 23, 0)));
}
movieTimeRepository.saveAll(movieTimes);
}
private void initTicket() {
List<MovieTime> movieTimes = movieTimeRepository.findAll();
List<Ticket> tickets = new ArrayList<>();
Integer ticketPrice = 15_000;
for (MovieTime movieTime : movieTimes) {
for (Seat seat : movieTime.getSeats()) {
tickets.add(Ticket.of(seat, movieTime, ticketPrice));
}
}
ticketRepository.saveAll(tickets);
}
}

View File

@@ -11,4 +11,4 @@ spring:
show_sql: true
format_sql: true
hibernate:
ddl-auto: create
ddl-auto: update

View File

@@ -0,0 +1,13 @@
package com.ticketing.server;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ServerApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -1,28 +0,0 @@
package com.ticketing.server.movie.domain;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDateTime;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class MovieTimeTest {
@ParameterizedTest
@DisplayName("영화상영시간 생성")
@ValueSource(longs = {130L, 140L, 30L})
void createMovieTime(Long runningTime) {
// given
Movie movie = new Movie("범죄도시2", runningTime);
Theater theater = new Theater(1);
LocalDateTime startAt = LocalDateTime.of(2022, 7, 4, 8, 10);
// when
MovieTime movieTime = MovieTime.of(movie, theater, 1, startAt);
// then
assertThat(movieTime.getEndAt()).isEqualTo(startAt.plusMinutes(runningTime));
}
}

View File

@@ -30,7 +30,7 @@ public class MovieRepositoryTest {
@DisplayName("Movie Repository Test - saving movie")
void shouldAbleToSaveMovie() {
// given
Movie movie = new Movie("범죄도시 2", 106L);
Movie movie = new Movie("범죄도시 2", 106);
// when
Movie savedMovie = movieRepository.save(movie);

View File

@@ -20,7 +20,7 @@ public class TheaterRepositoryTest {
@DisplayName("Theater Repository Test - saving theater")
void ShouldAbleToSaveTheater() {
// given
Theater theater = new Theater(1);
Theater theater = new Theater(1, 100);
// when
Theater savedTheater = theaterRepository.save(theater);

View File

@@ -35,8 +35,7 @@ public class MovieServiceImplTest {
@DisplayName("Movie Service Test - get movies when there is no movie")
void shouldGetEmptyList() {
// given
when(movieRepository.findValidMovies())
.thenReturn(Collections.emptyList());
when(movieRepository.findValidMovies()).thenReturn(Collections.emptyList());
// when
List<MovieDto> movieDtoList = movieService.getMovies();
@@ -49,13 +48,12 @@ public class MovieServiceImplTest {
@DisplayName("Movie Service Test - get movies")
void shouldAbleToGetMovies() {
// given
movie = new Movie("범죄도시2", 106L);
movieDto = movieDto.from(movie);
movie = new Movie("범죄도시2", 106);
movieDto = MovieDto.from(movie);
movies.add(movie);
movieDtos.add(movieDto);
when(movieRepository.findValidMovies())
.thenReturn(movies);
when(movieRepository.findValidMovies()).thenReturn(movies);
// when
List<MovieDto> movieDtoList = movieService.getMovies();

View File

@@ -1,18 +1,16 @@
package com.ticketing.server.movie.service;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.any;
import com.ticketing.server.movie.domain.Movie;
import com.ticketing.server.movie.domain.MovieTime;
import com.ticketing.server.movie.domain.Theater;
import com.ticketing.server.movie.domain.repository.MovieRepository;
import com.ticketing.server.movie.domain.repository.MovieTimeRepository;
import com.ticketing.server.movie.service.dto.MovieTimeDto;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -22,12 +20,13 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.annotation.Rollback;
@ExtendWith(MockitoExtension.class)
public class MovieTimeServiceImplTest {
String title = "범죄도시2";
List<MovieTime> movieTimes = new ArrayList<>();
LocalDateTime startOfDay = LocalDate.now().atStartOfDay().plusHours(6);
LocalDateTime endOfDay = startOfDay.plusDays(1);
@Mock
MovieRepository movieRepository;
@@ -41,12 +40,10 @@ public class MovieTimeServiceImplTest {
@Test
@DisplayName("MovieTime Service Test - get empty list when there is no valid movie times")
void shouldGetEmptyList() {
// given
Movie movie = new Movie(title, 106L);
when(movieRepository.findByTitle(title))
.thenReturn(Optional.of(movie));
String title = "범죄도시2";
Movie movie = new Movie(title, 106);
when(movieRepository.findByTitle(title)).thenReturn(Optional.of(movie));
when(movieTimeRepository.findValidMovieTimes(any(), any(), any()))
.thenReturn(Collections.emptyList());
@@ -55,31 +52,7 @@ public class MovieTimeServiceImplTest {
// then
assertTrue(movieTimeDtoList.isEmpty());
}
@Test
@DisplayName("MovieTime Service Test - get list when there is valid movie times")
void shouldGetMovieTimeList() {
// given
Movie movie = new Movie(title, 106L);
Theater theater = new Theater(1);
MovieTime movieTime = MovieTime.of(movie, theater, 1,
LocalDateTime.of(2022, 7, 1, 17, 0, 0)
);
movieTimes.add(movieTime);
when(movieRepository.findByTitle(title))
.thenReturn(Optional.of(movie));
when(movieTimeRepository.findValidMovieTimes(any(), any(), any()))
.thenReturn(movieTimes);
// when
List<MovieTimeDto> movieTimeDtoList = movieTimeService.getMovieTimes(title, LocalDate.of(2022, 07, 01));
// then
assertTrue(!movieTimeDtoList.isEmpty());
}
}

View File

@@ -31,7 +31,6 @@ class AuthControllerTest {
private static final String LOGIN_URL = "/api/auth/token";
private static final String REGISTER_URL = "/api/users";
private static final String USER_EMAIL = "ticketing@gmail.com";
@Autowired
WebApplicationContext context;
@@ -54,7 +53,7 @@ class AuthControllerTest {
@DisplayName("로그인 인증 성공")
void loginSuccess() throws Exception {
// given
LoginRequest request = new LoginRequest(USER_EMAIL, "qwe123");
LoginRequest request = new LoginRequest("ticketing@gmail.com", "qwe123");
// when
ResultActions actions = mvc.perform(post(LOGIN_URL)
@@ -70,7 +69,7 @@ class AuthControllerTest {
@DisplayName("로그인 패스워드 인증 실패")
void loginPasswordFail() throws Exception {
// given
LoginRequest request = new LoginRequest(USER_EMAIL, "qwe1234");
LoginRequest request = new LoginRequest("ticketing@gmail.com", "qwe1234");
// when
ResultActions actions = mvc.perform(post(LOGIN_URL)
@@ -82,6 +81,10 @@ class AuthControllerTest {
.andExpect(status().isUnauthorized());
}
private String asJsonString(Object object) throws JsonProcessingException {
return objectMapper.writeValueAsString(object);
}
@BeforeEach
void init() throws Exception {
mvc = MockMvcBuilders
@@ -89,7 +92,7 @@ class AuthControllerTest {
.apply(springSecurity())
.build();
SignUpRequest signUpRequest = new SignUpRequest("ticketing", USER_EMAIL, "qwe123", "010-1234-5678");
SignUpRequest signUpRequest = new SignUpRequest("ticketing", "ticketing@gmail.com", "qwe123", "010-2240-7920");
mvc.perform(post(REGISTER_URL)
.content(asJsonString(signUpRequest))
@@ -101,8 +104,4 @@ class AuthControllerTest {
refreshRedisRepository.deleteAll();
}
private String asJsonString(Object object) throws JsonProcessingException {
return objectMapper.writeValueAsString(object);
}
}

View File

@@ -0,0 +1,31 @@
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 = new User("유저1", "email@gmail.com", "testPassword01", UserGrade.GUEST, "010-1234-5678");
// when
userRepository.save(user);
// then
assertThat(user).isNotNull();
}
}

View File

@@ -1,3 +1,28 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/ticketing_test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: ENC(LowN1n4w0Ep/DqLD8+q5Bq6AXM4b8e3V)
password: ENC(OMvGcpZLpggFTiGNkqNe66Zq/SmJXF6o)
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
jpa:
properties:
hibernate:
show_sql: true
format_sql: true
hibernate:
ddl-auto: create
mvc:
pathmatch:
matching-strategy: ant_path_matcher
jasypt:
encryptor:
bean: jasyptStringEncryptor
jwt:
access-header: Authorization

3
test/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
test/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
test/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/test.iml" filepath="$PROJECT_DIR$/test.iml" />
</modules>
</component>
</project>

10
test/.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

6
test/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

Binary file not shown.

148
test/src/SnowFlake.java Normal file
View File

@@ -0,0 +1,148 @@
public class SnowFlake {
//Data center (machine room) id
private long datacenterId;
//Machine ID
private long workerId;
//Same time series
private long sequence;
public SnowFlake(long workerId, long datacenterId) {
this(workerId, datacenterId, 0);
}
public SnowFlake(long workerId, long datacenterId, long sequence) {
//Legal judgment
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequence;
}
//Start timestamp
private long twepoch = 1420041600000L;
//Machine room number, the number of digits occupied by the ID of the machine room is 5 bits, the maximum is 11111 (binary) - > 31 (decimal)
private long datacenterIdBits = 5L;
//Machine ID所占的位数 5个bit 最大:11111(2进制)--> 31(10进制)
private long workerIdBits = 5L;
//5 bit can only have 31 digits at most, that is, the machine ID can only be within 32 at most
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
//5-bit can only have 31 digits at most, and the machine room ID can only be within 32 at most
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
//Same time series所占的位数 12个bit 111111111111 = 4095 最多就是同一毫秒生成4096个
private long sequenceBits = 12L;
//Offset of workerid
private long workerIdShift = sequenceBits;
//Offset of datacenter ID
private long datacenterIdShift = sequenceBits + workerIdBits;
//Offset of timestampleft
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//Serial number mask 4095 (0b111111 = 0xfff = 4095)
//It is used for the sum operation of serial number to ensure that the maximum value of serial number is between 0-4095
private long sequenceMask = -1L ^ (-1L << sequenceBits);
//Last timestamp
private long lastTimestamp = -1L;
//Get machine ID
public long getWorkerId() {
return workerId;
}
//Get machine room ID
public long getDatacenterId() {
return datacenterId;
}
//Get the latest timestamp
public long getLastTimestamp() {
return lastTimestamp;
}
//Get the next random ID
public synchronized long nextId() {
//Gets the current timestamp in milliseconds
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
lastTimestamp - timestamp));
}
//Weight removal
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//Sequence sequence is greater than 4095
if (sequence == 0) {
//Method called to the next timestamp
timestamp = tilNextMillis(lastTimestamp);
}
} else {
//If it is the first acquisition of the current time, it is set to 0
sequence = 0;
}
//Record the last timestamp
lastTimestamp = timestamp;
//Offset calculation
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
//Get latest timestamp
long timestamp = timeGen();
//If the latest timestamp is found to be less than or equal to the timestamp whose serial number has exceeded 4095
while (timestamp <= lastTimestamp) {
//If not, continue
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlake worker = new SnowFlake(1, 1);
long timer = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
worker.nextId();
}
System.out.println(System.currentTimeMillis());
System.out.println(System.currentTimeMillis() - timer);
for(int i=0; i<200; i++) {
System.out.println( );
System.out.println(worker.nextId());
}
}
}

11
test/test.iml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>