SpringBoot + Redis尝试实现外卖拼单(二)

2021/4/18 2:25:55

本文主要是介绍SpringBoot + Redis尝试实现外卖拼单(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 前言
  • 五、实现后端websocket
    • 5.1 注入所需类
    • 5.2 设计内部类
    • 5.2 创建连接OnOpen
    • 5.3 关闭连接OnClose
    • 5.4 接收和推送消息OnMessage
  • 六、实现效果

前言

在上一篇文章中,我们实现了在前端创建websocket并初始化。在这篇文章中将会详细讲解后端逻辑实现步骤。

五、实现后端websocket

5.1 注入所需类

由于spring bean默认都是单例(singleton),而 websocket每次用户创建连接都会创建一个对象。这就会导致用户创建连接的websocket里需要注入的类为null,然后抛出NPE。

@Component
@ServerEndpoint("/groupbuying/{orderId}/{token}")
public class WebSocketServer extends BaseController {
    private static StringRedisTemplate redisTemplate;
    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate redisTemplate) {
        WebSocketServer.redisTemplate = redisTemplate;
    }

    private static ProductService productService;
    @Autowired
    public void setProductService(ProductService productService) {
        WebSocketServer.productService = productService;
    }
}

5.2 设计内部类

首先,我们需要在WebSocketServer里定义一个内部类UserGroup。这个的主要作用就是绑定用户ID和对应的session。再上一篇文章中的订单ID会被用于作为map的key,用户集合作为value。这样我们就可以通过订单ID找到相对应的用户来推送消息了。

@Component
@ServerEndpoint("/groupbuying/{orderId}/{token}")
public class WebSocketServer extends BaseController {
	private static class UserGroup {
        public Session session;
        public String userId;

        public UserGroup(Session session, String userId) {
            this.session = session;
            this.userId = userId;
        }

        public Session getSession() {
            return session;
        }
    }
    private static Map<String, Set<UserGroup>> sessionPool = new ConcurrentHashMap();
}

5.2 创建连接OnOpen

当客户端加入的时候,我们需要session和uid绑定在一起放入map中。代码如下:

	@OnOpen
    public void onOpen(Session session, @PathParam(value = "orderId") String orderId,
                       @PathParam(value = "token") String token) throws BusinessException {
        String uid = getUid(token);
        // 我接收的时候有空格,需要去空格
        orderId = orderId.trim();
        Set<UserGroup> orderGroupBuying = sessionPool.get(orderId);
        if (orderGroupBuying == null) {
            if (!sessionPool.containsKey(orderId)) {
                orderGroupBuying = new HashSet<>();
                orderGroupBuying.add(new UserGroup(session, uid));
                // 根据订单ID,更新用户集合
                sessionPool.put(orderId, orderGroupBuying);
            }
        } else {
            orderGroupBuying.add(new UserGroup(session, uid));
        }
        log.info(uid + "加入webSocket!订单号: " + orderId + ", 当前人数为" + orderGroupBuying.size());
    }

5.3 关闭连接OnClose

当客户端离开的时候,我们需要将map里对应的UserGroup给移除掉。代码如下:

	@OnClose
    public void onClose(@PathParam(value = "orderId") String orderId,
                        @PathParam(value = "token") String token) throws BusinessException {
        String uid = getUid(token);
        // 我接收的时候有空格,需要去空格
        orderId = orderId.trim();
        Set<UserGroup> orderGroupBuying = sessionPool.get(orderId);
        if (orderGroupBuying != null) {
            for (UserGroup user : orderGroupBuying) {
                if (uid.equals(user.userId)) {
                    orderGroupBuying.remove(user);
                    log.info(uid + "离开webSocket!订单号: " + orderId + ", 当前人数为" + orderGroupBuying.size());
                    break;
                }
            }
        }
    }

5.4 接收和推送消息OnMessage

当接收到消息后,需要更新redis中的数据。最后返回给客户端。

	@OnMessage
    public void onMessage(String message){
        log.info("客户端:" + message + ",已收到");
        if (!StringUtils.isBlank(message)) {
            try {
                // 将消息转换成list
                List<GroupJoinDto> gbList = JsonUtils.jsonToList(message, GroupJoinDto.class);
                if (gbList != null && gbList.size() > 0) {
                    // 获得订单ID
                    String orderId = gbList.get(0).getOrderId();
                    // 获得redis中拼单数据
                    String groupOrder = redisTemplate.opsForValue().get(orderId);
                    LinkedList<GroupBuyingDto> redisList;
                    if (!StringUtils.isBlank(groupOrder)) {
                        redisList = JsonUtils.jsonToLinkedList(groupOrder, GroupBuyingDto.class);
                        for (int i = 0; i < gbList.size(); i++) {
                            for (int j = 0; j < redisList.size(); j++) {
                                GroupJoinDto gb = gbList.get(i);
                                String token = gbList.get(i).getToken();
                                // 验证token合法性
                                String uid = getUid(token);
                                GroupBuyingDto redisDto = redisList.get(j);
                                ProductInfoDto product = productService.findSingleProduct(gb.getProductId());
                                // 如redis中存在拼单数据,更新数据
                                // 更新商品数量
                                if (redisDto.getProductId().equals(gb.getProductId()) && uid.equals(redisDto.getUserId()) && !redisDto.getCount().equals(gb.getCount())) {
                                    redisList.get(j).setCount(gb.getCount());                            
                                } else if (!redisList.stream().map(redisData -> redisData.getProductId()).collect(Collectors.toList()).contains(gb.getProductId())){
                                    redisList.add(new GroupBuyingDto(product.getProductId(), gb.getCount(), uid, product.getCategoryType(),
                                            product.getProductDescription(), product.getProductImg(), product.getProductName(),
                                            product.getProductPrice(), product.getProductStatus(), product.getProductStock()));
                                }
                            }
                        }
                        // 已选商品减少到0
                        for (int i = 0; i < redisList.size(); i++) {
                            GroupBuyingDto redisDto = redisList.get(i);
                            if (!gbList.stream().map(newGB -> newGB.getProductId()).collect(Collectors.toList()).contains(redisDto.getProductId())){
                                redisList.remove(i);
                            }
                        }
                    } else {
                        // 如redis中不存在拼单数据,直接添加
                        redisList = new LinkedList<>();
                        GroupJoinDto joinDto = gbList.get(0);
                        String uid = getUid(joinDto.getToken());
                        ProductInfoDto product = productService.findSingleProduct(joinDto.getProductId());
                        redisList.add(new GroupBuyingDto(product.getProductId(), joinDto.getCount(), uid, product.getCategoryType(),
                                product.getProductDescription(), product.getProductImg(), product.getProductName(),
                                product.getProductPrice(), product.getProductStatus(), product.getProductStock()));
                    }
                    // 保存到redis
                    redisTemplate.opsForValue().set(orderId, JsonUtils.objectToJson(redisList));
                    // 发送消息
                    for (Map.Entry<String, Set<UserGroup>> entry : sessionPool.entrySet()) {
                        if (entry.getKey().equals(orderId)) {
                            Set<UserGroup> users = entry.getValue();
                            for (UserGroup user : users) {
                                user.getSession().getBasicRemote().sendText(JsonUtils.objectToJson(redisList));
                            }
                        }
                    }
                }
            } catch (Busin![在这里插入图片描述](https://www.www.zyiz.net/i/ll/?i=20210417224115859.gif#pic_center)
essException e) {
                e.printStackTrace();
                log.info("token不合法");
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }

六、实现效果

实现效果如下:

拼单实现效果
在下一篇中,我们将会实现通过链接来邀请用户加入websocket实现多个用户一起操作商品的效果。



这篇关于SpringBoot + Redis尝试实现外卖拼单(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程