SpringBoot + Redis尝试实现外卖拼单(二)
作者:互联网
文章目录
前言
在上一篇文章中,我们实现了在前端创建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.icode9.com/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实现多个用户一起操作商品的效果。
标签:orderId,product,拼单,SpringBoot,token,外卖,redisList,uid,String 来源: https://blog.csdn.net/qq_30789421/article/details/115741941