餐飲、零售等應用程序之購物車設計

前言

經常和老婆去某大俠火鍋店吃飯,貼在桌子上的桌碼就是多人點餐的一個入口。就餐的人都可以實時看到當前桌子上點了什麼菜。非常的方便。這不、最近我們也在做這樣一個功能,大同小異吧,都是能實時的看到同一個桌子上點的什麼菜。那麼本文也就針對這個功能記錄了下實現的個人思路。前端加餐和申請購物車都是直接請求服務接口,服務接口再轉發到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);
        }
    }
}