From fbdaacfdeed3bcee3ee44b36245a045ce994eebc Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Wed, 27 Nov 2019 15:25:41 +0900 Subject: [PATCH 01/24] =?UTF-8?q?=EB=A9=94=EB=89=B4,=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=20=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/delfood/service/MenuServiceTest.java | 133 ++++++++++++++++++ .../delfood/service/OptionServiceTest.java | 83 +++++++++++ 2 files changed, 216 insertions(+) create mode 100644 src/test/java/com/delfood/service/MenuServiceTest.java create mode 100644 src/test/java/com/delfood/service/OptionServiceTest.java diff --git a/src/test/java/com/delfood/service/MenuServiceTest.java b/src/test/java/com/delfood/service/MenuServiceTest.java new file mode 100644 index 0000000..1b729c1 --- /dev/null +++ b/src/test/java/com/delfood/service/MenuServiceTest.java @@ -0,0 +1,133 @@ +package com.delfood.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.*; + +import com.delfood.dto.MenuDTO; +import com.delfood.dto.MenuDTO.Status; +import com.delfood.dto.OptionDTO; +import com.delfood.mapper.MenuMapper; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class MenuServiceTest { + + @InjectMocks + MenuService service; + + @Mock + MenuMapper mapper; + + public MenuDTO generateMenu() { + MenuDTO menu = new MenuDTO(); + menu.setId(1L); + menu.setMenuGroupId(1L); + menu.setContent("Test Menu Content"); + menu.setOptionList(new ArrayList()); + menu.setPhoto("Test Photo URL"); + menu.setPrice(12000L); + menu.setPriority(1L); + menu.setStatus(Status.DEFAULT); + menu.setCreatedAt(LocalDateTime.now()); + menu.setUpdatedAt(LocalDateTime.now()); + return menu; + } + + @Test + public void getMenuInfoTest_메뉴_조회_성공() { + MenuDTO menu = generateMenu(); + given(mapper.findById(1L)).willReturn(menu); + given(mapper.findById(999L)).willReturn(null); + assertThat(service.getMenuInfo(1L)).isEqualTo(menu); + assertThat(service.getMenuInfo(999L)).isNull(); + } + + @Test + public void addMenuTest_메뉴_추가_성공() { + MenuDTO menu = generateMenu(); + given(mapper.insertMenu(menu)).willReturn(menu.getId()); + service.addMenu(menu); + } + + @Test + public void deleteMenu_메뉴_삭제_성공() { + given(mapper.deleteMenu(1L)).willReturn(1); + service.deleteMenu(1L); + } + + @Test(expected = RuntimeException.class) + public void deleteMenu_메뉴_삭제_실패() { + given(mapper.deleteMenu(1L)).willReturn(0); + service.deleteMenu(1L); + } + + @Test + public void checkMenuTest_메뉴존재_체크_성공() { + given(mapper.checkMenu(1L, 1L)).willReturn(1); + service.checkMenu(1L, 1L); + } + + @Test(expected = RuntimeException.class) + public void checkMenuTest_메뉴존재_체크_없음() { + given(mapper.checkMenu(1L, 1L)).willReturn(0); + service.checkMenu(1L, 1L); + } + + @Test(expected = RuntimeException.class) + public void checkMenuTest_메뉴존재_체크_실패() { + given(mapper.checkMenu(1L, 1L)).willReturn(999); + service.checkMenu(1L, 1L); + } + + @Test + public void updateMenuPriorityTest_메뉴_순서_변경_성공() { + given(mapper.totalCount(1L)).willReturn(3); + given(mapper.updateMenuPriority(anyLong(), anyInt())).willReturn(1); + List idList = LongStream.of(1,2,3).boxed().collect(Collectors.toList()); + + service.updateMenuPriority(1L, idList); + } + + @Test(expected = RuntimeException.class) + public void updateMenuPriorityTest_메뉴_순서_변경_실패() { + given(mapper.totalCount(1L)).willReturn(0); + List idList = LongStream.of(1,2,3).boxed().collect(Collectors.toList()); + + service.updateMenuPriority(1L, idList); + } + + @Test(expected = RuntimeException.class) + public void updateMenuPriorityTest_메뉴_순서_변경_실패2() { + given(mapper.totalCount(1L)).willReturn(3); + given(mapper.updateMenuPriority(anyLong(), anyInt())).willReturn(0); + List idList = LongStream.of(1,2,3).boxed().collect(Collectors.toList()); + + service.updateMenuPriority(1L, idList); + } + + @Test + public void updateMenuTest_메뉴_정보_수정_성공() { + MenuDTO menu = generateMenu(); + given(mapper.updateMenu(menu)).willReturn(1); + service.updateMenu(menu); + } + + @Test(expected = RuntimeException.class) + public void updateMenuTest_메뉴_정보_수정_실패() { + MenuDTO menu = generateMenu(); + given(mapper.updateMenu(menu)).willReturn(0); + service.updateMenu(menu); + } + + + +} diff --git a/src/test/java/com/delfood/service/OptionServiceTest.java b/src/test/java/com/delfood/service/OptionServiceTest.java new file mode 100644 index 0000000..214187c --- /dev/null +++ b/src/test/java/com/delfood/service/OptionServiceTest.java @@ -0,0 +1,83 @@ +package com.delfood.service; + + +import static org.mockito.BDDMockito.*; + +import com.delfood.dto.OptionDTO; +import com.delfood.dto.OptionDTO.Status; +import com.delfood.mapper.OptionMapper; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class OptionServiceTest { + + @InjectMocks + OptionService service; + + @Mock + OptionMapper mapper; + + public OptionDTO generateOption() { + OptionDTO option = new OptionDTO(); + option.setId(1L); + option.setMenuId(1L); + option.setName("Test Option Name"); + option.setPrice(10000L); + option.setStatus(Status.DEFAULT); + return option; + } + + @Test + public void addOptionTest_옵션_추가_성공() { + OptionDTO option = generateOption(); + given(mapper.insertOption(option)).willReturn(1); + service.addOption(option); + } + + @Test(expected = RuntimeException.class) + public void addOptionTest_옵션_추가_실패_이름_null() { + OptionDTO option = generateOption(); + option.setName(null); + service.addOption(option); + } + + @Test(expected = RuntimeException.class) + public void addOptionTest_옵션_추가_실패_가격_null() { + OptionDTO option = generateOption(); + option.setPrice(null); + service.addOption(option); + } + + @Test(expected = RuntimeException.class) + public void addOptionTest_옵션_추가_실패_메뉴ID_null() { + OptionDTO option = generateOption(); + option.setMenuId(null); + service.addOption(option); + } + + @Test(expected = RuntimeException.class) + public void addOptionTest_옵션_추가_실패_DB에러() { + OptionDTO option = generateOption(); + given(mapper.insertOption(option)).willReturn(0); + service.addOption(option); + } + + public void addOptionList_옵션_리스트_추가_성공() { + List options = new ArrayList(); + for (long l = 1; l <= 3; l++) { + OptionDTO option = generateOption(); + option.setId(l); + options.add(option); + } + + given(mapper.insertOptionList(options, 1L)).willReturn(1); + service.addOptionList(options, 1L); + } + +} From ab9f140cdd95257d0c7f174f716c8fe2974bf3be Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Thu, 28 Nov 2019 13:30:07 +0900 Subject: [PATCH 02/24] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20-=20import=20*=20=EC=A0=9C=EA=B1=B0=20-=20=EB=B9=84=EC=8A=B7?= =?UTF-8?q?=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 나머지 내용은 본 로직이 수정되어야 해서 코드 수정해달라고 요청하였습니다. --- .../com/delfood/service/MenuServiceTest.java | 4 +- .../delfood/service/OptionServiceTest.java | 57 ++++++++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/test/java/com/delfood/service/MenuServiceTest.java b/src/test/java/com/delfood/service/MenuServiceTest.java index 1b729c1..b1e703b 100644 --- a/src/test/java/com/delfood/service/MenuServiceTest.java +++ b/src/test/java/com/delfood/service/MenuServiceTest.java @@ -1,7 +1,9 @@ package com.delfood.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.*; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; import com.delfood.dto.MenuDTO; import com.delfood.dto.MenuDTO.Status; diff --git a/src/test/java/com/delfood/service/OptionServiceTest.java b/src/test/java/com/delfood/service/OptionServiceTest.java index 214187c..87e1e50 100644 --- a/src/test/java/com/delfood/service/OptionServiceTest.java +++ b/src/test/java/com/delfood/service/OptionServiceTest.java @@ -1,13 +1,14 @@ package com.delfood.service; - -import static org.mockito.BDDMockito.*; +import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.given; import com.delfood.dto.OptionDTO; import com.delfood.dto.OptionDTO.Status; import com.delfood.mapper.OptionMapper; import java.util.ArrayList; import java.util.List; +import lombok.extern.log4j.Log4j2; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -15,6 +16,7 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) +@Log4j2 public class OptionServiceTest { @InjectMocks @@ -40,25 +42,37 @@ public OptionDTO generateOption() { service.addOption(option); } - @Test(expected = RuntimeException.class) - public void addOptionTest_옵션_추가_실패_이름_null() { - OptionDTO option = generateOption(); - option.setName(null); - service.addOption(option); - } - - @Test(expected = RuntimeException.class) - public void addOptionTest_옵션_추가_실패_가격_null() { - OptionDTO option = generateOption(); - option.setPrice(null); - service.addOption(option); - } - - @Test(expected = RuntimeException.class) - public void addOptionTest_옵션_추가_실패_메뉴ID_null() { - OptionDTO option = generateOption(); - option.setMenuId(null); - service.addOption(option); + @Test + public void addOptionTest_옵션_추가_실패_null() { + try { + OptionDTO option = generateOption(); + option.setName(null); + service.addOption(option); + log.info("name null check 실패"); + fail(); + } catch (RuntimeException e) { + log.info("name null check 성공"); + } + + try { + OptionDTO option = generateOption(); + option.setPrice(null); + service.addOption(option); + log.info("price null check 실패"); + fail(); + } catch (RuntimeException e) { + log.info("price null check 성공"); + } + + try { + OptionDTO option = generateOption(); + option.setMenuId(null); + service.addOption(option); + log.info("menuId null check 실패"); + fail(); + } catch (RuntimeException e) { + log.info("menuId null check 성공"); + } } @Test(expected = RuntimeException.class) @@ -68,6 +82,7 @@ public OptionDTO generateOption() { service.addOption(option); } + @Test public void addOptionList_옵션_리스트_추가_성공() { List options = new ArrayList(); for (long l = 1; l <= 3; l++) { From a1b5b1eef7493039f6ee937d8308e0e35506cfc5 Mon Sep 17 00:00:00 2001 From: binaryyoung Date: Fri, 29 Nov 2019 17:05:26 +0900 Subject: [PATCH 03/24] =?UTF-8?q?Owner=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/exception/DuplicateException.java | 7 + .../com/delfood/service/OwnerService.java | 32 +-- .../com/delfood/service/OwnerServiceTest.java | 200 ++++++++++++++++-- 3 files changed, 206 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/delfood/error/exception/DuplicateException.java diff --git a/src/main/java/com/delfood/error/exception/DuplicateException.java b/src/main/java/com/delfood/error/exception/DuplicateException.java new file mode 100644 index 0000000..902cca2 --- /dev/null +++ b/src/main/java/com/delfood/error/exception/DuplicateException.java @@ -0,0 +1,7 @@ +package com.delfood.error.exception; + +public class DuplicateException extends RuntimeException{ + public DuplicateException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/delfood/service/OwnerService.java b/src/main/java/com/delfood/service/OwnerService.java index 79bbf20..d217fd5 100644 --- a/src/main/java/com/delfood/service/OwnerService.java +++ b/src/main/java/com/delfood/service/OwnerService.java @@ -1,17 +1,14 @@ package com.delfood.service; import com.delfood.dto.OwnerDTO; +import com.delfood.error.exception.DuplicateException; import com.delfood.error.exception.DuplicateIdException; import com.delfood.mapper.OwnerMapper; import com.delfood.utils.SHA256Util; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.HttpStatusCodeException; @Service @@ -86,8 +83,9 @@ public OwnerDTO getOwner(String id) { @Transactional(rollbackFor = RuntimeException.class) public void updateOwnerMailAndTel(String id, String password, String mail, String tel) { // 정보 변경시 패스워드를 입력받는다. 해당 패스워드가 틀릴 시 정보는 변경되지 않는다. - if (ownerMapper.findByIdAndPassword(id, password) == null) { - throw new IllegalArgumentException("패스워드가 일치하지 않습니다"); + if (ownerMapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(password)) == null) { + log.error("password does not match"); + throw new IllegalArgumentException("password does not match"); } int result = ownerMapper.updateMailAndTel(id, mail, tel); @@ -101,20 +99,26 @@ public void updateOwnerMailAndTel(String id, String password, String mail, Strin * 사장 비밀번호 수정. * * @param id 아이디 - * @param passwordAfterChange 변경할 비밀번호 + * @param beforePassword 변경전 비밀번호 + * @param afterPassword 변경할 비밀번호 * @return */ @Transactional(rollbackFor = RuntimeException.class) // runtimeException이 발생하면 rollback을 수행한다. - public void updateOwnerPassword(String id, String passwordBeforeChange, String passwordAfterChange) { - if (ownerMapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(passwordBeforeChange)) == null) { // 아이디와 비밀번호 불일치 - throw new IllegalArgumentException(); - } else if (passwordBeforeChange.equals(SHA256Util.encryptSHA256(passwordAfterChange))) { // 이전 패스워드와 동일한 경우 - throw new HttpStatusCodeException(HttpStatus.CONFLICT, "변경 전 패스워드와 중복됩니다") {}; + public void updateOwnerPassword(String id, String beforePassword, String afterPassword) { + + if (ownerMapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(beforePassword)) + == null) { + log.error("id and password do not match id : {}, password : {}",id,beforePassword); + throw new IllegalArgumentException("id and password do not match"); + } else if (beforePassword.equals(afterPassword)) { + log.error("password duplication before: {}, after : {}", + beforePassword, afterPassword); + throw new DuplicateException("password duplication"); } - String cryptoPassword = SHA256Util.encryptSHA256(passwordAfterChange); + String cryptoPassword = SHA256Util.encryptSHA256(afterPassword); int result = ownerMapper.updatePassword(id, cryptoPassword); if (result != 1) { - log.error("updateOwnerPassword ERROR! id : {}, password : {}", id, passwordAfterChange); + log.error("updateOwnerPassword ERROR! id : {}, password : {}", id, afterPassword); throw new RuntimeException("password update error"); } diff --git a/src/test/java/com/delfood/service/OwnerServiceTest.java b/src/test/java/com/delfood/service/OwnerServiceTest.java index 33483bc..5ced618 100644 --- a/src/test/java/com/delfood/service/OwnerServiceTest.java +++ b/src/test/java/com/delfood/service/OwnerServiceTest.java @@ -1,13 +1,10 @@ package com.delfood.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import com.delfood.dto.OwnerDTO; +import com.delfood.error.exception.DuplicateException; import com.delfood.mapper.OwnerMapper; import com.delfood.utils.SHA256Util; import org.junit.Test; @@ -26,38 +23,203 @@ public class OwnerServiceTest { @Mock // mock 생성 OwnerMapper mapper; + /** + * owner 정보 생성. + * @return + */ + public OwnerDTO generateOwner() { + return OwnerDTO.builder() + .id("ljy2134") + .password(SHA256Util.encryptSHA256("2134")) + .name("이진영") + .mail("asdf@naver.com") + .tel("010-3333-3333") + .build(); + } + /** * 회원가입 성공 테스트. */ @Test public void signUp_success() { - OwnerDTO ownerInfo = OwnerDTO.builder() - .id("ljy2134") - .password(SHA256Util.encryptSHA256("2134")) - .name("이진영") - .mail("asdf@naver.com") - .tel("010-3333-3333") - .build(); + OwnerDTO ownerInfo = generateOwner(); given(mapper.insertOwner(ownerInfo)).willReturn(1); + given(mapper.idCheck(ownerInfo.getId())).willReturn(0); + service.signUp(ownerInfo); } /** - * 회원가입 실패 테스트. + * 회원가입 실패 테스트. (DB insert 실패) */ @Test(expected = RuntimeException.class) public void signUp_fail() { - OwnerDTO ownerInfo = OwnerDTO.builder() - .id("ljy2134") - .password(SHA256Util.encryptSHA256("2134")) - .name("이진영") - .mail("asdf@naver.com") - .tel("010-3333-3333") - .build(); + OwnerDTO ownerInfo = generateOwner(); + given(mapper.idCheck(ownerInfo.getId())).willReturn(0); given(mapper.insertOwner(ownerInfo)).willReturn(0); + service.signUp(ownerInfo); } + /** + * 회원가입 실패 테스트. (아이디 중복 발생) + */ + @Test(expected = RuntimeException.class) + public void signUp_fail2() { + OwnerDTO ownerInfo = generateOwner(); + + given(mapper.idCheck(ownerInfo.getId())).willReturn(1); + + service.signUp(ownerInfo); + } + + /** + * 아이디 중복 체크 성공 테스트. + */ + @Test + public void isDuplicatedId_success() { + String duplicatedId = "duplicatedId"; + String noDuplicatedId = "noDuplicatedId"; + given(mapper.idCheck(duplicatedId)).willReturn(1); + given(mapper.idCheck(noDuplicatedId)).willReturn(0); + + assertThat(service.isDuplicatedId(duplicatedId)).isTrue(); + assertThat(service.isDuplicatedId(noDuplicatedId)).isFalse(); + } + + /** + * 사장 정보 조회 성공 테스트. + */ + @Test + public void getOwner_success() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String password = ownerInfo.getPassword(); + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(password))) + .willReturn(ownerInfo); + given(mapper.findById(id)).willReturn(ownerInfo); + + assertThat(service.getOwner(id, password)).isEqualTo(ownerInfo); + assertThat(service.getOwner(id)).isEqualTo(ownerInfo); + } + + /** + * 사장 이메일, 전화번호 수정 성공 테스트. + */ + @Test + public void updateOwnerMailAndTel_success() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String password = ownerInfo.getPassword(); + String mail = ownerInfo.getMail(); + String tel = ownerInfo.getTel(); + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(password))).willReturn(ownerInfo); + given(mapper.updateMailAndTel(id, mail, tel)).willReturn(1); + + service.updateOwnerMailAndTel(id, password, mail, tel); + } + + /** + * 사장 이메일, 전화번호 수정 실패 테스트. (아이디, 패스워드 검증 실패) + */ + @Test(expected = IllegalArgumentException.class) + public void updateOwnerMailAndTel_fail() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String password = ownerInfo.getPassword(); + String mail = ownerInfo.getMail(); + String tel = ownerInfo.getTel(); + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(password))).willReturn(null); + + service.updateOwnerMailAndTel(id, password, mail, tel); + } + + /** + * 사장 이메일, 전화번호 수정 실패 테스트. (DB update 실패) + */ + @Test(expected = RuntimeException.class) + public void updateOwnerMailAndTel_fail2() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String password = ownerInfo.getPassword(); + String mail = ownerInfo.getMail(); + String tel = ownerInfo.getTel(); + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(password))).willReturn(ownerInfo); + given(mapper.updateMailAndTel(id, mail, tel)).willReturn(0); + + service.updateOwnerMailAndTel(id, password, mail, tel); + } + + + /** + * 사장 비밀번호 수정 성공 테스트. + */ + @Test + public void updateOwnerPassword_success() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String beforePassword = ownerInfo.getPassword(); + String afterPassword = "asdfasdf"; + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(beforePassword))) + .willReturn(ownerInfo); + given(mapper.updatePassword(id, SHA256Util.encryptSHA256(afterPassword))).willReturn(1); + + service.updateOwnerPassword(id, beforePassword, afterPassword); + } + + /** + * 사장 비밀번호 수정 실패 테스트. (아이디와 기존 패스워드 매칭 실패) + */ + @Test(expected = IllegalArgumentException.class) + public void updateOwnerPassword_fail() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String beforePassword = "strangePassword"; + String afterPassword = "asdfasdf"; + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(beforePassword))) + .willReturn(null); + + service.updateOwnerPassword(id, beforePassword, afterPassword); + } + + /** + * 사장 비밀번호 수정 실패 테스트. (변경 전 패스워드와 변경 후 패스워드가 일치) + */ + @Test(expected = DuplicateException.class) + public void updateOwnerPassword_fail2() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String beforePassword = ownerInfo.getPassword(); + String afterPassword = ownerInfo.getPassword(); + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(beforePassword))) + .willReturn(ownerInfo); + + service.updateOwnerPassword(id, beforePassword, afterPassword); + } + + /** + * 사장 비밀번호 수정 실패 테스트. (변경 전 패스워드와 변경 후 패스워드가 일치) + */ + @Test(expected = RuntimeException.class) + public void updateOwnerPassword_fail3() { + OwnerDTO ownerInfo = generateOwner(); + String id = ownerInfo.getId(); + String beforePassword = ownerInfo.getPassword(); + String afterPassword = "afterPassword"; + + given(mapper.findByIdAndPassword(id, SHA256Util.encryptSHA256(beforePassword))) + .willReturn(ownerInfo); + given(mapper.updatePassword(id, SHA256Util.encryptSHA256(afterPassword))).willReturn(0); + + service.updateOwnerPassword(id, beforePassword, afterPassword); + } } From dea9db139c7c33be4c3104b3097668ccb72cf972 Mon Sep 17 00:00:00 2001 From: binaryyoung Date: Tue, 3 Dec 2019 15:44:07 +0900 Subject: [PATCH 04/24] =?UTF-8?q?-=20=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EB=B0=98=EC=98=81=20-=20ShopServiceTest=20addShop()=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 8 ++ src/main/java/com/delfood/dto/ShopDTO.java | 2 + .../com/delfood/service/OwnerService.java | 3 +- .../com/delfood/service/ShopServiceTest.java | 112 ++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/delfood/service/ShopServiceTest.java diff --git a/pom.xml b/pom.xml index d0e2f7a..39f976c 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,14 @@ org.springframework.boot spring-boot-starter-aop + + + + org.apache.commons + commons-lang3 + 3.4 + + diff --git a/src/main/java/com/delfood/dto/ShopDTO.java b/src/main/java/com/delfood/dto/ShopDTO.java index 7a3de28..746035b 100644 --- a/src/main/java/com/delfood/dto/ShopDTO.java +++ b/src/main/java/com/delfood/dto/ShopDTO.java @@ -2,11 +2,13 @@ import java.time.LocalDateTime; import java.util.List; +import lombok.Builder; import lombok.Getter; import lombok.NonNull; import lombok.Setter; import lombok.ToString; +@Builder @Getter @Setter @ToString diff --git a/src/main/java/com/delfood/service/OwnerService.java b/src/main/java/com/delfood/service/OwnerService.java index d217fd5..265f477 100644 --- a/src/main/java/com/delfood/service/OwnerService.java +++ b/src/main/java/com/delfood/service/OwnerService.java @@ -6,6 +6,7 @@ import com.delfood.mapper.OwnerMapper; import com.delfood.utils.SHA256Util; import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -110,7 +111,7 @@ public void updateOwnerPassword(String id, String beforePassword, String afterPa == null) { log.error("id and password do not match id : {}, password : {}",id,beforePassword); throw new IllegalArgumentException("id and password do not match"); - } else if (beforePassword.equals(afterPassword)) { + } else if (StringUtils.equals(beforePassword, afterPassword)) { log.error("password duplication before: {}, after : {}", beforePassword, afterPassword); throw new DuplicateException("password duplication"); diff --git a/src/test/java/com/delfood/service/ShopServiceTest.java b/src/test/java/com/delfood/service/ShopServiceTest.java new file mode 100644 index 0000000..47e6d41 --- /dev/null +++ b/src/test/java/com/delfood/service/ShopServiceTest.java @@ -0,0 +1,112 @@ +package com.delfood.service; + +import static org.mockito.BDDMockito.given; + +import com.delfood.dto.ShopDTO; +import com.delfood.dto.ShopDTO.DeliveryType; +import com.delfood.dto.ShopDTO.OrderType; +import com.delfood.dto.ShopDTO.Status; +import com.delfood.mapper.DeliveryLocationMapper; +import com.delfood.mapper.ShopMapper; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ShopServiceTest { + + @InjectMocks + private ShopService shopService; + + @Mock + private ShopMapper shopMapper; + + @Mock + private WorkService workService; + + @Mock + private AddressService addressService; + + @Mock + private DeliveryLocationMapper deliveryLocationMapper; + + /** + * shop 정보 생성. + * @return + */ + public ShopDTO generateShop() { + return ShopDTO.builder() + .id(999L) + .name("교촌치킨") + .deliveryType(DeliveryType.COMPANY_DELIVERY) + .signatureMenuId(1L) + .tel("02-2222-2222") + .addressCode("1111010100100010000031108") + .addressDetail("교촌치킨") + .bizNumber("111-11-11111") + .info("허니콤보 맛집") + .minOrderPrice(10000L) + .notice("배달료 3000원 붙습니다") + .operatingTime("11:00 ~ 02:00") + .ownerId("ljy2134") + .orderType(OrderType.THIS_PAYMENT) + .originInfo("닭 : 국내산") + .status(Status.DEFAULT) + .build(); + } + + /** + * 매장 추가 성공 테스트. + */ + @Test + public void addShopSuccess() { + ShopDTO shopInfo = generateShop(); + + given(shopMapper.insertShop(shopInfo)).willReturn(1); + + shopService.addShop(shopInfo); + } + + /** + * 시그니처 메뉴가 없는 아이디 일 때 실패 테스트. + */ + @Test(expected = RuntimeException.class) + public void addShopFailBeacauseMenuDoesNotExist() { + ShopDTO shopInfo = generateShop(); + shopInfo.setSignatureMenuId(-1L); + + given(shopMapper.insertShop(shopInfo)).willReturn(0); + + shopService.addShop(shopInfo); + } + + /** + * Owner가 없는 아이디 일 때 실패 테스트. + */ + @Test(expected = RuntimeException.class) + public void addShopFailBeacauseOwnerDoesNotExist() { + ShopDTO shopInfo = generateShop(); + shopInfo.setOwnerId("noExistId"); + + given(shopMapper.insertShop(shopInfo)).willReturn(0); + + shopService.addShop(shopInfo); + } + + /** + * address가 없는 아이디 일 때 실패 테스트. + */ + @Test(expected = RuntimeException.class) + public void addShopFailBeacauseAddressDoesNotExist() { + ShopDTO shopInfo = generateShop(); + shopInfo.setAddressCode("0000000000000000000000000"); + + given(shopMapper.insertShop(shopInfo)).willReturn(0); + + shopService.addShop(shopInfo); + } + +} From 25f8710534b65b04fafb65fd7ad3e47a743903a6 Mon Sep 17 00:00:00 2001 From: binaryyoung Date: Wed, 4 Dec 2019 17:51:22 +0900 Subject: [PATCH 05/24] =?UTF-8?q?-=20=EC=BF=A0=ED=8F=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/CouponController.java | 38 ++++++++++++ src/main/java/com/delfood/dto/CouponDTO.java | 61 +++++++++++++++++++ .../java/com/delfood/mapper/CouponMapper.java | 11 ++++ .../com/delfood/service/CouponService.java | 57 +++++++++++++++++ src/main/resources/mybatis/mapper/coupon.xml | 14 +++++ 5 files changed, 181 insertions(+) create mode 100644 src/main/java/com/delfood/controller/CouponController.java create mode 100644 src/main/java/com/delfood/dto/CouponDTO.java create mode 100644 src/main/java/com/delfood/mapper/CouponMapper.java create mode 100644 src/main/java/com/delfood/service/CouponService.java create mode 100644 src/main/resources/mybatis/mapper/coupon.xml diff --git a/src/main/java/com/delfood/controller/CouponController.java b/src/main/java/com/delfood/controller/CouponController.java new file mode 100644 index 0000000..4bb170e --- /dev/null +++ b/src/main/java/com/delfood/controller/CouponController.java @@ -0,0 +1,38 @@ +package com.delfood.controller; + +import com.delfood.dto.CouponDTO; +import com.delfood.service.CouponService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +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; + +@RestController +@RequestMapping("/coupons/") +public class CouponController { + + @Autowired + CouponService couponService; + + /** + * 쿠폰을 추가한다. + * @param couponInfo 쿠폰 정보 + * @return + * + * @author jinyoung + */ + @PostMapping + public HttpStatus addCoupon(@RequestBody CouponDTO couponInfo) { + + if (CouponDTO.hasNullDataBeforeAdd(couponInfo)) { + throw new NullPointerException(couponInfo.toString()); + } + + couponService.addCoupon(couponInfo); + + return HttpStatus.OK; + } +} diff --git a/src/main/java/com/delfood/dto/CouponDTO.java b/src/main/java/com/delfood/dto/CouponDTO.java new file mode 100644 index 0000000..4576dd3 --- /dev/null +++ b/src/main/java/com/delfood/dto/CouponDTO.java @@ -0,0 +1,61 @@ +package com.delfood.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import java.time.LocalDateTime; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import org.apache.ibatis.type.Alias; + +@ToString +@Getter +@Setter +@Alias("coupon") +public class CouponDTO { + + @JsonFormat(shape = Shape.OBJECT) + public enum DiscountType { + WON, PERCENT + } + + public enum Status { + DEFAULT, DELETED + } + + private Long id; + + private String name; + + private DiscountType discountType; + + private Long discountValue; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updatedAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endAt; + + private Status status; + + /** + * 쿠폰 등록에 필요한 내용이 null인지 확인. + * @param couponInfo 쿠폰정보 + * @return + * + * @author jinyoung + */ + public static boolean hasNullDataBeforeAdd(CouponDTO couponInfo) { + if (couponInfo.getName() == null || couponInfo.getDiscountType() == null + || couponInfo.getDiscountValue() == null || couponInfo.getEndAt() == null) { + return true; + } + return false; + } +} diff --git a/src/main/java/com/delfood/mapper/CouponMapper.java b/src/main/java/com/delfood/mapper/CouponMapper.java new file mode 100644 index 0000000..2e89b6a --- /dev/null +++ b/src/main/java/com/delfood/mapper/CouponMapper.java @@ -0,0 +1,11 @@ +package com.delfood.mapper; + +import com.delfood.dto.CouponDTO; + +import java.time.LocalDateTime; + +public interface CouponMapper { + + public LocalDateTime insertCoupon(CouponDTO couponInfo); + +} diff --git a/src/main/java/com/delfood/service/CouponService.java b/src/main/java/com/delfood/service/CouponService.java new file mode 100644 index 0000000..e85cc68 --- /dev/null +++ b/src/main/java/com/delfood/service/CouponService.java @@ -0,0 +1,57 @@ +package com.delfood.service; + +import com.delfood.dto.CouponDTO; +import com.delfood.dto.CouponDTO.DiscountType; +import com.delfood.mapper.CouponMapper; + +import java.time.LocalDateTime; +import lombok.extern.log4j.Log4j2; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Log4j2 +public class CouponService { + + @Autowired + private CouponMapper couponMapper; + + /** + * 쿠폰 추가. + * @param couponInfo + * + * @author jinyoung + */ + @Transactional(rollbackFor = RuntimeException.class) + public void addCoupon(CouponDTO couponInfo) { + + verifyDiscountData(couponInfo.getDiscountType(), couponInfo.getDiscountValue()); + + LocalDateTime createdAt = couponMapper.insertCoupon(couponInfo); + + if (couponInfo.getEndAt().isBefore(createdAt)) { + log.error("coupon expiration date is ealire than creation date! " + + "EndAt : {}, startAt : {}",couponInfo.getEndAt(), createdAt); + throw new RuntimeException("coupon expiration date is ealire than creation date!"); + } + } + + /** + * 쿠폰 할인 값 검증. + * @param discountType 할인 타입 + * @param discountValue 할인 값 + * @throws IllegalArgumentException + * + * @author jinyoung + */ + public static void verifyDiscountData(DiscountType discountType, Long discountValue) { + if (DiscountType.PERCENT == discountType + && ((discountValue < 0 || discountValue > 100))) { + log.error("coupon discount setting error! couponType : {} , discountValue : {}", + discountType, discountValue); + throw new IllegalArgumentException("coupon discount setting error!"); + } + } +} diff --git a/src/main/resources/mybatis/mapper/coupon.xml b/src/main/resources/mybatis/mapper/coupon.xml new file mode 100644 index 0000000..45102a1 --- /dev/null +++ b/src/main/resources/mybatis/mapper/coupon.xml @@ -0,0 +1,14 @@ + + + + + + INSERT INTO COUPON(name, discount_type, discount_value, end_at) + VALUES(#{name}, #{discountType}, #{discountValue}, #{endAt}) + + SELECT created_at createdAt FROM COUPON + WHERE id = (SELECT LAST_INSERT_ID()) + + + + From 9f79e889d1e71033705ee878aa07323773f4b550 Mon Sep 17 00:00:00 2001 From: binaryyoung Date: Thu, 12 Dec 2019 17:19:32 +0900 Subject: [PATCH 06/24] =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EB=B0=8F=20=EC=BF=A0=ED=8F=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/CouponController.java | 10 +++- src/main/java/com/delfood/dto/CouponDTO.java | 10 ++-- .../java/com/delfood/dto/CouponIssueDTO.java | 36 +++++++++++ .../com/delfood/error/ErrorController.java | 7 +++ .../coupon/IssuedCouponExistException.java | 7 +++ .../com/delfood/mapper/CouponIssueMapper.java | 9 +++ .../java/com/delfood/mapper/CouponMapper.java | 28 ++++++++- .../delfood/service/CouponIssueService.java | 19 ++++++ .../com/delfood/service/CouponService.java | 60 +++++++++++++++++-- src/main/resources/mybatis/mapper/coupon.xml | 31 +++++++--- .../resources/mybatis/mapper/couponIssue.xml | 12 ++++ 11 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/delfood/dto/CouponIssueDTO.java create mode 100644 src/main/java/com/delfood/error/exception/coupon/IssuedCouponExistException.java create mode 100644 src/main/java/com/delfood/mapper/CouponIssueMapper.java create mode 100644 src/main/java/com/delfood/service/CouponIssueService.java create mode 100644 src/main/resources/mybatis/mapper/couponIssue.xml diff --git a/src/main/java/com/delfood/controller/CouponController.java b/src/main/java/com/delfood/controller/CouponController.java index 4bb170e..21e9e10 100644 --- a/src/main/java/com/delfood/controller/CouponController.java +++ b/src/main/java/com/delfood/controller/CouponController.java @@ -2,7 +2,7 @@ import com.delfood.dto.CouponDTO; import com.delfood.service.CouponService; - +import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Log4j2 @RestController @RequestMapping("/coupons/") public class CouponController { @@ -27,12 +28,15 @@ public class CouponController { @PostMapping public HttpStatus addCoupon(@RequestBody CouponDTO couponInfo) { - if (CouponDTO.hasNullDataBeforeAdd(couponInfo)) { - throw new NullPointerException(couponInfo.toString()); + if (CouponDTO.hasNullData(couponInfo)) { + log.error("insufficient coupon information! {}", couponInfo.toString()); + throw new NullPointerException("insufficient coupon information! " + couponInfo.toString()); } couponService.addCoupon(couponInfo); return HttpStatus.OK; } + + } diff --git a/src/main/java/com/delfood/dto/CouponDTO.java b/src/main/java/com/delfood/dto/CouponDTO.java index 4576dd3..6af6ae2 100644 --- a/src/main/java/com/delfood/dto/CouponDTO.java +++ b/src/main/java/com/delfood/dto/CouponDTO.java @@ -51,11 +51,9 @@ public enum Status { * * @author jinyoung */ - public static boolean hasNullDataBeforeAdd(CouponDTO couponInfo) { - if (couponInfo.getName() == null || couponInfo.getDiscountType() == null - || couponInfo.getDiscountValue() == null || couponInfo.getEndAt() == null) { - return true; - } - return false; + public static boolean hasNullData(CouponDTO couponInfo) { + return couponInfo.getName() == null || couponInfo.getDiscountType() == null + || couponInfo.getDiscountValue() == null || couponInfo.getEndAt() == null; + } } diff --git a/src/main/java/com/delfood/dto/CouponIssueDTO.java b/src/main/java/com/delfood/dto/CouponIssueDTO.java new file mode 100644 index 0000000..539e526 --- /dev/null +++ b/src/main/java/com/delfood/dto/CouponIssueDTO.java @@ -0,0 +1,36 @@ +package com.delfood.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.LocalDateTime; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import org.apache.ibatis.type.Alias; + +@Setter +@Getter +@ToString +@Alias("couponIssue") +public class CouponIssueDTO { + + public enum Status { + DEFAULT, USED + } + + private Long id; + + private String memberId; + + private Long couponId; + + @JsonFormat(pattern = "yy-MM-dd hh:mm:ss") + private LocalDateTime createdAt; + + private Status status; + + private Long paymentId; + +} diff --git a/src/main/java/com/delfood/error/ErrorController.java b/src/main/java/com/delfood/error/ErrorController.java index 7da0766..7bdbd57 100644 --- a/src/main/java/com/delfood/error/ErrorController.java +++ b/src/main/java/com/delfood/error/ErrorController.java @@ -1,6 +1,7 @@ package com.delfood.error; import com.delfood.error.exception.DuplicateIdException; +import com.delfood.error.exception.coupon.IssuedCouponExistException; import com.delfood.error.exception.menuGroup.InvalidMenuGroupCountException; import com.delfood.error.exception.menuGroup.InvalidMenuGroupIdException; import com.delfood.error.exception.shop.CanNotCloseShopException; @@ -70,4 +71,10 @@ public ErrorMsg handleIllegalArgumentException(IllegalArgumentException e) { public ErrorMsg handleCannotShopException(RuntimeException e) { return new ErrorMsg(e.getLocalizedMessage(), getSimpleName(e)); } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(IssuedCouponExistException.class) + public ErrorMsg handleIssuedCouponExistException(IssuedCouponExistException e) { + return new ErrorMsg(e.getLocalizedMessage(), getSimpleName(e)); + } } diff --git a/src/main/java/com/delfood/error/exception/coupon/IssuedCouponExistException.java b/src/main/java/com/delfood/error/exception/coupon/IssuedCouponExistException.java new file mode 100644 index 0000000..7f3d0d4 --- /dev/null +++ b/src/main/java/com/delfood/error/exception/coupon/IssuedCouponExistException.java @@ -0,0 +1,7 @@ +package com.delfood.error.exception.coupon; + +public class IssuedCouponExistException extends RuntimeException{ + public IssuedCouponExistException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/delfood/mapper/CouponIssueMapper.java b/src/main/java/com/delfood/mapper/CouponIssueMapper.java new file mode 100644 index 0000000..c1df8bc --- /dev/null +++ b/src/main/java/com/delfood/mapper/CouponIssueMapper.java @@ -0,0 +1,9 @@ +package com.delfood.mapper; + +import org.springframework.stereotype.Repository; + +@Repository +public interface CouponIssueMapper { + + public int countCouponIssue(Long couponId); +} diff --git a/src/main/java/com/delfood/mapper/CouponMapper.java b/src/main/java/com/delfood/mapper/CouponMapper.java index 2e89b6a..b28146a 100644 --- a/src/main/java/com/delfood/mapper/CouponMapper.java +++ b/src/main/java/com/delfood/mapper/CouponMapper.java @@ -1,11 +1,33 @@ package com.delfood.mapper; import com.delfood.dto.CouponDTO; - import java.time.LocalDateTime; +import org.springframework.stereotype.Repository; +@Repository public interface CouponMapper { - - public LocalDateTime insertCoupon(CouponDTO couponInfo); + + /** + * 쿠폰 추가. + * @param couponInfo 쿠폰 정보 + * @return + */ + public Long insertCoupon(CouponDTO couponInfo); + + /** + * 쿠폰 수정. + * @param id 쿠폰 아이디 + * @param name 이름 + * @param endAt 만료일 + * @return + */ + public int updateCouponNameAndEndAt(Long id, String name, LocalDateTime endAt); + + /** + * 쿠폰삭제. + * @param id 쿠폰 아이디 + * @return + */ + public int deleteCoupon(Long id); } diff --git a/src/main/java/com/delfood/service/CouponIssueService.java b/src/main/java/com/delfood/service/CouponIssueService.java new file mode 100644 index 0000000..9e1f31f --- /dev/null +++ b/src/main/java/com/delfood/service/CouponIssueService.java @@ -0,0 +1,19 @@ +package com.delfood.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.delfood.mapper.CouponIssueMapper; +import lombok.extern.log4j.Log4j2; + +@Service +@Log4j2 +public class CouponIssueService { + + @Autowired + private CouponIssueMapper couponIssueMapper; + + public boolean isIssued(Long couponId) { + return couponIssueMapper.countCouponIssue(couponId) > 0; + } + +} diff --git a/src/main/java/com/delfood/service/CouponService.java b/src/main/java/com/delfood/service/CouponService.java index e85cc68..3b36e5f 100644 --- a/src/main/java/com/delfood/service/CouponService.java +++ b/src/main/java/com/delfood/service/CouponService.java @@ -2,9 +2,11 @@ import com.delfood.dto.CouponDTO; import com.delfood.dto.CouponDTO.DiscountType; +import com.delfood.error.exception.coupon.IssuedCouponExistException; import com.delfood.mapper.CouponMapper; - +import java.time.DateTimeException; import java.time.LocalDateTime; +import java.util.Optional; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +20,9 @@ public class CouponService { @Autowired private CouponMapper couponMapper; + @Autowired + private CouponIssueService couponIssueService; + /** * 쿠폰 추가. * @param couponInfo @@ -29,13 +34,19 @@ public void addCoupon(CouponDTO couponInfo) { verifyDiscountData(couponInfo.getDiscountType(), couponInfo.getDiscountValue()); - LocalDateTime createdAt = couponMapper.insertCoupon(couponInfo); + Long insertResult = couponMapper.insertCoupon(couponInfo); + + if (insertResult != 1) { + log.error("coupon Insert Error! {}", couponInfo.toString()); + throw new RuntimeException("coupon Insert Error"); + } - if (couponInfo.getEndAt().isBefore(createdAt)) { + if (couponInfo.getEndAt().isBefore(couponInfo.getCreatedAt())) { log.error("coupon expiration date is ealire than creation date! " - + "EndAt : {}, startAt : {}",couponInfo.getEndAt(), createdAt); - throw new RuntimeException("coupon expiration date is ealire than creation date!"); + + "EndAt : {}, startAt : {}",couponInfo.getEndAt(), couponInfo.getCreatedAt()); + throw new DateTimeException("coupon expiration date is ealire than creation date!"); } + } /** @@ -54,4 +65,43 @@ public static void verifyDiscountData(DiscountType discountType, Long discountVa throw new IllegalArgumentException("coupon discount setting error!"); } } + + /** + * 쿠폰 이름과 만료일 수정. + * + * @param id 쿠폰 아이디 + * @param name 이름 + * @param endAt 만료일 + */ + @Transactional(rollbackFor = RuntimeException.class) + public void updateCouponNameAndEndAt(Long id, String name, LocalDateTime endAt) { + if (couponIssueService.isIssued(id)) { + log.error("Issued Coupon already exists"); + throw new IssuedCouponExistException("Issued Coupon already exists"); + } + + int result = couponMapper.updateCouponNameAndEndAt(id, name, endAt); + if (result != 0) { + log.error("coupon update error! id : {}, name : {}, EndAt : {} ", id, name, endAt); + throw new RuntimeException("coupon update error!"); + } + } + + /** + * 쿠폰삭제. + * @param id 쿠폰 아이디 + */ + @Transactional(rollbackFor = RuntimeException.class) + public void deleteCoupon(Long id) { + if (couponIssueService.isIssued(id)) { + log.error("Issued Coupon already exists"); + throw new IssuedCouponExistException("Issued Coupon already exists"); + } + + int result = couponMapper.deleteCoupon(id); + if (result != 1) { + log.error("coupon delete error! id : {}",id); + throw new RuntimeException("coupon delete error!"); + } + } } diff --git a/src/main/resources/mybatis/mapper/coupon.xml b/src/main/resources/mybatis/mapper/coupon.xml index 45102a1..22f0fd4 100644 --- a/src/main/resources/mybatis/mapper/coupon.xml +++ b/src/main/resources/mybatis/mapper/coupon.xml @@ -2,13 +2,26 @@ - - INSERT INTO COUPON(name, discount_type, discount_value, end_at) - VALUES(#{name}, #{discountType}, #{discountValue}, #{endAt}) - - SELECT created_at createdAt FROM COUPON - WHERE id = (SELECT LAST_INSERT_ID()) - - - + + INSERT INTO COUPON(name, discount_type, discount_value, end_at) + VALUES(#{name}, #{discountType}, #{discountValue}, #{endAt}) + + SELECT created_at FROM COUPON + WHERE id = (SELECT LAST_INSERT_ID()) + + + + + UPDATE COUPON + SET name = #{name} + AND endAt = #{endAt} + WHERE id = #{id} + + + + UPDATE COUPON + SET status = "DELETED" + WHERE id = #{id} + + diff --git a/src/main/resources/mybatis/mapper/couponIssue.xml b/src/main/resources/mybatis/mapper/couponIssue.xml new file mode 100644 index 0000000..e08452d --- /dev/null +++ b/src/main/resources/mybatis/mapper/couponIssue.xml @@ -0,0 +1,12 @@ + + + + + + + + From ffb600d47ad597ff4f400c8ca8ad8903dac86e0c Mon Sep 17 00:00:00 2001 From: binaryyoung Date: Sat, 14 Dec 2019 19:20:47 +0900 Subject: [PATCH 07/24] =?UTF-8?q?-=20=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EB=B0=98=EC=98=81=20-=20=EC=BF=A0=ED=8F=B0=EB=B0=9C?= =?UTF-8?q?=EA=B8=89,=20=EB=B0=9C=EA=B8=89=EC=BF=A0=ED=8F=B0=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/CouponController.java | 42 +++++++++++++- .../com/delfood/mapper/CouponIssueMapper.java | 40 +++++++++++++ .../java/com/delfood/mapper/CouponMapper.java | 12 +++- .../delfood/service/CouponIssueService.java | 56 +++++++++++++++++++ .../com/delfood/service/CouponService.java | 20 +++++-- src/main/resources/mybatis/mapper/coupon.xml | 11 +++- .../resources/mybatis/mapper/couponIssue.xml | 12 +++- 7 files changed, 180 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/delfood/controller/CouponController.java b/src/main/java/com/delfood/controller/CouponController.java index 21e9e10..c36f6c0 100644 --- a/src/main/java/com/delfood/controller/CouponController.java +++ b/src/main/java/com/delfood/controller/CouponController.java @@ -3,8 +3,12 @@ import com.delfood.dto.CouponDTO; import com.delfood.service.CouponService; import lombok.extern.log4j.Log4j2; +import java.time.LocalDateTime; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,7 +30,7 @@ public class CouponController { * @author jinyoung */ @PostMapping - public HttpStatus addCoupon(@RequestBody CouponDTO couponInfo) { + public void addCoupon(@RequestBody CouponDTO couponInfo) { if (CouponDTO.hasNullData(couponInfo)) { log.error("insufficient coupon information! {}", couponInfo.toString()); @@ -34,9 +38,41 @@ public HttpStatus addCoupon(@RequestBody CouponDTO couponInfo) { } couponService.addCoupon(couponInfo); - - return HttpStatus.OK; + } + + /** + * 쿠폰 이름과 만료일을 수정한다. + * + * @param id 쿠폰 아이디 + * @param name 수정할 쿠폰 이름 + * @param endAt 수정할 만료일 + * + * @author jinyoung + */ + @PatchMapping + public void updateCouponNameAndEndAt(Long id, String name, LocalDateTime endAt) { + couponService.updateCouponNameAndEndAt(id, name, endAt); } + /** + * 쿠폰을 삭제한다. + * + * @param id 쿠폰 아이디 + */ + public void deleteCoupon(Long id) { + couponService.deleteCoupon(id); + } + + + /** + * 만료일이 지나지 않은 쿠폰들을 조회한다. + * @return 쿠폰리스트 + * + * @author jinyoung + */ + @GetMapping + public List getAvailableCoupons() { + return couponService.getAvaliableCoupons(); + } } diff --git a/src/main/java/com/delfood/mapper/CouponIssueMapper.java b/src/main/java/com/delfood/mapper/CouponIssueMapper.java index c1df8bc..7e8d9e5 100644 --- a/src/main/java/com/delfood/mapper/CouponIssueMapper.java +++ b/src/main/java/com/delfood/mapper/CouponIssueMapper.java @@ -1,9 +1,49 @@ package com.delfood.mapper; +import java.util.List; import org.springframework.stereotype.Repository; +import com.delfood.dto.CouponIssueDTO; @Repository public interface CouponIssueMapper { + /** + * 해당 쿠폰아이디로 발급된 적이 있는 쿠폰의 수를 조회한다. + * + * @param couponId 쿠폰 아이디 + * @return 발급된 쿠폰의 수 + * + * @author jinyoung + */ public int countCouponIssue(Long couponId); + + /** + * 회원 아이디와 쿠폰 아이디를 통해 이미 발급된 쿠폰의 수를 조회한다. + * + * @param memberId 회원 아이디 + * @param couponId 쿠폰 아이디 + * @return 발급된 쿠폰의 수 + * + * @author jinyoung + */ + public int countCouponIssueByMemberIdAndCouponId(String memberId, Long couponId); + + /** + * 발급 쿠폰을 추가한다. + * @param memberId 회원 아이디 + * @param couponId 쿠폰 아이디 + * @return + * + * @author jinyoung + */ + public int insertCouponIssue(String memberId, Long couponId); + + /** + * 발급 쿠폰의 상태를 USED로 변경한다. + * @param id 발급 쿠폰 아이디 + * + * @author jinyoung + */ + public int updateCouponIssueStatusToUsed(Long id); + } diff --git a/src/main/java/com/delfood/mapper/CouponMapper.java b/src/main/java/com/delfood/mapper/CouponMapper.java index b28146a..6f99320 100644 --- a/src/main/java/com/delfood/mapper/CouponMapper.java +++ b/src/main/java/com/delfood/mapper/CouponMapper.java @@ -2,6 +2,7 @@ import com.delfood.dto.CouponDTO; import java.time.LocalDateTime; +import java.util.List; import org.springframework.stereotype.Repository; @Repository @@ -15,7 +16,7 @@ public interface CouponMapper { public Long insertCoupon(CouponDTO couponInfo); /** - * 쿠폰 수정. + * 쿠폰 이름과 만료일 수정. * @param id 쿠폰 아이디 * @param name 이름 * @param endAt 만료일 @@ -24,10 +25,15 @@ public interface CouponMapper { public int updateCouponNameAndEndAt(Long id, String name, LocalDateTime endAt); /** - * 쿠폰삭제. + * 쿠폰 삭제. * @param id 쿠폰 아이디 * @return */ public int deleteCoupon(Long id); - + + /** + * 만료일이 지나지 않은 쿠폰 조회. + * @return + */ + public List findByEndAtGreaterThanNow(); } diff --git a/src/main/java/com/delfood/service/CouponIssueService.java b/src/main/java/com/delfood/service/CouponIssueService.java index 9e1f31f..0df3109 100644 --- a/src/main/java/com/delfood/service/CouponIssueService.java +++ b/src/main/java/com/delfood/service/CouponIssueService.java @@ -1,7 +1,11 @@ package com.delfood.service; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.delfood.dto.CouponIssueDTO; +import com.delfood.error.exception.DuplicateException; import com.delfood.mapper.CouponIssueMapper; import lombok.extern.log4j.Log4j2; @@ -12,8 +16,60 @@ public class CouponIssueService { @Autowired private CouponIssueMapper couponIssueMapper; + @Autowired + private CouponService couponService; + + /** + * 쿠폰이 발급된 적이 있는 지 조회한다. + * @param couponId 쿠폰 아이디 + * @return 쿠폰의 발급여부 ( true : 발급된 적 있음 , false : 발급된 적 없음 + * + * @author jinyoung + */ public boolean isIssued(Long couponId) { return couponIssueMapper.countCouponIssue(couponId) > 0; } + public boolean checkDuplicateIssue(String memberId, Long couponId) { + return couponIssueMapper.countCouponIssueByMemberIdAndCouponId(memberId, couponId) > 0; + } + + /** + * 회원에게 쿠폰을 발급한다. + * @param memberId 회원 아이디 + * @param couponId 쿠폰 아이디 + * + * @author jinyoung + */ + @Transactional(rollbackFor = RuntimeException.class) + public void createCouponIssue(String memberId, Long couponId) { + + if (checkDuplicateIssue(memberId, couponId)) { + log.error("coupon has already been issued! memberid :{}, couponId :{}", memberId, couponId); + throw new DuplicateException("coupon has already been issued!"); + } + + int result = couponIssueMapper.insertCouponIssue(memberId, couponId); + if (result != 1) { + log.error("insert couponIssue Error! memberId : {}, couponId : {}", memberId, couponId); + throw new RuntimeException("insert couponIssue Error! "); + } + } + + /** + * 발급 쿠폰 사용. + * 발급 쿠폰의 상태를 사용됨으로 변경한다. + * @param id 발급 쿠폰 아이디 + * + * @author jinyoung + */ + @Transactional(rollbackFor = RuntimeException.class) + public void useCouponIssue(Long id) { + int result = couponIssueMapper.updateCouponIssueStatusToUsed(id); + if (result != 1) { + log.error("update coupon status error! id : {}", id); + throw new RuntimeException("update coupon status error!"); + } + } + } diff --git a/src/main/java/com/delfood/service/CouponService.java b/src/main/java/com/delfood/service/CouponService.java index 3b36e5f..fc7e8b5 100644 --- a/src/main/java/com/delfood/service/CouponService.java +++ b/src/main/java/com/delfood/service/CouponService.java @@ -4,9 +4,8 @@ import com.delfood.dto.CouponDTO.DiscountType; import com.delfood.error.exception.coupon.IssuedCouponExistException; import com.delfood.mapper.CouponMapper; -import java.time.DateTimeException; import java.time.LocalDateTime; -import java.util.Optional; +import java.util.List; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -44,7 +43,7 @@ public void addCoupon(CouponDTO couponInfo) { if (couponInfo.getEndAt().isBefore(couponInfo.getCreatedAt())) { log.error("coupon expiration date is ealire than creation date! " + "EndAt : {}, startAt : {}",couponInfo.getEndAt(), couponInfo.getCreatedAt()); - throw new DateTimeException("coupon expiration date is ealire than creation date!"); + throw new IllegalStateException("coupon expiration date is ealire than creation date!"); } } @@ -72,6 +71,8 @@ public static void verifyDiscountData(DiscountType discountType, Long discountVa * @param id 쿠폰 아이디 * @param name 이름 * @param endAt 만료일 + * + * @author jinyoung */ @Transactional(rollbackFor = RuntimeException.class) public void updateCouponNameAndEndAt(Long id, String name, LocalDateTime endAt) { @@ -81,7 +82,7 @@ public void updateCouponNameAndEndAt(Long id, String name, LocalDateTime endAt) } int result = couponMapper.updateCouponNameAndEndAt(id, name, endAt); - if (result != 0) { + if (result != 1) { log.error("coupon update error! id : {}, name : {}, EndAt : {} ", id, name, endAt); throw new RuntimeException("coupon update error!"); } @@ -90,6 +91,8 @@ public void updateCouponNameAndEndAt(Long id, String name, LocalDateTime endAt) /** * 쿠폰삭제. * @param id 쿠폰 아이디 + * + * @author jinyoung */ @Transactional(rollbackFor = RuntimeException.class) public void deleteCoupon(Long id) { @@ -104,4 +107,13 @@ public void deleteCoupon(Long id) { throw new RuntimeException("coupon delete error!"); } } + + /** + * 사용 가능한 쿠폰을 조회한다. (만료일이 현재시간 이후의 쿠폰만 조회) + * @return 쿠폰 리스트 + */ + public List getAvaliableCoupons() { + return couponMapper.findByEndAtGreaterThanNow(); + } + } diff --git a/src/main/resources/mybatis/mapper/coupon.xml b/src/main/resources/mybatis/mapper/coupon.xml index 22f0fd4..2374059 100644 --- a/src/main/resources/mybatis/mapper/coupon.xml +++ b/src/main/resources/mybatis/mapper/coupon.xml @@ -11,10 +11,10 @@ - + UPDATE COUPON SET name = #{name} - AND endAt = #{endAt} + AND end_at = #{endAt} WHERE id = #{id} @@ -24,4 +24,11 @@ WHERE id = #{id} + + diff --git a/src/main/resources/mybatis/mapper/couponIssue.xml b/src/main/resources/mybatis/mapper/couponIssue.xml index e08452d..ddff599 100644 --- a/src/main/resources/mybatis/mapper/couponIssue.xml +++ b/src/main/resources/mybatis/mapper/couponIssue.xml @@ -5,8 +5,18 @@ + + INSERT INTO COUPON_ISSUE(member_id, coupon_id) + VALUES(#{memberId}, #{couponId}) + + + + UPDATE COUPON_ISSUE + SET status = 'USED' + WHERE id = #{id} + + From 2ac117fd6baa4c495a3010170653ad554b5c6ea2 Mon Sep 17 00:00:00 2001 From: binaryyoung Date: Mon, 16 Dec 2019 19:35:34 +0900 Subject: [PATCH 08/24] =?UTF-8?q?-=20=EB=B0=9C=EA=B8=89=20=EC=BF=A0?= =?UTF-8?q?=ED=8F=B0=20=EC=A1=B0=ED=9A=8C=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20CouponIssueDTO=20=EB=B3=80=EA=B2=BD=20-?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=EB=B9=A0=EC=A7=84=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20-=20=EB=A1=9C=EC=A7=81=EB=A7=8C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=96=88=EB=8D=98=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=EC=9D=84=20postman=20=ED=85=8C=EC=8A=A4=ED=8A=B8=ED=95=98?= =?UTF-8?q?=EB=A9=B4=EC=84=9C=20=EC=9E=98=EB=AA=BB=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EB=90=9C=20=EB=B6=80=EB=B6=84=EC=9D=84=20=EA=B3=A0=EC=B3=A4?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CouponIssueController.java | 51 +++++++++++++++++++ .../java/com/delfood/dto/CouponIssueDTO.java | 19 +++++-- .../com/delfood/mapper/CouponIssueMapper.java | 7 +++ .../delfood/service/CouponIssueService.java | 18 +++++++ .../resources/mybatis/mapper/couponIssue.xml | 20 +++++++- 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/delfood/controller/CouponIssueController.java diff --git a/src/main/java/com/delfood/controller/CouponIssueController.java b/src/main/java/com/delfood/controller/CouponIssueController.java new file mode 100644 index 0000000..a4cf6f9 --- /dev/null +++ b/src/main/java/com/delfood/controller/CouponIssueController.java @@ -0,0 +1,51 @@ +package com.delfood.controller; + +import com.delfood.aop.MemberLoginCheck; +import com.delfood.dto.CouponIssueDTO; +import com.delfood.service.CouponIssueService; +import com.delfood.utils.SessionUtil; +import java.util.List; +import javax.servlet.http.HttpSession; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +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.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@Log4j2 +@RestController +@RequestMapping("/couponIssues/") +public class CouponIssueController { + + @Autowired + private CouponIssueService couponIssueService; + + /** + * 회원에게 쿠폰을 발행한다. + * @param session 현재 사용자 세션 + * @param couponId 쿠폰 아이디 + */ + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + @MemberLoginCheck + public void addCouponIssue(HttpSession session, @RequestBody Long couponId) { + + couponIssueService.createCouponIssue(SessionUtil.getLoginMemberId(session), couponId); + } + + /** + * 회원이 가지고 있는 발행 쿠폰들을 조회한다. + * @param session 현재 사용자 세션 + * @return + */ + @GetMapping + @MemberLoginCheck + public List getCouponIssues(HttpSession session) { + return couponIssueService.getCouponIssues(SessionUtil.getLoginMemberId(session)); + } + +} diff --git a/src/main/java/com/delfood/dto/CouponIssueDTO.java b/src/main/java/com/delfood/dto/CouponIssueDTO.java index 539e526..2525dbe 100644 --- a/src/main/java/com/delfood/dto/CouponIssueDTO.java +++ b/src/main/java/com/delfood/dto/CouponIssueDTO.java @@ -20,17 +20,30 @@ public enum Status { DEFAULT, USED } + public enum DiscountType { + WON, PERCENT + } + private Long id; private String memberId; private Long couponId; - @JsonFormat(pattern = "yy-MM-dd hh:mm:ss") - private LocalDateTime createdAt; - private Status status; private Long paymentId; + private DiscountType discountType; + + private String name; + + private Long discountValue; + + @JsonFormat(pattern = "yy-MM-dd hh:mm:ss") + private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endAt; + } diff --git a/src/main/java/com/delfood/mapper/CouponIssueMapper.java b/src/main/java/com/delfood/mapper/CouponIssueMapper.java index 7e8d9e5..142c10e 100644 --- a/src/main/java/com/delfood/mapper/CouponIssueMapper.java +++ b/src/main/java/com/delfood/mapper/CouponIssueMapper.java @@ -46,4 +46,11 @@ public interface CouponIssueMapper { */ public int updateCouponIssueStatusToUsed(Long id); + /** + * 회원이 가진 쿠폰들을 조회한다. + * @param memberId 회원 아이디 + * @return + */ + public List findByMemberId(String memberId); + } diff --git a/src/main/java/com/delfood/service/CouponIssueService.java b/src/main/java/com/delfood/service/CouponIssueService.java index 0df3109..40611d8 100644 --- a/src/main/java/com/delfood/service/CouponIssueService.java +++ b/src/main/java/com/delfood/service/CouponIssueService.java @@ -30,6 +30,12 @@ public boolean isIssued(Long couponId) { return couponIssueMapper.countCouponIssue(couponId) > 0; } + /** + * 회원이 이미 해당 쿠폰을 발급받은 적이 있는지 체크한다. + * @param memberId 회원 아이디 + * @param couponId 쿠폰 아이디 + * @return + */ public boolean checkDuplicateIssue(String memberId, Long couponId) { return couponIssueMapper.countCouponIssueByMemberIdAndCouponId(memberId, couponId) > 0; } @@ -72,4 +78,16 @@ public void useCouponIssue(Long id) { } } + /** + * 회원이 가진 발행 쿠폰들을 조회한다. + * @param memberId 회원 아이디 + * @return + */ + public List getCouponIssues(String memberId) { + return couponIssueMapper.findByMemberId(memberId); + } + + + + } diff --git a/src/main/resources/mybatis/mapper/couponIssue.xml b/src/main/resources/mybatis/mapper/couponIssue.xml index ddff599..8164412 100644 --- a/src/main/resources/mybatis/mapper/couponIssue.xml +++ b/src/main/resources/mybatis/mapper/couponIssue.xml @@ -1,11 +1,17 @@ - + - SELECT COUNT(*) FROM COUPON_ISSUE WHERE coupon_id = #{couponId} + + INSERT INTO COUPON_ISSUE(member_id, coupon_id) @@ -18,5 +24,15 @@ WHERE id = #{id} + + From 80228478597ed19bb7b37c6009809c83f69d825e Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Tue, 31 Dec 2019 13:42:14 +0900 Subject: [PATCH 09/24] =?UTF-8?q?=EC=A3=BC=EB=AC=B8=EC=8B=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9E=A5=EB=8B=98=EC=97=90=EA=B2=8C=20=ED=91=B8=EC=8B=9C=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=EB=A1=9C=20=EC=95=8C=EB=A6=BC.=20?= =?UTF-8?q?=ED=91=B8=EC=8B=9C=20=EC=A0=81=EC=9A=A9=ED=95=A0=20=EA=B3=B3?= =?UTF-8?q?=EC=9D=B4=20=EC=95=84=EC=A7=81=20=EC=97=86=EB=84=A4=EC=9A=94.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/OrderController.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/delfood/controller/OrderController.java b/src/main/java/com/delfood/controller/OrderController.java index d650297..6f272a1 100644 --- a/src/main/java/com/delfood/controller/OrderController.java +++ b/src/main/java/com/delfood/controller/OrderController.java @@ -5,9 +5,13 @@ import com.delfood.dto.ItemsBillDTO; import com.delfood.dto.OrderDTO; import com.delfood.dto.OrderItemDTO; +import com.delfood.dto.ShopDTO; +import com.delfood.dto.push.PushMessage; import com.delfood.error.exception.order.TotalPriceMismatchException; import com.delfood.dto.OrderBillDTO; import com.delfood.service.OrderService; +import com.delfood.service.PushService; +import com.delfood.service.ShopService; import com.delfood.utils.SessionUtil; import java.util.List; import javax.servlet.http.HttpSession; @@ -31,6 +35,12 @@ public class OrderController { @Autowired OrderService orderService; + @Autowired + ShopService shopService; + + @Autowired + PushService pushService; + /** * 아이템들의 가격과 정보를 조회한다. * @author jun @@ -84,9 +94,16 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques totalPriceFromServer); throw new TotalPriceMismatchException("Total Price Mismatch!"); } - - return orderService.order(SessionUtil.getLoginMemberId(session), request.getItems(), - request.getShopId()); + + OrderResponse orderResponse = orderService.order(SessionUtil.getLoginMemberId(session), + request.getItems(), request.getShopId()); + + // 사장님에게 푸시 메세지 전송 + PushMessage pushMsg = new PushMessage("DelFood 주문", "새로운 주문이 들어왔습니다."); + String ownerId = shopService.getShop(request.getShopId()).getOwnerId(); + pushService.sendMessageToOwner(pushMsg, ownerId); + + return orderResponse; } /** From ac1d4378e8fee68c36a707a9055858754e939b47 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Tue, 31 Dec 2019 15:02:13 +0900 Subject: [PATCH 10/24] =?UTF-8?q?AddressService=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/service/AddressServiceTest.java | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/test/java/com/delfood/service/AddressServiceTest.java diff --git a/src/test/java/com/delfood/service/AddressServiceTest.java b/src/test/java/com/delfood/service/AddressServiceTest.java new file mode 100644 index 0000000..96f830b --- /dev/null +++ b/src/test/java/com/delfood/service/AddressServiceTest.java @@ -0,0 +1,164 @@ +package com.delfood.service; + +import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.mockito.BDDMockito.given; + +import com.delfood.controller.reqeust.GetAddressByZipRequest; +import com.delfood.controller.reqeust.GetAddressesByRoadRequest; +import com.delfood.dto.AddressDTO; +import com.delfood.dto.address.Position; +import com.delfood.mapper.AddressMapper; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class AddressServiceTest { + + @InjectMocks + AddressService addressService; + + @Mock + AddressMapper addressMapper; + + public AddressDTO generateAddressDTO() { + AddressDTO addressInfo = new AddressDTO(); + addressInfo.setAdministrativeTownCode("1111051500"); + addressInfo.setAdministrativeTownName("청운효자동"); + addressInfo.setBuildingCenterPointXCoordinate(953035.318387); + addressInfo.setBuildingCenterPointYCoordinate(1954819.846972); + addressInfo.setBuildingCount(9); + addressInfo.setBuildingManagementNumber("1111010100100010000030843"); + addressInfo.setBuildingNameChangeHistory(""); + addressInfo.setBuildingNameForCity("청운벽산빌리지"); + addressInfo.setBuildingNumber(16); + addressInfo.setBuildingSideNumber(14); + addressInfo.setBuildingUseClassification("주택"); + addressInfo.setCityCountryName("종로구"); + addressInfo.setCityCountryNameEng("Jongno-gu"); + addressInfo.setCityName("서울특별시"); + addressInfo.setCityNameEng("Seoul"); + addressInfo.setClassificationApartmentBuildings("2"); + addressInfo.setDetailBuildingName("7동"); + addressInfo.setDetailBuildingNameChangeHistory(""); + addressInfo.setExitXCoordinate(953042.185946); + addressInfo.setExitYCoordinate(1954799.009030); + addressInfo.setGroundFloorNumber(3); + addressInfo.setLivingStatus("1"); + addressInfo.setMobileReasonCode(""); + addressInfo.setRoadName("자하문로36길"); + addressInfo.setRoadNameEng("Jahamun-ro 36-gil"); + addressInfo.setTownCode("1111010100"); + addressInfo.setTownMobileClassification("1"); + addressInfo.setTownName("청운동"); + addressInfo.setTownNameEng("Cheongun-dong"); + addressInfo.setUndergroundFloorNumber(0); + addressInfo.setUndergroundStatus("0"); + addressInfo.setZipCode("03046"); + + return addressInfo; + } + + @Test + public void getTownInfoByShopIdTest_아이디로_주소_조회() { + final Long shopId = 777L; + AddressDTO addressInfo = generateAddressDTO(); + List addressList = new ArrayList(); + addressList.add(addressInfo); + + given(addressMapper.findByShopId(shopId)).willReturn(addressList); + + assertThat(addressService.getTownInfoByShopId(shopId), equalTo(addressList)); + } + + @Test + public void getAddressByZipAddressTest_지번주소로_주소_검색() { + AddressDTO addressInfo = generateAddressDTO(); + List addressList = new ArrayList(); + addressList.add(addressInfo); + + GetAddressByZipRequest searchInfo = new GetAddressByZipRequest(); + searchInfo.setBuildingNameForCity(addressInfo.getBuildingNameForCity()); + searchInfo.setBuildingNumber(addressInfo.getBuildingNumber()); + searchInfo.setBuildingSideNumber(addressInfo.getBuildingSideNumber()); + searchInfo.setTownName(addressInfo.getTownName()); + + given(addressMapper.findByZipName(searchInfo)).willReturn(addressList); + assertThat(addressService.getAddressByZipAddress(searchInfo), equalTo(addressList)); + } + + @Test + public void getAddressByRoadNameTest_도로명주소로_주소_검색() { + AddressDTO addressInfo = generateAddressDTO(); + List addressList = new ArrayList(); + addressList.add(addressInfo); + + GetAddressesByRoadRequest searchInfo = new GetAddressesByRoadRequest(); + searchInfo.setBuildingNameForCity(addressInfo.getBuildingNameForCity()); + searchInfo.setBuildingNumber(addressInfo.getBuildingNumber()); + searchInfo.setBuildingSideNumber(addressInfo.getBuildingSideNumber()); + searchInfo.setRoadName(addressInfo.getRoadName()); + + given(addressMapper.findByRoadName(searchInfo)).willReturn(addressList); + assertThat(addressService.getAddressByRoadName(searchInfo), equalTo(addressList)); + } + + @Test + public void getDistanceMeterTest_거리_계산() { + Position startPosition = Position.builder() + .xPos(0.0) + .yPos(0.0) + .build(); + + Position endPosition = Position.builder() + .xPos(300.0) + .yPos(400.0) + .build(); + + given(addressMapper.findPositionByAddressCode("11111111")).willReturn(startPosition); + given(addressMapper.findPositionByAddressCode("22222222")).willReturn(endPosition); + + assertThat(addressService.getDistanceMeter("11111111", "22222222"), equalTo(500.0)); + } + + @Test + public void deliveryPriceTest_거리기반_배달료_계산() { + final double distances_300 = 300.0; + final double distances_1499_9 = 1499.9; + final double distances_1500 = 1500.0; + final double distances_1500_1 = 1500.1; + final double distances_2500 = 2500.0; + final double distances_1699 = 1699.0; + + assertThat(addressService.deliveryPrice(distances_300), equalTo(2000L)); + assertThat(addressService.deliveryPrice(distances_1499_9), equalTo(2000L)); + assertThat(addressService.deliveryPrice(distances_1500), equalTo(2000L)); + assertThat(addressService.deliveryPrice(distances_1500_1), equalTo(2000L)); + assertThat(addressService.deliveryPrice(distances_2500), equalTo(4000L)); + assertThat(addressService.deliveryPrice(distances_1699), equalTo(2200L)); + } + + @Test + public void deliveryPriceTest_아이디기반_배달료_계산() { + Position memberPosition = Position.builder() + .xPos(0.0) + .yPos(0.0) + .build(); + + Position shopPosition = Position.builder() + .xPos(3000.0) + .yPos(4000.0) + .build(); + + given(addressMapper.findPositionByMemberId("eric")).willReturn(memberPosition); + given(addressMapper.findPositionByShopId(555L)).willReturn(shopPosition); + + assertThat(addressService.deliveryPrice("eric", 555L), equalTo(9000L)); + } + +} From 873fb5e79e96f58b515cfbc24ee3e5864267bd5a Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Tue, 31 Dec 2019 16:04:54 +0900 Subject: [PATCH 11/24] =?UTF-8?q?=EC=9E=A5=EB=B0=94=EA=B5=AC=EB=8B=88=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=8C=80=EC=83=81=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/delfood/dto/ItemDTO.java | 8 + .../com/delfood/service/CartServiceTest.java | 157 +++++++++++++++++- 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/delfood/dto/ItemDTO.java b/src/main/java/com/delfood/dto/ItemDTO.java index 416c743..7a87817 100644 --- a/src/main/java/com/delfood/dto/ItemDTO.java +++ b/src/main/java/com/delfood/dto/ItemDTO.java @@ -2,12 +2,15 @@ import java.util.List; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @EqualsAndHashCode(of = {"menuInfo", "options", "shopInfo"}) +@NoArgsConstructor +@AllArgsConstructor public class ItemDTO { private CacheMenuDTO menuInfo; // id, name, price private List options; // id, name, price @@ -30,6 +33,8 @@ public boolean hasNullDataBeforeInsertCart() { @Getter @EqualsAndHashCode + @NoArgsConstructor + @AllArgsConstructor public static class CacheMenuDTO { private Long id; private String name; @@ -38,6 +43,8 @@ public static class CacheMenuDTO { @Getter @EqualsAndHashCode + @NoArgsConstructor + @AllArgsConstructor public static class CacheShopDTO { private Long id; private String name; @@ -45,6 +52,7 @@ public static class CacheShopDTO { @Getter @EqualsAndHashCode + @AllArgsConstructor public static class CacheOptionDTO { private Long id; private String name; diff --git a/src/test/java/com/delfood/service/CartServiceTest.java b/src/test/java/com/delfood/service/CartServiceTest.java index b58df32..b24ab51 100644 --- a/src/test/java/com/delfood/service/CartServiceTest.java +++ b/src/test/java/com/delfood/service/CartServiceTest.java @@ -1,8 +1,19 @@ package com.delfood.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.equalTo; import static org.mockito.BDDMockito.given; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import com.delfood.dao.CartDao; +import com.delfood.dto.ItemDTO; +import com.delfood.dto.ShopDTO; +import com.delfood.dto.ItemDTO.CacheMenuDTO; +import com.delfood.dto.ItemDTO.CacheOptionDTO; +import com.delfood.dto.ItemDTO.CacheShopDTO; +import com.delfood.error.exception.cart.DuplicateItemException; +import com.google.common.collect.Lists; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -12,15 +23,151 @@ @RunWith(MockitoJUnitRunner.class) public class CartServiceTest { @InjectMocks - CartService service; + CartService cartService; @Mock - CartDao dao; + CartDao cartDao; + + public ItemDTO generateItem111() { + CacheMenuDTO menuInfo = new CacheMenuDTO(111L, "테스트 메뉴 111", 11000L); + + List options = new ArrayList(); + CacheOptionDTO optionInfo1 = new CacheOptionDTO(1L, "111 옵션 1", 100L); + CacheOptionDTO optionInfo2 = new CacheOptionDTO(1L, "111 옵션 2", 200L); + CacheOptionDTO optionInfo3 = new CacheOptionDTO(1L, "111 옵션 3", 300L); + options.add(optionInfo1); + options.add(optionInfo2); + options.add(optionInfo3); + + CacheShopDTO shopInfo = new CacheShopDTO(222L, "테스트 매장 이름"); + + ItemDTO itemInfo = new ItemDTO(menuInfo, options, 1, 11600L, shopInfo); + + return itemInfo; + } + + public ItemDTO generateItem222() { + CacheMenuDTO menuInfo = new CacheMenuDTO(222L, "테스트 메뉴 222", 11000L); + + List options = new ArrayList(); + CacheOptionDTO optionInfo1 = new CacheOptionDTO(2L, "222 옵션 1", 100L); + CacheOptionDTO optionInfo2 = new CacheOptionDTO(2L, "222 옵션 2", 200L); + CacheOptionDTO optionInfo3 = new CacheOptionDTO(2L, "222 옵션 3", 300L); + options.add(optionInfo1); + options.add(optionInfo2); + options.add(optionInfo3); + + CacheShopDTO shopInfo = new CacheShopDTO(222L, "테스트 매장 이름"); + + ItemDTO itemInfo = new ItemDTO(menuInfo, options, 1, 11600L, shopInfo); + + return itemInfo; + } + public ItemDTO generateItemAnotherShop() { + CacheMenuDTO menuInfo = new CacheMenuDTO(222L, "테스트 메뉴 222", 11000L); + + List options = new ArrayList(); + CacheOptionDTO optionInfo1 = new CacheOptionDTO(2L, "222 옵션 1", 100L); + CacheOptionDTO optionInfo2 = new CacheOptionDTO(2L, "222 옵션 2", 200L); + CacheOptionDTO optionInfo3 = new CacheOptionDTO(2L, "222 옵션 3", 300L); + options.add(optionInfo1); + options.add(optionInfo2); + options.add(optionInfo3); + + CacheShopDTO shopInfo = new CacheShopDTO(123L, "테스트 매장 이름"); + + ItemDTO itemInfo = new ItemDTO(menuInfo, options, 1, 11600L, shopInfo); + + return itemInfo; + } - // 로직이 확정되면 테스트코드를 다시 작성할 예정입니다 @Test - public void mockTest() { + public void addOrdersItemTest_장바구니에_메뉴_추가() { + ItemDTO item1 = generateItem111(); + ItemDTO item2 = generateItem222(); + given(cartDao.findPeekByMemberId("eric")).willReturn(item1); + cartService.addOrdersItem(item2, "eric"); + } + + @Test(expected = IllegalArgumentException.class) + public void addOrdersItemTest_장바구니에_다른매장_메뉴_추가() { + ItemDTO item1 = generateItem111(); + ItemDTO item2 = generateItemAnotherShop(); + given(cartDao.findPeekByMemberId("eric")).willReturn(item1); + cartService.addOrdersItem(item2, "eric"); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void addOrdersItemTest_너무많은메뉴추가() { + given(cartDao.findPeekByMemberId("eric")).willReturn(generateItem111()); + given(cartDao.findAllByMemberId("eric")).willReturn(Arrays.asList( + new ItemDTO[] {generateItem111(), generateItem111(), generateItem111(), generateItem111(), + generateItem111(), generateItem111(), generateItem111(), generateItem111(), + generateItem111(), generateItem111(), generateItem111(), generateItem111()})); + cartService.addOrdersItem(generateItem222(), "eric"); + } + + @Test(expected = DuplicateItemException.class) + public void addOrdersItemTest_같메뉴추가() { + given(cartDao.findPeekByMemberId("eric")).willReturn(generateItem111()); + given(cartDao.findAllByMemberId("eric")).willReturn(Arrays.asList( + new ItemDTO[] {generateItem111()})); + cartService.addOrdersItem(generateItem111(), "eric"); + } + + @Test + public void deleteCartMenuTest_장바구니_메뉴_삭제_인덱스() { + given(cartDao.getMenuCount("eric")).willReturn(5L); + given(cartDao.deleteByMemberIdAndIndex("eric", 1L)).willReturn(true); + cartService.deleteCartMenu("eric", 1L); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void deleteCartMenuTest_장바구니_메뉴_삭제_인덱스_초과() { + given(cartDao.getMenuCount("eric")).willReturn(5L); + cartService.deleteCartMenu("eric", 6L); + } + + @Test(expected = RuntimeException.class) + public void deleteCartMenuTest_장바구니_메뉴_삭제_redis에러() { + given(cartDao.getMenuCount("eric")).willReturn(5L); + given(cartDao.deleteByMemberIdAndIndex("eric", 1L)).willReturn(false); + cartService.deleteCartMenu("eric", 1L); + } + + @Test + public void containsEqualItemTest_장바구니_동일아이템_포함여부_검사() { + given(cartDao.findAllByMemberId("eric")) + .willReturn(Arrays.asList(new ItemDTO[] {generateItem111()})); + assertThat(cartService.containsEqualItem("eric", generateItem111())).isEqualTo(true); + assertThat(cartService.containsEqualItem("eric", generateItem222())).isEqualTo(false); } + + @Test + public void allPriceTest_장바구니_총가격_계산() { + given(cartDao.findAllByMemberId("eric")) + .willReturn(Arrays.asList(new ItemDTO[] {generateItem111(), generateItem222()})); + assertThat(cartService.allPrice("eric")).isEqualTo(23200L); + } + + @Test + public void priceTest_아이템_가격계산() { + ItemDTO itemInfo = generateItem111(); + assertThat(cartService.price(itemInfo)).isEqualTo(11600L); + } + + @Test + public void menuPriceTest_아이템_메뉴만_가격계산() { + ItemDTO itemInfo = generateItem111(); + assertThat(CartService.menuPrice(itemInfo)).isEqualTo(11000L); + } + + @Test + public void menuPriceTest_아이템_옵션만_가격계산() { + ItemDTO itemInfo = generateItem111(); + assertThat(CartService.optionsPrice(itemInfo)).isEqualTo(600L); + } + } From f03803a3c9962ed32fa0fa1544b4d118e159caeb Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Fri, 3 Jan 2020 13:02:42 +0900 Subject: [PATCH 12/24] =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/delfood/service/CartServiceTest.java | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/src/test/java/com/delfood/service/CartServiceTest.java b/src/test/java/com/delfood/service/CartServiceTest.java index b24ab51..c46e09a 100644 --- a/src/test/java/com/delfood/service/CartServiceTest.java +++ b/src/test/java/com/delfood/service/CartServiceTest.java @@ -28,39 +28,35 @@ public class CartServiceTest { @Mock CartDao cartDao; - public ItemDTO generateItem111() { - CacheMenuDTO menuInfo = new CacheMenuDTO(111L, "테스트 메뉴 111", 11000L); - - List options = new ArrayList(); - CacheOptionDTO optionInfo1 = new CacheOptionDTO(1L, "111 옵션 1", 100L); - CacheOptionDTO optionInfo2 = new CacheOptionDTO(1L, "111 옵션 2", 200L); - CacheOptionDTO optionInfo3 = new CacheOptionDTO(1L, "111 옵션 3", 300L); - options.add(optionInfo1); - options.add(optionInfo2); - options.add(optionInfo3); - - CacheShopDTO shopInfo = new CacheShopDTO(222L, "테스트 매장 이름"); - - ItemDTO itemInfo = new ItemDTO(menuInfo, options, 1, 11600L, shopInfo); - - return itemInfo; - } - - public ItemDTO generateItem222() { - CacheMenuDTO menuInfo = new CacheMenuDTO(222L, "테스트 메뉴 222", 11000L); + /** + * id에 따른 Item을 생산하여 리턴한다. + * @author jun + * @param id 해당 아이디를 기반으로 메뉴와 옵션을 생산하여 아이템을 제작한다. + * @return + */ + public ItemDTO generateItem(long id) { + final long menuId = id * 111; + final long shopId = 222L; + final long menuPrice = 11000L; + final long optionId = id; + final long[] optionPrices = {100L, 200L, 300L}; + CacheMenuDTO menuInfo = new CacheMenuDTO(menuId, "테스트 메뉴 " + menuId, menuPrice); + List options = new ArrayList(); - CacheOptionDTO optionInfo1 = new CacheOptionDTO(2L, "222 옵션 1", 100L); - CacheOptionDTO optionInfo2 = new CacheOptionDTO(2L, "222 옵션 2", 200L); - CacheOptionDTO optionInfo3 = new CacheOptionDTO(2L, "222 옵션 3", 300L); + CacheOptionDTO optionInfo1 = new CacheOptionDTO(optionId, menuId + " 옵션 1", optionPrices[0]); + CacheOptionDTO optionInfo2 = + new CacheOptionDTO(optionId * 2L, menuId + " 옵션 2", optionPrices[1]); + CacheOptionDTO optionInfo3 = + new CacheOptionDTO(optionId * 3L, menuId + " 옵션 3", optionPrices[2]); options.add(optionInfo1); options.add(optionInfo2); options.add(optionInfo3); - - CacheShopDTO shopInfo = new CacheShopDTO(222L, "테스트 매장 이름"); - - ItemDTO itemInfo = new ItemDTO(menuInfo, options, 1, 11600L, shopInfo); - + + CacheShopDTO shopInfo = new CacheShopDTO(shopId, "테스트 매장 이름"); + + ItemDTO itemInfo = new ItemDTO(menuInfo, options, 1, menuPrice + 600L, shopInfo); + return itemInfo; } @@ -84,15 +80,15 @@ public ItemDTO generateItemAnotherShop() { @Test public void addOrdersItemTest_장바구니에_메뉴_추가() { - ItemDTO item1 = generateItem111(); - ItemDTO item2 = generateItem222(); + ItemDTO item1 = generateItem(1L); + ItemDTO item2 = generateItem(2L); given(cartDao.findPeekByMemberId("eric")).willReturn(item1); cartService.addOrdersItem(item2, "eric"); } @Test(expected = IllegalArgumentException.class) public void addOrdersItemTest_장바구니에_다른매장_메뉴_추가() { - ItemDTO item1 = generateItem111(); + ItemDTO item1 = generateItem(1L); ItemDTO item2 = generateItemAnotherShop(); given(cartDao.findPeekByMemberId("eric")).willReturn(item1); cartService.addOrdersItem(item2, "eric"); @@ -100,20 +96,20 @@ public ItemDTO generateItemAnotherShop() { @Test(expected = IndexOutOfBoundsException.class) public void addOrdersItemTest_너무많은메뉴추가() { - given(cartDao.findPeekByMemberId("eric")).willReturn(generateItem111()); + given(cartDao.findPeekByMemberId("eric")).willReturn(generateItem(1L)); given(cartDao.findAllByMemberId("eric")).willReturn(Arrays.asList( - new ItemDTO[] {generateItem111(), generateItem111(), generateItem111(), generateItem111(), - generateItem111(), generateItem111(), generateItem111(), generateItem111(), - generateItem111(), generateItem111(), generateItem111(), generateItem111()})); - cartService.addOrdersItem(generateItem222(), "eric"); + new ItemDTO[] {generateItem(1L), generateItem(1L), generateItem(1L), generateItem(1L), + generateItem(1L), generateItem(1L), generateItem(1L), generateItem(1L), + generateItem(1L), generateItem(1L), generateItem(1L), generateItem(1L)})); + cartService.addOrdersItem(generateItem(2L), "eric"); } @Test(expected = DuplicateItemException.class) public void addOrdersItemTest_같메뉴추가() { - given(cartDao.findPeekByMemberId("eric")).willReturn(generateItem111()); + given(cartDao.findPeekByMemberId("eric")).willReturn(generateItem(1L)); given(cartDao.findAllByMemberId("eric")).willReturn(Arrays.asList( - new ItemDTO[] {generateItem111()})); - cartService.addOrdersItem(generateItem111(), "eric"); + new ItemDTO[] {generateItem(1L)})); + cartService.addOrdersItem(generateItem(1L), "eric"); } @Test @@ -139,34 +135,34 @@ public ItemDTO generateItemAnotherShop() { @Test public void containsEqualItemTest_장바구니_동일아이템_포함여부_검사() { given(cartDao.findAllByMemberId("eric")) - .willReturn(Arrays.asList(new ItemDTO[] {generateItem111()})); + .willReturn(Arrays.asList(new ItemDTO[] {generateItem(1L)})); - assertThat(cartService.containsEqualItem("eric", generateItem111())).isEqualTo(true); - assertThat(cartService.containsEqualItem("eric", generateItem222())).isEqualTo(false); + assertThat(cartService.containsEqualItem("eric", generateItem(1L))).isEqualTo(true); + assertThat(cartService.containsEqualItem("eric", generateItem(2L))).isEqualTo(false); } @Test public void allPriceTest_장바구니_총가격_계산() { given(cartDao.findAllByMemberId("eric")) - .willReturn(Arrays.asList(new ItemDTO[] {generateItem111(), generateItem222()})); + .willReturn(Arrays.asList(new ItemDTO[] {generateItem(1L), generateItem(2L)})); assertThat(cartService.allPrice("eric")).isEqualTo(23200L); } @Test public void priceTest_아이템_가격계산() { - ItemDTO itemInfo = generateItem111(); + ItemDTO itemInfo = generateItem(1L); assertThat(cartService.price(itemInfo)).isEqualTo(11600L); } @Test public void menuPriceTest_아이템_메뉴만_가격계산() { - ItemDTO itemInfo = generateItem111(); + ItemDTO itemInfo = generateItem(1L); assertThat(CartService.menuPrice(itemInfo)).isEqualTo(11000L); } @Test public void menuPriceTest_아이템_옵션만_가격계산() { - ItemDTO itemInfo = generateItem111(); + ItemDTO itemInfo = generateItem(1L); assertThat(CartService.optionsPrice(itemInfo)).isEqualTo(600L); } From d1f8847a9ad6068a6216d21a108e5ef6ac971e99 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Fri, 3 Jan 2020 13:52:13 +0900 Subject: [PATCH 13/24] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20-=20=ED=91=B8=EC=8B=9C=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=EC=8B=9C=20=EC=97=90=EB=9F=AC=EA=B0=80=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=ED=95=98=EB=A9=B4=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=9C=EC=83=9D=EC=8B=9C=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=8C.=20=EB=8C=80=EC=8B=A0=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=97=90=20=ED=95=B4=EB=8B=B9=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=ED=95=98=EA=B3=A0=20redis=EC=97=90=20?= =?UTF-8?q?=ED=95=B4=EB=8B=B9=20=EC=98=A4=EB=A5=98=EA=B0=80=20=EB=82=9C=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EA=B8=B0=EB=A1=9D=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=B6=94=ED=9B=84=20=EB=8B=A4=EC=8B=9C=20=ED=91=B8?= =?UTF-8?q?=EC=8B=9C=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=A0=84=EC=86=A1?= =?UTF-8?q?=EC=9D=84=20=EC=8B=9C=EB=8F=84=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/OrderController.java | 5 ----- src/main/java/com/delfood/dao/FcmDao.java | 11 +++++++++++ .../com/delfood/dto/push/PushMessage.java | 19 +++++++++++++++++++ .../com/delfood/service/OrderService.java | 9 +++++++-- .../java/com/delfood/service/PushService.java | 19 +++++++++++++++---- .../com/delfood/utils/RedisKeyFactory.java | 10 +++++++++- 6 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/delfood/controller/OrderController.java b/src/main/java/com/delfood/controller/OrderController.java index 6f272a1..1ab226a 100644 --- a/src/main/java/com/delfood/controller/OrderController.java +++ b/src/main/java/com/delfood/controller/OrderController.java @@ -98,11 +98,6 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques OrderResponse orderResponse = orderService.order(SessionUtil.getLoginMemberId(session), request.getItems(), request.getShopId()); - // 사장님에게 푸시 메세지 전송 - PushMessage pushMsg = new PushMessage("DelFood 주문", "새로운 주문이 들어왔습니다."); - String ownerId = shopService.getShop(request.getShopId()).getOwnerId(); - pushService.sendMessageToOwner(pushMsg, ownerId); - return orderResponse; } diff --git a/src/main/java/com/delfood/dao/FcmDao.java b/src/main/java/com/delfood/dao/FcmDao.java index 869ddee..3efe483 100644 --- a/src/main/java/com/delfood/dao/FcmDao.java +++ b/src/main/java/com/delfood/dao/FcmDao.java @@ -1,7 +1,9 @@ package com.delfood.dao; +import com.delfood.dto.push.PushMessageForOne; import com.delfood.utils.RedisKeyFactory; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.firebase.messaging.Message; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -108,6 +110,15 @@ public List getOwnerTokens(String ownerId) { .map(e -> objectMapper.convertValue(e, String.class)) .collect(Collectors.toList()); } + + public void addMemberErrorPush(String memberId, List messages) { + redisTemplate.opsForList().rightPush(RedisKeyFactory.generateFcmMemberErrorKey(memberId), + messages); + } + public void addOwnerErrorPush(String ownerId, List messages) { + redisTemplate.opsForList().rightPush(RedisKeyFactory.generateFcmOwnerErrorKey(ownerId), + messages); + } } diff --git a/src/main/java/com/delfood/dto/push/PushMessage.java b/src/main/java/com/delfood/dto/push/PushMessage.java index 98a00fc..128766b 100644 --- a/src/main/java/com/delfood/dto/push/PushMessage.java +++ b/src/main/java/com/delfood/dto/push/PushMessage.java @@ -1,5 +1,6 @@ package com.delfood.dto.push; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NonNull; import org.joda.time.LocalDateTime; @@ -18,4 +19,22 @@ public PushMessage(String title, String message) { this.message = message; this.generatedTime = LocalDateTime.now(); } + + + public static PushMessage getMessasge(Type type) { + return type.pushMessage; + } + + @AllArgsConstructor + public static enum Type { + addOrderRequest(new PushMessage("DelFood 주문", "새로운 주문이 들어왔습니다")), + acceptOrderRequest(new PushMessage("DelFood 접수", "주문이 접수되었습니다")), + requiredOrderRequest(new PushMessage("DelFood 주문취소", "매장에서 주문을 취소하였습니다")), + deliveryMatch(new PushMessage("DelFood 배달원 매칭", "배달원이 매칭되었습니다")), + deliveryStart(new PushMessage("DelFood 배달 시작", "음식 배달이 시작되었습니다")), + deliverySuccess(new PushMessage("DelFood 배달 완료", "배달이 완료되었습니다")); + + private PushMessage pushMessage; + } + } diff --git a/src/main/java/com/delfood/service/OrderService.java b/src/main/java/com/delfood/service/OrderService.java index 932a8f3..94b93d9 100644 --- a/src/main/java/com/delfood/service/OrderService.java +++ b/src/main/java/com/delfood/service/OrderService.java @@ -16,6 +16,7 @@ import com.delfood.dto.OrderItemOptionDTO; import com.delfood.dto.PaymentDTO; import com.delfood.dto.PaymentDTO.Type; +import com.delfood.dto.push.PushMessage; import com.delfood.dto.OrderBillDTO; import com.delfood.mapper.OptionMapper; import com.delfood.mapper.OrderMapper; @@ -50,6 +51,9 @@ public class OrderService { @Autowired private PaymentService paymentService; + @Autowired + private PushService pushService; + /** * 미완성 로직
* 주문 요청을 진행한다. @@ -79,7 +83,9 @@ public OrderResponse order(String memberId, List items, long shopI paymentService.insertPayment(payResult); // 사장님에게 알림(푸시) - + PushMessage pushMsg = PushMessage.getMessasge(PushMessage.Type.addOrderRequest); + String ownerId = shopService.getShop(shopId).getOwnerId(); + pushService.sendMessageToOwner(pushMsg, ownerId); // Exception이 발생하지 않는다. return new OrderResponse(bill, orderId); } @@ -209,7 +215,6 @@ public List getMemberOrder(String memberId, Long lastViewedOrderId) { * 주문 번호를 기반으로 주문 상세를 조회한다. * @author jun * @param orderId 주문 아이디 - * @param memberId 고객 아이디 * @return */ public OrderDTO getOrder(Long orderId) { diff --git a/src/main/java/com/delfood/service/PushService.java b/src/main/java/com/delfood/service/PushService.java index fa17efe..84492bf 100644 --- a/src/main/java/com/delfood/service/PushService.java +++ b/src/main/java/com/delfood/service/PushService.java @@ -71,6 +71,7 @@ public void init() { * @author jun * @param messageInfo 전송할 푸시 정보 */ + @Async("asyncTask") public void sendByToken(PushMessageForOne messageInfo) { Message message = Message.builder() .setToken(messageInfo.getToken()) @@ -84,7 +85,7 @@ public void sendByToken(PushMessageForOne messageInfo) { response = FirebaseMessaging.getInstance().send(message); log.info("Sent message: " + response); } catch (FirebaseMessagingException e) { - throw new RuntimeException(e.getMessage()); + log.error("cannot send message by token. error info : {}", e.getMessage()); } } @@ -106,7 +107,7 @@ public void sendByTopic(PushMessageForTopic topicMessageInfo) { response = FirebaseMessaging.getInstance().send(message); log.info("Sent message: " + response); } catch (FirebaseMessagingException e) { - throw new RuntimeException(e.getMessage()); + log.error("cannot send message by topic. error info : {}", e.getMessage()); } } @@ -130,7 +131,8 @@ public void sendMessageToMember(PushMessage messageInfo, String memberId) { response = FirebaseMessaging.getInstance().sendAll(messages); log.info("Sent message: " + response); } catch (FirebaseMessagingException e) { - throw new RuntimeException(e.getMessage()); + log.error("cannot send to member push message. error info : {}", e.getMessage()); + addErrorMemberPush(memberId, messages); } } @@ -154,7 +156,8 @@ public void sendMessageToOwner(PushMessage messageInfo, String ownerId) { response = FirebaseMessaging.getInstance().sendAll(messages); log.info("Sent message: " + response); } catch (FirebaseMessagingException e) { - throw new RuntimeException(e.getMessage()); + log.error("cannot send message to owner. error info : {}", e.getMessage()); + addErrorOwnerPush(ownerId, messages); } } @@ -199,4 +202,12 @@ public List getMemberTokens(String memberId) { public List getOwnerTokens(String ownerId) { return fcmDao.getOwnerTokens(ownerId); } + + public void addErrorMemberPush(String memberId, List messages) { + fcmDao.addMemberErrorPush(memberId, messages); + } + + public void addErrorOwnerPush(String ownerId, List messages) { + fcmDao.addMemberErrorPush(ownerId, messages); + } } diff --git a/src/main/java/com/delfood/utils/RedisKeyFactory.java b/src/main/java/com/delfood/utils/RedisKeyFactory.java index b18e381..cbfa42d 100644 --- a/src/main/java/com/delfood/utils/RedisKeyFactory.java +++ b/src/main/java/com/delfood/utils/RedisKeyFactory.java @@ -2,7 +2,7 @@ public class RedisKeyFactory { public enum Key { - CART, FCM_MEMBER, FCM_OWNER + CART, FCM_MEMBER, FCM_OWNER, FCM_MEMBER_ERROR, FCM_OWNER_ERROR } // 인스턴스화 방지 @@ -33,4 +33,12 @@ public static String generateFcmOwnerKey(String ownerId) { public static String getIdFromKey(String key) { return key.substring(0, key.indexOf(":")); } + + public static String generateFcmMemberErrorKey(String memberId) { + return generateKey(memberId, Key.FCM_MEMBER_ERROR); + } + + public static String generateFcmOwnerErrorKey(String ownerId) { + return generateKey(ownerId, Key.FCM_OWNER_ERROR); + } } From 6fbfbade13667f6297e57c354ac7fa04dcaae87e Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Fri, 3 Jan 2020 14:39:40 +0900 Subject: [PATCH 14/24] =?UTF-8?q?=EB=B2=84=EA=B7=B8,=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20-=20=EC=A3=BC=EB=AC=B8=20INSERT?= =?UTF-8?q?=EC=8B=9C=20=EB=B0=B0=EB=8B=AC=EB=A3=8C=EA=B0=80=20DB=EC=97=90?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20-=20=ED=91=B8?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=EC=9D=B4=200=EA=B0=9C=EC=9D=B4?= =?UTF-8?q?=EB=A9=B4=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8D=98=20=EA=B2=83=EC=9D=84=20=EB=A1=9C=EA=B7=B8=EB=A7=8C=20?= =?UTF-8?q?=EC=B0=8D=EC=96=B4=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/delfood/service/PushService.java | 12 ++++++++++++ src/main/resources/mybatis/mapper/orders.xml | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/delfood/service/PushService.java b/src/main/java/com/delfood/service/PushService.java index 84492bf..0c169a8 100644 --- a/src/main/java/com/delfood/service/PushService.java +++ b/src/main/java/com/delfood/service/PushService.java @@ -119,6 +119,12 @@ public void sendByTopic(PushMessageForTopic topicMessageInfo) { @Async("asyncTask") public void sendMessageToMember(PushMessage messageInfo, String memberId) { List tokens = fcmDao.getMemberTokens(memberId); + + if (tokens.size() == 0) { // 토큰 개수가 0개이면 오류가 발생한다. + log.debug("해당 회원의 FCM 토큰이 없습니다. 회원 아이디 : {}, 메세지 정보 : {}", memberId, messageInfo); + return; + } + List messages = tokens.stream().map(token -> Message.builder() .putData("title", messageInfo.getTitle()) .putData("message", messageInfo.getMessage()) @@ -144,6 +150,12 @@ public void sendMessageToMember(PushMessage messageInfo, String memberId) { @Async("asyncTask") public void sendMessageToOwner(PushMessage messageInfo, String ownerId) { List tokens = fcmDao.getOwnerTokens(ownerId); + + if (tokens.size() == 0) { + log.debug("해당 사장님의 FCM 토큰이 없습니다. 회원 아이디 : {}, 메세지 정보 : {}", ownerId, messageInfo); + return; + } + List messages = tokens.stream().map(token -> Message.builder() .putData("title", messageInfo.getTitle()) .putData("message", messageInfo.getMessage()) diff --git a/src/main/resources/mybatis/mapper/orders.xml b/src/main/resources/mybatis/mapper/orders.xml index 927c99f..c52ab53 100644 --- a/src/main/resources/mybatis/mapper/orders.xml +++ b/src/main/resources/mybatis/mapper/orders.xml @@ -53,9 +53,9 @@ INSERT INTO ORDERS - (member_id, address_code, address_detail, shop_id) + (member_id, address_code, address_detail, shop_id, delivery_cost) VALUES - (#{memberId}, #{addressCode}, #{addressDetail}, #{shopId}) + (#{memberId}, #{addressCode}, #{addressDetail}, #{shopId}, #{deliveryCost}) From d67d94d3d14f4308a044292dabe7a44c98c5e475 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Mon, 6 Jan 2020 19:03:12 +0900 Subject: [PATCH 15/24] =?UTF-8?q?Coupon=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CouponIssueServiceTest.java | 54 ++++++++ .../delfood/service/CouponServiceTest.java | 129 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 src/test/java/com/delfood/service/CouponIssueServiceTest.java create mode 100644 src/test/java/com/delfood/service/CouponServiceTest.java diff --git a/src/test/java/com/delfood/service/CouponIssueServiceTest.java b/src/test/java/com/delfood/service/CouponIssueServiceTest.java new file mode 100644 index 0000000..98122f5 --- /dev/null +++ b/src/test/java/com/delfood/service/CouponIssueServiceTest.java @@ -0,0 +1,54 @@ +package com.delfood.service; + +import static org.mockito.BDDMockito.given; +import com.delfood.error.exception.DuplicateException; +import com.delfood.mapper.CouponIssueMapper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class CouponIssueServiceTest { + + @InjectMocks + private CouponIssueService couponIssueService; + + @Mock + private CouponIssueMapper couponIssueMapper; + + @Mock + private CouponService couponService; + + @Test + public void createCouponIssue_쿠폰_발급_성공() { + String memberId = "eric"; + long couponId = 1L; + + given(couponIssueMapper.countCouponIssueByMemberIdAndCouponId(memberId, couponId)) + .willReturn(0); + given(couponIssueMapper.insertCouponIssue(memberId, couponId)).willReturn(1); + + couponIssueService.createCouponIssue(memberId, couponId); + } + + @Test(expected = DuplicateException.class) + public void createCouponIssue_쿠폰_발급_실패_재발급() { + String memberId = "eric"; + long couponId = 1L; + + given(couponIssueMapper.countCouponIssueByMemberIdAndCouponId(memberId, couponId)) + .willReturn(1); + + couponIssueService.createCouponIssue(memberId, couponId); + } + + @Test + public void useCouponIssueTest_쿠폰_사용_성공() { + long id = 1L; + given(couponIssueMapper.updateCouponIssueStatusToUsed(id)).willReturn(1); + couponIssueService.useCouponIssue(id); + } + +} diff --git a/src/test/java/com/delfood/service/CouponServiceTest.java b/src/test/java/com/delfood/service/CouponServiceTest.java new file mode 100644 index 0000000..84e686c --- /dev/null +++ b/src/test/java/com/delfood/service/CouponServiceTest.java @@ -0,0 +1,129 @@ +package com.delfood.service; + +import static org.mockito.BDDMockito.given; +import static org.assertj.core.api.Assertions.assertThat; + +import com.delfood.dto.CouponDTO; +import com.delfood.dto.CouponDTO.DiscountType; +import com.delfood.dto.CouponDTO.Status; +import com.delfood.error.exception.coupon.IssuedCouponExistException; +import com.delfood.mapper.CouponMapper; +import java.time.LocalDateTime; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class CouponServiceTest { + + @InjectMocks + private CouponService couponService; + + @Mock + private CouponMapper couponMapper; + + @Mock + CouponIssueService couponIssueService; + + /** + * 정상적으로 작동할 수 있는 쿠폰 DTO를 새로 생성하여 반환한다. + * @author jun + * @return + */ + public static CouponDTO generateCoupon() { + CouponDTO couponInfo = new CouponDTO(); + + couponInfo.setId(111L); + couponInfo.setName("Test Coupon"); + couponInfo.setDiscountType(DiscountType.PERCENT); + couponInfo.setDiscountValue(10L); + couponInfo.setCreatedAt(LocalDateTime.now().minusDays(1)); // 항상 오늘보다 하루 전 만들어진 쿠폰으로 설정한다. + couponInfo.setUpdatedAt(LocalDateTime.now().minusDays(1)); + couponInfo.setEndAt(LocalDateTime.now().plusDays(1)); // 항상 오늘보다 하루 뒤 종료하도록 설정한다. + couponInfo.setStatus(Status.DEFAULT); + + return couponInfo; + } + + @Test + public void addCouponTest_쿠폰_추가_성공() { + CouponDTO couponInfo = generateCoupon(); + given(couponMapper.insertCoupon(couponInfo)).willReturn(1L); + + couponService.addCoupon(couponInfo); + } + + @Test(expected = IllegalStateException.class) + public void addCouponTest_쿠폰_추가_실패_종료일_설정_오류() { + CouponDTO couponInfo = generateCoupon(); + couponInfo.setEndAt(couponInfo.getCreatedAt().minusDays(1)); + given(couponMapper.insertCoupon(couponInfo)).willReturn(1L); + + couponService.addCoupon(couponInfo); + } + + @Test(expected = IllegalArgumentException.class) + public void addCouponTest_쿠폰_추가_실패_할인율_101퍼센트() { + CouponDTO discountValueErrorCouponInfo = generateCoupon(); + discountValueErrorCouponInfo.setDiscountValue(101L); + + couponService.addCoupon(discountValueErrorCouponInfo); + } + + @Test(expected = IllegalArgumentException.class) + public void addCouponTest_쿠폰_추가_실패_할인율_0미만퍼센트() { + CouponDTO discountValueErrorCouponInfo = generateCoupon(); + discountValueErrorCouponInfo.setDiscountValue(-1L); + + couponService.addCoupon(discountValueErrorCouponInfo); + } + + @Test + public void updateCouponNameAndEndAtTest_쿠폰_업데이트_성공() { + String updateName = "new Test Coupon Name"; + LocalDateTime updateEndAt = LocalDateTime.now().plusDays(1L); + given(couponIssueService.isIssued(1L)).willReturn(false); + given(couponMapper.updateCouponNameAndEndAt(1L, updateName, + updateEndAt)).willReturn(1); + + couponService.updateCouponNameAndEndAt(1L, updateName, updateEndAt); + } + + @Test(expected = IssuedCouponExistException.class) + public void updateCouponNameAndEndAtTest_쿠폰_업데이트_실패_이미발행() { + String updateName = "new Test Coupon Name"; + given(couponIssueService.isIssued(1L)).willReturn(true); + + couponService.updateCouponNameAndEndAt(1L, updateName, LocalDateTime.now().plusDays(1L)); + } + + @Test + public void deleteCouponTest_쿠폰_삭제_성공() { + given(couponIssueService.isIssued(1L)).willReturn(false); + given(couponMapper.deleteCoupon(1L)).willReturn(1); + + couponService.deleteCoupon(1L); + } + + @Test(expected = IssuedCouponExistException.class) + public void deleteCouponTest_쿠폰_삭제_실패_이미발행() { + given(couponIssueService.isIssued(1L)).willReturn(true); + + couponService.deleteCoupon(1L); + } + + @Test + public void getAvaliableCouponsTest_사용가능_쿠폰_조회_성공() { + given(couponMapper.findByEndAtGreaterThanNow()) + .willReturn(Arrays.asList(new CouponDTO[] {generateCoupon(), generateCoupon()})); + + assertThat(couponService.getAvaliableCoupons()) + .isNotEmpty() + .hasSize(2); + } + + +} From f7f8aafa579fb676b1666295a1c3e0a472aef017 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Mon, 6 Jan 2020 20:12:44 +0900 Subject: [PATCH 16/24] =?UTF-8?q?=EA=B3=84=EC=82=B0=EC=8B=9C=20=EC=BF=A0?= =?UTF-8?q?=ED=8F=B0=20=EC=A0=81=EC=9A=A9=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/OrderController.java | 39 +++++++++-- .../java/com/delfood/dto/ItemsBillDTO.java | 70 ++++++++++++++++++- .../com/delfood/mapper/CouponIssueMapper.java | 3 + .../delfood/service/CouponIssueService.java | 11 +++ .../com/delfood/service/OrderService.java | 25 +++++-- .../resources/mybatis/mapper/couponIssue.xml | 14 ++++ 6 files changed, 150 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/delfood/controller/OrderController.java b/src/main/java/com/delfood/controller/OrderController.java index 1ab226a..8259886 100644 --- a/src/main/java/com/delfood/controller/OrderController.java +++ b/src/main/java/com/delfood/controller/OrderController.java @@ -7,8 +7,10 @@ import com.delfood.dto.OrderItemDTO; import com.delfood.dto.ShopDTO; import com.delfood.dto.push.PushMessage; +import com.delfood.error.exception.coupon.IssuedCouponExistException; import com.delfood.error.exception.order.TotalPriceMismatchException; import com.delfood.dto.OrderBillDTO; +import com.delfood.service.CouponIssueService; import com.delfood.service.OrderService; import com.delfood.service.PushService; import com.delfood.service.ShopService; @@ -17,6 +19,7 @@ import javax.servlet.http.HttpSession; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NonNull; import lombok.extern.log4j.Log4j2; import org.codehaus.commons.nullanalysis.Nullable; import org.springframework.beans.factory.annotation.Autowired; @@ -41,6 +44,9 @@ public class OrderController { @Autowired PushService pushService; + @Autowired + CouponIssueService couponIssueService; + /** * 아이템들의 가격과 정보를 조회한다. * @author jun @@ -84,7 +90,14 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques if (orderService.isShopItems(request.getItems(), request.getShopId()) == false) { log.error("주문하신 매장의 메뉴 또는 옵션이 아닙니다."); throw new IllegalArgumentException("주문하신 매장의 메뉴 또는 옵션이 아닙니다."); - } + } + + // 쿠폰이 유효한지 검증 + if (couponIssueService.isIssued(request.getCouponIssueId())) { + log.info("이미 사용한 쿠폰 사용 시도. 요청 발행 쿠폰 아이디 : {}", request.getCouponIssueId()); + throw new IssuedCouponExistException("이미 사용한 쿠폰입니다"); + } + // 클라이언트가 계산한 금액과 서버에서 계산한 금액이 같은지 비교 long totalPriceFromServer = orderService.totalPrice(SessionUtil.getLoginMemberId(session), request.getItems()); @@ -96,7 +109,7 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques } OrderResponse orderResponse = orderService.order(SessionUtil.getLoginMemberId(session), - request.getItems(), request.getShopId()); + request.getItems(), request.getShopId(), request.getCouponIssueId()); return orderResponse; } @@ -104,13 +117,18 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques /** * 아이템 리스트들을 상세하게 계산서로 발행한다. * @param session 사용자의 세션 - * @param items 주문하기 전 아이템들 + * @param billRequest 주문할 아이템들, 쿠폰정보. 쿠폰정보는 Null 가능 * @return */ @GetMapping("bill") @MemberLoginCheck - public ItemsBillDTO getBill(HttpSession session, @RequestBody List items) { - return orderService.getBill(SessionUtil.getLoginMemberId(session), items); + public ItemsBillDTO getBill(HttpSession session, @RequestBody BillRequest billRequest) { + if (couponIssueService.isIssued(billRequest.getCouponIssueId())) { + log.info("이미 사용한 쿠폰 사용 시도. 요청 발행 쿠폰 아이디 : {}", billRequest.getCouponIssueId()); + throw new IssuedCouponExistException("이미 사용한 쿠폰입니다"); + } + return orderService.getBill(SessionUtil.getLoginMemberId(session), billRequest.getItems(), + billRequest.getCouponIssueId()); } /** @@ -152,8 +170,19 @@ public OrderDTO getOrder(HttpSession session, @PathVariable Long orderId) { // request @Getter private static class OrderRequest { + @NonNull private Long shopId; + @NonNull private List items; + @Nullable + private Long couponIssueId; private long totalPrice; } + + @Getter + private static class BillRequest { + private List items; + @Nullable + private Long couponIssueId; + } } diff --git a/src/main/java/com/delfood/dto/ItemsBillDTO.java b/src/main/java/com/delfood/dto/ItemsBillDTO.java index 53af7ce..1a51e77 100644 --- a/src/main/java/com/delfood/dto/ItemsBillDTO.java +++ b/src/main/java/com/delfood/dto/ItemsBillDTO.java @@ -1,5 +1,6 @@ package com.delfood.dto; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import lombok.Builder; @@ -14,6 +15,10 @@ public class ItemsBillDTO { @NonNull private List menus; + private long itemsPrice; + + private long discountPrice; + private long totalPrice; @NonNull private String memberId; @@ -24,6 +29,8 @@ public class ItemsBillDTO { private DeliveryInfo deliveryInfo; + private CouponInfo couponInfo; + /** * 해당 인자를 세팅하여 새로운 객체를 반환한다. * 리스트인 'menus'에는 ArrayList를 할당한다. @@ -37,7 +44,8 @@ public ItemsBillDTO(@NonNull String memberId, double distanceMeter, long deliveryPrice, long itemsPrice, - List menus) { + List menus, + CouponInfo couponInfo) { this.memberId = memberId; this.addressInfo = addressInfo; this.shopInfo = shopInfo; @@ -45,6 +53,7 @@ public ItemsBillDTO(@NonNull String memberId, .deliveryPrice(deliveryPrice) .build(); this.menus = menus; + this.couponInfo = couponInfo; this.totalPrice = totalPrice(); } @@ -111,15 +120,70 @@ public static class DeliveryInfo { private long deliveryPrice; } + @Getter + @NoArgsConstructor + public static class CouponInfo { + private long couponIssueId; + private long couponId; + private String memberId; + private String name; + private CouponDTO.DiscountType discountType; + private long discountValue; + private LocalDateTime createdAt; + private LocalDateTime endAt; + + /** + * 직접 쿠폰 정보를 생설할 경우 사용하는 빌더. + * @param couponIssueId 발행 쿠폰 아이디 + * @param couponId 쿠폰 아이디 + * @param memberId 회원 아이디 + * @param name 쿠폰 이름 + * @param discountType 할인 타입 + * @param discountValue 할인 가격 + * @param createAt 발행일 + * @param endAt 만료일 + */ + @Builder + public CouponInfo(long couponIssueId, long couponId, String memberId, String name, + CouponDTO.DiscountType discountType, long discountValue, LocalDateTime createAt, LocalDateTime endAt) { + this.couponIssueId = couponIssueId; + this.couponId = couponId; + this.memberId = memberId; + this.name = name; + this.discountType = discountType; + this.discountValue = discountValue; + this.createdAt = createAt; + this.endAt = endAt; + } + } + /** - * 메뉴 가격, 옵션 가격, 배달 가격을 합친 총 가격을 계산한다. + * 메뉴 가격, 옵션 가격, 배달 가격, 할인 가격을 합친 총 가격을 계산한다. * @author jun * @return */ public long totalPrice() { - return menus.stream().mapToLong(menu -> menu.getPrice() + long itemsPrice = menus.stream().mapToLong(menu -> menu.getPrice() + menu.getOptions().stream().mapToLong(option -> option.getPrice()).sum()).sum() + deliveryInfo.getDeliveryPrice(); + long couponDiscountPrice; + + if (couponInfo != null) { // 사용하는 쿠폰이 있을 경우 + if (couponInfo.getDiscountType() == CouponDTO.DiscountType.PERCENT) { + // 이렇게하면 소수점 미만이 버림된다. + couponDiscountPrice = itemsPrice * couponInfo.getDiscountValue() / 100L; + } else { + couponDiscountPrice = couponInfo.getDiscountValue(); + } + } else { // 쿠폰을 사용하지 않을 경우 + couponDiscountPrice = 0; + } + + this.itemsPrice = itemsPrice; + this.discountPrice = couponDiscountPrice; + this.totalPrice = itemsPrice - couponDiscountPrice; + + return totalPrice; } } diff --git a/src/main/java/com/delfood/mapper/CouponIssueMapper.java b/src/main/java/com/delfood/mapper/CouponIssueMapper.java index 142c10e..826f56d 100644 --- a/src/main/java/com/delfood/mapper/CouponIssueMapper.java +++ b/src/main/java/com/delfood/mapper/CouponIssueMapper.java @@ -3,6 +3,7 @@ import java.util.List; import org.springframework.stereotype.Repository; import com.delfood.dto.CouponIssueDTO; +import com.delfood.dto.ItemsBillDTO.CouponInfo; @Repository public interface CouponIssueMapper { @@ -53,4 +54,6 @@ public interface CouponIssueMapper { */ public List findByMemberId(String memberId); + public CouponInfo findInfoById(long couponIssueId); + } diff --git a/src/main/java/com/delfood/service/CouponIssueService.java b/src/main/java/com/delfood/service/CouponIssueService.java index 40611d8..9b221b0 100644 --- a/src/main/java/com/delfood/service/CouponIssueService.java +++ b/src/main/java/com/delfood/service/CouponIssueService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.delfood.dto.CouponIssueDTO; +import com.delfood.dto.ItemsBillDTO.CouponInfo; import com.delfood.error.exception.DuplicateException; import com.delfood.mapper.CouponIssueMapper; import lombok.extern.log4j.Log4j2; @@ -86,6 +87,16 @@ public void useCouponIssue(Long id) { public List getCouponIssues(String memberId) { return couponIssueMapper.findByMemberId(memberId); } + + /** + * 발행 쿠폰 아이디를 기준으로 쿠폰 전반 정보를 조회한다. + * @author jun + * @param couponIssueId 발행 쿠폰 아이디 + * @return + */ + public CouponInfo getCouponInfoByIssueId(long couponIssueId) { + return couponIssueMapper.findInfoById(couponIssueId); + } diff --git a/src/main/java/com/delfood/service/OrderService.java b/src/main/java/com/delfood/service/OrderService.java index 94b93d9..ebde7ab 100644 --- a/src/main/java/com/delfood/service/OrderService.java +++ b/src/main/java/com/delfood/service/OrderService.java @@ -7,6 +7,7 @@ import com.delfood.dto.ItemsBillDTO.ShopInfo; import com.delfood.dto.ItemsBillDTO.MenuInfo.OptionInfo; import com.delfood.dto.MemberDTO.Status; +import com.delfood.error.exception.coupon.IssuedCouponExistException; import com.delfood.error.exception.order.TotalPriceMismatchException; import com.delfood.dto.MemberDTO; import com.delfood.dto.MenuDTO; @@ -21,6 +22,7 @@ import com.delfood.mapper.OptionMapper; import com.delfood.mapper.OrderMapper; import com.delfood.utils.OrderUtil; +import com.google.firebase.database.annotations.Nullable; import lombok.NonNull; import lombok.extern.log4j.Log4j2; import java.util.ArrayList; @@ -54,8 +56,10 @@ public class OrderService { @Autowired private PushService pushService; + @Autowired + private CouponIssueService couponIssueService; + /** - * 미완성 로직
* 주문 요청을 진행한다. * 사용자가 주문 요청시 전달받은 가격과, 서버에서 직접 비교한 가격을 비교하여 다르면 예외처리 할 예정. * @param memberId 고객 아이디 @@ -63,13 +67,18 @@ public class OrderService { * @return */ @Transactional - public OrderResponse order(String memberId, List items, long shopId) { + public OrderResponse order(String memberId, List items, long shopId, @Nullable Long couponIssueId) { // 주문 준비 작업. 결제 전. Long orderId = preOrder(memberId, items, shopId); // 계산서 발행 - ItemsBillDTO bill = getBill(memberId, items); + ItemsBillDTO bill = getBill(memberId, items, couponIssueId); + + // 쿠폰 사용처리 + if (bill.getCouponInfo() != null) { + couponIssueService.useCouponIssue(bill.getCouponInfo().getCouponIssueId()); + } // 가상 결제 진행 PaymentDTO paymentInfo = PaymentDTO.builder() @@ -145,11 +154,18 @@ private Long preOrder(String memberId, List items, Long shopId) { * @return */ @Transactional(readOnly = true) - public ItemsBillDTO getBill(String memberId, List items) { + public ItemsBillDTO getBill(String memberId, List items, Long couponIssueId) { // 고객 주소 정보 추출 AddressDTO addressInfo = memberService.getMemberInfo(memberId).getAddressInfo(); // 매장 정보 추출 ShopInfo shopInfo = shopService.getShopByMenuId(items.get(0).getMenuId()); + + // 쿠폰 정보 추출 + ItemsBillDTO.CouponInfo couponInfo = null; + if (couponIssueId != null) { + couponInfo = couponIssueService.getCouponInfoByIssueId(couponIssueId); + } + // 배달료 계산 long deliveryPrice = addressService.deliveryPrice(memberId, shopInfo.getId()); @@ -160,6 +176,7 @@ public ItemsBillDTO getBill(String memberId, List items) { .shopInfo(shopInfo) .deliveryPrice(deliveryPrice) .menus(orderMapper.findItemsBill(items)) + .couponInfo(couponInfo) .build(); return bill; } diff --git a/src/main/resources/mybatis/mapper/couponIssue.xml b/src/main/resources/mybatis/mapper/couponIssue.xml index 8164412..dad751a 100644 --- a/src/main/resources/mybatis/mapper/couponIssue.xml +++ b/src/main/resources/mybatis/mapper/couponIssue.xml @@ -34,5 +34,19 @@ AND i.status = 'DEFAULT' + +
From 64e21c811bd45f66c084735519cf18b0945fe417 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Tue, 7 Jan 2020 16:14:11 +0900 Subject: [PATCH 17/24] =?UTF-8?q?-=20=EB=82=B4=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=97=90=EC=84=9C=EB=8F=84=20=EC=BF=A0?= =?UTF-8?q?=ED=8F=B0=20=EC=A0=81=EC=9A=A9=ED=95=9C=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EB=A5=BC=20=EB=B3=BC=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20-=20=EC=9E=90=EC=9E=98=ED=95=9C=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/OrderController.java | 37 ++++++++++---- .../java/com/delfood/dto/ItemsBillDTO.java | 50 ++++++++++++++++--- .../java/com/delfood/dto/OrderBillDTO.java | 2 + src/main/java/com/delfood/dto/OrderDTO.java | 5 +- .../com/delfood/dto/SimpleCouponInfo.java | 15 ++++++ .../com/delfood/mapper/CouponIssueMapper.java | 6 ++- .../java/com/delfood/mapper/OrderMapper.java | 1 + .../delfood/service/CouponIssueService.java | 34 ++++++++++++- .../com/delfood/service/OrderService.java | 37 ++++++-------- .../resources/mybatis/mapper/couponIssue.xml | 8 ++- src/main/resources/mybatis/mapper/orders.xml | 50 +++++++++++++++++-- src/main/resources/mybatis/mapper/payment.xml | 2 +- .../service/CouponIssueServiceTest.java | 7 --- 13 files changed, 196 insertions(+), 58 deletions(-) create mode 100644 src/main/java/com/delfood/dto/SimpleCouponInfo.java diff --git a/src/main/java/com/delfood/controller/OrderController.java b/src/main/java/com/delfood/controller/OrderController.java index 8259886..7de6e5e 100644 --- a/src/main/java/com/delfood/controller/OrderController.java +++ b/src/main/java/com/delfood/controller/OrderController.java @@ -18,6 +18,7 @@ import java.util.List; import javax.servlet.http.HttpSession; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NonNull; import lombok.extern.log4j.Log4j2; @@ -49,14 +50,17 @@ public class OrderController { /** * 아이템들의 가격과 정보를 조회한다. + * 쿠폰과 배달 가격을 제외한 순수 아이템 가격만 제공한다. * @author jun * @param items 가격을 계산할 아이템들 * @return */ @GetMapping("price") @MemberLoginCheck - public long getItemsBill(HttpSession session, @RequestBody List items) { - return orderService.totalPrice(SessionUtil.getLoginMemberId(session), items); + public ItemsBillResponse getItemsBill(HttpSession session, + @RequestBody List items) { + long itemsPrice = orderService.totalPrice(SessionUtil.getLoginMemberId(session), items); + return ItemsBillResponse.builder().itemsPrice(itemsPrice).build(); } /** @@ -93,21 +97,25 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques } // 쿠폰이 유효한지 검증 - if (couponIssueService.isIssued(request.getCouponIssueId())) { + if (couponIssueService.isUsed(request.getCouponIssueId())) { log.info("이미 사용한 쿠폰 사용 시도. 요청 발행 쿠폰 아이디 : {}", request.getCouponIssueId()); throw new IssuedCouponExistException("이미 사용한 쿠폰입니다"); } // 클라이언트가 계산한 금액과 서버에서 계산한 금액이 같은지 비교 - long totalPriceFromServer = - orderService.totalPrice(SessionUtil.getLoginMemberId(session), request.getItems()); - if (totalPriceFromServer != request.getTotalPrice()) { + long totalItemsPriceFromServer = orderService.totalPrice(SessionUtil.getLoginMemberId(session), + request.getItems()); + long discountPriceFromServer = + couponIssueService.discountPrice(request.getCouponIssueId(), totalItemsPriceFromServer); + long totalPrice = totalItemsPriceFromServer - discountPriceFromServer; + if (totalPrice != request.getTotalPrice()) { log.error("Total Price Mismatch! client price : {}, server price : {}", - request.getTotalPrice(), - totalPriceFromServer); + request.getTotalPrice(), totalPrice); + log.error("totalItemsPriceFromServer : {}, discountPriceFromServer : {}", + totalItemsPriceFromServer, discountPriceFromServer); throw new TotalPriceMismatchException("Total Price Mismatch!"); } - + OrderResponse orderResponse = orderService.order(SessionUtil.getLoginMemberId(session), request.getItems(), request.getShopId(), request.getCouponIssueId()); @@ -123,7 +131,7 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques @GetMapping("bill") @MemberLoginCheck public ItemsBillDTO getBill(HttpSession session, @RequestBody BillRequest billRequest) { - if (couponIssueService.isIssued(billRequest.getCouponIssueId())) { + if (couponIssueService.isUsed(billRequest.getCouponIssueId())) { log.info("이미 사용한 쿠폰 사용 시도. 요청 발행 쿠폰 아이디 : {}", billRequest.getCouponIssueId()); throw new IssuedCouponExistException("이미 사용한 쿠폰입니다"); } @@ -185,4 +193,13 @@ private static class BillRequest { @Nullable private Long couponIssueId; } + + + // response + + @Builder + @Getter + private static class ItemsBillResponse { + private long itemsPrice; + } } diff --git a/src/main/java/com/delfood/dto/ItemsBillDTO.java b/src/main/java/com/delfood/dto/ItemsBillDTO.java index 1a51e77..2856503 100644 --- a/src/main/java/com/delfood/dto/ItemsBillDTO.java +++ b/src/main/java/com/delfood/dto/ItemsBillDTO.java @@ -2,15 +2,20 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import org.codehaus.jackson.annotate.JsonIgnore; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; +import lombok.extern.log4j.Log4j2; // 사용자에게 전달하는 최종 주문서 DTO @Getter @NoArgsConstructor +@Log4j2 public class ItemsBillDTO { @NonNull private List menus; @@ -43,9 +48,9 @@ public ItemsBillDTO(@NonNull String memberId, @NonNull ShopInfo shopInfo, double distanceMeter, long deliveryPrice, - long itemsPrice, List menus, - CouponInfo couponInfo) { + CouponInfo couponInfo, + List ordersItems) { this.memberId = memberId; this.addressInfo = addressInfo; this.shopInfo = shopInfo; @@ -54,7 +59,29 @@ public ItemsBillDTO(@NonNull String memberId, .build(); this.menus = menus; this.couponInfo = couponInfo; - this.totalPrice = totalPrice(); + + // 아이템 개수 맞춰주기 + log.debug("아이템 개수 맞추기 시작"); + ordersItems.stream().forEach(item -> { + menus.stream().forEach(menu -> { + log.debug("비교 메뉴 1 : {}, 비교 메뉴 2 : {}", menu.getId(), item.getMenuId()); + log.debug("비교 옵션 1 : {}, 비교 옵션 2 : {}", + item.getOptions().stream().mapToLong(e -> e.getOptionId()).sorted().toArray(), + menu.getOptions().stream().mapToLong(e -> e.getId()).sorted().toArray()); + log.debug("메뉴 비교 결과 : {}, 옵션 비교 결과 : {}", menu.getId() == item.getMenuId(), + item.getOptions().stream().mapToLong(e -> e.getOptionId()).sorted().toArray() + .equals(menu.getOptions().stream().mapToLong(e -> e.getId()).sorted().toArray())); + if (menu.getId() == item.getMenuId() && Arrays.equals( + item.getOptions().stream().mapToLong(e -> e.getOptionId()).sorted().toArray(), + menu.getOptions().stream().mapToLong(e -> e.getId()).sorted().toArray())) { + log.debug("같은 아이템 발견. 카운트 : {} ", item.getCount()); + menu.count = item.getCount(); + } + }); + }); + log.debug("아이템 개수 맞추기 끝"); + + totalPrice(); } @Getter @@ -64,6 +91,7 @@ public static class MenuInfo { private String name; private long price; private List options; + private long count; /** * 메뉴 정보의 간략한 정보를 저장하는 DTO를 생성한다. @@ -72,10 +100,11 @@ public static class MenuInfo { * @param price 메뉴 가격 */ @Builder - public MenuInfo(long id, @NonNull String name, long price) { + public MenuInfo(long id, @NonNull String name, long price, long count) { this.id = id; this.name = name; this.price = price; + this.count = count; options = new ArrayList(); } @@ -159,13 +188,18 @@ public CouponInfo(long couponIssueId, long couponId, String memberId, String nam /** * 메뉴 가격, 옵션 가격, 배달 가격, 할인 가격을 합친 총 가격을 계산한다. + * 해당 인스턴스의 내부 상태를 변경시킨다. * @author jun * @return */ public long totalPrice() { - long itemsPrice = menus.stream().mapToLong(menu -> menu.getPrice() - + menu.getOptions().stream().mapToLong(option -> option.getPrice()).sum()).sum() - + deliveryInfo.getDeliveryPrice(); + long itemsPrice = menus.stream().mapToLong(menu -> { + log.debug("메뉴 가격 : {}, 개수 : {}", menu.getPrice(), menu.getCount()); + return (menu.getPrice() + + menu.getOptions().stream().mapToLong(option -> option.getPrice()).sum()) + * menu.getCount(); + }).sum(); + long couponDiscountPrice; if (couponInfo != null) { // 사용하는 쿠폰이 있을 경우 @@ -181,7 +215,7 @@ public long totalPrice() { this.itemsPrice = itemsPrice; this.discountPrice = couponDiscountPrice; - this.totalPrice = itemsPrice - couponDiscountPrice; + this.totalPrice = itemsPrice + deliveryInfo.getDeliveryPrice() - couponDiscountPrice; return totalPrice; } diff --git a/src/main/java/com/delfood/dto/OrderBillDTO.java b/src/main/java/com/delfood/dto/OrderBillDTO.java index 0783561..9281e07 100644 --- a/src/main/java/com/delfood/dto/OrderBillDTO.java +++ b/src/main/java/com/delfood/dto/OrderBillDTO.java @@ -12,6 +12,7 @@ public class OrderBillDTO { private String memberId; private OrderStatus orderStatus; private LocalDateTime startTime; + private SimpleCouponInfo couponInfo; private Long deliveryCost; private SimpleAddressInfo addressInfo; private List menus; @@ -27,4 +28,5 @@ public static class SimpleAddressInfo { private Integer buildingSideNumber; private String addressDetail; } + } diff --git a/src/main/java/com/delfood/dto/OrderDTO.java b/src/main/java/com/delfood/dto/OrderDTO.java index b3bda8a..f69d64c 100644 --- a/src/main/java/com/delfood/dto/OrderDTO.java +++ b/src/main/java/com/delfood/dto/OrderDTO.java @@ -55,7 +55,10 @@ public enum OrderStatus { @Nullable private String shopName; - List items; + private List items; + + @Nullable + private SimpleCouponInfo couponInfo; @Builder public OrderDTO(String memberId, String addressCode, String addressDetail, Long shopId, diff --git a/src/main/java/com/delfood/dto/SimpleCouponInfo.java b/src/main/java/com/delfood/dto/SimpleCouponInfo.java new file mode 100644 index 0000000..4bb0fd0 --- /dev/null +++ b/src/main/java/com/delfood/dto/SimpleCouponInfo.java @@ -0,0 +1,15 @@ +package com.delfood.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class SimpleCouponInfo { + private Long couponIssueId; + private Long couponId; + private String couponName; + private Long discountType; + private Long discountValue; + private Long discountPrice; +} diff --git a/src/main/java/com/delfood/mapper/CouponIssueMapper.java b/src/main/java/com/delfood/mapper/CouponIssueMapper.java index 826f56d..db19ae6 100644 --- a/src/main/java/com/delfood/mapper/CouponIssueMapper.java +++ b/src/main/java/com/delfood/mapper/CouponIssueMapper.java @@ -42,10 +42,10 @@ public interface CouponIssueMapper { /** * 발급 쿠폰의 상태를 USED로 변경한다. * @param id 발급 쿠폰 아이디 - * + * @param paymentId 결제 아이디 * @author jinyoung */ - public int updateCouponIssueStatusToUsed(Long id); + public int updateCouponIssueStatusToUsed(Long id, Long paymentId); /** * 회원이 가진 쿠폰들을 조회한다. @@ -56,4 +56,6 @@ public interface CouponIssueMapper { public CouponInfo findInfoById(long couponIssueId); + public CouponIssueDTO findById(long couponIssueId); + } diff --git a/src/main/java/com/delfood/mapper/OrderMapper.java b/src/main/java/com/delfood/mapper/OrderMapper.java index 5efe3f4..578d4a8 100644 --- a/src/main/java/com/delfood/mapper/OrderMapper.java +++ b/src/main/java/com/delfood/mapper/OrderMapper.java @@ -29,4 +29,5 @@ public interface OrderMapper { List findByMemberId(String memberId, Long lastViewedOrderId); boolean isShopItem(List items, Long shopId); + } diff --git a/src/main/java/com/delfood/service/CouponIssueService.java b/src/main/java/com/delfood/service/CouponIssueService.java index 9b221b0..5819ee5 100644 --- a/src/main/java/com/delfood/service/CouponIssueService.java +++ b/src/main/java/com/delfood/service/CouponIssueService.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.delfood.dto.CouponDTO; import com.delfood.dto.CouponIssueDTO; import com.delfood.dto.ItemsBillDTO.CouponInfo; import com.delfood.error.exception.DuplicateException; @@ -71,8 +72,8 @@ public void createCouponIssue(String memberId, Long couponId) { * @author jinyoung */ @Transactional(rollbackFor = RuntimeException.class) - public void useCouponIssue(Long id) { - int result = couponIssueMapper.updateCouponIssueStatusToUsed(id); + public void useCouponIssue(Long id, Long paymentId) { + int result = couponIssueMapper.updateCouponIssueStatusToUsed(id, paymentId); if (result != 1) { log.error("update coupon status error! id : {}", id); throw new RuntimeException("update coupon status error!"); @@ -99,6 +100,35 @@ public CouponInfo getCouponInfoByIssueId(long couponIssueId) { } + /** + * 쿠폰으로 인한 할인 가격을 계산한다. + * 쿠폰이 퍼센트 쿠폰일 시 입력된 가격을 기준으로 퍼센트 할인 가격을 리턴한다. + * 쿠폰이 정액 할인 쿠폰일 시 쿠폰의 할인값을 리턴한다. + * 쿠폰의 할인 값이 아이템 가격보다 클 시 아이템의 가격을 할인값으로 리턴한다. + * + * @author jun + * @param couponIssueId + * @param price + * @return + */ + public long discountPrice(long couponIssueId, long price) { + CouponInfo couponInfo = getCouponInfoByIssueId(couponIssueId); + long discountPrice = 0; + + if (couponInfo.getDiscountType() == CouponDTO.DiscountType.PERCENT) { + discountPrice = price * couponInfo.getDiscountValue() / 100L; + } else { + discountPrice = couponInfo.getDiscountValue(); + } + + return discountPrice > price ? price : discountPrice; + } + + public boolean isUsed(long couponIssueId) { + CouponIssueDTO couponIssueInfo = couponIssueMapper.findById(couponIssueId); + return couponIssueInfo.getStatus().equals(CouponIssueDTO.Status.USED); + } + } diff --git a/src/main/java/com/delfood/service/OrderService.java b/src/main/java/com/delfood/service/OrderService.java index ebde7ab..2e4c4cb 100644 --- a/src/main/java/com/delfood/service/OrderService.java +++ b/src/main/java/com/delfood/service/OrderService.java @@ -3,31 +3,21 @@ import com.delfood.controller.response.OrderResponse; import com.delfood.dto.AddressDTO; import com.delfood.dto.ItemsBillDTO; -import com.delfood.dto.ItemsBillDTO.MenuInfo; import com.delfood.dto.ItemsBillDTO.ShopInfo; -import com.delfood.dto.ItemsBillDTO.MenuInfo.OptionInfo; -import com.delfood.dto.MemberDTO.Status; -import com.delfood.error.exception.coupon.IssuedCouponExistException; -import com.delfood.error.exception.order.TotalPriceMismatchException; import com.delfood.dto.MemberDTO; -import com.delfood.dto.MenuDTO; -import com.delfood.dto.OptionDTO; +import com.delfood.dto.OrderBillDTO; import com.delfood.dto.OrderDTO; import com.delfood.dto.OrderItemDTO; import com.delfood.dto.OrderItemOptionDTO; import com.delfood.dto.PaymentDTO; import com.delfood.dto.PaymentDTO.Type; import com.delfood.dto.push.PushMessage; -import com.delfood.dto.OrderBillDTO; -import com.delfood.mapper.OptionMapper; import com.delfood.mapper.OrderMapper; import com.delfood.utils.OrderUtil; import com.google.firebase.database.annotations.Nullable; -import lombok.NonNull; -import lombok.extern.log4j.Log4j2; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; +import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -67,18 +57,15 @@ public class OrderService { * @return */ @Transactional - public OrderResponse order(String memberId, List items, long shopId, @Nullable Long couponIssueId) { - + public OrderResponse order(String memberId, List items, long shopId, + @Nullable Long couponIssueId) { + // 주문 준비 작업. 결제 전. Long orderId = preOrder(memberId, items, shopId); // 계산서 발행 ItemsBillDTO bill = getBill(memberId, items, couponIssueId); - // 쿠폰 사용처리 - if (bill.getCouponInfo() != null) { - couponIssueService.useCouponIssue(bill.getCouponInfo().getCouponIssueId()); - } // 가상 결제 진행 PaymentDTO paymentInfo = PaymentDTO.builder() @@ -91,6 +78,12 @@ public OrderResponse order(String memberId, List items, long shopI PaymentDTO payResult = mockPayService.pay(paymentInfo); paymentService.insertPayment(payResult); + + // 쿠폰 사용처리 + if (bill.getCouponInfo() != null) { + couponIssueService.useCouponIssue(bill.getCouponInfo().getCouponIssueId(), payResult.getId()); + } + // 사장님에게 알림(푸시) PushMessage pushMsg = PushMessage.getMessasge(PushMessage.Type.addOrderRequest); String ownerId = shopService.getShop(shopId).getOwnerId(); @@ -177,13 +170,14 @@ public ItemsBillDTO getBill(String memberId, List items, Long coup .deliveryPrice(deliveryPrice) .menus(orderMapper.findItemsBill(items)) .couponInfo(couponInfo) + .ordersItems(items) .build(); return bill; } /** - * 총 가격을 계산한다. + * 아이템들의 총 가격을 계산한다. * @author jun * @param items 계산할 아이템들 * @return 총 가격 @@ -191,10 +185,7 @@ public ItemsBillDTO getBill(String memberId, List items, Long coup @Transactional(readOnly = true) public long totalPrice(String memberId, List items) { long totalPrice = orderMapper.findItemsPrice(items); - long deliveryPrice = addressService.deliveryPrice(memberId, - shopService.getShopByMenuId(items.get(0).getMenuId()).getId()); - - return totalPrice + deliveryPrice; + return totalPrice; } diff --git a/src/main/resources/mybatis/mapper/couponIssue.xml b/src/main/resources/mybatis/mapper/couponIssue.xml index dad751a..b30c249 100644 --- a/src/main/resources/mybatis/mapper/couponIssue.xml +++ b/src/main/resources/mybatis/mapper/couponIssue.xml @@ -20,7 +20,8 @@ UPDATE COUPON_ISSUE - SET status = 'USED' + SET status = 'USED', + payment_id = #{paymentId} WHERE id = #{id} @@ -48,5 +49,10 @@ AND i.id = #{couponIssueId} + diff --git a/src/main/resources/mybatis/mapper/orders.xml b/src/main/resources/mybatis/mapper/orders.xml index c52ab53..bf0423c 100644 --- a/src/main/resources/mybatis/mapper/orders.xml +++ b/src/main/resources/mybatis/mapper/orders.xml @@ -14,6 +14,14 @@ + + + + + + + + @@ -30,6 +38,7 @@ + @@ -133,13 +151,22 @@ item_opt.id itemOptionId, opt.id optionId, opt.name optionName, - opt.price optionPrice + opt.price optionPrice, + cpn_isu.id couponIssueId, + cpn.id couponId, + cpn.name coouponName, + cpn.discount_type discountType, + cpn.discount_value discountValue, + pay.amount_discount discountPrice FROM ORDERS odr LEFT OUTER JOIN ORDERS_ITEM item ON (odr.id = item.order_id) LEFT OUTER JOIN ORDERS_ITEM_OPTION item_opt ON (item.id = item_opt.order_item_id) INNER JOIN MENU menu ON (menu.id = item.menu_id) LEFT OUTER JOIN OPTION opt ON (opt.menu_id = menu.id) INNER JOIN SHOP shop ON (odr.shop_id = shop.id) INNER JOIN MEMBER member ON (odr.member_id = member.id) + INNER JOIN PAYMENT pay ON (pay.order_id = odr.id) + LEFT OUTER JOIN COUPON_ISSUE cpn_isu ON (pay.id = cpn_isu.payment_id) + LEFT OUTER JOIN COUPON cpn ON (cpn_isu.coupon_id = cpn.id) WHERE member.id = #{memberId} AND odr.id > #{lastViewedOrderId} @@ -164,6 +191,14 @@ + + + + + + + + @@ -196,12 +231,21 @@ addr.building_side_number buildingSideNumber, odr.address_detail addressDetail, odr.member_id memberId, - odr.order_status orderStatus + odr.order_status orderStatus, + cpn_isu.id couponIssueId, + cpn.id couponId, + cpn.name coouponName, + cpn.discount_type discountType, + cpn.discount_value discountValue, + pay.amount_discount discountPrice FROM ORDERS odr LEFT OUTER JOIN ORDERS_ITEM item ON (odr.id = item.order_id) JOIN ORDERS_ITEM_OPTION item_opt ON (item.id = item_opt.order_item_id) JOIN MENU m ON (m.id = item.menu_id) JOIN OPTION m_opt ON (item_opt.option_id = m_opt.id) JOIN ADDRESS addr ON (odr.address_code = addr.building_management_number) + INNER JOIN PAYMENT pay ON (pay.order_id = odr.id) + LEFT OUTER JOIN COUPON_ISSUE cpn_isu ON (pay.id = cpn_isu.payment_id) + LEFT OUTER JOIN COUPON cpn ON (cpn_isu.coupon_id = cpn.id) WHERE odr.id = #{orderId} diff --git a/src/main/resources/mybatis/mapper/payment.xml b/src/main/resources/mybatis/mapper/payment.xml index 0db374a..230a4f0 100644 --- a/src/main/resources/mybatis/mapper/payment.xml +++ b/src/main/resources/mybatis/mapper/payment.xml @@ -1,7 +1,7 @@ - + INSERT INTO PAYMENT (type, amount_payment, pay_time, order_id, status, amount_discount) VALUES diff --git a/src/test/java/com/delfood/service/CouponIssueServiceTest.java b/src/test/java/com/delfood/service/CouponIssueServiceTest.java index 98122f5..8c6804b 100644 --- a/src/test/java/com/delfood/service/CouponIssueServiceTest.java +++ b/src/test/java/com/delfood/service/CouponIssueServiceTest.java @@ -44,11 +44,4 @@ public class CouponIssueServiceTest { couponIssueService.createCouponIssue(memberId, couponId); } - @Test - public void useCouponIssueTest_쿠폰_사용_성공() { - long id = 1L; - given(couponIssueMapper.updateCouponIssueStatusToUsed(id)).willReturn(1); - couponIssueService.useCouponIssue(id); - } - } From c04094818cd9aa87dc03b9c34c200c94c95eb867 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Fri, 10 Jan 2020 12:58:55 +0900 Subject: [PATCH 18/24] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20-=20=EB=B0=9C=ED=96=89=20=EC=BF=A0=ED=8F=B0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=8B=9C=20NULL=20=EC=B2=B4=ED=81=AC=20-=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=ED=95=98=EB=82=98=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/delfood/controller/OrderController.java | 8 ++++---- .../com/delfood/service/CouponIssueService.java | 13 +++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/delfood/controller/OrderController.java b/src/main/java/com/delfood/controller/OrderController.java index 7de6e5e..a3a1fd6 100644 --- a/src/main/java/com/delfood/controller/OrderController.java +++ b/src/main/java/com/delfood/controller/OrderController.java @@ -109,10 +109,10 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques couponIssueService.discountPrice(request.getCouponIssueId(), totalItemsPriceFromServer); long totalPrice = totalItemsPriceFromServer - discountPriceFromServer; if (totalPrice != request.getTotalPrice()) { - log.error("Total Price Mismatch! client price : {}, server price : {}", - request.getTotalPrice(), totalPrice); - log.error("totalItemsPriceFromServer : {}, discountPriceFromServer : {}", - totalItemsPriceFromServer, discountPriceFromServer); + log.error( + "Total Price Mismatch! client price : {}, server price : {}," + + " totalItemsPriceFromServer : {}, discountPriceFromServer : {}", + request.getTotalPrice(), totalPrice, totalItemsPriceFromServer, discountPriceFromServer); throw new TotalPriceMismatchException("Total Price Mismatch!"); } diff --git a/src/main/java/com/delfood/service/CouponIssueService.java b/src/main/java/com/delfood/service/CouponIssueService.java index 5819ee5..5dfa4c9 100644 --- a/src/main/java/com/delfood/service/CouponIssueService.java +++ b/src/main/java/com/delfood/service/CouponIssueService.java @@ -1,6 +1,7 @@ package com.delfood.service; import java.util.List; +import java.util.Objects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -124,8 +125,20 @@ public long discountPrice(long couponIssueId, long price) { return discountPrice > price ? price : discountPrice; } + /** + * 해당 발행 쿠폰이 사용상태인지 확인한다. 사용한 쿠폰이라면 true를 반환한다. + * @author jun + * @param couponIssueId 발행 쿠폰 아이디 + * @return + */ public boolean isUsed(long couponIssueId) { CouponIssueDTO couponIssueInfo = couponIssueMapper.findById(couponIssueId); + + if (Objects.isNull(couponIssueInfo)) { + log.error("발행쿠폰 사용여부 체크 오류! 조회한 발행 쿠폰 정보가 없습니다. 발행 쿠폰 아이디 : {}", couponIssueId); + throw new IllegalArgumentException("잘못된 쿠폰 발행 번호입니다."); + } + return couponIssueInfo.getStatus().equals(CouponIssueDTO.Status.USED); } From 0f235c5a43e83b20cb55d3db136cf904662c5cfa Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Fri, 10 Jan 2020 13:48:30 +0900 Subject: [PATCH 19/24] =?UTF-8?q?sql=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20coupon=EC=9D=B4=20'cooupon'=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=98=A4=ED=83=80=EA=B0=80=20=EB=82=AC=EB=8D=98=20=EA=B2=83?= =?UTF-8?q?=EC=9D=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주문성공시 상태 변경 - 결제 완료시 order_status가 'BEFOR_PAYMENT'에서 'ORDER_REQUEST'로 변경되도록 설정함 주문이 중간에 실패하더라도 기록이 남도록 변경 - 트랜잭션 경계 설정을 통해 실패시 기록을 남기도록 설정 --- src/main/java/com/delfood/dto/CouponDTO.java | 1 - .../java/com/delfood/dto/SimpleCouponInfo.java | 2 +- .../java/com/delfood/mapper/OrderMapper.java | 2 ++ .../java/com/delfood/service/OrderService.java | 18 +++++++++++++++++- src/main/resources/mybatis/mapper/orders.xml | 14 +++++++++++--- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/delfood/dto/CouponDTO.java b/src/main/java/com/delfood/dto/CouponDTO.java index 6af6ae2..b332dc3 100644 --- a/src/main/java/com/delfood/dto/CouponDTO.java +++ b/src/main/java/com/delfood/dto/CouponDTO.java @@ -16,7 +16,6 @@ @Alias("coupon") public class CouponDTO { - @JsonFormat(shape = Shape.OBJECT) public enum DiscountType { WON, PERCENT } diff --git a/src/main/java/com/delfood/dto/SimpleCouponInfo.java b/src/main/java/com/delfood/dto/SimpleCouponInfo.java index 4bb0fd0..7eb49b8 100644 --- a/src/main/java/com/delfood/dto/SimpleCouponInfo.java +++ b/src/main/java/com/delfood/dto/SimpleCouponInfo.java @@ -9,7 +9,7 @@ public class SimpleCouponInfo { private Long couponIssueId; private Long couponId; private String couponName; - private Long discountType; + private CouponDTO.DiscountType discountType; private Long discountValue; private Long discountPrice; } diff --git a/src/main/java/com/delfood/mapper/OrderMapper.java b/src/main/java/com/delfood/mapper/OrderMapper.java index 578d4a8..f7a7741 100644 --- a/src/main/java/com/delfood/mapper/OrderMapper.java +++ b/src/main/java/com/delfood/mapper/OrderMapper.java @@ -1,6 +1,7 @@ package com.delfood.mapper; import com.delfood.dto.OrderDTO; +import com.delfood.dto.OrderDTO.OrderStatus; import com.delfood.dto.OrderItemDTO; import com.delfood.dto.OrderItemOptionDTO; import com.delfood.dto.ItemsBillDTO.MenuInfo; @@ -30,4 +31,5 @@ public interface OrderMapper { boolean isShopItem(List items, Long shopId); + void updateStatus(@NonNull Long orderId, OrderStatus status); } diff --git a/src/main/java/com/delfood/service/OrderService.java b/src/main/java/com/delfood/service/OrderService.java index 2e4c4cb..726c12b 100644 --- a/src/main/java/com/delfood/service/OrderService.java +++ b/src/main/java/com/delfood/service/OrderService.java @@ -17,9 +17,11 @@ import com.google.firebase.database.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import lombok.NonNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service @@ -78,6 +80,9 @@ public OrderResponse order(String memberId, List items, long shopI PaymentDTO payResult = mockPayService.pay(paymentInfo); paymentService.insertPayment(payResult); + // 결제 완료 처리 + updateStatus(orderId, OrderDTO.OrderStatus.ORDER_REQUEST); + // 쿠폰 사용처리 if (bill.getCouponInfo() != null) { @@ -95,12 +100,13 @@ public OrderResponse order(String memberId, List items, long shopI /** * 주문 테이블에 insert를 진행한다. * 주문 메뉴, 주문 옵션이 추가된다. + * 주문도중 에러가 나더라도 주문기록을 남기기 위해 독자적인 트랜잭션을 가진다. * * @param memberId 고객 아이디 * @param items 주문할 아이템들 * @return */ - @Transactional + @Transactional(propagation = Propagation.NESTED) private Long preOrder(String memberId, List items, Long shopId) { MemberDTO memberInfo = memberService.getMemberInfo(memberId); OrderDTO order = OrderDTO @@ -240,5 +246,15 @@ public OrderDTO getOrder(Long orderId) { public boolean isShopItems(List items, Long shopId) { return orderMapper.isShopItem(items, shopId); } + + /** + * 주문 상태를 변경시킨다. + * @author jun + * @param orderId 주문 아이디 + * @param status 변경시킬 주문 상태 + */ + public void updateStatus(@NonNull Long orderId, OrderDTO.OrderStatus status) { + orderMapper.updateStatus(orderId, status); + } } diff --git a/src/main/resources/mybatis/mapper/orders.xml b/src/main/resources/mybatis/mapper/orders.xml index bf0423c..5cc4413 100644 --- a/src/main/resources/mybatis/mapper/orders.xml +++ b/src/main/resources/mybatis/mapper/orders.xml @@ -115,7 +115,7 @@ opt.price optionPrice, cpn_isu.id couponIssueId, cpn.id couponId, - cpn.name coouponName, + cpn.name couponName, cpn.discount_type discountType, cpn.discount_value discountValue, pay.amount_discount discountPrice @@ -154,7 +154,7 @@ opt.price optionPrice, cpn_isu.id couponIssueId, cpn.id couponId, - cpn.name coouponName, + cpn.name couponName, cpn.discount_type discountType, cpn.discount_value discountValue, pay.amount_discount discountPrice @@ -234,7 +234,7 @@ odr.order_status orderStatus, cpn_isu.id couponIssueId, cpn.id couponId, - cpn.name coouponName, + cpn.name couponName, cpn.discount_type discountType, cpn.discount_value discountValue, pay.amount_discount discountPrice @@ -301,4 +301,12 @@ #{option.optionId} ) + + + + UPDATE ORDERS + SET order_status = #{status} + WHERE id = #{orderId} + + From 62928e6bc859e1fc5078cf17054e4073a4afa3e5 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Fri, 10 Jan 2020 20:59:25 +0900 Subject: [PATCH 20/24] =?UTF-8?q?=EC=A3=BC=EB=AC=B8=20=EC=8A=B9=EC=9D=B8?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20-=20=EC=A3=BC?= =?UTF-8?q?=EB=AC=B8=20=EC=8A=B9=EC=9D=B8=20=EC=8B=9C=20=EB=8F=84=EC=B0=A9?= =?UTF-8?q?=EC=98=88=EC=A0=95=EC=8B=9C=EA=B0=84=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=20-=20=EC=A3=BC=EB=AC=B8=20=EC=8A=B9?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=20=EA=B3=A0=EA=B0=9D=EC=97=90=EA=B2=8C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delfood/controller/OrderController.java | 54 +++++++++++++++- .../java/com/delfood/mapper/OrderMapper.java | 9 +++ .../com/delfood/service/OrderService.java | 36 +++++++++++ src/main/resources/mybatis/mapper/orders.xml | 63 ++++++++++++++++++- 4 files changed, 158 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/delfood/controller/OrderController.java b/src/main/java/com/delfood/controller/OrderController.java index a3a1fd6..6dd8920 100644 --- a/src/main/java/com/delfood/controller/OrderController.java +++ b/src/main/java/com/delfood/controller/OrderController.java @@ -1,12 +1,11 @@ package com.delfood.controller; import com.delfood.aop.MemberLoginCheck; +import com.delfood.aop.OwnerLoginCheck; import com.delfood.controller.response.OrderResponse; import com.delfood.dto.ItemsBillDTO; import com.delfood.dto.OrderDTO; import com.delfood.dto.OrderItemDTO; -import com.delfood.dto.ShopDTO; -import com.delfood.dto.push.PushMessage; import com.delfood.error.exception.coupon.IssuedCouponExistException; import com.delfood.error.exception.order.TotalPriceMismatchException; import com.delfood.dto.OrderBillDTO; @@ -15,9 +14,9 @@ import com.delfood.service.PushService; import com.delfood.service.ShopService; import com.delfood.utils.SessionUtil; +import java.time.LocalDateTime; import java.util.List; import javax.servlet.http.HttpSession; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NonNull; @@ -25,6 +24,7 @@ import org.codehaus.commons.nullanalysis.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -175,6 +175,48 @@ public OrderDTO getOrder(HttpSession session, @PathVariable Long orderId) { return orderInfo; } + + + // 여기서 부터는 사장님 관련 컨트롤러입니다. + + /** + * 사장님이 소유한 가게에 요청된 주문들을 조회한다. + * 유효한 주문만 조회된다. + * @author jun + * @return + */ + @GetMapping("owner") + @OwnerLoginCheck + public List getRequestedOrders(HttpSession session) { + String ownerId = SessionUtil.getLoginOwnerId(session); + List shopOrders = orderService.getOwnerOrderRequest(ownerId); + return shopOrders; + } + + /** + * 주문을 승인한다. + * @author jun + * @param orderId 주문 아이디 + * @param request 주문 승낙시 입력해야하는 정보 + * @param session 사장님 세션 + */ + @PatchMapping("{orderId}/approve") + @OwnerLoginCheck + public void orderApprove(@PathVariable(name = "orderId") Long orderId, + @RequestBody OrderApproveRequest request, + HttpSession session) { + String ownerId = SessionUtil.getLoginOwnerId(session); + + // 해당 주문에 대한 권한이 있는지 확인한다 + if (orderService.isOwnerOrder(ownerId, orderId) == false) { + throw new IllegalArgumentException("해당 주문에 대한 권한이 없습니다."); + } + + // 주문 승인을 진행한다 + orderService.orderApprove(orderId, request.getMinute()); + } + + // request @Getter private static class OrderRequest { @@ -194,6 +236,12 @@ private static class BillRequest { private Long couponIssueId; } + @Getter + private static class OrderApproveRequest { + @NonNull + private Long minute; // 몇분이나 걸릴지 입력한 값 + + } // response diff --git a/src/main/java/com/delfood/mapper/OrderMapper.java b/src/main/java/com/delfood/mapper/OrderMapper.java index f7a7741..61721a9 100644 --- a/src/main/java/com/delfood/mapper/OrderMapper.java +++ b/src/main/java/com/delfood/mapper/OrderMapper.java @@ -6,6 +6,7 @@ import com.delfood.dto.OrderItemOptionDTO; import com.delfood.dto.ItemsBillDTO.MenuInfo; import com.delfood.dto.OrderBillDTO; +import java.time.LocalDateTime; import java.util.List; import lombok.NonNull; @@ -32,4 +33,12 @@ public interface OrderMapper { boolean isShopItem(List items, Long shopId); void updateStatus(@NonNull Long orderId, OrderStatus status); + + List findRequestByOwnerId(String shopId); + + String findOwnerIdByOrderId(Long orderId); + + void updateOrderStatusAndExArrivalTime(Long orderId, LocalDateTime exArrivalTime); + + String findMemberIdByOrderId(Long orderId); } diff --git a/src/main/java/com/delfood/service/OrderService.java b/src/main/java/com/delfood/service/OrderService.java index 726c12b..3ecfceb 100644 --- a/src/main/java/com/delfood/service/OrderService.java +++ b/src/main/java/com/delfood/service/OrderService.java @@ -15,8 +15,10 @@ import com.delfood.mapper.OrderMapper; import com.delfood.utils.OrderUtil; import com.google.firebase.database.annotations.Nullable; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import lombok.NonNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -257,4 +259,38 @@ public void updateStatus(@NonNull Long orderId, OrderDTO.OrderStatus status) { orderMapper.updateStatus(orderId, status); } + /** + * 사장님 아이디를 기반으로 주문 정보를 조회한다. + * @param ownerId 사장님 아이디 + * @return + */ + public List getOwnerOrderRequest(String ownerId) { + return orderMapper.findRequestByOwnerId(ownerId); + } + + public boolean isOwnerOrder(String ownerId, Long orderId) { + String ownerIdByOrderId = orderMapper.findOwnerIdByOrderId(orderId); + return Objects.equals(ownerId, ownerIdByOrderId); + } + + /** + * 해당 주문을 승인하고 도착 예정시간을 설정한다. + * 승인 완료 후 고객에게 푸시 메세지를 전송한다. + * @author jun + * @param orderId 주문 아이디 + * @param minute 배달까지 몇 분 걸릴지 예상시간 + */ + @Transactional + public void orderApprove(Long orderId, long minute) { + + LocalDateTime exArrivalTime = LocalDateTime.now().plusMinutes(minute); + orderMapper.updateOrderStatusAndExArrivalTime(orderId, exArrivalTime); + String memberId = orderMapper.findMemberIdByOrderId(orderId); + + // 푸시메세지 전송 + PushMessage messageInfo = new PushMessage("DelFood 주문 승인", + "사장님이 주문을 승인했어요! 도착 예정 시간 " + minute + "분 후"); + pushService.sendMessageToMember(messageInfo, memberId); + } + } diff --git a/src/main/resources/mybatis/mapper/orders.xml b/src/main/resources/mybatis/mapper/orders.xml index 5cc4413..216b253 100644 --- a/src/main/resources/mybatis/mapper/orders.xml +++ b/src/main/resources/mybatis/mapper/orders.xml @@ -213,7 +213,7 @@ + SELECT odr.id orderId, + odr.start_time orderStartTime, + odr.delivery_cost deliveryCost, + odr.address_code addressCode, + m.id menuId, + m.name menuName, + m.price menuPrice, + m_opt.id optionId, + m_opt.name optionName, + m_opt.price optionPrice, + addr.building_management_number buildingManagementNumber, + addr.city_name cityName, + addr.city_country_name cityCountryName, + addr.town_name townName, + addr.road_name roadName, + addr.building_number buildingNumber, + addr.building_side_number buildingSideNumber, + odr.address_detail addressDetail, + odr.member_id memberId, + odr.order_status orderStatus, + cpn_isu.id couponIssueId, + cpn.id couponId, + cpn.name couponName, + cpn.discount_type discountType, + cpn.discount_value discountValue, + pay.amount_discount discountPrice + FROM SHOP shop + INNER JOIN ORDERS odr ON (shop.id = odr.shop_id) + LEFT OUTER JOIN ORDERS_ITEM item ON (odr.id = item.order_id) + JOIN ORDERS_ITEM_OPTION item_opt ON (item.id = item_opt.order_item_id) + JOIN MENU m ON (m.id = item.menu_id) + JOIN OPTION m_opt ON (item_opt.option_id = m_opt.id) + JOIN ADDRESS addr ON (odr.address_code = addr.building_management_number) + INNER JOIN PAYMENT pay ON (pay.order_id = odr.id) + LEFT OUTER JOIN COUPON_ISSUE cpn_isu ON (pay.id = cpn_isu.payment_id) + LEFT OUTER JOIN COUPON cpn ON (cpn_isu.coupon_id = cpn.id) + WHERE shop.owner_id = #{ownerId} + AND odr.order_status = 'ORDER_REQUEST' + + + + + + UPDATE ORDERS + SET order_status = 'ORDER_APPROVAL', + ex_arrival_time = #{exArrivalTime} + WHERE id = #{orderId} + + + + From b8e0e7d46b81850ff3a851399e6493be307119d6 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Sun, 12 Jan 2020 10:51:39 +0900 Subject: [PATCH 21/24] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20-=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/delfood/service/OrderService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/delfood/service/OrderService.java b/src/main/java/com/delfood/service/OrderService.java index 3ecfceb..9d8b9f0 100644 --- a/src/main/java/com/delfood/service/OrderService.java +++ b/src/main/java/com/delfood/service/OrderService.java @@ -65,7 +65,7 @@ public OrderResponse order(String memberId, List items, long shopI @Nullable Long couponIssueId) { // 주문 준비 작업. 결제 전. - Long orderId = preOrder(memberId, items, shopId); + Long orderId = doOrder(memberId, items, shopId); // 계산서 발행 ItemsBillDTO bill = getBill(memberId, items, couponIssueId); @@ -109,7 +109,7 @@ public OrderResponse order(String memberId, List items, long shopI * @return */ @Transactional(propagation = Propagation.NESTED) - private Long preOrder(String memberId, List items, Long shopId) { + private Long doOrder(String memberId, List items, Long shopId) { MemberDTO memberInfo = memberService.getMemberInfo(memberId); OrderDTO order = OrderDTO .builder() From 317ca343e61d8e102f3129b599746d20f1d23112 Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Sun, 12 Jan 2020 13:58:07 +0900 Subject: [PATCH 22/24] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=8D=94=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?-=20=ED=9A=8C=EC=9B=90=20=EA=B0=80=EC=9E=85=20-=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20-=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83?= =?UTF-8?q?=20-=20=EC=95=84=EC=9D=B4=EB=94=94=EC=99=80=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=EB=A1=9C=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=20-=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B3=80=EA=B2=BD=20-=20=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/delfood/aop/AuthCheckAspect.java | 21 +++ .../java/com/delfood/aop/RiderLoginCheck.java | 9 + .../delfood/controller/RiderController.java | 162 ++++++++++++++++++ .../java/com/delfood/dto/rider/RiderDTO.java | 106 ++++++++++++ .../com/delfood/error/ErrorController.java | 7 + .../error/exception/IdDeletedException.java | 7 + .../com/delfood/mapper/RiderInfoMapper.java | 24 +++ .../service/rider/RiderInfoService.java | 147 ++++++++++++++++ .../java/com/delfood/utils/SessionUtil.java | 20 +++ src/main/resources/mybatis/mapper/rider.xml | 50 ++++++ 10 files changed, 553 insertions(+) create mode 100644 src/main/java/com/delfood/aop/RiderLoginCheck.java create mode 100644 src/main/java/com/delfood/controller/RiderController.java create mode 100644 src/main/java/com/delfood/dto/rider/RiderDTO.java create mode 100644 src/main/java/com/delfood/error/exception/IdDeletedException.java create mode 100644 src/main/java/com/delfood/mapper/RiderInfoMapper.java create mode 100644 src/main/java/com/delfood/service/rider/RiderInfoService.java create mode 100644 src/main/resources/mybatis/mapper/rider.xml diff --git a/src/main/java/com/delfood/aop/AuthCheckAspect.java b/src/main/java/com/delfood/aop/AuthCheckAspect.java index 4bd00b7..990280f 100644 --- a/src/main/java/com/delfood/aop/AuthCheckAspect.java +++ b/src/main/java/com/delfood/aop/AuthCheckAspect.java @@ -1,5 +1,6 @@ package com.delfood.aop; +import java.util.Objects; import javax.servlet.http.HttpSession; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; @@ -93,4 +94,24 @@ public void memberLoginCheck(JoinPoint jp) throws Throwable { throw new HttpStatusCodeException(HttpStatus.UNAUTHORIZED, "NO_LOGIN") {}; } } + + /** + * 라이더 로그인을 체크한다. + * @author jun + * @param jp 조인포인트 + * @throws Throwable 발생 가능한 예외 설정 + */ + @Before("@annotation(com.delfood.aop.RiderLoginCheck)") + public void riderLoginCheck(JoinPoint jp) throws Throwable { + log.debug("AOP - Rider Login Check Started"); + + HttpSession session = + ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest() + .getSession(); + String riderId = SessionUtil.getLoginRiderId(session); + + if (Objects.isNull(riderId)) { + throw new HttpStatusCodeException(HttpStatus.UNAUTHORIZED, "RIDER_NO_LOGIN") {}; + } + } } diff --git a/src/main/java/com/delfood/aop/RiderLoginCheck.java b/src/main/java/com/delfood/aop/RiderLoginCheck.java new file mode 100644 index 0000000..a7e0fb4 --- /dev/null +++ b/src/main/java/com/delfood/aop/RiderLoginCheck.java @@ -0,0 +1,9 @@ +package com.delfood.aop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +public @interface RiderLoginCheck { + +} diff --git a/src/main/java/com/delfood/controller/RiderController.java b/src/main/java/com/delfood/controller/RiderController.java new file mode 100644 index 0000000..cdc5c9b --- /dev/null +++ b/src/main/java/com/delfood/controller/RiderController.java @@ -0,0 +1,162 @@ +package com.delfood.controller; + +import com.delfood.aop.RiderLoginCheck; +import com.delfood.dto.rider.RiderDTO; +import com.delfood.service.rider.RiderInfoService; +import com.delfood.utils.SessionUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Objects; +import javax.servlet.http.HttpSession; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +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.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@Log4j2 +@RestController +@RequestMapping("/riders/") +public class RiderController { + + @Autowired + private RiderInfoService riderInfoService; + + @Autowired + private ObjectMapper objectMapper; + + /** + * 아이디 중복 체크. + * @author jun + * @param riderId 중복체크할 아이디 + * @return 중복된 아이디라면 true + */ + @GetMapping("duplicated/id/{riderId}") + public boolean isDuplicatedId(@PathVariable(name = "riderId") String riderId) { + return riderInfoService.isDuplicatedId(riderId); + } + + /** + * 라이더 회원가입. + * @author jun + * @param riderInfo 회원가입할 아이디 정보 + * @throws JsonProcessingException 로그를 기록할 때 직렬화중 생길 수 있는 예외 + */ + @PostMapping("signUp") + @ResponseStatus(code = HttpStatus.CREATED) + public void signUp(@RequestBody RiderDTO riderInfo) throws JsonProcessingException { + if (riderInfo.hasNullData()) { + log.info("회원가입 필수 데이터 누락. 요청 정보 : {}", objectMapper.writeValueAsString(riderInfo)); + throw new NullPointerException("라이더 회원가입에 필수 데이터가 누락되었습니다."); + } + + RiderDTO encryptRiderInfo = RiderDTO.encryptDTO(riderInfo); + riderInfoService.signUp(encryptRiderInfo); + } + + /** + * 라이더 로그인을 진행한다. + * + * @author jun + * @param request id, password 정보 + * @param session 현재 세션 + * @return + */ + @PostMapping("login") + public RiderDTO signIn(@RequestBody SignInRequest request, HttpSession session) { + if (Objects.isNull(SessionUtil.getLoginRiderId(session)) == false) { + logout(session); + } + + RiderDTO riderInfo = riderInfoService.signIn(request.getId(), request.getPassword()); + SessionUtil.setLoginRiderId(session, riderInfo.getId()); + return riderInfo; + } + + + /** + * 라이더 로그아웃을 진행한다. + * @author jun + * @param session 사용자의 세션 + */ + @GetMapping("logout") + public void logout(HttpSession session) { + SessionUtil.logoutRider(session); + } + + /** + * 라이더의 비밀번호를 변경한다. + * @param session 사용자의 세션 + * @param request 변경전 비밀번호, 변경할 비밀번호 정보 + */ + @PatchMapping("update/password") + @RiderLoginCheck + public void updatePassword(HttpSession session, @RequestBody UpdatePasswordRequest request) { + String id = SessionUtil.getLoginRiderId(session); + riderInfoService.changePassword(id, request.getPasswordBeforechange(), + request.getPasswordAfterChange()); + } + + /** + * 라이더의 계정을 삭제한다. + * 삭제가 완료된다면 로그아웃된다. + * @param session 현제 사용자의 세션 + * @param password 유효성 검사를 위한 계정 비밀번호 + */ + @DeleteMapping + @RiderLoginCheck + public void deleteRiderAccount(HttpSession session, String password) { + String id = SessionUtil.getLoginRiderId(session); + riderInfoService.deleteAccount(id, password); + SessionUtil.logoutRider(session); + } + + @PatchMapping("update/mail") + public void updateMail(HttpSession session, @RequestBody UpdateMailRequest request) { + String id = SessionUtil.getLoginRiderId(session); + riderInfoService.changeMail(id, request.getPassword(), request.getUpdateMail()); + } + + + + // Request + @Getter + private static class SignInRequest { + @NonNull + private String id; + + @NonNull + private String password; + } + + @Getter + private static class UpdatePasswordRequest { + @NonNull + private String passwordBeforechange; + + @NonNull + private String passwordAfterChange; + } + + @Getter + private static class UpdateMailRequest { + @NonNull + private String password; + + @NonNull + private String updateMail; + } + + + +} diff --git a/src/main/java/com/delfood/dto/rider/RiderDTO.java b/src/main/java/com/delfood/dto/rider/RiderDTO.java new file mode 100644 index 0000000..3899931 --- /dev/null +++ b/src/main/java/com/delfood/dto/rider/RiderDTO.java @@ -0,0 +1,106 @@ +package com.delfood.dto.rider; + +import java.time.LocalDateTime; +import java.util.Objects; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import org.codehaus.commons.nullanalysis.Nullable; +import org.codehaus.jackson.annotate.JsonIgnore; +import com.delfood.utils.SHA256Util; + +@Getter +@NoArgsConstructor +public class RiderDTO { + @NonNull + private String id; + + @NonNull + private String password; + + @NonNull + private String name; + + @NonNull + private String tel; + + @NonNull + private String mail; + + @Nullable + private Status status = Status.DEFAULT; + + @Nullable + private LocalDateTime createdAt; + + @Nullable + private LocalDateTime updatedAt; + + + public enum Status { + DEFAULT, DELETED + } + + /** + * RiderDTO Class Builder. + * @param id 아이디 + * @param password 비밀번호 + * @param name 이름 + * @param tel 휴대전화 번호 + * @param mail 메일 + * @param status 계정 상태 + * @param createdAt 회원가입일 + * @param updatedAt 회원정보 수정일 + */ + @Builder + public RiderDTO(String id, String password, String name, String tel, String mail, Status status, + LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.password = password; + this.name = name; + this.tel = tel; + this.mail = mail; + this.status = status == null ? Status.DEFAULT : status; + this.createdAt = createdAt == null ? LocalDateTime.now() : createdAt; + this.updatedAt = updatedAt == null ? LocalDateTime.now() : updatedAt; + } + + /** + * null이 허용되지 않는 필드에 null값이 있는지 확인한다. + * @author jun + * @return + */ + public boolean hasNullData() { + return Objects.isNull(this.id) + || Objects.isNull(this.password) + || Objects.isNull(this.name) + || Objects.isNull(this.tel) + || Objects.isNull(this.mail); + } + + /** + * 객체를 복사하여 패스워드를 암호화한 객체를 생성하여 리턴한다. + * @author jun + * @param riderInfo 암호화할 회원 정보 + * @return + */ + public static RiderDTO encryptDTO(RiderDTO riderInfo) { + String encryptPassword = SHA256Util.encryptSHA256(riderInfo.getPassword()); + return RiderDTO.builder() + .id(riderInfo.getId()) + .password(encryptPassword) + .name(riderInfo.getName()) + .tel(riderInfo.getTel()) + .mail(riderInfo.getMail()) + .status(riderInfo.getStatus()) + .createdAt(riderInfo.getCreatedAt()) + .updatedAt(riderInfo.getUpdatedAt()) + .build(); + } + + @JsonIgnore + public String getPassword() { + return this.password; + } +} diff --git a/src/main/java/com/delfood/error/ErrorController.java b/src/main/java/com/delfood/error/ErrorController.java index a377970..2429380 100644 --- a/src/main/java/com/delfood/error/ErrorController.java +++ b/src/main/java/com/delfood/error/ErrorController.java @@ -1,6 +1,7 @@ package com.delfood.error; import com.delfood.error.exception.DuplicateIdException; +import com.delfood.error.exception.IdDeletedException; import com.delfood.error.exception.cart.DuplicateItemException; import com.delfood.error.exception.coupon.IssuedCouponExistException; import com.delfood.error.exception.menuGroup.InvalidMenuGroupCountException; @@ -93,4 +94,10 @@ public ErrorMsg handleDuplicatedItemException(DuplicateItemException e) { public ErrorMsg handleMockPayException(MockPayException e) { return new ErrorMsg(e.getLocalizedMessage(), getSimpleName(e)); } + + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ExceptionHandler(IdDeletedException.class) + public ErrorMsg handleIdDeletedException(IdDeletedException e) { + return new ErrorMsg(e.getLocalizedMessage(), getSimpleName(e)); + } } diff --git a/src/main/java/com/delfood/error/exception/IdDeletedException.java b/src/main/java/com/delfood/error/exception/IdDeletedException.java new file mode 100644 index 0000000..a34248b --- /dev/null +++ b/src/main/java/com/delfood/error/exception/IdDeletedException.java @@ -0,0 +1,7 @@ +package com.delfood.error.exception; + +public class IdDeletedException extends IllegalArgumentException { + public IdDeletedException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/delfood/mapper/RiderInfoMapper.java b/src/main/java/com/delfood/mapper/RiderInfoMapper.java new file mode 100644 index 0000000..8df7aea --- /dev/null +++ b/src/main/java/com/delfood/mapper/RiderInfoMapper.java @@ -0,0 +1,24 @@ +package com.delfood.mapper; + +import com.delfood.dto.rider.RiderDTO; +import lombok.NonNull; +import org.springframework.stereotype.Repository; + +@Repository +public interface RiderInfoMapper { + + public boolean isExistById(@NonNull String id); + + public void insertRider(@NonNull RiderDTO riderInfo); + + public RiderDTO findByIdAndPassword(@NonNull String id, @NonNull String password); + + public long updatePassword(@NonNull String id, @NonNull String password); + + public long updateStatusAsDeleted(@NonNull String id); + + public boolean isExistAndEffectiveByIdAndPassword(@NonNull String id, + @NonNull String password); + + public long updateMail(@NonNull String id, @NonNull String mail); +} diff --git a/src/main/java/com/delfood/service/rider/RiderInfoService.java b/src/main/java/com/delfood/service/rider/RiderInfoService.java new file mode 100644 index 0000000..7478520 --- /dev/null +++ b/src/main/java/com/delfood/service/rider/RiderInfoService.java @@ -0,0 +1,147 @@ +package com.delfood.service.rider; + +import com.delfood.dto.rider.RiderDTO; +import com.delfood.error.exception.DuplicateException; +import com.delfood.error.exception.IdDeletedException; +import com.delfood.mapper.RiderInfoMapper; +import com.delfood.utils.SHA256Util; +import java.util.Objects; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Log4j2 +public class RiderInfoService { + + @Autowired + private RiderInfoMapper riderInfoMapper; + + private static final IllegalArgumentException passwordMismatchException = + new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + + /** + * 해당 아이디가 중복된 아이디인지 확인한다. + * + * @author jun + * @param riderId 중복인지 검사할 아이디 + * @return + */ + public boolean isDuplicatedId(@NonNull String riderId) { + return riderInfoMapper.isExistById(riderId); + } + + /** + * 라이더 회원가입을 진행한다. + * @param riderInfo 회원 가입 정보 + */ + @Transactional + public void signUp(@NonNull RiderDTO riderInfo) { + if (isDuplicatedId(riderInfo.getId())) { + throw new DuplicateException("아이디 \"" + riderInfo.getId() + "\" 는 이미 가입한 아이디입니다."); + } + + riderInfoMapper.insertRider(riderInfo); + } + + /** + * 라이더 로그인을 진행한다. + * @param id 아이디 + * @param password 암호화 전 비밀번호 + * @return + */ + public RiderDTO signIn(@NonNull String id, @NonNull String password) { + String encryptedPassword = SHA256Util.encryptSHA256(password); + RiderDTO riderInfo = getRiderInfo(id, encryptedPassword); + + if (RiderDTO.Status.DELETED.equals(riderInfo.getStatus())) { + log.info("signIn - 삭제 회원 로그인 시도. id : {}, password : {}", id, encryptedPassword); + throw new IdDeletedException("Rider의 계정이 삭제 상태입니다. 로그인할 수 없습니다."); + } + + + + return riderInfo; + } + + /** + * 라이더의 비밀번호를 변경한다. + * @param id 라이더 아이디 + * @param passwordBeforeChange 변경 전 비밀번호 + * @param passwordAfterChange 변경할 비밀번호 + */ + @Transactional + public void changePassword(@NonNull String id, @NonNull String passwordBeforeChange, + String passwordAfterChange) { + if (isEffective(id, passwordBeforeChange) == false) { + throw passwordMismatchException; + } + + String encryptedPasswordAfter = SHA256Util.encryptSHA256(passwordAfterChange); + riderInfoMapper.updatePassword(id, encryptedPasswordAfter); + } + + /** + * 라이더 계정 정보를 조회한다. + * 일치하는 계정이 없을 시 예외를 발생시킨다. + * @author jun + * @param id 조회할 라이더 계정 아이디 + * @param encryptedPassword 암호화를 진행한 비밀번호 + * @return + */ + public RiderDTO getRiderInfo(@NonNull String id, @NonNull String encryptedPassword) { + RiderDTO riderInfo = riderInfoMapper.findByIdAndPassword(id, encryptedPassword); + + if (Objects.isNull(riderInfo)) { + log.info("회원 정보 없음. id : {}, password : {}", id, encryptedPassword); + throw new IllegalArgumentException("id 또는 password가 일치하는 회원 정보가 없습니다."); + } + + return riderInfo; + } + + /** + * 라이더 계정을 삭제상태로 만든다. + * @param id 삭제할 라이더 아이디 + * @param password 삭제하기 전 유효성 검사를 위한 비밀번호 + */ + @Transactional + public void deleteAccount(@NonNull String id, @NonNull String password) { + if (isEffective(id, password) == false) { + log.info("회원 삭제를 시도하였지만 실패하였습니다. 원인 : 비밀번호 불일치. id : {}", id); + throw passwordMismatchException; + } + + riderInfoMapper.updateStatusAsDeleted(id); + } + + /** + * 아이디와 비밀번호를 기반으로 유효한 아이디인지, 아이디와 비밀번호가 일치하는지 검사한다. + * @author jun + * @param id 검사할 아이디 + * @param password 검사할 비밀번ㄹ호 + * @return + */ + public boolean isEffective(@NonNull String id, @NonNull String password) { + String encryptedPassword = SHA256Util.encryptSHA256(password); + return riderInfoMapper.isExistAndEffectiveByIdAndPassword(id, encryptedPassword); + } + + /** + * 라이더의 메일 주소를 변경한다. + * @author jun + * @param id 메일을 변경할 아이디 + * @param password 유효성 검사를 위한 비밀번호 + * @param mail 변경할 메일 주소 + */ + @Transactional + public void changeMail(@NonNull String id, @NonNull String password, @NonNull String mail) { + if (isEffective(id, password) == false) { + throw passwordMismatchException; + } + + riderInfoMapper.updateMail(id, mail); + } +} diff --git a/src/main/java/com/delfood/utils/SessionUtil.java b/src/main/java/com/delfood/utils/SessionUtil.java index a689943..02e57a4 100644 --- a/src/main/java/com/delfood/utils/SessionUtil.java +++ b/src/main/java/com/delfood/utils/SessionUtil.java @@ -1,11 +1,13 @@ package com.delfood.utils; import javax.servlet.http.HttpSession; +import lombok.NonNull; public class SessionUtil { private static final String LOGIN_MEMBER_ID = "LOGIN_MEMBER_ID"; private static final String LOGIN_OWNER_ID = "LOGIN_OWNER_ID"; + private static final String LOGIN_RIDER_ID = "LOGIN_RIDER_ID"; // 인스턴스화 방지 private SessionUtil() {} @@ -81,6 +83,24 @@ public static void logoutMember(HttpSession session) { public static void logoutOwner(HttpSession session) { session.removeAttribute(LOGIN_OWNER_ID); } + + /** + * 로그인한 라이더의 id를 세션에 저장한다. + * @author jun + * @param session 사용자의 세션 + * @param id 저장할 라이더 아이디 + */ + public static void setLoginRiderId(HttpSession session, @NonNull String id) { + session.setAttribute(LOGIN_RIDER_ID, id); + } + + public static String getLoginRiderId(HttpSession session) { + return (String) session.getAttribute(LOGIN_RIDER_ID); + } + + public static void logoutRider(HttpSession session) { + session.removeAttribute(LOGIN_RIDER_ID); + } diff --git a/src/main/resources/mybatis/mapper/rider.xml b/src/main/resources/mybatis/mapper/rider.xml new file mode 100644 index 0000000..7b54cf9 --- /dev/null +++ b/src/main/resources/mybatis/mapper/rider.xml @@ -0,0 +1,50 @@ + + + + + + + INSERT INTO RIDER(id, password, name, tel, mail) + VALUES (#{id}, #{password}, #{name}, #{tel}, #{mail}) + + + + + + UPDATE RIDER + SET password = #{password}, + updated_at = NOW() + WHERE id = #{id} + + + + UPDATE RIDER + SET status = 'DELETED', + updated_at = NOW() + WHERE id = #{id} + + + + + + UPDATE RIDER + SET mail = #{mail}, + updated_at = NOW() + WHERE id = #{id} + + \ No newline at end of file From 0ef437ab1109555e6860f1cb913ee51041bb309d Mon Sep 17 00:00:00 2001 From: yyy9942 Date: Tue, 14 Jan 2020 12:38:05 +0900 Subject: [PATCH 23/24] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20-=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20-=20=EB=A6=AC=ED=94=8C=EB=9E=99=EC=85=98?= =?UTF-8?q?=EC=9D=84=20=ED=99=9C=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=82=B4=EB=B6=80=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/delfood/aop/AuthCheckAspect.java | 85 +++++++++++++++---- src/main/java/com/delfood/aop/LoginCheck.java | 28 ++++++ .../java/com/delfood/aop/OwnerShopCheck.java | 11 ++- .../delfood/controller/CartControllelr.java | 12 +-- .../controller/CouponIssueController.java | 6 +- .../controller/LocationController.java | 6 +- .../delfood/controller/MemberController.java | 14 +-- .../delfood/controller/OrderController.java | 18 ++-- .../delfood/controller/OwnerController.java | 12 +-- .../delfood/controller/RiderController.java | 10 ++- .../delfood/controller/ShopController.java | 18 ++-- .../controller/ShopSearchController.java | 4 +- 12 files changed, 165 insertions(+), 59 deletions(-) create mode 100644 src/main/java/com/delfood/aop/LoginCheck.java diff --git a/src/main/java/com/delfood/aop/AuthCheckAspect.java b/src/main/java/com/delfood/aop/AuthCheckAspect.java index 990280f..3784c68 100644 --- a/src/main/java/com/delfood/aop/AuthCheckAspect.java +++ b/src/main/java/com/delfood/aop/AuthCheckAspect.java @@ -1,10 +1,16 @@ package com.delfood.aop; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.Objects; import javax.servlet.http.HttpSession; +import org.apache.commons.codec.binary.StringUtils; import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.codehaus.commons.compiler.util.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -28,9 +34,8 @@ public class AuthCheckAspect { * 로그인되어있지 않을 시 해당 메서드 로직을 중지시킨 후 리턴한다. * @OwnerLoginCheck 해당 어노테이션이 적용된 메서드를 검사한다. * @author jun - * @param pjp - * @return 로그인시 SUCCESS, 비로그인시 NO_LOGIN - * @throws Throwable + * @param jp 조인포인트 + * @throws Throwable 발생 가능한 예외 */ @Before("@annotation(com.delfood.aop.OwnerLoginCheck)") public void ownerLoginCheck(JoinPoint jp) throws Throwable { @@ -50,25 +55,46 @@ public void ownerLoginCheck(JoinPoint jp) throws Throwable { * 세션에서 사장님 로그인을 체크 한다. * 그 후 입력받은 파라미터 값 중 매장 id를 검색하여 해당 매장이 접속한 사장님의 것인지 검사한다. * @author jun - * @param pjp - * @return 비로그인시 NO_LOGIN, 해당 매장의 사장이 아닐 시 UNAUTHORIZED, 권한이 있을 시 SUCCESS - * @throws Throwable + * @param jp 조인포인트 + * @throws Throwable 발새 가능한 예외 */ - @Before("@annotation(com.delfood.aop.OwnerShopCheck)") - public void ownerShopCheck(JoinPoint jp) throws Throwable { + @Before("@annotation(com.delfood.aop.OwnerShopCheck) && @annotation(ownerShopCheck)") + public void ownerShopCheck(JoinPoint jp, OwnerShopCheck ownerShopCheck) throws Throwable { log.debug("AOP - Owner Shop Check Started"); - HttpSession session = ((ServletRequestAttributes)(RequestContextHolder.currentRequestAttributes())).getRequest().getSession(); + HttpSession session = + ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest() + .getSession(); String ownerId = SessionUtil.getLoginOwnerId(session); - - if(ownerId == null) { + + if (ownerId == null) { log.debug("AOP - Owner Shop Check Result - NO_LOGIN"); throw new HttpStatusCodeException(HttpStatus.UNAUTHORIZED, "NO_LOGIN") {}; } Object[] args = jp.getArgs(); - Long shopId = (Long) args[0]; + + // 메소드 파라미터 추출 + MethodSignature signature = (MethodSignature) jp.getSignature(); + Method method = signature.getMethod(); + Parameter[] parameters = method.getParameters(); + + Long shopId = null; + + // 파라미터의 이름과 어노테이션의 value를 비교하여 검사 + for (int i = 0; i < parameters.length; i++) { + String parameterName = parameters[i].getName(); + if (StringUtils.equals(ownerShopCheck.value(), parameterName)) { + shopId = (Long) args[i]; + } + } + + // 어노테이션 value로 설정된 값과 같은 변수 이름이 없을 경우 예외처리 + if (Objects.isNull(shopId)) { + throw new IllegalArgumentException("OwnerShopCheck 어노테이션 설정이 잘못되었습니다. value와 변수 명을 일치시켜주세요."); + } + if (!shopService.isShopOwner(shopId, ownerId)) { log.debug("AOP - Owner Shop Check Result - UNAUTHORIZED"); @@ -79,9 +105,8 @@ public void ownerShopCheck(JoinPoint jp) throws Throwable { /** * 고객의 로그인을 체크한다. * @author jun - * @param pjp - * @return - * @throws Throwable + * @param jp 조인포인튼 + * @throws Throwable 발생 가능한 예외 */ @Before("@annotation(com.delfood.aop.MemberLoginCheck)") public void memberLoginCheck(JoinPoint jp) throws Throwable { @@ -114,4 +139,34 @@ public void riderLoginCheck(JoinPoint jp) throws Throwable { throw new HttpStatusCodeException(HttpStatus.UNAUTHORIZED, "RIDER_NO_LOGIN") {}; } } + + /** + * 공통 로그인 체크 AOP. + * 고객, 사장님, 라이더의 로그인 체크 기능을 하나로 모아두었다. + * @param jp 조인포인트 + * @throws Throwable 발생 가능한 예외 + */ + @Before("@annotation(com.delfood.aop.LoginCheck) && @ annotation(loginCheck)") + public void loginCheck(JoinPoint jp, LoginCheck loginCheck) throws Throwable { + log.debug("AOP - Login Check Started"); + + HttpSession session = + ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest() + .getSession(); + + if (LoginCheck.UserType.MEMBER.equals(loginCheck.type())) { + memberLoginCheck(jp); + } + + if (LoginCheck.UserType.OWNER.equals(loginCheck.type())) { + ownerLoginCheck(jp); + } + + if (LoginCheck.UserType.RIDER.equals(loginCheck.type())) { + riderLoginCheck(jp); + } + + + } + } diff --git a/src/main/java/com/delfood/aop/LoginCheck.java b/src/main/java/com/delfood/aop/LoginCheck.java new file mode 100644 index 0000000..3aa4d32 --- /dev/null +++ b/src/main/java/com/delfood/aop/LoginCheck.java @@ -0,0 +1,28 @@ +package com.delfood.aop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 로그인의 상태를 확인한다. + * 회원, 사장님, 라이더의 로그인 상태를 확인하여 로그인 되지 않았다면 예외를 발생시킨다. + * @author jun + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface LoginCheck { + + /** + * 로그인을 체크하고 싶은 유저의 로그인 타입. + * 회원(MEMBER), 사장님(OWNER), 라이더(RIDER)중 선택할 수 있다. + * @return + */ + UserType type(); + + public static enum UserType { + MEMBER, OWNER, RIDER + } +} diff --git a/src/main/java/com/delfood/aop/OwnerShopCheck.java b/src/main/java/com/delfood/aop/OwnerShopCheck.java index 0034c2a..1908239 100644 --- a/src/main/java/com/delfood/aop/OwnerShopCheck.java +++ b/src/main/java/com/delfood/aop/OwnerShopCheck.java @@ -1,15 +1,22 @@ package com.delfood.aop; import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 매장 id가 첫 번째 파라미터로 와야한다. + * 매장 id를 파라미터로 주어야 한다. * 접속한 사장님이 해당 매장의 주인인지 확인한다. * @author yyy99 * */ @Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) public @interface OwnerShopCheck { - + /** + * 해당 변수의 이름. + * @return + */ + String value(); } diff --git a/src/main/java/com/delfood/controller/CartControllelr.java b/src/main/java/com/delfood/controller/CartControllelr.java index c2010f2..69bdc96 100644 --- a/src/main/java/com/delfood/controller/CartControllelr.java +++ b/src/main/java/com/delfood/controller/CartControllelr.java @@ -1,5 +1,7 @@ package com.delfood.controller; +import com.delfood.aop.LoginCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.aop.MemberLoginCheck; import com.delfood.dto.ItemDTO; import com.delfood.service.CartService; @@ -23,31 +25,31 @@ public class CartControllelr { private CartService cartService; @PostMapping("/members/cart/menus") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void addMenu(@RequestBody ItemDTO item, HttpSession session) { cartService.addOrdersItem(item, SessionUtil.getLoginMemberId(session)); } @GetMapping("/members/cart/menus") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public List getCart(HttpSession session) { return cartService.getItems(SessionUtil.getLoginMemberId(session)); } @DeleteMapping("/members/cart/menus") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void clearCart(HttpSession session) { cartService.claer(SessionUtil.getLoginMemberId(session)); } @DeleteMapping("/members/cart/menus/{index}") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void deleteCartMenu(HttpSession session, @PathVariable long index) { cartService.deleteCartMenu(SessionUtil.getLoginMemberId(session), index); } @GetMapping("/members/cart/price") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public CartPriceResponse cartPrice(HttpSession session) { String memberId = SessionUtil.getLoginMemberId(session); return new CartPriceResponse(cartService.getItems(memberId), cartService.allPrice(memberId)); diff --git a/src/main/java/com/delfood/controller/CouponIssueController.java b/src/main/java/com/delfood/controller/CouponIssueController.java index a4cf6f9..d1dcccb 100644 --- a/src/main/java/com/delfood/controller/CouponIssueController.java +++ b/src/main/java/com/delfood/controller/CouponIssueController.java @@ -1,5 +1,7 @@ package com.delfood.controller; +import com.delfood.aop.LoginCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.aop.MemberLoginCheck; import com.delfood.dto.CouponIssueDTO; import com.delfood.service.CouponIssueService; @@ -31,7 +33,7 @@ public class CouponIssueController { */ @PostMapping @ResponseStatus(HttpStatus.CREATED) - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void addCouponIssue(HttpSession session, @RequestBody Long couponId) { couponIssueService.createCouponIssue(SessionUtil.getLoginMemberId(session), couponId); @@ -43,7 +45,7 @@ public void addCouponIssue(HttpSession session, @RequestBody Long couponId) { * @return */ @GetMapping - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public List getCouponIssues(HttpSession session) { return couponIssueService.getCouponIssues(SessionUtil.getLoginMemberId(session)); } diff --git a/src/main/java/com/delfood/controller/LocationController.java b/src/main/java/com/delfood/controller/LocationController.java index 23c0dcb..d9657bb 100644 --- a/src/main/java/com/delfood/controller/LocationController.java +++ b/src/main/java/com/delfood/controller/LocationController.java @@ -43,7 +43,7 @@ public class LocationController { * @return */ @PostMapping("deliveries/{shopId}/possibles") - @OwnerShopCheck + @OwnerShopCheck("shopId") @ResponseStatus(HttpStatus.CREATED) public void addDeliveryLocation( @PathVariable(name = "shopId") Long shopId, @@ -60,7 +60,7 @@ public void addDeliveryLocation( * @return */ @GetMapping("deliveries/{shopId}/possibles") - @OwnerShopCheck + @OwnerShopCheck("shopId") public List getDeliveryLocations( @PathVariable(name = "shopId") Long shopId) { return shopService.getDeliveryLocations(shopId); @@ -76,7 +76,7 @@ public List getDeliveryLocations( * @return */ @DeleteMapping("deliveries/{shopId}/possibles/{deliveryLocationId}") - @OwnerShopCheck + @OwnerShopCheck("shopId") public void deleteDeliveryLocation( @PathVariable(value = "shopId") Long shopId, @PathVariable(value = "deliveryLocationId") Long deliveryLocationId, diff --git a/src/main/java/com/delfood/controller/MemberController.java b/src/main/java/com/delfood/controller/MemberController.java index eb75318..82935ec 100644 --- a/src/main/java/com/delfood/controller/MemberController.java +++ b/src/main/java/com/delfood/controller/MemberController.java @@ -1,5 +1,7 @@ package com.delfood.controller; +import com.delfood.aop.LoginCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.aop.MemberLoginCheck; import com.delfood.dto.MemberDTO; import com.delfood.error.exception.DuplicateIdException; @@ -69,7 +71,7 @@ public class MemberController { * @return MemberDTO */ @GetMapping("myInfo") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public MemberInfoResponse memberInfo(HttpSession session) { String id = SessionUtil.getLoginMemberId(session); MemberDTO memberInfo = memberService.getMemberInfo(id); @@ -149,7 +151,7 @@ public ResponseEntity login(@RequestBody @NonNull MemberLoginRequ * @return 로그인 하지 않았을 시 401코드를 반환하고 result:NO_LOGIN 반환 로그아웃 성공시 200 코드를 반환 */ @GetMapping("logout") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void logout(HttpSession session) { SessionUtil.logoutMember(session); } @@ -162,7 +164,7 @@ public void logout(HttpSession session) { * @return */ @PatchMapping("password") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void updateMemberInfo(HttpSession session, @RequestBody @NotNull UpdateMemberPasswordRequest passwordRequest) { String passwordBeforeChange = passwordRequest.getPasswordBeforeChange(); @@ -183,7 +185,7 @@ public void updateMemberInfo(HttpSession session, * @return */ @DeleteMapping("myInfo") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void deleteMemberInfo(HttpSession session) { String id = SessionUtil.getLoginMemberId(session); memberService.deleteMember(id); @@ -198,7 +200,7 @@ public void deleteMemberInfo(HttpSession session) { * @param session 현재 로그인한 고객의 세션 */ @PatchMapping("address") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public ResponseEntity updateMemberAddress( @RequestBody @NotNull UpdateMemberAddressRequest memberInfo, HttpSession session) { ResponseEntity responseEntity = null; @@ -225,7 +227,7 @@ public ResponseEntity updateMemberAddress( } @PostMapping("token") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public void addToken(HttpSession session, String token) { String memberId = SessionUtil.getLoginMemberId(session); pushService.addMemberToken(memberId, token); diff --git a/src/main/java/com/delfood/controller/OrderController.java b/src/main/java/com/delfood/controller/OrderController.java index 6dd8920..2720bfe 100644 --- a/src/main/java/com/delfood/controller/OrderController.java +++ b/src/main/java/com/delfood/controller/OrderController.java @@ -1,5 +1,7 @@ package com.delfood.controller; +import com.delfood.aop.LoginCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.aop.MemberLoginCheck; import com.delfood.aop.OwnerLoginCheck; import com.delfood.controller.response.OrderResponse; @@ -56,7 +58,7 @@ public class OrderController { * @return */ @GetMapping("price") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public ItemsBillResponse getItemsBill(HttpSession session, @RequestBody List items) { long itemsPrice = orderService.totalPrice(SessionUtil.getLoginMemberId(session), items); @@ -70,7 +72,7 @@ public ItemsBillResponse getItemsBill(HttpSession session, * @return */ @GetMapping("{orderId}/bill") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public OrderBillDTO orderInfo(@PathVariable("orderId") Long orderId) { return orderService.getPreOrderBill(orderId); } @@ -83,7 +85,7 @@ public OrderBillDTO orderInfo(@PathVariable("orderId") Long orderId) { * @return */ @PostMapping - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public OrderResponse order(HttpSession session, @RequestBody OrderRequest request) { if (request.getItems().isEmpty()) { // items가 null일때도 NullpointerException이 발생한다 @@ -129,7 +131,7 @@ public OrderResponse order(HttpSession session, @RequestBody OrderRequest reques * @return */ @GetMapping("bill") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public ItemsBillDTO getBill(HttpSession session, @RequestBody BillRequest billRequest) { if (couponIssueService.isUsed(billRequest.getCouponIssueId())) { log.info("이미 사용한 쿠폰 사용 시도. 요청 발행 쿠폰 아이디 : {}", billRequest.getCouponIssueId()); @@ -147,7 +149,7 @@ public ItemsBillDTO getBill(HttpSession session, @RequestBody BillRequest billRe * @return */ @GetMapping - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public List myOrders(HttpSession session, @Nullable Long lastViewedOrderId) { return orderService.getMemberOrder(SessionUtil.getLoginMemberId(session), lastViewedOrderId); } @@ -160,7 +162,7 @@ public List myOrders(HttpSession session, @Nullable Long lastViewedOrd * @return */ @GetMapping("{orderId}") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public OrderDTO getOrder(HttpSession session, @PathVariable Long orderId) { OrderDTO orderInfo = orderService.getOrder(orderId); if (orderInfo == null) { @@ -186,7 +188,7 @@ public OrderDTO getOrder(HttpSession session, @PathVariable Long orderId) { * @return */ @GetMapping("owner") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public List getRequestedOrders(HttpSession session) { String ownerId = SessionUtil.getLoginOwnerId(session); List shopOrders = orderService.getOwnerOrderRequest(ownerId); @@ -201,7 +203,7 @@ public List getRequestedOrders(HttpSession session) { * @param session 사장님 세션 */ @PatchMapping("{orderId}/approve") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public void orderApprove(@PathVariable(name = "orderId") Long orderId, @RequestBody OrderApproveRequest request, HttpSession session) { diff --git a/src/main/java/com/delfood/controller/OwnerController.java b/src/main/java/com/delfood/controller/OwnerController.java index efe1ceb..fec58da 100644 --- a/src/main/java/com/delfood/controller/OwnerController.java +++ b/src/main/java/com/delfood/controller/OwnerController.java @@ -1,5 +1,7 @@ package com.delfood.controller; +import com.delfood.aop.LoginCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.aop.MemberLoginCheck; import com.delfood.aop.OwnerLoginCheck; import com.delfood.dto.OwnerDTO; @@ -107,7 +109,7 @@ public ResponseEntity login(@RequestBody OwnerLoginRequest l * @return */ @GetMapping("logout") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public void logout(HttpSession session) { SessionUtil.logoutOwner(session); } @@ -120,7 +122,7 @@ public void logout(HttpSession session) { * @return */ @GetMapping("myInfo") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public OwnerInfoResponse ownerInfo(HttpSession session) { String id = SessionUtil.getLoginOwnerId(session); OwnerDTO ownerInfo = ownerService.getOwner(id); @@ -135,7 +137,7 @@ public OwnerInfoResponse ownerInfo(HttpSession session) { * @return */ @PatchMapping - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public void updateOwnerInfo( @RequestBody UpdateOwnerMailAndTelRequest updateRequest, HttpSession session) { @@ -159,7 +161,7 @@ public void updateOwnerInfo( * @return */ @PatchMapping("password") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public void updatePassword( @RequestBody UpdateOwnerPasswordRequest passwordResquest, HttpSession session) { String id = SessionUtil.getLoginOwnerId(session); @@ -173,7 +175,7 @@ public void updatePassword( } @PostMapping("token") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public void addToken(HttpSession session, String token) { String ownerId = SessionUtil.getLoginOwnerId(session); pushService.addOwnerToken(ownerId, token); diff --git a/src/main/java/com/delfood/controller/RiderController.java b/src/main/java/com/delfood/controller/RiderController.java index cdc5c9b..6164206 100644 --- a/src/main/java/com/delfood/controller/RiderController.java +++ b/src/main/java/com/delfood/controller/RiderController.java @@ -1,5 +1,7 @@ package com.delfood.controller; +import com.delfood.aop.LoginCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.aop.RiderLoginCheck; import com.delfood.dto.rider.RiderDTO; import com.delfood.service.rider.RiderInfoService; @@ -99,8 +101,8 @@ public void logout(HttpSession session) { * @param session 사용자의 세션 * @param request 변경전 비밀번호, 변경할 비밀번호 정보 */ - @PatchMapping("update/password") - @RiderLoginCheck + @PatchMapping("password") + @LoginCheck(type = UserType.RIDER) public void updatePassword(HttpSession session, @RequestBody UpdatePasswordRequest request) { String id = SessionUtil.getLoginRiderId(session); riderInfoService.changePassword(id, request.getPasswordBeforechange(), @@ -114,14 +116,14 @@ public void updatePassword(HttpSession session, @RequestBody UpdatePasswordReque * @param password 유효성 검사를 위한 계정 비밀번호 */ @DeleteMapping - @RiderLoginCheck + @LoginCheck(type = UserType.RIDER) public void deleteRiderAccount(HttpSession session, String password) { String id = SessionUtil.getLoginRiderId(session); riderInfoService.deleteAccount(id, password); SessionUtil.logoutRider(session); } - @PatchMapping("update/mail") + @PatchMapping("mail") public void updateMail(HttpSession session, @RequestBody UpdateMailRequest request) { String id = SessionUtil.getLoginRiderId(session); riderInfoService.changeMail(id, request.getPassword(), request.getUpdateMail()); diff --git a/src/main/java/com/delfood/controller/ShopController.java b/src/main/java/com/delfood/controller/ShopController.java index a7bd6f1..aad5125 100644 --- a/src/main/java/com/delfood/controller/ShopController.java +++ b/src/main/java/com/delfood/controller/ShopController.java @@ -15,8 +15,10 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.delfood.aop.LoginCheck; import com.delfood.aop.OwnerLoginCheck; import com.delfood.aop.OwnerShopCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.dto.AddressDTO; import com.delfood.dto.DeliveryLocationDTO; import com.delfood.dto.OwnerDTO; @@ -48,7 +50,7 @@ public class ShopController { * @return */ @PostMapping - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public ResponseEntity addShop(HttpSession session, @RequestBody ShopDTO shopInfo) { String ownerId = SessionUtil.getLoginOwnerId(session); @@ -71,7 +73,7 @@ public ResponseEntity addShop(HttpSession session, * @return 페이지에 따른 사장님 매장, 총 매장 개수 */ @GetMapping - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public MyShopsResponse myShops(MyShopsRequest myShopsRequest, HttpSession session) { String id = SessionUtil.getLoginOwnerId(session); @@ -90,7 +92,7 @@ public MyShopsResponse myShops(MyShopsRequest myShopsRequest, * @return */ @PatchMapping("{id}") - @OwnerShopCheck + @OwnerShopCheck("id") public void updateShop(@PathVariable Long id, @RequestBody(required = true) final ShopUpdateDTO updateInfo, HttpSession session) { final ShopUpdateDTO copyData = ShopUpdateDTO.copyWithId(updateInfo, id); @@ -107,7 +109,7 @@ public void updateShop(@PathVariable Long id, * @return */ @PatchMapping("open/{id}") - @OwnerShopCheck + @OwnerShopCheck("id") public ShopDTO openShop( @PathVariable(value = "id", required = true) Long id, HttpSession session) { shopService.openShop(id); @@ -123,7 +125,7 @@ public ShopDTO openShop( * @return 오픈한 매장의 id, 이름 */ @PatchMapping("open/") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public List openAllShops(HttpSession session) { String ownerId = SessionUtil.getLoginOwnerId(session); List openShops = shopService.openAllShops(ownerId); @@ -140,7 +142,7 @@ public List openAllShops(HttpSession session) { * @return */ @PatchMapping("close/{id}") - @OwnerShopCheck + @OwnerShopCheck("id") public ShopDTO closeShop( @PathVariable(value = "id", required = true) Long id, HttpSession session) { shopService.closeShop(id); @@ -156,7 +158,7 @@ public ShopDTO closeShop( * @return 운영 종료를 진행한 매장의 id, 이름 */ @PatchMapping("close/") - @OwnerLoginCheck + @LoginCheck(type = UserType.OWNER) public List closeAllShops(HttpSession session) { String ownerId = SessionUtil.getLoginOwnerId(session); return shopService.closeAllShops(ownerId); @@ -170,7 +172,7 @@ public List closeAllShops(HttpSession session) { * @return */ @GetMapping("{shopId}") - @OwnerShopCheck + @OwnerShopCheck("shopId") public ShopInfoResponse shopInfo( @PathVariable(value = "shopId", required = true) Long shopId, HttpSession session) { ShopDTO shopInfo = shopService.getShop(shopId); diff --git a/src/main/java/com/delfood/controller/ShopSearchController.java b/src/main/java/com/delfood/controller/ShopSearchController.java index 64ab6d7..f76ade3 100644 --- a/src/main/java/com/delfood/controller/ShopSearchController.java +++ b/src/main/java/com/delfood/controller/ShopSearchController.java @@ -1,5 +1,7 @@ package com.delfood.controller; +import com.delfood.aop.LoginCheck; +import com.delfood.aop.LoginCheck.UserType; import com.delfood.aop.MemberLoginCheck; import com.delfood.dto.ShopCategoryDTO; import com.delfood.dto.ShopDTO; @@ -52,7 +54,7 @@ public GetShopCategoriesResponse getShopCategories() { * @return */ @GetMapping("/available/shops") - @MemberLoginCheck + @LoginCheck(type = UserType.MEMBER) public GetShopByCategoryIdAndTownCodeResponse getShopsByCategoryIdAndTownCode( @RequestParam(required = true) Long categoryId, HttpSession session) { String memberId = SessionUtil.getLoginMemberId(session); From 6de3866d31a89e2bdbd5bc848b983068f68ba780 Mon Sep 17 00:00:00 2001 From: Jun Date: Thu, 16 Jan 2020 11:12:22 +0900 Subject: [PATCH 24/24] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/delfood/dto/push/PushMessage.java | 23 +++++++------------ .../com/delfood/service/OrderService.java | 2 +- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/delfood/dto/push/PushMessage.java b/src/main/java/com/delfood/dto/push/PushMessage.java index 128766b..0fcc1db 100644 --- a/src/main/java/com/delfood/dto/push/PushMessage.java +++ b/src/main/java/com/delfood/dto/push/PushMessage.java @@ -12,6 +12,14 @@ public class PushMessage { @NonNull private String message; + public static final PushMessage ADD_ORDER_REQUEST = new PushMessage("DelFood 주문", "새로운 주문이 들어왔습니다"); + public static final PushMessage ACCEPT_ORDER_REQUEST = new PushMessage("DelFood 접수", "주문이 접수되었습니다"); + public static final PushMessage REQUIRED_ORDER_REQUEST = new PushMessage("DelFood 주문취소", "매장에서 주문을 취소하였습니다"); + public static final PushMessage DELIVERY_MATCH = new PushMessage("DelFood 배달원 매칭", "배달원이 매칭되었습니다"); + public static final PushMessage DELIVERY_START = new PushMessage("DelFood 배달 시작", "음식 배달이 시작되었습니다"); + public static final PushMessage DELIVERY_SUCCESS = new PushMessage("DelFood 배달 완료", "배달이 완료되었습니다"); + + private LocalDateTime generatedTime; public PushMessage(String title, String message) { @@ -21,20 +29,5 @@ public PushMessage(String title, String message) { } - public static PushMessage getMessasge(Type type) { - return type.pushMessage; - } - - @AllArgsConstructor - public static enum Type { - addOrderRequest(new PushMessage("DelFood 주문", "새로운 주문이 들어왔습니다")), - acceptOrderRequest(new PushMessage("DelFood 접수", "주문이 접수되었습니다")), - requiredOrderRequest(new PushMessage("DelFood 주문취소", "매장에서 주문을 취소하였습니다")), - deliveryMatch(new PushMessage("DelFood 배달원 매칭", "배달원이 매칭되었습니다")), - deliveryStart(new PushMessage("DelFood 배달 시작", "음식 배달이 시작되었습니다")), - deliverySuccess(new PushMessage("DelFood 배달 완료", "배달이 완료되었습니다")); - - private PushMessage pushMessage; - } } diff --git a/src/main/java/com/delfood/service/OrderService.java b/src/main/java/com/delfood/service/OrderService.java index 9d8b9f0..b6184c0 100644 --- a/src/main/java/com/delfood/service/OrderService.java +++ b/src/main/java/com/delfood/service/OrderService.java @@ -92,7 +92,7 @@ public OrderResponse order(String memberId, List items, long shopI } // 사장님에게 알림(푸시) - PushMessage pushMsg = PushMessage.getMessasge(PushMessage.Type.addOrderRequest); + PushMessage pushMsg = PushMessage.ADD_ORDER_REQUEST; String ownerId = shopService.getShop(shopId).getOwnerId(); pushService.sendMessageToOwner(pushMsg, ownerId); // Exception이 발생하지 않는다.