效果如下用户 【me】和【xiaoming】的共同关注用户【up】上线后,两个粉丝均能实时接收到上线的推送消息。
零、RabbitMQ的安装
1、使用docker拉取RabbitMQ镜像
2、开启RabbitMQ,并暴露端口
docker run -d --hostname rabbit-stomp
--name rabbit-stomp-test2 -p 15672:15672
-p 1883:1883 -p 61613:61613 -p 5672:5672
-p 15674:15674 rabbitmq:3-management
3、安装stomp插件,stomp默认接口15674,
官方使用说明:https://www.rabbitmq.com/web-stomp.html
//进入rabbit操作
[root@codeman ~]# docker container exec -it rabbit-stomp-test2 bash
//开启stomp插件
[root@rabbit-stomp:]/# rabbitmq-plugins enable rabbitmq_web_stomp
Enabling plugins on node rabbit@rabbit-stomp:
rabbitmq_web_stomp
The following plugins have been configured:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_stomp
rabbitmq_web_dispatch
rabbitmq_web_stomp
Applying plugin configuration to rabbit@rabbit-stomp...
The following plugins have been enabled:
rabbitmq_stomp
rabbitmq_web_stomp
started 2 plugins.
安装完毕,打开RabbitMQ管理页面(http://ip:15672 用户名密码默认guest)可以看到stomp
一、jar包准备
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.7.5.RELEASE</version>
</dependency>
二、配置RabbitMQ连接工厂以及消息生产者(封装了RabbitMQ的方法)
1、配置RabbitMQ信息RabiitMQ.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.2.xsd">
<!--引入properties配置文件-->
<bean class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:rabbit.properties</value>
</list>
</property>
</bean>
<!--配置connection-factory,指定连接rabbit server参数 -->
<rabbit:connection-factory id="connectionFactory"
username="${rmq.manager.user}"
password="${rmq.manager.password}"
host="${rmq.ip}"
port="${rmq.port}"
/>
<!--生产者-->
<bean id="messageSender" class="com.codeman.rabbit.message.MessageSender">
<constructor-arg ref="connectionFactory"/>
</bean>
</beans>
rabbit.properties
rmq.ip=192.168.1.111
rmq.producer.num=20
rmq.port=5672
rmq.manager.user=guest
rmq.manager.password=guest
2、消息生产者:MessageSender.java(其实用其自带方法就足够了)
package com.codeman.rabbit.message;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Set;
/** 定制发送信心
* Created with IntelliJ IDEA.
*
* @author 张鸿杰
* Date:2019-04-19
* Time:15:06
*/
public class MessageSender {
private ConnectionFactory connectionFactory;
private AmqpTemplate amqpTemplate;
private RabbitAdmin rabbitAdmin;
private Queue queue;
private Exchange exchange = new FanoutExchange("followmessage");
public MessageSender(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
rabbitAdmin = new RabbitAdmin(connectionFactory);
amqpTemplate = new RabbitTemplate(connectionFactory);
((RabbitTemplate) amqpTemplate).setMessageConverter(new Jackson2JsonMessageConverter()); //可以自定序列化格式为json
}
public Exchange getExchange() {
return exchange;
}
public void setExchange(Exchange exchange) {
this.exchange = exchange;
}
public ConnectionFactory getConnectionFactory() {
return connectionFactory;
}
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
rabbitAdmin = new RabbitAdmin(connectionFactory);
amqpTemplate = new RabbitTemplate(connectionFactory);
}
public AmqpTemplate getAmqpTemplate() {
return amqpTemplate;
}
public void setAmqpTemplate(AmqpTemplate amqpTemplate) {
this.amqpTemplate = amqpTemplate;
}
public void sendDataToQueue(Object message) {
amqpTemplate.convertAndSend(message);
}
/**
* 发送到自己的频道上,粉丝关注后需要绑定自己频道的规则,就能形成广播效果
* @param routingKey 自己的频道
* @param message 消息
* @return void
* @author zhj
* @creed: Talk is cheap,show me the code
* @date 2019/4/19
*/
public void sendDataToQueue(String routingKey, String suffix, Object message) {
amqpTemplate.convertAndSend(exchange.getName(), routingKey + suffix, message);
}
public void sendDataToQueue(String exchange, String routingKey,String suffix, Object message) {
amqpTemplate.convertAndSend(exchange, routingKey+"."+suffix, message);
}
public void createExchange(Exchange exchange) {
rabbitAdmin.declareExchange(exchange);
}
public String createQueue(String queue) {
this.queue = new Queue(queue);
return rabbitAdmin.declareQueue(this.queue);
}
/**
* 创建自己的频道,绑定规则
* @param routKey 设定唯一用户标识.动态类型.编号(用户标识.# /用户标识.*.*)
* @return void
* @author zhj
* @creed: Talk is cheap,show me the code
* @date 2019/4/19
*/
public void bindQueue2Exchange(String queue, String routKey) {
rabbitAdmin.declareBinding(new Binding(
queue,
Binding.DestinationType.QUEUE,
exchange.getName(),
routKey,
null));
}
/**
* 解绑频道,比如退出登录就解绑好友登录提醒
* @param routKey
* @return void
* @author zhj
* @creed: Talk is cheap,show me the code
* @date 2019/4/19
*/
public void removeBinding(String routKey, String queue) {
rabbitAdmin.removeBinding(new Binding(
queue,
Binding.DestinationType.QUEUE,
exchange.getName(),
routKey,
null));
}
/**
* 删除自己的频道
* @param queue 队列
* @return void
* @author zhj
* @creed: Talk is cheap,show me the code
* @date 2019/4/19
*/
public boolean deleQueue(String queue) {
return rabbitAdmin.deleteQueue(queue);
}
/**
* 清空队列中的锁头消息
* @param queueName 队列名
* @param noWait 是否等待
* @return boolean
* @author zhj
* @creed: Talk is cheap,show me the code
* @date 2019/4/20
*/
public void purgeQueue(final String queueName, final boolean noWait) {
rabbitAdmin.purgeQueue(queueName, noWait);
}
}
将RabbitMQ.xml引入applicationContext.xml中
<!--导入RabbitMQ配置文件-->
<import resource="rabbitMQ.xml"/>
至此,后端配置全部完成。
三、前端配置:使用Stomp来实时监听RabbitMQ消息
js依赖:
<!-- https://mvnrepository.com/artifact/org.webjars.bower/stomp-websocket -->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.4</version>
</dependency>
jsp页面common/sockjs_stomp_rabbitmq.jsp
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2019/4/20
Time: 9:42
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head >
<title>测试</title>
<script src="/webjars/stomp-websocket/2.3.4/lib/stomp.min.js"></script>
</head>
<body lang="en">
<script>
var ws = new WebSocket('ws://192.168.1.111:15674/ws');
var client = Stomp.over(ws);
var on_connect = function() {
//data.body是接收到的数据
client.subscribe("me2.loginStatus", function(data) {
var msg = data.body;
alert("收到数据:" + msg);
});
};
var on_error = function() {
console.log('error');
};
client.connect('guest', 'guest', on_connect, on_error, '/');
</script>
</body>
</html>
测试代码:
package com.codeman.rabbit.controller;
import com.codeman.rabbit.entity.User;
import com.codeman.rabbit.message.MessageSender;
import com.codeman.rabbit.service.UserService;
import org.apache.log4j.Logger;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* Created with IntelliJ IDEA.
*
* @author 张鸿杰
* Date:2019-04-17
* Time:20:40
*/
@Controller
public class LoginController {
@Autowired
private MessageSender messageSender;
@Autowired
Logger logger;
@Autowired
private UserService userService;
@GetMapping(value = "/goLogin")
public String goLogin() {
return "redirect:/index.jsp";
}
@PostMapping("/login")
public String login(User user, HttpServletRequest request) {
logger.warn("=====================登录了哦");
User user1 = userService.login(user);
if (user1 != null) {
//TODO 0.创建队列,这些创建队列应该是在注册的时候创建的,这边作为模拟
messageSender.createQueue(user1.getName() + user1.getId()+ ".loginStatus");
messageSender.createQueue(user1.getName() + user1.getId() + ".news");
//1.清空队列中的消息
//清空队列中的消息
messageSender.purgeQueue(user1.getName() + user1.getId() + ".loginStatus", true);
//2.发布登录消息到自己的频道上
messageSender.sendDataToQueue(user1.getName() + user1.getId() ,".loginStatus", user1.getName() + "," + new Date() + ",登录了");
logger.warn("================发送完毕");
request.getSession().setAttribute("user", user1);
return "main";
}
request.setAttribute("loginError", "登录失败!");
return "redirect:/index.jsp";
}
@GetMapping("/logout")
public String logout(HttpServletRequest request) {
User user = (User) request.getSession().getAttribute("user");
request.getSession().removeAttribute("user");
request.setAttribute("loginError", "请重新登录!");
return "forward:/index.jsp";
}
@GetMapping("/follow/{nameNID}")
public void follow(@PathVariable String nameNID, HttpSession session) {
User user1 = (User) session.getAttribute("user");
//关注了人之后,绑定规则到Exchange
//绑定频道接口
messageSender.bindQueue2Exchange(user1.getName() + user1.getId()+ ".loginStatus",nameNID + ".loginStatus");
//新动态一直都会有,要绑在创建用户那边
messageSender.bindQueue2Exchange(user1.getName() + user1.getId() + ".news", nameNID + ".news");
}
@GetMapping("/unFollow/{nameNID}")
public void unFollow(@PathVariable String nameNID, HttpSession session) {
User user1 = (User) session.getAttribute("user");
//取消关注后,解除绑定
messageSender.removeBinding(nameNID + ".loginStatus", user1.getName() + user1.getId()+ ".loginStatus");
messageSender.removeBinding(nameNID + ".news", user1.getName() + user1.getId()+ ".news");
}
@GetMapping("/link2RabbitMQ")
public String link2RabbitMQ() {
return "/common/sockjs_stomp_rabbitmq";
}
}
`-----------------------------------------------------------------
后续的js方法提取封装,后端RabbitMQ发送消息、创建绑定队列等等可以使用AOP进行封装,这边就不详细写出了,大家自己进行拓展吧。
有什么不足之处,还请大家留下评论进行交流。