7、RocketMQ+Spring Cloud Stream环境搭建
7.1 整体结构示意图
生产者与消费者携带topic连接nameserver,nameserver通过topic(主题)返回broker,生产者消费者直接连接broker
index.html通过ajax提交数据到producer并通过websocket(携带唯一标识)连接到consumer,生产者接收到消息提交到broker,消费者或者消息并处理,处理完成通过websocket通知前端index.html页面。
7.2 工程搭建
首先新建maven父工程 —rocketmq
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xkp.lesson</groupId>
<artifactId>rocketmq3</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>producer</module>
<module>eureka</module>
<module>consumer</module>
</modules>
<packaging>pom</packaging>
<properties>
<cloud.version>Greenwich.SR2</cloud.version>
<boot.version>2.1.6.RELEASE</boot.version>
<mybatis.version>2.0.1</mybatis.version>
<mysql.version>5.1.38</mysql.version>
<druid.version>1.1.10</druid.version>
<lombok.version>1.18.6</lombok.version>
<log4j.version>1.2.17</log4j.version>
<junit.version>4.12</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
创建Eureka模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rocketmq3</artifactId>
<groupId>xkp.lesson</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka</artifactId>
<dependencies>
<!--eureka-server服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
EurekaServerApplication.java
package xkp.lesson.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
//EurekaServer服务器端启动类,接受其它微服务注册进来
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
application.yml
server:
port: 7001
eureka:
instance:
#eureka服务端的实例名称
hostname: localhost
client:
#false表示不向注册中心注册自己
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
spring:
application:
name: eurka-server
创建producer生产者模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rocketmq3</artifactId>
<groupId>xkp.lesson</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>producer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>
</project>
application.yml
server:
port: 7011
eureka:
client:
service-url:
# 客户端注册进eureka服务列表内,入驻的地址必须与EurekaServer的defaultZone地址一致
defaultZone: http://localhost:7001/eureka
instance:
instance-id: producer-server
prefer-ip-address: true
spring:
application:
name: producer-server
cloud:
stream:
rocketmq:
binder:
name-server: 192.168.113.189:9876
bindings:
output:
# 指定topic
destination: stream-test-topic
# 微服务信息的描述
info:
# 工程名称
app.name: newcapec-springcloud
# 公司名称
company.name: www.newcapec.com.cn
project.artifactId: springcloud-provider-7011
project.version: v1.0.1
CorsConfig.java
package xkp.lesson.producer.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 1允许任何域名使用
corsConfiguration.addAllowedOrigin("*");
// 2允许任何头
corsConfiguration.addAllowedHeader("*");
// 3允许任何方法(post、get等)
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
}
ProducerController.java
package xkp.lesson.producer.controller;
import com.alibaba.fastjson.JSON;
import netscape.javascript.JSObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import xkp.lesson.producer.entity.MQMessage;
import xkp.lesson.producer.entity.StudentInfo;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
/**
*
*/
@RestController
public class ProducerController {
@Resource
private MessageChannel output; //消息发送管道
@Autowired
private HttpServletRequest request;
@PostMapping("/test-stream")
public boolean testStream(@RequestBody StudentInfo studentInfo){
//String serial = UUID.randomUUID().toString();
MQMessage<StudentInfo> msg = new MQMessage<>();
msg.setData(studentInfo);
String token = request.getHeader("token");
msg.setToken(token);
String msgStr = JSON.toJSONString(msg);
return output.send(MessageBuilder.withPayload(msgStr).build());
}
}
MQMessage.java
package xkp.lesson.producer.entity;
public class MQMessage<T> {
//用户唯一识别token,用于标识是谁发送的数据
private String token;
//消息数据
private T data;
public MQMessage() {
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "MQMessage{" +
"token='" + token + '\'' +
", data=" + data +
'}';
}
}
StudentInfo.java
package xkp.lesson.producer.entity;
import java.io.Serializable;
public class StudentInfo implements Serializable {
private String name;
private Integer age;
public StudentInfo() {
}
public StudentInfo(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "StudentInfo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
ProducerServerApplication.java
package xkp.lesson.producer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
@SpringBootApplication
@EnableEurekaClient
@EnableBinding(Source.class)
public class ProducerServerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerServerApplication.class, args);
}
}
创建消费者模块consumer
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rocketmq3</artifactId>
<groupId>xkp.lesson</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer</artifactId>
<dependencies>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--Eureka Client 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-stream-rocketmq -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>
</project>
websocket搭建流程
1、引入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、WebSocketConfig.java
package xkp.lesson.consumer.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
2、ReceiptWebSocket.java
package xkp.lesson.consumer.websocket;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint(value = "/receiptMsg/{token}")
public class ReceiptWebSocket {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在线人数
*/
public static int onlineNumber = 0;
/**
* 以用户的姓名为key,WebSocket为对象保存起来
*/
private static Map<String, ReceiptWebSocket> clients = new ConcurrentHashMap<String, ReceiptWebSocket>();
/**
* 会话
*/
private Session session;
/**
* 用户名称
*/
private String token;
/**
* 建立连接
*
* @param session
*/
@OnOpen
public void onOpen(@PathParam("token") String token, Session session)
{
onlineNumber++;
logger.info("现在来连接的客户id:"+session.getId()+"用户名:"+token);
this.token = token;
this.session = session;
logger.info("有新连接加入! 当前在线人数" + onlineNumber);
clients.put(token, this);
}
@OnError
public void onError(Session session, Throwable error) {
logger.info("服务端发生了错误"+error.getMessage());
//error.printStackTrace();
}
/**
* 连接关闭
*/
@OnClose
public void onClose()
{
onlineNumber--;
//webSockets.remove(this);
clients.remove(token);
logger.info("有连接关闭! 当前在线人数" + onlineNumber);
}
/**
* 收到客户端的消息
*
* @param message 消息
* @param session 会话
*/
@OnMessage
public void onMessage(String message, Session session)
{
try {
logger.info("来自客户端消息:" + message+"客户端的id是:"+session.getId());
System.out.println("------------ :"+message);
JSONObject jsonObject = JSON.parseObject(message);
String textMessage = jsonObject.getString("message");
String fromuserId = jsonObject.getString("userId");
String touserId = jsonObject.getString("to");
//如果不是发给所有,那么就发给某一个人
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
Map<String,Object> map1 = Maps.newHashMap();
map1.put("messageType",4);
map1.put("textMessage",textMessage);
map1.put("fromuserId",fromuserId);
if(touserId.equals("All")){
map1.put("touserId","所有人");
sendMessageAll(JSON.toJSONString(map1),fromuserId);
}
else{
map1.put("touserId",touserId);
System.out.println("开始推送消息给"+touserId);
sendMessageTo(JSON.toJSONString(map1),touserId);
}
}
catch (Exception e){
e.printStackTrace();
logger.info("发生了错误了");
}
}
public void sendMessageTo(String message, String TouserId) throws IOException {
for (ReceiptWebSocket item : clients.values()) {
// System.out.println("在线人员名单 :"+item.userId.toString());
if (item.token.equals(TouserId) ) {
item.session.getAsyncRemote().sendText(message);
break;
}
}
}
public void sendMessageAll(String message,String FromuserId) throws IOException {
for (ReceiptWebSocket item : clients.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
public static synchronized int getOnlineCount() {
return onlineNumber;
}
}
3、MQMessage.java与StudentInfo.java同上
4、ConsumerController.java
package xkp.lesson.consumer.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import xkp.lesson.consumer.entity.MQMessage;
import xkp.lesson.consumer.entity.StudentInfo;
import xkp.lesson.consumer.websocket.ReceiptWebSocket;
import javax.annotation.Resource;
import java.io.IOException;
@RestController
public class ConsumerController {
@Autowired
private ReceiptWebSocket receiptWebSocket;
@StreamListener(Sink.INPUT)
public void input(Message<String> message) throws IOException, InterruptedException {
MQMessage<StudentInfo> msg =
JSON.parseObject(message.getPayload(), new TypeReference< MQMessage<StudentInfo>>() {});
//System.out.println("接收到的消息: "+message.getPayload());
String token = msg.getToken();
Thread.sleep(5*1000);
receiptWebSocket.sendMessageTo("true",token);
}
}
application.yml
server:
port: 7021
eureka:
client:
service-url:
# 客户端注册进eureka服务列表内,入驻的地址必须与EurekaServer的defaultZone地址一致
defaultZone: http://localhost:7001/eureka
instance:
instance-id: consumer-server
prefer-ip-address: true
spring:
application:
name: consumer-server
cloud:
stream:
rocketmq:
binder:
name-server: 192.168.113.189:9876
bindings:
input:
destination: stream-test-topic
# rocketmq一定要设置group(随便写) 其他的mq可留空
group: binding-group
前端测试页面index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
<script src="../static/jquery-1.11.3/jquery.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<h3>hello socket</h3>
token:<input type="text" id="token" value="123"/><br/>
name:<input type="text" id="name"/><br/>
age:<input type="text" id="age"/><br/>
<button onclick="submitInfo()">提交</button><br/>
<span id="status">消息提交状态</span>
</body>
<script>
var socket;
window.onload = function(){
openSocket();
}
function submitInfo(){
var sendData = {
"name":document.getElementById("name").value,
"age":document.getElementById("age").value
};
$.ajax({
type:"post",
url:"http://127.0.0.1:7011/test-stream",
dataType:"json",
contentType:"application/json",
data:JSON.stringify(sendData),
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("token", document.getElementById("token").value);
},
success:function(result){
if(result){
$("#status").text("正在处理");
}else{
$("#status").text("提交失败");
}
}
});
}
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
var token = document.getElementById('token').value;
// var socketUrl="ws://127.0.0.1:22599/webSocket/"+userId;
var socketUrl="ws://127.0.0.1:7021/receiptMsg/"+token;
console.log(socketUrl);
if(socket!=null){
socket.close();
socket=null;
}
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
/* var serverMsg = "收到服务端信息:" + msg.data;
console.log(serverMsg); */
//发现消息进入 开始处理前端触发逻辑
console.log(msg.data);
if(msg.data == "true"){
$("#status").text("处理成功");
}else{
$("#status").text("处理失败");
}
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
// console.log("您的浏览器支持WebSocket");
var toUserId = document.getElementById('toUserId').value;
var contentText = document.getElementById('contentText').value;
var msg = '{"toUserId":"'+toUserId+'","contentText":"'+contentText+'"}';
console.log(msg);
socket.send(msg);
}
}
</script>
</html>
/* var serverMsg = "收到服务端信息:" + msg.data;
console.log(serverMsg); */
//发现消息进入 开始处理前端触发逻辑
console.log(msg.data);
if(msg.data == "true"){
$("#status").text("处理成功");
}else{
$("#status").text("处理失败");
}
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
// console.log("您的浏览器支持WebSocket");
var toUserId = document.getElementById('toUserId').value;
var contentText = document.getElementById('contentText').value;
var msg = '{"toUserId":"'+toUserId+'","contentText":"'+contentText+'"}';
console.log(msg);
socket.send(msg);
}
}
</script>
```