Skip to content
This repository has been archived by the owner on Aug 13, 2022. It is now read-only.

[#70] 배달 매칭 서비스 #69

Open
wants to merge 18 commits into
base: rider_info_service
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/com/delfood/FoodDeliveryApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
Expand All @@ -24,6 +25,7 @@
@EnableAspectJAutoProxy // 최상위 클래스에 적용해야 AOP를 찾을 수 있도록 만들어준다.
@EnableCaching // Spring에서 Caching을 사용하겠다고 선언한다.
@EnableAsync // 메서드를 비동기 방식으로 실행할 수 있도록 설정한다.
@EnableScheduling // 스케줄링을 허용한다.
public class FoodDeliveryApplication {

public static void main(String[] args) {
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/com/delfood/controller/RiderController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.delfood.aop.LoginCheck.UserType;
import com.delfood.aop.RiderLoginCheck;
import com.delfood.dto.rider.RiderDTO;
import com.delfood.service.delivery.DeliveryService;
import com.delfood.service.rider.RiderInfoService;
import com.delfood.utils.SessionUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand Down Expand Up @@ -37,6 +38,9 @@ public class RiderController {
@Autowired
private ObjectMapper objectMapper;

@Autowired
private DeliveryService deliveryService;

/**
* 아이디 중복 체크.
* @author jun
Expand Down Expand Up @@ -129,7 +133,12 @@ public void updateMail(HttpSession session, @RequestBody UpdateMailRequest reque
riderInfoService.changeMail(id, request.getPassword(), request.getUpdateMail());
}


@PostMapping("delivery/accept")
@LoginCheck(type = UserType.RIDER)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모습을 보니 떠오른건데 type보다는 level이 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

level도 생각해보았는데 뭔가 접근의 계층이 있어야 할것같은 느낌이라서요. 제가 만든 로그인은 3가지의 전혀 다른 타입이라서 일단 타입으로 해두었습니다.

public void deliveryAccept(@RequestBody DeliveryAcceptRequest request, HttpSession session) {
String riderId = SessionUtil.getLoginRiderId(session);
deliveryService.acceptDeliveryRequest(riderId, request.getOrderId());
}

// Request
@Getter
Expand All @@ -140,7 +149,12 @@ private static class SignInRequest {
@NonNull
private String password;
}


@Getter
private static class DeliveryAcceptRequest {
private Long orderId;
}

@Getter
private static class UpdatePasswordRequest {
@NonNull
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/delfood/dao/FcmDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class FcmDao {

@Autowired
private ObjectMapper objectMapper;

@Value("#{expire.fcm.rider}")
private static Long riderTokenExpireSecond;

@Value("${expire.fcm.member}")
private static Long memberTokenExpireSecond;
Expand Down Expand Up @@ -85,6 +88,35 @@ public void addOwnerToken(String ownerId, String token) {
}
}

public void addRiderToken(String riderId, String token) {
String key = RedisKeyFactory.generateFcmRiderKey(riderId);
redisTemplate.watch(key);
try {
if (getRiderTokens(riderId).contains(token)) { // 토큰이 이미 있을 경우
return;
}
redisTemplate.multi();

redisTemplate.opsForList().rightPush(key, token);
redisTemplate.expire(key, riderTokenExpireSecond, TimeUnit.SECONDS);

redisTemplate.exec();
} catch (Exception e) {
log.error("Redis Add Rider Token ERROR! key : {}", key);
log.error("ERROR Info : {} ", e.getMessage());
redisTemplate.discard();
throw new RuntimeException(
"Cannot add rider token. key : " + key + ", ERROR Info " + e.getMessage());
}
}

public List<String> getRiderTokens(String riderId) {
return redisTemplate.opsForList().range(RedisKeyFactory.generateFcmRiderKey(riderId), 0, -1)
.stream()
.map(e -> objectMapper.convertValue(e, String.class))
.collect(Collectors.toList());
}

/**
* 해당 고객의 토큰 리스트를 조회한다.
* @author jun
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/delfood/dao/deliveery/DeliveryDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.delfood.dao.deliveery;

import com.delfood.dto.OrderDTO.OrderStatus;
import com.delfood.dto.address.Position;
import com.delfood.dto.rider.DeliveryRiderDTO;
import java.util.List;

public interface DeliveryDao {

void updateRiderInfo(DeliveryRiderDTO riderInfo);

boolean deleteRiderInfo(String riderId);

boolean hasRiderInfo(String riderId);

void deleteNonUpdatedRiders();

DeliveryRiderDTO getRiderInfo(String riderId);

List<DeliveryRiderDTO> getRiderList();

void deleteAll(List<String> idList);

OrderStatus getOrderStatus(Long orderId);

void setOrderStatus(Long orderId, OrderStatus status);

void deleteOrderStatus(Long orderId);
}
156 changes: 156 additions & 0 deletions src/main/java/com/delfood/dao/deliveery/LocalMemoryDeliveryDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package com.delfood.dao.deliveery;

import com.delfood.dto.OrderDTO.OrderStatus;
import com.delfood.dto.address.Position;
import com.delfood.dto.rider.DeliveryRiderDTO;
import com.delfood.service.OrderService;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.concurrent.ThreadSafe;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

@Repository("multiThreadDeliveryDao")
@ThreadSafe
public class LocalMemoryDeliveryDao implements DeliveryDao{
private ConcurrentHashMap<String, DeliveryRiderDTO> riders;
private ConcurrentHashMap<Long, OrderStatus> orders;

@Value("rider.expire")
private static Long expireTime;

@Autowired
private OrderService orderService;

@PostConstruct
public void init() {
this.riders = new ConcurrentHashMap<String, DeliveryRiderDTO>();
this.orders = new ConcurrentHashMap<Long, OrderStatus>();
}

/**
* 내부 Map에 라이더 정보를 갱신한다.
* 만약 Map 내부에 정보가 없다면 새롭게 정보를 추가한다.
* 라이더 정보가 저장되면 라이더는 실시간으로 정보를 업데이트해야한다.
* @param riderInfo 라이더 정보
*/
@Override
public void updateRiderInfo(DeliveryRiderDTO riderInfo) {
riders.put(riderInfo.getId(), riderInfo);
}

/**
* 배달 대기를 제거한다.
* @author jun
* @param riderId 제거할 라이더의 아이디
*/
@Override
public boolean deleteRiderInfo(String riderId) {
return riders.remove(riderId) != null;
}

/**
* 해당 라이더가 저장소 내에 존재하는지 확인한다.
* @author jun
* @param riderId 라이더 아이디
* @return
*/
@Override
public boolean hasRiderInfo(String riderId) {
return riders.containsKey(riderId);
}

/**
* 리스트 형태로 라이더를 조회한다.
* @author jun
* @return
*/
@Override
public List<DeliveryRiderDTO> getRiderList() {
return riders.values().stream().collect(Collectors.toList());
}


/**
* 일정 시간동안 자신의 위치를 업데이트 하지 않는 라이더를 제거한다.
* @author jun
*/
@Override
public void deleteNonUpdatedRiders() {
riders.values().stream()
.filter(
e -> ChronoUnit.SECONDS.between(e.getUpdatedAt(), LocalDateTime.now()) > expireTime)
.forEach(e -> riders.remove(e.getId()));
}

/**
* 라이더의 정보를 조회한다.
* @author jun
*/
@Override
public DeliveryRiderDTO getRiderInfo(String riderId) {
return riders.get(riderId);
}

/**
* 리스트로 받은 아이디를 기반으로 라이더를 배달 매칭에서 제거한다.
* @param idList 라이더의 아이디들
*/
@Override
public void deleteAll(List<String> idList) {
for (String id : idList) {
deleteRiderInfo(id);
}
}

/**
* 주문의 상태를 조회한다.
* 주문 정보가 내부 메모리에 없다면 DB에서 조회한 후 메모리에 저장한다.
* @author jun
* @param orderId 조회할 주문 아이디
*/
@Override
public OrderStatus getOrderStatus(Long orderId) {
synchronized (orders) {
OrderStatus status = orders.get(orderId);
if (Objects.isNull(status)) {
status = orderService.getOrderStatus(orderId);
setOrderStatus(orderId, status);
return status;
}
return status;
}
}

/**
* 주문 정보를 내부에 저장한다.
* @author jun
* @param orderId 저장할 주문 아이디
* @param status 주문의 상태
*/
@Override
public void setOrderStatus(Long orderId, OrderStatus status) {
orders.put(orderId, status);
}

/**
* 주문 정보를 삭제한다.
* @param orderId 삭제할 주문 정보 아이디
* @author jun
*/
@Override
public void deleteOrderStatus(Long orderId) {
orders.remove(orderId);
}
}
25 changes: 9 additions & 16 deletions src/main/java/com/delfood/dto/push/PushMessage.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.delfood.dto.push;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import org.joda.time.LocalDateTime;
Expand All @@ -12,6 +11,14 @@ public class PushMessage {
@NonNull
private String message;

public static final PushMessage ADD_ORDER_REQUEST = new PushMessage("DelFood 주문", "새로운 주문이 들어왔습니다");
public static final PushMessage ACCEPT_ORDER_REQUEST = new PushMessage("DelFood 접수", "주문이 접수되었습니다");
public static final PushMessage REQUIRED_ORDER_REQUEST = new PushMessage("DelFood 주문취소", "매장에서 주문을 취소하였습니다");
public static final PushMessage DELIVERY_MATCH = new PushMessage("DelFood 배달원 매칭", "배달원이 매칭되었습니다");
public static final PushMessage DELIVERY_START = new PushMessage("DelFood 배달 시작", "음식 배달이 시작되었습니다");
public static final PushMessage DELIVERY_SUCCESS = new PushMessage("DelFood 배달 완료", "배달이 완료되었습니다");
public static final PushMessage DELIVERY_REQUEST = new PushMessage("DelFood 배달 요청", "근처 매장에서 배달을 요청했습니다.");

private LocalDateTime generatedTime;

public PushMessage(String title, String message) {
Expand All @@ -20,21 +27,7 @@ public PushMessage(String title, String message) {
this.generatedTime = LocalDateTime.now();
}



public static PushMessage getMessasge(Type type) {
return type.pushMessage;
}

@AllArgsConstructor
public static enum Type {
addOrderRequest(new PushMessage("DelFood 주문", "새로운 주문이 들어왔습니다")),
acceptOrderRequest(new PushMessage("DelFood 접수", "주문이 접수되었습니다")),
requiredOrderRequest(new PushMessage("DelFood 주문취소", "매장에서 주문을 취소하였습니다")),
deliveryMatch(new PushMessage("DelFood 배달원 매칭", "배달원이 매칭되었습니다")),
deliveryStart(new PushMessage("DelFood 배달 시작", "음식 배달이 시작되었습니다")),
deliverySuccess(new PushMessage("DelFood 배달 완료", "배달이 완료되었습니다"));

private PushMessage pushMessage;
}

}
36 changes: 36 additions & 0 deletions src/main/java/com/delfood/dto/rider/AcceptDeliveryRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.delfood.dto.rider;

import java.time.LocalDateTime;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;

@Getter
public class AcceptDeliveryRequestDTO {

@NonNull
private Long orderId;

@NonNull
private String riderId;

@NonNull
private RequestResult result;

private LocalDateTime startedAt;


@Builder
public AcceptDeliveryRequestDTO(Long orderId, String riderId, RequestResult result) {
this.orderId = orderId;
this.riderId = riderId;
this.result = result;
startedAt = LocalDateTime.now();
}

public static enum RequestResult {
SUCCESS, FAIL
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/delfood/dto/rider/DeliveryRiderDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.delfood.dto.rider;

import java.time.LocalDateTime;
import com.delfood.dto.address.Position;
import lombok.EqualsAndHashCode;
import lombok.Getter;

@Getter
@EqualsAndHashCode(of = "id")
public class DeliveryRiderDTO {
private String id;

private Position position;

private LocalDateTime updatedAt;

public DeliveryRiderDTO() {
this.updatedAt = LocalDateTime.now();
}
}
Loading