├── src ├── main │ ├── resources │ │ ├── application.properties │ │ ├── mapper │ │ │ ├── PayMapper.xml │ │ │ ├── OrderMenuMapper.xml │ │ │ ├── OrderMenuOptionMapper.xml │ │ │ ├── OptionMapper.xml │ │ │ ├── StoreListMapper.xml │ │ │ ├── MenuMapper.xml │ │ │ ├── UserMapper.xml │ │ │ ├── StoreMapper.xml │ │ │ └── OrderMapper.xml │ │ └── application-dev.properties │ └── java │ │ └── com │ │ └── flab │ │ └── makedel │ │ ├── service │ │ ├── PayService.java │ │ ├── LoginService.java │ │ ├── CartService.java │ │ ├── OptionService.java │ │ ├── CardPayService.java │ │ ├── NaverPayService.java │ │ ├── DepositPayService.java │ │ ├── DeliveryService.java │ │ ├── StoreListService.java │ │ ├── PushService.java │ │ ├── MenuService.java │ │ ├── SessionLoginService.java │ │ ├── RiderService.java │ │ ├── UserService.java │ │ ├── OrderTransactionService.java │ │ ├── StoreService.java │ │ └── OrderService.java │ │ ├── mapper │ │ ├── PayMapper.java │ │ ├── OrderMenuMapper.java │ │ ├── OrderMenuOptionMapper.java │ │ ├── MenuMapper.java │ │ ├── StoreMapper.java │ │ ├── UserMapper.java │ │ ├── StoreListMapper.java │ │ ├── OptionMapper.java │ │ └── OrderMapper.java │ │ ├── Exception │ │ ├── NotExistIdException.java │ │ ├── DuplicatedIdException.java │ │ └── StoreNameAlreadyExistException.java │ │ ├── dto │ │ ├── OptionDTO.java │ │ ├── OrderDetailOptionDTO.java │ │ ├── OrderMenuDTO.java │ │ ├── OrderMenuOptionDTO.java │ │ ├── OrderDetailMenuDTO.java │ │ ├── PushMessageDTO.java │ │ ├── OrderStoreDetailDTO.java │ │ ├── MenuDTO.java │ │ ├── OrderDetailDTO.java │ │ ├── CartOptionDTO.java │ │ ├── OrderDTO.java │ │ ├── PayDTO.java │ │ ├── UserInfoDTO.java │ │ ├── StoreInfoDTO.java │ │ ├── UserDTO.java │ │ ├── StoreCategoryDTO.java │ │ ├── RiderDTO.java │ │ ├── StoreDTO.java │ │ ├── CartItemDTO.java │ │ └── OrderReceiptDTO.java │ │ ├── annotation │ │ ├── CurrentUserId.java │ │ └── LoginCheck.java │ │ ├── utils │ │ ├── PasswordEncrypter.java │ │ ├── ResponseEntityConstants.java │ │ ├── PayServiceFactory.java │ │ └── CurrentUserIdResolver.java │ │ ├── config │ │ ├── WebConfig.java │ │ ├── DatabaseConfig.java │ │ └── RedisConfig.java │ │ ├── DeliveryMakeApplication.java │ │ ├── controller │ │ ├── DeliveryController.java │ │ ├── MyProfileController.java │ │ ├── StoreListController.java │ │ ├── CartController.java │ │ ├── OrderController.java │ │ ├── OptionController.java │ │ ├── MenuController.java │ │ ├── RiderController.java │ │ ├── StoreController.java │ │ └── UserController.java │ │ ├── aop │ │ └── LoginCheckAspect.java │ │ └── dao │ │ ├── CartItemDAO.java │ │ └── DeliveryDAO.java └── test │ └── java │ └── com │ └── flab │ └── makedel │ ├── DeliveryMakeApplicationTests.java │ └── service │ ├── StoreListServiceTest.java │ ├── OptionServiceTest.java │ ├── MenuServiceTest.java │ ├── UserServiceTest.java │ ├── StoreServiceTest.java │ └── RiderServiceTest.java ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── .gitignore ├── Dockerfile ├── Jenkinsfile ├── README.md ├── pom.xml ├── mvnw.cmd └── mvnw /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=dev -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-lab-edu/make-delivery/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/PayService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | public interface PayService { 4 | 5 | void pay(long price, long orderId); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/PayMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.PayDTO; 4 | 5 | public interface PayMapper { 6 | 7 | void insertPay(PayDTO payDTO); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /src/test/java/com/flab/makedel/DeliveryMakeApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | class DeliveryMakeApplicationTests { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/Exception/NotExistIdException.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.Exception; 2 | 3 | public class NotExistIdException extends RuntimeException { 4 | 5 | public NotExistIdException(String message) { 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/Exception/DuplicatedIdException.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.Exception; 2 | 3 | public class DuplicatedIdException extends RuntimeException { 4 | 5 | public DuplicatedIdException(String message) { 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/OrderMenuMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.OrderMenuDTO; 4 | import java.util.List; 5 | 6 | public interface OrderMenuMapper { 7 | 8 | void insertOrderMenu(List orderMenuList); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/Exception/StoreNameAlreadyExistException.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.Exception; 2 | 3 | public class StoreNameAlreadyExistException extends RuntimeException { 4 | 5 | public StoreNameAlreadyExistException(String message) { 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/OrderMenuOptionMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.OrderMenuOptionDTO; 4 | import java.util.List; 5 | 6 | public interface OrderMenuOptionMapper { 7 | 8 | void insertOrderMenuOption(List orderMenuOptionList); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OptionDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public class OptionDTO { 9 | 10 | private final Long id; 11 | 12 | private final String name; 13 | 14 | private final Long price; 15 | 16 | private final Long menuId; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderDetailOptionDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public class OrderDetailOptionDTO { 9 | 10 | private final Long optionId; 11 | 12 | private final String optionName; 13 | 14 | private final Long optionPrice; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderMenuDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | @Builder 10 | public class OrderMenuDTO { 11 | 12 | private final Long orderId; 13 | 14 | private final Long menuId; 15 | 16 | private final Long count; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/MenuMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.MenuDTO; 4 | import java.util.List; 5 | 6 | public interface MenuMapper { 7 | 8 | boolean isExistsId(long menuId); 9 | 10 | void insertMenu(MenuDTO menu); 11 | 12 | void deleteMenu(long menuId); 13 | 14 | List selectStoreMenu(long storeId); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/annotation/CurrentUserId.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.PARAMETER) 10 | public @interface CurrentUserId { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderMenuOptionDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | @Builder 10 | public class OrderMenuOptionDTO { 11 | 12 | private final Long menuId; 13 | 14 | private final Long orderId; 15 | 16 | private final Long optionId; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/utils/PasswordEncrypter.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.utils; 2 | 3 | import org.mindrot.jbcrypt.BCrypt; 4 | 5 | public class PasswordEncrypter { 6 | 7 | public static String encrypt(String password) { 8 | return BCrypt.hashpw(password, BCrypt.gensalt()); 9 | } 10 | 11 | public static boolean isMatch(String password, String hashedPassword) { 12 | return BCrypt.checkpw(password, hashedPassword); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderDetailMenuDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import java.util.List; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @RequiredArgsConstructor 9 | public class OrderDetailMenuDTO { 10 | 11 | private final Long menuId; 12 | 13 | private final String menuName; 14 | 15 | private final Long menuPrice; 16 | 17 | private final Long menuCount; 18 | 19 | private List optionList; 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/LoginService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | /* 4 | * 로그인 하는 로직은 여러 Controller에서 쓰일 수 있기 때문에 LoginService로 구현하였습니다. 5 | * 지금은 세션 방식으로 구현하였으나 나중에 토큰이나 여러 형태로 구현이 가능하기 떄문에 6 | * LoginService 인터페이스로 Loose Coupling을 통해 컨트롤러가 로그인 서비스를 간접적으로 의존하게 하였습니다. 7 | * 컨트롤러는 어떤방식(세션,토큰등)으로 로그인을 구현하였는지 알 필요가 없으며 interface 메소드만 사용하면 됩니다. 8 | * */ 9 | 10 | public interface LoginService { 11 | 12 | void loginUser(String id); 13 | 14 | void logoutUser(); 15 | 16 | String getCurrentUser(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | firebaseSDK.json -------------------------------------------------------------------------------- /src/main/resources/mapper/PayMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | INSERT INTO PAY (pay_type, price, order_id, status) 10 | VALUES 11 | (#{payType}, #{price}, #{orderId}, #{status}) 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/PushMessageDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @Builder 8 | public class PushMessageDTO { 9 | 10 | private final String title; 11 | 12 | private final String content; 13 | 14 | private final OrderReceiptDTO orderReceipt; 15 | 16 | private final String createdAt; 17 | 18 | public static final String RIDER_MESSAGE_TITLE = "배차 요청"; 19 | public static final String RIDER_MESSAGE_CONTENT = "근처 가게에서 주문이 승인된 후 배차 요청이 도착했습니다. 승인하시겠습니까?"; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.drive-class-name=com.mysql.cj.jdbc.Driver 2 | spring.datasource.url=jdbc:mysql://localhost:3306/makedelivery?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC 3 | spring.datasource.username=root 4 | spring.datasource.password=tkfkd11!! 5 | 6 | spring.redis.host=localhost 7 | spring.redis.password=tkfkd11!! 8 | 9 | spring.redis.session.port=6379 10 | spring.redis.cache.port=6389 11 | spring.redis.cart.port=6399 12 | spring.redis.rider.port=6409 13 | 14 | fcm.rider.expire.second=86400 -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/utils/ResponseEntityConstants.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.utils; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | 6 | public class ResponseEntityConstants { 7 | 8 | public static final ResponseEntity RESPONSE_OK = new ResponseEntity(HttpStatus.OK); 9 | public static final ResponseEntity RESPONSE_CONFLICT = new ResponseEntity( 10 | HttpStatus.CONFLICT); 11 | public static final ResponseEntity RESPONSE_NOT_FOUND = new ResponseEntity( 12 | HttpStatus.NOT_FOUND); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/mapper/OrderMenuMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | INSERT INTO ORDER_MENU (order_id, menu_id, count) 9 | VALUES 10 | 11 | (#{orderMenu.orderId}, #{orderMenu.menuId}, #{orderMenu.count}) 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderStoreDetailDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.List; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | public class OrderStoreDetailDTO { 11 | 12 | private final Long orderId; 13 | 14 | private final LocalDateTime orderCreatedAt; 15 | 16 | private final String orderStatus; 17 | 18 | private final Long totalPrice; 19 | 20 | private UserInfoDTO userInfo; 21 | 22 | private List menuList; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/StoreMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.StoreInfoDTO; 4 | import com.flab.makedel.dto.StoreDTO; 5 | import java.util.List; 6 | 7 | public interface StoreMapper { 8 | 9 | void insertStore(StoreDTO store); 10 | 11 | List selectStoreList(String ownerId); 12 | 13 | boolean isMyStore(long storeId, String ownerId); 14 | 15 | StoreDTO selectStore(long storeId, String ownerId); 16 | 17 | void closeMyStore(long storeId); 18 | 19 | void openMyStore(long storeId); 20 | 21 | StoreInfoDTO selectStoreInfo(long storeId); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 4 | import com.flab.makedel.dto.UserDTO; 5 | import com.flab.makedel.dto.UserInfoDTO; 6 | import org.apache.ibatis.annotations.Mapper; 7 | 8 | @Mapper 9 | public interface UserMapper { 10 | 11 | boolean isExistsId(String id); 12 | 13 | UserInfoDTO selectUserInfo(String id); 14 | 15 | void insertUser(UserDTO user); 16 | 17 | UserDTO selectUserById(String id); 18 | 19 | void deleteUser(String id); 20 | 21 | void updateUserPassword(String id, String newPassword); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/mapper/OrderMenuOptionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | INSERT INTO ORDER_MENU_OPTION (order_id, menu_id, option_id) 9 | VALUES 10 | 11 | (#{orderMenuOption.orderId}, #{orderMenuOption.menuId}, #{orderMenuOption.optionId}) 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/StoreListMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.StoreCategoryDTO; 4 | import com.flab.makedel.dto.StoreDTO; 5 | import java.util.List; 6 | 7 | /* 8 | selectCategoryList로 가져오는 List는 운영자들이 카테고리를 정하기 때문에 9 | 많아야 20개 내외이고 항상 같은 리스트를 리턴해주기 때문에 모든 데이터를 호출하도록 했습니다. 10 | 또한 서비스에서 이 mapper를 호출하기전 캐시를 사용하기 때문에 호출이 잦지 않습니다. 11 | */ 12 | 13 | public interface StoreListMapper { 14 | 15 | List selectCategoryList(); 16 | 17 | List selectStoreListByCategory(long categoryId); 18 | 19 | List selectStoreListByCategoryAndAddress(long categoryId,String address); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/MenuDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import java.time.LocalDateTime; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | @Builder 11 | public class MenuDTO { 12 | 13 | private final Long id; 14 | 15 | private final String name; 16 | 17 | private final Long price; 18 | 19 | private final String photo; 20 | 21 | private final String description; 22 | 23 | private final Long menuGroupId; 24 | 25 | private final Long storeId; 26 | 27 | private final LocalDateTime createdAt; 28 | 29 | private final LocalDateTime updatedAt; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderDetailDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 6 | import java.util.List; 7 | import lombok.Getter; 8 | import lombok.RequiredArgsConstructor; 9 | 10 | @Getter 11 | @RequiredArgsConstructor 12 | public class OrderDetailDTO { 13 | 14 | private final Long orderId; 15 | 16 | private final String orderStatus; 17 | 18 | private final Long totalPrice; 19 | 20 | private UserInfoDTO userInfo; 21 | 22 | private StoreInfoDTO storeInfo; 23 | 24 | private List menuList; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/CartOptionDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | public class CartOptionDTO { 9 | 10 | private Long optionId; 11 | 12 | private String name; 13 | 14 | private Long price; 15 | 16 | @JsonCreator 17 | public CartOptionDTO( 18 | @JsonProperty(value = "optionId") Long optionId, 19 | @JsonProperty(value = "name") String name, 20 | @JsonProperty(value = "price") Long price) { 21 | this.optionId = optionId; 22 | this.name = name; 23 | this.price = price; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.config; 2 | 3 | import com.flab.makedel.utils.CurrentUserIdResolver; 4 | import java.util.List; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | @Configuration 11 | @RequiredArgsConstructor 12 | public class WebConfig implements WebMvcConfigurer { 13 | 14 | private final CurrentUserIdResolver currentUserIdResolver; 15 | 16 | @Override 17 | public void addArgumentResolvers(List argumentResolvers) { 18 | argumentResolvers.add(currentUserIdResolver); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.flab.makedel.dto.PayDTO.PayType; 4 | import java.time.LocalDateTime; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | @Builder 12 | public class OrderDTO { 13 | 14 | private final Long id; 15 | 16 | private final LocalDateTime createdAt; 17 | 18 | private final OrderStatus orderStatus; 19 | 20 | private final String address; 21 | 22 | private final String userId; 23 | 24 | private final Long storeId; 25 | 26 | private final Long totalPrice; 27 | 28 | public enum OrderStatus { 29 | BEFORE_ORDER, COMPLETE_ORDER, APPROVED_ORDER, DELIVERING, COMPLETE_DELIVERY 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/CartService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dao.CartItemDAO; 4 | import com.flab.makedel.dto.CartItemDTO; 5 | import java.util.List; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | public class CartService { 12 | 13 | private final CartItemDAO cartItemDAO; 14 | 15 | public List loadCart(String userId) { 16 | return cartItemDAO.selectCartList(userId); 17 | } 18 | 19 | public void registerMenuInCart(String userId, CartItemDTO cart) { 20 | cartItemDAO.insertMenu(userId, cart); 21 | } 22 | 23 | public void deleteAllMenuInCart(String userId) { 24 | cartItemDAO.deleteMenuList(userId); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/OptionMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.OptionDTO; 4 | import java.util.List; 5 | 6 | /* 7 | bulk insert를 사용한 이유 : 쿼리를 날려 DB에 데이터를 넣으면 쿼리 전후로 이루어지는 여러 작업들이 있다. 8 | (connecting, sending query to server, parsing query, inserting indexes,closing등) 9 | bulk insert를 사용하여 한번의 쿼리를 날려 여러개의 데이터를 넣으면 쿼리 전후로 이루어지는 작업들이 10 | 한번만 이루어지면 된다. 즉 여러개의 데이터가 하나의 transaction에서 처리가 된다. 만약 여러 개의 데이터중 11 | 한개라도 에러가 난다면 Rollback이 이루어진다. 12 | 500개의 데이터를 넣는다고 가정하면 13 | bulk insert를 사용 하지 않으면 500번의 insert가 이루어져 커넥션이 500번 일어난 것이다. 14 | */ 15 | 16 | public interface OptionMapper { 17 | 18 | void insertOptionList(List optionList); 19 | 20 | void deleteOptionList(List optionList); 21 | 22 | List selectOptionList(long menuId); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/OptionService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dto.OptionDTO; 4 | import com.flab.makedel.mapper.OptionMapper; 5 | import java.util.List; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | public class OptionService { 12 | 13 | private final OptionMapper optionMapper; 14 | 15 | public void registerOptionList(List optionList) { 16 | optionMapper.insertOptionList(optionList); 17 | } 18 | 19 | public List loadOptionList(long menuId) { 20 | return optionMapper.selectOptionList(menuId); 21 | } 22 | 23 | public void deleteOptionList(List optionList) { 24 | optionMapper.deleteOptionList(optionList); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-alpine 2 | VOLUME /tmp 3 | RUN apk --no-cache add curl \ 4 | && apk --no-cache add jq 5 | 6 | ENV SPRING_REDIS_PASSWORD=${SPRING_REDIS_PASSWORD} 7 | ENV SPRING_REDIS_HOST=${SPRING_REDIS_HOST} 8 | ENV SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD} 9 | ENV SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL} 10 | ENV VAULT_TOKEN=${VAULT_TOKEN} 11 | 12 | 13 | RUN curl -H "X-Vault-Token: s.Dsm16mhBp82Kw92FQLrxf4Rd" \ 14 | http://118.67.130.216:8200/v1/kv/sdk | jq .data > firebaseSDK.json 15 | 16 | ARG JAR_FILE=target/*.jar 17 | COPY ${JAR_FILE} app.jar 18 | 19 | ENTRYPOINT ["java", \ 20 | "-Dspring.datasource.password=${SPRING_DATASOURCE_PASSWORD}", \ 21 | "-Dspring.redis.password=${SPRING_REDIS_PASSWORD}", \ 22 | "-Dspring.datasource.url=${SPRING_DATASOURCE_URL}", \ 23 | "-Dspring.redis.host=${SPRING_REDIS_HOST}", \ 24 | "-jar", \ 25 | "/app.jar"] -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/PayDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import java.time.LocalDateTime; 4 | import javax.validation.constraints.NotNull; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | @Builder 12 | public class PayDTO { 13 | 14 | private final Long id; 15 | 16 | @NotNull 17 | private final PayType payType; 18 | 19 | @NotNull 20 | private final Long price; 21 | 22 | @NotNull 23 | private final LocalDateTime createdAt; 24 | 25 | @NotNull 26 | private final Long orderId; 27 | 28 | @NotNull 29 | private final PayStatus status; 30 | 31 | public enum PayType { 32 | CARD, NAVER_PAY, DEPOSIT 33 | } 34 | 35 | public enum PayStatus { 36 | BEFORE_PAY, BEFORE_DEPOSIT, COMPLETE_PAY 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/CardPayService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dto.PayDTO; 4 | import com.flab.makedel.dto.PayDTO.PayStatus; 5 | import com.flab.makedel.dto.PayDTO.PayType; 6 | import com.flab.makedel.mapper.PayMapper; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class CardPayService implements PayService { 13 | 14 | private final PayMapper payMapper; 15 | 16 | @Override 17 | public void pay(long price, long orderId) { 18 | PayDTO payDTO = PayDTO.builder() 19 | .payType(PayType.CARD) 20 | .price(price) 21 | .orderId(orderId) 22 | .status(PayStatus.COMPLETE_PAY) 23 | .build(); 24 | payMapper.insertPay(payDTO); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/NaverPayService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dto.PayDTO; 4 | import com.flab.makedel.dto.PayDTO.PayStatus; 5 | import com.flab.makedel.dto.PayDTO.PayType; 6 | import com.flab.makedel.mapper.PayMapper; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class NaverPayService implements PayService { 13 | 14 | private final PayMapper payMapper; 15 | 16 | @Override 17 | public void pay(long price, long orderId) { 18 | PayDTO payDTO = PayDTO.builder() 19 | .payType(PayType.NAVER_PAY) 20 | .price(price) 21 | .orderId(orderId) 22 | .status(PayStatus.COMPLETE_PAY) 23 | .build(); 24 | payMapper.insertPay(payDTO); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/DepositPayService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dto.PayDTO; 4 | import com.flab.makedel.dto.PayDTO.PayStatus; 5 | import com.flab.makedel.dto.PayDTO.PayType; 6 | import com.flab.makedel.mapper.PayMapper; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class DepositPayService implements PayService { 13 | 14 | private final PayMapper payMapper; 15 | 16 | @Override 17 | public void pay(long price, long orderId) { 18 | PayDTO payDTO = PayDTO.builder() 19 | .payType(PayType.DEPOSIT) 20 | .price(price) 21 | .orderId(orderId) 22 | .status(PayStatus.BEFORE_DEPOSIT) 23 | .build(); 24 | payMapper.insertPay(payDTO); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/UserInfoDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @Builder 11 | public class UserInfoDTO { 12 | 13 | private final String id; 14 | 15 | private final String name; 16 | 17 | private final String phone; 18 | 19 | private final String address; 20 | 21 | @JsonCreator 22 | public UserInfoDTO(@JsonProperty(value = "id") String id, 23 | @JsonProperty(value = "name") String name, 24 | @JsonProperty(value = "phone") String phone, 25 | @JsonProperty(value = "address") String address) { 26 | this.id = id; 27 | this.name = name; 28 | this.phone = phone; 29 | this.address = address; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/StoreInfoDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @Builder 11 | public class StoreInfoDTO { 12 | 13 | private final Long storeId; 14 | 15 | private final String name; 16 | 17 | private final String phone; 18 | 19 | private final String address; 20 | 21 | @JsonCreator 22 | public StoreInfoDTO(@JsonProperty(value = "storeId") Long storeId, 23 | @JsonProperty(value = "name") String name, 24 | @JsonProperty(value = "phone") String phone, 25 | @JsonProperty(value = "address") String address) { 26 | this.storeId = storeId; 27 | this.name = name; 28 | this.phone = phone; 29 | this.address = address; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/mapper/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.mapper; 2 | 3 | import com.flab.makedel.dto.OrderDTO; 4 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 5 | import com.flab.makedel.dto.OrderDetailDTO; 6 | import com.flab.makedel.dto.OrderReceiptDTO; 7 | import com.flab.makedel.dto.OrderStoreDetailDTO; 8 | import java.util.List; 9 | 10 | public interface OrderMapper { 11 | 12 | void insertOrder(OrderDTO orderDTO); 13 | 14 | void completeOrder(long totalPrice, long orderId, OrderStatus orderStatus); 15 | 16 | OrderReceiptDTO selectOrderReceipt(long orderId); 17 | 18 | List selectDetailStoreOrder(long storeId); 19 | 20 | void approveOrder(long orderId, OrderStatus orderStatus); 21 | 22 | void updateStandbyOrderToDelivering(long orderId, String riderId, OrderStatus orderStatus); 23 | 24 | void finishDeliveringOrder(long orderId, OrderStatus orderStatus); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/UserDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | /* 11 | @Builder는 DTO 생성 시, 빌더 클래스를 자동으로 추가해준다. 빌더 클래스는 멤버변수별 메소드가 있고 12 | 이는 변수에 값을 set하고 이값을 통하여 build() 메소드를 통해 멤버변수에 필수값들을 null체크하고 13 | 이 멤버변수값을 이용해 빌더 클래스의 생성자를 호출하고 인스턴스를 리턴한다. 14 | */ 15 | 16 | @Getter 17 | @AllArgsConstructor 18 | @Builder 19 | public class UserDTO { 20 | 21 | private final String id; 22 | 23 | private final String password; 24 | 25 | private final String email; 26 | 27 | private final String name; 28 | 29 | private final String phone; 30 | 31 | private final String address; 32 | 33 | private final UserLevel level; 34 | 35 | private final LocalDateTime createdAt; 36 | 37 | private final LocalDateTime updatedAt; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/annotation/LoginCheck.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /* 9 | * Run-Time Weaving을 사용하는 다이나믹 프록시 방식의 SPRING AOP를 사용할 것이기 때문에 10 | * Retention은 Runtime까지로 지정해줬습니다. 11 | * 12 | * 프록시 객체는 런타임에 생성하지만 13 | * 런타임에 그 프록시를 생성할 타겟 빈을 찾을때 클래스정보(바이트 코드)를 참고하기 때문에 14 | * 런타임까지 해당 어노테이션을 유지할 필요는 없습니다. Retention을 클래스까지로해도 똑같이 AOP적용이 가능합니다. 15 | * */ 16 | 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(ElementType.METHOD) 19 | public @interface LoginCheck { 20 | 21 | UserLevel userLevel(); 22 | 23 | enum UserLevel { 24 | 25 | USER, OWNER, RIDER; 26 | 27 | public static UserLevel getEnumLevel(String level) { 28 | UserLevel userLevel = Enum.valueOf(UserLevel.class, level); 29 | return userLevel; 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/DeliveryService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dao.DeliveryDAO; 4 | import com.flab.makedel.dto.OrderReceiptDTO; 5 | import com.flab.makedel.dto.RiderDTO; 6 | import java.util.List; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class DeliveryService { 13 | 14 | private final DeliveryDAO deliveryDAO; 15 | 16 | public void registerStandbyOrderWhenOrderApprove(long orderId, OrderReceiptDTO orderReceipt) { 17 | deliveryDAO.insertStandbyOrderWhenOrderApprove(orderId, orderReceipt); 18 | } 19 | 20 | public OrderReceiptDTO loadStandbyOrder(long orderId, String riderAddress) { 21 | return deliveryDAO.selectStandbyOrder(orderId, riderAddress); 22 | } 23 | 24 | public List loadStandbyOrderList(String riderAddress) { 25 | return deliveryDAO.selectStandbyOrderList(riderAddress); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/mapper/OptionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | INSERT INTO MENU_OPTION (name, price, menu_id) 9 | VALUES 10 | 11 | (#{option.name}, #{option.price}, #{option.menuId}) 12 | 13 | 14 | 15 | 19 | 20 | 21 | DELETE FROM MENU_OPTION 22 | WHERE id IN ( 23 | 24 | #{option.id} 25 | 26 | ) 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/mapper/StoreListMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 16 | 17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/StoreCategoryDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Getter; 6 | /* 7 | Jackson의 메세지 컨버터가 objectMapper를 사용하여 deserialize합니다. 8 | 자바 객체로 deserialize하는 과정에서 자바 객체의 기본 생성자를 이용하기 때문에 9 | @NoArgsConstructor가 필요합니다. 기본생성자로 객체를 만들고 10 | 필드가 public이면 직접 assignment하고 private이라면 setter를 사용합니다. 11 | 하지만 객체가 immutable하기를 원한다면 setter가 없어야한다. 12 | 13 | 다른 방법으로는 14 | Jackson의 @JsonCreator를 이용하면 인자가 없는 기본생성자와 setter 없이도 15 | 객체를 생성할 수 있으며 setter가 없는 불변객체를 생성할 수 있습니다. 16 | 이 어노테이션을 생성자나 팩토리 메소드 위에 붙이면 17 | jackson이 해당 함수를 통해 객체를 생성하고 필드를 생성과 동시에 채웁니다. 18 | 이렇게 생성자를 통해 필드 주입까지 끝내버리면 setter 함수가 필요 없게 됩니다. 19 | jackson을 통해 deserialze한 immutable한 객체를 얻을 수 있습니다. 20 | */ 21 | 22 | @Getter 23 | public class StoreCategoryDTO { 24 | 25 | private final Long id; 26 | 27 | private final String name; 28 | 29 | @JsonCreator 30 | public StoreCategoryDTO(@JsonProperty(value = "id") Long id, 31 | @JsonProperty(value = "name") String name) { 32 | this.id = id; 33 | this.name = name; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/StoreListService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dto.StoreCategoryDTO; 4 | import com.flab.makedel.dto.StoreDTO; 5 | import com.flab.makedel.mapper.StoreListMapper; 6 | import java.util.List; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.cache.annotation.Cacheable; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | public class StoreListService { 14 | 15 | private final StoreListMapper storeListMapper; 16 | 17 | @Cacheable(value = "categories", key = "'store_category'") 18 | public List getAllStoreCategory() { 19 | return storeListMapper.selectCategoryList(); 20 | } 21 | 22 | @Cacheable(value = "stores", key = "#categoryId") 23 | public List getStoreListByCategory(long categoryId) { 24 | return storeListMapper.selectStoreListByCategory(categoryId); 25 | } 26 | 27 | @Cacheable(value = "stores", key = "#address+#categoryId") 28 | public List getStoreListByCategoryAndAddress(long categoryId, String address) { 29 | return storeListMapper.selectStoreListByCategoryAndAddress(categoryId, address); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/mapper/MenuMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | INSERT INTO 14 | MENU 15 | ( 16 | name, 17 | price, 18 | photo, 19 | description, 20 | menu_group_id, 21 | store_id 22 | ) 23 | VALUES 24 | ( 25 | #{name}, 26 | #{price}, 27 | #{photo}, 28 | #{description}, 29 | #{menuGroupId}, 30 | #{storeId} 31 | ) 32 | 33 | 34 | 35 | DELETE FROM MENU WHERE id = #{menuId} 36 | 37 | 38 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/PushService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.google.auth.oauth2.GoogleCredentials; 4 | import com.google.firebase.FirebaseApp; 5 | import com.google.firebase.FirebaseOptions; 6 | import com.google.firebase.messaging.FirebaseMessaging; 7 | import com.google.firebase.messaging.Message; 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.List; 12 | import javax.annotation.PostConstruct; 13 | import lombok.extern.log4j.Log4j2; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | @Log4j2 18 | public class PushService { 19 | 20 | @PostConstruct 21 | public void init() throws IOException { 22 | InputStream fileStream = new FileInputStream("firebaseSDK.json"); 23 | FirebaseOptions options = new FirebaseOptions.Builder() 24 | .setCredentials(GoogleCredentials 25 | .fromStream(fileStream)) 26 | .build(); 27 | if (FirebaseApp.getApps().isEmpty()) { 28 | FirebaseApp.initializeApp(options); 29 | } 30 | } 31 | 32 | public void sendMessages(List messages) { 33 | FirebaseMessaging.getInstance().sendAllAsync(messages); 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/utils/PayServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.utils; 2 | 3 | import com.flab.makedel.dto.PayDTO.PayType; 4 | import com.flab.makedel.service.CardPayService; 5 | import com.flab.makedel.service.DepositPayService; 6 | import com.flab.makedel.service.NaverPayService; 7 | import com.flab.makedel.service.PayService; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @RequiredArgsConstructor 13 | public class PayServiceFactory { 14 | 15 | private final CardPayService cardPayService; 16 | private final NaverPayService naverPayService; 17 | private final DepositPayService depositPayService; 18 | 19 | public PayService getPayService(PayType payType) { 20 | 21 | PayService payService = null; 22 | 23 | switch (payType) { 24 | case CARD: 25 | payService = cardPayService; 26 | break; 27 | case NAVER_PAY: 28 | payService = naverPayService; 29 | break; 30 | case DEPOSIT: 31 | payService = depositPayService; 32 | break; 33 | default: 34 | throw new IllegalArgumentException(); 35 | } 36 | 37 | return payService; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/RiderDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import javax.validation.constraints.NotNull; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @Builder 11 | public class RiderDTO { 12 | 13 | @NotNull 14 | private final String id; 15 | 16 | @NotNull 17 | private final String name; 18 | 19 | @NotNull 20 | private final String phone; 21 | 22 | @NotNull 23 | private final String address; 24 | 25 | @NotNull 26 | private final String updatedAt; 27 | 28 | @NotNull 29 | private final String fcmToken; 30 | 31 | @JsonCreator 32 | public RiderDTO(@JsonProperty(value = "id") String id, 33 | @JsonProperty(value = "name") String name, 34 | @JsonProperty(value = "phone") String phone, 35 | @JsonProperty(value = "address") String address, 36 | @JsonProperty(value = "updatedAt") String updatedAt, 37 | @JsonProperty(value = "fcmToken") String fcmToken) { 38 | this.id = id; 39 | this.name = name; 40 | this.phone = phone; 41 | this.address = address; 42 | this.updatedAt = updatedAt; 43 | this.fcmToken = fcmToken; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/utils/CurrentUserIdResolver.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.utils; 2 | 3 | import com.flab.makedel.annotation.CurrentUserId; 4 | import com.flab.makedel.service.LoginService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.bind.support.WebDataBinderFactory; 9 | import org.springframework.web.context.request.NativeWebRequest; 10 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 11 | import org.springframework.web.method.support.ModelAndViewContainer; 12 | 13 | @Component 14 | @RequiredArgsConstructor 15 | public class CurrentUserIdResolver implements HandlerMethodArgumentResolver { 16 | 17 | private final LoginService loginService; 18 | 19 | @Override 20 | public boolean supportsParameter(MethodParameter methodParameter) { 21 | return methodParameter.hasParameterAnnotation(CurrentUserId.class); 22 | } 23 | 24 | @Override 25 | public Object resolveArgument(MethodParameter methodParameter, 26 | ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, 27 | WebDataBinderFactory webDataBinderFactory) throws Exception { 28 | String currentUserId = loginService.getCurrentUser(); 29 | return currentUserId; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | INSERT INTO USER(id, password, email, name, phone, address, level) 14 | VALUES(#{id}, #{password}, #{email}, #{name}, #{phone}, #{address}, #{level}) 15 | 16 | 17 | 21 | 22 | 26 | 27 | 28 | DELETE FROM USER WHERE id = #{id} 29 | 30 | 31 | 32 | UPDATE USER SET password = #{newPassword} , updated_at = NOW() 33 | WHERE id = #{id} 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/MenuService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.Exception.DuplicatedIdException; 4 | import com.flab.makedel.Exception.NotExistIdException; 5 | import com.flab.makedel.dto.MenuDTO; 6 | import com.flab.makedel.mapper.MenuMapper; 7 | import java.util.List; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | public class MenuService { 14 | 15 | private final MenuMapper menuMapper; 16 | 17 | public void insertMenu(MenuDTO menu) { 18 | menuMapper.insertMenu(menu); 19 | } 20 | 21 | public MenuDTO setStoreId(MenuDTO menu, long storeId) { 22 | MenuDTO newMenu = MenuDTO.builder() 23 | .name(menu.getName()) 24 | .price(menu.getPrice()) 25 | .photo(menu.getPhoto()) 26 | .description(menu.getDescription()) 27 | .menuGroupId(menu.getMenuGroupId()) 28 | .storeId(storeId) 29 | .build(); 30 | return newMenu; 31 | } 32 | 33 | public void deleteMenu(long menuId) { 34 | if (!menuMapper.isExistsId(menuId)) { 35 | throw new NotExistIdException("존재하지 않는 메뉴 아이디 입니다 " + menuId); 36 | } 37 | menuMapper.deleteMenu(menuId); 38 | } 39 | 40 | public List loadStoreMenu(long storeId) { 41 | return menuMapper.selectStoreMenu(storeId); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/DeliveryMakeApplication.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; 7 | 8 | /* 9 | @EnableRedisHttpSession : 기존 서버 세션 저장소를 사용하지 않고 10 | Redis의 Session Stroage에 Session을 저장하게 해줍니다. 11 | springSessionRepositoryFilter라는 이름의 필터를 스프링빈으로 생성합니다. 12 | springSessionRepositoryFilter 필터는 HttpSession을 스프링세션에 의해 지원되는 커스템 구현체로 바꿔줍니다. 13 | 이 어노테이션이 붙은 곳에서는 레디스가 스프링 세션을 지원합니다. 14 | 15 | @EnableAspectJAutoProxy: 16 | AOP를 RTW인 다이나믹 프록시 방식으로 사용가능하게 해줍니다. 17 | @SpringbootAplication 어노테이션 안에 @EnableAutoConfiguration이 있고 18 | 이 어노테이션은 스프링부트가 클래스패스에서 찾은 빈들을 설정하게 해줍니다. 19 | 따라서 스프링이 @Aspect를 보고 프록시방식으로 사용하게 해주기 때문에 20 | @EnableAspectJAutoProxy가 생략가능합니다. 21 | 22 | @EnableCaching: 스프링에 AOP로 구현되어있는 캐시 로직을 사용하게 해줍니다. 23 | 스프링에서 제공해주는 AOP가 없다면 캐시를 사용할 때 직접 부가기능을 AOP로 구현해야합니다. 24 | 스프링이 CacheManager 인터페이스를 추상화하였기 때문에 RedisCacheManager,EhCacheManager등 25 | 필요한 CacheManage로 갈아끼워 사용할 수 있습니다. 26 | */ 27 | 28 | @SpringBootApplication 29 | @EnableRedisHttpSession 30 | @EnableCaching 31 | public class DeliveryMakeApplication { 32 | 33 | public static void main(String[] args) { 34 | SpringApplication.run(DeliveryMakeApplication.class, args); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/SessionLoginService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import javax.servlet.http.HttpSession; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | 7 | /* 8 | * SessionLoginService는 HttpSession에 직접 의존하지 않습니다. 9 | * 스프링이 SessionLoginService에 HttpSession 의존성 주입(DI)을 해줍니다. 10 | * LoginService 인터페이스를 이용해 Session방식의 LoginService를 구현합니다. 11 | * SessionLoginService는 singleton scope을 가지며 HttpSession은 session scope를 가집니다. 12 | * 이러한 스코프 차이 때문에 Spring이 HttpSession 인스턴스를 동적 프록시로 생성하여 주입합니다. 13 | * 이러한 기법은 Scoped Proxy라고 합니다. 14 | * */ 15 | 16 | @Service 17 | @RequiredArgsConstructor 18 | public class SessionLoginService implements LoginService { 19 | 20 | private static final String USER_ID = "USER_ID"; 21 | private final HttpSession session; 22 | 23 | @Override 24 | public void loginUser(String id) { 25 | session.setAttribute(USER_ID, id); 26 | } 27 | 28 | @Override 29 | public void logoutUser() { 30 | session.removeAttribute(USER_ID); 31 | } 32 | 33 | @Override 34 | public String getCurrentUser() { 35 | return (String) session.getAttribute(USER_ID); 36 | } 37 | } 38 | 39 | /* 40 | 레디스의 키값으로 저장되는 타입입니다. 41 | spring:session:sessions:expires (string) 해당 세션의 만료키로 사용합니다. 42 | spring:session:expirations (set) expire time에 삭제될 세션 정보를 담고 있습니다. 43 | spring:session:sessions (hash) session은 map을 저장소로 사용하기 때문에 이곳에 세션 데이터가 있습니다. 44 | */ -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/DeliveryController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import com.flab.makedel.annotation.LoginCheck; 4 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 5 | import com.flab.makedel.dto.OrderReceiptDTO; 6 | import com.flab.makedel.dto.RiderDTO; 7 | import com.flab.makedel.service.DeliveryService; 8 | import java.util.List; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | @RequestMapping(value = "/orders", params = "status") 19 | @RequiredArgsConstructor 20 | public class DeliveryController { 21 | 22 | private final DeliveryService deliveryService; 23 | 24 | 25 | @GetMapping(params = "orderId") 26 | @LoginCheck(userLevel = UserLevel.RIDER) 27 | public OrderReceiptDTO loadStandbyOrder(long orderId, String riderAddress) { 28 | return deliveryService.loadStandbyOrder(orderId, riderAddress); 29 | } 30 | 31 | @GetMapping 32 | @LoginCheck(userLevel = UserLevel.RIDER) 33 | public List loadStandbyOrderList(String riderAddress) { 34 | return deliveryService.loadStandbyOrderList(riderAddress); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/StoreDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import java.time.LocalDateTime; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | 10 | @Getter 11 | @Builder 12 | public class StoreDTO { 13 | 14 | private final Long id; 15 | 16 | private final String name; 17 | 18 | private final String phone; 19 | 20 | private final String address; 21 | 22 | private final String ownerId; 23 | 24 | private final String openStatus; 25 | 26 | private final String introduction; 27 | 28 | private final Long categoryId; 29 | 30 | @JsonCreator 31 | public StoreDTO(@JsonProperty(value = "id") Long id, 32 | @JsonProperty(value = "name") String name, 33 | @JsonProperty(value = "phone") String phone, 34 | @JsonProperty(value = "address") String address, 35 | @JsonProperty(value = "ownerId") String ownerId, 36 | @JsonProperty(value = "openStatus") String openStatus, 37 | @JsonProperty(value = "introduction") String introduction, 38 | @JsonProperty(value = "categoryId") Long categoryId 39 | ) { 40 | this.id = id; 41 | this.name = name; 42 | this.phone = phone; 43 | this.address = address; 44 | this.ownerId = ownerId; 45 | this.openStatus = openStatus; 46 | this.introduction = introduction; 47 | this.categoryId = categoryId; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/CartItemDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import java.util.List; 6 | import javax.validation.constraints.NotNull; 7 | import lombok.Getter; 8 | import lombok.RequiredArgsConstructor; 9 | 10 | @Getter 11 | public class CartItemDTO { 12 | 13 | @NotNull 14 | private String name; 15 | 16 | @NotNull 17 | private Long price; 18 | 19 | @NotNull 20 | private Long menuId; 21 | 22 | @NotNull 23 | private Long storeId; 24 | 25 | @NotNull 26 | private Long count; 27 | 28 | @NotNull 29 | private List optionList; 30 | 31 | @JsonCreator 32 | public CartItemDTO(@JsonProperty(value = "name") String name, 33 | @JsonProperty(value = "price") Long price, 34 | @JsonProperty(value = "menuId") Long menuId, 35 | @JsonProperty(value = "storeId") Long storeId, 36 | @JsonProperty(value = "count") Long count, 37 | @JsonProperty(value = "optionList") List optionList 38 | ) { 39 | this.name = name; 40 | this.price = price; 41 | this.menuId = menuId; 42 | this.storeId = storeId; 43 | this.count = count; 44 | this.optionList = optionList; 45 | } 46 | 47 | public CartItemDTO(Long menuId, String name, Long price, Long count) { 48 | this.menuId = menuId; 49 | this.name = name; 50 | this.price = price; 51 | this.count = count; 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/MyProfileController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import static com.flab.makedel.utils.ResponseEntityConstants.RESPONSE_OK; 4 | 5 | import com.flab.makedel.annotation.CurrentUserId; 6 | import com.flab.makedel.annotation.LoginCheck; 7 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 8 | import com.flab.makedel.service.LoginService; 9 | import com.flab.makedel.service.UserService; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.PatchMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | @RequestMapping("/my-profiles") 19 | @RequiredArgsConstructor 20 | public class MyProfileController { 21 | 22 | private final LoginService loginService; 23 | private final UserService userService; 24 | 25 | @DeleteMapping 26 | @LoginCheck(userLevel = UserLevel.USER) 27 | public ResponseEntity deleteUser(@CurrentUserId String userId) { 28 | userService.deleteUser(userId); 29 | loginService.logoutUser(); 30 | return RESPONSE_OK; 31 | } 32 | 33 | @PatchMapping("/password") 34 | @LoginCheck(userLevel = UserLevel.USER) 35 | public ResponseEntity changeUserPassword(@CurrentUserId String userId, 36 | String password) { 37 | userService.changeUserPassword(userId, password); 38 | return RESPONSE_OK; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dto/OrderReceiptDTO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 6 | import java.util.List; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | import lombok.RequiredArgsConstructor; 11 | 12 | @Getter 13 | @Builder 14 | public class OrderReceiptDTO { 15 | 16 | private Long orderId; 17 | 18 | private String orderStatus; 19 | 20 | private UserInfoDTO userInfo; 21 | 22 | private Long totalPrice; 23 | 24 | private StoreInfoDTO storeInfo; 25 | 26 | private List cartList; 27 | 28 | @JsonCreator 29 | public OrderReceiptDTO(@JsonProperty(value = "orderId") Long orderId, 30 | @JsonProperty(value = "orderStatus") String orderStatus, 31 | @JsonProperty(value = "userInfo") UserInfoDTO userInfo, 32 | @JsonProperty(value = "totalPrice") Long totalPrice, 33 | @JsonProperty(value = "storeInfo") StoreInfoDTO storeInfo, 34 | @JsonProperty(value = "cartList") List cartList) { 35 | this.orderId = orderId; 36 | this.orderStatus = orderStatus; 37 | this.userInfo = userInfo; 38 | this.totalPrice = totalPrice; 39 | this.storeInfo = storeInfo; 40 | this.cartList = cartList; 41 | } 42 | 43 | public OrderReceiptDTO(Long orderId, String orderStatus, Long totalPrice) { 44 | this.orderId = orderId; 45 | this.orderStatus = orderStatus; 46 | this.totalPrice = totalPrice; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/StoreListController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import com.flab.makedel.dto.StoreCategoryDTO; 4 | import com.flab.makedel.dto.StoreDTO; 5 | import com.flab.makedel.service.StoreListService; 6 | import java.util.List; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | @RequestMapping("/stores") 16 | @RequiredArgsConstructor 17 | public class StoreListController { 18 | 19 | private final StoreListService storeListService; 20 | 21 | @GetMapping("/categories") 22 | public ResponseEntity> loadStoreCategory() { 23 | List categoryList = storeListService.getAllStoreCategory(); 24 | return ResponseEntity.ok().body(categoryList); 25 | } 26 | 27 | @GetMapping(params = "categoryId") 28 | public ResponseEntity> loadStoreListByCategory(long categoryId) { 29 | List storeList = storeListService.getStoreListByCategory(categoryId); 30 | return ResponseEntity.ok().body(storeList); 31 | } 32 | 33 | @GetMapping(params = {"categoryId", "address"}) 34 | public ResponseEntity> loadStoreListByCategoryAndAddress(long categoryId, 35 | String address) { 36 | List storeList = storeListService 37 | .getStoreListByCategoryAndAddress(categoryId, address); 38 | return ResponseEntity.ok().body(storeList); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/mapper/StoreMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | INSERT INTO STORE(name, phone, address, owner_id, category_id, introduction) 9 | VALUES(#{name}, #{phone}, #{address}, #{ownerId}, #{categoryId}, #{introduction}) 10 | 11 | 12 | 16 | 17 | 21 | 22 | 26 | 27 | 28 | UPDATE STORE SET open_status = 'closed' 29 | where id = #{storeId} 30 | 31 | 32 | 33 | UPDATE STORE SET open_status = 'opened' 34 | where id = #{storeId} 35 | 36 | 37 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/CartController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import com.flab.makedel.annotation.LoginCheck; 4 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 5 | import com.flab.makedel.dto.CartItemDTO; 6 | import com.flab.makedel.service.CartService; 7 | import java.util.List; 8 | import javax.validation.Valid; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | @RestController 19 | @RequestMapping("/users/{userId}/carts") 20 | @RequiredArgsConstructor 21 | public class CartController { 22 | 23 | private final CartService cartService; 24 | 25 | @PostMapping 26 | @LoginCheck(userLevel = UserLevel.USER) 27 | public void registerMenuInCart(@PathVariable String userId, 28 | @Valid @RequestBody CartItemDTO cart) { 29 | cartService.registerMenuInCart(userId, cart); 30 | } 31 | 32 | @GetMapping 33 | @LoginCheck(userLevel = UserLevel.USER) 34 | public List loadCart(@PathVariable String userId) { 35 | List cartList = cartService.loadCart(userId); 36 | return cartList; 37 | } 38 | 39 | @DeleteMapping 40 | @LoginCheck(userLevel = UserLevel.USER) 41 | public void deleteAllMenuInCart(@PathVariable String userId) { 42 | cartService.deleteAllMenuInCart(userId); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import com.flab.makedel.annotation.CurrentUserId; 4 | import com.flab.makedel.annotation.LoginCheck; 5 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 6 | import com.flab.makedel.dto.OrderDetailDTO; 7 | import com.flab.makedel.dto.OrderReceiptDTO; 8 | import com.flab.makedel.dto.OrderStoreDetailDTO; 9 | import com.flab.makedel.dto.PayDTO.PayType; 10 | import com.flab.makedel.service.OrderService; 11 | import java.util.List; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | @RestController 20 | @RequestMapping("/stores/{storeId}/orders") 21 | @RequiredArgsConstructor 22 | public class OrderController { 23 | 24 | private final OrderService orderService; 25 | 26 | @PostMapping 27 | @LoginCheck(userLevel = UserLevel.USER) 28 | public OrderReceiptDTO registerOrder(@CurrentUserId String userId, @PathVariable long storeId, 29 | PayType payType) { 30 | OrderReceiptDTO orderReceipt = orderService.registerOrder(userId, storeId, payType); 31 | return orderReceipt; 32 | } 33 | 34 | @GetMapping("/{orderId}") 35 | @LoginCheck(userLevel = UserLevel.USER) 36 | public OrderReceiptDTO loadOrder(@PathVariable long orderId) { 37 | return orderService.getOrderInfoByOrderId(orderId); 38 | } 39 | 40 | @GetMapping 41 | @LoginCheck(userLevel = UserLevel.OWNER) 42 | public List loadStoreOrder(@PathVariable long storeId) { 43 | return orderService.getStoreOrderInfoByStoreId(storeId); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/config/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.apache.ibatis.session.SqlSessionFactory; 5 | import org.mybatis.spring.SqlSessionFactoryBean; 6 | import org.mybatis.spring.SqlSessionTemplate; 7 | import org.mybatis.spring.annotation.MapperScan; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import javax.sql.DataSource; 12 | import org.springframework.transaction.annotation.EnableTransactionManagement; 13 | 14 | /* 15 | @Configuration 16 | 스프링이 빈 팩토리를 위해 별도의 환경설정 정보를 담당하는 클래스라고 인식할 수 있도록 @configuration 애노테이션을 추가합니다. 17 | 이 클래스 안에서는 @bean 애노테이션을 통해 메소드들을 빈으로 추가해줍니다. 18 | @SpringBootAplication 애노테이션에 메타애노테이션으로 @componentscan 이 있습니다. 19 | 이 컴퍼넌트 스캔은 스프링부트어플리케이션 애노테이션을 베이스패키지로 삼아 @component 애노테이션을 찾아서 애플리케이션 컨텍스트에 빈으로 추가합니다. 20 | @configuration 애노테이션에 메타애노테이션으로 @component가 있으므로 컴퍼넌트스캔이 @configuration을 빈으로 추가시킵니다. 21 | 22 | @MapperScan 23 | 베이스패키지라는 이름으로 Scan할 범위를 지정하고 24 | 그 베이스패키지부터 하위패키지 까지 Mapper Interface 가 있는지 모두 스캔하여 모든 Mapper를 등록합니다. 25 | 이 스캔 기능은 마이바티스 스프링 연동모듈이 자동스캔 해주는 것입니다. 26 | */ 27 | 28 | @Configuration 29 | @MapperScan(basePackages = "com.flab.makedel.mapper") 30 | @RequiredArgsConstructor 31 | public class DatabaseConfig { 32 | 33 | private final ApplicationContext applicationContext; 34 | 35 | @Bean 36 | public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { 37 | SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 38 | sqlSessionFactoryBean.setDataSource(dataSource); 39 | sqlSessionFactoryBean.setMapperLocations( 40 | applicationContext.getResources("classpath:/mapper/**/*.xml")); 41 | return sqlSessionFactoryBean.getObject(); 42 | } 43 | 44 | @Bean 45 | public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { 46 | return new SqlSessionTemplate(sqlSessionFactory); 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/OptionController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import com.flab.makedel.annotation.CurrentUserId; 4 | import com.flab.makedel.annotation.LoginCheck; 5 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 6 | import com.flab.makedel.dto.OptionDTO; 7 | import com.flab.makedel.service.OptionService; 8 | import com.flab.makedel.service.StoreService; 9 | import java.util.List; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | @RestController 21 | @RequestMapping("/stores/{storeId}/menus/{menuId}/options") 22 | @RequiredArgsConstructor 23 | public class OptionController { 24 | 25 | private final OptionService optionService; 26 | private final StoreService storeService; 27 | 28 | @PostMapping 29 | @LoginCheck(userLevel = UserLevel.OWNER) 30 | public void registerOptionList(@RequestBody List optionList, 31 | @PathVariable long storeId, @CurrentUserId String ownerId) { 32 | 33 | storeService.validateMyStore(storeId, ownerId); 34 | optionService.registerOptionList(optionList); 35 | 36 | } 37 | 38 | @GetMapping 39 | public ResponseEntity> loadOptionList(@PathVariable long menuId) { 40 | List optionList = optionService.loadOptionList(menuId); 41 | return ResponseEntity.ok().body(optionList); 42 | } 43 | 44 | @DeleteMapping 45 | @LoginCheck(userLevel = UserLevel.OWNER) 46 | public void deleteOptionList(@RequestBody List optionList, 47 | @PathVariable long storeId, @CurrentUserId String ownerId) { 48 | 49 | storeService.validateMyStore(storeId, ownerId); 50 | optionService.deleteOptionList(optionList); 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/MenuController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import com.flab.makedel.annotation.CurrentUserId; 4 | import com.flab.makedel.annotation.LoginCheck; 5 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 6 | import com.flab.makedel.dto.MenuDTO; 7 | import com.flab.makedel.service.MenuService; 8 | import com.flab.makedel.service.StoreService; 9 | import java.util.List; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.DeleteMapping; 14 | import org.springframework.web.bind.annotation.GetMapping; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.PostMapping; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | import org.springframework.web.client.HttpClientErrorException; 20 | 21 | @RestController 22 | @RequestMapping("/stores/{storeId}/menus") 23 | @RequiredArgsConstructor 24 | public class MenuController { 25 | 26 | private final MenuService menuService; 27 | private final StoreService storeService; 28 | 29 | @PostMapping 30 | @LoginCheck(userLevel = UserLevel.OWNER) 31 | public void insertMenu(MenuDTO menu, @PathVariable long storeId, 32 | @CurrentUserId String ownerId) { 33 | 34 | storeService.validateMyStore(storeId, ownerId); 35 | MenuDTO newMenu = menuService.setStoreId(menu, storeId); 36 | menuService.insertMenu(newMenu); 37 | 38 | } 39 | 40 | @DeleteMapping("/{menuId}") 41 | @LoginCheck(userLevel = UserLevel.OWNER) 42 | public void deleteMenu(@PathVariable Long menuId, @PathVariable long storeId, 43 | @CurrentUserId String ownerId) { 44 | 45 | storeService.validateMyStore(storeId, ownerId); 46 | menuService.deleteMenu(menuId); 47 | 48 | } 49 | 50 | @GetMapping 51 | public ResponseEntity> loadStoreMenu(@PathVariable long storeId) { 52 | List menuList = menuService.loadStoreMenu(storeId); 53 | return ResponseEntity.ok().body(menuList); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/RiderController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import com.flab.makedel.annotation.LoginCheck; 4 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 5 | import com.flab.makedel.dto.PushMessageDTO; 6 | import com.flab.makedel.dto.RiderDTO; 7 | import com.flab.makedel.service.PushService; 8 | import com.flab.makedel.service.RiderService; 9 | import com.google.firebase.messaging.FirebaseMessagingException; 10 | import java.io.IOException; 11 | import java.time.LocalDateTime; 12 | import java.util.Set; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.web.bind.annotation.DeleteMapping; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PatchMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | @RestController 23 | @RequestMapping("/riders") 24 | @RequiredArgsConstructor 25 | public class RiderController { 26 | 27 | private final RiderService riderService; 28 | 29 | @PostMapping("/{riderId}/standby") 30 | @LoginCheck(userLevel = UserLevel.RIDER) 31 | public void registerStandbyRider(RiderDTO rider) { 32 | riderService.registerStandbyRiderWhenStartWork(rider); 33 | } 34 | 35 | @DeleteMapping("/{riderId}/standby") 36 | @LoginCheck(userLevel = UserLevel.RIDER) 37 | public void deleteStandbyRider(RiderDTO rider) { 38 | riderService.deleteStandbyRiderWhenStopWork(rider); 39 | } 40 | 41 | @PatchMapping("/{riderId}/orders/{orderId}/accept") 42 | @LoginCheck(userLevel = UserLevel.RIDER) 43 | public void acceptStandbyOrder(@PathVariable long orderId, 44 | RiderDTO rider) { 45 | riderService.acceptStandbyOrder(orderId, rider); 46 | } 47 | 48 | @PatchMapping("/{riderId}/orders/{orderId}/finish") 49 | @LoginCheck(userLevel = UserLevel.RIDER) 50 | public void finishDeliveringOrder(@PathVariable long orderId, RiderDTO rider) { 51 | riderService.finishDeliveringOrder(orderId, rider); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/RiderService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dao.DeliveryDAO; 4 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 5 | import com.flab.makedel.dto.PushMessageDTO; 6 | import com.flab.makedel.dto.RiderDTO; 7 | import com.flab.makedel.mapper.OrderMapper; 8 | import com.google.firebase.messaging.FirebaseMessaging; 9 | import com.google.firebase.messaging.Message; 10 | import java.util.List; 11 | import java.util.Set; 12 | import java.util.stream.Collectors; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | @Service 18 | @RequiredArgsConstructor 19 | public class RiderService { 20 | 21 | private final DeliveryDAO deliveryDAO; 22 | private final OrderMapper orderMapper; 23 | private final PushService pushService; 24 | 25 | public void registerStandbyRiderWhenStartWork(RiderDTO rider) { 26 | deliveryDAO.insertStandbyRiderWhenStartWork(rider); 27 | } 28 | 29 | public void deleteStandbyRiderWhenStopWork(RiderDTO rider) { 30 | deliveryDAO.deleteStandbyRiderWhenStopWork(rider); 31 | } 32 | 33 | @Transactional 34 | public void acceptStandbyOrder(long orderId, RiderDTO rider) { 35 | deliveryDAO.updateStandbyOrderToDelivering(orderId, rider); 36 | orderMapper.updateStandbyOrderToDelivering(orderId, rider.getId(), OrderStatus.DELIVERING); 37 | } 38 | 39 | @Transactional 40 | public void finishDeliveringOrder(long orderId, RiderDTO rider) { 41 | orderMapper.finishDeliveringOrder(orderId, OrderStatus.COMPLETE_DELIVERY); 42 | deliveryDAO.insertStandbyRiderWhenStartWork(rider); 43 | } 44 | 45 | public void sendMessageToStandbyRidersInSameArea(String address, PushMessageDTO pushMessage) { 46 | Set tokenSet = deliveryDAO.selectStandbyRiderTokenList(address); 47 | List messages = tokenSet.stream().map(token -> Message.builder() 48 | .putData("title", pushMessage.getTitle()) 49 | .putData("content", pushMessage.getContent()) 50 | .putData("orderReceipt", pushMessage.getOrderReceipt().toString()) 51 | .putData("createdAt", pushMessage.getCreatedAt()) 52 | .setToken(token) 53 | .build()) 54 | .collect(Collectors.toList()); 55 | 56 | pushService.sendMessages(messages); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.Exception.DuplicatedIdException; 4 | import com.flab.makedel.Exception.NotExistIdException; 5 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 6 | import com.flab.makedel.dto.UserDTO; 7 | import com.flab.makedel.mapper.UserMapper; 8 | import com.flab.makedel.utils.PasswordEncrypter; 9 | import java.util.Optional; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.stereotype.Service; 12 | 13 | /* 14 | @Service 어노테이션은 비즈니스 로직을 처리하는 서비스라는 것을 알려주는 어노테이션이다. 15 | Component Scan을 통하여 @Service 어노테이션이 붙은 클래스를 스프링이 빈으로 등록하고 이 빈의 라이프사이클을 관리한다. 16 | */ 17 | 18 | @Service 19 | @RequiredArgsConstructor 20 | public class UserService { 21 | 22 | private final UserMapper userMapper; 23 | 24 | public void signUp(UserDTO user) { 25 | if (isExistsId(user.getId())) { 26 | throw new DuplicatedIdException("Same id exists id: " + user.getId()); 27 | } 28 | UserDTO encryptedUser = encryptUser(user); 29 | userMapper.insertUser(encryptedUser); 30 | } 31 | 32 | public boolean isExistsId(String id) { 33 | return userMapper.isExistsId(id); 34 | } 35 | 36 | public UserDTO findUserById(String id) { 37 | return userMapper.selectUserById(id); 38 | } 39 | 40 | public UserDTO encryptUser(UserDTO user) { 41 | String encryptedPassword = PasswordEncrypter.encrypt(user.getPassword()); 42 | UserDTO encryptedUser = UserDTO.builder() 43 | .id(user.getId()) 44 | .password(encryptedPassword) 45 | .email(user.getEmail()) 46 | .name(user.getName()) 47 | .phone(user.getPhone()) 48 | .address(user.getAddress()) 49 | .level(user.getLevel()) 50 | .build(); 51 | return encryptedUser; 52 | } 53 | 54 | public void deleteUser(String id) { 55 | if (!isExistsId(id)) { 56 | throw new NotExistIdException("Not exists id"); 57 | } 58 | userMapper.deleteUser(id); 59 | } 60 | 61 | public void changeUserPassword(String id, String newPassword) { 62 | userMapper.updateUserPassword(id, PasswordEncrypter.encrypt(newPassword)); 63 | } 64 | 65 | public Optional findUserByIdAndPassword(String id, String password) { 66 | 67 | Optional user = Optional.ofNullable(userMapper.selectUserById(id)); 68 | 69 | if (!user.isPresent()) { 70 | return Optional.empty(); 71 | } 72 | 73 | boolean isSamePassword = PasswordEncrypter.isMatch(password, user.get().getPassword()); 74 | 75 | if (!isSamePassword) { 76 | return Optional.empty(); 77 | } 78 | 79 | return user; 80 | } 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/test/java/com/flab/makedel/service/StoreListServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import static org.mockito.ArgumentMatchers.any; 4 | import static org.mockito.ArgumentMatchers.anyList; 5 | import static org.mockito.ArgumentMatchers.anyLong; 6 | import static org.mockito.ArgumentMatchers.isNull; 7 | import static org.mockito.Mockito.doThrow; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.doNothing; 10 | import static org.mockito.Mockito.when; 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | import com.flab.makedel.dto.StoreCategoryDTO; 14 | import com.flab.makedel.dto.StoreDTO; 15 | import com.flab.makedel.mapper.StoreListMapper; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import org.junit.jupiter.api.BeforeEach; 19 | import org.junit.jupiter.api.DisplayName; 20 | import org.junit.jupiter.api.Test; 21 | import org.junit.jupiter.api.extension.ExtendWith; 22 | import org.mockito.InjectMocks; 23 | import org.mockito.Mock; 24 | import org.mockito.junit.jupiter.MockitoExtension; 25 | 26 | @ExtendWith(MockitoExtension.class) 27 | class StoreListServiceTest { 28 | 29 | @Mock 30 | StoreListMapper storeListMapper; 31 | 32 | @InjectMocks 33 | StoreListService storeListService; 34 | 35 | @Test 36 | @DisplayName("올바른 카테고리 아이디로 음식점 목록을 조회한다") 37 | public void getStoreListByCategoryTest() { 38 | when(storeListMapper.selectStoreListByCategory(anyLong())).thenReturn(anyList()); 39 | 40 | storeListService.getStoreListByCategory(1L); 41 | 42 | verify(storeListMapper).selectStoreListByCategory(anyLong()); 43 | } 44 | 45 | @Test 46 | @DisplayName("올바른 카테고리 아이디로 음식점 목록을 조회했는데 음식점 목록이 없다면 빈 리스트를 리턴한다") 47 | public void getStoreListByCategoryTestReturnEmptyList() { 48 | when(storeListMapper.selectStoreListByCategory(anyLong())).thenReturn(new ArrayList<>()); 49 | 50 | assertEquals(storeListService.getStoreListByCategory(2L).isEmpty(), true); 51 | 52 | verify(storeListMapper).selectStoreListByCategory(anyLong()); 53 | } 54 | 55 | @Test 56 | @DisplayName("잘못된 카테고리 아이디로 음식점 목록을 조회하면 RuntimeException을 던진다") 57 | public void getStoreListByCategoryTestFailBecauseWrongId() { 58 | doThrow(RuntimeException.class).when(storeListMapper).selectStoreListByCategory(anyLong()); 59 | 60 | assertThrows(RuntimeException.class, () -> storeListService.getStoreListByCategory(100L)); 61 | 62 | verify(storeListMapper).selectStoreListByCategory(anyLong()); 63 | } 64 | 65 | @Test 66 | @DisplayName("음식 카테고리 전체 목록을 조회한다") 67 | public void getAllStoreCategoryTest() { 68 | when(storeListMapper.selectCategoryList()).thenReturn(new ArrayList()); 69 | 70 | storeListService.getAllStoreCategory(); 71 | 72 | verify(storeListMapper).selectCategoryList(); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | tools { 5 | maven "Maven 3.6.3" 6 | } 7 | 8 | environment { 9 | GIT_COMMIT_REV='' 10 | GIT_CHANGE_BRANCH_NAME='' 11 | GIT_COMMIT_SHA='' 12 | } 13 | 14 | options { 15 | skipDefaultCheckout() 16 | } 17 | 18 | stages { 19 | 20 | stage('git checkout & clone') { 21 | steps { 22 | script { 23 | cleanWs() 24 | GIT_CHANGE_BRANCH_NAME = sh(returnStdout: true, script: 'echo ${payload} | python3 -c \"import sys,json;print(json.load(sys.stdin,strict=False)[\'ref\'][11:])\"').trim() 25 | GIT_COMMIT_SHA = sh(returnStdout: true, script: 'echo ${payload} | python3 -c \"import sys,json;print(json.load(sys.stdin,strict=False)[\'head_commit\'][\'id\'])\"').trim() 26 | echo "arrive ${GIT_CHANGE_BRANCH_NAME}" 27 | sh "git clone -b ${GIT_CHANGE_BRANCH_NAME} --single-branch \"https://github.com/f-lab-edu/make-delivery.git\"" 28 | } 29 | } 30 | } 31 | 32 | stage('Build') { 33 | steps { 34 | sh "mvn -f make-delivery/pom.xml -DskipTests clean package" 35 | archiveArtifacts 'make-delivery/target/*.jar' 36 | } 37 | 38 | post { 39 | success { 40 | sh ("curl -X POST -H \"Content-Type: application/json\" \ 41 | --data '{\"state\": \"success\", \"context\": \"@@pass ci test & build\", \"target_url\": \"http://115.85.180.192:8080/job/make-delivery\"}' \ 42 | \"https://${GITHUB_TOKEN}@api.github.com/repos/f-lab-edu/make-delivery/statuses/${GIT_COMMIT_SHA}\"") 43 | } 44 | 45 | failure { 46 | sh ("curl -X POST -H \"Content-Type: application/json\" \ 47 | --data '{\"state\": \"failure\", \"context\": \"@@failure ci test & build\", \"target_url\": \"http://115.85.180.192:8080/job/make-delivery\"}' \ 48 | \"https://${GITHUB_TOKEN}@api.github.com/repos/f-lab-edu/make-delivery/statuses/${GIT_COMMIT_SHA}\"") 49 | } 50 | } 51 | } 52 | 53 | 54 | stage('Dockerfile Build & Push To Docker Hub & Delete Docker Image') { 55 | steps { 56 | script { 57 | sh "docker build -t tjdrnr05571/make-delivery make-delivery/." 58 | sh "docker push tjdrnr05571/make-delivery" 59 | sh "docker rmi tjdrnr05571/make-delivery" 60 | } 61 | } 62 | } 63 | 64 | stage('Deploy') { 65 | steps { 66 | script { 67 | sh "ssh -p 1039 root@106.10.53.113 -T sh < /var/lib/jenkins/docker-deploy.sh" 68 | } 69 | } 70 | } 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/aop/LoginCheckAspect.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.aop; 2 | 3 | import com.flab.makedel.annotation.LoginCheck; 4 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 5 | import com.flab.makedel.service.LoginService; 6 | import com.flab.makedel.service.UserService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.aspectj.lang.annotation.Aspect; 9 | import org.aspectj.lang.annotation.Before; 10 | import org.springframework.core.annotation.Order; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.web.client.HttpClientErrorException; 14 | 15 | /* 16 | * Aspect 클래스를 빈으로 등록하는 이유는 타깃을 DI받아야 하기 때문인 것 같다 17 | * ProceedingJoinPoint같이 타깃 메소드들을 알려면 타깃을 DI받아야한다. 18 | * InvocationHandler를 이용한 다이나믹 프록시 방식에서는 타깃을 직접 DI 받았지만 19 | * 아마 AspectJ에서 @Aspect를 선언하면 저절로 주입해주는 것 같다(?) 20 | * 21 | * @Order 어노테이션을 적용하면 여러개의 AOP를 적용할 시 어떤 Advice를 먼저 사용할지 순서를 22 | * 정해줄 수 있습니다. 23 | * */ 24 | 25 | @Aspect 26 | @Component 27 | @RequiredArgsConstructor 28 | public class LoginCheckAspect { 29 | 30 | private final LoginService loginService; 31 | private final UserService userService; 32 | 33 | @Before("@annotation(com.flab.makedel.annotation.LoginCheck) && @annotation(target)") 34 | public void loginCheck(LoginCheck target) throws HttpClientErrorException { 35 | 36 | if (target.userLevel() == UserLevel.USER) { 37 | userLoginCheck(); 38 | } else if (target.userLevel() == UserLevel.OWNER) { 39 | ownerLoginCheck(); 40 | } else if (target.userLevel() == UserLevel.RIDER) { 41 | riderLoginCheck(); 42 | } 43 | 44 | } 45 | 46 | private String getCurrentUser() throws HttpClientErrorException { 47 | 48 | String userId = loginService.getCurrentUser(); 49 | if (userId == null) { 50 | throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED); 51 | } 52 | 53 | return userId; 54 | 55 | } 56 | 57 | private void userLoginCheck() { 58 | 59 | String userId = getCurrentUser(); 60 | 61 | UserLevel level = userService.findUserById(userId).getLevel(); 62 | 63 | if (!(level == UserLevel.USER)) { 64 | throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED); 65 | } 66 | 67 | } 68 | 69 | private void ownerLoginCheck() { 70 | 71 | String userId = getCurrentUser(); 72 | 73 | UserLevel level = userService.findUserById(userId).getLevel(); 74 | 75 | if (!(level == UserLevel.OWNER)) { 76 | throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED); 77 | } 78 | 79 | } 80 | 81 | private void riderLoginCheck() { 82 | 83 | String userId = getCurrentUser(); 84 | 85 | UserLevel level = userService.findUserById(userId).getLevel(); 86 | 87 | if (!(level == UserLevel.RIDER)) { 88 | throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED); 89 | } 90 | } 91 | 92 | } 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # make-delivery 서버 구조도 2 | ![real](https://user-images.githubusercontent.com/34911552/102442304-772a4480-4067-11eb-839f-2d986933cde6.png) 3 | 4 | 5 | 6 | ## 프로젝트 목표 7 | * 배달의 민족 같은 배달 앱 서비스를 구현해 내는 것이 목표입니다. 8 | * 단순한 기능 구현뿐 아니라 대용량 트래픽 처리까지 고려한 기능을 구현하는 것이 목표입니다. 9 | * 객체지향 원리와 여러 이론적 토대위에서 올바른 코드를 작성하는 것이 목표입니다. 10 | * 문서화, 단위테스트는 높은 우선순위를 두어 작성했고 CI/CD를 통한 자동화 또한 구현하여 쉽게 협업이 가능한 프로젝트로 만들었습니다. 11 | 12 | ## 기술적 issue 해결 과정 13 | 14 | * [#11] 성능 테스트 결과에 따라 비용을 고려하여 적절한 서버 구조 설계 과정 15 | https://tjdrnr05571.tistory.com/16 16 | 17 | * [#10] Ngrinder를 이용해 성능테스트 : WAS, DB, Nginx등 서버에 병목이 있는지 확인하는 과정 18 | https://tjdrnr05571.tistory.com/17 19 | 20 | * [#9] 같은 주문에 2명의 라이더가 동시에 배달하는 문제 해결 - Redis Transaction을 이용하여 데이터 atomic 보장 21 | https://tjdrnr05571.tistory.com/18 22 | 23 | * [#8] Mysql Replication - Spring에서 AOP를 이용해 Master/Slave 이중화한 과정 24 | https://tjdrnr05571.tistory.com/14 25 | 26 | * [#7] TransactionManager가 DataSource정하는 로직을 쿼리를 날릴 때로 늦추기 - LazyConnectionDataSourceProxy 27 | https://tjdrnr05571.tistory.com/15 28 | 29 | * [#6] Jenkins를 이용하여 CI/CD 환경 구축하는 과정 30 | https://tjdrnr05571.tistory.com/13 31 | 32 | * [#5] 성능을 위해 Redis keys 대신 scan 이용하기 33 | https://tjdrnr05571.tistory.com/11 34 | 35 | * [#4] Spring에서 중복되는 로그인 체크 기능을 AOP로 분리하기 36 | https://tjdrnr05571.tistory.com/10 37 | 38 | * [#3] 정확히 트랜잭션이 롤백 되었을 때 장바구니를 복원하기 - TransactionSynchronization (Rollback hook) 39 | https://tjdrnr05571.tistory.com/9 40 | 41 | * [#2] Redis에 한번에 많은 데이터 추가 시 네트워크 병목 개선하기 - Redis Pipeline 이용하기 42 | https://tjdrnr05571.tistory.com/7 43 | 44 | * [#1] 서버가 여러대면 로그인 정보는 어디에 저장할까? - Sticky Session, Session Clustering, Redis Session Storage 45 | https://tjdrnr05571.tistory.com/3 46 | 47 | ## 프로젝트 중점사항 48 | * 버전관리 49 | * 문서화 50 | * Service Layer를 고립시켜 의존적이지 않은 단위테스트 작성 51 | * 서버의 확장성 52 | * 로그인을 했는지 확인하는 부가기능이 반복되어 AOP로 분리 53 | * 로그인 서비스 추상화 54 | * Redis Cache를 이용해 음식점 조회 기능 구현 55 | * 스프링의 @Transactional 이용하여 주문과 결제 로직 구현 56 | * Redis Transaction을 이용하여 장바구니 데이터의 atomic을 보장하도록 관리 57 | * Rollback Hook을 사용하여 주문,결제 오류 시 장바구니 복원 58 | * Redis Pipeline을 이용하여 한번에 많은 데이터 추가 시 네트워크 병목 개선 59 | * Mysql에서 인덱스 설정과 실행계획 분석 후 쿼리 튜닝 60 | * 같은 주문에 2명이상의 라이더가 동시에 배달하는 문제를 Redis Transaction을 이용하여 해결 61 | * Redis Scan을 이용해 라이더가 주문 목록을 조회하는 기능 구현 62 | * 새로운 스레드풀을 만들고 @Async를 이용하여 비동기 푸쉬 알람 서비스 구현 63 | * Jenkins를 사용하여 CI/CD 환경 구축 64 | * Docker를 이용하여 CD 구현 65 | * Vault 서버를 띄워 암호, 설정값 관리 66 | * Mysql Replication – AOP를 이용하여 Master/Slave로 데이터베이스 이중화 67 | * Nginx의 Reversed-Proxy를 이용하여 로드밸런싱 68 | * Nginx의 Micro caching을 이용해 요청 처리 69 | * Ngrinder를 이용하여 성능 테스트 70 | * VisualVM을 이용해 WAS서버 CPU사용량과 스레드, 힙 모니터링 71 | 72 | 73 | ## 라이더 배차 과정 구조도 74 | ![rider](https://user-images.githubusercontent.com/34911552/102442827-a55c5400-4068-11eb-93ab-705ae21e927e.png) 75 | 76 | ## DB ERD 77 | ![스크린샷 2020-10-12 22 05 51](https://user-images.githubusercontent.com/34911552/95750006-74ae1600-0cd7-11eb-8e10-2f16de2bbec4.png) 78 | 79 | ## Use Case 80 | https://github.com/f-lab-edu/make-delivery/wiki/Use-Case 81 | 82 | ## Front 83 | https://github.com/f-lab-edu/make-delivery/wiki/%ED%99%94%EB%A9%B4-%EC%84%A4%EA%B3%84 84 | -------------------------------------------------------------------------------- /src/test/java/com/flab/makedel/service/OptionServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import static org.mockito.ArgumentMatchers.any; 4 | import static org.mockito.ArgumentMatchers.anyList; 5 | import static org.mockito.ArgumentMatchers.anyLong; 6 | import static org.mockito.Mockito.doThrow; 7 | import static org.mockito.Mockito.verify; 8 | import static org.mockito.Mockito.doNothing; 9 | import static org.mockito.Mockito.when; 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | import com.flab.makedel.dto.OptionDTO; 13 | import com.flab.makedel.mapper.OptionMapper; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.DisplayName; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.api.extension.ExtendWith; 20 | import org.mockito.InjectMocks; 21 | import org.mockito.Mock; 22 | import org.mockito.junit.jupiter.MockitoExtension; 23 | 24 | @ExtendWith(MockitoExtension.class) 25 | public class OptionServiceTest { 26 | 27 | @Mock 28 | OptionMapper optionMapper; 29 | 30 | @InjectMocks 31 | OptionService optionService; 32 | 33 | List optionList; 34 | 35 | @BeforeEach 36 | public void init() { 37 | optionList = new ArrayList(); 38 | } 39 | 40 | @Test 41 | @DisplayName("사장님이 음식 옵션을 추가한다") 42 | public void registerOptionListTest() { 43 | doNothing().when(optionMapper).insertOptionList(anyList()); 44 | 45 | optionService.registerOptionList(optionList); 46 | 47 | verify(optionMapper).insertOptionList(anyList()); 48 | } 49 | 50 | @Test 51 | @DisplayName("음식 메뉴에 대한 옵션리스트를 조회한다") 52 | public void loadOptionListTest() { 53 | when(optionMapper.selectOptionList(anyLong())).thenReturn(optionList); 54 | 55 | optionService.loadOptionList(12L); 56 | 57 | verify(optionMapper).selectOptionList(anyLong()); 58 | } 59 | 60 | @Test 61 | @DisplayName("잘못된 메뉴 아이디로 음식 메뉴에 대한 옵션리스트를 조회하면 빈 리스트를 리턴한다") 62 | public void loadOptionListTestReturnEmptyListBecauseWrongMenuId() { 63 | when(optionMapper.selectOptionList(anyLong())).thenReturn(new ArrayList<>()); 64 | 65 | assertEquals(optionService.loadOptionList(12L).isEmpty(), true); 66 | 67 | verify(optionMapper).selectOptionList(anyLong()); 68 | } 69 | 70 | @Test 71 | @DisplayName("잘못된 가게 아이디로 음식 메뉴에 대한 옵션리스트를 조회하면 빈 리스트를 리턴한다") 72 | public void loadOptionListTestReturnEmptyListBecauseWrongStoreId() { 73 | when(optionMapper.selectOptionList(anyLong())).thenReturn(new ArrayList<>()); 74 | 75 | assertEquals(optionService.loadOptionList(12L).isEmpty(), true); 76 | 77 | verify(optionMapper).selectOptionList(anyLong()); 78 | } 79 | 80 | @Test 81 | @DisplayName("가게 사장님이 메뉴에 대한 옵션을 삭제한다") 82 | public void deleteOptionListTest() { 83 | doNothing().when(optionMapper).deleteOptionList(anyList()); 84 | 85 | optionService.deleteOptionList(optionList); 86 | 87 | verify(optionMapper).deleteOptionList(anyList()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/flab/makedel/service/MenuServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import static org.mockito.ArgumentMatchers.any; 4 | import static org.mockito.ArgumentMatchers.anyList; 5 | import static org.mockito.ArgumentMatchers.anyLong; 6 | import static org.mockito.Mockito.doThrow; 7 | import static org.mockito.Mockito.verify; 8 | import static org.mockito.Mockito.doNothing; 9 | import static org.mockito.Mockito.when; 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | import com.flab.makedel.Exception.NotExistIdException; 13 | import com.flab.makedel.dto.MenuDTO; 14 | import com.flab.makedel.mapper.MenuMapper; 15 | import java.util.ArrayList; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.DisplayName; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.api.extension.ExtendWith; 20 | import org.mockito.InjectMocks; 21 | import org.mockito.Mock; 22 | import org.mockito.junit.jupiter.MockitoExtension; 23 | 24 | @ExtendWith(MockitoExtension.class) 25 | public class MenuServiceTest { 26 | 27 | @Mock 28 | MenuMapper menuMapper; 29 | 30 | @InjectMocks 31 | MenuService menuService; 32 | 33 | MenuDTO menuDTO; 34 | 35 | @BeforeEach 36 | public void init() { 37 | menuDTO = MenuDTO.builder() 38 | .name("뿌링클") 39 | .price(12300L) 40 | .photo("34.jpg") 41 | .description("맛있습니다") 42 | .menuGroupId(2L) 43 | .storeId(3L) 44 | .build(); 45 | } 46 | 47 | @Test 48 | @DisplayName("사장님이 가게 메뉴를 추가한다") 49 | public void insertMenuTest() { 50 | doNothing().when(menuMapper).insertMenu(any(MenuDTO.class)); 51 | 52 | menuService.insertMenu(menuDTO); 53 | 54 | verify(menuMapper).insertMenu(any(MenuDTO.class)); 55 | } 56 | 57 | @Test 58 | @DisplayName("사장님이 가게 메뉴를 삭제한다") 59 | public void deleteMenuTest() { 60 | doNothing().when(menuMapper).deleteMenu(anyLong()); 61 | 62 | menuService.deleteMenu(12L); 63 | 64 | verify(menuMapper).deleteMenu(anyLong()); 65 | } 66 | 67 | @Test 68 | @DisplayName("사장님이 가게 메뉴를 삭제할 때 잘못된 아이디로 요청하면 NotExistIdException을 던진다") 69 | public void deleteMenuTestThrowNotExistIdException() { 70 | when(menuMapper.isExistsId(anyLong())).thenReturn(false); 71 | 72 | assertThrows(NotExistIdException.class, () -> menuService.deleteMenu(12L)); 73 | 74 | verify(menuMapper).isExistsId(anyLong()); 75 | } 76 | 77 | @Test 78 | @DisplayName("모든 사용자가 가게 메뉴를 조회한다") 79 | public void loadStoreMenuTest() { 80 | when(menuMapper.selectStoreMenu(anyLong())).thenReturn(anyList()); 81 | 82 | menuService.loadStoreMenu(12L); 83 | 84 | verify(menuMapper).selectStoreMenu(anyLong()); 85 | } 86 | 87 | @Test 88 | @DisplayName("가게 메뉴를 조회할 때 없는 가게 아이디로 조회를 하면 빈 리스트를 리턴한다") 89 | public void loadStoreMenuTestReturnEmptyList() { 90 | when(menuMapper.selectStoreMenu(anyLong())).thenReturn(new ArrayList()); 91 | 92 | assertEquals(menuService.loadStoreMenu(12L).isEmpty(), true); 93 | 94 | verify(menuMapper).selectStoreMenu(anyLong()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/StoreController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import static com.flab.makedel.utils.ResponseEntityConstants.RESPONSE_OK; 4 | 5 | import com.flab.makedel.annotation.CurrentUserId; 6 | import com.flab.makedel.annotation.LoginCheck; 7 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 8 | import com.flab.makedel.dto.StoreDTO; 9 | import com.flab.makedel.service.StoreService; 10 | import com.google.firebase.messaging.FirebaseMessagingException; 11 | import java.io.IOException; 12 | import java.util.List; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PatchMapping; 18 | import org.springframework.web.bind.annotation.PathVariable; 19 | import org.springframework.web.bind.annotation.PostMapping; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | import org.springframework.web.client.HttpClientErrorException; 23 | 24 | @RestController 25 | @RequestMapping("/stores") 26 | @RequiredArgsConstructor 27 | public class StoreController { 28 | 29 | private final StoreService storeService; 30 | 31 | @PostMapping 32 | @LoginCheck(userLevel = UserLevel.OWNER) 33 | public ResponseEntity insertStore(StoreDTO store, @CurrentUserId String ownerId) { 34 | 35 | storeService.insertStore(store, ownerId); 36 | return RESPONSE_OK; 37 | 38 | } 39 | 40 | @GetMapping 41 | @LoginCheck(userLevel = UserLevel.OWNER) 42 | public ResponseEntity> getMyAllStore(@CurrentUserId String ownerId) { 43 | 44 | List stores = storeService.getMyAllStore(ownerId); 45 | return ResponseEntity.ok().body(stores); 46 | 47 | } 48 | 49 | @GetMapping("/{storeId}") 50 | @LoginCheck(userLevel = UserLevel.OWNER) 51 | public ResponseEntity getMyStore(@PathVariable long storeId, 52 | @CurrentUserId String ownerId) { 53 | 54 | storeService.validateMyStore(storeId, ownerId); 55 | StoreDTO store = storeService.getMyStore(storeId, ownerId); 56 | return ResponseEntity.ok().body(store); 57 | 58 | } 59 | 60 | @PatchMapping("/{storeId}/closed") 61 | @LoginCheck(userLevel = UserLevel.OWNER) 62 | public ResponseEntity closeMyStore(@PathVariable long storeId, 63 | @CurrentUserId String ownerId) { 64 | 65 | storeService.validateMyStore(storeId, ownerId); 66 | storeService.closeMyStore(storeId); 67 | return RESPONSE_OK; 68 | 69 | } 70 | 71 | @PatchMapping("/{storeId}/opened") 72 | @LoginCheck(userLevel = UserLevel.OWNER) 73 | public ResponseEntity openMyStore(@PathVariable long storeId, 74 | @CurrentUserId String ownerId) { 75 | 76 | storeService.validateMyStore(storeId, ownerId); 77 | storeService.openMyStore(storeId); 78 | return RESPONSE_OK; 79 | 80 | } 81 | 82 | @PostMapping("/{storeId}/orders/{orderId}/approve") 83 | @LoginCheck(userLevel = UserLevel.OWNER) 84 | public void approveOrder(@PathVariable long orderId, @PathVariable long storeId, 85 | @CurrentUserId String ownerId) { 86 | storeService.validateMyStore(storeId, ownerId); 87 | storeService.approveOrder(orderId); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.controller; 2 | 3 | import static com.flab.makedel.utils.ResponseEntityConstants.RESPONSE_CONFLICT; 4 | import static com.flab.makedel.utils.ResponseEntityConstants.RESPONSE_NOT_FOUND; 5 | import static com.flab.makedel.utils.ResponseEntityConstants.RESPONSE_OK; 6 | 7 | import com.flab.makedel.annotation.LoginCheck; 8 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 9 | import com.flab.makedel.dto.UserDTO; 10 | import com.flab.makedel.service.LoginService; 11 | import com.flab.makedel.service.UserService; 12 | import java.util.Optional; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.web.bind.annotation.*; 16 | 17 | /* 18 | @RestController @Controller에 @ResponseBody까지 합쳐진것입니다. 19 | 주로 Http response로 view가 아닌 문자열과 JSON등을 보낼때 사용합니다. 20 | @RequestMapping 어노테이션은 URL을 컨트롤러의 클래스나 메서드와 매핑할 때 사용하는 스프링 프레임워크의 어노테이션입니다. 21 | @RequestBody는 HTTP 요청 body를 자바 객체로 변환합니다. 22 | 23 | request.getRequest은 해당 클라이언트의 세션이 없다면 생성해주고 있으면 반환해줍니다. 24 | 메소드의 파라미터로 HttpSession을 받아온다면 위 과정을 스프링이 해줍니다. 25 | */ 26 | 27 | @RestController 28 | @RequestMapping("/users") 29 | @RequiredArgsConstructor 30 | public class UserController { 31 | 32 | private final UserService userService; 33 | private final LoginService loginService; 34 | 35 | 36 | @PostMapping 37 | public void signUp(UserDTO user) { 38 | userService.signUp(user); 39 | } 40 | 41 | @GetMapping("/{id}/exists") 42 | public ResponseEntity checkUniqueId(@PathVariable String id) { 43 | boolean isUniqueId = userService.isExistsId(id); 44 | if (isUniqueId) { 45 | return RESPONSE_OK; 46 | } else { 47 | return RESPONSE_CONFLICT; 48 | } 49 | } 50 | 51 | @PostMapping("/login") 52 | public ResponseEntity login(String id, String password) { 53 | 54 | Optional user = userService.findUserByIdAndPassword(id, password); 55 | 56 | if (user.isPresent()) { 57 | loginService.loginUser(user.get().getId()); 58 | return RESPONSE_OK; 59 | } else { 60 | return RESPONSE_NOT_FOUND; 61 | } 62 | 63 | } 64 | 65 | @GetMapping("/logout") 66 | @LoginCheck(userLevel = UserLevel.USER) 67 | public ResponseEntity logout() { 68 | loginService.logoutUser(); 69 | return RESPONSE_OK; 70 | } 71 | 72 | } 73 | 74 | /* 75 | 컨트롤러 서비스 매퍼(dao)로 분리해서 사용하는 이유 76 | 결국은 객체지향의 원칙을 따르려고 분리하는 것입니다. 비즈니스 로직을 분리하고 재사용을 가능하게 하기 위해서입니다. 77 | 객체지향의 원칙인 단일책임 원칙을 지켜 서비스는 비즈니스 로직을 처리하게 하고 78 | 컨트롤러는 http 요청에 따라 사용할 서비스를 선택하고 매퍼(dao)에서는 sql 쿼리를 분리시켜 각각의 책임을 가지게 하는 것입니다. 79 | 80 | 또한 객체지향의 원칙인 개방폐쇄 원칙에 따라 서비스, 컨트롤러, 매퍼가 서로에 종속되지 않고 81 | 스프링 빈을 통해 객체를 주입받아 객체들끼리 서로 종속되는 일이 없도록 하기 위해서입니다. 82 | 스프링 프레임워크에서 스프링 빈은 객체의 라이프 사이클을 관리하기 때문에 83 | 서비스 컨트롤러 매퍼 각각에서 객체를 생성하거나 다른객체에 의존하지 않고 스프링 프레임워크에 의해서 객체의 라이프사이클을 관리받아야 합니다. 84 | 이러한 원칙들을 지키려면 컨트롤러 서비스 매퍼가 각각의 책임과 할일이 분명하게 나눠져야 합니다. 85 | 86 | 컨트롤러에서 비즈니스 로직을 처리하기 위해 서비스를 불러오고 서비스에서 필요한 매퍼를 호출하여 87 | 디비에 접근하는 식의 계층이 분리된 모델을 사용한다면 중복된 코드도 제거할 수 있습니다. 88 | 89 | 컨트롤러에서 여러 서비스들을 재사용하기 용이하다. 90 | 컨트롤러에서는 서비스 객체들을 주입받아 사용합니다. 비즈니스 로직이 데이터를 저장하고 수정하고 없애는 동작들을 여러 컨트롤러에서 사용할 가능성이 있으니까 91 | 컨트롤러 내에서 처리하지 않고 서비스 객체들에게 위임해서 처리하고 컨트롤러는 처리된 결과를 받아 응답하는 것입니다. 92 | 93 | 이러한 원칙들을 지킨다면 뷰에도 종속되지 않아 웹이던 앱이던 해당 비즈니스 로직을 그대로 가져갈 수 있습니다. 94 | */ 95 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.3.2.RELEASE 10 | 11 | 12 | com.flab.makedel 13 | make-delivery 14 | 0.0.1-SNAPSHOT 15 | delivery-make 16 | delivery project for Spring Boot 17 | 18 | 19 | 1.8 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | 29 | org.mybatis.spring.boot 30 | mybatis-spring-boot-starter 31 | 2.1.3 32 | 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | true 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-jdbc 55 | 2.3.1.RELEASE 56 | 57 | 58 | 59 | mysql 60 | mysql-connector-java 61 | 8.0.20 62 | 63 | 64 | 65 | org.mindrot 66 | jbcrypt 67 | 0.4 68 | 69 | 70 | 71 | org.springframework.session 72 | spring-session-data-redis 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-data-redis 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-aop 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-starter-validation 88 | 89 | 90 | 91 | com.google.firebase 92 | firebase-admin 93 | 6.8.1 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.springframework.boot 102 | spring-boot-maven-plugin 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/OrderTransactionService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dao.CartItemDAO; 4 | import com.flab.makedel.dto.CartItemDTO; 5 | import com.flab.makedel.dto.CartOptionDTO; 6 | import com.flab.makedel.dto.OrderDTO; 7 | import com.flab.makedel.dto.OrderMenuDTO; 8 | import com.flab.makedel.dto.OrderMenuOptionDTO; 9 | import com.flab.makedel.dto.PayDTO.PayType; 10 | import com.flab.makedel.mapper.OrderMapper; 11 | import com.flab.makedel.mapper.OrderMenuMapper; 12 | import com.flab.makedel.mapper.OrderMenuOptionMapper; 13 | import com.flab.makedel.utils.PayServiceFactory; 14 | import java.util.List; 15 | import lombok.RequiredArgsConstructor; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | import org.springframework.transaction.support.TransactionSynchronizationAdapter; 19 | import org.springframework.transaction.support.TransactionSynchronizationManager; 20 | 21 | /* 22 | 굳이 OrderTransactionService를 새로 만들은 이유 : 23 | 우선 @Transactional은 AOP로 구현되어있어 RTW방식으로 프록시를 생성합니다. 24 | 따라서 같은 서비스 내에 있는 메소드를 호출할때는 Transactional은 새롭게 애노테이션이 적용되지 않습니다. 25 | 지금 현재는 주문과 결제가 같은 트랜잭션에서 처리되야 하므로 26 | 굳이 OrderTransactionService를 만들어서 트랜잭션을 분리할 필요는 없지만 27 | 추후에 주문과 결제가 같은 트랜잭션에서 처리되지 않게 변경하게 될 수도 있으므로 28 | Transaction 전파 레벨이 변경되어야 할 수도 있다고 판단하여 분리하였습니다. 29 | 전파레벨 설정을 기본값으로 해준 이유는 주문 트랜잭션안에서 30 | 주문,결제가 모두 이루어져 커밋이 같이되어야하고, 롤백또한 같이 되어야하기 때문입니다. 31 | 전파레벨 설정이 모두 기본값인 이유는 자식 트랜잭션이 부모 트랜잭션에 합류하여 32 | 같은 트랜잭션에서 커밋,롤백이 되어야 하는 로직이기 때문입니다. 33 | 주문과 결제가 동시에 이루어져야 합니다. 34 | */ 35 | 36 | @Service 37 | @RequiredArgsConstructor 38 | public class OrderTransactionService { 39 | 40 | private final OrderMapper orderMapper; 41 | private final OrderMenuMapper orderMenuMapper; 42 | private final OrderMenuOptionMapper orderMenuOptionMapper; 43 | private final PayServiceFactory payServiceFactory; 44 | 45 | @Transactional 46 | public long order(OrderDTO orderDTO, List cartList, 47 | List orderMenuList, List orderMenuOptionList) { 48 | 49 | orderMapper.insertOrder(orderDTO); 50 | long totalPrice = registerOrderMenu(cartList, orderDTO.getId(), orderMenuList, 51 | orderMenuOptionList); 52 | 53 | return totalPrice; 54 | } 55 | 56 | @Transactional 57 | public void pay(PayType payType, long totalPrice, long orderId) { 58 | 59 | PayService payService = payServiceFactory.getPayService(payType); 60 | payService.pay(totalPrice, orderId); 61 | 62 | } 63 | 64 | private long registerOrderMenu(List cartList, Long orderId, 65 | List orderMenuList, List orderMenuOptionList) { 66 | 67 | long totalPrice = 0; 68 | 69 | for (int i = 0; i < cartList.size(); i++) { 70 | CartItemDTO cart = cartList.get(i); 71 | totalPrice += cart.getPrice() * cart.getCount(); 72 | 73 | OrderMenuDTO orderMenuDTO = OrderMenuDTO.builder() 74 | .orderId(orderId) 75 | .menuId(cart.getMenuId()) 76 | .count(cart.getCount()) 77 | .build(); 78 | orderMenuList.add(orderMenuDTO); 79 | 80 | for (int j = 0; j < cart.getOptionList().size(); j++) { 81 | CartOptionDTO option = cart.getOptionList().get(j); 82 | totalPrice += option.getPrice(); 83 | 84 | OrderMenuOptionDTO orderMenuOptionDTO = OrderMenuOptionDTO.builder() 85 | .optionId(option.getOptionId()) 86 | .menuId(cart.getMenuId()) 87 | .orderId(orderId) 88 | .build(); 89 | orderMenuOptionList.add(orderMenuOptionDTO); 90 | } 91 | } 92 | 93 | orderMenuMapper.insertOrderMenu(orderMenuList); 94 | orderMenuOptionMapper.insertOrderMenuOption(orderMenuOptionList); 95 | 96 | return totalPrice; 97 | 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/StoreService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.Exception.StoreNameAlreadyExistException; 4 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 5 | import com.flab.makedel.dto.OrderReceiptDTO; 6 | import com.flab.makedel.dto.PushMessageDTO; 7 | import com.flab.makedel.dto.StoreDTO; 8 | import com.flab.makedel.mapper.OrderMapper; 9 | import com.flab.makedel.mapper.StoreMapper; 10 | import java.time.LocalDateTime; 11 | import java.util.List; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.cache.annotation.CacheEvict; 14 | import org.springframework.cache.annotation.Caching; 15 | import org.springframework.dao.DuplicateKeyException; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.transaction.annotation.Transactional; 19 | import org.springframework.web.client.HttpClientErrorException; 20 | 21 | @Service 22 | @RequiredArgsConstructor 23 | public class StoreService { 24 | 25 | private final StoreMapper storeMapper; 26 | private final OrderMapper orderMapper; 27 | private final DeliveryService deliveryService; 28 | private final RiderService riderService; 29 | 30 | 31 | @Caching(evict = { 32 | @CacheEvict(value = "stores", key = "#store.categoryId"), 33 | @CacheEvict(value = "stores", key = "#store.address"), 34 | @CacheEvict(value = "stores", key = "#store.address+#store.categoryId") 35 | }) 36 | public void insertStore(StoreDTO store, String ownerId) { 37 | try { 38 | StoreDTO newStore = setOwnerID(store, ownerId); 39 | storeMapper.insertStore(newStore); 40 | } catch (DuplicateKeyException e) { 41 | throw new StoreNameAlreadyExistException("Same Store Name" + store.getName()); 42 | } 43 | } 44 | 45 | private StoreDTO setOwnerID(StoreDTO store, String ownerId) { 46 | StoreDTO newStore = StoreDTO.builder() 47 | .name(store.getName()) 48 | .phone(store.getPhone()) 49 | .address(store.getAddress()) 50 | .ownerId(ownerId) 51 | .introduction(store.getIntroduction()) 52 | .categoryId(store.getCategoryId()) 53 | .build(); 54 | return newStore; 55 | } 56 | 57 | public List getMyAllStore(String ownerId) { 58 | return storeMapper.selectStoreList(ownerId); 59 | } 60 | 61 | public StoreDTO getMyStore(long storeId, String ownerId) { 62 | return storeMapper.selectStore(storeId, ownerId); 63 | } 64 | 65 | private boolean isMyStore(long storeId, String ownerId) { 66 | return storeMapper.isMyStore(storeId, ownerId); 67 | } 68 | 69 | public void closeMyStore(long storeId) { 70 | storeMapper.closeMyStore(storeId); 71 | } 72 | 73 | public void openMyStore(long storeId) { 74 | storeMapper.openMyStore(storeId); 75 | } 76 | 77 | public void validateMyStore(long storeId, String ownerId) throws HttpClientErrorException { 78 | boolean isMyStore = isMyStore(storeId, ownerId); 79 | if (!isMyStore) { 80 | throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED); 81 | } 82 | } 83 | 84 | @Transactional 85 | public void approveOrder(long orderId) { 86 | orderMapper.approveOrder(orderId, OrderStatus.APPROVED_ORDER); 87 | OrderReceiptDTO orderReceipt = orderMapper.selectOrderReceipt(orderId); 88 | deliveryService.registerStandbyOrderWhenOrderApprove(orderId, orderReceipt); 89 | riderService.sendMessageToStandbyRidersInSameArea(orderReceipt.getStoreInfo().getAddress(), 90 | getPushMessage(orderReceipt)); 91 | } 92 | 93 | private PushMessageDTO getPushMessage(OrderReceiptDTO orderReceipt) { 94 | return PushMessageDTO.builder() 95 | .title(PushMessageDTO.RIDER_MESSAGE_TITLE) 96 | .content(PushMessageDTO.RIDER_MESSAGE_TITLE) 97 | .createdAt(LocalDateTime.now().toString()) 98 | .orderReceipt(orderReceipt) 99 | .build(); 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dao/CartItemDAO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dao; 2 | 3 | import com.flab.makedel.dto.CartItemDTO; 4 | import java.util.List; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.dao.DataAccessException; 7 | import org.springframework.data.redis.core.RedisCallback; 8 | import org.springframework.data.redis.core.RedisOperations; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.data.redis.core.SessionCallback; 11 | import org.springframework.data.redis.serializer.RedisSerializer; 12 | import org.springframework.stereotype.Repository; 13 | 14 | /* 15 | 레디스에서의 트랜잭션은 RDB의 트랜잭션과는 다르다. 레디스의 트랜잭션은 rollback기능이 없다. 16 | 레디스 트랜잭션에서의 오류는 잘못된 명령어나 데이터타입을 실수로 쓸 때만 나기 때문에 롤백을 전부다 하지않고 17 | exec 후에 오류가 나지 않은 부분은 실행된다. 18 | exec 이전에 command queue에 적재하는 도중 실패하는 경우 (command 문법오류,메모리 부족오류등, 19 | 다른 클라이언트에서 command날려 atomic보장이 안되는 경우) 에는 exec하면 전부 discard된다. 20 | (실험해보니 multi 후 트랜잭션중 다른 스레드에서 command를 날리면 discard된다.) 21 | (레디스 2.6.5이후로 트랜잭션시작 후 오류가 있으면 exec될 때 전부 discard된다.) 22 | 트랜잭션 명령어들은 exec되기 위해 큐에서 기다리는데 discard를 이용해 실행을 하지 않을 수 있다. 23 | 트랜잭션의 locking은 watch를 이용한 optimistic locking이다. watch로 어떠한 키를 감시하고 24 | 이 키의 트랜잭션이 multi로 시작되기전에 watch할 때의 값과 multi할 때의 값이 변경이 없어야지 25 | 트랜잭션이 시작할 수 있다. 만약에 이 값이 변경이 되었다면 race condition이 일어난 것이기 때문에 26 | 트랜잭션 에러가 난다. 27 | */ 28 | 29 | @Repository 30 | @RequiredArgsConstructor 31 | public class CartItemDAO { 32 | 33 | private final RedisTemplate redisTemplate; 34 | private static final String cartKey = ":CART"; 35 | 36 | public static String generateCartKey(String id) { 37 | return id + cartKey; 38 | } 39 | 40 | public List selectCartList(String userId) { 41 | 42 | final String key = generateCartKey(userId); 43 | 44 | List cartList = redisTemplate 45 | .opsForList() 46 | .range(key, 0, -1); 47 | 48 | return cartList; 49 | 50 | } 51 | 52 | public List getCartAndDelete(String userId) { 53 | 54 | final String key = generateCartKey(userId); 55 | 56 | List cartListObject = redisTemplate.execute( 57 | new SessionCallback>() { 58 | @Override 59 | public List execute(RedisOperations redisOperations) 60 | throws DataAccessException { 61 | try { 62 | redisOperations.watch(key); 63 | redisOperations.multi(); 64 | redisOperations.opsForList().range(key, 0, -1); 65 | redisOperations.delete(key); 66 | return redisOperations.exec(); 67 | } catch (Exception exception) { 68 | redisOperations.discard(); 69 | throw exception; 70 | } 71 | 72 | } 73 | } 74 | ); 75 | 76 | List cartList = (List) cartListObject.get(0); 77 | 78 | return cartList; 79 | } 80 | 81 | /* 82 | 레디스 서버에 반복문을 돌며 여러번 리스트의 원소를 push한다면 RTT때문에 오버헤드가 생깁니다. 83 | 따라서 레디스 서버에 요청을 보낼때 한번에 여러 원소들을 보내야합니다. 84 | MySQL에서는 이러한 기능을 위해 bulk insert를 지원하지 85 | 레디스에서는 bulk(다중) insert가 따로 존재하지 않습니다. 86 | 따라서 레디스에서 지원해주는 pipeline api인 executePipelined 메소드를 이용해 87 | 레디스에 연결을 한후 모든 원소들을 push한 뒤 연결을 닫습니다. 88 | */ 89 | 90 | public void insertMenuList(String userId, List cartList) { 91 | final String key = generateCartKey(userId); 92 | 93 | RedisSerializer keySerializer = redisTemplate.getStringSerializer(); 94 | RedisSerializer valueSerializer = redisTemplate.getValueSerializer(); 95 | 96 | redisTemplate.executePipelined((RedisCallback) RedisConnection -> { 97 | cartList.forEach(cart -> { 98 | RedisConnection.rPush(keySerializer.serialize(key), 99 | valueSerializer.serialize(cart)); 100 | }); 101 | return null; 102 | }); 103 | } 104 | 105 | public void insertMenu(String userId, CartItemDTO cart) { 106 | final String key = generateCartKey(userId); 107 | redisTemplate.opsForList().rightPush(key, cart); 108 | } 109 | 110 | public void deleteMenuList(String userId) { 111 | final String key = generateCartKey(userId); 112 | redisTemplate.delete(key); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import com.flab.makedel.dao.CartItemDAO; 4 | import com.flab.makedel.dto.CartItemDTO; 5 | import com.flab.makedel.dto.OrderDTO; 6 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 7 | import com.flab.makedel.dto.OrderDetailDTO; 8 | import com.flab.makedel.dto.OrderMenuDTO; 9 | import com.flab.makedel.dto.OrderMenuOptionDTO; 10 | import com.flab.makedel.dto.OrderReceiptDTO; 11 | import com.flab.makedel.dto.OrderStoreDetailDTO; 12 | import com.flab.makedel.dto.StoreInfoDTO; 13 | import com.flab.makedel.dto.PayDTO.PayType; 14 | import com.flab.makedel.dto.UserInfoDTO; 15 | import com.flab.makedel.mapper.OrderMapper; 16 | import com.flab.makedel.mapper.StoreMapper; 17 | import com.flab.makedel.mapper.UserMapper; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import lombok.RequiredArgsConstructor; 21 | import org.springframework.stereotype.Service; 22 | import org.springframework.transaction.annotation.Transactional; 23 | import org.springframework.transaction.support.TransactionSynchronizationAdapter; 24 | import org.springframework.transaction.support.TransactionSynchronizationManager; 25 | 26 | @Service 27 | @RequiredArgsConstructor 28 | public class OrderService { 29 | 30 | private final OrderMapper orderMapper; 31 | private final UserMapper userMapper; 32 | private final OrderTransactionService orderTransactionService; 33 | private final CartItemDAO cartItemDAO; 34 | private final StoreMapper storeMapper; 35 | 36 | @Transactional 37 | public OrderReceiptDTO registerOrder(String userId, long storeId, PayType payType) { 38 | 39 | UserInfoDTO user = userMapper.selectUserInfo(userId); 40 | OrderDTO orderDTO = getOrderDTO(user, storeId); 41 | List cartList; 42 | List orderMenuList = new ArrayList<>(); 43 | List orderMenuOptionList = new ArrayList<>(); 44 | OrderReceiptDTO orderReceipt; 45 | 46 | cartList = cartItemDAO.getCartAndDelete(userId); 47 | 48 | restoreCartListOnOrderRollback(userId, cartList); 49 | 50 | long totalPrice = orderTransactionService 51 | .order(orderDTO, cartList, orderMenuList, orderMenuOptionList); 52 | orderTransactionService.pay(payType, totalPrice, orderDTO.getId()); 53 | orderMapper.completeOrder(totalPrice, orderDTO.getId(), OrderStatus.COMPLETE_ORDER); 54 | orderReceipt = getOrderReceipt(orderDTO, cartList, totalPrice, storeId, 55 | user); 56 | 57 | return orderReceipt; 58 | } 59 | 60 | private void restoreCartListOnOrderRollback(String userId, List cartList) { 61 | TransactionSynchronizationManager.registerSynchronization( 62 | new TransactionSynchronizationAdapter() { 63 | @Override 64 | public void afterCompletion(int status) { 65 | if (status == STATUS_ROLLED_BACK) { 66 | cartItemDAO.insertMenuList(userId, cartList); 67 | } 68 | } 69 | }); 70 | } 71 | 72 | private OrderDTO getOrderDTO(UserInfoDTO userInfo, long storeId) { 73 | OrderDTO orderDTO = OrderDTO.builder() 74 | .address(userInfo.getAddress()) 75 | .userId(userInfo.getId()) 76 | .orderStatus(OrderStatus.BEFORE_ORDER) 77 | .storeId(storeId) 78 | .build(); 79 | return orderDTO; 80 | } 81 | 82 | private OrderReceiptDTO getOrderReceipt(OrderDTO orderDTO, List cartList, 83 | long totalPrice, long storeId, UserInfoDTO userInfo) { 84 | 85 | StoreInfoDTO storeInfo = storeMapper.selectStoreInfo(storeId); 86 | return OrderReceiptDTO.builder() 87 | .orderId(orderDTO.getId()) 88 | .orderStatus(OrderStatus.COMPLETE_ORDER.toString()) 89 | .userInfo(userInfo) 90 | .totalPrice(totalPrice) 91 | .storeInfo(storeInfo) 92 | .cartList(cartList) 93 | .build(); 94 | 95 | } 96 | 97 | public OrderReceiptDTO getOrderInfoByOrderId(long orderId) { 98 | OrderReceiptDTO orderReceipt = orderMapper.selectOrderReceipt(orderId); 99 | return orderReceipt; 100 | } 101 | 102 | public List getStoreOrderInfoByStoreId(long storeId) { 103 | List storeOrderDetailList = orderMapper 104 | .selectDetailStoreOrder(storeId); 105 | return storeOrderDetailList; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/flab/makedel/service/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import static org.mockito.Matchers.*; 4 | import static org.junit.jupiter.api.Assertions.*; 5 | import static org.mockito.Mockito.doNothing; 6 | import static org.mockito.Mockito.verify; 7 | import static org.mockito.Mockito.when; 8 | 9 | import com.flab.makedel.annotation.LoginCheck.UserLevel; 10 | import com.flab.makedel.dto.UserDTO; 11 | import com.flab.makedel.mapper.UserMapper; 12 | import com.flab.makedel.utils.PasswordEncrypter; 13 | import java.util.Optional; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.DisplayName; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.extension.ExtendWith; 18 | import org.mockito.InjectMocks; 19 | import org.mockito.Mock; 20 | import org.mockito.junit.jupiter.MockitoExtension; 21 | 22 | @ExtendWith(MockitoExtension.class) 23 | class UserServiceTest { 24 | 25 | @Mock 26 | UserMapper userMapper; 27 | 28 | @InjectMocks 29 | UserService userService; 30 | 31 | UserDTO user; 32 | 33 | @BeforeEach 34 | public void makeUser() { 35 | user = UserDTO.builder() 36 | .id("user") 37 | .password(PasswordEncrypter.encrypt("123")) 38 | .email("tjdrnr05571@naver.com") 39 | .name("이성국") 40 | .phone("010-1234-1234") 41 | .address("서울시") 42 | .level(UserLevel.USER) 43 | .build(); 44 | } 45 | 46 | @Test 47 | @DisplayName("회원가입에 성공합니다") 48 | public void signUpTestWhenSuccess() { 49 | when(userMapper.isExistsId(user.getId())).thenReturn(false); 50 | doNothing().when(userMapper).insertUser(any(UserDTO.class)); 51 | 52 | userService.signUp(user); 53 | 54 | verify(userMapper).insertUser(any(UserDTO.class)); 55 | } 56 | 57 | @Test 58 | @DisplayName("회원가입에 실패합니다 : 중복된 아이디") 59 | public void signUpTestWhenFail() { 60 | when(userMapper.isExistsId(user.getId())).thenReturn(true); 61 | 62 | assertThrows(RuntimeException.class, () -> userService.signUp(user)); 63 | 64 | verify(userMapper).isExistsId(user.getId()); 65 | } 66 | 67 | @Test 68 | @DisplayName("중복된 아이디일 경우 참을 Return합니다") 69 | public void isExistsIdTestWhenReturnTrue() { 70 | when(userMapper.isExistsId(user.getId())).thenReturn(true); 71 | 72 | assertEquals(userService.isExistsId(user.getId()), true); 73 | 74 | verify(userMapper).isExistsId(user.getId()); 75 | } 76 | 77 | @Test 78 | @DisplayName("중복된 아이디일 경우 거짓을 Return합니다") 79 | public void isExistsIdTestWhenReturnFalse() { 80 | when(userMapper.isExistsId(user.getId())).thenReturn(false); 81 | 82 | assertEquals(userService.isExistsId(user.getId()), false); 83 | 84 | verify(userMapper).isExistsId(user.getId()); 85 | } 86 | 87 | @Test 88 | @DisplayName("유저 삭제합니다") 89 | public void deleteUserTestWhenSuccess() { 90 | when(userMapper.isExistsId(user.getId())).thenReturn(true); 91 | doNothing().when(userMapper).deleteUser(user.getId()); 92 | 93 | userService.deleteUser(user.getId()); 94 | 95 | verify(userMapper).deleteUser(user.getId()); 96 | } 97 | 98 | @Test 99 | @DisplayName("유저 삭제에 실패합니다 : 삭제할 아이디 존재하지 않음") 100 | public void deleteUserTestWhenFail() { 101 | when(userMapper.isExistsId(user.getId())).thenReturn(false); 102 | 103 | assertThrows(RuntimeException.class, () -> userService.deleteUser(user.getId())); 104 | 105 | verify(userMapper).isExistsId(user.getId()); 106 | } 107 | 108 | @Test 109 | @DisplayName("유저 비밀번호를 변경합니다") 110 | public void changeUserPasswordTestWhenSuccess() { 111 | doNothing().when(userMapper).updateUserPassword(any(String.class), any(String.class)); 112 | 113 | userService.changeUserPassword(user.getId(), "123"); 114 | 115 | verify(userMapper).updateUserPassword(any(String.class), any(String.class)); 116 | } 117 | 118 | @Test 119 | @DisplayName("아이디와 비밀번호로 유저를 찾습니다") 120 | public void findUserByIdAndPasswordTestWhenSuccess() { 121 | when(userMapper.selectUserById(user.getId())).thenReturn(user); 122 | 123 | assertEquals(userService.findUserByIdAndPassword(user.getId(), "123"), 124 | Optional.ofNullable(user)); 125 | 126 | verify(userMapper).selectUserById(user.getId()); 127 | } 128 | 129 | @Test 130 | @DisplayName("아이디와 비밀번호로 유저 찾기에 실패합니다 : 주어진 유저 아이디 존재하지 않음") 131 | public void findUserByIdAndPasswordTestWhenFailBecauseNotExistId() { 132 | when(userMapper.selectUserById(user.getId())).thenReturn(null); 133 | 134 | assertEquals(userService.findUserByIdAndPassword(user.getId(), user.getPassword()), 135 | Optional.empty()); 136 | 137 | verify(userMapper).selectUserById(any(String.class)); 138 | } 139 | 140 | @Test 141 | @DisplayName("아이디와 비밀번호로 유저 찾기에 실패합니다 : 주어진 유저 비밀번호 오류") 142 | public void findUserByIdAndPasswordTestWhenFailBecausePasswordError() { 143 | when(userMapper.selectUserById(user.getId())).thenReturn(user); 144 | 145 | assertEquals(userService.findUserByIdAndPassword(user.getId(), "not same password"), 146 | Optional.empty()); 147 | 148 | verify(userMapper).selectUserById(any(String.class)); 149 | } 150 | 151 | } -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/dao/DeliveryDAO.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.dao; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.flab.makedel.dto.OrderReceiptDTO; 5 | import com.flab.makedel.dto.RiderDTO; 6 | import java.util.ArrayList; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Map.Entry; 10 | import java.util.Set; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.beans.factory.annotation.Qualifier; 13 | import org.springframework.dao.DataAccessException; 14 | import org.springframework.data.redis.connection.RedisConnection; 15 | import org.springframework.data.redis.core.Cursor; 16 | import org.springframework.data.redis.core.RedisCallback; 17 | import org.springframework.data.redis.core.RedisOperations; 18 | import org.springframework.data.redis.core.RedisTemplate; 19 | import org.springframework.data.redis.core.ScanOptions; 20 | import org.springframework.data.redis.core.SessionCallback; 21 | import org.springframework.stereotype.Repository; 22 | 23 | @Repository 24 | @RequiredArgsConstructor 25 | public class DeliveryDAO { 26 | 27 | @Qualifier("deliveryRedisTemplate") 28 | private final RedisTemplate redisTemplate; 29 | private static final String STANDBY_ORDERS_KEY = ":STANDBY_ORDERS"; 30 | private static final String STANDBY_RIDERS_KEY = ":STANDBY_RIDERS"; 31 | 32 | private static String generateOrderHashKey(long orderId) { 33 | return String.valueOf(orderId); 34 | } 35 | 36 | private static String generateStandbyRiderKey(String address) { 37 | return address + STANDBY_RIDERS_KEY; 38 | } 39 | 40 | private static String generateStandbyOrderKey(String address) { 41 | return address + STANDBY_ORDERS_KEY; 42 | } 43 | 44 | public void insertStandbyRiderWhenStartWork(RiderDTO rider) { 45 | redisTemplate.opsForHash() 46 | .put(generateStandbyRiderKey(rider.getAddress()), rider.getId(), rider.getFcmToken()); 47 | } 48 | 49 | public void deleteStandbyRiderWhenStopWork(RiderDTO rider) { 50 | redisTemplate.opsForHash() 51 | .delete(generateStandbyRiderKey(rider.getAddress()), rider.getId()); 52 | } 53 | 54 | /* 55 | 레디스의 명령어중 keys를 이용하면 모든 키값들을 가져올 수 있지만 이키값들을 가져오는동안 56 | lock이 걸립니다. 따라서 성능에 영향을 줄 수 있어 레디스에서는 scan,hscan을 권장합니다. 57 | 레디스는 싱글스레드로 동작하기 때문에 이처럼 어떤 명령어를 O(n)시간 동안 수행하면서 lock이 58 | 걸린다면 그시간동안 keys 명령어를 수행하기 위해 blocking이 되기 때문입니다. 59 | 60 | scan을 시작 한 후 데이터가 추가 된다면 전체 순회가 끝날때까지 추가된 값은 전체순회에 61 | 포함되지 않습니다. 62 | 63 | selectStandbyRiderTokenList : 같은 지역의 출근한 라이더들에게 푸쉬메세지를 보내기 64 | 위해 같은 지역의 출근한 라이더들의 fcm 토큰 값을 전체 스캔하는 함수입니다. 65 | */ 66 | 67 | public Set selectStandbyRiderTokenList(String address) { 68 | Set result = new HashSet<>(); 69 | 70 | redisTemplate.execute(new RedisCallback>() { 71 | @Override 72 | public Set doInRedis(RedisConnection redisConnection) 73 | throws DataAccessException { 74 | 75 | ScanOptions options = ScanOptions.scanOptions().match("*").count(200).build(); 76 | Cursor> entries = redisConnection 77 | .hScan(generateStandbyRiderKey(address).getBytes(), options); 78 | 79 | while (entries.hasNext()) { 80 | Entry entry = entries.next(); 81 | byte[] actualValue = entry.getValue(); 82 | result.add(new String(actualValue)); 83 | } 84 | return result; 85 | } 86 | }); 87 | 88 | return result; 89 | } 90 | 91 | public void insertStandbyOrderWhenOrderApprove(long orderId, OrderReceiptDTO orderReceipt) { 92 | redisTemplate.opsForHash() 93 | .put(generateStandbyOrderKey(orderReceipt.getUserInfo().getAddress()), 94 | generateOrderHashKey(orderId), orderReceipt); 95 | } 96 | 97 | public OrderReceiptDTO selectStandbyOrder(long orderId, String riderAddress) { 98 | return (OrderReceiptDTO) redisTemplate.opsForHash() 99 | .get(generateStandbyOrderKey(riderAddress), generateOrderHashKey(orderId)); 100 | } 101 | 102 | /* 103 | selectStandbyOrderList 104 | 라이더들이 자신의 지역에 배차요청을 기다리는 주문목록을 보는 메소드입니다. 105 | */ 106 | 107 | public List selectStandbyOrderList(String riderAddress) { 108 | 109 | List result = new ArrayList<>(); 110 | redisTemplate.execute(new RedisCallback>() { 111 | @Override 112 | public List doInRedis(RedisConnection redisConnection) 113 | throws DataAccessException { 114 | 115 | ScanOptions options = ScanOptions.scanOptions().match("*").count(200).build(); 116 | Cursor> entries = redisConnection 117 | .hScan(generateStandbyOrderKey(riderAddress).getBytes(), options); 118 | 119 | while (entries.hasNext()) { 120 | Entry entry = entries.next(); 121 | byte[] actualKey = entry.getKey(); 122 | result.add(new String(actualKey)); 123 | } 124 | 125 | return result; 126 | } 127 | }); 128 | return result; 129 | } 130 | 131 | /* 132 | updateStandbyOrderToDelivering 133 | 라이더들이 배차를 요청하여 배달을 시작할 때 사용하는 메소드입니다. 134 | */ 135 | 136 | public void updateStandbyOrderToDelivering(long orderId, RiderDTO rider) { 137 | String standbyRidersKey = generateStandbyRiderKey(rider.getAddress()); 138 | String standbyOrdersKey = generateStandbyOrderKey(rider.getAddress()); 139 | String orderHashKey = generateOrderHashKey(orderId); 140 | 141 | redisTemplate.execute(new SessionCallback() { 142 | @Override 143 | public Object execute(RedisOperations redisOperations) 144 | throws DataAccessException { 145 | 146 | redisOperations.watch(standbyOrdersKey); 147 | redisOperations.watch(standbyRidersKey); 148 | 149 | redisOperations.multi(); 150 | 151 | redisOperations.opsForHash() 152 | .delete(standbyOrdersKey, orderHashKey); 153 | redisOperations.opsForHash() 154 | .delete(standbyRidersKey, rider.getId()); 155 | 156 | return redisOperations.exec(); 157 | } 158 | }); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/test/java/com/flab/makedel/service/StoreServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | import static org.mockito.Mockito.doThrow; 5 | import static org.mockito.Mockito.verify; 6 | import static org.mockito.Mockito.doNothing; 7 | import static org.mockito.Mockito.when; 8 | import static org.mockito.Matchers.*; 9 | 10 | import com.flab.makedel.dao.DeliveryDAO; 11 | import com.flab.makedel.dto.CartItemDTO; 12 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 13 | import com.flab.makedel.dto.OrderReceiptDTO; 14 | import com.flab.makedel.dto.PushMessageDTO; 15 | import com.flab.makedel.dto.StoreDTO; 16 | import com.flab.makedel.dto.StoreInfoDTO; 17 | import com.flab.makedel.dto.UserInfoDTO; 18 | import com.flab.makedel.mapper.OrderMapper; 19 | import com.flab.makedel.mapper.StoreMapper; 20 | import com.google.firebase.auth.UserInfo; 21 | import java.util.ArrayList; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.DisplayName; 27 | import org.junit.jupiter.api.Test; 28 | import org.junit.jupiter.api.extension.ExtendWith; 29 | import org.mockito.InjectMocks; 30 | import org.mockito.Mock; 31 | import org.mockito.junit.jupiter.MockitoExtension; 32 | import org.springframework.web.client.HttpClientErrorException; 33 | 34 | @ExtendWith(MockitoExtension.class) 35 | public class StoreServiceTest { 36 | 37 | @Mock 38 | StoreMapper storeMapper; 39 | 40 | @Mock 41 | OrderMapper orderMapper; 42 | 43 | @Mock 44 | DeliveryService deliveryService; 45 | 46 | @Mock 47 | RiderService riderService; 48 | 49 | @InjectMocks 50 | StoreService storeService; 51 | 52 | StoreDTO store; 53 | 54 | @BeforeEach 55 | public void makeStore() { 56 | store = StoreDTO.builder() 57 | .name("60계 치킨") 58 | .phone("031-921-0101") 59 | .address("경기도 고양시 일산동") 60 | .introduction("고추 치킨이 맛있습니다") 61 | .categoryId(1L) 62 | .build(); 63 | } 64 | 65 | @Test 66 | @DisplayName("가게 생성에 성공합니다") 67 | public void insertStoreTestSuccess() { 68 | doNothing().when(storeMapper).insertStore(any(StoreDTO.class)); 69 | 70 | storeService.insertStore(store, "60owners"); 71 | 72 | verify(storeMapper).insertStore(any(StoreDTO.class)); 73 | } 74 | 75 | @Test 76 | @DisplayName("가게 생성에 실패합니다 : 중복된 가게 이름") 77 | public void insertStoreTestFailBecauseSameStoreName() { 78 | doThrow(RuntimeException.class).when(storeMapper).insertStore(any(StoreDTO.class)); 79 | 80 | assertThrows(RuntimeException.class, 81 | () -> storeService.insertStore(store, "60owners")); 82 | 83 | verify(storeMapper).insertStore(any(StoreDTO.class)); 84 | } 85 | 86 | @Test 87 | @DisplayName("사장이 내 가게 전체 목록을 조회하는데 성공합니다") 88 | public void getMyAllStoreTestSuccess() { 89 | when(storeMapper.selectStoreList(any(String.class))) 90 | .thenReturn(anyList()); 91 | 92 | storeService.getMyAllStore("owner"); 93 | 94 | verify(storeMapper).selectStoreList(any(String.class)); 95 | } 96 | 97 | @Test 98 | @DisplayName("가게를 소유하지 않은 사장이 자신의 가게 목록을 조회하면 빈 List를 리턴합니다") 99 | public void getMyAllStoreTestReturnEmptyArray() { 100 | List tempList = new ArrayList<>(); 101 | when(storeMapper.selectStoreList(any(String.class))) 102 | .thenReturn(tempList); 103 | 104 | storeService.getMyAllStore("owner"); 105 | 106 | verify(storeMapper).selectStoreList(any(String.class)); 107 | } 108 | 109 | @Test 110 | @DisplayName("사장이 내 특정 가게 목록을 조회하는데 성공합니다") 111 | public void getMyStoreTestSuccess() { 112 | when(storeMapper.selectStore(anyLong(), any(String.class))) 113 | .thenReturn(store); 114 | 115 | storeService.getMyStore(43, "owner"); 116 | 117 | verify(storeMapper).selectStore(anyLong(), any(String.class)); 118 | } 119 | 120 | 121 | @Test 122 | @DisplayName("자신의 가게가 맞는지 확인하는데 성공합니다.") 123 | public void validateMyStoreTestSuccess() { 124 | when(storeMapper.isMyStore(anyLong(), any(String.class))).thenReturn(true); 125 | 126 | storeService.validateMyStore(3, "owner"); 127 | 128 | verify(storeMapper).isMyStore(anyLong(), any(String.class)); 129 | } 130 | 131 | @Test 132 | @DisplayName("자신의 가게가 맞는지 확인하는데 실패합니다 : 본인 소유의 가게가 아님") 133 | public void validateMyStoreTestFail() { 134 | when(storeMapper.isMyStore(anyLong(), any(String.class))).thenReturn(false); 135 | 136 | assertThrows(HttpClientErrorException.class, 137 | () -> storeService.validateMyStore(3, "owner1")); 138 | 139 | verify(storeMapper).isMyStore(anyLong(), any(String.class)); 140 | } 141 | 142 | @Test 143 | @DisplayName("사장이 주문을 승인하는데 성공합니다") 144 | public void approveOrderTestSuccess() { 145 | 146 | UserInfoDTO userInfo = UserInfoDTO.builder() 147 | .id("사용자1") 148 | .name("이성국") 149 | .phone("010-1212-1212") 150 | .address("경기도 고양시 일산동") 151 | .build(); 152 | 153 | StoreInfoDTO storeInfo = StoreInfoDTO.builder() 154 | .storeId(1L) 155 | .name("bbq치킨") 156 | .phone("010-1234-1234") 157 | .address("경기도 고양시 일산동") 158 | .build(); 159 | 160 | List cartList = new ArrayList<>(); 161 | 162 | OrderReceiptDTO orderReceiptDTO = OrderReceiptDTO.builder() 163 | .orderId(1L) 164 | .orderStatus("COMPLETE_ORDER") 165 | .userInfo(userInfo) 166 | .storeInfo(storeInfo) 167 | .totalPrice(123000L) 168 | .cartList(cartList) 169 | .build(); 170 | 171 | doNothing().when(orderMapper).approveOrder(anyLong(), any(OrderStatus.class)); 172 | when(orderMapper.selectOrderReceipt(anyLong())).thenReturn(orderReceiptDTO); 173 | doNothing().when(deliveryService) 174 | .registerStandbyOrderWhenOrderApprove(anyLong(), any(OrderReceiptDTO.class)); 175 | doNothing().when(riderService) 176 | .sendMessageToStandbyRidersInSameArea(any(String.class), any(PushMessageDTO.class)); 177 | 178 | storeService.approveOrder(1); 179 | 180 | verify(orderMapper).approveOrder(anyLong(), any(OrderStatus.class)); 181 | verify(orderMapper).selectOrderReceipt(anyLong()); 182 | verify(deliveryService) 183 | .registerStandbyOrderWhenOrderApprove(anyLong(), any(OrderReceiptDTO.class)); 184 | verify(riderService) 185 | .sendMessageToStandbyRidersInSameArea(any(String.class), any(PushMessageDTO.class)); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /src/main/java/com/flab/makedel/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.config; 2 | 3 | 4 | import com.flab.makedel.dto.CartItemDTO; 5 | import com.flab.makedel.dto.OrderDetailDTO; 6 | import com.flab.makedel.dto.OrderReceiptDTO; 7 | import com.flab.makedel.dto.RiderDTO; 8 | import java.time.Duration; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Primary; 17 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 18 | import org.springframework.data.redis.cache.RedisCacheManager; 19 | import org.springframework.data.redis.connection.RedisConnectionFactory; 20 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 21 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 22 | import org.springframework.data.redis.core.RedisTemplate; 23 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 24 | import org.springframework.data.redis.serializer.RedisSerializationContext; 25 | import org.springframework.data.redis.serializer.StringRedisSerializer; 26 | import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory; 27 | import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; 28 | /* 29 | @Value : Spring이 지원하는 의존성 주입 방법중 하나입니다. 30 | SpEL을 지원하며 application.properties의 속성값을 프로퍼티에 넣어줍니다. 31 | 32 | RedisConnectionFactory을 생성하여 스프링 세션을 레디스 서버로 연결시킵니다. 33 | RedisConnectionFactory는 Connection 객체를 생성하여 관리하는 인터페이스입니다. 34 | RedisConnection을 리턴합니다. RedisConnection은 Redis 서버와의 통신을 추상화합니다. 35 | 36 | Redis Connection Factory를 리턴해주는 라이브러리로 Lettuce를 선택하였습니다. 37 | 비동기로 요청하기 때문에 높은성능을 가지기 때문입니다. 38 | Lettuce는 Netty기반이며 Netty는 비동기 네트워크 프레임워크입니다. 39 | Netty는 Channel에서 발생하는 이벤트들을 EventLoop로 비동기 처리하는 구조입니다. 40 | 41 | RedisCacheManager: Cache Manager는 스프링에서 추상화한 인터페이스고 이를 레디스 방식으로 42 | 구현한 것이 RedisCacheManager입니다. serializeKeysWith, serializeValueWith로 43 | 캐시 Key와 Value를 직렬화,역직렬화 할때 설정을 해줍니다. 44 | Value에는 GenericJackson2JsonRedisSerializer를 사용했는데 이 Serializer는 45 | 별도로 Class Type을 지정해줄 필요없이 자동으로 Object를 Json으로 직렬화 해줍니다. 46 | 단점으로는 Object의 Class Type을 레디스에 함꼐 넣기 때문에 47 | 데이터를 꺼내올 때 그 클래스타입으로만 가져올 수 있습니다. 48 | */ 49 | 50 | @Configuration 51 | public class RedisConfig { 52 | 53 | @Value("${spring.redis.host}") 54 | private String redisHost; 55 | 56 | @Value("${spring.redis.session.port}") 57 | private int redisSessionPort; 58 | 59 | @Value("${spring.redis.cache.port}") 60 | private int redisCachePort; 61 | 62 | @Value("${spring.redis.cart.port}") 63 | private int redisCartPort; 64 | 65 | @Value("${spring.redis.rider.port}") 66 | private int redisDeliveryPort; 67 | 68 | @Value("${spring.redis.password}") 69 | private String redisPassword; 70 | 71 | 72 | @Bean({"redisConnectionFactory", "redisSessionConnectionFactory"}) 73 | public RedisConnectionFactory redisSessionConnectionFactory() { 74 | RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); 75 | redisStandaloneConfiguration.setHostName(redisHost); 76 | redisStandaloneConfiguration.setPort(redisSessionPort); 77 | redisStandaloneConfiguration.setPassword(redisPassword); 78 | LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory( 79 | redisStandaloneConfiguration); 80 | 81 | return lettuceConnectionFactory; 82 | } 83 | 84 | @Bean 85 | public RedisConnectionFactory redisCacheConnectionFactory() { 86 | 87 | RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); 88 | redisStandaloneConfiguration.setHostName(redisHost); 89 | redisStandaloneConfiguration.setPort(redisCachePort); 90 | redisStandaloneConfiguration.setPassword(redisPassword); 91 | LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory( 92 | redisStandaloneConfiguration); 93 | 94 | return lettuceConnectionFactory; 95 | } 96 | 97 | @Bean 98 | public RedisConnectionFactory redisCartConnectionFactory() { 99 | 100 | RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); 101 | redisStandaloneConfiguration.setHostName(redisHost); 102 | redisStandaloneConfiguration.setPort(redisCartPort); 103 | redisStandaloneConfiguration.setPassword(redisPassword); 104 | LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory( 105 | redisStandaloneConfiguration); 106 | 107 | return lettuceConnectionFactory; 108 | } 109 | 110 | @Bean 111 | public RedisConnectionFactory redisDeliveryConnectionFactory() { 112 | 113 | RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); 114 | redisStandaloneConfiguration.setHostName(redisHost); 115 | redisStandaloneConfiguration.setPort(redisDeliveryPort); 116 | redisStandaloneConfiguration.setPassword(redisPassword); 117 | LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory( 118 | redisStandaloneConfiguration); 119 | 120 | return lettuceConnectionFactory; 121 | } 122 | 123 | @Bean 124 | public RedisTemplate cartItemDTORedisTemplate() { 125 | GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = 126 | new GenericJackson2JsonRedisSerializer(); 127 | 128 | RedisTemplate redisTemplate = new RedisTemplate<>(); 129 | 130 | redisTemplate.setConnectionFactory(redisCartConnectionFactory()); 131 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 132 | redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer); 133 | 134 | return redisTemplate; 135 | } 136 | 137 | @Bean 138 | @Qualifier("deliveryRedisTemplate") 139 | public RedisTemplate deliveryRedisTemplate() { 140 | GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = 141 | new GenericJackson2JsonRedisSerializer(); 142 | 143 | RedisTemplate redisTemplate = new RedisTemplate<>(); 144 | 145 | redisTemplate.setConnectionFactory(redisDeliveryConnectionFactory()); 146 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 147 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 148 | redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer); 149 | redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer); 150 | 151 | return redisTemplate; 152 | } 153 | 154 | @Bean 155 | public RedisCacheManager redisCacheManager() { 156 | 157 | RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration 158 | .defaultCacheConfig() 159 | .disableCachingNullValues() 160 | .serializeKeysWith( 161 | RedisSerializationContext.SerializationPair 162 | .fromSerializer(new StringRedisSerializer())) 163 | .serializeValuesWith( 164 | RedisSerializationContext.SerializationPair 165 | .fromSerializer(new GenericJackson2JsonRedisSerializer()) 166 | ) 167 | .entryTtl(Duration.ofDays(1L)); 168 | 169 | return RedisCacheManager.RedisCacheManagerBuilder 170 | .fromConnectionFactory(redisCacheConnectionFactory()) 171 | .cacheDefaults(redisCacheConfiguration) 172 | .build(); 173 | } 174 | } -------------------------------------------------------------------------------- /src/test/java/com/flab/makedel/service/RiderServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.flab.makedel.service; 2 | 3 | import static org.mockito.ArgumentMatchers.any; 4 | import static org.mockito.ArgumentMatchers.anyList; 5 | import static org.mockito.ArgumentMatchers.anyLong; 6 | import static org.mockito.Mockito.doThrow; 7 | import static org.mockito.Mockito.verify; 8 | import static org.mockito.Mockito.doNothing; 9 | import static org.mockito.Mockito.when; 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | import com.flab.makedel.dao.DeliveryDAO; 13 | import com.flab.makedel.dto.OrderDTO.OrderStatus; 14 | import com.flab.makedel.dto.RiderDTO; 15 | import com.flab.makedel.mapper.OrderMapper; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.DisplayName; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.api.extension.ExtendWith; 20 | import org.mockito.InjectMocks; 21 | import org.mockito.Mock; 22 | import org.mockito.junit.jupiter.MockitoExtension; 23 | 24 | @ExtendWith(MockitoExtension.class) 25 | public class RiderServiceTest { 26 | 27 | @InjectMocks 28 | RiderService riderService; 29 | 30 | @Mock 31 | DeliveryDAO deliveryDAO; 32 | 33 | @Mock 34 | OrderMapper orderMapper; 35 | 36 | RiderDTO riderDTO; 37 | 38 | @BeforeEach 39 | public void init() { 40 | riderDTO = RiderDTO.builder() 41 | .id("일산라이더") 42 | .name("이성국") 43 | .phone("010-1111-1111") 44 | .address("경기도 고양시 일산동") 45 | .fcmToken("sdkfjkclxvwer1234") 46 | .build(); 47 | } 48 | 49 | @Test 50 | @DisplayName("라이더가 출근할 시 지역별 라이더 대기 목록에 등록된다") 51 | public void registerStandbyRiderWhenStartWorkTest() { 52 | doNothing().when(deliveryDAO).insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 53 | 54 | deliveryDAO.insertStandbyRiderWhenStartWork(riderDTO); 55 | 56 | verify(deliveryDAO).insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 57 | } 58 | 59 | @Test 60 | @DisplayName("라이더가 출근할 시 지역별 라이더 대기 목록에 등록하는데 아이디 값을 누락하고 요청하여 실패한다") 61 | public void registerStandbyRiderWhenStartWorkTestFailBecauseEmptyId() { 62 | riderDTO = RiderDTO.builder() 63 | .name("이성국") 64 | .build(); 65 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 66 | .insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 67 | 68 | assertThrows(IllegalArgumentException.class, 69 | () -> deliveryDAO.insertStandbyRiderWhenStartWork(riderDTO)); 70 | 71 | verify(deliveryDAO).insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 72 | } 73 | 74 | @Test 75 | @DisplayName("라이더가 출근할 시 지역별 라이더 대기 목록에 등록하는데 토큰값을 누락하고 요청하면 실패한다") 76 | public void registerStandbyRiderWhenStartWorkTestFailBecauseEmptyToken() { 77 | riderDTO = RiderDTO.builder() 78 | .id("일산라이더") 79 | .build(); 80 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 81 | .insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 82 | 83 | assertThrows(IllegalArgumentException.class, 84 | () -> deliveryDAO.insertStandbyRiderWhenStartWork(riderDTO)); 85 | 86 | verify(deliveryDAO).insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 87 | } 88 | 89 | @Test 90 | @DisplayName("라이더가 출근할 시 지역별 라이더 대기 목록에 등록하는데 주소값을 누락하고 요청하면 실패한다") 91 | public void registerStandbyRiderWhenStartWorkTestFailBecauseEmptyAddress() { 92 | riderDTO = RiderDTO.builder() 93 | .id("일산라이더") 94 | .build(); 95 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 96 | .insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 97 | 98 | assertThrows(IllegalArgumentException.class, 99 | () -> deliveryDAO.insertStandbyRiderWhenStartWork(riderDTO)); 100 | 101 | verify(deliveryDAO).insertStandbyRiderWhenStartWork(any(RiderDTO.class)); 102 | } 103 | 104 | @Test 105 | @DisplayName("라이더가 퇴근할 시 지역별 라이더 대기 목록에서 삭제한다") 106 | public void deleteStandbyRiderWhenStopWorkTest() { 107 | doNothing().when(deliveryDAO).deleteStandbyRiderWhenStopWork(any(RiderDTO.class)); 108 | 109 | deliveryDAO.deleteStandbyRiderWhenStopWork(riderDTO); 110 | 111 | verify(deliveryDAO).deleteStandbyRiderWhenStopWork(any(RiderDTO.class)); 112 | } 113 | 114 | @Test 115 | @DisplayName("라이더가 퇴근할 시 지역별 라이더 대기 목록에서 삭제할 때 아이디를 누락하여 보내면 실패한다") 116 | public void deleteStandbyRiderWhenStopWorkTestFailBecauseEmptyId() { 117 | riderDTO = RiderDTO.builder() 118 | .name("이성국") 119 | .phone("010-1111-1111") 120 | .address("경기도 고양시 일산동") 121 | .fcmToken("sdkfjkclxvwer1234") 122 | .build(); 123 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 124 | .deleteStandbyRiderWhenStopWork(any(RiderDTO.class)); 125 | 126 | assertThrows(IllegalArgumentException.class, 127 | () -> deliveryDAO.deleteStandbyRiderWhenStopWork(riderDTO)); 128 | 129 | verify(deliveryDAO).deleteStandbyRiderWhenStopWork(any(RiderDTO.class)); 130 | } 131 | 132 | @Test 133 | @DisplayName("라이더가 퇴근할 시 지역별 라이더 대기 목록에서 삭제할 때 주소를 누락하여 보내면 실패한다") 134 | public void deleteStandbyRiderWhenStopWorkTestFailBecauseEmptyAddress() { 135 | riderDTO = RiderDTO.builder() 136 | .id("rider") 137 | .name("이성국") 138 | .phone("010-1111-1111") 139 | .fcmToken("sdkfjkclxvwer1234") 140 | .build(); 141 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 142 | .deleteStandbyRiderWhenStopWork(any(RiderDTO.class)); 143 | 144 | assertThrows(IllegalArgumentException.class, 145 | () -> deliveryDAO.deleteStandbyRiderWhenStopWork(riderDTO)); 146 | 147 | verify(deliveryDAO).deleteStandbyRiderWhenStopWork(any(RiderDTO.class)); 148 | } 149 | 150 | @Test 151 | @DisplayName("라이더가 주문완료된 음식에 배차 요청을 하는데 성공한다") 152 | public void acceptStandbyOrderTest() { 153 | doNothing().when(deliveryDAO) 154 | .updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 155 | doNothing().when(orderMapper) 156 | .updateStandbyOrderToDelivering(anyLong(), any(String.class), any( 157 | OrderStatus.class)); 158 | 159 | riderService.acceptStandbyOrder(1L, riderDTO); 160 | 161 | verify(deliveryDAO).updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 162 | verify(orderMapper).updateStandbyOrderToDelivering(anyLong(), any(String.class), any( 163 | OrderStatus.class)); 164 | } 165 | 166 | @Test 167 | @DisplayName("라이더가 잘못된 주문 아이디로 배차 요청을 하면 IllegalArgumentException을 던진다") 168 | public void acceptStandbyOrderTestFailBecauseWrongOrderId() { 169 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 170 | .updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 171 | 172 | assertThrows(IllegalArgumentException.class, 173 | () -> riderService.acceptStandbyOrder(100L, riderDTO)); 174 | 175 | verify(deliveryDAO).updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 176 | } 177 | 178 | @Test 179 | @DisplayName("라이더가 잘못된 라이더 아이디로 배차 요청을 하면 IllegalArgumentException을 던진다") 180 | public void acceptStandbyOrderTestFailBecauseWrongRiderId() { 181 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 182 | .updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 183 | 184 | assertThrows(IllegalArgumentException.class, 185 | () -> riderService.acceptStandbyOrder(100L, riderDTO)); 186 | 187 | verify(deliveryDAO).updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 188 | } 189 | 190 | @Test 191 | @DisplayName("라이더가 잘못된 주소값으로 배차 요청을 하면 IllegalArgumentException을 던진다") 192 | public void acceptStandbyOrderTestFailBecauseWrongAddress() { 193 | doThrow(IllegalArgumentException.class).when(deliveryDAO) 194 | .updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 195 | 196 | assertThrows(IllegalArgumentException.class, 197 | () -> riderService.acceptStandbyOrder(100L, riderDTO)); 198 | 199 | verify(deliveryDAO).updateStandbyOrderToDelivering(anyLong(), any(RiderDTO.class)); 200 | } 201 | 202 | 203 | } -------------------------------------------------------------------------------- /src/main/resources/mapper/OrderMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | INSERT INTO `ORDER`(order_status, address, user_id, store_id) 10 | VALUES(#{orderStatus}, #{address}, #{userId}, #{storeId}) 11 | 12 | 13 | 14 | UPDATE `ORDER` SET total_price = #{totalPrice}, 15 | order_status = #{orderStatus} 16 | WHERE id = #{orderId} 17 | 18 | 19 | 20 | UPDATE `ORDER` SET order_status = #{orderStatus} 21 | WHERE id = #{orderId} 22 | 23 | 24 | 25 | UPDATE `ORDER` SET order_status = #{orderStatus}, 26 | rider_id = #{riderId} 27 | WHERE id = #{orderId} 28 | 29 | 30 | 31 | UPDATE `ORDER` SET order_status = #{orderStatus} 32 | WHERE id = #{orderId} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | --------------------------------------------------------------------------------