From b6f5b1c3e3592d4c93e7b442a6da43748abdf4ca Mon Sep 17 00:00:00 2001 From: dongsuu <78404073+dongsuu@users.noreply.github.com> Date: Sat, 21 Jan 2023 20:07:19 +0900 Subject: [PATCH 01/11] 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 9967a3f2e2b79f2522a9b7f0117b41d0fe07d3fb Mon Sep 17 00:00:00 2001 From: suudh99 Date: Sat, 28 Jan 2023 00:59:06 +0900 Subject: [PATCH 02/11] add entity --- build.gradle | 4 +- .../controller/MemberController.java | 15 +++ .../com/dku/springstudy/domain/ImageFile.java | 27 ++++++ .../java/com/dku/springstudy/domain/Item.java | 53 +++++++++++ .../com/dku/springstudy/domain/ItemLike.java | 30 ++++++ .../com/dku/springstudy/domain/Member.java | 32 +++++++ .../com/dku/springstudy/enums/Category.java | 6 ++ .../com/dku/springstudy/enums/ItemStatus.java | 11 +++ .../repository/ItemLikeRepository.java | 8 ++ .../repository/ItemRepository.java | 7 ++ .../repository/MemberRepository.java | 13 +++ .../springstudy/service/MemberService.java | 18 ++++ src/main/resources/application.properties | 1 - src/main/resources/application.yml | 15 +++ .../dku/springstudy/domain/MemberTest.java | 93 +++++++++++++++++++ 15 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/controller/MemberController.java create mode 100644 src/main/java/com/dku/springstudy/domain/ImageFile.java create mode 100644 src/main/java/com/dku/springstudy/domain/Item.java create mode 100644 src/main/java/com/dku/springstudy/domain/ItemLike.java create mode 100644 src/main/java/com/dku/springstudy/domain/Member.java create mode 100644 src/main/java/com/dku/springstudy/enums/Category.java create mode 100644 src/main/java/com/dku/springstudy/enums/ItemStatus.java create mode 100644 src/main/java/com/dku/springstudy/repository/ItemLikeRepository.java create mode 100644 src/main/java/com/dku/springstudy/repository/ItemRepository.java create mode 100644 src/main/java/com/dku/springstudy/repository/MemberRepository.java create mode 100644 src/main/java/com/dku/springstudy/service/MemberService.java delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/com/dku/springstudy/domain/MemberTest.java diff --git a/build.gradle b/build.gradle index 15b77ef..c4fac23 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.0.1' + id 'org.springframework.boot' version '2.7.6' id 'io.spring.dependency-management' version '1.1.0' } group = 'com.dku' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '17' +sourceCompatibility = '11' configurations { compileOnly { diff --git a/src/main/java/com/dku/springstudy/controller/MemberController.java b/src/main/java/com/dku/springstudy/controller/MemberController.java new file mode 100644 index 0000000..b49dfcc --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/MemberController.java @@ -0,0 +1,15 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequiredArgsConstructor +@RequestMapping("/member") +public class MemberController { + + private final MemberService memberService; + +} diff --git a/src/main/java/com/dku/springstudy/domain/ImageFile.java b/src/main/java/com/dku/springstudy/domain/ImageFile.java new file mode 100644 index 0000000..80f88c8 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/ImageFile.java @@ -0,0 +1,27 @@ +package com.dku.springstudy.domain; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ImageFile { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String imageUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + private Item item; + + public void createImageFile(String imageUrl, Item item){ + this.imageUrl = imageUrl; + this.item = item; + } +} diff --git a/src/main/java/com/dku/springstudy/domain/Item.java b/src/main/java/com/dku/springstudy/domain/Item.java new file mode 100644 index 0000000..9a7c132 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/Item.java @@ -0,0 +1,53 @@ +package com.dku.springstudy.domain; + +import com.dku.springstudy.enums.Category; +import com.dku.springstudy.enums.ItemStatus; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Item { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "item_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + private String title; + private String content; + private int price; + + @OneToMany(mappedBy = "item") + private List images = new ArrayList<>(); + + @Enumerated(value = EnumType.STRING) + private ItemStatus status; + + @Enumerated(EnumType.STRING) + private Category category; + + + public void createItem(Member member, String title, String content, int price, Category category){ + this.member = member; + this.title = title; + this.content = content; + this.price = price; + this.category = category; + this.status = ItemStatus.SELLING; // 상품 처음 등록시에는 SELLING(판매중) 상태 + } + + public void addImage(ImageFile image){ + images.add(image); + } + +} diff --git a/src/main/java/com/dku/springstudy/domain/ItemLike.java b/src/main/java/com/dku/springstudy/domain/ItemLike.java new file mode 100644 index 0000000..5c9356a --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/ItemLike.java @@ -0,0 +1,30 @@ +package com.dku.springstudy.domain; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "ITEM_LIKE") +public class ItemLike { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + public void createItemLike(Item item, Member member){ + this.item = item; + this.member = member; + } +} diff --git a/src/main/java/com/dku/springstudy/domain/Member.java b/src/main/java/com/dku/springstudy/domain/Member.java new file mode 100644 index 0000000..01aa59c --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/Member.java @@ -0,0 +1,32 @@ +package com.dku.springstudy.domain; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_id") + private Long id; + + private String email; + private String password; + private String username; + private String phone; + private String nickname; + + public void createMember(String email, String password, String username, String phone, String nickname){ + this.email = email; + this.password = password; + this.username = username; + this.phone = phone; + this.nickname = nickname; + } + +} diff --git a/src/main/java/com/dku/springstudy/enums/Category.java b/src/main/java/com/dku/springstudy/enums/Category.java new file mode 100644 index 0000000..4070129 --- /dev/null +++ b/src/main/java/com/dku/springstudy/enums/Category.java @@ -0,0 +1,6 @@ +package com.dku.springstudy.enums; + +public enum Category { + DIGITAL, ELECTRONIC, FURNITURE_INTERIOR, KIDS, LIFE_FOOD, BOOK_KIDS, SPORTS, WOMAN_MERCHANDISE, WOMAN_CLOTHES, MAN_MERCHANDISE_CLOTHES, + GAME_HOBBY, BEAUTY_HAIR, PET, BOOK_TICKET_MUSIC, PLANTS, USED_THINGS, USED_CAR +} diff --git a/src/main/java/com/dku/springstudy/enums/ItemStatus.java b/src/main/java/com/dku/springstudy/enums/ItemStatus.java new file mode 100644 index 0000000..855ae44 --- /dev/null +++ b/src/main/java/com/dku/springstudy/enums/ItemStatus.java @@ -0,0 +1,11 @@ +package com.dku.springstudy.enums; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ItemStatus { + SELLING, RESERVATION, COMPLETE +} diff --git a/src/main/java/com/dku/springstudy/repository/ItemLikeRepository.java b/src/main/java/com/dku/springstudy/repository/ItemLikeRepository.java new file mode 100644 index 0000000..e8b424a --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/ItemLikeRepository.java @@ -0,0 +1,8 @@ +package com.dku.springstudy.repository; + + +import com.dku.springstudy.domain.ItemLike; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ItemLikeRepository extends JpaRepository { +} diff --git a/src/main/java/com/dku/springstudy/repository/ItemRepository.java b/src/main/java/com/dku/springstudy/repository/ItemRepository.java new file mode 100644 index 0000000..12e70b8 --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/ItemRepository.java @@ -0,0 +1,7 @@ +package com.dku.springstudy.repository; + +import com.dku.springstudy.domain.Item; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ItemRepository extends JpaRepository { +} diff --git a/src/main/java/com/dku/springstudy/repository/MemberRepository.java b/src/main/java/com/dku/springstudy/repository/MemberRepository.java new file mode 100644 index 0000000..5b3d8dc --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/MemberRepository.java @@ -0,0 +1,13 @@ +package com.dku.springstudy.repository; + +import com.dku.springstudy.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + Optional findByUsername(String username); + + Optional findByEmail(String email); + +} diff --git a/src/main/java/com/dku/springstudy/service/MemberService.java b/src/main/java/com/dku/springstudy/service/MemberService.java new file mode 100644 index 0000000..a2a8412 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/MemberService.java @@ -0,0 +1,18 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.Member; +import com.dku.springstudy.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MemberService { + private MemberRepository memberRepository; + + public Long join(Member member){ + memberRepository.save(member); + return member.getId(); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..6bd57b1 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,15 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/karrot?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + username: root + password: dlehdgus12 + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 5 + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + show_sql: true + format_sql: true \ No newline at end of file diff --git a/src/test/java/com/dku/springstudy/domain/MemberTest.java b/src/test/java/com/dku/springstudy/domain/MemberTest.java new file mode 100644 index 0000000..b4c1605 --- /dev/null +++ b/src/test/java/com/dku/springstudy/domain/MemberTest.java @@ -0,0 +1,93 @@ +package com.dku.springstudy.domain; + +import com.dku.springstudy.enums.Category; +import com.dku.springstudy.repository.ItemLikeRepository; +import com.dku.springstudy.repository.ItemRepository; +import com.dku.springstudy.repository.MemberRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +class EntityTest { + @PersistenceContext + EntityManager em; + @Autowired MemberRepository memberRepository; + @Autowired + ItemRepository itemRepository; + + @Autowired + ItemLikeRepository itemLikeRepository; + + @Test + public void createMember(){ + //given + Member member = new Member(); + member.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍"); + memberRepository.save(member); + + //when + Member findMember = memberRepository.findByUsername("동현").get(); + + //then + assertThat(findMember).isEqualTo(member); + } + + @Test + public void createItem(){ + //given + Member member = new Member(); + member.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍"); + memberRepository.save(member); + + //when + Item item = new Item(); + item.createItem(member, "title", "content", 10000, Category.ELECTRONIC); + itemRepository.save(item); + + //then + assertThat(item.getMember()).isEqualTo(member); + + } + + @Test + public void createLike(){ + //given + Member member1 = new Member(); + member1.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍"); + memberRepository.save(member1); + Member member2 = new Member(); + member2.createMember("qwe123@gmail.com", "qwe123", "dong", "010-1234-5678", "공공공"); + memberRepository.save(member2); + + Item item = new Item(); + item.createItem(member1, "title", "content", 10000, Category.ELECTRONIC); + itemRepository.save(item); + + //when + ItemLike itemLike1 = new ItemLike(); + itemLike1.createItemLike(item, member1); + ItemLike itemLike2 = new ItemLike(); + itemLike2.createItemLike(item, member2); + itemLikeRepository.save(itemLike1); + itemLikeRepository.save(itemLike2); + + //then + assertThat(itemLike1.getMember().getUsername()).isEqualTo("동현"); + assertThat(itemLike2.getMember().getUsername()).isEqualTo("dong"); + assertThat(itemLike1.getItem().getTitle()).isEqualTo("title"); + assertThat(itemLike2.getItem().getContent()).isEqualTo("content"); + } + +} \ No newline at end of file From 6895e55c3fe68bda3b3183d536915331f399ddd0 Mon Sep 17 00:00:00 2001 From: suudh99 Date: Sun, 29 Jan 2023 18:51:27 +0900 Subject: [PATCH 03/11] add login with jwt and reissue access token --- build.gradle | 7 + .../com/dku/springstudy/SpringConfig.java | 12 ++ .../controller/MemberController.java | 45 ++++++- .../com/dku/springstudy/domain/Member.java | 62 +++++++-- .../domain/token/RefreshToken.java | 22 ++++ .../com/dku/springstudy/dto/LoginDto.java | 17 +++ .../com/dku/springstudy/dto/TokenDto.java | 19 +++ .../java/com/dku/springstudy/enums/Role.java | 10 ++ .../{ => jpa}/ItemLikeRepository.java | 2 +- .../repository/{ => jpa}/ItemRepository.java | 2 +- .../{ => jpa}/MemberRepository.java | 4 +- .../redis/RefreshTokenRepository.java | 7 + .../security/JwtAuthenticationFilter.java | 57 +++++++++ .../security/JwtTokenProvider.java | 120 ++++++++++++++++++ .../security/config/SecurityConfig.java | 43 +++++++ .../service/CustomUserDetailsService.java | 35 +++++ .../springstudy/service/MemberService.java | 58 ++++++++- src/main/resources/application.yml | 17 ++- .../dku/springstudy/domain/MemberTest.java | 12 +- 19 files changed, 525 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/SpringConfig.java create mode 100644 src/main/java/com/dku/springstudy/domain/token/RefreshToken.java create mode 100644 src/main/java/com/dku/springstudy/dto/LoginDto.java create mode 100644 src/main/java/com/dku/springstudy/dto/TokenDto.java create mode 100644 src/main/java/com/dku/springstudy/enums/Role.java rename src/main/java/com/dku/springstudy/repository/{ => jpa}/ItemLikeRepository.java (80%) rename src/main/java/com/dku/springstudy/repository/{ => jpa}/ItemRepository.java (79%) rename src/main/java/com/dku/springstudy/repository/{ => jpa}/MemberRepository.java (73%) create mode 100644 src/main/java/com/dku/springstudy/repository/redis/RefreshTokenRepository.java create mode 100644 src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/dku/springstudy/security/JwtTokenProvider.java create mode 100644 src/main/java/com/dku/springstudy/security/config/SecurityConfig.java create mode 100644 src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java diff --git a/build.gradle b/build.gradle index c4fac23..cbb4f17 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,13 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' + // jwt + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE' // redis + + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/dku/springstudy/SpringConfig.java b/src/main/java/com/dku/springstudy/SpringConfig.java new file mode 100644 index 0000000..ac99f57 --- /dev/null +++ b/src/main/java/com/dku/springstudy/SpringConfig.java @@ -0,0 +1,12 @@ +package com.dku.springstudy; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import static com.dku.springstudy.SpringConfig.BASE_PACKAGE; + +@Configuration +@EnableJpaRepositories(basePackages = {BASE_PACKAGE}) +public class SpringConfig { + static final String BASE_PACKAGE = "com.dku.springstudy.repository.jpa"; +} diff --git a/src/main/java/com/dku/springstudy/controller/MemberController.java b/src/main/java/com/dku/springstudy/controller/MemberController.java index b49dfcc..1b11dc7 100644 --- a/src/main/java/com/dku/springstudy/controller/MemberController.java +++ b/src/main/java/com/dku/springstudy/controller/MemberController.java @@ -1,15 +1,54 @@ package com.dku.springstudy.controller; +import com.dku.springstudy.domain.Member; +import com.dku.springstudy.dto.LoginDto; +import com.dku.springstudy.dto.TokenDto; +import com.dku.springstudy.enums.Role; +import com.dku.springstudy.repository.jpa.MemberRepository; import com.dku.springstudy.service.MemberService; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; -@Controller +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +@Slf4j +@RestController @RequiredArgsConstructor @RequestMapping("/member") public class MemberController { + private final MemberRepository memberRepository; private final MemberService memberService; + @PostMapping("/login") + public TokenDto login(@RequestBody LoginDto loginDto){ + log.info("login request={}", loginDto); + return memberService.login(loginDto.getEmail(), loginDto.getPassword()); + } + @PostMapping("/reissue") + public ResponseEntity reissue(@RequestBody TokenDto tokenDto){ + log.info("Reissue={}", tokenDto); + return memberService.reissue(tokenDto); + } + + @GetMapping("/user") + public ResponseEntity accessUser(HttpServletRequest request){ + return ResponseEntity.ok(request); + } + + @GetMapping("/admin") + public String accessAdmin(){ + return "ok"; + } + + @PostConstruct + public void init(){ + Member member = Member.createMember("test@naver.com", "1234", "lee", "01010", "nickname", Role.USER); + memberRepository.save(member); + } + + } diff --git a/src/main/java/com/dku/springstudy/domain/Member.java b/src/main/java/com/dku/springstudy/domain/Member.java index 01aa59c..dba4ebd 100644 --- a/src/main/java/com/dku/springstudy/domain/Member.java +++ b/src/main/java/com/dku/springstudy/domain/Member.java @@ -1,15 +1,23 @@ package com.dku.springstudy.domain; +import com.dku.springstudy.enums.Role; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Member { +public class Member implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "member_id") @@ -17,16 +25,54 @@ public class Member { private String email; private String password; - private String username; + private String name; private String phone; private String nickname; - public void createMember(String email, String password, String username, String phone, String nickname){ - this.email = email; - this.password = password; - this.username = username; - this.phone = phone; - this.nickname = nickname; + // 권한 정보(Role) + @Enumerated(EnumType.STRING) + private Role role; + + public static Member createMember(String email, String password, String name, String phone, String nickname, Role role){ + Member member = new Member(); + member.email = email; + member.password = password; + member.name = name; + member.phone = phone; + member.nickname = nickname; + member.role = role; + return member; + } + + @Override + public Collection getAuthorities() { + String s = this.getRole().toString(); + SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(s); + return List.of(simpleGrantedAuthority); + } + + @Override + public String getUsername() { + return this.getEmail(); + } + + @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/domain/token/RefreshToken.java b/src/main/java/com/dku/springstudy/domain/token/RefreshToken.java new file mode 100644 index 0000000..42681cd --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/token/RefreshToken.java @@ -0,0 +1,22 @@ +package com.dku.springstudy.domain.token; + +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@RedisHash(value = "refreshToken", timeToLive = 3600) +@Getter +public class RefreshToken { + + @Id + private String memberEmail; + private String refreshToken; + + public RefreshToken(String memberEmail, String refreshToken){ + this.memberEmail = memberEmail; + this.refreshToken = refreshToken; + } + + + +} diff --git a/src/main/java/com/dku/springstudy/dto/LoginDto.java b/src/main/java/com/dku/springstudy/dto/LoginDto.java new file mode 100644 index 0000000..c23304e --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/LoginDto.java @@ -0,0 +1,17 @@ +package com.dku.springstudy.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class LoginDto { + + private String email; + private String password; + + + +} diff --git a/src/main/java/com/dku/springstudy/dto/TokenDto.java b/src/main/java/com/dku/springstudy/dto/TokenDto.java new file mode 100644 index 0000000..3f28572 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/TokenDto.java @@ -0,0 +1,19 @@ +package com.dku.springstudy.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +@AllArgsConstructor +public class TokenDto { + + private String grantType; // Bearer + private String accessToken; + private String refreshToken; + + public TokenDto(){ + } + +} diff --git a/src/main/java/com/dku/springstudy/enums/Role.java b/src/main/java/com/dku/springstudy/enums/Role.java new file mode 100644 index 0000000..2c7cbd8 --- /dev/null +++ b/src/main/java/com/dku/springstudy/enums/Role.java @@ -0,0 +1,10 @@ +package com.dku.springstudy.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Role { + USER, ADMIN +} diff --git a/src/main/java/com/dku/springstudy/repository/ItemLikeRepository.java b/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java similarity index 80% rename from src/main/java/com/dku/springstudy/repository/ItemLikeRepository.java rename to src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java index e8b424a..6c9dd4a 100644 --- a/src/main/java/com/dku/springstudy/repository/ItemLikeRepository.java +++ b/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.repository; +package com.dku.springstudy.repository.jpa; import com.dku.springstudy.domain.ItemLike; diff --git a/src/main/java/com/dku/springstudy/repository/ItemRepository.java b/src/main/java/com/dku/springstudy/repository/jpa/ItemRepository.java similarity index 79% rename from src/main/java/com/dku/springstudy/repository/ItemRepository.java rename to src/main/java/com/dku/springstudy/repository/jpa/ItemRepository.java index 12e70b8..c39f9c3 100644 --- a/src/main/java/com/dku/springstudy/repository/ItemRepository.java +++ b/src/main/java/com/dku/springstudy/repository/jpa/ItemRepository.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.repository; +package com.dku.springstudy.repository.jpa; import com.dku.springstudy.domain.Item; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/dku/springstudy/repository/MemberRepository.java b/src/main/java/com/dku/springstudy/repository/jpa/MemberRepository.java similarity index 73% rename from src/main/java/com/dku/springstudy/repository/MemberRepository.java rename to src/main/java/com/dku/springstudy/repository/jpa/MemberRepository.java index 5b3d8dc..a0c381b 100644 --- a/src/main/java/com/dku/springstudy/repository/MemberRepository.java +++ b/src/main/java/com/dku/springstudy/repository/jpa/MemberRepository.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.repository; +package com.dku.springstudy.repository.jpa; import com.dku.springstudy.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; @@ -6,7 +6,7 @@ import java.util.Optional; public interface MemberRepository extends JpaRepository { - Optional findByUsername(String username); + Optional findByName(String name); Optional findByEmail(String email); diff --git a/src/main/java/com/dku/springstudy/repository/redis/RefreshTokenRepository.java b/src/main/java/com/dku/springstudy/repository/redis/RefreshTokenRepository.java new file mode 100644 index 0000000..5ef4915 --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/redis/RefreshTokenRepository.java @@ -0,0 +1,7 @@ +package com.dku.springstudy.repository.redis; + +import com.dku.springstudy.domain.token.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RefreshTokenRepository extends JpaRepository { +} diff --git a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..709fbac --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java @@ -0,0 +1,57 @@ +package com.dku.springstudy.security; + +import io.jsonwebtoken.ExpiredJwtException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; +import org.springframework.web.filter.OncePerRequestFilter; + +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 +@RequiredArgsConstructor +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try{ + String token = getTokenFromHeader(request); + String path = request.getServletPath(); + if(path.startsWith("/member/reissue")){ + filterChain.doFilter(request,response); + } else { + if(token != null && jwtTokenProvider.validateToken(token)){ + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); + } + } catch (ExpiredJwtException e){ + log.info("Expired JWT token", e); + } + } + + private String getTokenFromHeader(HttpServletRequest request) { + HttpServletRequest req = request; + String bearerToken = req.getHeader("Authorization"); + if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")){ + return bearerToken.substring(7); + } + return null; + } +} diff --git a/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java new file mode 100644 index 0000000..abe025a --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java @@ -0,0 +1,120 @@ +package com.dku.springstudy.security; + +import com.dku.springstudy.domain.token.RefreshToken; +import com.dku.springstudy.dto.TokenDto; +import com.dku.springstudy.repository.redis.RefreshTokenRepository; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class JwtTokenProvider { + private final Key key; + + private final RefreshTokenRepository refreshTokenRepository; + + @Autowired + public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, RefreshTokenRepository refreshTokenRepository){ + byte[] bytes = Decoders.BASE64.decode(secretKey); + this.key = Keys.hmacShaKeyFor(bytes); + this.refreshTokenRepository = refreshTokenRepository; + } + + // authentication 객체를 기반으로 access token, refresh token 생성 + public TokenDto generateToken(Authentication authentication){ + // 권한 정보 + String authorities = authentication.getAuthorities() + .stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + long now = new Date().getTime(); + + // access token 생성 + Date accessTokenExpDate = new Date(now + 30000); // 발급시간으로부터 1일 + String accessToken = Jwts.builder() + .setSubject(authentication.getName()) + .claim("auth", authorities) + .setExpiration(accessTokenExpDate) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + // refresh token 생성 + Date refreshTokenExpDate = new Date(now + 86400000); + String refreshToken = Jwts.builder() + .setExpiration(refreshTokenExpDate) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + // refresh token 저장 + RefreshToken refToken = new RefreshToken(authentication.getName(), refreshToken); + refreshTokenRepository.save(refToken); + + + return TokenDto.builder() + .grantType("Bearer") + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + // token을 복호화하여 토큰 정보 꺼내는 메서드 + // client로부터 받은 token 정보를 통해 Authentication 객체 생성 + public Authentication getAuthentication(String accessToken){ + Claims claims = getClaims(accessToken); + + if(claims.get("auth") == null){ + throw new RuntimeException("권한이 없는 토큰입니다."); + } + + // claims에서 권한정보 가져오기 + List authorities = Arrays.stream(claims.get("auth").toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + // UserDetails 객체 만들어서 Authentication return + UserDetails principal = new User(claims.getSubject(), "", authorities); + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + // 토큰 유효성 검증 + public boolean validateToken(String token){ + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { + log.info("Invalid JWT Token", e); + } catch (UnsupportedJwtException e) { + log.info("Unsupported JWT Token", e); + } catch (IllegalArgumentException e) { + log.info("JWT claims string is empty.", e); + } + return false; + } + + private Claims getClaims(String accessToken) { + try{ + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody(); + } catch(ExpiredJwtException e){ + return e.getClaims(); + } + } + + +} diff --git a/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java new file mode 100644 index 0000000..cd15be7 --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java @@ -0,0 +1,43 @@ +package com.dku.springstudy.security.config; + +import com.dku.springstudy.security.JwtAuthenticationFilter; +import com.dku.springstudy.security.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +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.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .csrf().disable() + .httpBasic().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/member/login", "/member/reissue").permitAll() + .antMatchers("/item/**").hasRole("USER") + .anyRequest().authenticated() + .and() + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + +} 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..b45d237 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java @@ -0,0 +1,35 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.Member; +import com.dku.springstudy.repository.jpa.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return memberRepository.findByEmail(username) // username = email + .map(this::createUserDetails) + .orElseThrow(() -> new UsernameNotFoundException("해당 회원이 존재하지 않습니다.")); + } + + private UserDetails createUserDetails(Member member){ + return User.builder() + .username(member.getEmail()) + .password(passwordEncoder.encode(member.getPassword())) + .roles(member.getRole().toString()) + .build(); + } +} diff --git a/src/main/java/com/dku/springstudy/service/MemberService.java b/src/main/java/com/dku/springstudy/service/MemberService.java index a2a8412..1bca7dc 100644 --- a/src/main/java/com/dku/springstudy/service/MemberService.java +++ b/src/main/java/com/dku/springstudy/service/MemberService.java @@ -1,18 +1,72 @@ package com.dku.springstudy.service; import com.dku.springstudy.domain.Member; -import com.dku.springstudy.repository.MemberRepository; +import com.dku.springstudy.domain.token.RefreshToken; +import com.dku.springstudy.repository.jpa.MemberRepository; +import com.dku.springstudy.repository.redis.RefreshTokenRepository; +import com.dku.springstudy.security.JwtTokenProvider; +import com.dku.springstudy.dto.TokenDto; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; + +@Slf4j @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class MemberService { - private MemberRepository memberRepository; + private final MemberRepository memberRepository; + private final JwtTokenProvider jwtTokenProvider; + private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final RefreshTokenRepository refreshTokenRepository; public Long join(Member member){ memberRepository.save(member); return member.getId(); } + @Transactional + public TokenDto login(String email, String password){ + // email/password + + // email, password기반으로 Authentication 객체 생성 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password); + + // 실제 검증 + // authenticate() 메서드 실행 시, CustomUserDetailService의 loadByUsername() 메서드 실행 + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + + // 인증 정보를 기반으로 Token 생성 + return jwtTokenProvider.generateToken(authentication); + } + + public ResponseEntity reissue(TokenDto token){ +// if(!jwtTokenProvider.validateToken(token.getRefreshToken())){ +// return ResponseEntity.badRequest().body("유효하지 않은 Refresh Token 입니다."); +// } + log.info("authentication"); + Authentication authentication = jwtTokenProvider.getAuthentication(token.getAccessToken()); + + + // redis에서 refresh Token 가져오기 + RefreshToken refreshToken = refreshTokenRepository.findById(authentication.getName()) + .orElseThrow(() -> new RuntimeException("Refresh Token이 존재하지 않습니다.")); + if(!token.getRefreshToken().equals(refreshToken.getRefreshToken())){ + return ResponseEntity.badRequest().body("토큰의 유저 정보가 일치하지 않습니다."); + } + + TokenDto reissuedTokenDto = jwtTokenProvider.generateToken(authentication); + RefreshToken reissuedRefreshToken = new RefreshToken(authentication.getName(), reissuedTokenDto.getRefreshToken()); + refreshTokenRepository.save(reissuedRefreshToken); + + return ResponseEntity.ok(reissuedTokenDto); + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6bd57b1..0b8bf32 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,10 +6,25 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 5 + + redis: + host: localhost + port: 6379 + jpa: hibernate: ddl-auto: create properties: hibernate: show_sql: true - format_sql: true \ No newline at end of file + format_sql: true + +jwt: + secret: caf4bd8949582e4f85f8750c5e44bfba217e4a346c79c204cd2fb2a37071694bde9e55edab2f81b4fa6d2cd7a7d54e7e313c1a869f43cd30f5f37f462ce051b3 + +server: + error: + include-message: always + include-binding-errors: always + include-stacktrace: always + include-exception: false \ No newline at end of file diff --git a/src/test/java/com/dku/springstudy/domain/MemberTest.java b/src/test/java/com/dku/springstudy/domain/MemberTest.java index b4c1605..ea8fd2b 100644 --- a/src/test/java/com/dku/springstudy/domain/MemberTest.java +++ b/src/test/java/com/dku/springstudy/domain/MemberTest.java @@ -1,22 +1,18 @@ package com.dku.springstudy.domain; import com.dku.springstudy.enums.Category; -import com.dku.springstudy.repository.ItemLikeRepository; -import com.dku.springstudy.repository.ItemRepository; -import com.dku.springstudy.repository.MemberRepository; -import org.assertj.core.api.Assertions; +import com.dku.springstudy.repository.jpa.ItemLikeRepository; +import com.dku.springstudy.repository.jpa.ItemRepository; +import com.dku.springstudy.repository.jpa.MemberRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; -import java.util.Optional; import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @Transactional @@ -38,7 +34,7 @@ public void createMember(){ memberRepository.save(member); //when - Member findMember = memberRepository.findByUsername("동현").get(); + Member findMember = memberRepository.findByName("동현").get(); //then assertThat(findMember).isEqualTo(member); From 3225fb42e964f324c4992cbbddae12c779f1f38b Mon Sep 17 00:00:00 2001 From: suudh99 Date: Wed, 1 Feb 2023 23:43:15 +0900 Subject: [PATCH 04/11] solve CORS Policy --- .../springstudy/security/config/WebMvcConfig.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java diff --git a/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java b/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java new file mode 100644 index 0000000..e66e183 --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java @@ -0,0 +1,15 @@ +package com.dku.springstudy.security.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry + .addMapping("/**") + .allowedOrigins("*"); + } +} From a907aea89e8b13883d4a12a0fa0a5a27cbb88756 Mon Sep 17 00:00:00 2001 From: suudh99 Date: Thu, 2 Feb 2023 02:10:09 +0900 Subject: [PATCH 05/11] unity response value and add join --- .../controller/MemberController.java | 35 +++++++++--------- .../dku/springstudy/domain/dto/JoinDto.java | 16 +++++++++ .../domain/dto/ResponseEntity.java | 20 +++++++++++ .../security/CustomSecurityFilter.java | 27 ++++++++++++++ .../security/ResponseInterceptor.java | 36 +++++++++++++++++++ .../security/config/SecurityConfig.java | 2 +- .../security/config/WebMvcConfig.java | 13 +++++++ .../springstudy/service/MemberService.java | 7 ++-- src/main/resources/application.yml | 2 +- .../dku/springstudy/domain/MemberTest.java | 13 +++---- 10 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/domain/dto/JoinDto.java create mode 100644 src/main/java/com/dku/springstudy/domain/dto/ResponseEntity.java create mode 100644 src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java create mode 100644 src/main/java/com/dku/springstudy/security/ResponseInterceptor.java diff --git a/src/main/java/com/dku/springstudy/controller/MemberController.java b/src/main/java/com/dku/springstudy/controller/MemberController.java index 1b11dc7..e79b007 100644 --- a/src/main/java/com/dku/springstudy/controller/MemberController.java +++ b/src/main/java/com/dku/springstudy/controller/MemberController.java @@ -1,6 +1,7 @@ package com.dku.springstudy.controller; import com.dku.springstudy.domain.Member; +import com.dku.springstudy.domain.dto.JoinDto; import com.dku.springstudy.dto.LoginDto; import com.dku.springstudy.dto.TokenDto; import com.dku.springstudy.enums.Role; @@ -23,32 +24,30 @@ public class MemberController { private final MemberService memberService; + @PostMapping("/join") + public JoinDto join(@RequestBody JoinDto joinDto){ + log.info("Join Request={}", joinDto); + Member joinMember = Member.createMember( + joinDto.getEmail(), + joinDto.getPassword(), + joinDto.getName(), + joinDto.getPhone(), + joinDto.getNickname(), + Role.USER + ); + memberService.join(joinMember); + return joinDto; + } + @PostMapping("/login") public TokenDto login(@RequestBody LoginDto loginDto){ log.info("login request={}", loginDto); return memberService.login(loginDto.getEmail(), loginDto.getPassword()); } @PostMapping("/reissue") - public ResponseEntity reissue(@RequestBody TokenDto tokenDto){ + public TokenDto reissue(@RequestBody TokenDto tokenDto){ log.info("Reissue={}", tokenDto); return memberService.reissue(tokenDto); } - @GetMapping("/user") - public ResponseEntity accessUser(HttpServletRequest request){ - return ResponseEntity.ok(request); - } - - @GetMapping("/admin") - public String accessAdmin(){ - return "ok"; - } - - @PostConstruct - public void init(){ - Member member = Member.createMember("test@naver.com", "1234", "lee", "01010", "nickname", Role.USER); - memberRepository.save(member); - } - - } diff --git a/src/main/java/com/dku/springstudy/domain/dto/JoinDto.java b/src/main/java/com/dku/springstudy/domain/dto/JoinDto.java new file mode 100644 index 0000000..70fac1d --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/dto/JoinDto.java @@ -0,0 +1,16 @@ +package com.dku.springstudy.domain.dto; + +import lombok.*; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class JoinDto { + private String email; + private String password; + private String name; + private String phone; + private String nickname; + +} diff --git a/src/main/java/com/dku/springstudy/domain/dto/ResponseEntity.java b/src/main/java/com/dku/springstudy/domain/dto/ResponseEntity.java new file mode 100644 index 0000000..23f15c6 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/dto/ResponseEntity.java @@ -0,0 +1,20 @@ +package com.dku.springstudy.domain.dto; + +import lombok.Data; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; + +@Data +public class ResponseEntity extends HttpEntity { + private HttpStatus status; + private T body; + + public ResponseEntity(@Nullable T body, HttpStatus status) { +// super(body, headers); + this.body = body; + this.status = status; + } +} diff --git a/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java b/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java new file mode 100644 index 0000000..96c5323 --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java @@ -0,0 +1,27 @@ +package com.dku.springstudy.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class CustomSecurityFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); + filterChain.doFilter(requestWrapper, responseWrapper); + responseWrapper.copyBodyToResponse(); + } +} diff --git a/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java new file mode 100644 index 0000000..5f6e9a0 --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java @@ -0,0 +1,36 @@ +package com.dku.springstudy.security; + +import com.dku.springstudy.domain.dto.ResponseEntity; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +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 +@Component +@RequiredArgsConstructor +public class ResponseInterceptor implements HandlerInterceptor { + private final ObjectMapper objectMapper; + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + ContentCachingResponseWrapper res = (ContentCachingResponseWrapper) response; + String contentString = new String(res.getContentAsByteArray()); + Object readValue = objectMapper.readValue(contentString, Object.class); + + ResponseEntity objectResponseEntity = new ResponseEntity<>(readValue, HttpStatus.OK); + log.info("objectResponseEntity={}", objectResponseEntity); + String wrappedBody = objectMapper.writeValueAsString(objectResponseEntity); + res.resetBuffer(); + res.getOutputStream().write(wrappedBody.getBytes(), 0, wrappedBody.getBytes().length); + + res.copyBodyToResponse(); + } +} diff --git a/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java index cd15be7..003facd 100644 --- a/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java +++ b/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java @@ -27,7 +27,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() - .antMatchers("/member/login", "/member/reissue").permitAll() + .antMatchers("/member/login", "/member/join", "/member/reissue").permitAll() .antMatchers("/item/**").hasRole("USER") .anyRequest().authenticated() .and() diff --git a/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java b/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java index e66e183..489f3d3 100644 --- a/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java +++ b/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java @@ -1,15 +1,28 @@ package com.dku.springstudy.security.config; +import com.dku.springstudy.security.ResponseInterceptor; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration +@RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { + + private final ResponseInterceptor responseInterceptor; @Override public void addCorsMappings(CorsRegistry registry) { registry .addMapping("/**") .allowedOrigins("*"); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry + .addInterceptor(responseInterceptor) + .addPathPatterns("/**"); + } } diff --git a/src/main/java/com/dku/springstudy/service/MemberService.java b/src/main/java/com/dku/springstudy/service/MemberService.java index 1bca7dc..6c7bd3c 100644 --- a/src/main/java/com/dku/springstudy/service/MemberService.java +++ b/src/main/java/com/dku/springstudy/service/MemberService.java @@ -27,6 +27,7 @@ public class MemberService { private final AuthenticationManagerBuilder authenticationManagerBuilder; private final RefreshTokenRepository refreshTokenRepository; + @Transactional public Long join(Member member){ memberRepository.save(member); return member.getId(); @@ -47,7 +48,7 @@ public TokenDto login(String email, String password){ return jwtTokenProvider.generateToken(authentication); } - public ResponseEntity reissue(TokenDto token){ + public TokenDto reissue(TokenDto token){ // if(!jwtTokenProvider.validateToken(token.getRefreshToken())){ // return ResponseEntity.badRequest().body("유효하지 않은 Refresh Token 입니다."); // } @@ -59,14 +60,14 @@ public ResponseEntity reissue(TokenDto token){ RefreshToken refreshToken = refreshTokenRepository.findById(authentication.getName()) .orElseThrow(() -> new RuntimeException("Refresh Token이 존재하지 않습니다.")); if(!token.getRefreshToken().equals(refreshToken.getRefreshToken())){ - return ResponseEntity.badRequest().body("토큰의 유저 정보가 일치하지 않습니다."); + throw new RuntimeException("토큰 정보 유효성 검증 실패"); } TokenDto reissuedTokenDto = jwtTokenProvider.generateToken(authentication); RefreshToken reissuedRefreshToken = new RefreshToken(authentication.getName(), reissuedTokenDto.getRefreshToken()); refreshTokenRepository.save(reissuedRefreshToken); - return ResponseEntity.ok(reissuedTokenDto); + return reissuedTokenDto; } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0b8bf32..0c72408 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,7 @@ spring: datasource: url: jdbc:mysql://localhost:3306/karrot?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 username: root - password: dlehdgus12 + password: password!23 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 5 diff --git a/src/test/java/com/dku/springstudy/domain/MemberTest.java b/src/test/java/com/dku/springstudy/domain/MemberTest.java index ea8fd2b..4791b95 100644 --- a/src/test/java/com/dku/springstudy/domain/MemberTest.java +++ b/src/test/java/com/dku/springstudy/domain/MemberTest.java @@ -1,6 +1,7 @@ package com.dku.springstudy.domain; import com.dku.springstudy.enums.Category; +import com.dku.springstudy.enums.Role; import com.dku.springstudy.repository.jpa.ItemLikeRepository; import com.dku.springstudy.repository.jpa.ItemRepository; import com.dku.springstudy.repository.jpa.MemberRepository; @@ -29,8 +30,7 @@ class EntityTest { @Test public void createMember(){ //given - Member member = new Member(); - member.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍"); + Member member = Member.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍", Role.USER); memberRepository.save(member); //when @@ -43,8 +43,7 @@ public void createMember(){ @Test public void createItem(){ //given - Member member = new Member(); - member.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍"); + Member member = Member.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍", Role.USER); memberRepository.save(member); //when @@ -60,11 +59,9 @@ public void createItem(){ @Test public void createLike(){ //given - Member member1 = new Member(); - member1.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍"); + Member member1 = Member.createMember("test@naver.com", "1234", "동현", "010-9358-4027", "홍홍홍", Role.USER); memberRepository.save(member1); - Member member2 = new Member(); - member2.createMember("qwe123@gmail.com", "qwe123", "dong", "010-1234-5678", "공공공"); + Member member2 = Member.createMember("qwe123@gmail.com", "qwe123", "dong", "010-1234-5678", "공공공", Role.USER); memberRepository.save(member2); Item item = new Item(); From d2098b87263452823952dd2fed953ec53ac31306 Mon Sep 17 00:00:00 2001 From: suudh99 Date: Sun, 5 Feb 2023 20:32:13 +0900 Subject: [PATCH 06/11] add exception handling --- .../exception/ControllerAdvisor.java | 13 +++++++++++++ .../exception/KarrotException.java | 19 +++++++++++++++++++ .../security/JwtAuthenticationFilter.java | 11 +++++++++++ .../security/ResponseInterceptor.java | 3 +++ 4 files changed, 46 insertions(+) create mode 100644 src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java create mode 100644 src/main/java/com/dku/springstudy/exception/KarrotException.java diff --git a/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java b/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java new file mode 100644 index 0000000..13e3b5e --- /dev/null +++ b/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java @@ -0,0 +1,13 @@ +package com.dku.springstudy.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ControllerAdvisor { + @ExceptionHandler(value = KarrotException.class) + protected ResponseEntity exceptionHandler(KarrotException e){ + return ResponseEntity.status(e.getHttpStatus()).body(new KarrotException(e.getHttpStatus(), e.getCode(), e.getMessage())); + } +} diff --git a/src/main/java/com/dku/springstudy/exception/KarrotException.java b/src/main/java/com/dku/springstudy/exception/KarrotException.java new file mode 100644 index 0000000..7ed33dc --- /dev/null +++ b/src/main/java/com/dku/springstudy/exception/KarrotException.java @@ -0,0 +1,19 @@ +package com.dku.springstudy.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Data +public class KarrotException extends RuntimeException{ + private final HttpStatus httpStatus; + private final String code; + private final String message; + + public KarrotException(HttpStatus httpStatus, String code, String message) { + this.httpStatus = httpStatus; + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java index 709fbac..7b70414 100644 --- a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java @@ -1,8 +1,11 @@ package com.dku.springstudy.security; +import com.dku.springstudy.exception.KarrotException; +import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.ExpiredJwtException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -43,6 +46,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } catch (ExpiredJwtException e){ log.info("Expired JWT token", e); + } catch (KarrotException e){ + response.setStatus(e.getHttpStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + ResponseEntity exception = ResponseEntity.status(e.getHttpStatus()).body(new KarrotException(e.getHttpStatus(), e.getCode(), e.getMessage())); + ObjectMapper objectMapper = new ObjectMapper(); + String exceptionMessage = objectMapper.writeValueAsString(exception); + response.getWriter().write(exceptionMessage); } } diff --git a/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java index 5f6e9a0..544d3dc 100644 --- a/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java +++ b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java @@ -1,5 +1,6 @@ package com.dku.springstudy.security; +import com.dku.springstudy.domain.Member; import com.dku.springstudy.domain.dto.ResponseEntity; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -12,6 +13,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; @Slf4j @Component From 04c0b6954c59f5734467e0ac675282adda66ff80 Mon Sep 17 00:00:00 2001 From: suudh99 Date: Sun, 5 Feb 2023 22:40:38 +0900 Subject: [PATCH 07/11] add : Add item --- .../controller/ItemController.java | 48 +++++++++++++++++++ .../java/com/dku/springstudy/domain/Item.java | 17 ++++--- .../com/dku/springstudy/dto/AddItemDto.java | 22 +++++++++ .../com/dku/springstudy/enums/Category.java | 15 +++++- .../exception/KarrotException.java | 4 +- .../security/CustomSecurityFilter.java | 5 +- .../security/JwtAuthenticationFilter.java | 7 +++ .../security/JwtTokenProvider.java | 2 +- .../dku/springstudy/service/ItemService.java | 21 ++++++++ .../springstudy/service/MemberService.java | 7 +++ 10 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/controller/ItemController.java create mode 100644 src/main/java/com/dku/springstudy/dto/AddItemDto.java create mode 100644 src/main/java/com/dku/springstudy/service/ItemService.java diff --git a/src/main/java/com/dku/springstudy/controller/ItemController.java b/src/main/java/com/dku/springstudy/controller/ItemController.java new file mode 100644 index 0000000..857734d --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/ItemController.java @@ -0,0 +1,48 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.domain.ImageFile; +import com.dku.springstudy.domain.Item; +import com.dku.springstudy.domain.Member; +import com.dku.springstudy.dto.AddItemDto; +import com.dku.springstudy.enums.Category; +import com.dku.springstudy.repository.jpa.MemberRepository; +import com.dku.springstudy.service.ItemService; +import com.dku.springstudy.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +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 java.util.Optional; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/item") +public class ItemController { + private final ItemService itemService; + private final MemberService memberService; + + @PostMapping("/add") + private AddItemDto addItem(@RequestBody AddItemDto itemDto){ + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loginEmail = authentication.getName(); + Member currentLoginMember = memberService.getCurrentLoginMember(loginEmail); + + + Item item = Item.createItem(currentLoginMember, itemDto.getTitle(), itemDto.getContent(), itemDto.getPrice(), Category.valueOf(itemDto.getCategory())); + + if(itemDto.getImages()!= null){ + for (ImageFile image:itemDto.getImages()) { + item.addImage(image); + } + } + + itemService.addItem(item); + return itemDto; + } + + +} diff --git a/src/main/java/com/dku/springstudy/domain/Item.java b/src/main/java/com/dku/springstudy/domain/Item.java index 9a7c132..a7ae153 100644 --- a/src/main/java/com/dku/springstudy/domain/Item.java +++ b/src/main/java/com/dku/springstudy/domain/Item.java @@ -37,13 +37,16 @@ public class Item { private Category category; - public void createItem(Member member, String title, String content, int price, Category category){ - this.member = member; - this.title = title; - this.content = content; - this.price = price; - this.category = category; - this.status = ItemStatus.SELLING; // 상품 처음 등록시에는 SELLING(판매중) 상태 + public static Item createItem(Member member, String title, String content, int price, Category category){ + Item item = new Item(); + item.member = member; + item.title = title; + item.content = content; + item.price = price; + item.category = category; + item.status = ItemStatus.SELLING; // 상품 처음 등록시에는 SELLING(판매중) 상태 + + return item; } public void addImage(ImageFile image){ diff --git a/src/main/java/com/dku/springstudy/dto/AddItemDto.java b/src/main/java/com/dku/springstudy/dto/AddItemDto.java new file mode 100644 index 0000000..5ca3a08 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/AddItemDto.java @@ -0,0 +1,22 @@ +package com.dku.springstudy.dto; + +import com.dku.springstudy.domain.ImageFile; +import com.dku.springstudy.enums.Category; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AddItemDto { + private String title; + private String category; + private int price; + private String content; + private List images; + + +} diff --git a/src/main/java/com/dku/springstudy/enums/Category.java b/src/main/java/com/dku/springstudy/enums/Category.java index 4070129..acb67af 100644 --- a/src/main/java/com/dku/springstudy/enums/Category.java +++ b/src/main/java/com/dku/springstudy/enums/Category.java @@ -1,6 +1,17 @@ package com.dku.springstudy.enums; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor public enum Category { - DIGITAL, ELECTRONIC, FURNITURE_INTERIOR, KIDS, LIFE_FOOD, BOOK_KIDS, SPORTS, WOMAN_MERCHANDISE, WOMAN_CLOTHES, MAN_MERCHANDISE_CLOTHES, - GAME_HOBBY, BEAUTY_HAIR, PET, BOOK_TICKET_MUSIC, PLANTS, USED_THINGS, USED_CAR + + DIGITAL("DIGITAL"), ELECTRONIC("ELECTRONIC"), FURNITURE_INTERIOR("FURNITURE_INTERIOR"), KIDS("KIDS"), LIFE_FOOD("LIFE_FOOD"), BOOK_KIDS("BOOK_KIDS"), SPORTS("SPORTS"), + WOMAN_MERCHANDISE("WOMAN_MERCHANDISE"), WOMAN_CLOTHES("WOMAN_CLOTHES"), MAN_MERCHANDISE_CLOTHES("MAN_MERCHANDISE_CLOTHES"), + GAME_HOBBY("GAME_HOBBY"), BEAUTY_HAIR("BEAUTY_HAIR"), PET("PET"), BOOK_TICKET_MUSIC("BOOK_TICKET_MUSIC"), PLANTS("PLANTS"), USED_THINGS("USED_THINGS"), USED_CAR("USED_CAR"); + + @JsonValue + private final String category; } diff --git a/src/main/java/com/dku/springstudy/exception/KarrotException.java b/src/main/java/com/dku/springstudy/exception/KarrotException.java index 7ed33dc..96272d2 100644 --- a/src/main/java/com/dku/springstudy/exception/KarrotException.java +++ b/src/main/java/com/dku/springstudy/exception/KarrotException.java @@ -8,10 +8,10 @@ @Data public class KarrotException extends RuntimeException{ private final HttpStatus httpStatus; - private final String code; + private final int code; private final String message; - public KarrotException(HttpStatus httpStatus, String code, String message) { + public KarrotException(HttpStatus httpStatus, int code, String message) { this.httpStatus = httpStatus; this.code = code; this.message = message; diff --git a/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java b/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java index 96c5323..8789f1c 100644 --- a/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java +++ b/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java @@ -19,9 +19,6 @@ public class CustomSecurityFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); - ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); - filterChain.doFilter(requestWrapper, responseWrapper); - responseWrapper.copyBodyToResponse(); + } } diff --git a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java index 7b70414..aa499cb 100644 --- a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java @@ -14,6 +14,8 @@ import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -32,6 +34,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); + try{ String token = getTokenFromHeader(request); String path = request.getServletPath(); @@ -43,6 +48,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); + filterChain.doFilter(requestWrapper, responseWrapper); + responseWrapper.copyBodyToResponse(); } } catch (ExpiredJwtException e){ log.info("Expired JWT token", e); diff --git a/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java index abe025a..1e4202b 100644 --- a/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java +++ b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java @@ -48,7 +48,7 @@ public TokenDto generateToken(Authentication authentication){ long now = new Date().getTime(); // access token 생성 - Date accessTokenExpDate = new Date(now + 30000); // 발급시간으로부터 1일 + Date accessTokenExpDate = new Date(now + 180000); // 발급시간으로부터 1일 String accessToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth", authorities) diff --git a/src/main/java/com/dku/springstudy/service/ItemService.java b/src/main/java/com/dku/springstudy/service/ItemService.java new file mode 100644 index 0000000..e61bbb0 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/ItemService.java @@ -0,0 +1,21 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.Item; +import com.dku.springstudy.domain.Member; +import com.dku.springstudy.repository.jpa.ItemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ItemService { + private final ItemRepository itemRepository; + + @Transactional + public Long addItem(Item item){ + itemRepository.save(item); + return item.getId(); + } +} diff --git a/src/main/java/com/dku/springstudy/service/MemberService.java b/src/main/java/com/dku/springstudy/service/MemberService.java index 6c7bd3c..0f03501 100644 --- a/src/main/java/com/dku/springstudy/service/MemberService.java +++ b/src/main/java/com/dku/springstudy/service/MemberService.java @@ -2,12 +2,14 @@ import com.dku.springstudy.domain.Member; import com.dku.springstudy.domain.token.RefreshToken; +import com.dku.springstudy.exception.KarrotException; import com.dku.springstudy.repository.jpa.MemberRepository; import com.dku.springstudy.repository.redis.RefreshTokenRepository; import com.dku.springstudy.security.JwtTokenProvider; import com.dku.springstudy.dto.TokenDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -70,4 +72,9 @@ public TokenDto reissue(TokenDto token){ return reissuedTokenDto; } + public Member getCurrentLoginMember(String email){ + return memberRepository.findByEmail(email) + .orElseThrow(() -> new KarrotException(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.value(), "존재하지 않은 사용자 입니다.")); + } + } From 833d19aa39679a22091a1cf77b22f9e9d76a0757 Mon Sep 17 00:00:00 2001 From: suudh99 Date: Mon, 6 Feb 2023 17:39:49 +0900 Subject: [PATCH 08/11] delete unuse package --- build.gradle | 4 ++++ .../com/dku/springstudy/config/AwsConfig.java | 4 ++++ .../{security => }/config/SecurityConfig.java | 2 +- .../{security => }/config/WebMvcConfig.java | 2 +- .../controller/MemberController.java | 6 +---- .../springstudy/{domain => }/dto/JoinDto.java | 2 +- .../{domain => }/dto/ResponseEntity.java | 2 +- .../security/CustomSecurityFilter.java | 24 ------------------- .../security/JwtAuthenticationFilter.java | 13 +++++----- .../security/ResponseInterceptor.java | 6 +---- src/main/resources/application.yml | 16 ++++++++++++- 11 files changed, 35 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/config/AwsConfig.java rename src/main/java/com/dku/springstudy/{security => }/config/SecurityConfig.java (97%) rename src/main/java/com/dku/springstudy/{security => }/config/WebMvcConfig.java (95%) rename src/main/java/com/dku/springstudy/{domain => }/dto/JoinDto.java (86%) rename src/main/java/com/dku/springstudy/{domain => }/dto/ResponseEntity.java (92%) delete mode 100644 src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java diff --git a/build.gradle b/build.gradle index cbb4f17..e13342b 100644 --- a/build.gradle +++ b/build.gradle @@ -26,8 +26,12 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE' // redis + // s3 storage + implementation 'com.amazonaws:aws-java-sdk-s3:1.12.281' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/com/dku/springstudy/config/AwsConfig.java b/src/main/java/com/dku/springstudy/config/AwsConfig.java new file mode 100644 index 0000000..e967003 --- /dev/null +++ b/src/main/java/com/dku/springstudy/config/AwsConfig.java @@ -0,0 +1,4 @@ +package com.dku.springstudy.config; + +public class AwsConfig { +} diff --git a/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java similarity index 97% rename from src/main/java/com/dku/springstudy/security/config/SecurityConfig.java rename to src/main/java/com/dku/springstudy/config/SecurityConfig.java index 003facd..e10067a 100644 --- a/src/main/java/com/dku/springstudy/security/config/SecurityConfig.java +++ b/src/main/java/com/dku/springstudy/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.security.config; +package com.dku.springstudy.config; import com.dku.springstudy.security.JwtAuthenticationFilter; import com.dku.springstudy.security.JwtTokenProvider; diff --git a/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java b/src/main/java/com/dku/springstudy/config/WebMvcConfig.java similarity index 95% rename from src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java rename to src/main/java/com/dku/springstudy/config/WebMvcConfig.java index 489f3d3..a6193ba 100644 --- a/src/main/java/com/dku/springstudy/security/config/WebMvcConfig.java +++ b/src/main/java/com/dku/springstudy/config/WebMvcConfig.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.security.config; +package com.dku.springstudy.config; import com.dku.springstudy.security.ResponseInterceptor; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/dku/springstudy/controller/MemberController.java b/src/main/java/com/dku/springstudy/controller/MemberController.java index e79b007..e69b8c5 100644 --- a/src/main/java/com/dku/springstudy/controller/MemberController.java +++ b/src/main/java/com/dku/springstudy/controller/MemberController.java @@ -1,7 +1,7 @@ package com.dku.springstudy.controller; import com.dku.springstudy.domain.Member; -import com.dku.springstudy.domain.dto.JoinDto; +import com.dku.springstudy.dto.JoinDto; import com.dku.springstudy.dto.LoginDto; import com.dku.springstudy.dto.TokenDto; import com.dku.springstudy.enums.Role; @@ -9,12 +9,8 @@ import com.dku.springstudy.service.MemberService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; - @Slf4j @RestController @RequiredArgsConstructor diff --git a/src/main/java/com/dku/springstudy/domain/dto/JoinDto.java b/src/main/java/com/dku/springstudy/dto/JoinDto.java similarity index 86% rename from src/main/java/com/dku/springstudy/domain/dto/JoinDto.java rename to src/main/java/com/dku/springstudy/dto/JoinDto.java index 70fac1d..4881615 100644 --- a/src/main/java/com/dku/springstudy/domain/dto/JoinDto.java +++ b/src/main/java/com/dku/springstudy/dto/JoinDto.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.domain.dto; +package com.dku.springstudy.dto; import lombok.*; diff --git a/src/main/java/com/dku/springstudy/domain/dto/ResponseEntity.java b/src/main/java/com/dku/springstudy/dto/ResponseEntity.java similarity index 92% rename from src/main/java/com/dku/springstudy/domain/dto/ResponseEntity.java rename to src/main/java/com/dku/springstudy/dto/ResponseEntity.java index 23f15c6..a387463 100644 --- a/src/main/java/com/dku/springstudy/domain/dto/ResponseEntity.java +++ b/src/main/java/com/dku/springstudy/dto/ResponseEntity.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.domain.dto; +package com.dku.springstudy.dto; import lombok.Data; import org.springframework.http.HttpEntity; diff --git a/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java b/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java deleted file mode 100644 index 8789f1c..0000000 --- a/src/main/java/com/dku/springstudy/security/CustomSecurityFilter.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.dku.springstudy.security; - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.ContentCachingRequestWrapper; -import org.springframework.web.util.ContentCachingResponseWrapper; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Component -@RequiredArgsConstructor -public class CustomSecurityFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - - } -} diff --git a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java index aa499cb..c2845bc 100644 --- a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java @@ -38,16 +38,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); try{ - String token = getTokenFromHeader(request); - String path = request.getServletPath(); + String token = getTokenFromHeader(requestWrapper); + String path = requestWrapper.getServletPath(); if(path.startsWith("/member/reissue")){ - filterChain.doFilter(request,response); + filterChain.doFilter(requestWrapper,responseWrapper); } else { if(token != null && jwtTokenProvider.validateToken(token)){ Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } - filterChain.doFilter(request, response); filterChain.doFilter(requestWrapper, responseWrapper); responseWrapper.copyBodyToResponse(); } @@ -64,9 +63,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } - private String getTokenFromHeader(HttpServletRequest request) { - HttpServletRequest req = request; - String bearerToken = req.getHeader("Authorization"); + private String getTokenFromHeader(ContentCachingRequestWrapper request) { +// HttpServletRequest req = request; + String bearerToken = request.getHeader("Authorization"); if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")){ return bearerToken.substring(7); } diff --git a/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java index 544d3dc..e4cfb33 100644 --- a/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java +++ b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java @@ -1,8 +1,6 @@ package com.dku.springstudy.security; -import com.dku.springstudy.domain.Member; -import com.dku.springstudy.domain.dto.ResponseEntity; -import com.fasterxml.jackson.databind.JsonNode; +import com.dku.springstudy.dto.ResponseEntity; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -13,8 +11,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.List; @Slf4j @Component diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0c72408..261d4c3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,4 +27,18 @@ server: include-message: always include-binding-errors: always include-stacktrace: always - include-exception: false \ No newline at end of file + include-exception: false + + +cloud: + aws: + credentials: + accessKey: AKIAXW4DDD5LZRII4EWE # AWS IAM AccessKey 적기 + secretKey: fKI02S0pNFwTq/e0rLRNTXy0z+GVElu9ujlUiDyJ # AWS IAM SecretKey 적기 + s3: + bucket: karrotbucket # ex) marryting-gyunny + #dir: /karrotbucket # ex) /gyunny + region: + static: ap-northeast-2 + stack: + auto: false \ No newline at end of file From d1631da6e80a8946af22f4b52e505e4a4172659c Mon Sep 17 00:00:00 2001 From: suudh99 Date: Tue, 7 Feb 2023 17:40:31 +0900 Subject: [PATCH 09/11] add itemAddApi, mainPageApi, itemDetailsApi --- .../springstudy/SpringStudyApplication.java | 2 + .../com/dku/springstudy/config/AwsConfig.java | 27 ++++++ .../controller/HomeController.java | 36 ++++++++ .../controller/ItemController.java | 88 ++++++++++++++++--- .../dku/springstudy/domain/BaseEntity.java | 27 ++++++ .../com/dku/springstudy/domain/ImageFile.java | 12 ++- .../java/com/dku/springstudy/domain/Item.java | 9 +- .../com/dku/springstudy/domain/ItemLike.java | 2 +- .../com/dku/springstudy/domain/Member.java | 2 +- .../com/dku/springstudy/dto/AddItemDto.java | 3 - .../dku/springstudy/dto/ItemDetailsDto.java | 21 +++++ .../java/com/dku/springstudy/dto/ItemDto.java | 19 ++++ .../exception/ControllerAdvisor.java | 1 + .../repository/jpa/ImageFileRepository.java | 7 ++ .../repository/jpa/ItemLikeRepository.java | 4 + .../repository/jpa/ItemRepository.java | 4 + .../security/JwtTokenProvider.java | 2 +- .../springstudy/service/ImageFileService.java | 20 +++++ .../springstudy/service/ItemLikeService.java | 21 +++++ .../dku/springstudy/service/ItemService.java | 17 ++++ .../springstudy/service/MemberService.java | 7 +- .../com/dku/springstudy/service/S3Upload.java | 33 +++++++ 22 files changed, 336 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/controller/HomeController.java create mode 100644 src/main/java/com/dku/springstudy/domain/BaseEntity.java create mode 100644 src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java create mode 100644 src/main/java/com/dku/springstudy/dto/ItemDto.java create mode 100644 src/main/java/com/dku/springstudy/repository/jpa/ImageFileRepository.java create mode 100644 src/main/java/com/dku/springstudy/service/ImageFileService.java create mode 100644 src/main/java/com/dku/springstudy/service/ItemLikeService.java create mode 100644 src/main/java/com/dku/springstudy/service/S3Upload.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/config/AwsConfig.java b/src/main/java/com/dku/springstudy/config/AwsConfig.java index e967003..14800e4 100644 --- a/src/main/java/com/dku/springstudy/config/AwsConfig.java +++ b/src/main/java/com/dku/springstudy/config/AwsConfig.java @@ -1,4 +1,31 @@ package com.dku.springstudy.config; +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 AwsConfig { + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3() { + AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } } diff --git a/src/main/java/com/dku/springstudy/controller/HomeController.java b/src/main/java/com/dku/springstudy/controller/HomeController.java new file mode 100644 index 0000000..3cca437 --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/HomeController.java @@ -0,0 +1,36 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.domain.ImageFile; +import com.dku.springstudy.domain.Item; +import com.dku.springstudy.dto.ItemDto; +import com.dku.springstudy.service.ItemService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequiredArgsConstructor +public class HomeController { + + private final ItemService itemService; + + @GetMapping("/home") + public List home(){ + List allItems = itemService.findAll(); + return allItems.stream() + .map(i -> new ItemDto( + i.getId(), + i.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), + i.getTitle(), + i.getContent(), + i.getPrice(), + i.getLikes().size()) + ) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/dku/springstudy/controller/ItemController.java b/src/main/java/com/dku/springstudy/controller/ItemController.java index 857734d..d0cdde3 100644 --- a/src/main/java/com/dku/springstudy/controller/ItemController.java +++ b/src/main/java/com/dku/springstudy/controller/ItemController.java @@ -4,45 +4,107 @@ import com.dku.springstudy.domain.Item; import com.dku.springstudy.domain.Member; import com.dku.springstudy.dto.AddItemDto; +import com.dku.springstudy.dto.ItemDetailsDto; +import com.dku.springstudy.dto.ItemDto; import com.dku.springstudy.enums.Category; import com.dku.springstudy.repository.jpa.MemberRepository; +import com.dku.springstudy.service.ImageFileService; import com.dku.springstudy.service.ItemService; import com.dku.springstudy.service.MemberService; +import com.dku.springstudy.service.S3Upload; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -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 org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/item") public class ItemController { private final ItemService itemService; private final MemberService memberService; + private final ImageFileService imageFileService; + private final S3Upload s3Upload; @PostMapping("/add") - private AddItemDto addItem(@RequestBody AddItemDto itemDto){ - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String loginEmail = authentication.getName(); - Member currentLoginMember = memberService.getCurrentLoginMember(loginEmail); - + private AddItemDto addItem(@RequestPart("data") AddItemDto itemDto, @RequestPart("images")MultipartFile[] multipartFiles) throws IOException { + Member currentLoginMember = getCurrentLoginMember(); Item item = Item.createItem(currentLoginMember, itemDto.getTitle(), itemDto.getContent(), itemDto.getPrice(), Category.valueOf(itemDto.getCategory())); + itemService.addItem(item); - if(itemDto.getImages()!= null){ - for (ImageFile image:itemDto.getImages()) { - item.addImage(image); + if(multipartFiles.length != 0){ + for(MultipartFile multipartFile : multipartFiles){ + String imagePath = s3Upload.upload(multipartFile); + ImageFile imageFile = ImageFile.createImageFile(imagePath); + imageFile.updateItem(item); + imageFileService.save(imageFile); } } - itemService.addItem(item); return itemDto; } + @GetMapping("/details/{itemId}") + public ItemDetailsDto details(@PathVariable("itemId") Long itemId){ + Item findItem = itemService.findById(itemId); + Member seller = findItem.getMember(); + List sellerItems = itemService.findByMember(seller); + + List result = sellerItems.stream() + .map(i -> new ItemDto( + i.getId(), + i.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), + i.getTitle(), + i.getContent(), + i.getPrice(), + i.getLikes().size()) + ) + .collect(Collectors.toList()); + + + return new ItemDetailsDto( + findItem.getMember().getNickname(), + findItem.getCategory().getCategory(), + findItem.getLastModifiedDate(), + findItem.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), + findItem.getTitle(), + findItem.getContent(), + findItem.getPrice(), + result + ); + } + + @GetMapping("/details/all/{sellerId}") + public List sellerItems(@PathVariable("sellerId") Long sellerId){ + Member seller = memberService.findById(sellerId); + List sellerItems = itemService.findByMember(seller); + return sellerItems.stream() + .map(i -> new ItemDto( + i.getId(), + i.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), + i.getTitle(), + i.getContent(), + i.getPrice(), + i.getLikes().size()) + ) + .collect(Collectors.toList()); + } + + private Member getCurrentLoginMember() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loginEmail = authentication.getName(); + return memberService.getCurrentLoginMember(loginEmail); + } + } diff --git a/src/main/java/com/dku/springstudy/domain/BaseEntity.java b/src/main/java/com/dku/springstudy/domain/BaseEntity.java new file mode 100644 index 0000000..27af012 --- /dev/null +++ b/src/main/java/com/dku/springstudy/domain/BaseEntity.java @@ -0,0 +1,27 @@ +package com.dku.springstudy.domain; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +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; + +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +@Getter +public class BaseEntity { + + @CreatedDate + @Column(name = "created_date", nullable = false) + private LocalDateTime createdDate; + + @LastModifiedDate + @Column(name = "last_modified_date", nullable = false) + private LocalDateTime lastModifiedDate; +} diff --git a/src/main/java/com/dku/springstudy/domain/ImageFile.java b/src/main/java/com/dku/springstudy/domain/ImageFile.java index 80f88c8..967078a 100644 --- a/src/main/java/com/dku/springstudy/domain/ImageFile.java +++ b/src/main/java/com/dku/springstudy/domain/ImageFile.java @@ -9,7 +9,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ImageFile { +public class ImageFile extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -20,8 +20,14 @@ public class ImageFile { @JoinColumn(name = "item_id") private Item item; - public void createImageFile(String imageUrl, Item item){ - this.imageUrl = imageUrl; + public static ImageFile createImageFile(String imageUrl){ + ImageFile imageFile = new ImageFile(); + imageFile.imageUrl = imageUrl; + return imageFile; + } + + public void updateItem(Item item){ this.item = item; + item.getImages().add(this); } } diff --git a/src/main/java/com/dku/springstudy/domain/Item.java b/src/main/java/com/dku/springstudy/domain/Item.java index a7ae153..9889e76 100644 --- a/src/main/java/com/dku/springstudy/domain/Item.java +++ b/src/main/java/com/dku/springstudy/domain/Item.java @@ -13,7 +13,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Item { +public class Item extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "item_id") @@ -36,6 +36,9 @@ public class Item { @Enumerated(EnumType.STRING) private Category category; + @OneToMany(mappedBy = "item") + private List likes = new ArrayList<>(); + public static Item createItem(Member member, String title, String content, int price, Category category){ Item item = new Item(); @@ -49,8 +52,4 @@ public static Item createItem(Member member, String title, String content, int p return item; } - public void addImage(ImageFile image){ - images.add(image); - } - } diff --git a/src/main/java/com/dku/springstudy/domain/ItemLike.java b/src/main/java/com/dku/springstudy/domain/ItemLike.java index 5c9356a..5489cac 100644 --- a/src/main/java/com/dku/springstudy/domain/ItemLike.java +++ b/src/main/java/com/dku/springstudy/domain/ItemLike.java @@ -10,7 +10,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "ITEM_LIKE") -public class ItemLike { +public class ItemLike extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/com/dku/springstudy/domain/Member.java b/src/main/java/com/dku/springstudy/domain/Member.java index dba4ebd..1f2f498 100644 --- a/src/main/java/com/dku/springstudy/domain/Member.java +++ b/src/main/java/com/dku/springstudy/domain/Member.java @@ -17,7 +17,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Member implements UserDetails { +public class Member extends BaseEntity implements UserDetails{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "member_id") diff --git a/src/main/java/com/dku/springstudy/dto/AddItemDto.java b/src/main/java/com/dku/springstudy/dto/AddItemDto.java index 5ca3a08..928ba40 100644 --- a/src/main/java/com/dku/springstudy/dto/AddItemDto.java +++ b/src/main/java/com/dku/springstudy/dto/AddItemDto.java @@ -16,7 +16,4 @@ public class AddItemDto { private String category; private int price; private String content; - private List images; - - } diff --git a/src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java b/src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java new file mode 100644 index 0000000..6b2a44a --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java @@ -0,0 +1,21 @@ +package com.dku.springstudy.dto; + +import com.dku.springstudy.domain.Item; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@AllArgsConstructor +public class ItemDetailsDto { + private String sellerNickname; + private String category; + private LocalDateTime lastModifiedDate; + private List imagePath; + private String title; + private String content; + private int price; + private List sellerItems; +} diff --git a/src/main/java/com/dku/springstudy/dto/ItemDto.java b/src/main/java/com/dku/springstudy/dto/ItemDto.java new file mode 100644 index 0000000..91fec37 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/ItemDto.java @@ -0,0 +1,19 @@ +package com.dku.springstudy.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class ItemDto { + private Long id; + private List imagePath; + private String title; + private String content; + private int price; + private int likeCount; +} diff --git a/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java b/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java index 13e3b5e..964257e 100644 --- a/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java +++ b/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java @@ -5,6 +5,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice + public class ControllerAdvisor { @ExceptionHandler(value = KarrotException.class) protected ResponseEntity exceptionHandler(KarrotException e){ diff --git a/src/main/java/com/dku/springstudy/repository/jpa/ImageFileRepository.java b/src/main/java/com/dku/springstudy/repository/jpa/ImageFileRepository.java new file mode 100644 index 0000000..2fe06cb --- /dev/null +++ b/src/main/java/com/dku/springstudy/repository/jpa/ImageFileRepository.java @@ -0,0 +1,7 @@ +package com.dku.springstudy.repository.jpa; + +import com.dku.springstudy.domain.ImageFile; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ImageFileRepository extends JpaRepository { +} diff --git a/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java b/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java index 6c9dd4a..5f4d574 100644 --- a/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java +++ b/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java @@ -1,8 +1,12 @@ package com.dku.springstudy.repository.jpa; +import com.dku.springstudy.domain.Item; import com.dku.springstudy.domain.ItemLike; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface ItemLikeRepository extends JpaRepository { + List findByItem(Item item); } diff --git a/src/main/java/com/dku/springstudy/repository/jpa/ItemRepository.java b/src/main/java/com/dku/springstudy/repository/jpa/ItemRepository.java index c39f9c3..f027e61 100644 --- a/src/main/java/com/dku/springstudy/repository/jpa/ItemRepository.java +++ b/src/main/java/com/dku/springstudy/repository/jpa/ItemRepository.java @@ -1,7 +1,11 @@ package com.dku.springstudy.repository.jpa; import com.dku.springstudy.domain.Item; +import com.dku.springstudy.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface ItemRepository extends JpaRepository { + List findByMember(Member member); } diff --git a/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java index 1e4202b..7be0056 100644 --- a/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java +++ b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java @@ -48,7 +48,7 @@ public TokenDto generateToken(Authentication authentication){ long now = new Date().getTime(); // access token 생성 - Date accessTokenExpDate = new Date(now + 180000); // 발급시간으로부터 1일 + Date accessTokenExpDate = new Date(now + 3600000); // 발급시간으로부터 1h String accessToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth", authorities) diff --git a/src/main/java/com/dku/springstudy/service/ImageFileService.java b/src/main/java/com/dku/springstudy/service/ImageFileService.java new file mode 100644 index 0000000..81d21e2 --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/ImageFileService.java @@ -0,0 +1,20 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.ImageFile; +import com.dku.springstudy.repository.jpa.ImageFileRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ImageFileService { + + private final ImageFileRepository imageFileRepository; + + @Transactional + public void save(ImageFile imageFile){ + imageFileRepository.save(imageFile); + } +} diff --git a/src/main/java/com/dku/springstudy/service/ItemLikeService.java b/src/main/java/com/dku/springstudy/service/ItemLikeService.java new file mode 100644 index 0000000..61cc1ad --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/ItemLikeService.java @@ -0,0 +1,21 @@ +package com.dku.springstudy.service; + +import com.dku.springstudy.domain.Item; +import com.dku.springstudy.domain.ItemLike; +import com.dku.springstudy.repository.jpa.ItemLikeRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ItemLikeService { + + private final ItemLikeRepository itemLikeRepository; + + public int getLikeCountByItemId(Item item){ + List itemLikes = itemLikeRepository.findByItem(item); + return itemLikes.size(); + } +} diff --git a/src/main/java/com/dku/springstudy/service/ItemService.java b/src/main/java/com/dku/springstudy/service/ItemService.java index e61bbb0..6b235d6 100644 --- a/src/main/java/com/dku/springstudy/service/ItemService.java +++ b/src/main/java/com/dku/springstudy/service/ItemService.java @@ -2,11 +2,15 @@ import com.dku.springstudy.domain.Item; import com.dku.springstudy.domain.Member; +import com.dku.springstudy.exception.KarrotException; import com.dku.springstudy.repository.jpa.ItemRepository; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -18,4 +22,17 @@ public Long addItem(Item item){ itemRepository.save(item); return item.getId(); } + + public List findAll() { + return itemRepository.findAll(); + } + + public Item findById(Long id) { + return itemRepository.findById(id) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "해당 상품이 존재하지 않습니다.")); + } + + public List findByMember(Member member) { + return itemRepository.findByMember(member); + } } diff --git a/src/main/java/com/dku/springstudy/service/MemberService.java b/src/main/java/com/dku/springstudy/service/MemberService.java index 0f03501..b33075c 100644 --- a/src/main/java/com/dku/springstudy/service/MemberService.java +++ b/src/main/java/com/dku/springstudy/service/MemberService.java @@ -74,7 +74,12 @@ public TokenDto reissue(TokenDto token){ public Member getCurrentLoginMember(String email){ return memberRepository.findByEmail(email) - .orElseThrow(() -> new KarrotException(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.value(), "존재하지 않은 사용자 입니다.")); + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "존재하지 않은 사용자 입니다.")); + } + + public Member findById(Long id){ + return memberRepository.findById(id) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "존재하지 않은 사용자 입니다.")); } } diff --git a/src/main/java/com/dku/springstudy/service/S3Upload.java b/src/main/java/com/dku/springstudy/service/S3Upload.java new file mode 100644 index 0000000..4ba48ec --- /dev/null +++ b/src/main/java/com/dku/springstudy/service/S3Upload.java @@ -0,0 +1,33 @@ +package com.dku.springstudy.service; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.ObjectMetadata; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class S3Upload { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String upload(MultipartFile multipartFile) throws IOException { + String s3FileName = UUID.randomUUID() + "-" + multipartFile.getOriginalFilename(); + + ObjectMetadata objMeta = new ObjectMetadata(); + objMeta.setContentLength(multipartFile.getInputStream().available()); + + amazonS3.putObject(bucket, s3FileName, multipartFile.getInputStream(), objMeta); + + return amazonS3.getUrl(bucket, s3FileName).toString(); + } +} From a63a2fea1ddec77c0c5694eb0e26567e0886a4d9 Mon Sep 17 00:00:00 2001 From: suudh99 Date: Wed, 8 Feb 2023 17:17:41 +0900 Subject: [PATCH 10/11] add: myPage apis, itemLike api --- .../controller/ItemController.java | 53 ++++----- .../controller/ItemLikeController.java | 26 +++++ .../controller/MemberController.java | 51 ++++++++- .../java/com/dku/springstudy/domain/Item.java | 14 +++ .../com/dku/springstudy/domain/ItemLike.java | 9 +- .../com/dku/springstudy/domain/Member.java | 7 ++ .../com/dku/springstudy/dto/AddItemDto.java | 2 +- .../java/com/dku/springstudy/dto/ItemDto.java | 2 + .../com/dku/springstudy/dto/MyPageDto.java | 13 +++ .../springstudy/dto/UpdateItemStatusDto.java | 10 ++ .../springstudy/dto/UpdateProfilesDto.java | 10 ++ .../repository/jpa/ItemLikeRepository.java | 6 + .../security/AuthenticationProvider.java | 17 +++ .../CustomUserDetailsService.java | 13 +-- .../springstudy/security/UserDetailsImpl.java | 16 +++ .../springstudy/service/ImageFileService.java | 9 ++ .../springstudy/service/ItemLikeService.java | 30 +++++ .../dku/springstudy/service/ItemService.java | 104 +++++++++++++++++- .../springstudy/service/MemberService.java | 17 +++ 19 files changed, 362 insertions(+), 47 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/controller/ItemLikeController.java create mode 100644 src/main/java/com/dku/springstudy/dto/MyPageDto.java create mode 100644 src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java create mode 100644 src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java create mode 100644 src/main/java/com/dku/springstudy/security/AuthenticationProvider.java rename src/main/java/com/dku/springstudy/{service => security}/CustomUserDetailsService.java (70%) create mode 100644 src/main/java/com/dku/springstudy/security/UserDetailsImpl.java diff --git a/src/main/java/com/dku/springstudy/controller/ItemController.java b/src/main/java/com/dku/springstudy/controller/ItemController.java index d0cdde3..652246c 100644 --- a/src/main/java/com/dku/springstudy/controller/ItemController.java +++ b/src/main/java/com/dku/springstudy/controller/ItemController.java @@ -39,7 +39,7 @@ public class ItemController { private AddItemDto addItem(@RequestPart("data") AddItemDto itemDto, @RequestPart("images")MultipartFile[] multipartFiles) throws IOException { Member currentLoginMember = getCurrentLoginMember(); - Item item = Item.createItem(currentLoginMember, itemDto.getTitle(), itemDto.getContent(), itemDto.getPrice(), Category.valueOf(itemDto.getCategory())); + Item item = Item.createItem(currentLoginMember, itemDto.getTitle(), itemDto.getContent(), itemDto.getPrice(), itemDto.getCategory()); itemService.addItem(item); if(multipartFiles.length != 0){ @@ -58,19 +58,7 @@ private AddItemDto addItem(@RequestPart("data") AddItemDto itemDto, @RequestPart public ItemDetailsDto details(@PathVariable("itemId") Long itemId){ Item findItem = itemService.findById(itemId); Member seller = findItem.getMember(); - List sellerItems = itemService.findByMember(seller); - - List result = sellerItems.stream() - .map(i -> new ItemDto( - i.getId(), - i.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), - i.getTitle(), - i.getContent(), - i.getPrice(), - i.getLikes().size()) - ) - .collect(Collectors.toList()); - + List result = itemService.findByMember(seller); return new ItemDetailsDto( findItem.getMember().getNickname(), @@ -87,17 +75,32 @@ public ItemDetailsDto details(@PathVariable("itemId") Long itemId){ @GetMapping("/details/all/{sellerId}") public List sellerItems(@PathVariable("sellerId") Long sellerId){ Member seller = memberService.findById(sellerId); - List sellerItems = itemService.findByMember(seller); - return sellerItems.stream() - .map(i -> new ItemDto( - i.getId(), - i.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), - i.getTitle(), - i.getContent(), - i.getPrice(), - i.getLikes().size()) - ) - .collect(Collectors.toList()); + return itemService.findByMember(seller); + } + + @GetMapping("/update/{itemId}") + public ItemDto transferPreviousItemInfo(@PathVariable Long itemId){ + return itemService.transferPreviousItemInfo(itemId); + } + + @PostMapping("/update/{itemId}") + public void updateItem( @PathVariable("itemId") Long itemId, + @RequestPart("data") AddItemDto updateItemDto, + @RequestPart("images")MultipartFile[] updateMultipartFiles) throws IOException { + + itemService.updateItem( + itemId, + updateItemDto.getTitle(), + updateItemDto.getContent(), + updateItemDto.getCategory(), + updateItemDto.getPrice(), + updateMultipartFiles + ); + } + + @PostMapping("/delete/{itemId}") + public void deleteItem(@PathVariable Long itemId){ + itemService.deleteItem(itemId); } private Member getCurrentLoginMember() { diff --git a/src/main/java/com/dku/springstudy/controller/ItemLikeController.java b/src/main/java/com/dku/springstudy/controller/ItemLikeController.java new file mode 100644 index 0000000..fd563c8 --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/ItemLikeController.java @@ -0,0 +1,26 @@ +package com.dku.springstudy.controller; + +import com.dku.springstudy.service.ItemLikeService; +import lombok.RequiredArgsConstructor; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/item-like") +public class ItemLikeController { + + private final ItemLikeService itemLikeService; + + @PostMapping("/add/{itemId}") + public void addItemLike(@PathVariable Long itemId){ + itemLikeService.addItemLike(itemId); + } + + @PostMapping("/delete/{itemId}") + public void deleteItemLike(@PathVariable Long itemId){ + itemLikeService.deleteItemLike(itemId); + } +} diff --git a/src/main/java/com/dku/springstudy/controller/MemberController.java b/src/main/java/com/dku/springstudy/controller/MemberController.java index e69b8c5..903709b 100644 --- a/src/main/java/com/dku/springstudy/controller/MemberController.java +++ b/src/main/java/com/dku/springstudy/controller/MemberController.java @@ -1,24 +1,30 @@ package com.dku.springstudy.controller; +import com.dku.springstudy.domain.ImageFile; import com.dku.springstudy.domain.Member; -import com.dku.springstudy.dto.JoinDto; -import com.dku.springstudy.dto.LoginDto; -import com.dku.springstudy.dto.TokenDto; +import com.dku.springstudy.dto.*; import com.dku.springstudy.enums.Role; import com.dku.springstudy.repository.jpa.MemberRepository; +import com.dku.springstudy.security.AuthenticationProvider; +import com.dku.springstudy.service.ItemService; import com.dku.springstudy.service.MemberService; +import com.dku.springstudy.service.S3Upload; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; @Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/member") public class MemberController { - private final MemberRepository memberRepository; - private final MemberService memberService; + private final ItemService itemService; + private final S3Upload s3Upload; @PostMapping("/join") public JoinDto join(@RequestBody JoinDto joinDto){ @@ -46,4 +52,39 @@ public TokenDto reissue(@RequestBody TokenDto tokenDto){ return memberService.reissue(tokenDto); } + @GetMapping("/mypage") + public MyPageDto myPageHome(){ + return memberService.myPageHome(); + } + + @PostMapping("/mypage/update-profiles") + public void updateProfiles(@RequestPart("data") UpdateProfilesDto updateProfilesDto, + @RequestPart("images") MultipartFile multipartFile) throws IOException { + + + String uploadImageUri = s3Upload.upload(multipartFile); + ImageFile profileImageFile = ImageFile.createImageFile(uploadImageUri); + + memberService.updateProfiles(updateProfilesDto.getNickname(), profileImageFile); + } + + @GetMapping("/mypage/sales-details") + public List salesDetails(){ + Member currentMember = AuthenticationProvider.getCurrentMember(); + return itemService.findByMember(currentMember); + } + + @PostMapping("/mypage/update-sales-status") + public void updateSalesStatus(@RequestBody UpdateItemStatusDto updateItemStatusDto){ + itemService.updateItemStatus(updateItemStatusDto.getId(), updateItemStatusDto.getItemStatus()); + } + + @GetMapping("/mypage/favorite") + public List favoriteItemList(){ + return itemService.findFavoriteItems(); + } + + + + } diff --git a/src/main/java/com/dku/springstudy/domain/Item.java b/src/main/java/com/dku/springstudy/domain/Item.java index 9889e76..4b79a9b 100644 --- a/src/main/java/com/dku/springstudy/domain/Item.java +++ b/src/main/java/com/dku/springstudy/domain/Item.java @@ -5,6 +5,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; import javax.persistence.*; import java.util.ArrayList; @@ -52,4 +53,17 @@ public static Item createItem(Member member, String title, String content, int p return item; } + public void updateItem(String title, String content, Category category, int price, List updateMultipartFiles){ + this.title = title; + this.content = content; + this.category = category; + this.price = price; + this.images.clear(); + this.images.addAll(updateMultipartFiles); + } + + public void changeStatus(ItemStatus itemStatus){ + this.status = itemStatus; + } + } diff --git a/src/main/java/com/dku/springstudy/domain/ItemLike.java b/src/main/java/com/dku/springstudy/domain/ItemLike.java index 5489cac..f9d0baf 100644 --- a/src/main/java/com/dku/springstudy/domain/ItemLike.java +++ b/src/main/java/com/dku/springstudy/domain/ItemLike.java @@ -23,8 +23,11 @@ public class ItemLike extends BaseEntity{ @JoinColumn(name = "member_id") private Member member; - public void createItemLike(Item item, Member member){ - this.item = item; - this.member = member; + public static ItemLike createItemLike(Item item, Member member){ + ItemLike itemLike = new ItemLike(); + itemLike.item = item; + item.getLikes().add(itemLike); + itemLike.member = member; + return itemLike; } } diff --git a/src/main/java/com/dku/springstudy/domain/Member.java b/src/main/java/com/dku/springstudy/domain/Member.java index 1f2f498..7b48d58 100644 --- a/src/main/java/com/dku/springstudy/domain/Member.java +++ b/src/main/java/com/dku/springstudy/domain/Member.java @@ -29,6 +29,9 @@ public class Member extends BaseEntity implements UserDetails{ private String phone; private String nickname; + @OneToOne(fetch = FetchType.LAZY) + private ImageFile profileImage; + // 권한 정보(Role) @Enumerated(EnumType.STRING) private Role role; @@ -44,6 +47,10 @@ public static Member createMember(String email, String password, String name, St return member; } + public void updateMemberProfiles(String nickname, ImageFile profileImage){ + this.nickname = nickname; + } + @Override public Collection getAuthorities() { String s = this.getRole().toString(); diff --git a/src/main/java/com/dku/springstudy/dto/AddItemDto.java b/src/main/java/com/dku/springstudy/dto/AddItemDto.java index 928ba40..8e67062 100644 --- a/src/main/java/com/dku/springstudy/dto/AddItemDto.java +++ b/src/main/java/com/dku/springstudy/dto/AddItemDto.java @@ -13,7 +13,7 @@ @NoArgsConstructor public class AddItemDto { private String title; - private String category; + private Category category; private int price; private String content; } diff --git a/src/main/java/com/dku/springstudy/dto/ItemDto.java b/src/main/java/com/dku/springstudy/dto/ItemDto.java index 91fec37..05ba53f 100644 --- a/src/main/java/com/dku/springstudy/dto/ItemDto.java +++ b/src/main/java/com/dku/springstudy/dto/ItemDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto; +import com.dku.springstudy.enums.ItemStatus; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,4 +17,5 @@ public class ItemDto { private String content; private int price; private int likeCount; + private ItemStatus itemStatus; } diff --git a/src/main/java/com/dku/springstudy/dto/MyPageDto.java b/src/main/java/com/dku/springstudy/dto/MyPageDto.java new file mode 100644 index 0000000..054a1e9 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/MyPageDto.java @@ -0,0 +1,13 @@ +package com.dku.springstudy.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MyPageDto { + + private String profileImagePath; + private String nickname; + +} diff --git a/src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java b/src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java new file mode 100644 index 0000000..16ce274 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java @@ -0,0 +1,10 @@ +package com.dku.springstudy.dto; + +import com.dku.springstudy.enums.ItemStatus; +import lombok.Getter; + +@Getter +public class UpdateItemStatusDto { + private Long id; + private ItemStatus itemStatus; +} diff --git a/src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java b/src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java new file mode 100644 index 0000000..9470f27 --- /dev/null +++ b/src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java @@ -0,0 +1,10 @@ +package com.dku.springstudy.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UpdateProfilesDto { + private String nickname; +} diff --git a/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java b/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java index 5f4d574..222587b 100644 --- a/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java +++ b/src/main/java/com/dku/springstudy/repository/jpa/ItemLikeRepository.java @@ -3,10 +3,16 @@ import com.dku.springstudy.domain.Item; import com.dku.springstudy.domain.ItemLike; +import com.dku.springstudy.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; public interface ItemLikeRepository extends JpaRepository { List findByItem(Item item); + + List findByMember(Member member); + + Optional findByMemberAndItem(Member member, Item item); } diff --git a/src/main/java/com/dku/springstudy/security/AuthenticationProvider.java b/src/main/java/com/dku/springstudy/security/AuthenticationProvider.java new file mode 100644 index 0000000..370fd3b --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/AuthenticationProvider.java @@ -0,0 +1,17 @@ +package com.dku.springstudy.security; + +import com.dku.springstudy.domain.Member; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +public class AuthenticationProvider { + public static Member getCurrentMember() { + UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return userDetails.getMember(); + } + + public static Long getCurrentMemberId() { + return getCurrentMember().getId(); + } +} diff --git a/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java b/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java similarity index 70% rename from src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java rename to src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java index b45d237..4a87e96 100644 --- a/src/main/java/com/dku/springstudy/service/CustomUserDetailsService.java +++ b/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java @@ -1,4 +1,4 @@ -package com.dku.springstudy.service; +package com.dku.springstudy.security; import com.dku.springstudy.domain.Member; import com.dku.springstudy.repository.jpa.MemberRepository; @@ -15,21 +15,12 @@ public class CustomUserDetailsService implements UserDetailsService { private final MemberRepository memberRepository; - private final PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return memberRepository.findByEmail(username) // username = email - .map(this::createUserDetails) + .map(UserDetailsImpl::new) .orElseThrow(() -> new UsernameNotFoundException("해당 회원이 존재하지 않습니다.")); } - - private UserDetails createUserDetails(Member member){ - return User.builder() - .username(member.getEmail()) - .password(passwordEncoder.encode(member.getPassword())) - .roles(member.getRole().toString()) - .build(); - } } diff --git a/src/main/java/com/dku/springstudy/security/UserDetailsImpl.java b/src/main/java/com/dku/springstudy/security/UserDetailsImpl.java new file mode 100644 index 0000000..826c5fe --- /dev/null +++ b/src/main/java/com/dku/springstudy/security/UserDetailsImpl.java @@ -0,0 +1,16 @@ +package com.dku.springstudy.security; + +import com.dku.springstudy.domain.Member; +import lombok.Getter; +import org.springframework.security.core.userdetails.User; + +@Getter +public class UserDetailsImpl extends User { + + private final Member member; + + public UserDetailsImpl(Member member) { + super(member.getEmail(), member.getPassword(), member.getAuthorities()); + this.member = member; + } +} diff --git a/src/main/java/com/dku/springstudy/service/ImageFileService.java b/src/main/java/com/dku/springstudy/service/ImageFileService.java index 81d21e2..51f4b41 100644 --- a/src/main/java/com/dku/springstudy/service/ImageFileService.java +++ b/src/main/java/com/dku/springstudy/service/ImageFileService.java @@ -6,6 +6,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -17,4 +19,11 @@ public class ImageFileService { public void save(ImageFile imageFile){ imageFileRepository.save(imageFile); } + + @Transactional + public void deleteAllImages(List imageFiles){ + for (ImageFile imageFile : imageFiles) { + imageFileRepository.delete(imageFile); + } + } } diff --git a/src/main/java/com/dku/springstudy/service/ItemLikeService.java b/src/main/java/com/dku/springstudy/service/ItemLikeService.java index 61cc1ad..aa2ca87 100644 --- a/src/main/java/com/dku/springstudy/service/ItemLikeService.java +++ b/src/main/java/com/dku/springstudy/service/ItemLikeService.java @@ -2,9 +2,15 @@ import com.dku.springstudy.domain.Item; import com.dku.springstudy.domain.ItemLike; +import com.dku.springstudy.domain.Member; +import com.dku.springstudy.exception.KarrotException; import com.dku.springstudy.repository.jpa.ItemLikeRepository; +import com.dku.springstudy.repository.jpa.ItemRepository; +import com.dku.springstudy.security.AuthenticationProvider; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -13,9 +19,33 @@ public class ItemLikeService { private final ItemLikeRepository itemLikeRepository; + private final ItemRepository itemRepository; public int getLikeCountByItemId(Item item){ List itemLikes = itemLikeRepository.findByItem(item); return itemLikes.size(); } + + @Transactional + public void addItemLike(Long itemId) { + Member currentMember = AuthenticationProvider.getCurrentMember(); + Item findItem = itemRepository.findById(itemId) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "해당 상품이 존재하지 않습니다.")); + + ItemLike itemLike = ItemLike.createItemLike(findItem, currentMember); + itemLikeRepository.save(itemLike); + } + + @Transactional + public void deleteItemLike(Long itemId) { + Member currentMember = AuthenticationProvider.getCurrentMember(); + Item findItem = itemRepository.findById(itemId) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "해당 상품이 존재하지 않습니다.")); + + ItemLike deleteItemLike = itemLikeRepository.findByMemberAndItem(currentMember, findItem) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "해당 좋아요 정보가 없습니다.")); + + itemLikeRepository.delete(deleteItemLike); + + } } diff --git a/src/main/java/com/dku/springstudy/service/ItemService.java b/src/main/java/com/dku/springstudy/service/ItemService.java index 6b235d6..f27ce26 100644 --- a/src/main/java/com/dku/springstudy/service/ItemService.java +++ b/src/main/java/com/dku/springstudy/service/ItemService.java @@ -1,21 +1,37 @@ package com.dku.springstudy.service; +import com.dku.springstudy.domain.ImageFile; import com.dku.springstudy.domain.Item; +import com.dku.springstudy.domain.ItemLike; import com.dku.springstudy.domain.Member; +import com.dku.springstudy.dto.ItemDetailsDto; +import com.dku.springstudy.dto.ItemDto; +import com.dku.springstudy.enums.Category; +import com.dku.springstudy.enums.ItemStatus; import com.dku.springstudy.exception.KarrotException; +import com.dku.springstudy.repository.jpa.ItemLikeRepository; import com.dku.springstudy.repository.jpa.ItemRepository; +import com.dku.springstudy.security.AuthenticationProvider; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class ItemService { private final ItemRepository itemRepository; + private final ImageFileService imageFileService; + private final ItemLikeRepository itemLikeRepository; + private final S3Upload s3Upload; @Transactional public Long addItem(Item item){ @@ -32,7 +48,91 @@ public Item findById(Long id) { .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "해당 상품이 존재하지 않습니다.")); } - public List findByMember(Member member) { - return itemRepository.findByMember(member); + public List findByMember(Member member) { + List sellerItems = itemRepository.findByMember(member); + return sellerItems.stream() + .map(i -> new ItemDto( + i.getId(), + i.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), + i.getTitle(), + i.getContent(), + i.getPrice(), + i.getLikes().size(), + i.getStatus()) + ) + .collect(Collectors.toList()); + } + + @Transactional + public void updateItemStatus(Long id, ItemStatus itemStatus) { + Item item = itemRepository.findById(id) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "존재하지 않는 상품입니다.")); + item.changeStatus(itemStatus); + } + + + public ItemDto transferPreviousItemInfo(Long itemId) { + Item item = itemRepository.findById(itemId) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "존재하지 않는 상품입니다.")); + return new ItemDto( + item.getId(), + item.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), + item.getTitle(), + item.getContent(), + item.getPrice(), + item.getLikes().size(), + item.getStatus()); + } + + @Transactional + public void updateItem(Long itemId, String title, String content, Category category, int price, MultipartFile[] updateMultipartFiles) throws IOException { + Item item = itemRepository.findById(itemId) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "존재하지 않는 상품입니다.")); + + imageFileService.deleteAllImages(item.getImages()); + + List imageFiles = new ArrayList<>(); + + if(updateMultipartFiles.length != 0){ + for(MultipartFile multipartFile : updateMultipartFiles){ + String imagePath = s3Upload.upload(multipartFile); + ImageFile imageFile = ImageFile.createImageFile(imagePath); + imageFiles.add(imageFile); + imageFile.updateItem(item); + imageFileService.save(imageFile); + } + } + + item.updateItem(title, content, category, price, imageFiles); + + + } + + @Transactional + public void deleteItem(Long itemId) { + itemRepository.deleteById(itemId); + } + + public List findFavoriteItems() { + Member currentMember = AuthenticationProvider.getCurrentMember(); + + List itemLikesByMember = itemLikeRepository.findByMember(currentMember); + + List result = itemLikesByMember.stream() + .map(ItemLike::getItem) + .collect(Collectors.toList()); + + return result.stream() + .map(i -> new ItemDto( + i.getId(), + i.getImages().stream().map(ImageFile::getImageUrl).collect(Collectors.toList()), + i.getTitle(), + i.getContent(), + i.getPrice(), + i.getLikes().size(), + i.getStatus()) + ) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/dku/springstudy/service/MemberService.java b/src/main/java/com/dku/springstudy/service/MemberService.java index b33075c..e99e6bb 100644 --- a/src/main/java/com/dku/springstudy/service/MemberService.java +++ b/src/main/java/com/dku/springstudy/service/MemberService.java @@ -1,10 +1,13 @@ package com.dku.springstudy.service; +import com.dku.springstudy.domain.ImageFile; import com.dku.springstudy.domain.Member; import com.dku.springstudy.domain.token.RefreshToken; +import com.dku.springstudy.dto.MyPageDto; import com.dku.springstudy.exception.KarrotException; import com.dku.springstudy.repository.jpa.MemberRepository; import com.dku.springstudy.repository.redis.RefreshTokenRepository; +import com.dku.springstudy.security.AuthenticationProvider; import com.dku.springstudy.security.JwtTokenProvider; import com.dku.springstudy.dto.TokenDto; import lombok.RequiredArgsConstructor; @@ -14,6 +17,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -82,4 +86,17 @@ public Member findById(Long id){ .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "존재하지 않은 사용자 입니다.")); } + public MyPageDto myPageHome() { + Member currentMember = AuthenticationProvider.getCurrentMember(); + return new MyPageDto(currentMember.getProfileImage().getImageUrl(), currentMember.getNickname()); + } + + @Transactional + public void updateProfiles(String nickname, ImageFile profileImage) { + Long currentMemberId = AuthenticationProvider.getCurrentMemberId(); + Member updateMember = memberRepository.findById(currentMemberId) + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NO_CONTENT.value(), "존재하지 않은 사용자 입니다.")); + + updateMember.updateMemberProfiles(nickname, profileImage); + } } From d12fb0df850a85fd8d328aff70f9eb75ac39ffc2 Mon Sep 17 00:00:00 2001 From: suudh99 Date: Mon, 13 Feb 2023 00:21:26 +0900 Subject: [PATCH 11/11] add: swagger --- build.gradle | 3 + .../springstudy/SpringStudyApplication.java | 1 + .../springstudy/config/SecurityConfig.java | 8 ++- .../dku/springstudy/config/SwaggerConfig.java | 58 +++++++++++++++++++ .../dku/springstudy/config/WebMvcConfig.java | 2 + .../controller/HomeController.java | 11 +++- .../controller/ItemController.java | 43 +++++++++++++- .../controller/ItemLikeController.java | 14 +++++ .../controller/MemberController.java | 55 ++++++++++++++++-- .../controller/SwaggerController.java | 14 +++++ .../java/com/dku/springstudy/domain/Item.java | 4 +- .../com/dku/springstudy/dto/AddItemDto.java | 6 ++ .../dku/springstudy/dto/ItemDetailsDto.java | 16 +++++ .../java/com/dku/springstudy/dto/ItemDto.java | 14 +++++ .../java/com/dku/springstudy/dto/JoinDto.java | 6 ++ .../com/dku/springstudy/dto/LoginDto.java | 4 +- .../com/dku/springstudy/dto/MyPageDto.java | 4 +- .../com/dku/springstudy/dto/TokenDto.java | 6 ++ .../springstudy/dto/UpdateItemStatusDto.java | 3 + .../springstudy/dto/UpdateProfilesDto.java | 2 + .../exception/ControllerAdvisor.java | 3 + .../security/AuthenticationProvider.java | 2 +- .../security/CustomUserDetailsService.java | 2 +- .../security/JwtAuthenticationFilter.java | 2 + .../security/JwtTokenProvider.java | 16 ++++- .../security/ResponseInterceptor.java | 22 ++++--- .../springstudy/service/MemberService.java | 28 ++++++--- 27 files changed, 316 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/dku/springstudy/config/SwaggerConfig.java create mode 100644 src/main/java/com/dku/springstudy/controller/SwaggerController.java diff --git a/build.gradle b/build.gradle index e13342b..c124dac 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,9 @@ dependencies { // s3 storage implementation 'com.amazonaws:aws-java-sdk-s3:1.12.281' + // swagger + implementation 'io.springfox:springfox-boot-starter:3.0.0' + implementation 'io.springfox:springfox-swagger-ui:3.0.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/com/dku/springstudy/SpringStudyApplication.java b/src/main/java/com/dku/springstudy/SpringStudyApplication.java index 1bef475..5002d38 100644 --- a/src/main/java/com/dku/springstudy/SpringStudyApplication.java +++ b/src/main/java/com/dku/springstudy/SpringStudyApplication.java @@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import springfox.documentation.swagger2.annotations.EnableSwagger2; @EnableJpaAuditing @SpringBootApplication diff --git a/src/main/java/com/dku/springstudy/config/SecurityConfig.java b/src/main/java/com/dku/springstudy/config/SecurityConfig.java index e10067a..e1b706e 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 lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.SecurityConfigurer; 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; @@ -27,14 +28,17 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() - .antMatchers("/member/login", "/member/join", "/member/reissue").permitAll() - .antMatchers("/item/**").hasRole("USER") + .antMatchers("/member/login", "/member/join", "/member/reissue", "/swagger-ui/**", "/swagger-ui.html", "/api/usage").permitAll() + .antMatchers("/swagger-resources/**", "/swagger-ui.html", "/webjars/**", "/v2/**").permitAll() +// .antMatchers("/item/**").hasRole("USER") .anyRequest().authenticated() .and() .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) .build(); } + + @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 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..32db9b7 --- /dev/null +++ b/src/main/java/com/dku/springstudy/config/SwaggerConfig.java @@ -0,0 +1,58 @@ +package com.dku.springstudy.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.view.InternalResourceViewResolver; +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; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.HashSet; +import java.util.Set; + +@Configuration +@EnableWebMvc +public class SwaggerConfig { + private ApiInfo swaggerInfo() { + return new ApiInfoBuilder() + .title("Karrot API") + .description("Karrot API Docs") + .build(); + } + + @Bean + public Docket swaggerApi() { + return new Docket(DocumentationType.SWAGGER_2) + .consumes(getConsumeContentTypes()) + .produces(getProduceContentTypes()) + .apiInfo(swaggerInfo()).select() + .apis(RequestHandlerSelectors.basePackage("com.dku.springstudy.controller")) + .paths(PathSelectors.any()) + .build() + .useDefaultResponseMessages(false); + } + + @Bean + public InternalResourceViewResolver defaultViewResolver() { + return new InternalResourceViewResolver(); + } + + private Set getConsumeContentTypes() { + Set consumes = new HashSet<>(); + consumes.add("application/json;charset=UTF-8"); + consumes.add("application/x-www-form-urlencoded"); + return consumes; + } + + private Set getProduceContentTypes() { + Set produces = new HashSet<>(); + produces.add("application/json;charset=UTF-8"); + return produces; + } + +} diff --git a/src/main/java/com/dku/springstudy/config/WebMvcConfig.java b/src/main/java/com/dku/springstudy/config/WebMvcConfig.java index a6193ba..2bf82bf 100644 --- a/src/main/java/com/dku/springstudy/config/WebMvcConfig.java +++ b/src/main/java/com/dku/springstudy/config/WebMvcConfig.java @@ -25,4 +25,6 @@ public void addInterceptors(InterceptorRegistry registry) { .addInterceptor(responseInterceptor) .addPathPatterns("/**"); } + + } diff --git a/src/main/java/com/dku/springstudy/controller/HomeController.java b/src/main/java/com/dku/springstudy/controller/HomeController.java index 3cca437..568cefe 100644 --- a/src/main/java/com/dku/springstudy/controller/HomeController.java +++ b/src/main/java/com/dku/springstudy/controller/HomeController.java @@ -4,6 +4,9 @@ import com.dku.springstudy.domain.Item; import com.dku.springstudy.dto.ItemDto; import com.dku.springstudy.service.ItemService; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,6 +21,11 @@ public class HomeController { private final ItemService itemService; + @ApiOperation(value = "홈", notes = "등록된 전체 상품 리스트 반환") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @GetMapping("/home") public List home(){ List allItems = itemService.findAll(); @@ -28,7 +36,8 @@ public List home(){ i.getTitle(), i.getContent(), i.getPrice(), - i.getLikes().size()) + i.getLikes().size(), + i.getStatus()) ) .collect(Collectors.toList()); } diff --git a/src/main/java/com/dku/springstudy/controller/ItemController.java b/src/main/java/com/dku/springstudy/controller/ItemController.java index 652246c..be78730 100644 --- a/src/main/java/com/dku/springstudy/controller/ItemController.java +++ b/src/main/java/com/dku/springstudy/controller/ItemController.java @@ -12,6 +12,9 @@ import com.dku.springstudy.service.ItemService; import com.dku.springstudy.service.MemberService; import com.dku.springstudy.service.S3Upload; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; @@ -35,10 +38,16 @@ public class ItemController { private final ImageFileService imageFileService; private final S3Upload s3Upload; + + @ApiOperation(value = "상품 추가 API", notes = "상품 title, 상품 content, 상품 category, 상품 price, 상품 이미지들을 form-data형식으로 받아서 상품 추가") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @PostMapping("/add") private AddItemDto addItem(@RequestPart("data") AddItemDto itemDto, @RequestPart("images")MultipartFile[] multipartFiles) throws IOException { Member currentLoginMember = getCurrentLoginMember(); - + log.info("currentLoginMember={}", currentLoginMember); Item item = Item.createItem(currentLoginMember, itemDto.getTitle(), itemDto.getContent(), itemDto.getPrice(), itemDto.getCategory()); itemService.addItem(item); @@ -54,6 +63,13 @@ private AddItemDto addItem(@RequestPart("data") AddItemDto itemDto, @RequestPart return itemDto; } + + @ApiOperation(value = "상품 상세 페이지 API", notes = "상품 판매자 닉네임, 상품 카테고리, 상품 등록일(최근 수정날짜), 상품 이미지 링크들, 상품 제목, 상품 내용, 상품 가격을 반환") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) + @GetMapping("/details/{itemId}") public ItemDetailsDto details(@PathVariable("itemId") Long itemId){ Item findItem = itemService.findById(itemId); @@ -72,17 +88,36 @@ public ItemDetailsDto details(@PathVariable("itemId") Long itemId){ ); } + + @ApiOperation(value = "해당 판매자의 모든 판매중인 상품 조회", notes = "판매자의 모든 판매상품들 조회. PathVariable로 판매자 id 필요") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) + @GetMapping("/details/all/{sellerId}") public List sellerItems(@PathVariable("sellerId") Long sellerId){ Member seller = memberService.findById(sellerId); return itemService.findByMember(seller); } + + @ApiOperation(value = "상품 수정 페이지 조회", notes = "상품 수정 페이지 이동. PathVariable로 상품 id 필요. 기존의 상품 정보가 입력되어있어야 하기에 기존의 상품 정보 반환") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @GetMapping("/update/{itemId}") public ItemDto transferPreviousItemInfo(@PathVariable Long itemId){ return itemService.transferPreviousItemInfo(itemId); } + + @ApiOperation(value = "상품 수정 API", notes = "실제 상품 수정 API. PathVariable로 상품 id, form-data로 수정한 상품 정보들, 상품 이미지들 받음") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @PostMapping("/update/{itemId}") public void updateItem( @PathVariable("itemId") Long itemId, @RequestPart("data") AddItemDto updateItemDto, @@ -98,6 +133,12 @@ public void updateItem( @PathVariable("itemId") Long itemId, ); } + + @ApiOperation(value = "상품 삭제 API", notes = "상품 삭제 API. PathVariable로 상품 id 필요") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @PostMapping("/delete/{itemId}") public void deleteItem(@PathVariable Long itemId){ itemService.deleteItem(itemId); diff --git a/src/main/java/com/dku/springstudy/controller/ItemLikeController.java b/src/main/java/com/dku/springstudy/controller/ItemLikeController.java index fd563c8..4e87e63 100644 --- a/src/main/java/com/dku/springstudy/controller/ItemLikeController.java +++ b/src/main/java/com/dku/springstudy/controller/ItemLikeController.java @@ -1,6 +1,9 @@ package com.dku.springstudy.controller; import com.dku.springstudy.service.ItemLikeService; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -14,11 +17,22 @@ public class ItemLikeController { private final ItemLikeService itemLikeService; + @ApiOperation(value = "상품 좋아요 추가 API", notes = "상품 좋아요 기능") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @PostMapping("/add/{itemId}") public void addItemLike(@PathVariable Long itemId){ itemLikeService.addItemLike(itemId); } + + @ApiOperation(value = "상품 좋아요 삭제 API", notes = "상품 좋아요 취소 기능") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @PostMapping("/delete/{itemId}") public void deleteItemLike(@PathVariable Long itemId){ itemLikeService.deleteItemLike(itemId); diff --git a/src/main/java/com/dku/springstudy/controller/MemberController.java b/src/main/java/com/dku/springstudy/controller/MemberController.java index 903709b..f3484ef 100644 --- a/src/main/java/com/dku/springstudy/controller/MemberController.java +++ b/src/main/java/com/dku/springstudy/controller/MemberController.java @@ -9,6 +9,9 @@ import com.dku.springstudy.service.ItemService; import com.dku.springstudy.service.MemberService; import com.dku.springstudy.service.S3Upload; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -26,37 +29,64 @@ public class MemberController { private final ItemService itemService; private final S3Upload s3Upload; + @ApiOperation(value = "회원가입", notes = "회원가입한 회원 리턴") + @ApiResponse(code = 200, message = "API 정상 작동") @PostMapping("/join") public JoinDto join(@RequestBody JoinDto joinDto){ log.info("Join Request={}", joinDto); - Member joinMember = Member.createMember( + memberService.join( joinDto.getEmail(), joinDto.getPassword(), joinDto.getName(), joinDto.getPhone(), joinDto.getNickname(), - Role.USER - ); - memberService.join(joinMember); + Role.USER); return joinDto; } + + @ApiOperation(value = "로그인", notes = "회원 로그인 후, Token 정보 리턴") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동"), + @ApiResponse(code = 401, message = "토큰 정보 오류"), + @ApiResponse(code = 404, message = "토큰 만료 오류") + } + ) @PostMapping("/login") public TokenDto login(@RequestBody LoginDto loginDto){ log.info("login request={}", loginDto); return memberService.login(loginDto.getEmail(), loginDto.getPassword()); } + + @ApiOperation(value = "토큰 재발행", notes = "accessToken 유효기간 만료되었는데 refreshToken은 아직 유효기간 만료되지 않았으면 토큰 재발행") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동"), + @ApiResponse(code = 401, message = "토큰 정보 오류"), + @ApiResponse(code = 404, message = "토큰 만료 오류, 회원 존재하지 않음") + } + ) @PostMapping("/reissue") public TokenDto reissue(@RequestBody TokenDto tokenDto){ log.info("Reissue={}", tokenDto); return memberService.reissue(tokenDto); } + + @ApiOperation(value = "마이페이지", notes = "마이페이지 홈에서는 프로필 이미지 링크와 회원 닉네임 반환") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @GetMapping("/mypage") public MyPageDto myPageHome(){ return memberService.myPageHome(); } + @ApiOperation(value = "마이페이지의 프로필 수정", notes = "마이페이지 내의 프로필 수정 기능. 프로필 사진과 닉네임 변경 가능") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @PostMapping("/mypage/update-profiles") public void updateProfiles(@RequestPart("data") UpdateProfilesDto updateProfilesDto, @RequestPart("images") MultipartFile multipartFile) throws IOException { @@ -68,17 +98,34 @@ public void updateProfiles(@RequestPart("data") UpdateProfilesDto updateProfiles memberService.updateProfiles(updateProfilesDto.getNickname(), profileImageFile); } + @ApiOperation(value = "내 판매내역 조회", notes = "마이페이지에서 나의 판매내역 조회") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @GetMapping("/mypage/sales-details") public List salesDetails(){ Member currentMember = AuthenticationProvider.getCurrentMember(); return itemService.findByMember(currentMember); } + @ApiOperation(value = "나의 판매 상품 중에서 판매상태 변경", notes = "판맵중, 예약중, 거래완료로 상태변경 가능") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) @PostMapping("/mypage/update-sales-status") public void updateSalesStatus(@RequestBody UpdateItemStatusDto updateItemStatusDto){ itemService.updateItemStatus(updateItemStatusDto.getId(), updateItemStatusDto.getItemStatus()); } + + @ApiOperation(value = "좋아요 누른 상품 리스트 확인", notes = "좋아요 누른 상품 리스트 리턴") + @ApiResponses({ + @ApiResponse(code = 200, message = "API 정상 작동") + } + ) + @GetMapping("/mypage/favorite") public List favoriteItemList(){ return itemService.findFavoriteItems(); diff --git a/src/main/java/com/dku/springstudy/controller/SwaggerController.java b/src/main/java/com/dku/springstudy/controller/SwaggerController.java new file mode 100644 index 0000000..d3d6455 --- /dev/null +++ b/src/main/java/com/dku/springstudy/controller/SwaggerController.java @@ -0,0 +1,14 @@ +package com.dku.springstudy.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class SwaggerController { + + @GetMapping("/api/usage") + public String swaggerUi(){ + return "redirect:/swagger-ui/index.html"; + } + +} diff --git a/src/main/java/com/dku/springstudy/domain/Item.java b/src/main/java/com/dku/springstudy/domain/Item.java index 4b79a9b..4c9c9d8 100644 --- a/src/main/java/com/dku/springstudy/domain/Item.java +++ b/src/main/java/com/dku/springstudy/domain/Item.java @@ -28,7 +28,7 @@ public class Item extends BaseEntity{ private String content; private int price; - @OneToMany(mappedBy = "item") + @OneToMany(mappedBy = "item", cascade = CascadeType.REMOVE) private List images = new ArrayList<>(); @Enumerated(value = EnumType.STRING) @@ -37,7 +37,7 @@ public class Item extends BaseEntity{ @Enumerated(EnumType.STRING) private Category category; - @OneToMany(mappedBy = "item") + @OneToMany(mappedBy = "item", cascade = CascadeType.REMOVE) private List likes = new ArrayList<>(); diff --git a/src/main/java/com/dku/springstudy/dto/AddItemDto.java b/src/main/java/com/dku/springstudy/dto/AddItemDto.java index 8e67062..ec78c2b 100644 --- a/src/main/java/com/dku/springstudy/dto/AddItemDto.java +++ b/src/main/java/com/dku/springstudy/dto/AddItemDto.java @@ -2,6 +2,7 @@ import com.dku.springstudy.domain.ImageFile; import com.dku.springstudy.enums.Category; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -12,8 +13,13 @@ @AllArgsConstructor @NoArgsConstructor public class AddItemDto { + + @ApiModelProperty(value = "상품 제목", dataType = "string") private String title; + @ApiModelProperty(value = "상품 카테고리", dataType = "string") private Category category; + @ApiModelProperty(value = "상품 가격", dataType = "int") private int price; + @ApiModelProperty(value = "상품 내용", dataType = "string") private String content; } diff --git a/src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java b/src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java index 6b2a44a..8fc4141 100644 --- a/src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java +++ b/src/main/java/com/dku/springstudy/dto/ItemDetailsDto.java @@ -1,6 +1,7 @@ package com.dku.springstudy.dto; import com.dku.springstudy.domain.Item; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Getter; @@ -10,12 +11,27 @@ @Getter @AllArgsConstructor public class ItemDetailsDto { + @ApiModelProperty(value = "판매자 닉네임", dataType = "string") private String sellerNickname; + + @ApiModelProperty(value = "상품 카테고리", dataType = "string") private String category; + + @ApiModelProperty(value = "상품 등록일(최근 수정일)", dataType = "LocalDateTime") private LocalDateTime lastModifiedDate; + + @ApiModelProperty(value = "상품 이미지 경로들", dataType = "List") private List imagePath; + + @ApiModelProperty(value = "상품 제목", dataType = "string") private String title; + + @ApiModelProperty(value = "상품 내용", dataType = "string") private String content; + + @ApiModelProperty(value = "상품 가격", dataType = "int") private int price; + + @ApiModelProperty(value = "판매자의 모든 상품 내역", dataType = "List") private List sellerItems; } diff --git a/src/main/java/com/dku/springstudy/dto/ItemDto.java b/src/main/java/com/dku/springstudy/dto/ItemDto.java index 05ba53f..1c28fc9 100644 --- a/src/main/java/com/dku/springstudy/dto/ItemDto.java +++ b/src/main/java/com/dku/springstudy/dto/ItemDto.java @@ -1,6 +1,7 @@ package com.dku.springstudy.dto; import com.dku.springstudy.enums.ItemStatus; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,11 +12,24 @@ @AllArgsConstructor @NoArgsConstructor public class ItemDto { + @ApiModelProperty(value = "상품 id", dataType = "Long") private Long id; + + @ApiModelProperty(value = "상품 이미지 경로들", dataType = "List") private List imagePath; + + @ApiModelProperty(value = "상품 제목", dataType = "string") private String title; + + @ApiModelProperty(value = "상품 내용", dataType = "string") private String content; + + @ApiModelProperty(value = "상품 가격", dataType = "price") private int price; + + @ApiModelProperty(value = "상품 좋아요 개수", dataType = "int") private int likeCount; + + @ApiModelProperty(value = "상품 상태", dataType = "string") private ItemStatus itemStatus; } diff --git a/src/main/java/com/dku/springstudy/dto/JoinDto.java b/src/main/java/com/dku/springstudy/dto/JoinDto.java index 4881615..a748c50 100644 --- a/src/main/java/com/dku/springstudy/dto/JoinDto.java +++ b/src/main/java/com/dku/springstudy/dto/JoinDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.*; @Builder @@ -7,10 +8,15 @@ @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class JoinDto { + @ApiModelProperty(value = "이메일", dataType = "string") private String email; + @ApiModelProperty(value = "비밀번호", dataType = "string") private String password; + @ApiModelProperty(value = "이름", dataType = "string") private String name; + @ApiModelProperty(value = "전화번호", dataType = "string") private String phone; + @ApiModelProperty(value = "닉네임", dataType = "string") private String nickname; } diff --git a/src/main/java/com/dku/springstudy/dto/LoginDto.java b/src/main/java/com/dku/springstudy/dto/LoginDto.java index c23304e..10c4b50 100644 --- a/src/main/java/com/dku/springstudy/dto/LoginDto.java +++ b/src/main/java/com/dku/springstudy/dto/LoginDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -8,8 +9,9 @@ @AllArgsConstructor @NoArgsConstructor public class LoginDto { - + @ApiModelProperty(value = "이메일", dataType = "string") private String email; + @ApiModelProperty(value = "비밀번호", dataType = "string") private String password; diff --git a/src/main/java/com/dku/springstudy/dto/MyPageDto.java b/src/main/java/com/dku/springstudy/dto/MyPageDto.java index 054a1e9..e3092f8 100644 --- a/src/main/java/com/dku/springstudy/dto/MyPageDto.java +++ b/src/main/java/com/dku/springstudy/dto/MyPageDto.java @@ -1,13 +1,15 @@ package com.dku.springstudy.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public class MyPageDto { - + @ApiModelProperty(value = "프로필 이미지 경로", dataType = "string") private String profileImagePath; + @ApiModelProperty(value = "닉네임", dataType = "string") private String nickname; } diff --git a/src/main/java/com/dku/springstudy/dto/TokenDto.java b/src/main/java/com/dku/springstudy/dto/TokenDto.java index 3f28572..57ac467 100644 --- a/src/main/java/com/dku/springstudy/dto/TokenDto.java +++ b/src/main/java/com/dku/springstudy/dto/TokenDto.java @@ -1,5 +1,6 @@ package com.dku.springstudy.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -9,8 +10,13 @@ @AllArgsConstructor public class TokenDto { + @ApiModelProperty(value = "토큰 타입(Bearer사용)", dataType = "string") private String grantType; // Bearer + + @ApiModelProperty(value = "Access Token", dataType = "string") private String accessToken; + + @ApiModelProperty(value = "Refresh Token", dataType = "string") private String refreshToken; public TokenDto(){ diff --git a/src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java b/src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java index 16ce274..d830d2f 100644 --- a/src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java +++ b/src/main/java/com/dku/springstudy/dto/UpdateItemStatusDto.java @@ -1,10 +1,13 @@ package com.dku.springstudy.dto; import com.dku.springstudy.enums.ItemStatus; +import io.swagger.annotations.ApiModelProperty; import lombok.Getter; @Getter public class UpdateItemStatusDto { + @ApiModelProperty(value = "수정할 상품 id", dataType = "Long") private Long id; + @ApiModelProperty(value = "수정할 상품 상태", dataType = "string") private ItemStatus itemStatus; } diff --git a/src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java b/src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java index 9470f27..a3b7130 100644 --- a/src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java +++ b/src/main/java/com/dku/springstudy/dto/UpdateProfilesDto.java @@ -1,10 +1,12 @@ package com.dku.springstudy.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public class UpdateProfilesDto { + @ApiModelProperty(value = "수정할 회원 닉네임", dataType = "string") private String nickname; } diff --git a/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java b/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java index 964257e..64dd955 100644 --- a/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java +++ b/src/main/java/com/dku/springstudy/exception/ControllerAdvisor.java @@ -1,14 +1,17 @@ package com.dku.springstudy.exception; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice +@Slf4j public class ControllerAdvisor { @ExceptionHandler(value = KarrotException.class) protected ResponseEntity exceptionHandler(KarrotException e){ + log.error("KarrotException : ", e); return ResponseEntity.status(e.getHttpStatus()).body(new KarrotException(e.getHttpStatus(), e.getCode(), e.getMessage())); } } diff --git a/src/main/java/com/dku/springstudy/security/AuthenticationProvider.java b/src/main/java/com/dku/springstudy/security/AuthenticationProvider.java index 370fd3b..ad522ca 100644 --- a/src/main/java/com/dku/springstudy/security/AuthenticationProvider.java +++ b/src/main/java/com/dku/springstudy/security/AuthenticationProvider.java @@ -7,7 +7,7 @@ @Component public class AuthenticationProvider { public static Member getCurrentMember() { - UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return userDetails.getMember(); } diff --git a/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java b/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java index 4a87e96..0a0e705 100644 --- a/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java +++ b/src/main/java/com/dku/springstudy/security/CustomUserDetailsService.java @@ -18,7 +18,7 @@ public class CustomUserDetailsService implements UserDetailsService { @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + public UserDetailsImpl loadUserByUsername(String username) throws UsernameNotFoundException { return memberRepository.findByEmail(username) // username = email .map(UserDetailsImpl::new) .orElseThrow(() -> new UsernameNotFoundException("해당 회원이 존재하지 않습니다.")); diff --git a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java index c2845bc..4e3892e 100644 --- a/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/dku/springstudy/security/JwtAuthenticationFilter.java @@ -5,6 +5,7 @@ import io.jsonwebtoken.ExpiredJwtException; 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; @@ -52,6 +53,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } catch (ExpiredJwtException e){ log.info("Expired JWT token", e); + throw new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "토큰 유효기간 만료"); } catch (KarrotException e){ response.setStatus(e.getHttpStatus().value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); diff --git a/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java index 7be0056..74f6fe7 100644 --- a/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java +++ b/src/main/java/com/dku/springstudy/security/JwtTokenProvider.java @@ -2,6 +2,8 @@ import com.dku.springstudy.domain.token.RefreshToken; import com.dku.springstudy.dto.TokenDto; +import com.dku.springstudy.exception.KarrotException; +import com.dku.springstudy.repository.jpa.MemberRepository; import com.dku.springstudy.repository.redis.RefreshTokenRepository; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; @@ -9,12 +11,14 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import java.security.Key; @@ -29,12 +33,14 @@ public class JwtTokenProvider { private final Key key; private final RefreshTokenRepository refreshTokenRepository; + private final MemberRepository memberRepository; @Autowired - public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, RefreshTokenRepository refreshTokenRepository){ + public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, RefreshTokenRepository refreshTokenRepository, MemberRepository memberRepository){ byte[] bytes = Decoders.BASE64.decode(secretKey); this.key = Keys.hmacShaKeyFor(bytes); this.refreshTokenRepository = refreshTokenRepository; + this.memberRepository = memberRepository; } // authentication 객체를 기반으로 access token, refresh token 생성 @@ -89,7 +95,9 @@ public Authentication getAuthentication(String accessToken){ .collect(Collectors.toList()); // UserDetails 객체 만들어서 Authentication return - UserDetails principal = new User(claims.getSubject(), "", authorities); + UserDetailsImpl principal = memberRepository.findByEmail(claims.getSubject()) + .map(UserDetailsImpl::new) + .orElseThrow(() -> new UsernameNotFoundException("Can't find User")); return new UsernamePasswordAuthenticationToken(principal, "", authorities); } @@ -100,12 +108,14 @@ public boolean validateToken(String token){ return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { log.info("Invalid JWT Token", e); + throw new KarrotException(HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED.value(),"토큰 인증 오류 발생"); } catch (UnsupportedJwtException e) { log.info("Unsupported JWT Token", e); + throw new KarrotException(HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED.value(),"토큰 인증 오류 발생"); } catch (IllegalArgumentException e) { log.info("JWT claims string is empty.", e); + throw new KarrotException(HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED.value(),"토큰 인증 오류 발생"); } - return false; } private Claims getClaims(String accessToken) { diff --git a/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java index e4cfb33..fc9fa72 100644 --- a/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java +++ b/src/main/java/com/dku/springstudy/security/ResponseInterceptor.java @@ -6,11 +6,14 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; import org.springframework.web.util.ContentCachingResponseWrapper; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; @Slf4j @Component @@ -21,15 +24,20 @@ public class ResponseInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { ContentCachingResponseWrapper res = (ContentCachingResponseWrapper) response; - String contentString = new String(res.getContentAsByteArray()); - Object readValue = objectMapper.readValue(contentString, Object.class); - ResponseEntity objectResponseEntity = new ResponseEntity<>(readValue, HttpStatus.OK); - log.info("objectResponseEntity={}", objectResponseEntity); - String wrappedBody = objectMapper.writeValueAsString(objectResponseEntity); - res.resetBuffer(); - res.getOutputStream().write(wrappedBody.getBytes(), 0, wrappedBody.getBytes().length); + if(!request.getRequestURI().contains("swagger")){ + if(!request.getRequestURI().contains("v2")){ + String contentString = new String(res.getContentAsByteArray()); + Object readValue = objectMapper.readValue(contentString, Object.class); + + ResponseEntity objectResponseEntity = new ResponseEntity<>(readValue, HttpStatus.OK); + String wrappedBody = objectMapper.writeValueAsString(objectResponseEntity); + res.resetBuffer(); + res.getOutputStream().write(wrappedBody.getBytes(), 0, wrappedBody.getBytes().length); + } + } res.copyBodyToResponse(); + } } diff --git a/src/main/java/com/dku/springstudy/service/MemberService.java b/src/main/java/com/dku/springstudy/service/MemberService.java index e99e6bb..79d4818 100644 --- a/src/main/java/com/dku/springstudy/service/MemberService.java +++ b/src/main/java/com/dku/springstudy/service/MemberService.java @@ -4,6 +4,7 @@ import com.dku.springstudy.domain.Member; import com.dku.springstudy.domain.token.RefreshToken; import com.dku.springstudy.dto.MyPageDto; +import com.dku.springstudy.enums.Role; import com.dku.springstudy.exception.KarrotException; import com.dku.springstudy.repository.jpa.MemberRepository; import com.dku.springstudy.repository.redis.RefreshTokenRepository; @@ -13,16 +14,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - @Slf4j @Service @RequiredArgsConstructor @@ -33,10 +31,22 @@ public class MemberService { private final AuthenticationManagerBuilder authenticationManagerBuilder; private final RefreshTokenRepository refreshTokenRepository; + private final PasswordEncoder passwordEncoder; + @Transactional - public Long join(Member member){ - memberRepository.save(member); - return member.getId(); + public Long join(String email, String password, String name, String phone, String nickname, Role role){ + String encodedPassword = passwordEncoder.encode(password); + + Member joinMember = Member.createMember( + email, + encodedPassword, + name, + phone, + nickname, + role + ); + memberRepository.save(joinMember); + return joinMember.getId(); } @Transactional @@ -64,9 +74,9 @@ public TokenDto reissue(TokenDto token){ // redis에서 refresh Token 가져오기 RefreshToken refreshToken = refreshTokenRepository.findById(authentication.getName()) - .orElseThrow(() -> new RuntimeException("Refresh Token이 존재하지 않습니다.")); + .orElseThrow(() -> new KarrotException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.value(), "Refresh Token이 존재하지 않습니다.")); if(!token.getRefreshToken().equals(refreshToken.getRefreshToken())){ - throw new RuntimeException("토큰 정보 유효성 검증 실패"); + throw new KarrotException(HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED.value(), "토큰 정보 유효성 검증 실패"); } TokenDto reissuedTokenDto = jwtTokenProvider.generateToken(authentication);