지난번 주문기능에 이어 이번에는 포인트 적립과 사용 주문한 내용을 확인할수 있는
주문목록화면을 구현하도록 하겠습니다
사용자가 포인트를 사용하여 주문을 완료하면 동시에 회원테이블에서 사용한 포인트를
차감하여야 합니다. 포인트 적립의 경우 주문취소의 경우가 있기 때문에 배달이 완료되면
적립하는게 좋겠지만 그 부분은 아직 구현전이기 때문에 지금은 포인트사용과 마찬가지로
주문이 완료되면 적립하도록 구현하겠습니다
포인트를 적립하거나 사용하기 위해 포인트 테이블을 하나 추가해줘야 합니다
이미 User테이블에 포인트칼럼이 존재하는데 새로운 테이블을 하나 추가하는 이유는
포인트가 언제 어디서 사용, 적립되었는지를 확인하기 위함입니다
-- 포인트 테이블
CREATE TABLE DL_POINT (
USER_ID NUMBER,
USED_DATE TIMESTAMP DEFAULT SYSDATE,
INFO VARCHAR2(100) NOT NULL,
POINT NUMBER NOT NULL
);
ALTER TABLE DL_POINT
ADD CONSTRAINT POINT
FOREIGN KEY (USER_ID)
REFERENCES DL_USER(ID)
on delete cascade;
지난번 OrderService에 작성한 order메서드 제일밑에 다음의 코드를 추가해줍니다
@Autowired
AdminMapper adminMapper;
// 회원 포인트 적립
if (principal != null) {
String storeName = cartListDto.getStoreName();
int point = (int)(total * 0.01);
Map<String, Object> ponitUpdateMap = new HashMap<>();
ponitUpdateMap.put("userId", userId);
ponitUpdateMap.put("info", storeName);
ponitUpdateMap.put("point", point);
//Point테이블 내역 Insert
int result = adminMapper.pointUpdate(ponitUpdateMap);
//User테이블 Point Update
ponitUpdateMap.put("totalPoint", principal.getPoint() + point);
int result2 = adminMapper.pointUpdateUser(ponitUpdateMap);
if(result == 1 && result2 == 1) {
UserInfoSessionUpdate.sessionUpdate(point+"", "point", principal, session);
}
}
// 로그인 사용자가 포인트 사용했을때
if(orderInfoDto.getUsedPoint() != 0 ) {
String storeName = cartListDto.getStoreName();
int usedPoint = -orderInfoDto.getUsedPoint();
Map<String, Object> ponitUpdateMap = new HashMap<>();
ponitUpdateMap.put("userId", userId);
ponitUpdateMap.put("info", storeName);
ponitUpdateMap.put("point", usedPoint);
//Point테이블 내역 Insert
int result = adminMapper.pointUpdate(ponitUpdateMap);
//User테이블 Point Update
ponitUpdateMap.put("totalPoint", principal.getPoint() + usedPoint);
int result2 = adminMapper.pointUpdateUser(ponitUpdateMap);
if(result == 1 && result2 == 1) {
UserInfoSessionUpdate.sessionUpdate(usedPoint+"", "point", principal, session);
}
}
포인트를 처리하기 위해서는 Userid, storename, point가 필요하므로
주문처리에서와 마찬가지로 Map을 생성하여 넣어주도록 합니다
UserInfoSessionUpdate는 밑에서 설명하도록 하겠습니다
포인트는 AdminMapper에서 처리할것이므로 AdminMapper를 추가하겠습니다
@Mapper
public interface AdminMapper {
//포인트 테이블 내역 insert
public int pointUpdate(Map<String, Object> ponitUpdateMap);
//유저 테이블 point update
public int pointUpdateUser(Map<String, Object> ponitUpdateMap);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.han.delivery.dao.AdminMapper">
<insert id="pointUpdate" >
INSERT INTO DL_POINT (
USER_ID
,INFO
,POINT
) VALUES (
#{userId }
,#{info }
,#{point }
)
</insert>
<update id="pointUpdateUser">
UPDATE DL_USER SET
POINT = #{totalPoint }
WHERE
ID = ${userId}
</update>
</mapper>
여기까지 하면 주문시 포인트처리가 완료됩니다. 하지만 우리는 현재 스프링시큐리티를
사용하여 로그인을 하고 있기 때문에 주문이 완료되더라도 재로그인을 하지 않으면
사용자는 포인트적립, 사용을 확인할수 없습니다. 그 이유는 스프링 시큐리티는 로그인요청시
회원테이블에서 해당 회원의 모든 정보를 가져와 세션에 저장해두고 있습니다
따라서 우리가 회원테이블에서 포인트를 변경하더라도 세션은 이미 그전에 회원정보를
저장하고 있기 때문에 테이블과 세션에 저장한 정보가 일치하지 않습니다 따라서 우리는
세션에 저장한 회원정보에 접근하여 포인트를 수정해줘야 합니다
그것을 위해 utils패키지안에 클래스를 하나 추가해주겠습니다
public class UserInfoSessionUpdate {
public static void sessionUpdate(String value, String valueType, CustomUserDetails principal, HttpSession session) {
CustomUserDetails customUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(valueType.equals("nickname")) {
customUserDetails.setNickname(value);
}
else if(valueType.equals("password")) {
customUserDetails.setPassword(value);
}
else if(valueType.equals("point")) {
int point = customUserDetails.getPoint() + Integer.parseInt(value);
customUserDetails.setPoint(point);
}
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(new UsernamePasswordAuthenticationToken(customUserDetails, null, principal.getAuthorities()));
session.setAttribute("SPRING_SECURITY_CONTEXT", sc);
}
}
nickname과 password는 나중에 회원정보 변경을 위해 미리 추가해줬습니다
이제 주문과 동시에 포인트가 적립, 차감되는것을 확인할수 있습니다
이제 회원이 지금까지 주문한 정보를 확인할수 있는 주문목록 페이지를 추가하겠습니다
이 주문목록 페이지에서는 지금까지 사용자가 요청한 모든 주문의 상태와 매장명, 주문메뉴등을
간략하게 확인할수 있어야합니다. 또한 리뷰도 관리할수 있어야하는데 현재 리뷰기능은
아직 구현을 하지 않았기에 주문의 간략한 정보를 나타낼것입니다
밑의 파일을 받아 덮어쓰기 하시면 됩니다
화면을 구성하는 요소들을 추가하였으니 이제 화면으로 접근할수 있도록
기존 OrderController에 다음의 코드를 추가해주세요
@GetMapping("/orderList")
public String orderList(@AuthenticationPrincipal CustomUserDetails principal, Model model) {
if (principal == null) {
System.out.println("비로그인");
} else {
System.out.println("로그인");
long userId = principal.getId();
List<OrderListDto> orderList = orderService.orderList(userId);
if (orderList.size() == 0) {
return "order/orderList";
}
List<List<CartDto>> cartDtoList = new ArrayList<>();
for (int i=0;i<orderList.size();i++) {
cartDtoList.add(FoodInfoFromJson.foodInfoFromJson(orderList.get(i).getFoodInfo()));
}
model.addAttribute("cartList", cartDtoList);
model.addAttribute("orderList", orderList);
}
return "order/orderList";
}
배달 주문시에는 회원과 비회원으로 나뉘기 때문에 회원일때만 주문목록에 기존 주문내역을
표시해주면 됩니다. 주문내역에는 회원이 주문한 정보뿐만 아니라 매장의 몇가지 정보도
필요합니다. 즉 OrderInfo, OrderDetail, Store 이 세개의 테이블에 저장해놓은
정보를 join해서 가져올것이기 때문에 Dto를 새로 추가해줘야 합니다
@Data
public class OrderListDto {
private String orderNum;
private long storeId;
private long userId;
private Date orderDate;
private String DeliveryStatus;
private int DeliveryAddress1;
private String DeliveryAddress2;
private String DeliveryAddress3;
private String payMethod;
private int totalPrice;
private int usedPoint;
private String phone;
private String request;
private String foodInfo;
private String storeName;
private String storeImg;
private String storeThumb;
private String deliveryTip;
}
이제 필요한 정보를 이 Dto에 받아오기 위해 Service , dao, mapper에 다음 코드를
추가해 주겠습니다
public List<OrderListDto> orderList(long userId) {
return orderMapper.orderList(userId);
}
// 주문 목록 가져오기
public List<OrderListDto> orderList(long userId);
<select id="orderList" resultType="com.han.delivery.dto.OrderListDto">
SELECT * FROM (
SELECT ROWNUM R
,O.*
FROM (SELECT count(*) over() list_count,
o.order_num,
o.user_id,
o.order_date,
o.pay_method,
o.delivery_status,
o.delivery_address1,
o.delivery_address2,
o.delivery_address3,
o.store_id,
o.total_price,
o.used_point,
o.request,
d.food_info,
s.store_name,
s.store_img,
s.store_thumb,
s.delivery_tip
FROM dl_order_user o
LEFT JOIN (SELECT ORDER_NUM,
LISTAGG(FOOD_INFO, '/') food_info
FROM dl_ORDER_DETAIL_USER
GROUP BY ORDER_NUM) d
ON o.order_num = d.order_num
LEFT JOIN dl_store s
ON o.store_id = s.id
WHERE o.user_id = 8
ORDER BY o.order_date desc
) O
)
</select>
LISTAGG는 Order_Num가 똑같은 요소를 '/'를 구분자로 하나의 칼럼으로 합쳐줍니다
Order_Detail테이블의 경우 주문 메뉴 + 옵션을 하나의 칼럼으로 가지고 있고
하나의 주문에는 여러개의 메뉴가 존재할수 있기 때문에 LISTAGG를 사용합니다
우리는 Order_Detail에서 메뉴정보를 저장할때 object를 json타입으로 변환해서
DB에 저장했었습니다. 따라서 LISTAGG를 사용하여 Dto에 해당 데이터를 받아오게 되면
"{json데이터}/{json데이터}/{json데이터}" 이런식으로 String으로 저장됩니다
우리는 "/"를 구분자로 하나로 합쳐져 있는 데이터를 다시 분리하여 배열로 만들고
이 json데이터를 object로 변환해줘야 합니다. json -> object변환은 여기뿐만 아니라
다른곳에서도 쓰일 예정이므로 utils패키지에 변환처리를 위한 클래스를 하나 추가합니다
public class FoodInfoFromJson {
public static List<CartDto> foodInfoFromJson(String foodInfoJSON) {
String[] arr = foodInfoJSON.split("/");
Gson gson = new Gson();
List<CartDto> cartDtoList = new ArrayList<>();
for(int i=0;i<arr.length;i++) {
cartDtoList.add(gson.fromJson(arr[i], CartDto.class));
}
return cartDtoList;
}
}
코드는 위에서 말한 그대로이며 gson을 사용합니다
이 클래스의 메서드를 이용하여 하나의 주문에 포함된 모든 메뉴의 정보를
List<CartDto>
의 형태로 변환할수 있습니다.
하지만 주문목록의 경우 하나의 주문이 아닌 지금까지의 모든 주문의 정보를
표시해야 하기 때문에 List<CartDto>
를 포함하는 List가 필요합니다
따라서 List<List<CartDto>>
2차원 리스트를 사용합니다
이제 주문내역에 들어가면 위와 같은 화면을 보실수 있습니다.
주문 목록은 구현이 완료되었고 다음에는 주문 상세페이지를 구현해보도록 하겠습니다