From c3be2a1da310793265289b06f6e56ea6c4d8cc43 Mon Sep 17 00:00:00 2001 From: sujeong11 <87969121+sujeong11@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:36:09 +0900 Subject: [PATCH 01/36] Create README.md --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f651ce5 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Spring-JPA-study From 24c2bf37bd8f0cf0cbc0d4224960ba8282a310e7 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 26 Jan 2023 05:22:20 +0900 Subject: [PATCH 02/36] =?UTF-8?q?chore:=20=EC=8A=A4=ED=94=84=EB=A7=81?= =?UTF-8?q?=EB=B6=80=ED=8A=B8=EC=99=80=20=EC=9E=90=EB=B0=94=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 +++--- .../resources/{application.properties => application.yml} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename src/main/resources/{application.properties => application.yml} (100%) diff --git a/build.gradle b/build.gradle index 15b77ef..3dcba92 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.0.1' - id 'io.spring.dependency-management' version '1.1.0' + id 'org.springframework.boot' version '2.7.8' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'com.dku' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '17' +sourceCompatibility = '11' configurations { compileOnly { diff --git a/src/main/resources/application.properties b/src/main/resources/application.yml similarity index 100% rename from src/main/resources/application.properties rename to src/main/resources/application.yml From af7b00cdbef33c2342c8cac89289f900ff251f34 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Fri, 27 Jan 2023 20:29:18 +0900 Subject: [PATCH 03/36] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8b13789..1482493 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1 +1,25 @@ +server: + port: 8080 +spring: + application: + name: springstudy + + datasource: + url: ${DB_URL} + driver-class-name: com.mysql.cj.jdbc.Driver + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + show_sql: true + +logging: + level: + com.dku.springstudy: DEBUG \ No newline at end of file From e3a744644c56ddfc76e3b38c28bf79e64f5ec498 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Fri, 27 Jan 2023 20:31:42 +0900 Subject: [PATCH 04/36] =?UTF-8?q?docs:=20ERD=20=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- document/ERD.png | Bin 0 -> 80982 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 document/ERD.png diff --git a/document/ERD.png b/document/ERD.png new file mode 100644 index 0000000000000000000000000000000000000000..a8542933bd64fd1e48965633920592a94638401a GIT binary patch literal 80982 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஢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 E0Md*U(*OVf literal 0 HcmV?d00001 From bbf68c94923a11f4ee5d84af5425e14cca4b9a8d Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Fri, 27 Jan 2023 20:48:00 +0900 Subject: [PATCH 05/36] =?UTF-8?q?feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dku/springstudy/domain/User.java | 52 +++++++++++++++++++ .../dku/springstudy/domain/constant/Role.java | 6 +++ 2 files changed, 58 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/domain/User.java create mode 100644 src/main/java/com/dku/springstudy/domain/constant/Role.java diff --git a/src/main/java/com/dku/springstudy/domain/User.java b/src/main/java/com/dku/springstudy/domain/User.java new file mode 100644 index 0000000..970f5ae --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/User.java @@ -0,0 +1,52 @@ +package com.dku.springstudy.domain; + +import com.dku.springstudy.domain.constant.Role; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(unique = true, nullable = false) + private String email; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private String name; + + @Column(unique = true, nullable = false) + private String phoneNumber; + + @Column(unique = true, nullable = false) + private String nickname; + + @Column(nullable = false) + private int status; + + @Enumerated(EnumType.STRING) + private Role role; + + @Builder + private User(String email, String password, String name, String phoneNumber, String nickname) { + this.email = email; + this.password = password; + this.name = name; + this.phoneNumber = phoneNumber; + this.nickname = nickname; + this.status = 1; + this.role = role.USER; + } +} diff --git a/src/main/java/com/dku/springstudy/domain/constant/Role.java b/src/main/java/com/dku/springstudy/domain/constant/Role.java new file mode 100644 index 0000000..7d64e83 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/constant/Role.java @@ -0,0 +1,6 @@ +package com.dku.springstudy.domain.constant; + +public enum Role { + + USER, ADMIN; +} From 3be625f8e8903f5c044310b8aa02d6831bfa0115 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Fri, 27 Jan 2023 20:49:42 +0900 Subject: [PATCH 06/36] =?UTF-8?q?feat:=20JPA=20Auditing=EC=9D=84=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9=ED=95=9C=20BaseTimeEntity=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/SpringStudyApplication.java | 2 ++ .../java/com/dku/springstudy/domain/User.java | 3 ++- .../domain/common/BaseTimeEntity.java | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/dku/springstudy/domain/common/BaseTimeEntity.java diff --git a/src/main/java/com/dku/springstudy/SpringStudyApplication.java b/src/main/java/com/dku/springstudy/SpringStudyApplication.java index ef164c9..1bef475 100644 --- a/src/main/java/com/dku/springstudy/SpringStudyApplication.java +++ b/src/main/java/com/dku/springstudy/SpringStudyApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class SpringStudyApplication { diff --git a/src/main/java/com/dku/springstudy/domain/User.java b/src/main/java/com/dku/springstudy/domain/User.java index 970f5ae..e2ad573 100644 --- a/src/main/java/com/dku/springstudy/domain/User.java +++ b/src/main/java/com/dku/springstudy/domain/User.java @@ -1,5 +1,6 @@ package com.dku.springstudy.domain; +import com.dku.springstudy.domain.common.BaseTimeEntity; import com.dku.springstudy.domain.constant.Role; import lombok.AccessLevel; import lombok.Builder; @@ -11,7 +12,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity -public class User { +public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/dku/springstudy/domain/common/BaseTimeEntity.java b/src/main/java/com/dku/springstudy/domain/common/BaseTimeEntity.java new file mode 100644 index 0000000..06716e7 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/common/BaseTimeEntity.java @@ -0,0 +1,25 @@ +package com.dku.springstudy.domain.common; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.time.LocalDateTime; + +@Getter +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +public class BaseTimeEntity { + + @CreatedDate + @Column(nullable = false, updatable = false) + protected LocalDateTime createdAt; + + @LastModifiedDate + @Column(nullable = false) + protected LocalDateTime modifiedAt; +} From 12b4691342b1ed18e903d9a61f0b063e7d411d66 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Fri, 27 Jan 2023 21:30:11 +0900 Subject: [PATCH 07/36] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=20Security=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/config/SecurityConfig.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/config/SecurityConfig.java diff --git a/src/main/java/com/dku/springstudy/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java new file mode 100644 index 0000000..9b65c0f --- /dev/null +++ b/src/main/java/com/dku/springstudy/config/SecurityConfig.java @@ -0,0 +1,31 @@ +package com.dku.springstudy.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Slf4j +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/api/sign-up", "/api/login").permitAll() + .anyRequest().authenticated(); + + return http.build(); + } +} From 305e8e08cdf8fa87ee7192a33481a488bf9d86ab Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sat, 28 Jan 2023 00:08:39 +0900 Subject: [PATCH 08/36] =?UTF-8?q?feat:=20User=20=EB=A0=88=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dku/springstudy/repository/UserRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/repository/UserRepository.java diff --git a/src/main/java/com/dku/springstudy/repository/UserRepository.java b/src/main/java/com/dku/springstudy/repository/UserRepository.java new file mode 100644 index 0000000..d9aa029 --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/UserRepository.java @@ -0,0 +1,7 @@ +package com.dku.springstudy.repository; + +import com.dku.springstudy.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} From ab114282ca719426212e2ea78f08f290de898155 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sat, 28 Jan 2023 13:20:07 +0900 Subject: [PATCH 09/36] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20API=20=EB=B0=8F=20validation=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20-=20SignUpRequest=EB=A5=BC=20@?= =?UTF-8?q?RequestBody=EB=A1=9C=20=EC=9E=85=EB=A0=A5=EB=B0=9B=EC=95=84=20v?= =?UTF-8?q?alidation=EC=9D=84=20=EA=B2=80=EC=82=AC=20=ED=95=98=EA=B3=A0,?= =?UTF-8?q?=20=EC=9D=B4=EC=83=81=EC=9D=B4=20=EC=97=86=EC=9C=BC=EB=A9=B4=20?= =?UTF-8?q?UserService=EC=9D=98=20signUp()=EC=9C=BC=EB=A1=9C=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EA=B0=92=EC=9D=84=20=EC=A0=84=EB=8B=AC=ED=95=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=EC=9D=B4=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EA=B0=80=EC=9E=85=EB=90=98=EC=96=B4=20=EC=9E=88=EB=8A=94?= =?UTF-8?q?=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98=EA=B3=A0=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=EB=90=98=EC=96=B4=20=EC=9E=88=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8B=A4=EB=A9=B4=20=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=95=94=ED=98=B8=ED=99=94=ED=95=98=EA=B3=A0=20DB?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=A8=20-=20DB=EC=97=90=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EB=B0=9B=EC=9D=80=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20JPA?= =?UTF-8?q?=20=EC=97=90=20=EC=BF=BC=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20existsByEmail(String=20email)=EC=9D=84=20=EC=A0=95=EC=9D=98?= =?UTF-8?q?=ED=95=A8=20-=20=ED=9C=B4=EB=8C=80=ED=8F=B0=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=98=95=EC=8B=9D=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?validation=20=EC=B6=94=EA=B0=80=20-=20token=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=98=88=EC=A0=95=EC=9D=B4?= =?UTF-8?q?=EB=AF=80=EB=A1=9C=20=EC=8B=9C=ED=81=90=EB=A6=AC=ED=8B=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=EC=97=90=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=EC=9D=84=20=EC=82=AC=EC=9A=A9=20=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EC=84=A4=EC=A0=95=EA=B3=BC=20CSRF=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../springstudy/config/SecurityConfig.java | 5 +++ .../controller/UserController.java | 24 ++++++++++++ .../springstudy/dto/user/SignUpRequest.java | 35 +++++++++++++++++ .../com/dku/springstudy/dto/user/UserDto.java | 20 ++++++++++ .../repository/UserRepository.java | 2 + .../dku/springstudy/service/UserService.java | 38 +++++++++++++++++++ 7 files changed, 125 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/controller/UserController.java create mode 100644 src/main/java/com/dku/springstudy/dto/user/SignUpRequest.java create mode 100644 src/main/java/com/dku/springstudy/dto/user/UserDto.java create mode 100644 src/main/java/com/dku/springstudy/service/UserService.java diff --git a/build.gradle b/build.gradle index 3dcba92..e27e1fa 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + implementation 'org.springframework.boot:spring-boot-starter-validation' } tasks.named('test') { diff --git a/src/main/java/com/dku/springstudy/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java index 9b65c0f..86da269 100644 --- a/src/main/java/com/dku/springstudy/config/SecurityConfig.java +++ b/src/main/java/com/dku/springstudy/config/SecurityConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @@ -22,6 +23,10 @@ public PasswordEncoder passwordEncoder() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http + .csrf().disable() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() .authorizeRequests() .antMatchers("/api/sign-up", "/api/login").permitAll() .anyRequest().authenticated(); diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java new file mode 100644 index 0000000..b48b062 --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -0,0 +1,24 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.dto.user.SignUpRequest; +import com.dku.springstudy.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @PostMapping("/sign-up") + public ResponseEntity signUp(@Valid @RequestBody SignUpRequest signUpRequest) throws Exception { + Long userId = userService.signUp(signUpRequest); + + return ResponseEntity.ok(userId); + } +} diff --git a/src/main/java/com/dku/springstudy/dto/user/SignUpRequest.java b/src/main/java/com/dku/springstudy/dto/user/SignUpRequest.java new file mode 100644 index 0000000..3685802 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/user/SignUpRequest.java @@ -0,0 +1,35 @@ +package com.dku.springstudy.dto.user; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class SignUpRequest { + + @Email(message = "이메일 형식에 맞지 않습니다.") + @NotBlank(message = "이메일을 입력해주세요.") + private String email; + + @NotBlank(message = "비밀번호를 입력해주세요.") + @Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", + message = "비밀번호는 영문 대/소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자여야 합니다.") + private String password; + + @NotBlank(message = "이름을 입력해주세요.") + private String name; + + @NotBlank(message = "폰번호를 입력해주세요.") + @Pattern(regexp = "[0-9]{3}-+[0-9]{4}-+[0-9]{4}", message = "폰번호는 010-XXXX-XXXX 형태여야 합니다.") + private String phoneNumber; + + @NotBlank(message = "닉네임을 입력해주세요.") + @Size(min = 2, message = "닉네임이 너무 짧습니다.") + private String nickname; +} diff --git a/src/main/java/com/dku/springstudy/dto/user/UserDto.java b/src/main/java/com/dku/springstudy/dto/user/UserDto.java new file mode 100644 index 0000000..ef949a1 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/user/UserDto.java @@ -0,0 +1,20 @@ +package com.dku.springstudy.dto.user; + +import com.dku.springstudy.domain.constant.Role; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class UserDto { + + private Long id; + private String email; + private String password; + private String name; + private String phoneNumber; + private String nickname; + private int status; + private Role role; +} diff --git a/src/main/java/com/dku/springstudy/repository/UserRepository.java b/src/main/java/com/dku/springstudy/repository/UserRepository.java index d9aa029..2fb7d42 100644 --- a/src/main/java/com/dku/springstudy/repository/UserRepository.java +++ b/src/main/java/com/dku/springstudy/repository/UserRepository.java @@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { + + boolean existsByEmail(String email); } diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java new file mode 100644 index 0000000..68d09c8 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -0,0 +1,38 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.User; +import com.dku.springstudy.dto.user.SignUpRequest; +import com.dku.springstudy.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class UserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Transactional + public Long signUp(SignUpRequest signUpRequest) throws Exception { + + if (userRepository.existsByEmail(signUpRequest.getEmail())) { + throw new Exception("이미 가입된 이메일입니다."); + } + + User user = User.builder() + .email(signUpRequest.getEmail()) + .name(signUpRequest.getName()) + .password(passwordEncoder.encode(signUpRequest.getPassword())) + .phoneNumber(signUpRequest.getPhoneNumber()) + .nickname(signUpRequest.getNickname()) + .build(); + + userRepository.save(user); + + return user.getId(); + } +} From f053e403e2824cda4163d870d0fb2d73f91be02b Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sat, 28 Jan 2023 21:42:40 +0900 Subject: [PATCH 10/36] =?UTF-8?q?chore:=20jwt=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index e27e1fa..8ff55cd 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,11 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' implementation 'org.springframework.boot:spring-boot-starter-validation' + + // JWT 관련 라이브러리 추가 + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' } tasks.named('test') { From e51812141b5662d465b3bcf5f084098cd324b653 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 29 Jan 2023 13:01:01 +0900 Subject: [PATCH 11/36] =?UTF-8?q?feat:=20Spring=20Security=20&=20JWT=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/config/SecurityConfig.java | 16 +++- .../domain/auth/CustomUserDetails.java | 61 ++++++++++++ .../jwt/JwtAuthenticationFilter.java | 48 ++++++++++ .../dku/springstudy/jwt/TokenProvider.java | 94 +++++++++++++++++++ .../repository/UserRepository.java | 3 + .../service/CustomUserDetailsService.java | 24 +++++ 6 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/domain/auth/CustomUserDetails.java create mode 100644 src/main/java/com/dku/springstudy/jwt/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/dku/springstudy/jwt/TokenProvider.java create mode 100644 src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java diff --git a/src/main/java/com/dku/springstudy/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java index 86da269..8fd3db9 100644 --- a/src/main/java/com/dku/springstudy/config/SecurityConfig.java +++ b/src/main/java/com/dku/springstudy/config/SecurityConfig.java @@ -1,5 +1,8 @@ package com.dku.springstudy.config; +import com.dku.springstudy.jwt.JwtAuthenticationFilter; +import com.dku.springstudy.jwt.TokenProvider; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,12 +12,16 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Slf4j @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { + private final TokenProvider tokenProvider; + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -25,11 +32,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 X .and() .authorizeRequests() .antMatchers("/api/sign-up", "/api/login").permitAll() - .anyRequest().authenticated(); + .anyRequest().authenticated() + .and() + .formLogin() + .usernameParameter("email") // 식별 데이터를 email로 사용 + .and() + .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/dku/springstudy/domain/auth/CustomUserDetails.java b/src/main/java/com/dku/springstudy/domain/auth/CustomUserDetails.java new file mode 100644 index 0000000..4f6c92f --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/auth/CustomUserDetails.java @@ -0,0 +1,61 @@ +package com.dku.springstudy.domain.auth; + +import com.dku.springstudy.domain.User; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Arrays; +import java.util.Collection; + +@Getter +public class CustomUserDetails implements UserDetails { + + private Long id; + private String email; + @JsonIgnore + private String password; + private String nickname; + private String authority; + + public CustomUserDetails(User user) { + this.id = user.getId(); + this.email = user.getEmail(); + this.password = user.getPassword(); + this.nickname = user.getNickname(); + this.authority = user.getRole().name(); + } + + @Override + public Collection getAuthorities() { + // 만약, 여러 개의 권한을 부여해 관리해야 한다면 List 형태로 반환해야 한다. + return Arrays.asList(new SimpleGrantedAuthority(authority)); + } + + @Override + public String getUsername() { + return nickname; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/dku/springstudy/jwt/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..bd84357 --- /dev/null +++ b/src/main/java/com/dku/springstudy/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,48 @@ +package com.dku.springstudy.jwt; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Slf4j +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends GenericFilterBean { + + private final TokenProvider tokenProvider; + + public static final String AUTHORIZATION_HEADER = "Authorization"; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String token = resolveToken((HttpServletRequest) request); + + if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { // 토큰이 유효하다면 + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); // 사용자 정보를 SecurityContext에 저장 + } else { + log.debug("유효한 JWT 토큰이 없습니다, uri: {}", ((HttpServletRequest) request).getRequestURI()); + } + + chain.doFilter(request, response); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); // Header에서 token을 가져온다. + + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); // Bearer를 제외시킨 값 + } + + return null; + } +} diff --git a/src/main/java/com/dku/springstudy/jwt/TokenProvider.java b/src/main/java/com/dku/springstudy/jwt/TokenProvider.java new file mode 100644 index 0000000..d987c17 --- /dev/null +++ b/src/main/java/com/dku/springstudy/jwt/TokenProvider.java @@ -0,0 +1,94 @@ +package com.dku.springstudy.jwt; + +import com.dku.springstudy.service.CustomUserDetailsService; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SecurityException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TokenProvider { + + private final CustomUserDetailsService customUserDetailsService; + + // TODO: @Value를 사용 + private String secretKey = "alkmcldnenlsodoewkrkkdjsmlskopkemdmimmvfdsnlkdsmflsjdodf"; + + private final Long accessTokenValidMilliSecond = 24 * 60 * 60 * 1000L; // 1일 + private final Long refreshTokenValidMilliSecond = 30 * 24 * 60 * 60 * 1000L; // 30일 + + // secretKey 값을 BASE64로 decode해 key 변수에 할당 + private Key getSecretKey(String secretKey) { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + return Keys.hmacShaKeyFor(keyBytes); + } + + // 토큰 발급 + public String createToken(Claims claims, long expiredDuration) { + Date now = new Date(); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSecretKey(secretKey), SignatureAlgorithm.HS256) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + expiredDuration)) + .compact(); + } + + // 로그인 성공 시 access token 발급 + public String createLoginAccessToken(String userEmail, String role) { + Claims claims = Jwts.claims(); + claims.setSubject(userEmail); + claims.put("role", role); + + return createToken(claims, accessTokenValidMilliSecond); + } + + // 로그인 성공 시 refresh token 발급 + public String createLoginRefreshToken(String userEmail) { + Claims claims = Jwts.claims(); + claims.setSubject(userEmail); + + return createToken(claims, refreshTokenValidMilliSecond); + } + + // 토큰에서 인증 정보 추출 + public Authentication getAuthentication(String token) { + UserDetails userDetails = customUserDetailsService.loadUserByUsername(getEmail(token)); + return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + } + + // 토큰에서 Email 추출 + public String getEmail(String token) { + return Jwts.parserBuilder().setSigningKey(getSecretKey(secretKey)).build().parseClaimsJws(token).getBody().getSubject(); + } + + // 토큰 검증 + public boolean validateToken(String token) { + try { + Jws claimsJws = Jwts.parserBuilder().setSigningKey(getSecretKey(secretKey)).build().parseClaimsJws(token); + return !claimsJws.getBody().getExpiration().before(new Date()); + } catch (ExpiredJwtException e) { + log.info("만료된 토큰입니다."); + } catch (UnsupportedJwtException e) { + log.info("지원되지 않는 토큰입니다."); + } catch (IllegalArgumentException e) { + log.info("JWT 토큰이 잘못되었습니다."); + } catch (SecurityException | MalformedJwtException e) { + log.info("잘못된 JWT 서명입니다."); + } + return false; + } +} diff --git a/src/main/java/com/dku/springstudy/repository/UserRepository.java b/src/main/java/com/dku/springstudy/repository/UserRepository.java index 2fb7d42..28eea54 100644 --- a/src/main/java/com/dku/springstudy/repository/UserRepository.java +++ b/src/main/java/com/dku/springstudy/repository/UserRepository.java @@ -3,7 +3,10 @@ import com.dku.springstudy.domain.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface UserRepository extends JpaRepository { boolean existsByEmail(String email); + Optional findByEmail(String nickname); } diff --git a/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java b/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java new file mode 100644 index 0000000..ab6c7b3 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java @@ -0,0 +1,24 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.User; +import com.dku.springstudy.domain.auth.CustomUserDetails; +import com.dku.springstudy.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("해당 이메일을 가진 유저를 찾을 수 없습니다.")); + return new CustomUserDetails(user); + } +} From 89d78144a4faffdf1f069a829c713f696b7175b0 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 29 Jan 2023 13:16:54 +0900 Subject: [PATCH 12/36] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/UserController.java | 11 +++++++-- .../dto/user/request/LoginRequest.java | 23 +++++++++++++++++++ .../dto/user/{ => request}/SignUpRequest.java | 2 +- .../dto/user/response/LoginResponse.java | 17 ++++++++++++++ .../dku/springstudy/service/UserService.java | 19 ++++++++++++++- 5 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/dto/user/request/LoginRequest.java rename src/main/java/com/dku/springstudy/dto/user/{ => request}/SignUpRequest.java (96%) create mode 100644 src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index b48b062..b24348d 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -1,6 +1,8 @@ package com.dku.springstudy.controller; -import com.dku.springstudy.dto.user.SignUpRequest; +import com.dku.springstudy.dto.user.request.LoginRequest; +import com.dku.springstudy.dto.user.response.LoginResponse; +import com.dku.springstudy.dto.user.request.SignUpRequest; import com.dku.springstudy.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -18,7 +20,12 @@ public class UserController { @PostMapping("/sign-up") public ResponseEntity signUp(@Valid @RequestBody SignUpRequest signUpRequest) throws Exception { Long userId = userService.signUp(signUpRequest); - return ResponseEntity.ok(userId); } + + @PostMapping("/login") + public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest) { + LoginResponse response = userService.login(loginRequest); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/dku/springstudy/dto/user/request/LoginRequest.java b/src/main/java/com/dku/springstudy/dto/user/request/LoginRequest.java new file mode 100644 index 0000000..338eee1 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/user/request/LoginRequest.java @@ -0,0 +1,23 @@ +package com.dku.springstudy.dto.user.request; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class LoginRequest { + + @Email(message = "이메일 형식에 맞지 않습니다.") + @NotBlank(message = "이메일을 입력해주세요.") + private String email; + + @NotBlank(message = "비밀번호를 입력해주세요.") + @Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", + message = "비밀번호는 영문 대/소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자여야 합니다.") + private String password; +} diff --git a/src/main/java/com/dku/springstudy/dto/user/SignUpRequest.java b/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequest.java similarity index 96% rename from src/main/java/com/dku/springstudy/dto/user/SignUpRequest.java rename to src/main/java/com/dku/springstudy/dto/user/request/SignUpRequest.java index 3685802..07fdc7c 100644 --- a/src/main/java/com/dku/springstudy/dto/user/SignUpRequest.java +++ b/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequest.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.dto.user; +package com.dku.springstudy.dto.user.request; import lombok.AccessLevel; import lombok.Getter; diff --git a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java b/src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java new file mode 100644 index 0000000..b3c7a2c --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java @@ -0,0 +1,17 @@ +package com.dku.springstudy.dto.user.response; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class LoginResponse { + + private String loginAccessToken; + private String loginRefreshToken; + + public static LoginResponse of(String loginAccessToken, String loginRefreshToken) { + return new LoginResponse(loginAccessToken, loginRefreshToken); + } +} diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java index 68d09c8..c4a659e 100644 --- a/src/main/java/com/dku/springstudy/service/UserService.java +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -1,7 +1,10 @@ package com.dku.springstudy.service; import com.dku.springstudy.domain.User; -import com.dku.springstudy.dto.user.SignUpRequest; +import com.dku.springstudy.dto.user.request.LoginRequest; +import com.dku.springstudy.dto.user.response.LoginResponse; +import com.dku.springstudy.dto.user.request.SignUpRequest; +import com.dku.springstudy.jwt.TokenProvider; import com.dku.springstudy.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; @@ -15,6 +18,7 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final TokenProvider tokenProvider; @Transactional public Long signUp(SignUpRequest signUpRequest) throws Exception { @@ -35,4 +39,17 @@ public Long signUp(SignUpRequest signUpRequest) throws Exception { return user.getId(); } + + public LoginResponse login(LoginRequest loginRequest) { + User user = userRepository.findByEmail(loginRequest.getEmail()) + .orElseThrow(() -> new IllegalArgumentException("해당 이메일로 가입된 회원이 없습니다.")); + + if(passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { + String loginAccessToken = tokenProvider.createLoginAccessToken(user.getEmail(), user.getRole().name()); + String loginRefreshToken = tokenProvider.createLoginRefreshToken(user.getEmail()); + return LoginResponse.of(loginAccessToken, loginRefreshToken); + } else { + throw new IllegalArgumentException("비밀번호가 틀렸습니다."); + } + } } From 867f3ec5c0bbfd4e15d86157e38b2e4ddcc55a6f Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 29 Jan 2023 13:35:39 +0900 Subject: [PATCH 13/36] =?UTF-8?q?feat:=20JWT=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/config/SecurityConfig.java | 8 ++++++++ .../jwt/exception/JwtAccessDeniedHandler.java | 19 +++++++++++++++++++ .../JwtAuthenticationEntryPoint.java | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/jwt/exception/JwtAccessDeniedHandler.java create mode 100644 src/main/java/com/dku/springstudy/jwt/exception/JwtAuthenticationEntryPoint.java diff --git a/src/main/java/com/dku/springstudy/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java index 8fd3db9..d0c7a4d 100644 --- a/src/main/java/com/dku/springstudy/config/SecurityConfig.java +++ b/src/main/java/com/dku/springstudy/config/SecurityConfig.java @@ -2,6 +2,8 @@ import com.dku.springstudy.jwt.JwtAuthenticationFilter; import com.dku.springstudy.jwt.TokenProvider; +import com.dku.springstudy.jwt.exception.JwtAccessDeniedHandler; +import com.dku.springstudy.jwt.exception.JwtAuthenticationEntryPoint; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -21,6 +23,8 @@ public class SecurityConfig { private final TokenProvider tokenProvider; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Bean public PasswordEncoder passwordEncoder() { @@ -41,6 +45,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .formLogin() .usernameParameter("email") // 식별 데이터를 email로 사용 .and() + .exceptionHandling() + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .accessDeniedHandler(jwtAccessDeniedHandler) + .and() .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); diff --git a/src/main/java/com/dku/springstudy/jwt/exception/JwtAccessDeniedHandler.java b/src/main/java/com/dku/springstudy/jwt/exception/JwtAccessDeniedHandler.java new file mode 100644 index 0000000..3166945 --- /dev/null +++ b/src/main/java/com/dku/springstudy/jwt/exception/JwtAccessDeniedHandler.java @@ -0,0 +1,19 @@ +package com.dku.springstudy.jwt.exception; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_FORBIDDEN); // 403 에러 + } +} diff --git a/src/main/java/com/dku/springstudy/jwt/exception/JwtAuthenticationEntryPoint.java b/src/main/java/com/dku/springstudy/jwt/exception/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..bd28920 --- /dev/null +++ b/src/main/java/com/dku/springstudy/jwt/exception/JwtAuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +package com.dku.springstudy.jwt.exception; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // 401 에러 + } +} From c7c4a1e1cfd75f605422dccf43ee681957870d54 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 29 Jan 2023 19:14:22 +0900 Subject: [PATCH 14/36] =?UTF-8?q?feat:=20API=20=EC=84=B1=EA=B3=B5/?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dku/springstudy/dto/common/BaseResponse.java | 11 +++++++++++ .../springstudy/dto/common/ErrorResponse.java | 16 ++++++++++++++++ .../springstudy/dto/common/SuccessResponse.java | 14 ++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/dto/common/BaseResponse.java create mode 100644 src/main/java/com/dku/springstudy/dto/common/ErrorResponse.java create mode 100644 src/main/java/com/dku/springstudy/dto/common/SuccessResponse.java diff --git a/src/main/java/com/dku/springstudy/dto/common/BaseResponse.java b/src/main/java/com/dku/springstudy/dto/common/BaseResponse.java new file mode 100644 index 0000000..116b9fa --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/common/BaseResponse.java @@ -0,0 +1,11 @@ +package com.dku.springstudy.dto.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class BaseResponse { + + private boolean isSuccess; +} diff --git a/src/main/java/com/dku/springstudy/dto/common/ErrorResponse.java b/src/main/java/com/dku/springstudy/dto/common/ErrorResponse.java new file mode 100644 index 0000000..809fc31 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/common/ErrorResponse.java @@ -0,0 +1,16 @@ +package com.dku.springstudy.dto.common; + +import lombok.Getter; + +@Getter +public class ErrorResponse extends BaseResponse { + + private int errorCode; + private String errorMessage; + + public ErrorResponse(int errorCode, String errorMessage) { + super(false); + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } +} diff --git a/src/main/java/com/dku/springstudy/dto/common/SuccessResponse.java b/src/main/java/com/dku/springstudy/dto/common/SuccessResponse.java new file mode 100644 index 0000000..c44aaf7 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/common/SuccessResponse.java @@ -0,0 +1,14 @@ +package com.dku.springstudy.dto.common; + +import lombok.Getter; + +@Getter +public class SuccessResponse extends BaseResponse { + + private T data; + + public SuccessResponse(T data) { + super(true); + this.data = data; + } +} From eb354a250b948f13fff8f5f139998f5b1b684d1c Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 29 Jan 2023 19:18:59 +0900 Subject: [PATCH 15/36] =?UTF-8?q?feat:=20Exception=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20-=20enum=EC=9C=BC=EB=A1=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=9D=98=20=EC=83=81=ED=83=9C=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EC=99=80=20=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=ED=95=98=EA=B3=A0=20=EC=97=90=EB=9F=AC=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=8B=9C=20@RestControllerAdvice=EC=99=80=20@Excep?= =?UTF-8?q?tionHandler=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=B4=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../controller/UserController.java | 16 ++++++--- .../exception/CustomException.java | 11 ++++++ .../dku/springstudy/exception/ErrorCode.java | 24 +++++++++++++ .../exception/GlobalExceptionHandler.java | 35 +++++++++++++++++++ .../dku/springstudy/service/UserService.java | 10 +++--- 6 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/exception/CustomException.java create mode 100644 src/main/java/com/dku/springstudy/exception/ErrorCode.java create mode 100644 src/main/java/com/dku/springstudy/exception/GlobalExceptionHandler.java diff --git a/build.gradle b/build.gradle index 8ff55cd..73c5cc7 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,8 @@ dependencies { implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' + + implementation 'org.apache.httpcomponents:httpclient:4.5.7' } tasks.named('test') { diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index b24348d..3986396 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -1,10 +1,12 @@ package com.dku.springstudy.controller; +import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.user.request.LoginRequest; import com.dku.springstudy.dto.user.response.LoginResponse; import com.dku.springstudy.dto.user.request.SignUpRequest; import com.dku.springstudy.service.UserService; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -18,14 +20,20 @@ public class UserController { private final UserService userService; @PostMapping("/sign-up") - public ResponseEntity signUp(@Valid @RequestBody SignUpRequest signUpRequest) throws Exception { + public ResponseEntity> signUp(@Valid @RequestBody SignUpRequest signUpRequest) { Long userId = userService.signUp(signUpRequest); - return ResponseEntity.ok(userId); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse(userId)); } @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest) { + public ResponseEntity> login(@Valid @RequestBody LoginRequest loginRequest) { LoginResponse response = userService.login(loginRequest); - return ResponseEntity.ok(response); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse(response)); } } diff --git a/src/main/java/com/dku/springstudy/exception/CustomException.java b/src/main/java/com/dku/springstudy/exception/CustomException.java new file mode 100644 index 0000000..2c7713a --- /dev/null +++ b/src/main/java/com/dku/springstudy/exception/CustomException.java @@ -0,0 +1,11 @@ +package com.dku.springstudy.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class CustomException extends RuntimeException { + + ErrorCode errorCode; +} diff --git a/src/main/java/com/dku/springstudy/exception/ErrorCode.java b/src/main/java/com/dku/springstudy/exception/ErrorCode.java new file mode 100644 index 0000000..404c83e --- /dev/null +++ b/src/main/java/com/dku/springstudy/exception/ErrorCode.java @@ -0,0 +1,24 @@ +package com.dku.springstudy.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.http.HttpException; +import org.springframework.http.HttpStatus; + + +@AllArgsConstructor +@Getter +public enum ErrorCode { + USER_EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 이메일을 가진 회원을 찾을 수 없습니다."), + USER_EMAIL_Duplication(HttpStatus.CONFLICT, "이미 가입된 이메일입니다."), + USER_PASSWORD_NOT_MATCHES(HttpStatus.UNAUTHORIZED, "비밀번호가 틀렸습니다."), + ; + + private final HttpStatus httpstatus; + private final String message; + + ErrorCode(HttpException e) { + this.httpstatus = HttpStatus.INTERNAL_SERVER_ERROR; + this.message = e.getMessage(); + } +} diff --git a/src/main/java/com/dku/springstudy/exception/GlobalExceptionHandler.java b/src/main/java/com/dku/springstudy/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..7f9e386 --- /dev/null +++ b/src/main/java/com/dku/springstudy/exception/GlobalExceptionHandler.java @@ -0,0 +1,35 @@ +package com.dku.springstudy.exception; + +import com.dku.springstudy.dto.common.ErrorResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(CustomException.class) + protected ResponseEntity customExceptionHandle(CustomException e) { + log.error("ExceptionHandler throw CustomException : {}", e.getErrorCode()); + + HttpStatus status = e.getErrorCode().getHttpstatus(); + + return ResponseEntity + .status(status) + .body(new ErrorResponse(status.value(), e.getErrorCode().getMessage())); + } + + @ExceptionHandler(Exception.class) + protected ResponseEntity exceptionHandle(Exception e){ + log.error("ExceptionHandler throw Exception : {}", e.getMessage()); + + HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; + + return ResponseEntity + .status(status) + .body(new ErrorResponse(status.value(), e.getMessage())); + } +} diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java index c4a659e..8d34322 100644 --- a/src/main/java/com/dku/springstudy/service/UserService.java +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -4,6 +4,8 @@ import com.dku.springstudy.dto.user.request.LoginRequest; import com.dku.springstudy.dto.user.response.LoginResponse; import com.dku.springstudy.dto.user.request.SignUpRequest; +import com.dku.springstudy.exception.CustomException; +import com.dku.springstudy.exception.ErrorCode; import com.dku.springstudy.jwt.TokenProvider; import com.dku.springstudy.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -21,10 +23,10 @@ public class UserService { private final TokenProvider tokenProvider; @Transactional - public Long signUp(SignUpRequest signUpRequest) throws Exception { + public Long signUp(SignUpRequest signUpRequest) { if (userRepository.existsByEmail(signUpRequest.getEmail())) { - throw new Exception("이미 가입된 이메일입니다."); + throw new CustomException(ErrorCode.USER_EMAIL_Duplication); } User user = User.builder() @@ -42,14 +44,14 @@ public Long signUp(SignUpRequest signUpRequest) throws Exception { public LoginResponse login(LoginRequest loginRequest) { User user = userRepository.findByEmail(loginRequest.getEmail()) - .orElseThrow(() -> new IllegalArgumentException("해당 이메일로 가입된 회원이 없습니다.")); + .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); if(passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { String loginAccessToken = tokenProvider.createLoginAccessToken(user.getEmail(), user.getRole().name()); String loginRefreshToken = tokenProvider.createLoginRefreshToken(user.getEmail()); return LoginResponse.of(loginAccessToken, loginRefreshToken); } else { - throw new IllegalArgumentException("비밀번호가 틀렸습니다."); + throw new CustomException(ErrorCode.USER_PASSWORD_NOT_MATCHES); } } } From f38d8f409888645d0854fae4cbf339918b04c0dc Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 29 Jan 2023 21:23:32 +0900 Subject: [PATCH 16/36] =?UTF-8?q?fix:=20secretKey=EB=A5=BC=20yml=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A3=BC=EC=9E=85=EB=B0=9B=EC=95=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/dku/springstudy/jwt/TokenProvider.java | 5 +++-- src/main/resources/application.yml | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/dku/springstudy/jwt/TokenProvider.java b/src/main/java/com/dku/springstudy/jwt/TokenProvider.java index d987c17..e21a8b2 100644 --- a/src/main/java/com/dku/springstudy/jwt/TokenProvider.java +++ b/src/main/java/com/dku/springstudy/jwt/TokenProvider.java @@ -7,6 +7,7 @@ import io.jsonwebtoken.security.SecurityException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -22,8 +23,8 @@ public class TokenProvider { private final CustomUserDetailsService customUserDetailsService; - // TODO: @Value를 사용 - private String secretKey = "alkmcldnenlsodoewkrkkdjsmlskopkemdmimmvfdsnlkdsmflsjdodf"; + @Value("${jwt.secret-key}") + private String secretKey; private final Long accessTokenValidMilliSecond = 24 * 60 * 60 * 1000L; // 1일 private final Long refreshTokenValidMilliSecond = 30 * 24 * 60 * 60 * 1000L; // 30일 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1482493..00958eb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,4 +22,7 @@ spring: logging: level: - com.dku.springstudy: DEBUG \ No newline at end of file + com.dku.springstudy: DEBUG + +jwt: + secret-key: ${JWT_SECRET_KEY} \ No newline at end of file From 0ef5b831575208123ac7874d56e2ed76f02bd8c7 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Wed, 1 Feb 2023 19:42:49 +0900 Subject: [PATCH 17/36] =?UTF-8?q?refactor:=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B0=8F=20=EB=B3=80=EC=88=98=EB=AA=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/config/SecurityConfig.java | 12 +++---- .../controller/UserController.java | 16 ++++----- ...LoginRequest.java => LoginRequestDto.java} | 2 +- ...gnUpRequest.java => SignUpRequestDto.java} | 2 +- .../dto/user/response/LoginResponse.java | 17 --------- .../dto/user/response/LoginResponseDto.java | 17 +++++++++ .../auth => security}/CustomUserDetails.java | 2 +- .../CustomUserDetailsService.java | 3 +- .../exception/JwtAccessDeniedHandler.java | 2 +- .../JwtAuthenticationEntryPoint.java | 2 +- .../jwt/JwtAuthenticationFilter.java | 8 ++--- .../jwt/JwtTokenProvider.java} | 6 ++-- .../dku/springstudy/service/UserService.java | 36 +++++++++---------- 13 files changed, 62 insertions(+), 63 deletions(-) rename src/main/java/com/dku/springstudy/dto/user/request/{LoginRequest.java => LoginRequestDto.java} (96%) rename src/main/java/com/dku/springstudy/dto/user/request/{SignUpRequest.java => SignUpRequestDto.java} (97%) delete mode 100644 src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java create mode 100644 src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java rename src/main/java/com/dku/springstudy/{domain/auth => security}/CustomUserDetails.java (97%) rename src/main/java/com/dku/springstudy/{service => security}/CustomUserDetailsService.java (90%) rename src/main/java/com/dku/springstudy/{jwt => security}/exception/JwtAccessDeniedHandler.java (93%) rename src/main/java/com/dku/springstudy/{jwt => security}/exception/JwtAuthenticationEntryPoint.java (93%) rename src/main/java/com/dku/springstudy/{ => security}/jwt/JwtAuthenticationFilter.java (84%) rename src/main/java/com/dku/springstudy/{jwt/TokenProvider.java => security/jwt/JwtTokenProvider.java} (96%) diff --git a/src/main/java/com/dku/springstudy/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java index d0c7a4d..94e555d 100644 --- a/src/main/java/com/dku/springstudy/config/SecurityConfig.java +++ b/src/main/java/com/dku/springstudy/config/SecurityConfig.java @@ -1,9 +1,9 @@ package com.dku.springstudy.config; -import com.dku.springstudy.jwt.JwtAuthenticationFilter; -import com.dku.springstudy.jwt.TokenProvider; -import com.dku.springstudy.jwt.exception.JwtAccessDeniedHandler; -import com.dku.springstudy.jwt.exception.JwtAuthenticationEntryPoint; +import com.dku.springstudy.security.jwt.JwtAuthenticationFilter; +import com.dku.springstudy.security.jwt.JwtTokenProvider; +import com.dku.springstudy.security.exception.JwtAccessDeniedHandler; +import com.dku.springstudy.security.exception.JwtAuthenticationEntryPoint; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -22,7 +22,7 @@ @RequiredArgsConstructor public class SecurityConfig { - private final TokenProvider tokenProvider; + private final JwtTokenProvider jwtTokenProvider; private final JwtAccessDeniedHandler jwtAccessDeniedHandler; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @@ -49,7 +49,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler(jwtAccessDeniedHandler) .and() - .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 3986396..96293d5 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -1,9 +1,9 @@ package com.dku.springstudy.controller; import com.dku.springstudy.dto.common.SuccessResponse; -import com.dku.springstudy.dto.user.request.LoginRequest; -import com.dku.springstudy.dto.user.response.LoginResponse; -import com.dku.springstudy.dto.user.request.SignUpRequest; +import com.dku.springstudy.dto.user.request.LoginRequestDto; +import com.dku.springstudy.dto.user.response.LoginResponseDto; +import com.dku.springstudy.dto.user.request.SignUpRequestDto; import com.dku.springstudy.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -20,8 +20,8 @@ public class UserController { private final UserService userService; @PostMapping("/sign-up") - public ResponseEntity> signUp(@Valid @RequestBody SignUpRequest signUpRequest) { - Long userId = userService.signUp(signUpRequest); + public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { + Long userId = userService.signUp(signUpRequestDto); return ResponseEntity .status(HttpStatus.OK) @@ -29,11 +29,11 @@ public ResponseEntity> signUp(@Valid @RequestBody SignUpRe } @PostMapping("/login") - public ResponseEntity> login(@Valid @RequestBody LoginRequest loginRequest) { - LoginResponse response = userService.login(loginRequest); + public ResponseEntity> login(@Valid @RequestBody LoginRequestDto loginRequestDto) { + LoginResponseDto response = userService.login(loginRequestDto); return ResponseEntity .status(HttpStatus.OK) - .body(new SuccessResponse(response)); + .body(new SuccessResponse(response)); } } diff --git a/src/main/java/com/dku/springstudy/dto/user/request/LoginRequest.java b/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java similarity index 96% rename from src/main/java/com/dku/springstudy/dto/user/request/LoginRequest.java rename to src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java index 338eee1..bd684d9 100644 --- a/src/main/java/com/dku/springstudy/dto/user/request/LoginRequest.java +++ b/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java @@ -10,7 +10,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class LoginRequest { +public class LoginRequestDto { @Email(message = "이메일 형식에 맞지 않습니다.") @NotBlank(message = "이메일을 입력해주세요.") diff --git a/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequest.java b/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java similarity index 97% rename from src/main/java/com/dku/springstudy/dto/user/request/SignUpRequest.java rename to src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java index 07fdc7c..a48d830 100644 --- a/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequest.java +++ b/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java @@ -11,7 +11,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class SignUpRequest { +public class SignUpRequestDto { @Email(message = "이메일 형식에 맞지 않습니다.") @NotBlank(message = "이메일을 입력해주세요.") diff --git a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java b/src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java deleted file mode 100644 index b3c7a2c..0000000 --- a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponse.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.dku.springstudy.dto.user.response; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@Getter -public class LoginResponse { - - private String loginAccessToken; - private String loginRefreshToken; - - public static LoginResponse of(String loginAccessToken, String loginRefreshToken) { - return new LoginResponse(loginAccessToken, loginRefreshToken); - } -} diff --git a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java b/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java new file mode 100644 index 0000000..0b48d77 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java @@ -0,0 +1,17 @@ +package com.dku.springstudy.dto.user.response; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class LoginResponseDto { + + private String accessToken; + private String refreshToken; + + public static LoginResponseDto of(String accessToken, String refreshToken) { + return new LoginResponseDto(accessToken, refreshToken); + } +} diff --git a/src/main/java/com/dku/springstudy/domain/auth/CustomUserDetails.java b/src/main/java/com/dku/springstudy/security/CustomUserDetails.java similarity index 97% rename from src/main/java/com/dku/springstudy/domain/auth/CustomUserDetails.java rename to src/main/java/com/dku/springstudy/security/CustomUserDetails.java index 4f6c92f..76cb5ef 100644 --- a/src/main/java/com/dku/springstudy/domain/auth/CustomUserDetails.java +++ b/src/main/java/com/dku/springstudy/security/CustomUserDetails.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.domain.auth; +package com.dku.springstudy.security; import com.dku.springstudy.domain.User; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java b/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java similarity index 90% rename from src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java rename to src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java index ab6c7b3..8e010f8 100644 --- a/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java +++ b/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java @@ -1,7 +1,6 @@ -package com.dku.springstudy.service; +package com.dku.springstudy.security; import com.dku.springstudy.domain.User; -import com.dku.springstudy.domain.auth.CustomUserDetails; import com.dku.springstudy.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; diff --git a/src/main/java/com/dku/springstudy/jwt/exception/JwtAccessDeniedHandler.java b/src/main/java/com/dku/springstudy/security/exception/JwtAccessDeniedHandler.java similarity index 93% rename from src/main/java/com/dku/springstudy/jwt/exception/JwtAccessDeniedHandler.java rename to src/main/java/com/dku/springstudy/security/exception/JwtAccessDeniedHandler.java index 3166945..1c629bb 100644 --- a/src/main/java/com/dku/springstudy/jwt/exception/JwtAccessDeniedHandler.java +++ b/src/main/java/com/dku/springstudy/security/exception/JwtAccessDeniedHandler.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.jwt.exception; +package com.dku.springstudy.security.exception; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; diff --git a/src/main/java/com/dku/springstudy/jwt/exception/JwtAuthenticationEntryPoint.java b/src/main/java/com/dku/springstudy/security/exception/JwtAuthenticationEntryPoint.java similarity index 93% rename from src/main/java/com/dku/springstudy/jwt/exception/JwtAuthenticationEntryPoint.java rename to src/main/java/com/dku/springstudy/security/exception/JwtAuthenticationEntryPoint.java index bd28920..0e32682 100644 --- a/src/main/java/com/dku/springstudy/jwt/exception/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/dku/springstudy/security/exception/JwtAuthenticationEntryPoint.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.jwt.exception; +package com.dku.springstudy.security.exception; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; diff --git a/src/main/java/com/dku/springstudy/jwt/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java similarity index 84% rename from src/main/java/com/dku/springstudy/jwt/JwtAuthenticationFilter.java rename to src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java index bd84357..86c2a84 100644 --- a/src/main/java/com/dku/springstudy/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.jwt; +package com.dku.springstudy.security.jwt; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,7 +18,7 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends GenericFilterBean { - private final TokenProvider tokenProvider; + private final JwtTokenProvider jwtTokenProvider; public static final String AUTHORIZATION_HEADER = "Authorization"; @@ -26,8 +26,8 @@ public class JwtAuthenticationFilter extends GenericFilterBean { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String token = resolveToken((HttpServletRequest) request); - if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { // 토큰이 유효하다면 - Authentication authentication = tokenProvider.getAuthentication(token); + if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { // 토큰이 유효하다면 + Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); // 사용자 정보를 SecurityContext에 저장 } else { log.debug("유효한 JWT 토큰이 없습니다, uri: {}", ((HttpServletRequest) request).getRequestURI()); diff --git a/src/main/java/com/dku/springstudy/jwt/TokenProvider.java b/src/main/java/com/dku/springstudy/security/jwt/JwtTokenProvider.java similarity index 96% rename from src/main/java/com/dku/springstudy/jwt/TokenProvider.java rename to src/main/java/com/dku/springstudy/security/jwt/JwtTokenProvider.java index e21a8b2..cfaaa5a 100644 --- a/src/main/java/com/dku/springstudy/jwt/TokenProvider.java +++ b/src/main/java/com/dku/springstudy/security/jwt/JwtTokenProvider.java @@ -1,6 +1,6 @@ -package com.dku.springstudy.jwt; +package com.dku.springstudy.security.jwt; -import com.dku.springstudy.service.CustomUserDetailsService; +import com.dku.springstudy.security.CustomUserDetailsService; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @@ -19,7 +19,7 @@ @Slf4j @Component @RequiredArgsConstructor -public class TokenProvider { +public class JwtTokenProvider { private final CustomUserDetailsService customUserDetailsService; diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java index 8d34322..501f037 100644 --- a/src/main/java/com/dku/springstudy/service/UserService.java +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -1,12 +1,12 @@ package com.dku.springstudy.service; import com.dku.springstudy.domain.User; -import com.dku.springstudy.dto.user.request.LoginRequest; -import com.dku.springstudy.dto.user.response.LoginResponse; -import com.dku.springstudy.dto.user.request.SignUpRequest; +import com.dku.springstudy.dto.user.request.LoginRequestDto; +import com.dku.springstudy.dto.user.response.LoginResponseDto; +import com.dku.springstudy.dto.user.request.SignUpRequestDto; import com.dku.springstudy.exception.CustomException; import com.dku.springstudy.exception.ErrorCode; -import com.dku.springstudy.jwt.TokenProvider; +import com.dku.springstudy.security.jwt.JwtTokenProvider; import com.dku.springstudy.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; @@ -20,21 +20,21 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; - private final TokenProvider tokenProvider; + private final JwtTokenProvider jwtTokenProvider; @Transactional - public Long signUp(SignUpRequest signUpRequest) { + public Long signUp(SignUpRequestDto signUpRequestDto) { - if (userRepository.existsByEmail(signUpRequest.getEmail())) { + if (userRepository.existsByEmail(signUpRequestDto.getEmail())) { throw new CustomException(ErrorCode.USER_EMAIL_Duplication); } User user = User.builder() - .email(signUpRequest.getEmail()) - .name(signUpRequest.getName()) - .password(passwordEncoder.encode(signUpRequest.getPassword())) - .phoneNumber(signUpRequest.getPhoneNumber()) - .nickname(signUpRequest.getNickname()) + .email(signUpRequestDto.getEmail()) + .name(signUpRequestDto.getName()) + .password(passwordEncoder.encode(signUpRequestDto.getPassword())) + .phoneNumber(signUpRequestDto.getPhoneNumber()) + .nickname(signUpRequestDto.getNickname()) .build(); userRepository.save(user); @@ -42,14 +42,14 @@ public Long signUp(SignUpRequest signUpRequest) { return user.getId(); } - public LoginResponse login(LoginRequest loginRequest) { - User user = userRepository.findByEmail(loginRequest.getEmail()) + public LoginResponseDto login(LoginRequestDto loginRequestDto) { + User user = userRepository.findByEmail(loginRequestDto.getEmail()) .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); - if(passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { - String loginAccessToken = tokenProvider.createLoginAccessToken(user.getEmail(), user.getRole().name()); - String loginRefreshToken = tokenProvider.createLoginRefreshToken(user.getEmail()); - return LoginResponse.of(loginAccessToken, loginRefreshToken); + if(passwordEncoder.matches(loginRequestDto.getPassword(), user.getPassword())) { + String loginAccessToken = jwtTokenProvider.createLoginAccessToken(user.getEmail(), user.getRole().name()); + String loginRefreshToken = jwtTokenProvider.createLoginRefreshToken(user.getEmail()); + return LoginResponseDto.of(loginAccessToken, loginRefreshToken); } else { throw new CustomException(ErrorCode.USER_PASSWORD_NOT_MATCHES); } From 13c9bb0da22407ffa117d800a3525bdba5267860 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 2 Feb 2023 02:00:39 +0900 Subject: [PATCH 18/36] =?UTF-8?q?fix:=20UserService=EC=9D=98=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20Dto=EB=A5=BC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/controller/UserController.java | 11 ++++++----- .../dto/user/response/SignUpResponseDto.java | 16 ++++++++++++++++ .../com/dku/springstudy/service/UserService.java | 7 ++++--- 3 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 96293d5..2a54699 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -4,6 +4,7 @@ import com.dku.springstudy.dto.user.request.LoginRequestDto; import com.dku.springstudy.dto.user.response.LoginResponseDto; import com.dku.springstudy.dto.user.request.SignUpRequestDto; +import com.dku.springstudy.dto.user.response.SignUpResponseDto; import com.dku.springstudy.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -20,20 +21,20 @@ public class UserController { private final UserService userService; @PostMapping("/sign-up") - public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { - Long userId = userService.signUp(signUpRequestDto); + public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { + SignUpResponseDto userId = userService.signUp(signUpRequestDto); return ResponseEntity .status(HttpStatus.OK) - .body(new SuccessResponse(userId)); + .body(new SuccessResponse<>(userId)); } @PostMapping("/login") public ResponseEntity> login(@Valid @RequestBody LoginRequestDto loginRequestDto) { - LoginResponseDto response = userService.login(loginRequestDto); + LoginResponseDto tokens = userService.login(loginRequestDto); return ResponseEntity .status(HttpStatus.OK) - .body(new SuccessResponse(response)); + .body(new SuccessResponse<>(tokens)); } } diff --git a/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java b/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java new file mode 100644 index 0000000..8b7461e --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java @@ -0,0 +1,16 @@ +package com.dku.springstudy.dto.user.response; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class SignUpResponseDto { + + private Long id; + + public static SignUpResponseDto of(Long id) { + return new SignUpResponseDto(id); + } +} diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java index 501f037..d99a86b 100644 --- a/src/main/java/com/dku/springstudy/service/UserService.java +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -4,6 +4,7 @@ import com.dku.springstudy.dto.user.request.LoginRequestDto; import com.dku.springstudy.dto.user.response.LoginResponseDto; import com.dku.springstudy.dto.user.request.SignUpRequestDto; +import com.dku.springstudy.dto.user.response.SignUpResponseDto; import com.dku.springstudy.exception.CustomException; import com.dku.springstudy.exception.ErrorCode; import com.dku.springstudy.security.jwt.JwtTokenProvider; @@ -23,7 +24,7 @@ public class UserService { private final JwtTokenProvider jwtTokenProvider; @Transactional - public Long signUp(SignUpRequestDto signUpRequestDto) { + public SignUpResponseDto signUp(SignUpRequestDto signUpRequestDto) { if (userRepository.existsByEmail(signUpRequestDto.getEmail())) { throw new CustomException(ErrorCode.USER_EMAIL_Duplication); @@ -37,9 +38,9 @@ public Long signUp(SignUpRequestDto signUpRequestDto) { .nickname(signUpRequestDto.getNickname()) .build(); - userRepository.save(user); + Long id = userRepository.save(user).getId(); - return user.getId(); + return SignUpResponseDto.of(id); } public LoginResponseDto login(LoginRequestDto loginRequestDto) { From 30d1ff55256ce2f0a49e745beacfd083a01cc4f0 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 2 Feb 2023 03:21:42 +0900 Subject: [PATCH 19/36] =?UTF-8?q?docs:=20swagger=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 +++- .../dku/springstudy/config/SwaggerConfig.java | 33 +++++++++++++++++++ src/main/resources/application.yml | 4 +++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/dku/springstudy/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index 73c5cc7..6a117bf 100644 --- a/build.gradle +++ b/build.gradle @@ -29,12 +29,17 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' implementation 'org.springframework.boot:spring-boot-starter-validation' - // JWT 관련 라이브러리 추가 + // Jwt implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' + // Http implementation 'org.apache.httpcomponents:httpclient:4.5.7' + + // Swagger + implementation 'io.springfox:springfox-boot-starter:3.0.0' + implementation 'io.springfox:springfox-swagger-ui:3.0.0' } tasks.named('test') { diff --git a/src/main/java/com/dku/springstudy/config/SwaggerConfig.java b/src/main/java/com/dku/springstudy/config/SwaggerConfig.java new file mode 100644 index 0000000..aa76ea8 --- /dev/null +++ b/src/main/java/com/dku/springstudy/config/SwaggerConfig.java @@ -0,0 +1,33 @@ +package com.dku.springstudy.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; + +@Configuration +public class SwaggerConfig { + + @Bean + public Docket api() { + return new Docket(DocumentationType.OAS_30) + .useDefaultResponseMessages(false) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("com.dku.springstudy.controller")) + .paths(PathSelectors.ant("/api/**")) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("D-coding 당근마켓 클론 API") + .description("당근마켓 클론 API 명세서") + .version("v0.0.1") + .build(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 00958eb..eed43fc 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,6 +20,10 @@ spring: format_sql: true show_sql: true + mvc: + pathmatch: + matching-strategy: ant_path_matcher + logging: level: com.dku.springstudy: DEBUG From 668830a09a3dd121be9351101bb7e944292ba792 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 2 Feb 2023 03:34:31 +0900 Subject: [PATCH 20/36] =?UTF-8?q?docs:=20jwt=EB=A5=BC=20=EC=9C=84=ED=95=9C?= =?UTF-8?q?=20swagger=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/config/SecurityConfig.java | 1 + .../dku/springstudy/config/SwaggerConfig.java | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/java/com/dku/springstudy/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java index 94e555d..a631aff 100644 --- a/src/main/java/com/dku/springstudy/config/SecurityConfig.java +++ b/src/main/java/com/dku/springstudy/config/SecurityConfig.java @@ -40,6 +40,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .and() .authorizeRequests() .antMatchers("/api/sign-up", "/api/login").permitAll() + .antMatchers("/v3/api-docs/**", "/swagger*/**").permitAll() .anyRequest().authenticated() .and() .formLogin() diff --git a/src/main/java/com/dku/springstudy/config/SwaggerConfig.java b/src/main/java/com/dku/springstudy/config/SwaggerConfig.java index aa76ea8..4238a53 100644 --- a/src/main/java/com/dku/springstudy/config/SwaggerConfig.java +++ b/src/main/java/com/dku/springstudy/config/SwaggerConfig.java @@ -6,15 +6,24 @@ import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.SecurityReference; import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; +import java.util.Arrays; +import java.util.List; + @Configuration public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.OAS_30) + .securityContexts(Arrays.asList(securityContext())) + .securitySchemes(Arrays.asList(apiKey())) .useDefaultResponseMessages(false) .apiInfo(apiInfo()) .select() @@ -30,4 +39,21 @@ private ApiInfo apiInfo() { .version("v0.0.1") .build(); } + + private SecurityContext securityContext() { + return SecurityContext.builder() + .securityReferences(defaultAuth()) + .build(); + } + + private List defaultAuth() { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + return Arrays.asList(new SecurityReference("Authorization", authorizationScopes)); + } + + private ApiKey apiKey() { + return new ApiKey("Authorization", "Authorization", "header"); + } } From ad4490a21421298504e097e2ef98e23324b3a261 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 2 Feb 2023 05:31:16 +0900 Subject: [PATCH 21/36] =?UTF-8?q?docs:=20=ED=9A=8C=EC=9B=90=20API=20swagge?= =?UTF-8?q?r=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/UserController.java | 22 +++++++++++++++++++ .../dto/user/request/LoginRequestDto.java | 3 +++ .../dto/user/request/SignUpRequestDto.java | 6 +++++ .../dto/user/response/LoginResponseDto.java | 3 +++ .../dto/user/response/SignUpResponseDto.java | 2 ++ 5 files changed, 36 insertions(+) diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 2a54699..9f40310 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -6,6 +6,10 @@ import com.dku.springstudy.dto.user.request.SignUpRequestDto; import com.dku.springstudy.dto.user.response.SignUpResponseDto; import com.dku.springstudy.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -13,6 +17,7 @@ import javax.validation.Valid; +@Tag(name = "UserController", description = "회원 API") @RestController @RequestMapping("/api") @RequiredArgsConstructor @@ -20,6 +25,14 @@ public class UserController { private final UserService userService; + @Operation( + summary = "회원가입", + description = "사용자에게 회원가입에 필요한 데이터를 입력받고, 가입되지 않는 이메일이라면 회원가입을 진행한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "409", description = "이미 가입된 이메일로 회원가입을 시도하는 경우") + }) @PostMapping("/sign-up") public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { SignUpResponseDto userId = userService.signUp(signUpRequestDto); @@ -29,6 +42,15 @@ public ResponseEntity> signUp(@Valid @Request .body(new SuccessResponse<>(userId)); } + @Operation( + summary = "로그인", + description = "사용자로부터 입력받은 아이디와 비밀번호가 존재한다면 access-token과 refresh-token을 발급한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "이메일(ID)이 존재하지 않는 경우"), + @ApiResponse(responseCode = "401", description = "비밀번호가 일치하지 않는 경우") + }) @PostMapping("/login") public ResponseEntity> login(@Valid @RequestBody LoginRequestDto loginRequestDto) { LoginResponseDto tokens = userService.login(loginRequestDto); diff --git a/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java b/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java index bd684d9..35c2847 100644 --- a/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java +++ b/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto.user.request; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,10 +13,12 @@ @Getter public class LoginRequestDto { + @Schema(example = "abc123@gmail.com", description = "이메일(ID)") @Email(message = "이메일 형식에 맞지 않습니다.") @NotBlank(message = "이메일을 입력해주세요.") private String email; + @Schema(example = "abc123!", description = "비밀번호") @NotBlank(message = "비밀번호를 입력해주세요.") @Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "비밀번호는 영문 대/소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자여야 합니다.") diff --git a/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java b/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java index a48d830..2969ee8 100644 --- a/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java +++ b/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto.user.request; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -13,22 +14,27 @@ @Getter public class SignUpRequestDto { + @Schema(example = "abc123@gmail.com", description = "이메일(ID)") @Email(message = "이메일 형식에 맞지 않습니다.") @NotBlank(message = "이메일을 입력해주세요.") private String email; + @Schema(example = "abc123!", description = "비밀번호") @NotBlank(message = "비밀번호를 입력해주세요.") @Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "비밀번호는 영문 대/소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자여야 합니다.") private String password; + @Schema(example = "이수정", description = "이름") @NotBlank(message = "이름을 입력해주세요.") private String name; + @Schema(example = "010-1234-5678", description = "폰번호") @NotBlank(message = "폰번호를 입력해주세요.") @Pattern(regexp = "[0-9]{3}-+[0-9]{4}-+[0-9]{4}", message = "폰번호는 010-XXXX-XXXX 형태여야 합니다.") private String phoneNumber; + @Schema(example = "sujeong", description = "닉네임") @NotBlank(message = "닉네임을 입력해주세요.") @Size(min = 2, message = "닉네임이 너무 짧습니다.") private String nickname; diff --git a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java b/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java index 0b48d77..999bbdf 100644 --- a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java +++ b/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto.user.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,7 +9,9 @@ @Getter public class LoginResponseDto { + @Schema(description = "로그인 성공 시 발급되는 토큰") private String accessToken; + @Schema(description = "로그인 성공 시 발급되는 재발급토큰") private String refreshToken; public static LoginResponseDto of(String accessToken, String refreshToken) { diff --git a/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java b/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java index 8b7461e..ca20bfe 100644 --- a/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java +++ b/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto.user.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,6 +9,7 @@ @Getter public class SignUpResponseDto { + @Schema(example = "12", description = "회원가입이 완료된 사용자의 PK") private Long id; public static SignUpResponseDto of(Long id) { From 527be4d2d9a7bd4dfa1aaa9b29309494551dbb81 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 2 Feb 2023 05:33:46 +0900 Subject: [PATCH 22/36] =?UTF-8?q?feat:=20Point,=20Image=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dku/springstudy/domain/Image.java | 33 +++++++++++ .../com/dku/springstudy/domain/Product.java | 57 +++++++++++++++++++ .../springstudy/domain/constant/Category.java | 28 +++++++++ .../springstudy/domain/constant/Status.java | 26 +++++++++ 4 files changed, 144 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/domain/Image.java create mode 100644 src/main/java/com/dku/springstudy/domain/Product.java create mode 100644 src/main/java/com/dku/springstudy/domain/constant/Category.java create mode 100644 src/main/java/com/dku/springstudy/domain/constant/Status.java diff --git a/src/main/java/com/dku/springstudy/domain/Image.java b/src/main/java/com/dku/springstudy/domain/Image.java new file mode 100644 index 0000000..a99927d --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/Image.java @@ -0,0 +1,33 @@ +package com.dku.springstudy.domain; + +import com.dku.springstudy.domain.common.BaseTimeEntity; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class Image extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "image_id") + private Long id; + + @JoinColumn(name = "product_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private Product product; + + @Column(nullable = false) + private String url; + + @Builder + private Image(Product product, String url) { + this.product = product; + this.url = url; + } +} diff --git a/src/main/java/com/dku/springstudy/domain/Product.java b/src/main/java/com/dku/springstudy/domain/Product.java new file mode 100644 index 0000000..a54378f --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/Product.java @@ -0,0 +1,57 @@ +package com.dku.springstudy.domain; + +import com.dku.springstudy.domain.common.BaseTimeEntity; +import com.dku.springstudy.domain.constant.Category; +import com.dku.springstudy.domain.constant.Status; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class Product extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "product_id") + private Long id; + + @JoinColumn(name = "user_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private User user; + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private String content; + + @Column(nullable = false) + private Integer price; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private Category category; + + @Column(nullable = false) + private Integer likeAndComment; // 좋아요 및 댓글 숫자 + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private Status status; + + @Builder + private Product(User user, String title, String content, Integer price, Category category, Integer likeAndComment) { + this.user = user; + this.title = title; + this.content = content; + this.price = price; + this.category = category; + this.likeAndComment = likeAndComment; + this.status = status.PROGRESS; + } +} diff --git a/src/main/java/com/dku/springstudy/domain/constant/Category.java b/src/main/java/com/dku/springstudy/domain/constant/Category.java new file mode 100644 index 0000000..fff7840 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/constant/Category.java @@ -0,0 +1,28 @@ +package com.dku.springstudy.domain.constant; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum Category { + + DIGITAL("디지털기기"), LIFE_APPLIANCE("생활가전"), FURNITURE_INTERIOR("가구/인테리어"), CHILD_GOODS("유아용품"), + LIFE_FOOD("생활/가공식품"), CHILD_BOOK("유아도서"), WOMEN_DRESS("여성의류"), MEN_DRESS_ETC("남성패션/잡화"), GAME_HOBBY("게임/취미"), + BEAUTY("뷰티/미용"), ANIMAL("반려동물용품"), BOOK_TICKET_MUSIC("도서/티켓/음반"), ETC("기타중고물품"), CAR("중고차"); + + private final String name; + + // 캐싱해두므로 조회할 때마다 모든 값 순회하지 않는다. + private static final Map BY_NAME = + Stream.of(values()).collect(Collectors.toMap(Category::name, e -> e)); + + public static Category valueOfName(String name) { + return BY_NAME.get(name); + } +} diff --git a/src/main/java/com/dku/springstudy/domain/constant/Status.java b/src/main/java/com/dku/springstudy/domain/constant/Status.java new file mode 100644 index 0000000..5b407bd --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/constant/Status.java @@ -0,0 +1,26 @@ +package com.dku.springstudy.domain.constant; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum Status { + + PROGRESS("판매중"), COMPLETE("거래완료"); + + private final String name; + + // 캐싱해두므로 조할 때마다 모든 값 순회하지 않는다. + private static final Map BY_NAME = + Stream.of(values()).collect(Collectors.toMap(Status::name, e -> e)); + + public static Status valueOfName(String name) { + return BY_NAME.get(name); + } +} From e2f03dd2da3ce4c8b5730304d8150f1f3fee42ab Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 2 Feb 2023 13:58:22 +0900 Subject: [PATCH 23/36] =?UTF-8?q?chore:=20application.yml=20gitignore?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + gradlew | 0 gradlew.bat | 182 ++++++++++++++--------------- src/main/resources/application.yml | 32 ----- 4 files changed, 92 insertions(+), 123 deletions(-) mode change 100755 => 100644 gradlew delete mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore index c2065bc..ba9d2be 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ bin/ out/ !**/src/main/**/out/ !**/src/test/**/out/ +application.yml ### NetBeans ### /nbproject/private/ diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/gradlew.bat b/gradlew.bat index 53a6b23..f127cfd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,91 +1,91 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index eed43fc..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,32 +0,0 @@ -server: - port: 8080 - -spring: - application: - name: springstudy - - datasource: - url: ${DB_URL} - driver-class-name: com.mysql.cj.jdbc.Driver - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - - jpa: - database-platform: org.hibernate.dialect.MySQL5InnoDBDialect - hibernate: - ddl-auto: create - properties: - hibernate: - format_sql: true - show_sql: true - - mvc: - pathmatch: - matching-strategy: ant_path_matcher - -logging: - level: - com.dku.springstudy: DEBUG - -jwt: - secret-key: ${JWT_SECRET_KEY} \ No newline at end of file From b9bd9b51f7c9c656ecb8962bbf59f89b7eacf685 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Fri, 3 Feb 2023 23:28:00 +0900 Subject: [PATCH 24/36] =?UTF-8?q?feat:=20AWS=20S3=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../dku/springstudy/exception/ErrorCode.java | 2 + .../java/com/dku/springstudy/s3/S3Config.java | 34 ++++++++++ .../Image.java => s3/domain/File.java} | 13 ++-- .../dku/springstudy/s3/service/S3Service.java | 67 +++++++++++++++++++ 5 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/s3/S3Config.java rename src/main/java/com/dku/springstudy/{domain/Image.java => s3/domain/File.java} (65%) create mode 100644 src/main/java/com/dku/springstudy/s3/service/S3Service.java diff --git a/build.gradle b/build.gradle index 6a117bf..5c5e43c 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,9 @@ dependencies { // Swagger implementation 'io.springfox:springfox-boot-starter:3.0.0' implementation 'io.springfox:springfox-swagger-ui:3.0.0' + + // AWS S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { diff --git a/src/main/java/com/dku/springstudy/exception/ErrorCode.java b/src/main/java/com/dku/springstudy/exception/ErrorCode.java index 404c83e..e0d9025 100644 --- a/src/main/java/com/dku/springstudy/exception/ErrorCode.java +++ b/src/main/java/com/dku/springstudy/exception/ErrorCode.java @@ -12,6 +12,8 @@ public enum ErrorCode { USER_EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 이메일을 가진 회원을 찾을 수 없습니다."), USER_EMAIL_Duplication(HttpStatus.CONFLICT, "이미 가입된 이메일입니다."), USER_PASSWORD_NOT_MATCHES(HttpStatus.UNAUTHORIZED, "비밀번호가 틀렸습니다."), + FILE_EXTENSION_NOT_SUPPORT(HttpStatus.BAD_REQUEST, "지원하지 않는 파일 확장자입니다."), + FILE_UPLOAD_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다."), ; private final HttpStatus httpstatus; diff --git a/src/main/java/com/dku/springstudy/s3/S3Config.java b/src/main/java/com/dku/springstudy/s3/S3Config.java new file mode 100644 index 0000000..af348ab --- /dev/null +++ b/src/main/java/com/dku/springstudy/s3/S3Config.java @@ -0,0 +1,34 @@ +package com.dku.springstudy.s3; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3() { + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + + return AmazonS3ClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region) + .build(); + } +} diff --git a/src/main/java/com/dku/springstudy/domain/Image.java b/src/main/java/com/dku/springstudy/s3/domain/File.java similarity index 65% rename from src/main/java/com/dku/springstudy/domain/Image.java rename to src/main/java/com/dku/springstudy/s3/domain/File.java index a99927d..36037a1 100644 --- a/src/main/java/com/dku/springstudy/domain/Image.java +++ b/src/main/java/com/dku/springstudy/s3/domain/File.java @@ -1,6 +1,6 @@ -package com.dku.springstudy.domain; +package com.dku.springstudy.s3.domain; -import com.dku.springstudy.domain.common.BaseTimeEntity; +import com.dku.springstudy.domain.Product; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -11,7 +11,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity -public class Image extends BaseTimeEntity { +public class File { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -20,14 +20,19 @@ public class Image extends BaseTimeEntity { @JoinColumn(name = "product_id", nullable = false) @ManyToOne(fetch = FetchType.LAZY) +// @OnDelete(action = OnDeleteAction.CASCADE) private Product product; @Column(nullable = false) private String url; + @Column(nullable = false) + private String fileName; + @Builder - private Image(Product product, String url) { + private File(Product product, String url, String fileName) { this.product = product; this.url = url; + this.fileName = fileName; } } diff --git a/src/main/java/com/dku/springstudy/s3/service/S3Service.java b/src/main/java/com/dku/springstudy/s3/service/S3Service.java new file mode 100644 index 0000000..2415a70 --- /dev/null +++ b/src/main/java/com/dku/springstudy/s3/service/S3Service.java @@ -0,0 +1,67 @@ +package com.dku.springstudy.s3.service; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.dku.springstudy.exception.CustomException; +import com.dku.springstudy.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class S3Service { + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + private final AmazonS3 amazonS3; + + @Transactional + public List uploadFiles(List multipartFile) { + List fileNameList = new ArrayList<>(); + + multipartFile.forEach(file -> { + String fileName = createFileName(file.getOriginalFilename()); + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(file.getSize()); + objectMetadata.setContentType(file.getContentType()); + + try (InputStream inputStream = file.getInputStream()) { + amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (IOException e) { + throw new CustomException(ErrorCode.FILE_UPLOAD_FAIL); + } + fileNameList.add(fileName); + }); + return fileNameList; + } + + private String createFileName(String fileName) { + return UUID.randomUUID().toString().concat(getFileExtension(fileName)); + } + + private String getFileExtension(String fileName) { + List extensions = Arrays.asList(".jpg", ".jpeg", ".png"); + String fileExtension = fileName.substring(fileName.lastIndexOf(".")); + + if (!fileName.contains(fileExtension)) { + throw new CustomException(ErrorCode.FILE_EXTENSION_NOT_SUPPORT); + } + + return fileName.substring(fileName.lastIndexOf(".")); + } +} From acb0b96826d33469c465d8364a06b9cbe7c04e8b Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sat, 4 Feb 2023 03:14:28 +0900 Subject: [PATCH 25/36] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20API=20=EB=B0=8F=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dku/springstudy/config/SwaggerConfig.java | 2 + .../controller/ProductController.java | 54 ++++++++++++++++++ .../controller/UserController.java | 2 +- .../com/dku/springstudy/domain/Product.java | 4 +- .../dto/product/request/CreateRequestDto.java | 31 ++++++++++ .../product/response/CreateResponseDto.java | 33 +++++++++++ .../dku/springstudy/exception/ErrorCode.java | 5 ++ .../repository/ProductRepository.java | 7 +++ .../s3/repository/FileRepository.java | 7 +++ .../dku/springstudy/s3/service/S3Service.java | 24 ++++++-- .../springstudy/service/ProductService.java | 56 +++++++++++++++++++ 11 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/controller/ProductController.java create mode 100644 src/main/java/com/dku/springstudy/dto/product/request/CreateRequestDto.java create mode 100644 src/main/java/com/dku/springstudy/dto/product/response/CreateResponseDto.java create mode 100644 src/main/java/com/dku/springstudy/repository/ProductRepository.java create mode 100644 src/main/java/com/dku/springstudy/s3/repository/FileRepository.java create mode 100644 src/main/java/com/dku/springstudy/service/ProductService.java diff --git a/src/main/java/com/dku/springstudy/config/SwaggerConfig.java b/src/main/java/com/dku/springstudy/config/SwaggerConfig.java index 4238a53..ab4d435 100644 --- a/src/main/java/com/dku/springstudy/config/SwaggerConfig.java +++ b/src/main/java/com/dku/springstudy/config/SwaggerConfig.java @@ -2,6 +2,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; @@ -22,6 +23,7 @@ public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.OAS_30) + .ignoredParameterTypes(AuthenticationPrincipal.class) .securityContexts(Arrays.asList(securityContext())) .securitySchemes(Arrays.asList(apiKey())) .useDefaultResponseMessages(false) diff --git a/src/main/java/com/dku/springstudy/controller/ProductController.java b/src/main/java/com/dku/springstudy/controller/ProductController.java new file mode 100644 index 0000000..10b3d95 --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/ProductController.java @@ -0,0 +1,54 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.dto.common.SuccessResponse; +import com.dku.springstudy.dto.product.request.CreateRequestDto; +import com.dku.springstudy.dto.product.response.CreateResponseDto; +import com.dku.springstudy.security.CustomUserDetails; +import com.dku.springstudy.service.ProductService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Tag(name = "상품 API") +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class ProductController { + + private final ProductService productService; + + @Operation( + summary = "상품 등록", + description = "로그인된 사용자가 상품의 사진 / 제목 / 카테고리 / 가격 / 내용을 입력하면 상품 글을 등록한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "아이디(PK)가 존재하지 않는 경우"), + @ApiResponse(responseCode = "500", description = "파일의 업로드가 실패했거나 파일 확장자가 올바르지 않는 경우") + }) + @PostMapping("/product") + public ResponseEntity> createPost( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @Parameter(description = "data 키 값으로 CreateRequestDto의 필드들을 입력한다.") + @RequestPart("data") CreateRequestDto dto, + @Parameter(description = "file 키 값으로 이미지들을 입력한다.") + @RequestPart(value = "file", required = false) List file + ) { + + CreateResponseDto response = productService.createPost(dto, file, customUserDetails.getId()); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(response)); + } +} diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 9f40310..12ea43f 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -17,7 +17,7 @@ import javax.validation.Valid; -@Tag(name = "UserController", description = "회원 API") +@Tag(name = "사용자 API") @RestController @RequestMapping("/api") @RequiredArgsConstructor diff --git a/src/main/java/com/dku/springstudy/domain/Product.java b/src/main/java/com/dku/springstudy/domain/Product.java index a54378f..1d63789 100644 --- a/src/main/java/com/dku/springstudy/domain/Product.java +++ b/src/main/java/com/dku/springstudy/domain/Product.java @@ -45,13 +45,13 @@ public class Product extends BaseTimeEntity { private Status status; @Builder - private Product(User user, String title, String content, Integer price, Category category, Integer likeAndComment) { + private Product(User user, String title, String content, Integer price, Category category) { this.user = user; this.title = title; this.content = content; this.price = price; this.category = category; - this.likeAndComment = likeAndComment; + this.likeAndComment = 0; this.status = status.PROGRESS; } } diff --git a/src/main/java/com/dku/springstudy/dto/product/request/CreateRequestDto.java b/src/main/java/com/dku/springstudy/dto/product/request/CreateRequestDto.java new file mode 100644 index 0000000..781038f --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/product/request/CreateRequestDto.java @@ -0,0 +1,31 @@ +package com.dku.springstudy.dto.product.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class CreateRequestDto { + + @Schema(example = "옷 판매", description = "상품 글 제목") + @NotBlank + private String title; + + @Schema(example = "WOMEN_DRESS", description = "상품 카테고리") + @NotNull + private String category; + + @Schema(example = "10000", description = "상품 가격") + @Min(value = 0) + private Integer price; + + @Schema(example = "1년 사용했습니다.", description = "상품 글 내용") + @NotBlank + private String content; +} diff --git a/src/main/java/com/dku/springstudy/dto/product/response/CreateResponseDto.java b/src/main/java/com/dku/springstudy/dto/product/response/CreateResponseDto.java new file mode 100644 index 0000000..f604647 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/product/response/CreateResponseDto.java @@ -0,0 +1,33 @@ +package com.dku.springstudy.dto.product.response; + +import com.dku.springstudy.domain.constant.Category; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class CreateResponseDto { + + @Schema(example = "https://d-coding.s3.ap-northeast-2.amazonaws.com/67a29351-9339-42b0-ba46-da6584ab93cc.jpg", description = "업로드한 사진들의 url") + private List fileUrls; + + @Schema(example = "옷 판매", description = "상품 글 제목") + private String title; + + @Schema(example = "WOMEN_DRESS", description = "상품 카테고리") + private Category category; + + @Schema(example = "10000", description = "상품 가격") + private Integer price; + + @Schema(example = "1년 사용했습니다.", description = "상품 글 내용") + private String content; + + public static CreateResponseDto of(List fileUrls, String title, Category category, Integer price, String content) { + return new CreateResponseDto(fileUrls, title, category, price, content); + } +} diff --git a/src/main/java/com/dku/springstudy/exception/ErrorCode.java b/src/main/java/com/dku/springstudy/exception/ErrorCode.java index e0d9025..e562fd7 100644 --- a/src/main/java/com/dku/springstudy/exception/ErrorCode.java +++ b/src/main/java/com/dku/springstudy/exception/ErrorCode.java @@ -9,9 +9,14 @@ @AllArgsConstructor @Getter public enum ErrorCode { + + // user + USER_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 아이디(PK)를 가진 회원을 찾을 수 없습니다."), USER_EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 이메일을 가진 회원을 찾을 수 없습니다."), USER_EMAIL_Duplication(HttpStatus.CONFLICT, "이미 가입된 이메일입니다."), USER_PASSWORD_NOT_MATCHES(HttpStatus.UNAUTHORIZED, "비밀번호가 틀렸습니다."), + + // product FILE_EXTENSION_NOT_SUPPORT(HttpStatus.BAD_REQUEST, "지원하지 않는 파일 확장자입니다."), FILE_UPLOAD_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다."), ; diff --git a/src/main/java/com/dku/springstudy/repository/ProductRepository.java b/src/main/java/com/dku/springstudy/repository/ProductRepository.java new file mode 100644 index 0000000..dadc85f --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/ProductRepository.java @@ -0,0 +1,7 @@ +package com.dku.springstudy.repository; + +import com.dku.springstudy.domain.Product; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProductRepository extends JpaRepository { +} diff --git a/src/main/java/com/dku/springstudy/s3/repository/FileRepository.java b/src/main/java/com/dku/springstudy/s3/repository/FileRepository.java new file mode 100644 index 0000000..3dce164 --- /dev/null +++ b/src/main/java/com/dku/springstudy/s3/repository/FileRepository.java @@ -0,0 +1,7 @@ +package com.dku.springstudy.s3.repository; + +import com.dku.springstudy.s3.domain.File; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FileRepository extends JpaRepository { +} diff --git a/src/main/java/com/dku/springstudy/s3/service/S3Service.java b/src/main/java/com/dku/springstudy/s3/service/S3Service.java index 2415a70..eb817f4 100644 --- a/src/main/java/com/dku/springstudy/s3/service/S3Service.java +++ b/src/main/java/com/dku/springstudy/s3/service/S3Service.java @@ -4,8 +4,11 @@ import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; +import com.dku.springstudy.domain.Product; import com.dku.springstudy.exception.CustomException; import com.dku.springstudy.exception.ErrorCode; +import com.dku.springstudy.s3.domain.File; +import com.dku.springstudy.s3.repository.FileRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -28,10 +31,11 @@ public class S3Service { private String bucket; private final AmazonS3 amazonS3; + private final FileRepository fileRepository; @Transactional - public List uploadFiles(List multipartFile) { - List fileNameList = new ArrayList<>(); + public List uploadFiles(List multipartFile, Product product) { + List fileUrlList = new ArrayList<>(); multipartFile.forEach(file -> { String fileName = createFileName(file.getOriginalFilename()); @@ -45,9 +49,17 @@ public List uploadFiles(List multipartFile) { } catch (IOException e) { throw new CustomException(ErrorCode.FILE_UPLOAD_FAIL); } - fileNameList.add(fileName); + + File saveFile = File.builder() + .product(product) + .url(getUrl(fileName)) + .fileName(fileName) + .build(); + + fileRepository.save(saveFile); + fileUrlList.add(getUrl(fileName)); }); - return fileNameList; + return fileUrlList; } private String createFileName(String fileName) { @@ -64,4 +76,8 @@ private String getFileExtension(String fileName) { return fileName.substring(fileName.lastIndexOf(".")); } + + private String getUrl(String fileName) { + return amazonS3.getUrl(bucket, fileName).toString(); + } } diff --git a/src/main/java/com/dku/springstudy/service/ProductService.java b/src/main/java/com/dku/springstudy/service/ProductService.java new file mode 100644 index 0000000..48c44db --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/ProductService.java @@ -0,0 +1,56 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.Product; +import com.dku.springstudy.domain.User; +import com.dku.springstudy.domain.constant.Category; +import com.dku.springstudy.dto.product.request.CreateRequestDto; +import com.dku.springstudy.dto.product.response.CreateResponseDto; +import com.dku.springstudy.exception.CustomException; +import com.dku.springstudy.exception.ErrorCode; +import com.dku.springstudy.repository.ProductRepository; +import com.dku.springstudy.repository.UserRepository; +import com.dku.springstudy.s3.service.S3Service; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class ProductService { + + private final ProductRepository productRepository; + private final UserRepository userRepository; + private final S3Service s3Service; + + /** + * 글 등록에 필요한 데이터를 적절히 입력받아 Product 테이블에 저장, 사진들을 S3에 업로드하고 File 테이블에 저장한다. + * + * @param dto 글 등록에 필요한 정보 + * @param file 업로드할 사진들 + * @param loginMemberId 로그인한 사용자의 아이디(PK) + * @return CreateResponseDto + */ + @Transactional + public CreateResponseDto createPost(CreateRequestDto dto, List file, Long loginMemberId) { + User user = userRepository.findById(loginMemberId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_ID_NOT_FOUND)); + + Product product = Product.builder() + .user(user) + .title(dto.getTitle()) + .content(dto.getContent()) + .price(dto.getPrice()) + .category(Category.valueOf(dto.getCategory())) + .build(); + + productRepository.save(product); + + List fileUrls = s3Service.uploadFiles(file, product); // S3에 이미지 업로드 + + return CreateResponseDto.of(fileUrls, product.getTitle(), product.getCategory(), product.getPrice(), product.getContent()); + } +} From f1d227996e28e72b8844fd05507a5b19ee65a540 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 5 Feb 2023 02:18:13 +0900 Subject: [PATCH 26/36] =?UTF-8?q?docs:=20ERD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- document/ERD.png | Bin 80982 -> 105088 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/document/ERD.png b/document/ERD.png index a8542933bd64fd1e48965633920592a94638401a..57e7e8cfa875805f18cb4fdf16cf79efebb5f803 100644 GIT binary patch literal 105088 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

}RU zBsWbcC07=jq)(W9-|ebJ%;@OrCP{xRn&S584Yz7zJA^NxVqhCWKVxKm`L#tBP)$_=Yb#8Qs{TwiX{86+G0h#WcrS*bvKdTA9dO8GKQurQ$g;iDt~MpOHdzo z{DEMcz%8{!^VrWrHTH)-RbR)G`2IX(n5(O&bI^+tI4iITKWF~d-O@5z zzi`hWE-LpET0y}Q7-Z>irz0d`euCy#QKr%yTKrttocgh&V_zPd?PyOWZ<%%EM$aS5 zU{7T@ITtDXaf!)(XQEELu0&zMp``!)!2BzR{6KQ#PII!Io-*Oyl> zexZ6S+6J-3726UQ$mqkWem+F}kf^O}g!V*DPQz)_6%<`|V`r;fCY|*(cV-@lRq~b2 z+P^cIBL;#pZS6arZW`Bf$f&3&O@`x*QI^qZrc?$Wb_T=ii{WJy&Rc)uBnY}tP`vbe zQI_u20!!O1GL9R`yN@nD04kySKV?(AYgBi%&%#+x_St z(V8-o;Cjs4(-KE6;42SG+vLHB;Zo$qc}>j|&G;?ggBFPHEQ$5&jz&tH23EOLXfjPP zn)+&wbiI^V$1l7c+|NuKN2^ZTlFve`M%yWxyBoX(jUsNY_|nJ=hKXZ!%C96OS7|VV}lK~#L|vdlEF4z zjudNzs9v-d+SJKgo*NmT{Hg94O#F(+WZRl>3 z_+*SzAqWG4^}3NlYx_I3?bxA&r<3&=rUV}`Potpw)5W?nG&fHTz$NWqZEQvUQ8a(c z2k3*#(LKHs#sA@dX#UR_b20l3^ihYaa&7)?798KRIRoLGsc;8#E*P}_3?rt#eI<}X z^g2pyAC;&0pVWEHJt>(6+ua?#;o+Ib-d=WUjJ#D8kl(6IfX(AgZR<$tCDOP2>dk$d z%E%SyW0m!lNXO^cW$he{LUn4G%1BiPB93ee`$ zP_l{Qk`q&ovPIPZ3{d3qIrn|VMBLEkg*$cdgD~iJjj6}#v?ezxUb_qZh^xE?p9&1)+_*b?NsN~%H%oCT)51e~@FqoHj;2JV+ zC(f1@tYNem<=q@bvrN<;nyiqyFWDZU7*VmQZNy{=S33)PcL69vJe<<+P#7 zQVB478MD4PT4Do_k|1)30+ss~=MAHx>)6k;fA~wWbFuS)Sl>0Ry<4af3TFr0R3rfF zyE!{4=v1=hN>X^wpAJfT_r`Kmr5Kc%1<$mD>G&LCW(s z>K>KQM8K$!g7A^*4SA)is=C!l=Gg$cJdagp%O}R{)eFN!fdgkO&VI)G`jQBPn^$OO zBsPS)U?^L-!-QfeC1e{IffTHgfh6QJ%PIqqgi!DcKEB~Pp*LKAIoJPdt{>;o^dlK7 z9jjYr>g5nFJ@**E_V-zIT142KO;*s!ZwXItK9Vw)hY*R5y{z?I={?oe*x?3jH3y5Z z_Tt?-D$;8K7&AHL#c#^2_~v5v$G|D4J`|JHX_0SwienMhRIMqUZ?}v)QR_^@G4^Inw~5( z?~QmOTQCGrdE>A86adPn==_K$!pH#>Wx5X*`S0qhcx;|_Qo1DFXaOC-5B^yCLNRh4 z4DO=uB6{>E@`fPo5USQ|4*mLxfNm;9E`-f49_ygMCTqNiXYG-y0B6F17-HL$zSR*I z^buGI2nS{P%OFsr;yg|jC{L8TdP~i3;u=3P*0Hp!IM!2pRF_pls?+?HLZCj;<2?N> z69-4c%1YeDfkANTLe+G{aHhY5fdwFhb&0#jU#X6NqK0;Zb$bBpA)2PvbmM?9c9fsd z@y>mmqfru70AYw zCZu(yijZf|G{>K7=D3$vR%%}wiJM=5nHnOpzUJ@Sj5uXmGH@Ou<14eqvshqGR2cV2 z40(RXL>E}S;LeCN^++_9lJpQPytMX*9tgJw4X!t|NWV@j#wp%a1Vdf|K^uGNj!)m^+T1s}2J_X!D0oIZxL&Le+x zt+_Lj0ZEE>Q5d1oKePbt?do@GncD6Xn+Pt5d6SS2$U0}Tz_zVgMRpS*&=B|s&i1WD zD2xfrnRH=wN-xP(?R@{To1j4c;a0W2Z1UQve-;!DD+N<(kdE1LkSq`SEQRvacWqVF zq;LiJn6&yvTkcC)^Eq$LjZE&tQg60)mt)d0jGO$T%O9YW5)-9fN^gORkj+wp*iaj!trO^I7vn2et~2hif5Ka$czM6nmG-6GOHlll%8h^-mYtgz zR>%7#om4ETms{qG@qLDvN*5jE(5`SvDpG^DW;S!W#iH)sK$^JxM7>y_jgVe03=s5M z@e|p|xk?+I7nK#0ye}zxmIvX|}wy8*UYFtricwW@{q(|o=!-@XKW zx#wr`<@=XU9Vp>Jod|=Z+s}g?X%c9Y5hO1w#!fmm34F=57LNtSTvL74By_?i*D$!q ztsag)H(a#8Yi$yg;ej>vlYn_m_A}qq^BbbFb+LLbSE7PTs@aX$5k-uOfD1g;<~XJ3~=1VGykv>3s;I zu%f%)+S%p=Qc8Wbz^L#RcImW~K zy)fN(M}zb8wZxM}a^s3XTlbYG1Dsas;@{G|ikSRcSYb*nFHM0#U4$HN@sxale6uIY zpWA$(TtD4a$btu!yx~sgPR>PUL^3Ei+Rs~7MO#4|7d{)DYX5Bn(}V|&50n+DYIfoY zR-VxQE``5RgcgLp!X9S8bc;rd)`^;^do&k&Og&X45NGx3>~? zHst9G8dVpjA8a3w+Y2oFC2_%K-@Xy=5j~l49L`Zp&D@R7-a38++m4^2=Wy@$C_aeV z5b7IW>W(DjT2|3pJOoB@`Vj{goOYKT%`%3Y6_N`4aEaGh&c4$9M{k7cCN9>vP^k6R zjr%F?V~!garla;O(29SBLP#?grdet6oe{xWACp=;T&qca<@T(j1b&h;Cmdlh0b%#H zV`T3Q%h90r-l`wU2JwX};rH{EJ6FXp{2Zq5N*TN7(B%-MBO5IiWbB#nH^whV{uymG zLg=jdAp0@I%4N}D?BzAzvvZ2^%qcWrT7X7(9CMMM?aZtCT5gQwj{SNs zE3>V2N5ru}xR+%g5us$ydsFPFfu zSWKzn;hy6AIu)p6Hr9>zz(t?R0b(`PJl6P+)O8n}kqu=F@p_6h77Nob%Y=pw2si9x z5b4akn8qS&O5W2Nx2&}P+#~;4oBPf}dthNgpO>v|uJ5#orLF6_DrAOT%JB(|w$x>J zj?!EFCk{QWpBk39iMZh+zlogqBf9))Rz=?}n+tJg-T@yHzwaIV2}i-ldlXB2Q>`^! zcl^R6)Oo!m#vo@uTW1UjL&Z%tCE_eF5Q}7oKDRy7S+NJ2FWzR?bOd<~-7#t=LDWwJ zhryzDe|3kI>D9&Qeh*7=TUo~JMsuPvuUbcyPLKjA{Ft2SeS=Ogw2Z2Ty75g!v_SW> z#p&(Ko)oQ6H@p1VqD9B42P3`j=T~_|)%~w+KkMO98$q zng}*AdpQ;NllxSi?!!$!w+mO*u&jdLCGD2K(JLBHZ4d>!os&p-3nH^ut@5Qnh0>5C zQD;ha?WTnI96~{1> zUB%QRCqRS}R4D$N?jN-Th142zQ(Oq)8v1To6|26d338(;8i?e)JLw#N!jhY?4Z&(-YddZPgiHKIpWp5bP~w zTAFuLw9K!@{wZ2MUeqxswjtf=lTr;zYv?a51c`xEbAjAgv1;Ti)4}%Ep@fz|xbh4(24f~ftwNBzb+^6eK|`jsDD>4Rs1Ye0P`KR+35kmGdT z`t!<3mD|F@qHUb1zku@mkv+L%4zVf&c~?8eg~FE!(^&U#Or#7uOh4ZMuS3HJQK56e z-B@cyjW*Ca?u9tgE%>9VYu9j5k|i`?>!4=%{y9F+Hj7+p_Ac|N#)k%lFR@OawG)wf zTqGMEnk0m<9*S~t<5wPD6c^$>cmX%bWBS$Ulkxh7E!X|MC$bod%3xeOLx1BuZcWD(?lNX*VM3YQg-H(&7 zM<&Tz38XjgDL-)(ef*(tB33AK7>IKp!Tr|MS)wZ;@s*QAO5&o_kza zUAvLnI8#345Bw)>6n=e;_m;{M4_v+T`IcxSnkhZOqJQ3!+>twdTv>GD_w-u&i*LPu zM}||!eg9Qxs()>_rK9$QP?1k5gu=^zt0*_TGu2xn#cg>xsQdHpkGdo6RR)4#Ib~8a zZ4ln77HUP=7}l@jOJ#vjQHUQG#CMnzP0wTT=pJca;v9{wqcQ!%`mkC)405itg1e;+ zwDI9@f;VYfXf^Cr)zq}&XZSE)fMbxqJP_oX#M|k@vwPCh9ZqZ5;|sdF`9|c4SB4@# zMT;a^yNbp-?L-znxPA%Z{mi%EYb{BEe;l@B>pQi5dB=f#20ygBU$-jkbPaEHV*(#f zrruD_m1>xxgvKn0ob$JoQ;2@(=8y=<+KOg^Coa7!1?K{1Tj1t1qAF`Ij$+X(+U3s# zgP3F}#i<4tA%ge7S1Lz=?N@3JfWtR<0z$%iy=_GDe_ypLJ0ZmPjOA5$678%^?@};Z zVpd+d@|~k~mQ8(EiK5zF1xssNnMA`M@vpH+W02?OQ#6s4eZ zS64V)JIpflj&*FEnJ2$gJrLIRL@}uhQyG|Z>?v=c4MV%aXw@}pPs5qak~fBYZu7FE z63*ITuReFhC3#AdavH?zbijC#^mZ|QAVi~Bv4VeOV>I8i|tuG8Eg+*!zMLXt8uMJL&b^2 zABo?}pkzz)g{k_BU!{8oVQl}l*A4CDK5bP3@;7(#|B%1!5;M;`)ssl(IX;D&9rL;x zZLJYwbmR^joF+nOFI=|~cub;)~E#MEiKhb!PsveZP)AHe^VWU z81(P3iZ>sX)BJOXLA>IKBMuQ3Q7xy~5guOc@(wDe@|peZ-X+CB;V0vBE;;2@g5uV; zHufHm2ob@Q)lOB=uBk{xk;#Y`#+@O-v6(~3?Mf-P2_E=%$?GOA-00heiwzo)r}!Cs zz_*^GkW3akyV8#mgaSmf+-*_i*)&uF@4m~W2=TMS`pH(!V(M;$Zw)2@YcO#SCF79^ z;a?%K(%)Jv?X?O1Wy#>ovEY8XzdVK$#bY(5*&@`nAQ;~?8f5&it>-0o7Qhb^liN7- zMH6gro@1ZpA2lJSK8C6D2RqCA*cRO{oGdKWL@!Fo&|0)G7*wMzj{oIpEEK0(DoChTP+2Ps-1$k_ z*cQh(!@Q+VUqx&$MTq4Ma!dw%e$JHn#?`5 zy-}YJmNyxKB6sBl^yX$twP4=oT>QWN6?-|%by<0ED4*QwqZ^xJ5AN^hLTpeA4EMkE zP)v9xP2=iU+lEtpl?{(cswmPZY3wY{5 zjd$7Mb0zAUIJmrEy70;)BL=c^Cw^j3_WSj;hEepx?r8 zb=8-Rj&rm$gDyJVYYy77^_0&T!7PoTZBj$#&3}2Ofthavi5KgvNN_8PpRyq4d~xx@ zj!s3lEFSC&OZqA-`@@W7#rh)YDp87`>{E_;v4t(9#x239Dxc__`)xItLN(>3gDYnT znms;bRTeLz;2qePc^=UFj3aUcLP6wy)VBIjie_{**1BEp{wu`x%MY7C9Y?;)ud6Zn zjckth)QxSzQSz@O#RgtyZ0x0it)^#cf%}!^X>Uv7(vIYKHxp>8Dmv$-vU0J*d%E#` zG0WXD_sM6ws8}aL6F2G+?{6LlO9+R<0&EY!RQ^nR@xCvPQ@!H=43u3=AK&U?Y8__^ z^SBRZ_Er?!E=Qx%u5D+mt=ig)N z|L!Jz&XrT247swmIOZ>suE5NmrVg>dyQY~t^1PR&QZC>2g|8Wl(}%L-%@~^oQ?Uc} zR`b(Y$EQSXAFdF1LG~6a4Kb0;-cE!HqF$c=3?e_gy}F&QxBk|B;{#f)?}wNNBhXjQ zukLL*dIKm__u)VfKwzZ}lkyokS`u-IXivJzf|?5H@l+VY%) zC^<2=e^eIc+bcvrkgD*p$P0qPlAka%6=T&E;|4%s?_2)1n}fgZGVGXSoYe(vK02-a z!_W)+|F`T?`SLfPZ5yYN2gD8ej+X(*MWSW2RdmRyZH#D{(HeJkHe8_ zB7KEhI`mdUyH2L`*p@AyV?d+$lFo&%oZ9*XoQtQ}9Q8vBCR7PqKEwXC>q04243F1& z)&^mg^&+cLjOky6Cr0Mh$qN*|vYQzo)t~k}SV(MJc@v5!{XBur8FSh4!BZF)oj~p8{$!MARmsBz$K_b&=HX$&bUkeaSd^D^k$odp*?_v91%BS&Uz%Z7OmUJ+w-ln~xHQNi#MVjS;eRy2->4f`?%gQb zl|}abmtTgiDQ8YV|GJxKsqq%|7T0B1wDd*1e&nW8xMh-_eKNIGI?Dp?i&TKEmNkiH z`u>n16|Y6B_wbUglIK{hYeRg=*Pc#h}XnbnKSK zbiHgve1FrrT)?3i)#^vZ>b|+L^m8>O#*gY+)bg!u<&?-i=!)3tI1n4KGn-YD^-ABv zGxA~|7A<{#bnsDEM4>oVlwjX}MwdAGyp@w0p!CKIK=;}untAazl)m1}3n5sfnlPzC<2hHX;*Jd%1*8A?dUKQhNsRq8xFdl@_Y^@V!=}=VI#IHMkI=Q?6wAVEr z6l%Y*9Yuoio=>EBR9FSJxsBlD3>xlHYqcNWb)-C8xije+OxHVM`OCUN;bip#D4m+T zDA$O|(H6|NYEUp!)_rjDoXU0vx*W@{--$NHLEu9n4bL)}%=<4RN@-!qV_v*%RZRK) zCo&9(?Z-d+$0fMH_x%3Nq$$%5m+a!qJ$-QO&ea$%f#=UnfyUpPZ-w7Mv2`;~ltLIi zwmowA@~Hxvb7GbpIT7ybdoS)>|7U{cy45)e3+zp9`HZihc52_J**OB-k$dDNsb!+St+Z z?`S)@)21<>=p_B9KXxglz1X36t#Rv~iDYf6!B+9@^Lpr9J^$65kr(#^xUXKLSA%gb zVQ~KpE7cR(t@B6xvp-tTCvVinkb?+M7WznK1~LtS1;JlIyQ&1j@yIdZ=;8h?g$ke2+fz()GoMX69IpMue9;{xWG0b~wL=%e_b6?kfC(nAmBnLMWixFSjVD%l3d$xn z6pUu9a4czaj=J=t_uG>#UVV0@a5aU=RDUhVLmNQL{kf(-WXHvm=`hNB*@~&`EE+{Yv0wH| z`5|%f5!_wC1QX{(`9f~-k5dQ$IL2KTU3UIv{%@r$DDb;oDX!c3@kjVD(n$|OJ8AMN zfK-DY4N}^%?Nope`8bY0<=ZDAs%d^xETNKziW|}yQ!J|t;mV}YxKjYbkfh)m_2P4P zSBmjPglf7Yn3{^2GXZyeo%4Qss$%SHJdvfk$z!+a28cKLV8ynjnx*iA2;{idf#h$E z^81{aos7JUn{?f6A$RW=tC%_EDKh}m5qz@`XK6cr08yG0I{$uyR8wi{jG(+$(6AAZ{$j`zy~b&3Y5&9%*Nz3^!Vk29L{OR zW^RcKYK;DU3EFWZA=YY-`#h6qsXQvk)yj*vHk1J>Gg# zrdPNb*gu`rWAo0LYSv35o~v)^vk2`uCvVx(9g}Bm_g|*iikJ^hw2|Nn?TN`Ui`%BR zVc+mnR?9Pl-?rUegcR~wo_A%!gSJ>uB~!&8nXnq zIBOVK-APkuDc3CleY@t4cY(wlA?79Y(`bV!=*g@s0;FXFMFt!TP#hqw8wEPv6n98ky4Pck~8HV2DCzlrs1)H)Kto#W&GzmW!z zvbI5O|$8_F}F>0t#zGdbobquQsg0N0&_CYGmH2VG$(5Muxd$u9_LR0<20gxo`QgU@{XFjOCpC|Zt*^p?S~Y^E&f;wp?}nb2+RoNqzO<_)h1b$1n|(1i zh5^E@kT9q~w|}WJkf$mLnj=rc8DavQwhjRl*u%gP4(f4f9t!}LViJ(QYHn|WkQ&xRS3MvNj?lCiho*y z!WJ#?Rv(>{y&%(?M3QR3*Cg>AKgtW?h8L2*NvCWJwHm^}J^WhA%7?KyB>@H5W)^CZz3FHe-W<@HOMZ*XSVTVK0!LBdQJU}Wz$qloIsW|g(;3hg1t08l@ z6x$29jXNsVO*}T^wjp9kTqgZw{bU)yts{>J-rNFqozg%)LF00t0waoJW#R79V4gWu ztMsz1gVQsUO{|05#O8$JH|O>5nXDDOp5|-Tos-2Mf{}g(A81>HAf<7_j#EV7STCWMIY1ua6YIb7!ja+~$j{x>dyD z@)oouh*WF0s!afx^}8Fn7m@s9{KLyv8u>%QWhDe+FXE?DchV~N-B{fTNKgV|SX@*Wi=-bvqx%<^iZ zZISQjzN;Fb91Ec9k3D>Avyk6o_?_JNjU1BMaR@i~A>z~?5L3^?ZxjWpB8^K4R_5mi z`*nF`o@j{o9F69_Brfbm0pJ|?$oAh5wwKFzodW?-k7Y-I-!7=>Cdx8{{)mHFGx5a> zreuE88lMrP)+2m8t4%X$`i(_v$7*kKigzBVpVRa3K_`B^IiD4K>Qdp7=WUs~sXN-G zIu|y8KeKX29%R$x(;&Px;I=F{l>;`(|4-MHl-Y{eg)(|E=o z6Cm7wGvI=jNGMtmyc!M9%*MN`hIZxeNZdAl2V>oM$8M{RkXgyj32ZZnfWRd`QF8n0}C%v zyimVEn_e!CCXk4&wBtcDO!vVdX~5ktqJ=o>{hcSbLS;Qxf!@D>8yl}3= z_CG$F?&3k=0C>uM!dFp(qac?q3S8QJbdyV;P#3ZM=@@fSPqbBY(?IbbT`d5=x2S|c z_4wpsk6G(_p=>`=%|#lFYI$X~r6k$Q28YRA9nPpYYO0SqgLHrl@euK#j7Yr1wHm3h1GB!It zd+IvZ6x!DoKKxNi-D3-=3{+fzQk0A-g|)kI`Q(#!GW$CfBrxIRVoh#6_M+9~nI`ik z+Sl0h5$}2-m7gS0mn{Z~fBWwa{{AG1Aphs*FLmB$=CyAF24BUE#71K?mX5uj5reSn zeVgxXiau5+p=>!hx9xYhz=}RaRMd`qN^F>WIn~>3VM*Gk?+18ajtFapSTto%uWof# zV16?JQr8;xh~mJT9z1g&tBWqa#fjSN9ccepjrfZ#oo19e1l)~IWu-B_Y}Osqg=~1MifJ;l4EB;`o(&XVb?c~(}15cpy!;e2up-L)3q=^5av_tvzzw4m#d{uVz$ zk}P3vl9cewcReakT8dCAIh-h;G&~ojBK(_#G)B8Te%e{H@nPj8gCYSct}!$ zY6A)JK1fVu^jdCV2xRd-jb9i{xjI@nDb{C#olLtBd3$bB2N0$rw-+u28_QgKW4+xl zKQkJfK7_LG?O%N$GL(qk|K)L2qvu=pK69<6Dxs?y zq#)??62IrrwG@Lo^PaF&(Ws?2ull#8Yn+=8)Nrf8{e(W1xJl(53NH3MYAMS1q}Eo_ z?>*OFJ2FI4W$fNqzrT zrhWHpGL=!}{aVQa2GwQC@WHuY$ThTYy}Y~~IY#{cP_;-#B97iJsGH3vtgR*c7W#VN z%lMnVBQpM&x^-Y2&ttPg6-8kl%wjPvsTTpaq5$P2yv(0Gz(ijqM`(qbKV|f(^J+>6 zb^jYv;J*=hl(KW5D02P z{fn_>QD(1mMRp>CqZJ6T!NuS;E9^H+zI~TcRBNX8fTMlzx_NRzZK^T$IUUj_` z**@rb?H8B~KO9)dXgsjS?9P`NCxIL5B~*Z(CLfzRMUG2+tCvw*0=yNJyL1CN_BoFc%MxUKEP`Q`%t;shjy?v0$w=tEEyI^u#N0 za#d0{V{7dU#V1>x@Dwv{>{X9+ zN(QqUQSZPnc@oV*;C9Q~F8LF9gdeQEUqlv8!%C3zCtDnIHAj@~tBre~7dH9_AAlQq zu+RuQ%yKA)x^)7(#ldfCcGw0RlnHhSzt+1Lg%>|)JnnX^hNSfvf2?yNuVc+&}UYIZP{B!BsD2QYC-0SOgT@Sy}s0VM%qe-z)Qm zEJ;!Y3|<3Kj*V%Y$Y;F_s!pa)s`420!2tf1`)@hqWuLC%R< zSpY1Y%qX@d)Q#n7_9pSS$8){0?0O00$PhQ#vjUsc-8}aBy1|)o?o1s*&Gs)^cY)_!9 zVbnrUHo&Kv=dFFCIKl=q@Hq{V)aws+!(aH!Z%<`WWHQ%Bp2%q9G?G#?EK0`6UsFH$ z^V8x)7w1-1vNh?W=Ps#(Ij@R0H!JEjhUNu#k4l`tAr_?R!#T^xKFoDeW3QX(2HNI{ zX%!D?p1hn+S$CK_2Z=+kh<$RqGEL`Z>{@hw+b=~h4GVJ+v;=G>a1IYjAc$0qbWK3( zvuv@ApiSj>G-IZ%YKZ|zef~G-E#5X^MrEk&gqpo zG#JVZF}p4!cq@%00jIpfwh$_~%gzkbcr`PoKrIEoLna^ibxAUmy}PCa@_uU-?DQtq zLX@m}v~rX_)XpvUTg-MuL-&55zfif=O|i=1bZcY;rL;7a|4xva2lNz4cDzWue)x(Cw#`3{(q#h9zq=J?&CB z*HUYY2`jlVtg|WclTOlZ)vxplO>O+J{pn6F951-29GBz88g?b_4&YRd*zI}{omt+3 zD0em?08TlH0Kl$X3`3G>48vgrKQl;aWKn#7Q7OJ@@u)xB$dB|U%oj-cb?uW+ zrg2LRzY5pG_#lK*FaT(} zOh1@U_KfF5An_;bc51C3E+zAkkn7(3*KI8?A{xq)Uv=)0cLTnRHZRHa~2Ek#-7~--? zr4unI{7^i;KS%o4$gt$R$hVLcmvE*@n}cF{DQH;WZsb3RwwwQ#7)VpK8GDx~gNP|p zPB=gfa>N=k#Dfn1DX6`D8sZ9iCI|Xay|*9`$$$nyKSSalA{AP5Mm`ICZ4y9-uZskF zR{B7j-w72N3tn0vauq%ET(8gJ1~2L~Yh68EP~c*o?Fx;c4`cis88ndmoFL*H6C*4G{y5H#Gj-6o?zc~ ze(q)+9F_n}-1tFdk$Y9azIH#f^J*~^?cfTg8H4a>h&hq@>q#6o#}}fBr8s+&o;`hw ze&}CSKO({Qg(0;86`ZWb)~n26`A?~^PLw16=y9=dOE>k-dv2-8*#np&#)zXcf9X8p42NeUW^tnn?zU8D)bCUspjp zkh6{NI3YSz@7pEm)TZhPm}TS3AEN*CG#rE9F2=3*oaAn=JM+KP1ANr9ZgOVbjsIB= zpuyHdiQ$t0(ei5#kGf}2ep!5$x9AiynsIpU*6dj>*$Q)t!{&V|!tpG->p=gv333|a z!o~ACI4k95+uHnP+kR+1@h66>RNWK>Xz2G~4U|OEJ~K@&Y7Q?B3$SFBi1Qv%LHChG zx2M(-m9H7p(j>t&maKT6-P+d-07LK;=OgsFeBd!YDTIckm&2hNZo4Al+c`^FyA3LqL8uAZ4{6a}S zd9F`8_a3`K4^Rt~F($CTXNS~-Yjs4e^Zn6e+}@}l9B1q+-+^(D>SpMuTnplKFN>LLp=$?LDTTqC2`dG&kVlXqe5at zoPnWo>ui1h`sg0)+=_RQ=nniqb=p|#CV_v?7&aYjjQ1j(kP z=h-2IyyDUv=m~id^ur=J@u&rL?~mS+66!d9{mNZeVE@ZSO#n(Dr|j>ROh1D5#mKKE zVjg|aLkfACa#C*|@#*Ol#j1G2px1Gk3MXt@2<9`i4^yu{DmvJ`4a@Qj!W=Bgd&;Nw zM!mKTsnG1H(?#hvv5D#Xj>DKbLD?B3{}m7U<_^VuiVbS(9rHGkXQoF}Y{s$%#mfzO z{}NPOK~Pzalk+TqV8WUVar68G0zX$7RIo8F(Yd z6zsAPb&!MIqpc}6`10D`?d&OXz?}^TcA!tKV~$?X{`e}U^5WkM$IC96DD zXggh~8g^5=RIB%P~`f`>7F4(H>3|%=6qCYB;k>pT>gR5pbcZSms<;$x(<7B^fMx zCd}MNH>**k8$Zk?zx26KJ`_tsxK=jtP|0iqfU|{sZu?77cPMi9q@e$+xATr_YU|p4 z2t=d_Qlx_oq$8qIq=_iKND-upQ4uMjN&-O>6cv$!tMnoW z-WAky@Z9&@@!m1M@$p}PWUoE<&R)Oy%xBFN_GQ}gmGKL$oWn;SMfyoxWLj{pEXWEu zpAkE937WlqWaRQ0G0>hs3V3s%x8QHmi*YN`K_#53eC;=PeRltxr@%1LpDle)&rBF7 zO7*s14>05{HkV?B)!p9-dH%)W{%ZLH+gZCmnoGS~MB2@tl(>VL;UITa-rluYTr(ZA zv)%VBz(qQ8!C$9?>{>2cjk&AG;BAYxGV9`?%@-t6IKxnfxc#Re6JNrE^*JW7#Nn3;$6Y9gE90?=8v4%$B_u*2;(8IXC&i9u9~>G|x)UPO#=~&PJjI@npSU^} zNEIxt`tGFNi1JflJeqEF(8Lq;PTwEv{kO?meCb%Kvaap2W=V(N_d$@&Y(Tj|{p z&`yW?#8x`5*N#heWRWdMjMIK_)c+kE!}o*Zbvqw{VYPtG<2SHr3aPUZmrYX@dc>cf z%{y;KCos#xF&lTcU(%x4%gX(#t(-e)rZDB)m$LxsMDbBc-mQ-9 z&{6l4>AI_1Y7s^B&5{RpBt7ik-=DyIDkAq;QDDaOpkJw)ad(S)B9czI+L%+XbOyoN z6?YG|t;fx{3`yx*7`ns`Fn?Uy;*$#K{tyR5fwg>F9yo_yca|LZLjGC3@^5lL`JgmZ z03Pmzn*bJatM@tyLCU(x$M{~W781`}b{F9dB2V*z{b*;#hLLEz>y{#hRr-?*nc>An z*~NfQV3o80fXHhq6=HxNp_4)k{Uzg3j*R;B2#6SL@fSf>#&68^8r&-ZKH!XUyerau z>}cSm@EvJN113n@~#w_CUG9b}0}qbJP;;lIrV_eV`A)eO|Gv*2Id ze%a$e5eJPB+zcFQ%WSFr`dqx)L~gb9QfM%THykZoS)Yyb;7~{r>r|(`9^$i*Y0|U2 zr2wFtB9gq;pp5Hb?VJ> z#|u38gn5;HA5lPhXpJthw%c{@7zbxsTYwCbJ>DlF1Oj0;x%g#w^I0 z;K-F1O!CPr{3`jJ!MVXgGrFg*=f^GyE-R#In=v+}a;k2P!jQ(<_R4wby7nJ*ir(`V z!FePc%M&Vz3Vlh|!?H~lj%yHJ0VE{CG|ga z&TL#OwLo%0oIlswha07`uvAsg1PFvZQ(fhvAs~gcXtO<o_QZkzE!NbnkzW^J z_E$m~aMvkGt=<;dZ6Ua;%THU`smQyT+TGN2liZB^YMetEi3KW|Btw^lDTy?*mDZ`* z(39mV;JcUmJm(FG$(hTsi16eFuc*zAymz}>%w<}kCJlw0Q(FXhP*_%@gi;%*9$MAi z4L?;Da!3CZ|Hn?}+BXyLl@l_=u6b~rfth%$&2;&&jYer|#q{3pKPN8~(;Urk{AK3j zD4gCLL?O-f)U^_~*Hy}YsN^{Zl6Ase8pU{q{htDL{Y$+Ye56s9ZVQ9hN{4P5Sv$2e zNEgl1RA=>dq6jqW#=b8vQfhQQ`Cd9L*^S3nR9f}TY+n6*$CtLc|Nh<$5(tV8GiMRe zbwK1@c(oOKp7faUYWcUv%x0ZLOD*rLw$Bgw)gyT$tY$-GJ#5?D3NYrI>aCuDFS8M! zP-0E8reP~_L_gse^C#52-OTF_=QN445nbsel@7E3Hq9O_RQBMz!3}r%CJa4FKrF%M zoJ<=oRSGpD-^HcbO`=2d!%@Y>Yq07XkA9x(pHK_Jj~2S-I7{ozu(vB8sIREdRzclA zmp&16d=X}U_&Ym z^sU*D>smJf7uy8OLoEWJBWT*jthglrUJ`2JYi%L6Xxuihf0mu3rYw=L`zb;7%L#E< z_|jI2@a6%w;cD*+sU{22yVrbdlw*s-NOI=yC$6Rgdv3`HSfX#h@#ol*(c84I4ijNXDs~iZ z{5F@hf@;5jk2$+t@o<=gdBlB~xTU6z?$K zZoZxE^*T?-Xss$Q%4jv9lcA!?yJ_#zOjB2*wqBg<^R9}l@R@KERUgpGWpVK<+B@qx zKlO%woQKv->@SHk-g~`QePo^)ZOrFk<>2xq@|oFe*oDoB9a>tWc7?e>zXBK%`?m9# z^I7Z5kWYYC%{%tf>gg!gRn0f{yY1pTf}cyaQn}%6;cs6Y%hz_X?G9bC4H88f&$6w} zh8V(G^SLiTc9b^wl?KCOI0y4j%^Vp{!lCQPAKN)LJz@}V@>@}I-uOIgg=e2t1bB`)}d!B@mtPLLmK~Y3d5m5W^|Yn$%^im=J4%%%_B-pC#_zPrM!%A)C#6ccpY=W!%^s% z6#gYf_KA6xW+yk3EPe}D1xU0TYHxq7-EJ+G|9!5;5$+8gAZ^CVU!_Dzfc`pFNU+@w zbx62@CFmNZKb)i*q{FB(OUSjT&_E!_lo+pARU(~*WHu5nJ#BbUV&AL3?oM(vftB^+ z1(7#o`veC2hj<<8%fIP|%Eb1EHRaFwOYOPb;cT;cHuG%yN!rBE`Q#*%VgQgNE zPRkncrWs;L6iHyeLGw5j5+c)VH3Lb{AitF&`jb{Eoyai;7?FG}D~M%XO<|P0#svsi za8avt3vzmgVVPpVxsH*6a`%kFN}^(5M;wV(w=VxjS}i{c$@_XHs6;l~Y<1|0?nHAL z^F#(OP4f3v9?&YO@%9A-$dmkklacxZMxRC8yf*_%&yAU(KzC~h1etF^&+oVFK(19E zPD<%|tBJc6^0V*A5}sH5;5b&w^>NHpIgl|&Ks%btw3nQL;X+U?>Ph-Dp7tQQWwiq5 z(K@QB1Cjcfa;3I-lNR|6bN1sWJmDToP2F2k-o`0ADqff!&?{VA{&i9~XnQZT7#V)W zNVbtv9^mORD6eXs&P5J8&zUTM`n5sS4 zf$E_L^Ri}I#dk8q`_3qKLq*Rya_C%1ygjkwT_7_073AGv`}6LjrJVS~;bo>^m`D0^J!gSwT4ZCBr%$895gQx`|Lqcb9Dnuf#H^43*45r0JxnK ziz5J6Q{?g$UJmrbu@k(48=*JjM)`CE9UrYP52!Ui4_QbkRa6rf_=8 z_rY|YkYx!*(sksi&%yTv7DMws%LI;N=e(u5tmRwvckC=$Pw)io;CZS3EpAoLTm{T= zZ};l?ozzo@5vYs+Q(9dGFIXL<+TbHiQ!aEEcv|B9d2?!KsiTY(pKyMT#4O0+Jg668 zg;}^#h~ryzAT3q+EOh;;yFguCz`n2+78b_XPCjmPc!mO}ef6e(0trbUy1n`@kd)~9 zV&Ria&9V@N_T+@zs0zKUFV*3>1Ifb4{##61N9=9T{*^7Z9pMy(ROk*~@#;br;pkSu z8K(Yxrks4L`AF3lW?};3WkKtA38jZzYYx;+`qnX{hJ<5iA8up5&%Jv#VV_X55d&M0 z0qK=)jrk94R~*QhlPN{`0uo1!CohCCrq^AXye-Nj@0h@EU6SnqkRyD z88qH)%%&#?b&b}AMeusWisraNn-(2kN!#U3o%t6lY)~|lu3WBB&xQr>qG-@V1=n$` z!cTSc%MW$au;MRo!WnYiHkh;shUVje1eZhfHVHyJO%ifcL;aPItWL6ZXlBNVJjB*$ z3@-Pm$fC(oo&o(tJ~oE%W;U(y zdynMim%Ls%Gd6UKfkV)37^ufiB`P^e1^=uGul0M9DhE<;f2$@2kFIG( zAsG`?*_f1_1Z+>r3OXl`T%YAy`SAEW}NIUkayr)5?azg?+-@Omt`&XCN8fO%_ zy?5St&W9JIkmgOJ{qv?vr8C!(i&m;acQl0+QJjCXZj`bmHIc{4!E+9IE}nkK_`q;h zuaskGDfGG9P6*g5J!h^jdu6S0qL`}eaSoVepO|P^y`xPWSzfgU6M*Ud2_RohD$pV# zLr})BMVQwIJs};zVWmLe?=u^Eib)P3u35T6lA!ZUNLGtE|7=yI9CbyF_p@ zn|2Lt?cxa$w5YhBbGM@4vLa8O0_YP76#xD??iqAP(tBOdztI`&{FJe#AEOm=iEKjf zFKsh5-V>C?g>^u~gcLHopq#RKN(Im8?rNH%Jc`SEv@QUF%&we`x@=J~N0lHc!36i= z9~wo%jrkn(zx&fu<$p1I##&!?(s*R}PqaBrFl68wD zC*J}8;S7_E?TrO-(gt%>2U%ZKyU$*u$~Nk|d8-}`eMg4Dd8G7?Kx-1ymPnV`lDqpW;&NKB+x*FbK&$Dvr zEYH&h_s-UOtgAQBfV%ebi+7$@bfmleGfCBG|A(s4fya)1S3d-kPPGR)M}wvmza>=f zD#?_pJQR6aohrEfqy|Qx+OihMr=calJWIauWvI&R6_@{gLbi&LyI*8x2urO4;eM!| zjc#Y6Sb3Kn*j3u|46nSWTS$T${{DR5xiGEgk1w-|TgGS{a-V8VZ0{5`D`76GK&{+y z28-#IO|+a0Q@r}kaztf1eQ8+zfqeMN$o&7jK)l;n2ln_tyEPLOY*B3#u;R;Y6S|p_wcH)qN!= z1y%~;Ijf1bYfs&u5A{#g53onPms^8lSL4G}E4#H@tLJxSR<7NU-;cI)6pxNxmL7!S^S>}m6=PhFkGSo$ref9GHU(J(! zCaY0O|B~SJgNY0!#~$&O7_Vk{0E-I{m=_vI&xWD2e=GTr(DVFa709R52X0?%iLiQ5 zjwE6!8-n>jGOKrr8bMMv%UbPej$Tlbexfo>o)1TcD$D~Fxt&4OeWxy%4`^Ec?xJ}J zx!EkY1G41Repep=&jaHFRR%8iVIY1^hw(nIoZnMEOY3Txw!tbugQ2NQj}Xd9e@0Fs z>kV99ELp&uSo$bVH=$6v_;b&O`Z1Oe$)Q1wMYVLM%5X9wv>G}9alUAR9hAlBn4#ht z4Wb_u9A2a$){UABHE~Vy={k}**Cl$#Q^aUX1+Hp7G+KHCi+RPQ$gIK;IKS-3`V!xL zFk`wRA2Ceqw7 z#v;fD+_pnu`oU5=JSPByQFp(?+oe|lj3DP3uG zeKyXE z*WM=SRjg>!A1m4^qUC&}%kR%yu2Pv?d!y0I=hQ+_T4Wx82Pr2WdGaZdh0)4d_biS={u5pD*BmDrpRWcB z0Qs7N;)oJdx4LdhVG*@i-E3`OrAk`S35plZU3>#CZ9G>wCfSrMh88U@#WL8*uN5Jp zT`8PEH`=%mzPjZ*RYsYR2S$Xb%v-N5ZlW7U%|=rkX5{ADqV-wy4{!?rc_c0h9q+mQ+8LNV;# z80y&}wsI!$$75$^@3AvU0?$ixCpP|w0D(f~Jk9wTsINjGP*zp_S0pzb4L(lhklfm{ z$-tvAMH-t#H{a)z5=g-A9K@x9ltz<6RO z-@o`Vc>Lbr6Mqffnv_KBxykoF0KAEE?-w*+z#ehW&t3ayt7HrOvw!QjMWhS=_t6Ay z1pPfqlJI@{!0;jWkS|R?j&or6N09z;oJ`=aUa#Ie?yXGHk!U1IN4oxwmo#A_pL$w> zZ(>2vz|(zW1LFd3;@NwXNHA1z)ADGCL*|cvpiRd`53Q!YDJ`v46WWI?`saH(J_saP=`a-wKm1 z=C5#LQ-G=5YdV6!^pA-C`oLfR`9c$XuyanSjosgGCJp@8-+x^T6HnMKWynDA)ykDt Rb_n=UzpSH@t9--f{{Wj!5P<*y literal 80982 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஢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 E0Md*U(*OVf From 557bab7fb42b5314a59c1f316cb73a593d192e5d Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 5 Feb 2023 02:57:46 +0900 Subject: [PATCH 27/36] =?UTF-8?q?feat:=20Like=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dku/springstudy/domain/Like.java | 35 +++++++++++++++++++ .../com/dku/springstudy/domain/Product.java | 4 +-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/domain/Like.java diff --git a/src/main/java/com/dku/springstudy/domain/Like.java b/src/main/java/com/dku/springstudy/domain/Like.java new file mode 100644 index 0000000..6a813a7 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/Like.java @@ -0,0 +1,35 @@ +package com.dku.springstudy.domain; + +import com.dku.springstudy.domain.common.BaseTimeEntity; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +@Table(name = "Likes") +public class Like extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "like_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id") + private Product product; + + @Builder + private Like(User user, Product product) { + this.user = user; + this.product = product; + } +} diff --git a/src/main/java/com/dku/springstudy/domain/Product.java b/src/main/java/com/dku/springstudy/domain/Product.java index 1d63789..bc04ce8 100644 --- a/src/main/java/com/dku/springstudy/domain/Product.java +++ b/src/main/java/com/dku/springstudy/domain/Product.java @@ -38,7 +38,7 @@ public class Product extends BaseTimeEntity { private Category category; @Column(nullable = false) - private Integer likeAndComment; // 좋아요 및 댓글 숫자 + private Integer like; @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -51,7 +51,7 @@ private Product(User user, String title, String content, Integer price, Category this.content = content; this.price = price; this.category = category; - this.likeAndComment = 0; + this.like = 0; this.status = status.PROGRESS; } } From f63e2ceeef38688b1f21fe5f60869d60afb2f51f Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 5 Feb 2023 14:43:28 +0900 Subject: [PATCH 28/36] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80/=EC=B7=A8=EC=86=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LikeController.java | 48 +++++++++++++++++ .../domain/{Like.java => Likes.java} | 9 ++-- .../com/dku/springstudy/domain/Product.java | 4 +- .../java/com/dku/springstudy/domain/User.java | 2 +- .../dto/like/LikeClickResponseDto.java | 18 +++++++ .../dku/springstudy/exception/ErrorCode.java | 1 + .../repository/LikeRepository.java | 13 +++++ .../com/dku/springstudy/s3/domain/File.java | 5 +- .../dku/springstudy/service/LikeService.java | 51 +++++++++++++++++++ 9 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/controller/LikeController.java rename src/main/java/com/dku/springstudy/domain/{Like.java => Likes.java} (75%) create mode 100644 src/main/java/com/dku/springstudy/dto/like/LikeClickResponseDto.java create mode 100644 src/main/java/com/dku/springstudy/repository/LikeRepository.java create mode 100644 src/main/java/com/dku/springstudy/service/LikeService.java diff --git a/src/main/java/com/dku/springstudy/controller/LikeController.java b/src/main/java/com/dku/springstudy/controller/LikeController.java new file mode 100644 index 0000000..91f4953 --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/LikeController.java @@ -0,0 +1,48 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.dto.common.SuccessResponse; +import com.dku.springstudy.dto.like.LikeClickResponseDto; +import com.dku.springstudy.security.CustomUserDetails; +import com.dku.springstudy.service.LikeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "좋아요 API") +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class LikeController { + + private final LikeService likeService; + + @Operation( + summary = "좋아요 추가 / 취소", + description = "좋아요를 누른 적이 있다면 좋아요를 취소하고, 누른 적이 없다면 좋아요를 추가한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "회원 또는 상품 아이디(PK)가 존재하지 않는 경우"), + }) + @PostMapping("/like/{productId}") + public ResponseEntity> clickLikeBtn( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable Long productId + ) { + + LikeClickResponseDto response = likeService.clickLikeBtn(customUserDetails.getId(), productId); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(response)); + } +} diff --git a/src/main/java/com/dku/springstudy/domain/Like.java b/src/main/java/com/dku/springstudy/domain/Likes.java similarity index 75% rename from src/main/java/com/dku/springstudy/domain/Like.java rename to src/main/java/com/dku/springstudy/domain/Likes.java index 6a813a7..2614af3 100644 --- a/src/main/java/com/dku/springstudy/domain/Like.java +++ b/src/main/java/com/dku/springstudy/domain/Likes.java @@ -11,8 +11,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity -@Table(name = "Likes") -public class Like extends BaseTimeEntity { +public class Likes extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -20,15 +19,15 @@ public class Like extends BaseTimeEntity { private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") + @JoinColumn(name = "user_id", nullable = false) private User user; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "product_id") + @JoinColumn(name = "product_id", nullable = false) private Product product; @Builder - private Like(User user, Product product) { + private Likes(User user, Product product) { this.user = user; this.product = product; } diff --git a/src/main/java/com/dku/springstudy/domain/Product.java b/src/main/java/com/dku/springstudy/domain/Product.java index bc04ce8..96e2af6 100644 --- a/src/main/java/com/dku/springstudy/domain/Product.java +++ b/src/main/java/com/dku/springstudy/domain/Product.java @@ -38,7 +38,7 @@ public class Product extends BaseTimeEntity { private Category category; @Column(nullable = false) - private Integer like; + private Integer likeCount; @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -51,7 +51,7 @@ private Product(User user, String title, String content, Integer price, Category this.content = content; this.price = price; this.category = category; - this.like = 0; + this.likeCount = 0; this.status = status.PROGRESS; } } diff --git a/src/main/java/com/dku/springstudy/domain/User.java b/src/main/java/com/dku/springstudy/domain/User.java index e2ad573..0e96164 100644 --- a/src/main/java/com/dku/springstudy/domain/User.java +++ b/src/main/java/com/dku/springstudy/domain/User.java @@ -35,7 +35,7 @@ public class User extends BaseTimeEntity { private String nickname; @Column(nullable = false) - private int status; + private Integer status; @Enumerated(EnumType.STRING) private Role role; diff --git a/src/main/java/com/dku/springstudy/dto/like/LikeClickResponseDto.java b/src/main/java/com/dku/springstudy/dto/like/LikeClickResponseDto.java new file mode 100644 index 0000000..fb89177 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/like/LikeClickResponseDto.java @@ -0,0 +1,18 @@ +package com.dku.springstudy.dto.like; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class LikeClickResponseDto { + + @Schema(example = "add", description = "좋아요 추가 / 취소 여부") + private String activity; + + public static LikeClickResponseDto of(String activity) { + return new LikeClickResponseDto(activity); + } +} diff --git a/src/main/java/com/dku/springstudy/exception/ErrorCode.java b/src/main/java/com/dku/springstudy/exception/ErrorCode.java index e562fd7..43483be 100644 --- a/src/main/java/com/dku/springstudy/exception/ErrorCode.java +++ b/src/main/java/com/dku/springstudy/exception/ErrorCode.java @@ -19,6 +19,7 @@ public enum ErrorCode { // product FILE_EXTENSION_NOT_SUPPORT(HttpStatus.BAD_REQUEST, "지원하지 않는 파일 확장자입니다."), FILE_UPLOAD_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다."), + PRODUCT_ID_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 아이디(PK)를 가진 상품을 찾을 수 없습니다."), ; private final HttpStatus httpstatus; diff --git a/src/main/java/com/dku/springstudy/repository/LikeRepository.java b/src/main/java/com/dku/springstudy/repository/LikeRepository.java new file mode 100644 index 0000000..f7ee5a9 --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/LikeRepository.java @@ -0,0 +1,13 @@ +package com.dku.springstudy.repository; + +import com.dku.springstudy.domain.Likes; +import com.dku.springstudy.domain.Product; +import com.dku.springstudy.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface LikeRepository extends JpaRepository { + + Optional findByUserAndProduct(User user, Product product); +} diff --git a/src/main/java/com/dku/springstudy/s3/domain/File.java b/src/main/java/com/dku/springstudy/s3/domain/File.java index 36037a1..792658a 100644 --- a/src/main/java/com/dku/springstudy/s3/domain/File.java +++ b/src/main/java/com/dku/springstudy/s3/domain/File.java @@ -1,6 +1,7 @@ package com.dku.springstudy.s3.domain; import com.dku.springstudy.domain.Product; +import com.dku.springstudy.domain.common.BaseTimeEntity; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -11,11 +12,11 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity -public class File { +public class File extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "image_id") + @Column(name = "file_id") private Long id; @JoinColumn(name = "product_id", nullable = false) diff --git a/src/main/java/com/dku/springstudy/service/LikeService.java b/src/main/java/com/dku/springstudy/service/LikeService.java new file mode 100644 index 0000000..464c1c3 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/LikeService.java @@ -0,0 +1,51 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.Likes; +import com.dku.springstudy.domain.Product; +import com.dku.springstudy.domain.User; +import com.dku.springstudy.dto.like.LikeClickResponseDto; +import com.dku.springstudy.exception.CustomException; +import com.dku.springstudy.exception.ErrorCode; +import com.dku.springstudy.repository.LikeRepository; +import com.dku.springstudy.repository.ProductRepository; +import com.dku.springstudy.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class LikeService { + + private final LikeRepository likeRepository; + private final UserRepository userRepository; + private final ProductRepository productRepository; + + @Transactional + public LikeClickResponseDto clickLikeBtn(Long memberId, Long productId) { + User user = userRepository.findById(memberId).orElseThrow(() -> new CustomException(ErrorCode.USER_ID_NOT_FOUND)); + Product product = productRepository.findById(productId).orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_ID_NOT_FOUND)); + + return checkActivity(user, product); + } + + private LikeClickResponseDto checkActivity(User user, Product product) { + Optional likeByUserAndProduct = likeRepository.findByUserAndProduct(user, product); + + if (likeByUserAndProduct.isPresent()) { // 좋아요를 누른적이 있다면 취소 + Likes like = likeByUserAndProduct.get(); + likeRepository.delete(like); + return LikeClickResponseDto.of("cancel"); + } else { + Likes like = Likes.builder() + .user(user) + .product(product) + .build(); + likeRepository.save(like); + return LikeClickResponseDto.of("add"); + } + } +} From be9d377764331a9d74418bc685af7a252764bdae Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 5 Feb 2023 17:40:32 +0900 Subject: [PATCH 29/36] =?UTF-8?q?feat:=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/config/InterceptorConfig.java | 20 ++++++++ .../controller/LikeController.java | 9 +--- .../controller/ProductController.java | 9 +--- .../controller/UserController.java | 15 ++---- .../common/interceptor/HttpInterceptor.java | 48 ++++++++++++++++++ .../security/jwt/JwtAuthenticationFilter.java | 50 ++++++++++++++++--- 6 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/config/InterceptorConfig.java create mode 100644 src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java diff --git a/src/main/java/com/dku/springstudy/config/InterceptorConfig.java b/src/main/java/com/dku/springstudy/config/InterceptorConfig.java new file mode 100644 index 0000000..9fc009e --- /dev/null +++ b/src/main/java/com/dku/springstudy/config/InterceptorConfig.java @@ -0,0 +1,20 @@ +package com.dku.springstudy.config; + +import com.dku.springstudy.dto.common.interceptor.HttpInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class InterceptorConfig implements WebMvcConfigurer { + + private final HttpInterceptor httpInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(httpInterceptor) // 만든 인터셉터 등록 + .addPathPatterns("/**"); + } +} diff --git a/src/main/java/com/dku/springstudy/controller/LikeController.java b/src/main/java/com/dku/springstudy/controller/LikeController.java index 91f4953..12f3533 100644 --- a/src/main/java/com/dku/springstudy/controller/LikeController.java +++ b/src/main/java/com/dku/springstudy/controller/LikeController.java @@ -1,6 +1,5 @@ package com.dku.springstudy.controller; -import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.like.LikeClickResponseDto; import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.LikeService; @@ -9,8 +8,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -34,15 +31,13 @@ public class LikeController { @ApiResponse(responseCode = "404", description = "회원 또는 상품 아이디(PK)가 존재하지 않는 경우"), }) @PostMapping("/like/{productId}") - public ResponseEntity> clickLikeBtn( + public LikeClickResponseDto clickLikeBtn( @AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable Long productId ) { LikeClickResponseDto response = likeService.clickLikeBtn(customUserDetails.getId(), productId); - return ResponseEntity - .status(HttpStatus.OK) - .body(new SuccessResponse<>(response)); + return response; } } diff --git a/src/main/java/com/dku/springstudy/controller/ProductController.java b/src/main/java/com/dku/springstudy/controller/ProductController.java index 10b3d95..8d8a364 100644 --- a/src/main/java/com/dku/springstudy/controller/ProductController.java +++ b/src/main/java/com/dku/springstudy/controller/ProductController.java @@ -1,6 +1,5 @@ package com.dku.springstudy.controller; -import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.product.request.CreateRequestDto; import com.dku.springstudy.dto.product.response.CreateResponseDto; import com.dku.springstudy.security.CustomUserDetails; @@ -11,8 +10,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -37,7 +34,7 @@ public class ProductController { @ApiResponse(responseCode = "500", description = "파일의 업로드가 실패했거나 파일 확장자가 올바르지 않는 경우") }) @PostMapping("/product") - public ResponseEntity> createPost( + public CreateResponseDto createPost( @AuthenticationPrincipal CustomUserDetails customUserDetails, @Parameter(description = "data 키 값으로 CreateRequestDto의 필드들을 입력한다.") @RequestPart("data") CreateRequestDto dto, @@ -47,8 +44,6 @@ public ResponseEntity> createPost( CreateResponseDto response = productService.createPost(dto, file, customUserDetails.getId()); - return ResponseEntity - .status(HttpStatus.OK) - .body(new SuccessResponse<>(response)); + return response; } } diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 12ea43f..7cf6f82 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -1,6 +1,5 @@ package com.dku.springstudy.controller; -import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.user.request.LoginRequestDto; import com.dku.springstudy.dto.user.response.LoginResponseDto; import com.dku.springstudy.dto.user.request.SignUpRequestDto; @@ -11,8 +10,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @@ -34,12 +31,10 @@ public class UserController { @ApiResponse(responseCode = "409", description = "이미 가입된 이메일로 회원가입을 시도하는 경우") }) @PostMapping("/sign-up") - public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { + public SignUpResponseDto signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { SignUpResponseDto userId = userService.signUp(signUpRequestDto); - return ResponseEntity - .status(HttpStatus.OK) - .body(new SuccessResponse<>(userId)); + return userId; } @Operation( @@ -52,11 +47,9 @@ public ResponseEntity> signUp(@Valid @Request @ApiResponse(responseCode = "401", description = "비밀번호가 일치하지 않는 경우") }) @PostMapping("/login") - public ResponseEntity> login(@Valid @RequestBody LoginRequestDto loginRequestDto) { + public LoginResponseDto login(@Valid @RequestBody LoginRequestDto loginRequestDto) { LoginResponseDto tokens = userService.login(loginRequestDto); - return ResponseEntity - .status(HttpStatus.OK) - .body(new SuccessResponse<>(tokens)); + return tokens; } } diff --git a/src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java b/src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java new file mode 100644 index 0000000..5ba456a --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java @@ -0,0 +1,48 @@ +package com.dku.springstudy.dto.common.interceptor; + +import com.dku.springstudy.dto.common.SuccessResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Slf4j +@RequiredArgsConstructor +@Component +public class HttpInterceptor implements HandlerInterceptor { + + private final ObjectMapper objectMapper; + + // 사용자에게 response가 나가기 전에 처리 + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception ex) throws Exception { + + final ContentCachingResponseWrapper cachingResponse = (ContentCachingResponseWrapper) response; // 시큐리티 필터에서 넘어온 응답 값 + + if (!String.valueOf(response.getStatus()).startsWith("2")) { + return; + } + + if ((cachingResponse.getContentType() != null) && (cachingResponse.getContentType().contains("application/json"))) { // JSON 응답 값만 + if (cachingResponse.getContentAsByteArray().length != 0) { + String body = new String(cachingResponse.getContentAsByteArray()); + + Object data = objectMapper.readValue(body, Object.class); + + SuccessResponse successResponseDto = new SuccessResponse<>(data); + + String wrappedBody = objectMapper.writeValueAsString(successResponseDto); + + cachingResponse.resetBuffer(); + + cachingResponse.getOutputStream().write(wrappedBody.getBytes(), 0, wrappedBody.getBytes().length); + log.info("SuccessResponse Body : ", wrappedBody); + } + } + } +} diff --git a/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java index 86c2a84..00d8ee7 100644 --- a/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java @@ -1,17 +1,26 @@ package com.dku.springstudy.security.jwt; +import com.dku.springstudy.dto.common.ErrorResponse; +import com.dku.springstudy.exception.CustomException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j @@ -24,16 +33,41 @@ public class JwtAuthenticationFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - String token = resolveToken((HttpServletRequest) request); + try { + // SecurityFilter에서 넘어오는 Request와 Response 캐싱 + ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper((HttpServletRequest) request); + ContentCachingResponseWrapper wrappingResponse = new ContentCachingResponseWrapper((HttpServletResponse) response); - if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { // 토큰이 유효하다면 - Authentication authentication = jwtTokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); // 사용자 정보를 SecurityContext에 저장 - } else { - log.debug("유효한 JWT 토큰이 없습니다, uri: {}", ((HttpServletRequest) request).getRequestURI()); - } + String token = resolveToken((HttpServletRequest) request); + + if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { // 토큰이 유효하다면 + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); // 사용자 정보를 SecurityContext에 저장 + } else { + log.debug("유효한 JWT 토큰이 없습니다, uri: {}", ((HttpServletRequest) request).getRequestURI()); + } + + chain.doFilter(wrappingRequest , wrappingResponse); + wrappingResponse.copyBodyToResponse(); // 캐싱된 응답값을 덮어쓴다. + } catch (CustomException e) { + HttpServletResponse errorResponse = (HttpServletResponse) response; + + // header 작성 + errorResponse.setStatus(e.getErrorCode().getHttpstatus().value()); + errorResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); - chain.doFilter(request, response); + // exception 응답값 생성 + HttpStatus status = e.getErrorCode().getHttpstatus(); + ResponseEntity errorEntity = ResponseEntity + .status(status) + .body(new ErrorResponse(status.value(), e.getErrorCode().getMessage())); + + ObjectMapper objectMapper = new ObjectMapper(); + String exceptionMessage = objectMapper.writeValueAsString(errorEntity); + + errorResponse.getWriter().write(exceptionMessage); + log.info("ErrorResponse Body : ", errorResponse); + } } private String resolveToken(HttpServletRequest request) { From 056e2b94da7dce7e6cea43d4853851f277e6c124 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 9 Feb 2023 00:59:46 +0900 Subject: [PATCH 30/36] =?UTF-8?q?fix:=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springstudy/config/InterceptorConfig.java | 40 ++++---- .../controller/LikeController.java | 9 +- .../controller/ProductController.java | 11 ++- .../controller/UserController.java | 15 ++- .../common/interceptor/HttpInterceptor.java | 96 +++++++++---------- .../security/jwt/JwtAuthenticationFilter.java | 41 ++------ 6 files changed, 102 insertions(+), 110 deletions(-) diff --git a/src/main/java/com/dku/springstudy/config/InterceptorConfig.java b/src/main/java/com/dku/springstudy/config/InterceptorConfig.java index 9fc009e..d8c5b47 100644 --- a/src/main/java/com/dku/springstudy/config/InterceptorConfig.java +++ b/src/main/java/com/dku/springstudy/config/InterceptorConfig.java @@ -1,20 +1,20 @@ -package com.dku.springstudy.config; - -import com.dku.springstudy.dto.common.interceptor.HttpInterceptor; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@RequiredArgsConstructor -public class InterceptorConfig implements WebMvcConfigurer { - - private final HttpInterceptor httpInterceptor; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(httpInterceptor) // 만든 인터셉터 등록 - .addPathPatterns("/**"); - } -} +//package com.dku.springstudy.config; +// +//import com.dku.springstudy.dto.common.interceptor.HttpInterceptor; +//import lombok.RequiredArgsConstructor; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +// +//@Configuration +//@RequiredArgsConstructor +//public class InterceptorConfig implements WebMvcConfigurer { +// +// private final HttpInterceptor httpInterceptor; +// +// @Override +// public void addInterceptors(InterceptorRegistry registry) { +// registry.addInterceptor(httpInterceptor) // 만든 인터셉터 등록 +// .addPathPatterns("/**"); +// } +//} diff --git a/src/main/java/com/dku/springstudy/controller/LikeController.java b/src/main/java/com/dku/springstudy/controller/LikeController.java index 12f3533..91f4953 100644 --- a/src/main/java/com/dku/springstudy/controller/LikeController.java +++ b/src/main/java/com/dku/springstudy/controller/LikeController.java @@ -1,5 +1,6 @@ package com.dku.springstudy.controller; +import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.like.LikeClickResponseDto; import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.LikeService; @@ -8,6 +9,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -31,13 +34,15 @@ public class LikeController { @ApiResponse(responseCode = "404", description = "회원 또는 상품 아이디(PK)가 존재하지 않는 경우"), }) @PostMapping("/like/{productId}") - public LikeClickResponseDto clickLikeBtn( + public ResponseEntity> clickLikeBtn( @AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable Long productId ) { LikeClickResponseDto response = likeService.clickLikeBtn(customUserDetails.getId(), productId); - return response; + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(response)); } } diff --git a/src/main/java/com/dku/springstudy/controller/ProductController.java b/src/main/java/com/dku/springstudy/controller/ProductController.java index 8d8a364..b36a445 100644 --- a/src/main/java/com/dku/springstudy/controller/ProductController.java +++ b/src/main/java/com/dku/springstudy/controller/ProductController.java @@ -1,5 +1,6 @@ package com.dku.springstudy.controller; +import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.product.request.CreateRequestDto; import com.dku.springstudy.dto.product.response.CreateResponseDto; import com.dku.springstudy.security.CustomUserDetails; @@ -10,6 +11,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -34,16 +37,18 @@ public class ProductController { @ApiResponse(responseCode = "500", description = "파일의 업로드가 실패했거나 파일 확장자가 올바르지 않는 경우") }) @PostMapping("/product") - public CreateResponseDto createPost( + public ResponseEntity> createPost( @AuthenticationPrincipal CustomUserDetails customUserDetails, @Parameter(description = "data 키 값으로 CreateRequestDto의 필드들을 입력한다.") @RequestPart("data") CreateRequestDto dto, @Parameter(description = "file 키 값으로 이미지들을 입력한다.") @RequestPart(value = "file", required = false) List file - ) { + ) { CreateResponseDto response = productService.createPost(dto, file, customUserDetails.getId()); - return response; + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(response)); } } diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 7cf6f82..12ea43f 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -1,5 +1,6 @@ package com.dku.springstudy.controller; +import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.user.request.LoginRequestDto; import com.dku.springstudy.dto.user.response.LoginResponseDto; import com.dku.springstudy.dto.user.request.SignUpRequestDto; @@ -10,6 +11,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @@ -31,10 +34,12 @@ public class UserController { @ApiResponse(responseCode = "409", description = "이미 가입된 이메일로 회원가입을 시도하는 경우") }) @PostMapping("/sign-up") - public SignUpResponseDto signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { + public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { SignUpResponseDto userId = userService.signUp(signUpRequestDto); - return userId; + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(userId)); } @Operation( @@ -47,9 +52,11 @@ public SignUpResponseDto signUp(@Valid @RequestBody SignUpRequestDto signUpReque @ApiResponse(responseCode = "401", description = "비밀번호가 일치하지 않는 경우") }) @PostMapping("/login") - public LoginResponseDto login(@Valid @RequestBody LoginRequestDto loginRequestDto) { + public ResponseEntity> login(@Valid @RequestBody LoginRequestDto loginRequestDto) { LoginResponseDto tokens = userService.login(loginRequestDto); - return tokens; + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(tokens)); } } diff --git a/src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java b/src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java index 5ba456a..bf54f7d 100644 --- a/src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java +++ b/src/main/java/com/dku/springstudy/dto/common/interceptor/HttpInterceptor.java @@ -1,48 +1,48 @@ -package com.dku.springstudy.dto.common.interceptor; - -import com.dku.springstudy.dto.common.SuccessResponse; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.util.ContentCachingResponseWrapper; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@Slf4j -@RequiredArgsConstructor -@Component -public class HttpInterceptor implements HandlerInterceptor { - - private final ObjectMapper objectMapper; - - // 사용자에게 response가 나가기 전에 처리 - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception ex) throws Exception { - - final ContentCachingResponseWrapper cachingResponse = (ContentCachingResponseWrapper) response; // 시큐리티 필터에서 넘어온 응답 값 - - if (!String.valueOf(response.getStatus()).startsWith("2")) { - return; - } - - if ((cachingResponse.getContentType() != null) && (cachingResponse.getContentType().contains("application/json"))) { // JSON 응답 값만 - if (cachingResponse.getContentAsByteArray().length != 0) { - String body = new String(cachingResponse.getContentAsByteArray()); - - Object data = objectMapper.readValue(body, Object.class); - - SuccessResponse successResponseDto = new SuccessResponse<>(data); - - String wrappedBody = objectMapper.writeValueAsString(successResponseDto); - - cachingResponse.resetBuffer(); - - cachingResponse.getOutputStream().write(wrappedBody.getBytes(), 0, wrappedBody.getBytes().length); - log.info("SuccessResponse Body : ", wrappedBody); - } - } - } -} +//package com.dku.springstudy.dto.common.interceptor; +// +//import com.dku.springstudy.dto.common.SuccessResponse; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.stereotype.Component; +//import org.springframework.web.servlet.HandlerInterceptor; +//import org.springframework.web.util.ContentCachingResponseWrapper; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//@Slf4j +//@RequiredArgsConstructor +//@Component +//public class HttpInterceptor implements HandlerInterceptor { +// +// private final ObjectMapper objectMapper; +// +// // 사용자에게 response가 나가기 전에 처리 +// @Override +// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception ex) throws Exception { +// +// final ContentCachingResponseWrapper cachingResponse = (ContentCachingResponseWrapper) response; // 시큐리티 필터에서 넘어온 응답 값 +// +// if (!String.valueOf(response.getStatus()).startsWith("2")) { +// return; +// } +// +// if ((cachingResponse.getContentType() != null) && (cachingResponse.getContentType().contains("application/json"))) { // JSON 응답 값만 +// if (cachingResponse.getContentAsByteArray().length != 0) { +// String body = new String(cachingResponse.getContentAsByteArray()); +// +// Object data = objectMapper.readValue(body, Object.class); +// +// SuccessResponse successResponseDto = new SuccessResponse<>(data); +// +// String wrappedBody = objectMapper.writeValueAsString(successResponseDto); +// +// cachingResponse.resetBuffer(); +// +// cachingResponse.getOutputStream().write(wrappedBody.getBytes(), 0, wrappedBody.getBytes().length); +// log.info("SuccessResponse Body : ", wrappedBody); +// } +// } +// } +//} diff --git a/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java index 00d8ee7..b3a8d62 100644 --- a/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/dku/springstudy/security/jwt/JwtAuthenticationFilter.java @@ -33,41 +33,16 @@ public class JwtAuthenticationFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - try { - // SecurityFilter에서 넘어오는 Request와 Response 캐싱 - ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper((HttpServletRequest) request); - ContentCachingResponseWrapper wrappingResponse = new ContentCachingResponseWrapper((HttpServletResponse) response); + String token = resolveToken((HttpServletRequest) request); - String token = resolveToken((HttpServletRequest) request); - - if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { // 토큰이 유효하다면 - Authentication authentication = jwtTokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); // 사용자 정보를 SecurityContext에 저장 - } else { - log.debug("유효한 JWT 토큰이 없습니다, uri: {}", ((HttpServletRequest) request).getRequestURI()); - } - - chain.doFilter(wrappingRequest , wrappingResponse); - wrappingResponse.copyBodyToResponse(); // 캐싱된 응답값을 덮어쓴다. - } catch (CustomException e) { - HttpServletResponse errorResponse = (HttpServletResponse) response; - - // header 작성 - errorResponse.setStatus(e.getErrorCode().getHttpstatus().value()); - errorResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); - - // exception 응답값 생성 - HttpStatus status = e.getErrorCode().getHttpstatus(); - ResponseEntity errorEntity = ResponseEntity - .status(status) - .body(new ErrorResponse(status.value(), e.getErrorCode().getMessage())); - - ObjectMapper objectMapper = new ObjectMapper(); - String exceptionMessage = objectMapper.writeValueAsString(errorEntity); - - errorResponse.getWriter().write(exceptionMessage); - log.info("ErrorResponse Body : ", errorResponse); + if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { // 토큰이 유효하다면 + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); // 사용자 정보를 SecurityContext에 저장 + } else { + log.debug("유효한 JWT 토큰이 없습니다, uri: {}", ((HttpServletRequest) request).getRequestURI()); } + + chain.doFilter(request, response); } private String resolveToken(HttpServletRequest request) { From 4b26ef8f7f1e73202619b8813f7c4b23082af34b Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 9 Feb 2023 01:00:44 +0900 Subject: [PATCH 31/36] =?UTF-8?q?fix:=20swagger=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dku/springstudy/controller/LikeController.java | 4 ++-- .../com/dku/springstudy/controller/ProductController.java | 4 ++-- .../java/com/dku/springstudy/controller/UserController.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/dku/springstudy/controller/LikeController.java b/src/main/java/com/dku/springstudy/controller/LikeController.java index 91f4953..597deb7 100644 --- a/src/main/java/com/dku/springstudy/controller/LikeController.java +++ b/src/main/java/com/dku/springstudy/controller/LikeController.java @@ -4,10 +4,10 @@ import com.dku.springstudy.dto.like.LikeClickResponseDto; import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.LikeService; +import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "좋아요 API") +@Api(tags = "좋아요 API") @RestController @RequestMapping("/api") @RequiredArgsConstructor diff --git a/src/main/java/com/dku/springstudy/controller/ProductController.java b/src/main/java/com/dku/springstudy/controller/ProductController.java index b36a445..b51eca9 100644 --- a/src/main/java/com/dku/springstudy/controller/ProductController.java +++ b/src/main/java/com/dku/springstudy/controller/ProductController.java @@ -5,11 +5,11 @@ import com.dku.springstudy.dto.product.response.CreateResponseDto; import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.ProductService; +import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -19,7 +19,7 @@ import java.util.List; -@Tag(name = "상품 API") +@Api(tags = "상품 API") @RestController @RequestMapping("/api") @RequiredArgsConstructor diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 12ea43f..b4d393a 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -6,10 +6,10 @@ import com.dku.springstudy.dto.user.request.SignUpRequestDto; import com.dku.springstudy.dto.user.response.SignUpResponseDto; import com.dku.springstudy.service.UserService; +import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,7 +17,7 @@ import javax.validation.Valid; -@Tag(name = "사용자 API") +@Api(tags = "사용자 API") @RestController @RequestMapping("/api") @RequiredArgsConstructor From d19c114b29ae83ba52bcadeb22164e5f190adf1a Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 9 Feb 2023 04:48:52 +0900 Subject: [PATCH 32/36] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=88=98=EC=A0=95=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProductController.java | 3 ++- .../controller/UserController.java | 25 +++++++++++++++++++ .../java/com/dku/springstudy/domain/User.java | 8 ++++++ .../user/response/UserUpdateResponseDto.java | 24 ++++++++++++++++++ .../dku/springstudy/s3/service/S3Service.java | 20 +++++++++++++++ .../dku/springstudy/service/UserService.java | 19 ++++++++++++++ 6 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/dku/springstudy/dto/user/response/UserUpdateResponseDto.java diff --git a/src/main/java/com/dku/springstudy/controller/ProductController.java b/src/main/java/com/dku/springstudy/controller/ProductController.java index b51eca9..248a26c 100644 --- a/src/main/java/com/dku/springstudy/controller/ProductController.java +++ b/src/main/java/com/dku/springstudy/controller/ProductController.java @@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.validation.Valid; import java.util.List; @Api(tags = "상품 API") @@ -40,7 +41,7 @@ public class ProductController { public ResponseEntity> createPost( @AuthenticationPrincipal CustomUserDetails customUserDetails, @Parameter(description = "data 키 값으로 CreateRequestDto의 필드들을 입력한다.") - @RequestPart("data") CreateRequestDto dto, + @Valid @RequestPart("data") CreateRequestDto dto, @Parameter(description = "file 키 값으로 이미지들을 입력한다.") @RequestPart(value = "file", required = false) List file ) { diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index b4d393a..9bb7e1b 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -5,6 +5,8 @@ import com.dku.springstudy.dto.user.response.LoginResponseDto; import com.dku.springstudy.dto.user.request.SignUpRequestDto; import com.dku.springstudy.dto.user.response.SignUpResponseDto; +import com.dku.springstudy.dto.user.response.UserUpdateResponseDto; +import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.UserService; import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation; @@ -13,7 +15,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; @@ -59,4 +63,25 @@ public ResponseEntity> login(@Valid @RequestBo .status(HttpStatus.OK) .body(new SuccessResponse<>(tokens)); } + + @Operation( + summary = "회원 프로필 수정", + description = "사용자로부터 닉네임과 사진을 입력받아 회원의 정보를 수정한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "아이디(PK)가 존재하지 않는 경우"), + }) + @PatchMapping("/user/profile") + public ResponseEntity> update( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @Valid @RequestPart(value = "nickname") String nickname, + @RequestPart(value = "profile", required = false) MultipartFile file + ) { + UserUpdateResponseDto response = userService.update(customUserDetails.getId(), nickname, file); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(response)); + } } diff --git a/src/main/java/com/dku/springstudy/domain/User.java b/src/main/java/com/dku/springstudy/domain/User.java index 0e96164..c452356 100644 --- a/src/main/java/com/dku/springstudy/domain/User.java +++ b/src/main/java/com/dku/springstudy/domain/User.java @@ -40,6 +40,8 @@ public class User extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Role role; + private String ImgUrl; + @Builder private User(String email, String password, String name, String phoneNumber, String nickname) { this.email = email; @@ -50,4 +52,10 @@ private User(String email, String password, String name, String phoneNumber, Str this.status = 1; this.role = role.USER; } + + // 회원 정보 수정 + public void update(String nickname, String ImgUrl) { + this.nickname = nickname; + this.ImgUrl = ImgUrl; + } } diff --git a/src/main/java/com/dku/springstudy/dto/user/response/UserUpdateResponseDto.java b/src/main/java/com/dku/springstudy/dto/user/response/UserUpdateResponseDto.java new file mode 100644 index 0000000..f5d23ed --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/user/response/UserUpdateResponseDto.java @@ -0,0 +1,24 @@ +package com.dku.springstudy.dto.user.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import javax.validation.constraints.NotBlank; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class UserUpdateResponseDto { + + @Schema(example = "https://d-coding.s3.ap-northeast-2.amazonaws.com/67a29351-9339-42b0-ba46-da6584ab93cc.jpg", description = "업로드한 사진 url") + private String imgUrl; + + @Schema(example = "sujjeong", description = "닉네임") + @NotBlank + private String nickname; + + public static UserUpdateResponseDto of(String imgUrl, String nickname) { + return new UserUpdateResponseDto(imgUrl, nickname); + } +} diff --git a/src/main/java/com/dku/springstudy/s3/service/S3Service.java b/src/main/java/com/dku/springstudy/s3/service/S3Service.java index eb817f4..1ca346f 100644 --- a/src/main/java/com/dku/springstudy/s3/service/S3Service.java +++ b/src/main/java/com/dku/springstudy/s3/service/S3Service.java @@ -33,6 +33,7 @@ public class S3Service { private final AmazonS3 amazonS3; private final FileRepository fileRepository; + // 파일 업로드 (여러 개) @Transactional public List uploadFiles(List multipartFile, Product product) { List fileUrlList = new ArrayList<>(); @@ -62,6 +63,25 @@ public List uploadFiles(List multipartFile, Product produ return fileUrlList; } + // 파일 업로드 (1개) + @Transactional + public String uploadFile(MultipartFile file) { + + String fileName = createFileName(file.getOriginalFilename()); + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(file.getSize()); + objectMetadata.setContentType(file.getContentType()); + + try (InputStream inputStream = file.getInputStream()) { + amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (IOException e) { + throw new CustomException(ErrorCode.FILE_UPLOAD_FAIL); + } + + return getUrl(fileName); + } + private String createFileName(String fileName) { return UUID.randomUUID().toString().concat(getFileExtension(fileName)); } diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java index d99a86b..83fa3c7 100644 --- a/src/main/java/com/dku/springstudy/service/UserService.java +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -5,14 +5,17 @@ import com.dku.springstudy.dto.user.response.LoginResponseDto; import com.dku.springstudy.dto.user.request.SignUpRequestDto; import com.dku.springstudy.dto.user.response.SignUpResponseDto; +import com.dku.springstudy.dto.user.response.UserUpdateResponseDto; import com.dku.springstudy.exception.CustomException; import com.dku.springstudy.exception.ErrorCode; +import com.dku.springstudy.s3.service.S3Service; import com.dku.springstudy.security.jwt.JwtTokenProvider; import com.dku.springstudy.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @RequiredArgsConstructor @Transactional(readOnly = true) @@ -22,6 +25,7 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtTokenProvider jwtTokenProvider; + private final S3Service s3Service; @Transactional public SignUpResponseDto signUp(SignUpRequestDto signUpRequestDto) { @@ -55,4 +59,19 @@ public LoginResponseDto login(LoginRequestDto loginRequestDto) { throw new CustomException(ErrorCode.USER_PASSWORD_NOT_MATCHES); } } + + @Transactional + public UserUpdateResponseDto update(Long loginMemberId, String nickname, MultipartFile file) { + + User user = userRepository.findById(loginMemberId).orElseThrow(() -> new CustomException(ErrorCode.USER_ID_NOT_FOUND)); + String imgUrl = null; + + if(!file.isEmpty()) { + imgUrl = s3Service.uploadFile(file); + } + + user.update(nickname, imgUrl); + + return UserUpdateResponseDto.of(nickname, imgUrl); + } } From b6f49bcd81adc415aba1bf37950b9f906190d269 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Thu, 9 Feb 2023 05:20:46 +0900 Subject: [PATCH 33/36] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=82=AC=EC=A7=84=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/UserController.java | 18 ++++++++++++++++++ .../java/com/dku/springstudy/domain/User.java | 5 +++++ .../dku/springstudy/s3/service/S3Service.java | 19 +++++++++++++++++++ .../dku/springstudy/service/UserService.java | 10 ++++++++++ 4 files changed, 52 insertions(+) diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index 9bb7e1b..b1de53c 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -84,4 +84,22 @@ public ResponseEntity> update( .status(HttpStatus.OK) .body(new SuccessResponse<>(response)); } + + @Operation( + summary = "회원 프로필 사진 삭제", + description = "로그인된 사용자의 프로필 사진을 삭제한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "아이디(PK)가 존재하지 않는 경우"), + }) + @DeleteMapping("/user/profile") + public ResponseEntity> deleteImg(@AuthenticationPrincipal CustomUserDetails customUserDetails) { + + UserUpdateResponseDto response = userService.deleteImg(customUserDetails.getId()); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(response)); + } } diff --git a/src/main/java/com/dku/springstudy/domain/User.java b/src/main/java/com/dku/springstudy/domain/User.java index c452356..47aa0d4 100644 --- a/src/main/java/com/dku/springstudy/domain/User.java +++ b/src/main/java/com/dku/springstudy/domain/User.java @@ -58,4 +58,9 @@ public void update(String nickname, String ImgUrl) { this.nickname = nickname; this.ImgUrl = ImgUrl; } + + // 프로필 사진 삭제 + public void deleteImg() { + this.ImgUrl = null; + } } diff --git a/src/main/java/com/dku/springstudy/s3/service/S3Service.java b/src/main/java/com/dku/springstudy/s3/service/S3Service.java index 1ca346f..38ac427 100644 --- a/src/main/java/com/dku/springstudy/s3/service/S3Service.java +++ b/src/main/java/com/dku/springstudy/s3/service/S3Service.java @@ -1,6 +1,8 @@ package com.dku.springstudy.s3.service; +import com.amazonaws.AmazonServiceException; import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; @@ -30,6 +32,9 @@ public class S3Service { @Value("${cloud.aws.s3.bucket}") private String bucket; + @Value("${cloud.aws.region.static}") + private String region; + private final AmazonS3 amazonS3; private final FileRepository fileRepository; @@ -100,4 +105,18 @@ private String getFileExtension(String fileName) { private String getUrl(String fileName) { return amazonS3.getUrl(bucket, fileName).toString(); } + + // 파일 삭제 (1개) + public void deleteFile(String fileUrl) { + + String fileKey = fileUrl.substring(49); + final AmazonS3 s3 = AmazonS3ClientBuilder.standard().withRegion(region).build(); + + try { + s3.deleteObject(bucket, fileKey); + } catch (AmazonServiceException e) { + System.err.println(e.getErrorMessage()); + System.exit(1); + } + } } diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java index 83fa3c7..aef6b55 100644 --- a/src/main/java/com/dku/springstudy/service/UserService.java +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -74,4 +74,14 @@ public UserUpdateResponseDto update(Long loginMemberId, String nickname, Multipa return UserUpdateResponseDto.of(nickname, imgUrl); } + + @Transactional + public UserUpdateResponseDto deleteImg(Long loginMemberId) { + + User user = userRepository.findById(loginMemberId).orElseThrow(() -> new CustomException(ErrorCode.USER_ID_NOT_FOUND)); + s3Service.deleteFile(user.getImgUrl()); + user.deleteImg(); + + return UserUpdateResponseDto.of(user.getNickname(), null); + } } From af0c39cb237229ab43d3720f99f20c8e7e5ea287 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sat, 11 Feb 2023 02:17:01 +0900 Subject: [PATCH 34/36] =?UTF-8?q?style:=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dku/springstudy/controller/ProductController.java | 10 +++++----- ...ateRequestDto.java => ProductCreateRequestDto.java} | 2 +- ...eResponseDto.java => ProductCreateResponseDto.java} | 6 +++--- .../com/dku/springstudy/service/ProductService.java | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) rename src/main/java/com/dku/springstudy/dto/product/request/{CreateRequestDto.java => ProductCreateRequestDto.java} (95%) rename src/main/java/com/dku/springstudy/dto/product/response/{CreateResponseDto.java => ProductCreateResponseDto.java} (78%) diff --git a/src/main/java/com/dku/springstudy/controller/ProductController.java b/src/main/java/com/dku/springstudy/controller/ProductController.java index 248a26c..fcaceb3 100644 --- a/src/main/java/com/dku/springstudy/controller/ProductController.java +++ b/src/main/java/com/dku/springstudy/controller/ProductController.java @@ -1,8 +1,8 @@ package com.dku.springstudy.controller; import com.dku.springstudy.dto.common.SuccessResponse; -import com.dku.springstudy.dto.product.request.CreateRequestDto; -import com.dku.springstudy.dto.product.response.CreateResponseDto; +import com.dku.springstudy.dto.product.request.ProductCreateRequestDto; +import com.dku.springstudy.dto.product.response.ProductCreateResponseDto; import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.ProductService; import io.swagger.annotations.Api; @@ -38,15 +38,15 @@ public class ProductController { @ApiResponse(responseCode = "500", description = "파일의 업로드가 실패했거나 파일 확장자가 올바르지 않는 경우") }) @PostMapping("/product") - public ResponseEntity> createPost( + public ResponseEntity> createPost( @AuthenticationPrincipal CustomUserDetails customUserDetails, @Parameter(description = "data 키 값으로 CreateRequestDto의 필드들을 입력한다.") - @Valid @RequestPart("data") CreateRequestDto dto, + @Valid @RequestPart("data") ProductCreateRequestDto dto, @Parameter(description = "file 키 값으로 이미지들을 입력한다.") @RequestPart(value = "file", required = false) List file ) { - CreateResponseDto response = productService.createPost(dto, file, customUserDetails.getId()); + ProductCreateResponseDto response = productService.createPost(dto, file, customUserDetails.getId()); return ResponseEntity .status(HttpStatus.OK) diff --git a/src/main/java/com/dku/springstudy/dto/product/request/CreateRequestDto.java b/src/main/java/com/dku/springstudy/dto/product/request/ProductCreateRequestDto.java similarity index 95% rename from src/main/java/com/dku/springstudy/dto/product/request/CreateRequestDto.java rename to src/main/java/com/dku/springstudy/dto/product/request/ProductCreateRequestDto.java index 781038f..bc1bd05 100644 --- a/src/main/java/com/dku/springstudy/dto/product/request/CreateRequestDto.java +++ b/src/main/java/com/dku/springstudy/dto/product/request/ProductCreateRequestDto.java @@ -11,7 +11,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class CreateRequestDto { +public class ProductCreateRequestDto { @Schema(example = "옷 판매", description = "상품 글 제목") @NotBlank diff --git a/src/main/java/com/dku/springstudy/dto/product/response/CreateResponseDto.java b/src/main/java/com/dku/springstudy/dto/product/response/ProductCreateResponseDto.java similarity index 78% rename from src/main/java/com/dku/springstudy/dto/product/response/CreateResponseDto.java rename to src/main/java/com/dku/springstudy/dto/product/response/ProductCreateResponseDto.java index f604647..80e58e6 100644 --- a/src/main/java/com/dku/springstudy/dto/product/response/CreateResponseDto.java +++ b/src/main/java/com/dku/springstudy/dto/product/response/ProductCreateResponseDto.java @@ -10,7 +10,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter -public class CreateResponseDto { +public class ProductCreateResponseDto { @Schema(example = "https://d-coding.s3.ap-northeast-2.amazonaws.com/67a29351-9339-42b0-ba46-da6584ab93cc.jpg", description = "업로드한 사진들의 url") private List fileUrls; @@ -27,7 +27,7 @@ public class CreateResponseDto { @Schema(example = "1년 사용했습니다.", description = "상품 글 내용") private String content; - public static CreateResponseDto of(List fileUrls, String title, Category category, Integer price, String content) { - return new CreateResponseDto(fileUrls, title, category, price, content); + public static ProductCreateResponseDto of(List fileUrls, String title, Category category, Integer price, String content) { + return new ProductCreateResponseDto(fileUrls, title, category, price, content); } } diff --git a/src/main/java/com/dku/springstudy/service/ProductService.java b/src/main/java/com/dku/springstudy/service/ProductService.java index 48c44db..13636e6 100644 --- a/src/main/java/com/dku/springstudy/service/ProductService.java +++ b/src/main/java/com/dku/springstudy/service/ProductService.java @@ -3,8 +3,8 @@ import com.dku.springstudy.domain.Product; import com.dku.springstudy.domain.User; import com.dku.springstudy.domain.constant.Category; -import com.dku.springstudy.dto.product.request.CreateRequestDto; -import com.dku.springstudy.dto.product.response.CreateResponseDto; +import com.dku.springstudy.dto.product.request.ProductCreateRequestDto; +import com.dku.springstudy.dto.product.response.ProductCreateResponseDto; import com.dku.springstudy.exception.CustomException; import com.dku.springstudy.exception.ErrorCode; import com.dku.springstudy.repository.ProductRepository; @@ -35,7 +35,7 @@ public class ProductService { * @return CreateResponseDto */ @Transactional - public CreateResponseDto createPost(CreateRequestDto dto, List file, Long loginMemberId) { + public ProductCreateResponseDto createPost(ProductCreateRequestDto dto, List file, Long loginMemberId) { User user = userRepository.findById(loginMemberId) .orElseThrow(() -> new CustomException(ErrorCode.USER_ID_NOT_FOUND)); @@ -51,6 +51,6 @@ public CreateResponseDto createPost(CreateRequestDto dto, List fi List fileUrls = s3Service.uploadFiles(file, product); // S3에 이미지 업로드 - return CreateResponseDto.of(fileUrls, product.getTitle(), product.getCategory(), product.getPrice(), product.getContent()); + return ProductCreateResponseDto.of(fileUrls, product.getTitle(), product.getCategory(), product.getPrice(), product.getContent()); } } From 58f7c66ad5d47dc7213f843614354cac245d44e3 Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sat, 11 Feb 2023 04:13:22 +0900 Subject: [PATCH 35/36] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProductController.java | 21 ++++ .../com/dku/springstudy/domain/Product.java | 6 + .../java/com/dku/springstudy/domain/User.java | 5 + .../response/ProductInfoResponseDto.java | 119 ++++++++++++++++++ .../s3/repository/FileRepository.java | 3 + .../springstudy/service/ProductService.java | 10 ++ 6 files changed, 164 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/dto/product/response/ProductInfoResponseDto.java diff --git a/src/main/java/com/dku/springstudy/controller/ProductController.java b/src/main/java/com/dku/springstudy/controller/ProductController.java index fcaceb3..49c909a 100644 --- a/src/main/java/com/dku/springstudy/controller/ProductController.java +++ b/src/main/java/com/dku/springstudy/controller/ProductController.java @@ -3,6 +3,7 @@ import com.dku.springstudy.dto.common.SuccessResponse; import com.dku.springstudy.dto.product.request.ProductCreateRequestDto; import com.dku.springstudy.dto.product.response.ProductCreateResponseDto; +import com.dku.springstudy.dto.product.response.ProductInfoResponseDto; import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.ProductService; import io.swagger.annotations.Api; @@ -52,4 +53,24 @@ public ResponseEntity> createPost( .status(HttpStatus.OK) .body(new SuccessResponse<>(response)); } + + @Operation( + summary = "상품 상세 조회", + description = "상품 아이디를 입력받아 상세 정보를 조회한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "회원 또는 상품의 아이디(PK)가 존재하지 않는 경우"), + }) + @GetMapping("/product/{productId}") + public ResponseEntity> getProductInfo( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable("productId") Long productId + ) { + ProductInfoResponseDto response = productService.getProductInfo(customUserDetails.getId(), productId); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(response)); + } } diff --git a/src/main/java/com/dku/springstudy/domain/Product.java b/src/main/java/com/dku/springstudy/domain/Product.java index 96e2af6..8b0ba6d 100644 --- a/src/main/java/com/dku/springstudy/domain/Product.java +++ b/src/main/java/com/dku/springstudy/domain/Product.java @@ -3,12 +3,15 @@ import com.dku.springstudy.domain.common.BaseTimeEntity; import com.dku.springstudy.domain.constant.Category; import com.dku.springstudy.domain.constant.Status; +import com.dku.springstudy.s3.domain.File; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -44,6 +47,9 @@ public class Product extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Status status; + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) + private List imageUrls = new ArrayList<>(); + @Builder private Product(User user, String title, String content, Integer price, Category category) { this.user = user; diff --git a/src/main/java/com/dku/springstudy/domain/User.java b/src/main/java/com/dku/springstudy/domain/User.java index 47aa0d4..dd89dc8 100644 --- a/src/main/java/com/dku/springstudy/domain/User.java +++ b/src/main/java/com/dku/springstudy/domain/User.java @@ -8,6 +8,8 @@ import lombok.NoArgsConstructor; import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -42,6 +44,9 @@ public class User extends BaseTimeEntity { private String ImgUrl; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List productList = new ArrayList<>(); + @Builder private User(String email, String password, String name, String phoneNumber, String nickname) { this.email = email; diff --git a/src/main/java/com/dku/springstudy/dto/product/response/ProductInfoResponseDto.java b/src/main/java/com/dku/springstudy/dto/product/response/ProductInfoResponseDto.java new file mode 100644 index 0000000..a56a3bc --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/product/response/ProductInfoResponseDto.java @@ -0,0 +1,119 @@ +package com.dku.springstudy.dto.product.response; + +import com.dku.springstudy.domain.Product; +import com.dku.springstudy.domain.User; +import com.dku.springstudy.domain.constant.Category; +import com.dku.springstudy.domain.constant.Status; +import com.dku.springstudy.s3.domain.File; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class ProductInfoResponseDto { + + @Schema(example = "1", description = "상품 아이디") + private Long productId; + + @Schema(example = "자전거 판매", description = "상품 글 제목") + private String title; + + @Schema(example = "자전거 판매합니다.", description = "상품 글 내용") + private String content; + + @Schema(example = "100000", description = "상품 가격") + private Integer price; + + @Schema(example = "EXERCISE", description = "상품 카테고리") + private Category category; + + @Schema(example = "2", description = "상품 좋아요 개수") + private Integer likeCount; + + @Schema(example = "PROGRESS", description = "상품 상태") + private Status status; + + @Schema(description = "상품 사진") + private List imageUrls; + + @Schema(description = "상품 판매자") + private userInfoDto writer; + + @Schema(description = "판매자의 판매 중인 상품") + private List itemList; + + @Schema(example = "true", description = "판매자 여부") + private boolean isWriter; + + @Schema(example = "2023-02-11 01:01:01", description = "상품 글 수정시간") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updatedAt; + + @Schema(description = "판매자 정보") + @Getter + public static class userInfoDto { + + @Schema(example = "1", description = "판매자 아이디(PK)") + private Long userId; + + @Schema(example = "jjeong", description = "판매자 닉네임") + private String nickname; + + @Schema(description = "판매자 프로필 사진") + private String imgUrl; + + public userInfoDto(User user) { + this.userId = user.getId(); + this.nickname = user.getNickname(); + this.imgUrl = user.getImgUrl(); + } + } + + @Schema(description = "판매자가 판매 중인 상품 정보") + @Getter + public static class SubItemInfoDto { + + @Schema(example = "1", description = "상품 아이디") + private Long productId; + + @Schema(description = "") + private String imageUrl; + + @Schema(example = "선풍기 판매", description = "상품 글 제목") + private String title; + + @Schema(example = "50000", description = "상품 글 가격") + private Integer price; + + public SubItemInfoDto(Product product) { + this.productId = product.getId(); + this.imageUrl = product.getImageUrls().get(0).getUrl(); + this.title = product.getTitle(); + this.price = product.getPrice(); + } + } + + public static ProductInfoResponseDto from(User user, Product product) { + return new ProductInfoResponseDto( + product.getId(), + product.getTitle(), + product.getContent(), + product.getPrice(), + product.getCategory(), + product.getLikeCount(), + product.getStatus(), + product.getImageUrls().stream().map(File::getUrl).collect(Collectors.toUnmodifiableList()), + new userInfoDto(product.getUser()), + product.getUser().getProductList().stream().map(SubItemInfoDto::new).collect(Collectors.toUnmodifiableList()), + product.getUser().getId().equals(user.getId()), + product.getModifiedAt() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/dku/springstudy/s3/repository/FileRepository.java b/src/main/java/com/dku/springstudy/s3/repository/FileRepository.java index 3dce164..f3f439f 100644 --- a/src/main/java/com/dku/springstudy/s3/repository/FileRepository.java +++ b/src/main/java/com/dku/springstudy/s3/repository/FileRepository.java @@ -3,5 +3,8 @@ import com.dku.springstudy.s3.domain.File; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface FileRepository extends JpaRepository { + List findByProduct_Id(Long productId); } diff --git a/src/main/java/com/dku/springstudy/service/ProductService.java b/src/main/java/com/dku/springstudy/service/ProductService.java index 13636e6..a8ec865 100644 --- a/src/main/java/com/dku/springstudy/service/ProductService.java +++ b/src/main/java/com/dku/springstudy/service/ProductService.java @@ -5,10 +5,12 @@ import com.dku.springstudy.domain.constant.Category; import com.dku.springstudy.dto.product.request.ProductCreateRequestDto; import com.dku.springstudy.dto.product.response.ProductCreateResponseDto; +import com.dku.springstudy.dto.product.response.ProductInfoResponseDto; import com.dku.springstudy.exception.CustomException; import com.dku.springstudy.exception.ErrorCode; import com.dku.springstudy.repository.ProductRepository; import com.dku.springstudy.repository.UserRepository; +import com.dku.springstudy.s3.repository.FileRepository; import com.dku.springstudy.s3.service.S3Service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -22,6 +24,7 @@ @Service public class ProductService { + private final FileRepository fileRepository; private final ProductRepository productRepository; private final UserRepository userRepository; private final S3Service s3Service; @@ -53,4 +56,11 @@ public ProductCreateResponseDto createPost(ProductCreateRequestDto dto, List new CustomException(ErrorCode.USER_ID_NOT_FOUND)); + Product product = productRepository.findById(productId).orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_ID_NOT_FOUND)); + return ProductInfoResponseDto.from(user, product); + } } From afdccb4007024e8d310f64fb2e1971485ba565ed Mon Sep 17 00:00:00 2001 From: sujeong11 Date: Sun, 12 Feb 2023 12:21:11 +0900 Subject: [PATCH 36/36] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=EA=B3=BC?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AuthController.java | 65 +++++++++++++++++++ .../controller/UserController.java | 39 ----------- .../request/LoginRequestDto.java | 2 +- .../request/SignUpRequestDto.java | 2 +- .../response/LoginResponseDto.java | 2 +- .../response/SignUpResponseDto.java | 2 +- .../com/dku/springstudy/dto/user/UserDto.java | 20 ------ .../dku/springstudy/service/AuthService.java | 58 +++++++++++++++++ .../dku/springstudy/service/UserService.java | 42 +----------- 9 files changed, 128 insertions(+), 104 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/controller/AuthController.java rename src/main/java/com/dku/springstudy/dto/{user => auth}/request/LoginRequestDto.java (95%) rename src/main/java/com/dku/springstudy/dto/{user => auth}/request/SignUpRequestDto.java (97%) rename src/main/java/com/dku/springstudy/dto/{user => auth}/response/LoginResponseDto.java (92%) rename src/main/java/com/dku/springstudy/dto/{user => auth}/response/SignUpResponseDto.java (90%) delete mode 100644 src/main/java/com/dku/springstudy/dto/user/UserDto.java create mode 100644 src/main/java/com/dku/springstudy/service/AuthService.java diff --git a/src/main/java/com/dku/springstudy/controller/AuthController.java b/src/main/java/com/dku/springstudy/controller/AuthController.java new file mode 100644 index 0000000..71336e5 --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/AuthController.java @@ -0,0 +1,65 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.dto.common.SuccessResponse; +import com.dku.springstudy.dto.auth.request.LoginRequestDto; +import com.dku.springstudy.dto.auth.request.SignUpRequestDto; +import com.dku.springstudy.dto.auth.response.LoginResponseDto; +import com.dku.springstudy.dto.auth.response.SignUpResponseDto; +import com.dku.springstudy.service.AuthService; +import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +@Api(tags = "인증 API") +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class AuthController { + + private final AuthService authService; + + @Operation( + summary = "회원가입", + description = "사용자에게 회원가입에 필요한 데이터를 입력받고, 가입되지 않는 이메일이라면 회원가입을 진행한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "409", description = "이미 가입된 이메일로 회원가입을 시도하는 경우") + }) + @PostMapping("/sign-up") + public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { + SignUpResponseDto userId = authService.signUp(signUpRequestDto); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(userId)); + } + + @Operation( + summary = "로그인", + description = "사용자로부터 입력받은 아이디와 비밀번호가 존재한다면 access-token과 refresh-token을 발급한다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "이메일(ID)이 존재하지 않는 경우"), + @ApiResponse(responseCode = "401", description = "비밀번호가 일치하지 않는 경우") + }) + @PostMapping("/login") + public ResponseEntity> login(@Valid @RequestBody LoginRequestDto loginRequestDto) { + LoginResponseDto tokens = authService.login(loginRequestDto); + + return ResponseEntity + .status(HttpStatus.OK) + .body(new SuccessResponse<>(tokens)); + } +} diff --git a/src/main/java/com/dku/springstudy/controller/UserController.java b/src/main/java/com/dku/springstudy/controller/UserController.java index b1de53c..e600f6d 100644 --- a/src/main/java/com/dku/springstudy/controller/UserController.java +++ b/src/main/java/com/dku/springstudy/controller/UserController.java @@ -1,10 +1,6 @@ package com.dku.springstudy.controller; import com.dku.springstudy.dto.common.SuccessResponse; -import com.dku.springstudy.dto.user.request.LoginRequestDto; -import com.dku.springstudy.dto.user.response.LoginResponseDto; -import com.dku.springstudy.dto.user.request.SignUpRequestDto; -import com.dku.springstudy.dto.user.response.SignUpResponseDto; import com.dku.springstudy.dto.user.response.UserUpdateResponseDto; import com.dku.springstudy.security.CustomUserDetails; import com.dku.springstudy.service.UserService; @@ -29,41 +25,6 @@ public class UserController { private final UserService userService; - @Operation( - summary = "회원가입", - description = "사용자에게 회원가입에 필요한 데이터를 입력받고, 가입되지 않는 이메일이라면 회원가입을 진행한다." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Success"), - @ApiResponse(responseCode = "409", description = "이미 가입된 이메일로 회원가입을 시도하는 경우") - }) - @PostMapping("/sign-up") - public ResponseEntity> signUp(@Valid @RequestBody SignUpRequestDto signUpRequestDto) { - SignUpResponseDto userId = userService.signUp(signUpRequestDto); - - return ResponseEntity - .status(HttpStatus.OK) - .body(new SuccessResponse<>(userId)); - } - - @Operation( - summary = "로그인", - description = "사용자로부터 입력받은 아이디와 비밀번호가 존재한다면 access-token과 refresh-token을 발급한다." - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Success"), - @ApiResponse(responseCode = "404", description = "이메일(ID)이 존재하지 않는 경우"), - @ApiResponse(responseCode = "401", description = "비밀번호가 일치하지 않는 경우") - }) - @PostMapping("/login") - public ResponseEntity> login(@Valid @RequestBody LoginRequestDto loginRequestDto) { - LoginResponseDto tokens = userService.login(loginRequestDto); - - return ResponseEntity - .status(HttpStatus.OK) - .body(new SuccessResponse<>(tokens)); - } - @Operation( summary = "회원 프로필 수정", description = "사용자로부터 닉네임과 사진을 입력받아 회원의 정보를 수정한다." diff --git a/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java b/src/main/java/com/dku/springstudy/dto/auth/request/LoginRequestDto.java similarity index 95% rename from src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java rename to src/main/java/com/dku/springstudy/dto/auth/request/LoginRequestDto.java index 35c2847..af099c0 100644 --- a/src/main/java/com/dku/springstudy/dto/user/request/LoginRequestDto.java +++ b/src/main/java/com/dku/springstudy/dto/auth/request/LoginRequestDto.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.dto.user.request; +package com.dku.springstudy.dto.auth.request; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; diff --git a/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java b/src/main/java/com/dku/springstudy/dto/auth/request/SignUpRequestDto.java similarity index 97% rename from src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java rename to src/main/java/com/dku/springstudy/dto/auth/request/SignUpRequestDto.java index 2969ee8..54ecc11 100644 --- a/src/main/java/com/dku/springstudy/dto/user/request/SignUpRequestDto.java +++ b/src/main/java/com/dku/springstudy/dto/auth/request/SignUpRequestDto.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.dto.user.request; +package com.dku.springstudy.dto.auth.request; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; diff --git a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java b/src/main/java/com/dku/springstudy/dto/auth/response/LoginResponseDto.java similarity index 92% rename from src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java rename to src/main/java/com/dku/springstudy/dto/auth/response/LoginResponseDto.java index 999bbdf..50f95e4 100644 --- a/src/main/java/com/dku/springstudy/dto/user/response/LoginResponseDto.java +++ b/src/main/java/com/dku/springstudy/dto/auth/response/LoginResponseDto.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.dto.user.response; +package com.dku.springstudy.dto.auth.response; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; diff --git a/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java b/src/main/java/com/dku/springstudy/dto/auth/response/SignUpResponseDto.java similarity index 90% rename from src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java rename to src/main/java/com/dku/springstudy/dto/auth/response/SignUpResponseDto.java index ca20bfe..3bdacb0 100644 --- a/src/main/java/com/dku/springstudy/dto/user/response/SignUpResponseDto.java +++ b/src/main/java/com/dku/springstudy/dto/auth/response/SignUpResponseDto.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.dto.user.response; +package com.dku.springstudy.dto.auth.response; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; diff --git a/src/main/java/com/dku/springstudy/dto/user/UserDto.java b/src/main/java/com/dku/springstudy/dto/user/UserDto.java deleted file mode 100644 index ef949a1..0000000 --- a/src/main/java/com/dku/springstudy/dto/user/UserDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.dku.springstudy.dto.user; - -import com.dku.springstudy.domain.constant.Role; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@Getter -public class UserDto { - - private Long id; - private String email; - private String password; - private String name; - private String phoneNumber; - private String nickname; - private int status; - private Role role; -} diff --git a/src/main/java/com/dku/springstudy/service/AuthService.java b/src/main/java/com/dku/springstudy/service/AuthService.java new file mode 100644 index 0000000..45d3f92 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/AuthService.java @@ -0,0 +1,58 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.User; +import com.dku.springstudy.dto.auth.request.LoginRequestDto; +import com.dku.springstudy.dto.auth.request.SignUpRequestDto; +import com.dku.springstudy.dto.auth.response.LoginResponseDto; +import com.dku.springstudy.dto.auth.response.SignUpResponseDto; +import com.dku.springstudy.exception.CustomException; +import com.dku.springstudy.exception.ErrorCode; +import com.dku.springstudy.repository.UserRepository; +import com.dku.springstudy.security.jwt.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class AuthService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final JwtTokenProvider jwtTokenProvider; + + @Transactional + public SignUpResponseDto signUp(SignUpRequestDto signUpRequestDto) { + + if (userRepository.existsByEmail(signUpRequestDto.getEmail())) { + throw new CustomException(ErrorCode.USER_EMAIL_Duplication); + } + + User user = User.builder() + .email(signUpRequestDto.getEmail()) + .name(signUpRequestDto.getName()) + .password(passwordEncoder.encode(signUpRequestDto.getPassword())) + .phoneNumber(signUpRequestDto.getPhoneNumber()) + .nickname(signUpRequestDto.getNickname()) + .build(); + + Long id = userRepository.save(user).getId(); + + return SignUpResponseDto.of(id); + } + + public LoginResponseDto login(LoginRequestDto loginRequestDto) { + User user = userRepository.findByEmail(loginRequestDto.getEmail()) + .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); + + if(passwordEncoder.matches(loginRequestDto.getPassword(), user.getPassword())) { + String loginAccessToken = jwtTokenProvider.createLoginAccessToken(user.getEmail(), user.getRole().name()); + String loginRefreshToken = jwtTokenProvider.createLoginRefreshToken(user.getEmail()); + return LoginResponseDto.of(loginAccessToken, loginRefreshToken); + } else { + throw new CustomException(ErrorCode.USER_PASSWORD_NOT_MATCHES); + } + } +} diff --git a/src/main/java/com/dku/springstudy/service/UserService.java b/src/main/java/com/dku/springstudy/service/UserService.java index aef6b55..749ffe2 100644 --- a/src/main/java/com/dku/springstudy/service/UserService.java +++ b/src/main/java/com/dku/springstudy/service/UserService.java @@ -1,18 +1,12 @@ package com.dku.springstudy.service; import com.dku.springstudy.domain.User; -import com.dku.springstudy.dto.user.request.LoginRequestDto; -import com.dku.springstudy.dto.user.response.LoginResponseDto; -import com.dku.springstudy.dto.user.request.SignUpRequestDto; -import com.dku.springstudy.dto.user.response.SignUpResponseDto; import com.dku.springstudy.dto.user.response.UserUpdateResponseDto; import com.dku.springstudy.exception.CustomException; import com.dku.springstudy.exception.ErrorCode; import com.dku.springstudy.s3.service.S3Service; -import com.dku.springstudy.security.jwt.JwtTokenProvider; import com.dku.springstudy.repository.UserRepository; import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -23,42 +17,8 @@ public class UserService { private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - private final JwtTokenProvider jwtTokenProvider; - private final S3Service s3Service; - - @Transactional - public SignUpResponseDto signUp(SignUpRequestDto signUpRequestDto) { - - if (userRepository.existsByEmail(signUpRequestDto.getEmail())) { - throw new CustomException(ErrorCode.USER_EMAIL_Duplication); - } - - User user = User.builder() - .email(signUpRequestDto.getEmail()) - .name(signUpRequestDto.getName()) - .password(passwordEncoder.encode(signUpRequestDto.getPassword())) - .phoneNumber(signUpRequestDto.getPhoneNumber()) - .nickname(signUpRequestDto.getNickname()) - .build(); - - Long id = userRepository.save(user).getId(); - return SignUpResponseDto.of(id); - } - - public LoginResponseDto login(LoginRequestDto loginRequestDto) { - User user = userRepository.findByEmail(loginRequestDto.getEmail()) - .orElseThrow(() -> new CustomException(ErrorCode.USER_EMAIL_NOT_FOUND)); - - if(passwordEncoder.matches(loginRequestDto.getPassword(), user.getPassword())) { - String loginAccessToken = jwtTokenProvider.createLoginAccessToken(user.getEmail(), user.getRole().name()); - String loginRefreshToken = jwtTokenProvider.createLoginRefreshToken(user.getEmail()); - return LoginResponseDto.of(loginAccessToken, loginRefreshToken); - } else { - throw new CustomException(ErrorCode.USER_PASSWORD_NOT_MATCHES); - } - } + private final S3Service s3Service; @Transactional public UserUpdateResponseDto update(Long loginMemberId, String nickname, MultipartFile file) {