Merge branch 'master' into mypage

This commit is contained in:
Sangbum Park
2022-03-16 20:26:44 +09:00
committed by GitHub
35 changed files with 654 additions and 257 deletions

View File

@@ -46,7 +46,7 @@ spring:
allow-credentials: true
routes:
- id: order-service
uri: lb://ORDER-SERVCIE
uri: lb://ORDER-SERVICE
predicates:
- Path=/order-service/**
filters:
@@ -54,7 +54,7 @@ spring:
- RewritePath=/order-service/(?<segment>.*),/$\{segment}
- id: store-service
uri: lb://STORE-SERVCIE
uri: lb://STORE-SERVICE
predicates:
- Path=/store-service/**
filters:

View File

@@ -19,7 +19,10 @@ axios.interceptors.request.use(function (config) {
config.headers.Authorization = "Bearer " + jwt.getToken();
return config;
});
Vue.filter('currency', function (value) {
var num = new Number(value);
return num.toFixed(0).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")
});
axios.interceptors.response.use(
(response) => {

View File

@@ -23,7 +23,7 @@ const authCheck = async function (to, from, next) {
await auth.requestCheckAccessToken();
}
} catch (error) {
await router.replace("/login");
await router.push("/login");
}
next();
};
@@ -37,60 +37,46 @@ const routes = [
children: [
{
path: "/home",
beforeEnter: authCheck,
name: 'home',
component: () => import('../views/HomeView')
},
{
path: "/search",
beforeEnter: authCheck,
name: 'search-store',
component: () => import('../views/SearchStore')
},
{
path: "/history",
beforeEnter: authCheck,
name: 'order-history',
component: () => import('../views/OrderHistory')
},
{
path: "/favorite",
beforeEnter: authCheck,
name: 'favorite-store',
component: () => import('../views/FavoriteStore')
},
{
path: "/notification",
beforeEnter: authCheck,
name: 'notification',
component: () => import('../views/NotificationView')
},
{
path: '/login',
beforeEnter: authCheck,
name: 'login',
component: () => import('../views/LoginPage')
},
{
path: "/item/:itemId",
beforeEnter: authCheck,
name: 'itemDetail',
component: () => import('../views/ItemDetail')
},
{
path: "/order",
beforeEnter: authCheck,
name: 'orderPage',
component: () => import('../views/OrderPage')
},
{
path: "/mypage",
beforeEnter: authCheck,
name: 'myPage',
component: () => import('../views/MyPage')
},
]
},
{
path: '/login',
component: () => import('../views/LoginPage'),
},
{
path: '/store',
redirect: 'store',
@@ -100,7 +86,6 @@ const routes = [
{
path: "/store/:storeId",
name: "store",
beforeEnter: authCheck,
component: () => import('../views/StoreView'),
props: true
},

View File

@@ -133,7 +133,7 @@ export default {
})
.catch(error=>{
console.log(error)
this.$router.replace("/")
this.$router.push("/")
})
},
parseGroup: function (type){
@@ -151,10 +151,10 @@ export default {
orderApi.addItemToBasket(this.setItem)
.then(response=>{
console.log(response)
this.$router.replace("/store/"+this.storeId)
this.$router.push("/store/"+this.storeId)
})
.catch(error=>{
console.log(error)
console.log(error.response)
})
}
},

View File

@@ -8,6 +8,17 @@
<router-view
v-on:getStoreId="renderNavigation">
</router-view>
<div align="right" >
<v-btn
color="primary"
dark
right
fab
@click="toOrder"
>
<v-icon>mdi-basket</v-icon>
</v-btn>
</div>
</v-container>
</v-main>
<bottom-navigation></bottom-navigation>
@@ -45,6 +56,11 @@ export default {
const response = await storeApi.requestStore(this.store.id);
this.store = response.data.data;
},
toOrder(){
if(confirm("주문화면으로 이동할까요?")){
this.$router.push("/order")
}
}
}
}

View File

@@ -1,79 +1,84 @@
<template>
<v-container
fill-height
>
<v-row>
<v-col>
<div align="center" ><v-img
max-height="150"
max-width="250"
:src="logo"></v-img></div>
</v-col>
</v-row>
<v-row
justify="center"
>
<v-col class="align-content-center">
<v-form ref="form" lazy-validation>
<v-text-field
:rules="[v => /.+@.+\..+/.test(v) || 'E-mail must be valid', v => !!v || '이메일은 필수 값입니다']"
label="이메일"
prepend-icon="mdi-account-circle"
></v-text-field>
<v-text-field
:rules="[v => !!v || '비밀번호는 필수 값입니다']"
label="비밀번호"
type="Password"
prepend-icon="mdi-lock"
append-icon="mdi-eye-off"
></v-text-field>
<v-btn
block
>
Login
</v-btn>
<div class="d-block my-7" align="center">
<v-subheader class="d-inline" >소셜 아이디로 로그인해보세요!</v-subheader>
</div>
<div class="d-block " align="center">
<v-app>
<v-main>
<v-container class="px-8 py-8">
<v-container
fill-height
>
<v-row>
<v-col>
<div align="center" ><v-img
max-height="150"
max-width="250"
:src="logo"></v-img></div>
</v-col>
</v-row>
<v-row
justify="center"
>
<v-col class="align-content-center">
<v-form ref="form" lazy-validation>
<v-text-field
:rules="[v => /.+@.+\..+/.test(v) || 'E-mail must be valid', v => !!v || '이메일은 필수 값입니다']"
label="이메일"
prepend-icon="mdi-account-circle"
></v-text-field>
<v-text-field
:rules="[v => !!v || '비밀번호는 필수 값입니다']"
label="비밀번호"
type="Password"
prepend-icon="mdi-lock"
append-icon="mdi-eye-off"
></v-text-field>
<v-btn
class="mx-2"
fab
small
block
>
<v-img
class="d-inline-block align-lg-center mx-5"
style=""
max-width="38"
max-height="38"
:src="logo_naver"
@click="login_auth('naver')"
/>
</v-btn>
<v-btn
class="mx-2"
fab
small
>
<v-img
class="d-inline-block mx-5"
max-width="38"
max-height="38"
min-width="38"
min-height="38"
:src="logo_google"
@click="login_auth('google')"
/>
Login
</v-btn>
<div class="d-block my-7" align="center">
<v-subheader class="d-inline" >소셜 아이디로 로그인해보세요!</v-subheader>
</div>
<div class="d-block " align="center">
<v-btn
class="mx-2"
fab
small
>
<v-img
class="d-inline-block align-lg-center mx-5"
style=""
max-width="38"
max-height="38"
:src="logo_naver"
@click="login_auth('naver')"
/>
</v-btn>
<v-btn
class="mx-2"
fab
small
>
<v-img
class="d-inline-block mx-5"
max-width="38"
max-height="38"
min-width="38"
min-height="38"
:src="logo_google"
@click="login_auth('google')"
/>
</v-btn>
</div>
</div>
</v-form>
</v-col>
</v-row>
</v-container>
</v-container>
</v-main>
</v-app>
</v-form>
</v-col>
</v-row>
</v-container>
</template>
@@ -81,29 +86,31 @@
import logo from '@/assets/justLogo.png'
import logo_naver from '@/assets/logo_naver.svg'
import logo_google from '@/assets/logo_google.png'
import jwt from "@/common/jwt";
import router from "@/router/router";
export default {
name: "LoginPage",
data (){
data() {
return {
logo : logo,
logo: logo,
logo_naver: logo_naver,
logo_google : logo_google,
logo_google: logo_google,
auth_popup: null,
}
},
watch: {
auth_popup : function () {
this.auth_popup.addEventListener('beforeunload', function() {
window.location.href=process.env.VUE_APP_BASEURL;
auth_popup: function () {
this.auth_popup.addEventListener('beforeunload', function () {
window.location.href = process.env.VUE_APP_BASEURL;
});
}
},
methods: {
login_auth: async function(target) {
const _url = process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL+'/user-service/oauth2/authorization/'+target
login_auth: async function (target) {
const _url = process.env.VUE_APP_CUSTOMER_SERVICE_BASEURL + '/user-service/oauth2/authorization/' + target
this.auth_popup = window.open(
_url,
"",
@@ -112,9 +119,22 @@ export default {
},
},
async mounted() {
if (!jwt.isExpired())
await router.push("/");
}
}
</script>
<style scoped>
.container {
max-width: 768px;
background-color: white;
height: 100%;
}
main {
background-color: #f2f2f2!important;
}
</style>

View File

@@ -2,12 +2,12 @@
<div>
<v-row>
<v-col>
<div class="text-h4" style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{ orderData.storeId }}</div>
<div class="text-h4" style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{ orderData.storeName }}</div>
</v-col>
</v-row>
<v-row>
<v-col
v-for=" orderItem in orderData._orderItemDtos"
v-for=" orderItem in orderData.orderItemDtoList"
:key = "orderItem.itemId"
>
<v-card
@@ -17,16 +17,18 @@
<v-list-item three-line>
<v-list-item-content>
<v-list-item-title class="text-h5 mb-3">
{{ orderItem.itemId }}
{{ orderItem.itemName }}
</v-list-item-title>
<v-list-item-subtitle class="mb-5">
수량 : {{ orderItem.count }}
수량 : {{ orderItem.count| currency }}
</v-list-item-subtitle>
<div class="text-body-1 mb-5">
{{ orderItem.itemOptionIds.join(', ')}}
<div class="text-body-1 mb-5" style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
{{ orderItem.orderItemOptionDtoList ?
orderItem.orderItemOptionDtoList.map(x=>x.name).join(', ')
: null}}
</div>
<div class="text--primary">
합계 : <b> {{ orderItem.count * orderItem.price }} </b>
합계 : <b> {{ orderItem.count * orderItem.price | currency}} </b>
</div>
</v-list-item-content>
<v-list-item-avatar
@@ -46,7 +48,7 @@
</v-row>
<v-row>
<v-col>
<div> 합계 : {{orderData.totalPrice}} </div>
<div> 합계 : {{orderData.orderPrice | currency}} </div>
</v-col>
</v-row>
<v-btn
@@ -72,31 +74,24 @@ export default {
data: function(){
return {
orderData:{
storeId:Number,
_orderItemDtos:[{
itemId:Number,
itemOptionIds:Array,
price:Number,
count:Number,
}],
totalPrice:Number,
// storeName:Number,
// orderItemDtoList:[{
// name:Number,
// itemOptionIds:Array,
// price:Number,
// count:Number,
// }],
// orderPrice:Number,
},
}
},
computed:{
test:function (a){
return a;
}
},
methods:{
saveOrder: function(){
orderApi.saveOrder()
.then(()=>{
alert('주문되었습니다.')
this.$router.replace("/")
this.$router.push("/history")
})
.catch(error=>{
console.log(error)
@@ -106,11 +101,12 @@ export default {
getOrder: function(){
orderApi.getOrder()
.then(response=>{
console.log(response)
this.orderData=response.data.data
})
.catch(error=>{
console.log(error)
this.$router.replace("/")
console.log(error.response)
history.back();
})
},
}

View File

@@ -40,7 +40,3 @@ logging:
# jpa query, parameter 로그 (p6spy)
decorator.datasource.p6spy:
enable-logging: true
#feign key
token:
feign: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmZWlnbiIsImV4cCI6MTE2NDY5NzY4NzAsImlhdCI6MTY0Njk3Njg3MH0.5x4Nx7oMnpF0_kZpbZsiB1u9eEbQ4IKIhJlEsa3D22cjZjvTHKz57GCz0sgXb_olhSNIVv9xF41A29-XYiFeBQ

View File

@@ -1,18 +1,15 @@
package com.justpickup.orderservice.domain.order.dto;
import com.justpickup.orderservice.domain.order.entity.Order;
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
import com.justpickup.orderservice.domain.orderItemOption.dto.OrderItemOptionDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import com.justpickup.orderservice.domain.orderItem.entity.OrderItem;
import com.justpickup.orderservice.global.client.store.GetItemResponse;
import lombok.*;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FetchOrderDto {
private Long id;
@@ -20,19 +17,36 @@ public class FetchOrderDto {
private Long orderPrice;
private Long storeId;
private String storeName;
private List<OrderItemDto> orderItemDtoList;
@Getter
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class OrderItemDto{
public FetchOrderDto(Order order) {
this.id = order.getId();
this.userId = order.getUserId();
this.orderPrice = order.getOrderPrice();
this.storeId = order.getStoreId();
this.orderItemDtoList = order.getOrderItems().stream()
.map(orderItem -> OrderItemDto.of(orderItem.getId(),orderItem.getItemId(),orderItem.getPrice(),orderItem.getCount(),orderItem.getOrderItemOptions().stream().map(orderItemOption -> new OrderItemOptionDto(orderItemOption.getId())).collect(Collectors.toList())))
.collect(Collectors.toList());
private Long id;
private Long itemId;
private String itemName;
private List<GetItemResponse.ItemOptionDto> orderItemOptionDtoList;
private Long price;
private Long count;
public OrderItemDto(GetItemResponse getItemResponse, OrderItem orderItem) {
this.id = orderItem.getId();
this.itemId = getItemResponse.getId();
this.itemName = getItemResponse.getName();
this.orderItemOptionDtoList = getItemResponse.getItemOptions();
this.price = orderItem.getPrice();
this.count = orderItem.getCount();
}
}
}

View File

@@ -16,6 +16,7 @@ import java.util.List;
@Entity
@Table(name = "orders")
@Getter
@EntityListeners(value = {OrderListener.class})
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order extends BaseEntity {
@@ -108,4 +109,8 @@ public class Order extends BaseEntity {
public void reject() {
this.orderStatus = OrderStatus.REJECT;
}
public void fail() {
this.orderStatus = OrderStatus.FAIL;
}
}

View File

@@ -1,18 +1,33 @@
package com.justpickup.orderservice.domain.order.entity;
import com.justpickup.orderservice.domain.order.exception.OrderException;
import com.justpickup.orderservice.domain.order.service.OrderSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import javax.persistence.PostUpdate;
import javax.persistence.*;
@Slf4j
public class OrderListener {
@Autowired
@Lazy
private OrderSender orderSender;
// TODO: 2022/03/15 exception 발생시 order fail 처리
@PostUpdate
private void postUpdate(Order order) {
public void postUpdate(Order order){
OrderStatus orderStatus = order.getOrderStatus();
if (orderStatus == OrderStatus.ORDER) {
// TODO: 2022/03/10 Kafka 알림 전송
log.info("[OrderListener] {}", OrderStatus.ORDER.name());
try{
orderSender.orderPlaced(OrderSender.KafkaSendOrderDto.createPrimitiveField(order));
}catch (Exception ex){
throw new OrderException(ex.getMessage());
}
} else if (orderStatus == OrderStatus.PLACED) {
log.info("[OrderListener] {}", OrderStatus.PLACED.name());
}

View File

@@ -31,7 +31,7 @@ public class OrderSender {
@Data
@AllArgsConstructor
@Builder
static class KafkaSendOrderDto{
public static class KafkaSendOrderDto{
private Long id;
private Long userId;

View File

@@ -1,10 +1,6 @@
package com.justpickup.orderservice.domain.order.service;
import com.justpickup.orderservice.domain.order.dto.FetchOrderDto;
import com.justpickup.orderservice.domain.order.dto.OrderDto;
import com.justpickup.orderservice.domain.order.dto.OrderMainDto;
import com.justpickup.orderservice.domain.order.dto.OrderSearchCondition;
import com.justpickup.orderservice.domain.order.dto.PrevOrderSearch;
import com.justpickup.orderservice.domain.order.dto.*;
import com.justpickup.orderservice.domain.order.entity.OrderStatus;
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
import org.springframework.data.domain.Page;

View File

@@ -9,6 +9,9 @@ import com.justpickup.orderservice.domain.order.repository.OrderRepositoryCustom
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
import com.justpickup.orderservice.domain.orderItem.entity.OrderItem;
import com.justpickup.orderservice.domain.orderItemOption.entity.OrderItemOption;
import com.justpickup.orderservice.global.client.store.GetItemResponse;
import com.justpickup.orderservice.global.client.store.GetStoreReseponse;
import com.justpickup.orderservice.global.client.store.StoreClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
@@ -16,10 +19,16 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
@@ -31,7 +40,8 @@ public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final OrderRepositoryCustom orderRepositoryCustom;
private final OrderSender orderSender;
private final StoreClient storeClient;
@Override
public OrderMainDto findOrderMain(OrderSearchCondition condition, Long storeId) {
@@ -75,7 +85,7 @@ public class OrderServiceImpl implements OrderService {
@Override
@Transactional
public void addItemToBasket(OrderItemDto orderItemDto,Long storeId, Long userId) {
public void addItemToBasket(OrderItemDto orderItemDto, Long storeId, Long userId) {
//orderItemOption Entity를 생성한다.
List<OrderItemOption> orderItemOptions = orderItemDto.getOrderItemOptionDtoList()
@@ -93,7 +103,7 @@ public class OrderServiceImpl implements OrderService {
Optional<Order> optionalOrder = orderRepository.findByUserIdAndOrderStatus(userId, OrderStatus.PENDING);
if(optionalOrder.isPresent()){
if(optionalOrder.get().addOrderItem(orderItem)
if(!optionalOrder.get().addOrderItem(orderItem)
.getStoreId().equals(storeId))
throw new OrderException("장바구니에 여러 카페의 메뉴를 담을수 없습니다.");
}else{
@@ -105,21 +115,46 @@ public class OrderServiceImpl implements OrderService {
public FetchOrderDto fetchOrder(Long userId) {
Order order = orderRepositoryCustom.fetchOrder(userId)
.orElseThrow(() -> new OrderException("장바구니 정보를 찾을 수 없습니다."));
GetStoreReseponse store = storeClient.getStore(String.valueOf(order.getStoreId())).getData();
return new FetchOrderDto(order);
List<GetItemResponse> data = storeClient.getItemAndItemOptions(order.getOrderItems().stream()
.map(OrderItem::getItemId)
.filter(Objects::nonNull)
.collect(Collectors.toUnmodifiableList())
).getData();
Map<Long, GetItemResponse> itemMap = data.stream().collect(
Collectors.toMap(
GetItemResponse::getId
, getItemResponse -> getItemResponse
, (t, t2) -> t
)
);
List<FetchOrderDto.OrderItemDto> orderItemDtoList = order.getOrderItems()
.stream().map(orderItem ->
new FetchOrderDto.OrderItemDto(
itemMap.get(orderItem.getItemId())
,orderItem))
.collect(Collectors.toList());
FetchOrderDto fetchOrderDto = FetchOrderDto.builder()
.userId(order.getUserId())
.orderPrice(order.getOrderPrice())
.storeName(store.getName())
.orderItemDtoList(orderItemDtoList)
.build();
return fetchOrderDto;
}
@Override
@Transactional
public void saveOrder(Long userId) {
Order order = orderRepository.findByUserIdAndOrderStatus(userId, OrderStatus.PENDING)
orderRepository.findByUserIdAndOrderStatus(userId, OrderStatus.PENDING)
.orElseThrow(() -> new OrderException("장바구니 정보를 찾을 수 없습니다."))
.setOrderStatus(OrderStatus.PLACED);
try{
orderSender.orderPlaced(OrderSender.KafkaSendOrderDto.createPrimitiveField(order));
}catch (Exception ex){
throw new OrderException(ex.getMessage());
}
.order();
}
@Override

View File

@@ -116,55 +116,12 @@ public class OrderCustomerApiController {
private Long price;
private Long count;
private List<Long> itemOptionIds ;
}
@GetMapping("/orders")
public ResponseEntity fetchOrder(@RequestHeader(value = "user-id") String userId){
FetchOrderDto fetchOrderDto = orderService.fetchOrder(Long.parseLong(userId));
FetchOrderResponse fetchOrderResponse = new FetchOrderResponse(fetchOrderDto);
return ResponseEntity.ok(Result.createSuccessResult(fetchOrderResponse));
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class FetchOrderResponse {
private Long storeId;
private List<_OrderItemDto> _orderItemDtos;
private Long totalPrice;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class _OrderItemDto {
private Long itemId;
private List<Long> itemOptionIds;
private Long price;
private Long count;
public _OrderItemDto(OrderItemDto orderItemDto) {
this.itemId = orderItemDto.getItemId();
this.itemOptionIds = orderItemDto.getOrderItemOptionDtoList()
.stream()
.map(OrderItemOptionDto::getId)
.collect(Collectors.toList());
this.price = orderItemDto.getPrice();
this.count = orderItemDto.getCount();
}
}
public FetchOrderResponse(FetchOrderDto fetchOrderDto){
this.storeId = fetchOrderDto.getStoreId();
this._orderItemDtos = fetchOrderDto.getOrderItemDtoList().stream()
.map(_OrderItemDto::new)
.collect(Collectors.toList());
this.totalPrice = fetchOrderDto.getOrderPrice();
}
return ResponseEntity.ok(Result.createSuccessResult(fetchOrderDto));
}
@PostMapping("/orders")

View File

@@ -1,12 +1,34 @@
package com.justpickup.orderservice.global.client.store;
import com.justpickup.orderservice.global.entity.Yn;
import lombok.Data;
import lombok.*;
@Data
import java.util.List;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class GetItemResponse {
private Long id;
private String name;
private Yn salesYn;
private Long price;
private List<ItemOptionDto> itemOptions;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ItemOptionDto{
private Long id;
private OptionType optionType;
private String name;
}
}

View File

@@ -0,0 +1,14 @@
package com.justpickup.orderservice.global.client.store;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GetStoreReseponse {
private Long id;
private String name;
private String phoneNumber;
}

View File

@@ -0,0 +1,5 @@
package com.justpickup.orderservice.global.client.store;
public enum OptionType {
REQUIRED, OTHER
}

View File

@@ -5,9 +5,15 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "STORE-SERVICE", url = "127.0.0.1:8001/store-service")
import java.util.List;
@FeignClient("store-service")
public interface StoreClient {
@GetMapping("/item/{itemId}")
Result<GetItemResponse> getItem(@PathVariable("itemId") Long itemId);
@GetMapping("/store/{storeId}")
Result<GetStoreReseponse> getStore(@PathVariable(value = "storeId") String storeId);
@GetMapping("/api/customer/items/{itemId}")
Result<List<GetItemResponse>> getItemAndItemOptions(@PathVariable(value = "itemId") List<Long> itemIds);
}

View File

@@ -31,6 +31,7 @@ public class KafkaConfig {
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServerHost+":"+kafkaServerPort);
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// properties.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 5000);
return new DefaultKafkaProducerFactory<>(properties);
}

View File

@@ -3,7 +3,7 @@ server:
spring:
application:
name: order-servcie
name: order-service
config:
import: optional:configserver:http://127.0.0.1:8888
cloud:
@@ -46,7 +46,3 @@ decorator.datasource.p6spy:
kafka:
host: 127.0.0.1
port: 9092
#feign key
token:
feign: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmZWlnbiIsImV4cCI6MTE2NDY5NzY4NzAsImlhdCI6MTY0Njk3Njg3MH0.5x4Nx7oMnpF0_kZpbZsiB1u9eEbQ4IKIhJlEsa3D22cjZjvTHKz57GCz0sgXb_olhSNIVv9xF41A29-XYiFeBQ

View File

@@ -9,6 +9,8 @@ import com.justpickup.orderservice.domain.order.repository.OrderRepository;
import com.justpickup.orderservice.domain.order.service.OrderService;
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
import com.justpickup.orderservice.domain.orderItemOption.dto.OrderItemOptionDto;
import com.justpickup.orderservice.global.client.store.GetItemResponse;
import com.justpickup.orderservice.global.client.store.OptionType;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
@@ -177,15 +179,15 @@ class OrderCustomerApiControllerTest {
void fetchOrder() throws Exception{
//Given
FetchOrderDto fetchOrderDto =
new FetchOrderDto(2L,2L,12000L,2L
new FetchOrderDto(2L,2L,12000L,"저스트카페"
,List.of(
OrderItemDto.of(1L,300L,3000L,2L,
List.of(new OrderItemOptionDto(2L)
,new OrderItemOptionDto(3L))
new FetchOrderDto.OrderItemDto(1L,1L,"카페라테",
List.of(new GetItemResponse.ItemOptionDto(2L, OptionType.REQUIRED,"Hot")
,new GetItemResponse.ItemOptionDto(2L, OptionType.OTHER,"샷추카")),3000L,32L)
)
)
);
given(orderService.fetchOrder(2L)).willReturn(fetchOrderDto);
//When
@@ -201,12 +203,20 @@ class OrderCustomerApiControllerTest {
responseFields(
fieldWithPath("code").description("결과 코드 SUCCESS/ERROR"),
fieldWithPath("message").description("메시지"),
fieldWithPath("data.storeId").description("매장 고유번호"),
fieldWithPath("data.totalPrice").description("총 합계"),
fieldWithPath("data._orderItemDtos[*].itemId").description("상품 고유번호"),
fieldWithPath("data._orderItemDtos[*].price").description("상품 가격"),
fieldWithPath("data._orderItemDtos[*].count").description("상품 갯수"),
fieldWithPath("data._orderItemDtos[*].itemOptionIds[*]").description("아이템 옵션들")
fieldWithPath("data.id").description("주문 고유번호"),
fieldWithPath("data.userId").description("주문한 유저 고유번호"),
fieldWithPath("data.storeName").description("매장 명"),
fieldWithPath("data.orderPrice").description("총 합계"),
fieldWithPath("data.orderItemDtoList[*].id").description("orderItem 고유번호"),
fieldWithPath("data.orderItemDtoList[*].itemId").description("상품 고유번호"),
fieldWithPath("data.orderItemDtoList[*].itemName").description("상품 명"),
fieldWithPath("data.orderItemDtoList[*].orderItemOptionDtoList[*]").description("아이템 옵션들"),
fieldWithPath("data.orderItemDtoList[*].orderItemOptionDtoList[*].id").description("아이템 옵션 고유번호"),
fieldWithPath("data.orderItemDtoList[*].orderItemOptionDtoList[*].optionType").description("아이템 옵션 타입"),
fieldWithPath("data.orderItemDtoList[*].orderItemOptionDtoList[*].name").description("아이템 옵션명"),
fieldWithPath("data.orderItemDtoList[*].price").description("상품 가격"),
fieldWithPath("data.orderItemDtoList[*].count").description("상품 갯수")
)));

View File

@@ -43,7 +43,7 @@ spring:
allow-credentials: true
routes:
- id: order-service
uri: lb://ORDER-SERVCIE
uri: lb://ORDER-SERVICE
predicates:
- Path=/order-service/**
filters:
@@ -51,7 +51,7 @@ spring:
- RewritePath=/order-service/(?<segment>.*),/$\{segment}
- id: store-service
uri: lb://STORE-SERVCIE
uri: lb://STORE-SERVICE
predicates:
- Path=/store-service/**
filters:

View File

@@ -34,6 +34,6 @@ export default {
})
},
getMenu(searchParam){
return axios.get(process.env.VUE_APP_OWNER_SERVICE_BASEURL+'/store-service/api/owner/item',searchParam);
return axios.get(process.env.VUE_APP_OWNER_SERVICE_BASEURL+'/store-service/api/owner/item',{params:searchParam});
},
}

View File

@@ -70,6 +70,9 @@ operation::item-get[snippets='curl-request,http-request,http-response,path-param
=== 상품 조회 (존재하지 않는 상품)
operation::item-get-notExistItemException[snippets='curl-request,http-request,http-response,path-parameters,response-fields']
=== 상품 리스트 조회(구매자)
operation::customer-itemList-get[snippets='curl-request,http-request,http-response,path-parameters,response-fields']
=== 상품 조회(판매자)
operation::owner-item-get[snippets='curl-request,http-request,http-response,path-parameters,response-fields']

View File

@@ -0,0 +1,57 @@
package com.justpickup.storeservice.domain.item.dto;
import com.justpickup.storeservice.domain.category.dto.CategoryDto;
import com.justpickup.storeservice.domain.item.entity.Item;
import com.justpickup.storeservice.domain.itemoption.dto.ItemOptionDto;
import com.justpickup.storeservice.domain.itemoption.entity.ItemOption;
import com.justpickup.storeservice.domain.itemoption.entity.OptionType;
import com.justpickup.storeservice.global.entity.Yn;
import lombok.*;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetItemDto {
private Long id;
private String name;
private Yn salesYn;
private Long price;
private List<ItemOptionDto> itemOptions;
public GetItemDto(Item item) {
this.id = item.getId();
this.name = item.getName();
this.salesYn = item.getSalesYn();
this.price = item.getPrice();
this.itemOptions = item.getItemOptions().stream().
map(ItemOptionDto::new)
.collect(Collectors.toList());
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class ItemOptionDto{
private Long id;
private OptionType optionType;
private String name;
public ItemOptionDto (ItemOption itemOption){
this.id = itemOption.getId();
this.optionType = itemOption.getOptionType();
this.name = itemOption.getName();
}
}
}

View File

@@ -1,9 +1,7 @@
package com.justpickup.storeservice.domain.item.repository;
import com.justpickup.storeservice.domain.category.entity.QCategory;
import com.justpickup.storeservice.domain.item.entity.Item;
import com.justpickup.storeservice.domain.item.entity.QItem;
import com.justpickup.storeservice.domain.itemoption.entity.QItemOption;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
@@ -14,6 +12,10 @@ import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import static com.justpickup.storeservice.domain.category.entity.QCategory.category;
import static com.justpickup.storeservice.domain.item.entity.QItem.item;
import static com.justpickup.storeservice.domain.itemoption.entity.QItemOption.itemOption;
@Repository
@RequiredArgsConstructor
public class ItemRepositoryCustom {
@@ -21,15 +23,25 @@ public class ItemRepositoryCustom {
private final JPAQueryFactory queryFactory;
public Optional<Item> fetchItem(Long itemId){
Item item = queryFactory.selectFrom(QItem.item)
.join(QItem.item.itemOptions, QItemOption.itemOption).fetchJoin()
.join(QItem.item.category,QCategory.category).fetchJoin()
.where(QItem.item.id.eq(itemId))
Item fetchItem = queryFactory.selectFrom(item)
.join(item.itemOptions, itemOption).fetchJoin()
.join(item.category,category).fetchJoin()
.where(item.id.eq(itemId))
.fetchOne();
return Optional.ofNullable(item);
return Optional.ofNullable(fetchItem);
}
public List<Item> getItemAndItemOptions(List<Long> itemIds){
return queryFactory.selectFrom(item)
.join(item.itemOptions,itemOption).fetchJoin()
.where(item.id.in(itemIds))
.fetch();
}
public Page<Item> findItem(Long userId,String word, Pageable pageable){
//count 가져오기

View File

@@ -1,6 +1,7 @@
package com.justpickup.storeservice.domain.item.service;
import com.justpickup.storeservice.domain.item.dto.FetchItemDto;
import com.justpickup.storeservice.domain.item.dto.GetItemDto;
import com.justpickup.storeservice.domain.item.dto.ItemDto;
import com.justpickup.storeservice.domain.itemoption.dto.ItemOptionDto;
import org.springframework.data.domain.Page;
@@ -12,6 +13,7 @@ public interface ItemService {
ItemDto findItemByItemId(Long itemId);
List<GetItemDto> getItemAndItemOptions(List<Long> itemIds);
FetchItemDto fetchItem(Long itemId);
Page<ItemDto> findItemList(Long userId,String word, Pageable pageable);

View File

@@ -3,6 +3,7 @@ package com.justpickup.storeservice.domain.item.service;
import com.justpickup.storeservice.domain.category.entity.Category;
import com.justpickup.storeservice.domain.category.repository.CategoryRepository;
import com.justpickup.storeservice.domain.item.dto.FetchItemDto;
import com.justpickup.storeservice.domain.item.dto.GetItemDto;
import com.justpickup.storeservice.domain.item.dto.ItemDto;
import com.justpickup.storeservice.domain.item.entity.Item;
import com.justpickup.storeservice.domain.item.exception.NotExistItemException;
@@ -46,6 +47,15 @@ public class ItemServiceImpl implements ItemService {
return ItemDto.createWithCategoryItemDtoAndItemOption(findItem);
}
@Override
public List<GetItemDto> getItemAndItemOptions(List<Long> itemIds) {
List<Item> items = itemRepositoryCustom.getItemAndItemOptions(itemIds);
return items.stream()
.map(GetItemDto::new)
.collect(Collectors.toList());
}
@Override
public FetchItemDto fetchItem(Long itemId) {
Item findItem = itemRepositoryCustom.fetchItem(itemId)

View File

@@ -1,6 +1,7 @@
package com.justpickup.storeservice.domain.item.web;
import com.justpickup.storeservice.domain.item.dto.FetchItemDto;
import com.justpickup.storeservice.domain.item.dto.GetItemDto;
import com.justpickup.storeservice.domain.item.dto.ItemDto;
import com.justpickup.storeservice.domain.item.service.ItemService;
import com.justpickup.storeservice.domain.itemoption.dto.ItemOptionDto;
@@ -22,9 +23,16 @@ public class ItemCustomerApiController {
private final ItemService itemService;
@GetMapping("/items/{itemId}")
public ResponseEntity getItemAndItemOptions(@PathVariable("itemId") List<Long> itemId) {
List<GetItemDto> itemList = itemService.getItemAndItemOptions(itemId);
return ResponseEntity.status(HttpStatus.OK)
.body(Result.createSuccessResult(itemList));
}
@GetMapping("/item/{itemId}")
public ResponseEntity getItem(@PathVariable("itemId") Long itemId) {
public ResponseEntity fetchItem(@PathVariable("itemId") Long itemId) {
FetchItemDto fetchItem = itemService.fetchItem(itemId);
GetItemResponse getItemResponse = new GetItemResponse(fetchItem);

View File

@@ -19,7 +19,7 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
//@Component
@RequiredArgsConstructor
public class SqlCommandLineRunner implements CommandLineRunner {

View File

@@ -3,7 +3,7 @@ server:
spring:
application:
name: store-servcie
name: store-service
config:
import: optional:configserver:http://127.0.0.1:8888
cloud:
@@ -18,6 +18,8 @@ spring:
properties:
hibernate:
default_batch_fetch_size: 1000
defer-datasource-initialization: true
datasource:
driver-class-name: org.postgresql.Driver
@@ -25,6 +27,11 @@ spring:
username: postgres
password: admin
sql:
init:
data-locations: classpath:data/data.sql
mode: always
eureka:
client:
@@ -42,7 +49,3 @@ logging:
# jpa query, parameter 로그 (p6spy)
decorator.datasource.p6spy:
enable-logging: true
#feign key
token:
feign: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmZWlnbiIsImV4cCI6MTE2NDY5NzY4NzAsImlhdCI6MTY0Njk3Njg3MH0.5x4Nx7oMnpF0_kZpbZsiB1u9eEbQ4IKIhJlEsa3D22cjZjvTHKz57GCz0sgXb_olhSNIVv9xF41A29-XYiFeBQ

View File

@@ -0,0 +1,77 @@
-- store
INSERT INTO public.store(
store_id, created_at, created_by, last_modified_at, last_modified_by, city, street, zipcode, business_end_time, business_start_time, phone_number, photo_name, photo_path, user_id, map_id)
VALUES (1, now(), 1, now(), 1, '서울시', '광화문로', '123-456', to_timestamp('20:00:00', 'HH24:MI:SS'), to_timestamp('09:00:00', 'HH24:MI:SS'), '010-9418-1307', '사진명1', '/Users/sangbum/Desktop', 1, null);
-- category
INSERT INTO public.category(
category_id, created_at, created_by, last_modified_at, last_modified_by, name, orders, store_id)
VALUES (10, now(), 1, now(), 1, '카테고리1', 0, 1);
INSERT INTO public.category(
category_id, created_at, created_by, last_modified_at, last_modified_by, name, orders, store_id)
VALUES (11, now(), 1, now(), 1, '카테고리2', 1, 1);
-- item
INSERT INTO public.item(
item_id, created_at, created_by, last_modified_at, last_modified_by, name, photo_name, photo_path, price, sales_yn, category_id, store_id)
VALUES (100, now(), 1, now(), 1, '아이템1', '아이템_사진명', '/Users/sangbum/Desktop', 1000, 'Y', 10, 1);
INSERT INTO public.item(
item_id, created_at, created_by, last_modified_at, last_modified_by, name, photo_name, photo_path, price, sales_yn, category_id, store_id)
VALUES (101, now(), 1, now(), 1, '아이템2', '아이템_사진명2', '/Users/sangbum/Desktop', 2000, 'Y', 10, 1);
INSERT INTO public.item(
item_id, created_at, created_by, last_modified_at, last_modified_by, name, photo_name, photo_path, price, sales_yn, category_id, store_id)
VALUES (102, now(), 1, now(), 1, '아이템3', '아이템_사진명3', '/Users/sangbum/Desktop', 3000, 'Y', 11, 1);
-- item option
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1000, now(), 1, now(), 1, 'ICE', 'REQUIRED', 100);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1001, now(), 1, now(), 1, 'HOT', 'REQUIRED', 100);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1002, now(), 1, now(), 1, '샷 추가', 'OTHER', 100);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1003, now(), 1, now(), 1, '투샷 추카', 'OTHER', 100);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1004, now(), 1, now(), 1, 'ICE', 'REQUIRED', 101);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1005, now(), 1, now(), 1, 'HOT', 'REQUIRED', 101);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1006, now(), 1, now(), 1, '샷 추가', 'OTHER', 101);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1007, now(), 1, now(), 1, '투샷 추카', 'OTHER', 101);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1008, now(), 1, now(), 1, 'ICE', 'REQUIRED', 102);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1009, now(), 1, now(), 1, 'HOT', 'REQUIRED', 102);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1010, now(), 1, now(), 1, '샷 추가', 'OTHER', 102);
INSERT INTO public.item_option(
item_option_id, created_at, created_by, last_modified_at, last_modified_by, name, option_type, item_id)
VALUES (1011, now(), 1, now(), 1, '투샷 추카', 'OTHER', 102);

View File

@@ -0,0 +1,134 @@
package com.justpickup.storeservice.domain.item.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.justpickup.storeservice.config.TestConfig;
import com.justpickup.storeservice.domain.favoritestore.repository.FavoriteStoreRepository;
import com.justpickup.storeservice.domain.item.dto.GetItemDto;
import com.justpickup.storeservice.domain.item.dto.ItemDto;
import com.justpickup.storeservice.domain.item.service.ItemService;
import com.justpickup.storeservice.domain.itemoption.entity.OptionType;
import com.justpickup.storeservice.domain.store.repository.StoreRepository;
import com.justpickup.storeservice.global.dto.Code;
import com.justpickup.storeservice.global.entity.Yn;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(ItemCustomerApiController.class)
@Import(TestConfig.class)
@AutoConfigureRestDocs(uriHost = "just-pickup.com", uriPort = 8000)
class ItemCustomerApiControllerTest {
@Autowired
ObjectMapper objectMapper;
@Autowired
MockMvc mockMvc;
@MockBean
ItemService itemService;
@MockBean
private StoreRepository storeRepository;
@MockBean
private FavoriteStoreRepository favoriteStoreRepository;
@Test
@DisplayName("상품리스트 조회")
void getItem() throws Exception {
// GIVEN
List<String> itemIds = List.of("1","2");
List<GetItemDto> willReturnDtoList =
List.of(
GetItemDto.builder()
.id(1L)
.salesYn(Yn.Y)
.price(1500L)
.name("아메리카노")
.itemOptions(List.of(
GetItemDto.ItemOptionDto.builder()
.id(1L)
.name("Hot")
.optionType(OptionType.REQUIRED)
.build()
,GetItemDto.ItemOptionDto.builder()
.id(2L)
.name("add shot")
.optionType(OptionType.OTHER)
.build()
))
.build(),
GetItemDto.builder()
.id(2L)
.salesYn(Yn.Y)
.price(2500L)
.name("카페라테")
.itemOptions(List.of(
GetItemDto.ItemOptionDto.builder()
.id(1L)
.name("Hot")
.optionType(OptionType.REQUIRED)
.build()
,GetItemDto.ItemOptionDto.builder()
.id(2L)
.name("add shot")
.optionType(OptionType.OTHER)
.build()
))
.build()
);
given(itemService.getItemAndItemOptions(any() ))
.willReturn(willReturnDtoList);
String param = String.join(",", itemIds);
// WHEN
ResultActions actions = mockMvc.perform(get("/api/customer/items/{itemIds}", param));
// THEN
actions.andExpect(status().isOk())
.andDo(print())
.andDo(document("customer-itemList-get",
pathParameters(
parameterWithName("itemIds").description("상품 고유 번호리스트")
),
responseFields(
fieldWithPath("code").description("결과 코드 SUCCESS/ERROR"),
fieldWithPath("message").description("메시지"),
fieldWithPath("data[*].id").description("상품 고유 번호"),
fieldWithPath("data[*].name").description("상품 이름"),
fieldWithPath("data[*].salesYn").description("화면 표시 여부 Y/N"),
fieldWithPath("data[*].price").description("상품 가격"),
fieldWithPath("data[*].itemOptions[*].id").description("아이템 옵션 고유 번호"),
fieldWithPath("data[*].itemOptions[*].optionType").description("옵션 타입"),
fieldWithPath("data[*].itemOptions[*].name").description("옵션명")
)
));
}
}

View File

@@ -50,7 +50,6 @@ decorator.datasource.p6spy:
token:
access-expired-time: 3600000
refresh-expired-time: 604800000
secret: my-secret
secret: $2a$10$q42lY7Y18xqrFt1qbODZIO4OMTeOxnrCe7tF3n9bazJinVE7VH5Pi
refresh-token-name: refresh-token
access-token-name: access-token
fegin: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmZWlnbiIsImV4cCI6MTE2NDY5NzY4NzAsImlhdCI6MTY0Njk3Njg3MH0.5x4Nx7oMnpF0_kZpbZsiB1u9eEbQ4IKIhJlEsa3D22cjZjvTHKz57GCz0sgXb_olhSNIVv9xF41A29-XYiFeBQ
access-token-name: access-token