餐飲、零售等應用程序之購物車設計
前言
經常和老婆去某大俠火鍋店吃飯,貼在桌子上的桌碼就是多人點餐的一個入口。就餐的人都可以實時看到當前桌子上點了什麼菜。非常的方便。這不、最近我們也在做這樣一個功能,大同小異吧,都是能實時的看到同一個桌子上點的什麼菜。那麼本文也就針對這個功能記錄了下實現的個人思路。前端加餐和申請購物車都是直接請求服務接口,服務接口再轉發到Socket服務,前端只要保持和Socket正常的接收消息刷新購物車即可。接下的代碼片段主要是表示個人的思路,一些業務具體實現代碼沒有貼出來。
1.購物車實體類關係
模擬了現實超市購物、結合實現業務場景,把購物車模型和具體的購物車給分離開來。AbstractCart
為模擬的購物車、分配了購物車的基本屬性.
1.1 重要基本屬性介紹
1.1.1 active
表示是否激活,可以認為當前實體類序列化實體是否存儲到Db,在實際業務場景中,當給你分配了購物車,就存儲到了Db/ 設置為 1
1.1.2 cartId
表示即將或者已經分配的購物車Id(結合當前用戶、和實際場景),也用於到Db里去檢索。一旦分配了,後續的對購物車的操作都基本它
1.1.3 cartType
購物車類型、前者說到,我給購物車分配了很多不同類型的購物車,不同類型的購物車有它自己的屬性
public enum CartTypeEnum {
/**
* 購物車類型
*/
MULTI_CART(1, "多人點餐"),
}
//多人點餐購物車模型、它有 就餐方式、桌碼信息等
public class MultiTableCart extends AbstractCart {
/**
* 就餐方式
*/
private Integer eatType;
/**
* 桌子信息
*/
private TableInfo tableInfo;
}
1.1.4 bags
這個屬性重要、理解了這個屬性,那麼整個購物車的設計都明白了。我們在去超市購物的時候,一般都是推着一個小車子,結合我們現實的業務場景,可能一個我們好幾個都共用這一個車子,但是還要區分我們各自的商品,所以在購物車的基礎上再設計出購物袋的模型。即一個購物車裏面可以放N個購物車袋(為每個人分配),每個購物袋放對應人的商品 這樣就能方便的區分出每個人買的商品。
public class Bag implements Serializable {
private static final long serialVersionUID = -2114201035393187375L;
/**
* 購物袋Id 初始化的時候 IdUtil
*/
private String id;
/**
* 購物袋名 沒有什麼實在意義,就是給購物車加個名字
*/
private String name;
/**
* 當前購物袋所屬用戶
*/
private User user;
/**
* 購物袋裡的商品
*/
private List<Item> items = new LinkedList<>();
}
到此購物車的模型就設計好了、那麼具體看Service是怎麼處理的吧。
2. 購物車業務
2.1 申請購物車
第一步就要檢測當前用戶有沒有購物車。由業務區分如果當前用戶沒有購物車,是否由場景申請出一個購物車。
定義一個申請購物車的接口、和一個具體實現。那麼再定義一個申請購物車工廠,給業務提供具體的申請購物車業務實現類。
@Component
public class CartFactory implements BeanPostProcessor {
/**
* 定義購物車分配方式
*/
private final ConcurrentHashMap<CartTypeEnum, ApplyCartService<?>> cartCols = new ConcurrentHashMap<>();
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ApplyCartService) {
ApplyCartService<?> applyCartService = (ApplyCartService<?>) bean;
cartCols.put(applyCartService.cartType(), applyCartService);
}
return bean;
}
/**
* get a applyCartService
* @return
*/
public ApplyCartService<?> applyCartService(CartTypeEnum cartType) {
return cartCols.get(cartType);
}
}
用戶接口向外提供申請購物車接口方法
@Service
public class CustomCartServiceImpl implements CustomCartService {
//購物車工廠
@Resource
CartFactory cartFactory;
@Override
public Result<AbstractCart> applyCart(ApplyCartDTO applyCart) {
//由前端告知想要申請的購物車類型
CartTypeEnum typeEnum = CartTypeEnum.CART_TYPE_MAPPINGS.get(applyCart.getCartType());
if (Objects.isNull(typeEnum)) {
return Result.failureData("非法操作!");
}
//由購物車功工廠提供分配購物車實例
ApplyCartService<AbstractCart> abstractCartApplyCartService = (ApplyCartService<AbstractCart>) cartFactory.applyCartService(typeEnum);
Result<AbstractCart> cartResult = abstractCartApplyCartService.applyCart(applyCart);
if (!cartResult.isStatus()) {
return cartResult;
}
//父類的引用
AbstractCart cart = cartResult.getData();
//需要激活、且沒有激活過、存入Db
if (Objects.equals(cart.getActive(), OnOffEnum.OFF.getCode())) {
//激活購物車
cart.setActive(OnOffEnum.ON.getCode());
cartService.flushDb(cart);
}
return cartResult;
}
}
2.2 購物車具體的操作
2.2.1 抽象購物車操作的接口定義
public interface CartService<T extends AbstractCart> {
/**
* 向某個購物車裡添加商品
* @param t
* @param bagId
* @param clientIdentity
* @param items
* @return
*/
Result<T> insertItem(T t, String bagId, ClientIdentity clientIdentity, List<Item> items);
/**
* 刪除某些商品
* @param t
* @param bagId
* @param itemUniqIds
* @return
*/
Result<T> updateBags(T t, String bagId, List<ItemNumDTO> itemUniqIds);
/**
* 獲取購物車
* @param shopId
* @param cartId
* @return
*/
Result<T> getCart(Long shopId, String cartId);
/**
* 刷新到存儲
* @param t
* @return
*/
Result<T> flushDb(T t);
/**
* 清空購物車
* @param t
* @return
*/
Result<T> clearUpCart(T t);
/**
* 銷毀購物車
* @param t
* @return
*/
Result<T> destroy(T t);
}
2.2.2 業務購物車操作接口的定義
public interface CustomCartService {
/**
* 用於檢測購物車是否激活
* @param applyCart
* @return
*/
Result<AbstractCart> checkCartActive(ApplyCartDTO applyCart);
/**
* 獲取一個購物車
* @param applyCart
* @return
*/
Result<AbstractCart> applyCart(ApplyCartDTO applyCart);
/**
* 刪除某些商品
* @param addBagItem
* @return
*/
Result<String> updateBags(AddBagItemDTO addBagItem);
/**
* 清空購物車
* @param shopId
* @param cartId
* @return
*/
Result<String> clearUpCart(Long shopId, String cartId);
/**
* 銷毀購物車
* 銷毀的人
* @param userId
* @param orderNo
* @param cart
* @return
*/
void destroyCartAndPushSocket(Long userId, String orderNo, AbstractCart cart);
}
2.2.3 業務購物車操作事件監聽
public class CartEvent extends ApplicationEvent {
private static final long serialVersionUID = -8248110180060447856L;
/**
* 購物車數據
*/
private final AbstractCart cart;
/**
* 其他參數
*/
private final Object[] objects;
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public CartEvent(Object source, AbstractCart cart, Object... objects) {
super(source);
this.cart = cart;
this.objects = objects;
}
/**
* getter
* @return
*/
public AbstractCart getCart() {
return cart;
}
/**
* 其他參數
* @return
*/
public Object[] getObjects() {
return objects;
}
}
@Component
public class CartListener {
//一個推送服務、推送Socket
@Resource
SocketCenterSpi socketCenterSpi;
/**
* 添加商品
*/
@EventListener(
classes = CartEvent.class,
condition = "#cartEvent.getSource() == T(com.zm.baking.biz.service.cart.event.CartEventSourceEnum).ADD_ITEM " +
"and #cartEvent.getCart().getCartType() == T(com.zm.baking.common.enums.CartTypeEnum).MULTI_CART.getCode()"
)
public void afterInsertCartItemToSocket(CartEvent cartEvent) {
AbstractCart cart = cartEvent.getCart();
this.pushTag(Collections.singletonList(cart.getCartId()), JSON.toJSONString(cart));
}
/**
* 更新商品
*/
@EventListener(
classes = CartEvent.class,
condition = "#cartEvent.getSource() == T(com.zm.baking.biz.service.cart.event.CartEventSourceEnum).UPDATE_ITEM " +
"and #cartEvent.getCart().getCartType() == T(com.zm.baking.common.enums.CartTypeEnum).MULTI_CART.getCode()"
)
public void afterDelCartItemToSocket(CartEvent cartEvent) {
AbstractCart cart = cartEvent.getCart();
this.pushTag(Collections.singletonList(cart.getCartId()), JSON.toJSONString(cart));
}
/**
* 清空商品
*/
@EventListener(
classes = CartEvent.class,
condition = "#cartEvent.getSource() == T(com.zm.baking.biz.service.cart.event.CartEventSourceEnum).CLEAR_UP " +
"and #cartEvent.getCart().getCartType() == T(com.zm.baking.common.enums.CartTypeEnum).MULTI_CART.getCode()"
)
public void clearUpCartItemToSocket(CartEvent cartEvent) {
AbstractCart cart = cartEvent.getCart();
this.pushTag(Collections.singletonList(cart.getCartId()), JSON.toJSONString(cart));
}
/**
* 銷毀購物車
*/
@EventListener(
classes = CartEvent.class,
condition = "#cartEvent.getSource() == T(com.zm.baking.biz.service.cart.event.CartEventSourceEnum).DESTROY " +
"and #cartEvent.getCart().getCartType() == T(com.zm.baking.common.enums.CartTypeEnum).MULTI_CART.getCode()"
)
public void distroyCartToSocket(CartEvent cartEvent) {
AbstractCart cart = cartEvent.getCart();
Object[] objects = cartEvent.getObjects();
Long userId = (Long) objects[0];
String orderNo = (String)objects[1];
SubmitOrder submitOrder = new SubmitOrder(userId.toString(), orderNo);
pushTag(Collections.singletonList(cart.getCartId()), submitOrder.toString());
}
/**
* 推送數據
* @param tags
* @param message
*/
private void pushTag(List<String> tags, String message) {
//推送數據
final ClientTagPushRequest pushRequest = new ClientTagPushRequest();
pushRequest.setMessage(message);
pushRequest.setTags(tags);
socketCenterSpi.tagPush(pushRequest);
}
static class SubmitOrder {
private String submitUserId;
private String orderNo;
public SubmitOrder() {
}
public SubmitOrder(String submitUserId, String orderNo) {
this.submitUserId = submitUserId;
this.orderNo = orderNo;
}
public String getSubmitUserId() {
return submitUserId;
}
public void setSubmitUserId(String submitUserId) {
this.submitUserId = submitUserId;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
}