餐饮、零售等应用程序之购物车设计

前言

经常和老婆去某大侠火锅店吃饭,贴在桌子上的桌码就是多人点餐的一个入口。就餐的人都可以实时看到当前桌子上点了什么菜。非常的方便。这不、最近我们也在做这样一个功能,大同小异吧,都是能实时的看到同一个桌子上点的什么菜。那么本文也就针对这个功能记录了下实现的个人思路。前端加餐和申请购物车都是直接请求服务接口,服务接口再转发到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);
        }
    }
}