直播弹幕系统(六)- SpringBoot + STOMP + RabbitMQ(使用MQ替代Spring代理)

news2024/11/19 11:22:23

直播弹幕系统(六)- SpringBoot + STOMP + RabbitMQ(使用MQ替代Spring代理)

  • 前言
  • 一. SpringBoot整合RabbitMQ代理Broker
    • 1.1 RabbitMQ安装STOMP插件(Docker)
    • 1.2 RabbitMQ相关准备
    • 1.3 其他代码
  • 二. 前端整合RabbitMQ
    • 2.1 最终效果
    • 2.2 和Spring代理方式有何不同

前言

上一篇文章整合Stomp替换原生WebSocket方案探究 中,完成了STOMP对原生WebSocket写法的一个平移架构替换。这篇文章则在其基础上做一些优化和操作。

在设计原生WebSocket架构的时候,我们用本地缓存来存储对应的WebSocket。在进行消息交互的时候,还需要自己去拿到对应的Session信息,然后发送。如果是群发消息,还得借助for循环进行遍历发送。

但是我们在整合STOMP的时候,这种群发、对于Session的管理,框架都帮我们做好了。我们只需要关注业务上的操作即可。上篇文章中,我们主要使用的是SimpMessagingTemplate来完成的。同时我们使用Spring本身作为STOMP的一个代理Broker

  • 例如当我们打开了两个会话窗口:就会发现SimpleBrokerMessageHandler下的session就有两份。(AbstractSubscribableChannel.handlers)。也就是说关于Session会话的存储,使用了本地缓存来存储。
    在这里插入图片描述
  • 关于Spring代理,比如路由信息的存储,也是使用了本地缓存来存储。
    在这里插入图片描述

也许,我们改变Session的存储机制比较困难,不容易上手。但是如果从Broker代理层面来考虑,我们是否有办法将代理的事情丢给第三方呢?这样不就可以节省一定的内存空间了吗?

因此本文就来探讨下,如何使用RabbitMQ来作为代理Broker

一. SpringBoot整合RabbitMQ代理Broker

代码都是在上篇文章代码基础上更改的哦。

1.1 RabbitMQ安装STOMP插件(Docker)

1.查看容器docker ps在这里插入图片描述

2.进入rabbitmq容器内部:

docker exec -it b5ec297fb969 /bin/bash

3.执行指令开启web stomp插件:

rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_web_stomp_examples

在这里插入图片描述
4.提交容器为新镜像:

docker commit b5ec297fb969 rabbitmq:stomp

在这里插入图片描述

5.停止原容器:

docker stop rabbitmq

6.开启新容器:

docker run -d --name=rabbitmq2  -p 5617:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 -p 15670:15670 -p 15674:15674 rabbitmq:stomp

7.登录管理界面,检查是否开启成功:15674端口。
在这里插入图片描述

1.2 RabbitMQ相关准备

1.首先还是准备我们的pom依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2.增加一下RabbitMQ相关的信息配置,增加application.yml文件中的配置:

spring:
  rabbitmq:
    username: guest
    password: guest
    # 虚拟主机,默认是/
    virtual-host: /
    # 超时时间
    connection-timeout: 30000
    listener:
      simple:
        # 消费模式,手动
        acknowledge-mode: manual
        # 并发数
        concurrency: 10
        # 最大并发数
        max-concurrency: 20
        # 限流,如果严格控制消费顺序,这里应该填1,数值越大,消费处理速度越快。MQ会把这个数值的消息放到缓存当中。
        # 因此数值越大,内存占用越大,还需要考虑消费的速度
        prefetch: 10
    addresses: 你的RabbitMQ地址

3.我们去RabbitMQManagement控制台上增加一个案例用的交换机和队列。http://你的IP地址:15672/
在这里插入图片描述

添加一个名为stomp-exchange的主题交换机(如果没有,一般上面安装完插件后,会自动生成)。
在这里插入图片描述
添加一个名为stomp-queue的队列(如果没有,一般上面安装完插件后,会自动生成):

在这里插入图片描述
4.绑定交换机和队列:(添加好后,点击队列的名称,就会跳到详情页),路由为live.topic
在这里插入图片描述
按照同样的方式,我们再创建一个队列和交换机,用于测试用:

  • 队列:test-queue
  • 交换机:testTopic-exchange
  • 记得添加绑定关系(这里就不加路由Key了)

对应的在LiveConstants中添加五个常量:

public static final String STOMP_EXCHANGE = "stomp-exchange";
public static final String STOMP_QUEUE = "stomp-queue";
public static final String STOMP_ROUTE_KEY = "live.topic";

public static final String TEST_QUEUE = "test-queue";
public static final String TEST_TOPIC_EXCHANGE = "testTopic-exchange";

这里先重点声明一下:

  • test开头的队列和交换机,是用来前端接收RabbitMQ队列消息的。
  • stomp开头的队列和交换机,是前端发送消息时候,消息发送的目标队列。

而且本文当中,暂时不区分直播间号,因此才使用一个test交换机和队列。会在下一篇文章里面专门写一个动态地队列和交换机。这样就可以区分不同直播间内的消息队列了。

5.紧接着,可以在代码里添加RabbitMQ的配置类RabbitMQConfig,主要做两件事:

  • 加载我们上面创建好的交换机、队列、绑定关系。
  • 自定义我们的消息监听器。
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import zong.constants.LiveConstants;
import zong.service.ChatService;

/**
 * @author Zong0915
 * @date 2022/12/15 下午1:29
 */
@Configuration
@Slf4j
public class RabbitMQConfig {
    @Autowired
    private ConnectionFactory connectionFactory;
    @Autowired
    private ChatService chatService;

    /**
     * 初始化队列
     */
    @Bean
    public Queue stompQueue() {
        return new Queue(LiveConstants.STOMP_QUEUE, true);
    }

    @Bean
    public Queue testQueue() {
        return new Queue(LiveConstants.TEST_QUEUE, true);
    }

    /**
     * 初始化交换机
     */
    @Bean
    TopicExchange stompTopicExchange() {
        return new TopicExchange(LiveConstants.STOMP_EXCHANGE, true, false);
    }

    @Bean
    TopicExchange testTopicExchange() {
        return new TopicExchange(LiveConstants.TEST_TOPIC_EXCHANGE, true, false);
    }

    /**
     * 初始化队列和交换机的绑定信息
     */
    @Bean
    Binding stompBinding() {
        return BindingBuilder.bind(stompQueue()).to(stompTopicExchange()).with(LiveConstants.STOMP_ROUTE_KEY);
    }

    @Bean
    Binding stompBinding2() {
        return BindingBuilder.bind(testQueue()).to(testTopicExchange()).with("");
    }

    @Bean
    SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);

        container.setQueues(stompQueue());
        container.setExposeListenerChannel(true);
        container.setMaxConcurrentConsumers(1);
        container.setConcurrentConsumers(1);
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认
        container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
            byte[] body = message.getBody();
            String msg = new String(body);
            log.info("rabbitmq收到消息 : {}", msg);
            boolean sendToWebsocket = chatService.sendMsg(msg);

            if (sendToWebsocket) {
                System.out.println("消息处理成功! 已经推送到websocket!");
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), true); //确认消息成功消费
            }
        });
        return container;
    }
}

1.3 其他代码

JSON序列化工具类JsonUtil中增加一个反序列化函数:

public static <T> T parseJsonToObj(String json, Class<T> c) {
    if (StringUtils.isBlank(json)) {
        return null;
    }
    try {
        return JSONObject.parseObject(json, c);
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    return null;
}

1.业务类ChatService,主要添加一个入口,将处理好的消息丢给MQ,然后客户端直接监听对应的test交换机即可。

@Autowired
private RabbitTemplate rabbitTemplate;

public boolean sendMsg(String message) {
    if (StringUtils.isBlank(message)) {
        return false;
    }
    ChatMessage chatMessage = JsonUtil.parseJsonToObj(message, ChatMessage.class);
    if (chatMessage == null) {
        return false;
    }
    LiveMessage liveMessage = new LiveMessage();
    liveMessage.setType(MessageType.CHAT.toString());
    liveMessage.setContent("用户 [" + chatMessage.getSender() + "] 说 (来自MQ):" + chatMessage.getContent());
    rabbitTemplate.convertAndSend(LiveConstants.TEST_TOPIC_EXCHANGE, "", JsonUtil.toJSON(liveMessage));
    return true;
}

2.视图层ChatController,添加一个普通的Post请求。咱们不搞@MessageMapping那一套了。

/**
 * 客户端发送消息入口,RabbitMQ入口
 */
@PostMapping(value = "/live/sendMessageToMQ")
public void sendMessage(@RequestBody ChatMessage chatMessage) {
    rabbitTemplate.convertAndSend(LiveConstants.STOMP_EXCHANGE, LiveConstants.STOMP_ROUTE_KEY, JsonUtil.toJSON(chatMessage));
}

3.哦对,这里我们还要加一个序列化配置FastJsonCfg,否则这里参数接收到不到:

import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.charset.StandardCharsets;

@Configuration
public class FastJsonCfg {

    @Bean
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter(){
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setCharset(StandardCharsets.UTF_8);
        config.setDateFormat("yyyy-MM-dd");
        converter.setFastJsonConfig(config);
        return converter;
    }
}

二. 前端整合RabbitMQ

Egg项目架构:
在这里插入图片描述

1.修改前端页面index.tsx文件,整合RabbitMQ相关代码:

const ws = new WebSocket('ws://你的RabbitMQ地址:15674/ws');
const stompMQClient = Stomp.over(ws);

const onMQConnected = () => {
  console.log('RabbitMQ初始化成功');
  // 订阅交换机,就是/exchange/交换机名称
  // 如果订阅队列,就是/queue/队列名称
  stompMQClient.subscribe('/exchange/testTopic-exchange', function(data:any) {
    const res = data.body;
    // 消息体
    const entity = JSON.parse(res);
    const arr :any = [ entity.content ];
    setBulletList((pre: any[]) => [].concat(...pre, ...arr));
    // 消息确认
    data.ack();
  }, { ack: 'client' });
};
// 从前往后的参数:用户名、密码、连接成功回调、连接错误回调、虚拟路径,默认/
stompMQClient.connect('guest', 'guest', onMQConnected, onError, '/');

完整代码:

import React, { useEffect, useState } from 'react';
import { Button, Row, Col, Input } from 'antd';
import { getValueByParam } from '../utils/pageHelper';
import axios from '../utils/axiosHelper';
const SockJS = window.SockJS;
const Stomp = window.Stomp;
const socket = new SockJS('http://localhost:8080/ws');
const stompClient = Stomp.over(socket);
const ws = new WebSocket('ws://你的RabbitMQ地址:15674/ws');
const stompMQClient = Stomp.over(ws);

const roomId = getValueByParam('roomId');
const userId = getValueByParam('userId');

const UserPage = () => {
  const [ message, setMessage ] = useState<string>('');
  const [ bulletList, setBulletList ] = useState<any>([]);
  const [ onlineCount, setOnlineCount ] = useState<number>(0);

  useEffect(() => {
    const onMessageReceived = (msg:any) => {
      const entity = JSON.parse(msg.body);
      const arr :any = [ entity.content ];
      setBulletList((pre: any[]) => [].concat(...pre, ...arr));
      if (entity.type === 'JOIN' || entity.type === 'LEAVE') {
        setOnlineCount(entity.count ?? 0);
      }
    };

    const onConnected = () => {
      // 订阅群发主题
      stompClient.subscribe(`/live/topic_${roomId}`, onMessageReceived);

      const chatMessage = {
        sender: userId,
        type: 'JOIN',
        roomId,
      };

      stompClient.send('/live/sendMessage',
        {},
        JSON.stringify(chatMessage),
      );
    };

    const onError = (error:any) => {
      console.log(error);
    };

    const onMQConnected = () => {
      console.log('RabbitMQ初始化成功');
      // 订阅交换机
      stompMQClient.subscribe('/exchange/testTopic-exchange', function(data:any) {
        const res = data.body;
        const entity = JSON.parse(res);
        const arr :any = [ entity.content ];
        setBulletList((pre: any[]) => [].concat(...pre, ...arr));
        data.ack();
      }, { ack: 'client' });
    };

    stompClient.connect({ userId, roomId }, onConnected, onError);
    stompMQClient.connect('guest', 'guest', onMQConnected, onError, '/');
  }, []);

  const sendMsgToMQ = () => {
    const chatMessage = {
      sender: userId,
      content: message,
      type: 'CHAT',
      roomId,
    };

    axios('', '/live/sendMessageToMQ', chatMessage);
  };

  const sendMsg = () => {
    const chatMessage = {
      sender: userId,
      content: message,
      type: 'CHAT',
      roomId,
    };

    stompClient.send('/live/sendMessage',
      {},
      JSON.stringify(chatMessage),
    );
  };

  return <>
    <Row style={{ width: 2000, marginTop: 200 }}>
      <Col offset={6}>
        <Input onChange={event => setMessage(event.target.value)} />
      </Col>
      <Col>
        <Button
          onClick={sendMsg}
          type='primary'
        >发送弹幕</Button>
        <Button
          onClick={sendMsgToMQ}
          type='primary'
        >发送弹幕整合MQ</Button>
      </Col>
      <Col style={{ marginLeft: 100 }}>
        {'在线人数: ' + onlineCount}
      </Col>
      <Col style={{ marginLeft: 10 }}>
        <div style={{ border: '1px solid', width: 500, height: 500 }}>
          {bulletList.map((item: string, index: number) => {
            return <Row key={index}>
              {item}
            </Row>;
          })}
        </div>
      </Col>
    </Row>
  </>;
};

export default UserPage;

2.由于前端发起了一个axios请求,因此需要在Egg后端router.ts文件中增加一个路由:

router.post('/live/sendMessageToMQ', controller.home.sendMessageToMQ);

3.对应Controller增加函数:

async sendMessageToMQ() {
    const entity = this.ctx.request.body;
    const preUrl = 'http://localhost:4396/api';
    const result = await this.ctx.curl(`${preUrl}/live/sendMessageToMQ`, {
      method: 'POST',
      contentType: 'application/json;',
      timeout: 30000,
      headers: {
        host: preUrl,
      },
      data: JSON.stringify(entity),
      dataType: 'json',
});

4.增加一个代理配置,在config目录下增加一个文件夹envConfig,并添加代理配置:
在这里插入图片描述
文件内容:如果以/api为前缀,那么会将请求转发到http://localhost:8080,并且/api这个路径会重写为空字符串。

{
    "urlPrefix": "",
    "proxy": {
        "/api": {
            "target": "http://localhost:8080",
            "changeOrigin": true,
            "pathRewrite": {
                "^/api": ""
            },
            "secure": false
        }
    }
}

5.修改config.dafult.ts文件,增加下面的配置:
在这里插入图片描述

代码:

config.httpProxy = {
  ...envInfo.proxy,
  request: {
    enableProxy: true,
  },
};

2.1 最终效果

访问页面:
在这里插入图片描述
点击发送弹幕按钮:发送666
在这里插入图片描述

点击发送弹幕整合MQ按钮:发送 “MQMQ”:
在这里插入图片描述

2.2 和Spring代理方式有何不同

说下Spring代理的大致流程:

  1. 前端和Spring代理Broker之间建立Socket链接。

  2. 前端可以向路径A发送一条信息。服务器可以通过 @MessageMapping("A")的方式拿到这个消息。
    在这里插入图片描述

  3. 服务器再通过SimpMessagingTemplate,通过Spring代理向客户端返回数据。
    在这里插入图片描述

再说下RabbitMQ代理有何不同。

  1. 不再使用SimpMessaging那一套东东了。可以通过接口请求。直接将数据打到RabbitMQ中。
    在这里插入图片描述
  2. 通过对RabbitMQ监听器的设置,可以监听到客户端发送的消息,然后再将对应的消息发送到对应的直播间所订阅的队列/交换机中。
    在这里插入图片描述
    3.发送的方式:
    在这里插入图片描述
    4.前端直接监听MQ
    在这里插入图片描述

像这样。如果想替换Spring代理的写法,那就应该避免使用SimpMessagingTemplate这类方式将消息广播出去。我们可以继续使用RabbitMQ来广播消息。前端只需要监听RabbitMQ即可。

当然,本篇文章还存在一个问题就是:不区分直播间号,所有直播间共享一个消息队列。因为案例代码使用的是写死的队列和交换机(test)。因此在下一篇文章,会探讨如何让不同直播间动态地绑定交换机和队列。同时后端还能动态创建队列和交换机并注入到容器中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/110856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Prometheus_原理架构-安装部署

文章目录1、prometheus简介常见监控软件优势2、组成图讲解3、安装和配置3.1 容器安装3.2 二进制安装3.3 配置热加载1、prometheus简介 是一个监控软件–》监控容器非常好&#xff0c;也可以监控其他的非容器的机器的业务&#xff0c;例如&#xff1a;MySQL&#xff0c;nginx&am…

locksupport的park和unpark

locksupport是什么 LockSupport是一个线程阻塞工具类&#xff0c;所有的方法都是静态方法&#xff0c;可以让线程在任意位置阻塞&#xff0c;当然阻塞之后肯定得有唤醒的方法。 有什么用 接下面我来看看LockSupport有哪些常用的方法。主要有两类方法&#xff1a;park和unpar…

java:jackson 二:Custom Deserialization in Jackson

java&#xff1a;jackson 二&#xff1a;Custom Deserialization in Jackson 1 前言 jackson支持自定义反序列化器&#xff0c;参考文档地址如下&#xff1a; https://www.baeldung.com/jacksonhttps://www.baeldung.com/jackson-deserialization依赖如下&#xff08;这里使…

基于FPGA的幅频均衡带通滤波器的设计

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 数字通信系统中&#xff0c;由于多径传输、信道衰落等影响&#xff0c;在接收端会产生严重的码间干扰&#xff0c;增大误码率。为了克服码间干扰&#xff0c;提高通信系统的性能&#xff0c;在接…

记录Android Jni编译过程

Gradle配置 我们主要看这个配置文件里面吧&#xff0c;这里面有关于ndk配置的选项。 大概介绍一下&#xff0c;这里面一些字段是干嘛的。 我们看&#xff0c;这里面有两个相仿的字段&#xff0c;都是externalNativeBuild字段&#xff0c;但是位于两个不同的位置&#xff0c;其…

小程序02/小程序 响应式单位rpx 、image组件概念说明 和 mode属性介绍

一. 响应式单位rpx rpx 说明 rpx: 规定不管屏幕为多少px , 100%的屏幕宽度就是750rpx 100% 屏幕的宽度 750rpx rpx响应单位 rpx是微信小程序独有的&#xff0c;解决屏幕自适应的尺寸单位 可以根据屏幕宽度进行自适应&#xff0c;不论大小屏幕&#xff0c;规定屏幕宽为750…

数据首发!空气悬挂前装搭载率破1%,明年冲刺70万套

新能源智能化的合力变革&#xff0c;带动汽车行业进入新的发展周期&#xff1a;如何进一步提升整车轻量化、驾驶和乘坐的安全和体验。这其中&#xff0c;乘用车悬挂系统也在发生新的变化。 此前&#xff0c;除了传统固定式金属螺旋弹簧悬挂&#xff0c;主动悬架系统的前装上车主…

学计算机网络太难?原来方法没用对...

计算机世界里的三座大山: 计算机网络&#xff0c;操作系统&#xff0c;算法与数据结构。跨过去的人都是神一样的存在了。 学计算机网络也要讲究学习方法 从实际案例出发&#xff08;比如我们在浏览器输入一个网址到展示出内容中间发生了什么事情&#xff09; 计算机网络出现的…

简单记录一下怎么看package.json文件

首先每个vue工程文件从仓库克隆代码下来的时候&#xff0c;一般都会包含这个文件&#xff0c;这个文件非常重要&#xff0c;package.json包含了关于项目重要信息&#xff0c;如下图所示 其中包含了name、version、description、author、scripts、dependencies、devDependencies…

Django基础

Django 1.项目的创建 创建项目&#xff1a; 删除一些内容&#xff1a; settings.py中&#xff1a; 2.默认项目文件的介绍 3.APP 创建APP&#xff1a; APP文件介绍&#xff1a; 4.快速上手 APP注册&#xff1a; 在app中找到apps.py&#xff1a; 在django的项目setti…

海量数据处理

1.给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址&#xff1f; 如何找到top K的IP&#xff1f; 思路&#xff1a;&#xff08;哈希切割&#xff09; 1.ip本身就是一个字符串&#xff0c;先把ip变成一个整数hash(ip) 2.文件的下标index…

用知识图谱打开梁山好汉一百单八将

说起《水浒传》大家一定不会陌生&#xff0c;《水浒传》是一部以描写古代农民起义为题材的长篇小说&#xff0c;全书描写北宋末年以宋江为首的108位好汉在梁山聚义&#xff0c;之后接受招安、四处征战的故事。它的一大看点便是其人物的描写&#xff0c;用金人瑞曾评的话说&…

算法之贪心算法

目录 前言&#xff1a; 如何理解贪心算法&#xff1f; 贪心算法的实战分析 分糖果 钱币找零 问题 总结&#xff1a; 参考资料 前言&#xff1a; 贪心算法有很多经典的应用&#xff0c;比如霍夫曼编码&#xff08;Huffman Coding&#xff09;、Prim 和 Kruskal 最小生成树…

Windows下Jenkins常见问题汇总

Jenkins运行时&#xff0c;场景遇到一些奇怪的问题&#xff0c;特别是在Powershell下能运行的命令&#xff0c;在Jenkins下运行就不行。 原因在于其特殊性&#xff1a;Jenkins执行脚本时&#xff0c;不是用当前Windows的登录账户执行的&#xff0c;所以当前登录账户的很多属性&…

数据库,计算机网络、操作系统刷题笔记16

数据库&#xff0c;计算机网络、操作系统刷题笔记16 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

【码极客精讲】二维数组

二维数组本质上是以数组作为数组元素的数组&#xff0c;即“数组的数组”&#xff0c;类型说明符 数组名[常量表达式][常量表达式]。二维数组又称为矩阵&#xff0c;行列数相等的矩阵称为方阵。对称矩阵a[i][j] a[j][i]&#xff0c;对角矩阵&#xff1a;n阶方阵主对角线外都是…

Go语言web极速入门-(Gin+Mysql实现后端接口)

文章目录Gin框架github地址 ⬅️点击此处安装Gin及安装框架超时问题解决参考地址 ⬅️点击此处Mysql操作建表增加测试数据代码实现需要导的包数据库连接函数及常量、数据传输结构体业务代码:获取一条信息(GET请求)业务代码:获取多条信息(GET请求)业务代码:保存一条信息(POST请求…

指令重排现象,多线程情况下,你的代码执行顺序可能不是顺序执行,结果会不一致

一、思考多线程情况下&#xff0c;程序执行顺序是否是按顺序执行 首先定义x 0; y 0; a 0; b 0;然后思考a 1;x b;两行代码谁先执行问题&#xff1f; 二、实战测试 2.1 测试逻辑 首先默认为x 0; y 0; a 0; b 0;然后开启两个线程&#xff1b;线程1执行&#xff1a;a…

java 瑞吉外卖day6 移动端 套餐 菜品展示 购物车加减,清空,用户下单

导入用户地址簿相关功能代码 菜品展示 购物车模块 加入购物车&#xff1a; PostMapping("/add") public R add(RequestBody ShoppingCart shoppingCart){//获取当前线程用户的id并设置到shoppingCart中Long currentId BaseContext.getCurrentId();shoppingCart.set…

使用3种不同的算法从倾斜风速计中检索3个风分量(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…