消息提醒
框架添加了消息提醒的功能,主版本RSP框架已包含此功能,老版本框架若要使用,请进行如下操作,集成功能:
# 一. [项目]-framework 模块
# 1. 修改pom.xml
添加 WebSocket 依赖
<!-- springboot WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
# 2. 修改SecurityConfig.java
修改 configure(HttpSecurity httpSecurity) 方法,添加匿名访问
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
- .antMatchers("/login", "/register", "/captchaImage", "/sendLoginSmsCode/**").permitAll()
+ .antMatchers("/login", "/register", "/captchaImage", "/sendLoginSmsCode/**", "/websocket/**").permitAll()
# 3. 添加新文件
在com.risun.framework下,创建包websocket,在其中添加4个java文件
WebSocketMsg.java
package com.risun.framework.websocket;
import java.io.Serializable;
import java.util.Map;
import com.google.common.collect.Maps;
import cn.hutool.core.collection.CollUtil;
import lombok.Data;
/**
* 消息信息
*
* @author dante
*
*/
@Data
public class WebSocketMsg implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 额外信息
*/
private Map<String, Object> extra;
public void addExtra(String key, Object val) {
if(CollUtil.isEmpty(extra)) {
this.extra = Maps.newHashMap();
}
this.extra.put(key, val);
}
}
WebSocketConfig.java
package com.risun.framework.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* WebSocket 配置
*
* @author dante
*
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocketClient.java
package com.risun.framework.websocket;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.Session;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
/**
* websocket 客户端用户集
*
* @author sunchao
*/
@Slf4j
public class WebSocketClient {
/**
* 用户集
*/
private static Map<String, Session> USERS = new ConcurrentHashMap<String, Session>();
/**
* 存储用户
*
* @param userName 用户名
* @param session 用户信息
*/
public static void put(String userName, Session session) {
USERS.put(userName, session);
}
/**
* 获取用户名
*
* @param session
* @return
*/
public static String getUserName(Session session) {
String key = null;
boolean flag = USERS.containsValue(session);
if (flag) {
Set<Map.Entry<String, Session>> entries = USERS.entrySet();
for (Map.Entry<String, Session> entry : entries) {
Session value = entry.getValue();
if (value.equals(session)) {
key = entry.getKey();
break;
}
}
}
return key;
}
/**
* 移除用户
*
* @param session 用户信息
*
* @return 移除结果
*/
public static boolean remove(Session session) {
String key = null;
boolean flag = USERS.containsValue(session);
if (flag) {
Set<Map.Entry<String, Session>> entries = USERS.entrySet();
for (Map.Entry<String, Session> entry : entries) {
Session value = entry.getValue();
if (value.equals(session)) {
key = entry.getKey();
break;
}
}
} else {
return true;
}
return remove(key);
}
/**
* 移出用户
*
* @param key 键
*/
public static boolean remove(String key) {
Session remove = USERS.remove(key);
if (remove != null) {
boolean containsValue = USERS.containsValue(remove);
return containsValue;
} else {
return true;
}
}
/**
* 获取在线用户列表
*
* @return 返回用户集合
*/
public static Map<String, Session> getUsers() {
return USERS;
}
/**
* 群发消息文本消息
*
* @param message 消息内容
*/
public static void sendMessageToAll(WebSocketMsg message) {
Set<String> keys = USERS.keySet();
for (String userName : keys) {
sendMessageToUser(userName, message);
}
}
/**
* 发送文本消息
*
* @param userName 用户名
* @param message 消息内容
*/
public static void sendMessageToUser(String userName, WebSocketMsg message) {
Session session = USERS.get(userName);
if (session != null) {
try {
session.getBasicRemote().sendText(JSON.toJSONString(message));
} catch (Exception e) {
log.error("[你已离线]", e);
}
} else {
log.info("[{}已离线]", userName);
}
}
}
WebSocketServer.java
package com.risun.framework.websocket;
import java.io.IOException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
/**
* WebSocket 消息处理
*
* @author sunchao
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/message/{userName}")
public class WebSocketServer {
/**
* 连接建立成功调用的方法
*
* @param userName 当前登录用户
*/
@OnOpen
public void onOpen(Session session, @PathParam("userName") String userName) throws Exception {
if(StrUtil.isEmpty(userName)) {
log.info("未登录系统!");
session.close();
} else {
// 添加用户
WebSocketClient.put(userName, session);
// 获取当前用户的待办信息,发送消息给用户
/*
WebSocketMsg msg = new WebSocketMsg();
msg.setTitle("待办信息");
msg.setContent("你有一条待办信息待处理!");
msg.addExtra("url", "/demo/test");
WebSocketClient.sendMessageToUser(userName, msg);
*/
}
}
/**
* 连接关闭时处理
*/
@OnClose
public void onClose(Session session) {
log.info("关闭连接 - {}", session);
// 移除用户
WebSocketClient.remove(session);
}
/**
* 抛出异常时处理
*/
@OnError
public void onError(Session session, Throwable exception) throws Exception {
if (session.isOpen()) {
// 关闭连接
session.close();
}
String sessionId = session.getId();
log.info("连接异常 - {}", sessionId);
log.info("异常信息 - {}", exception);
// 移出用户
WebSocketClient.remove(session);
}
/**
* 服务器接收到客户端消息时调用的方法
*
* @throws IOException
*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
String userName = WebSocketClient.getUserName(session);
if(StrUtil.isEmpty(userName)) {
log.info("未登录系统!");
session.close();
} else {
WebSocketMsg msg = new WebSocketMsg();
msg.setTitle(message);
msg.setContent(message);
WebSocketClient.sendMessageToUser(userName, msg);
}
}
}
# 二. [项目]-ui 模块
# 1. 修改环境信息
修改 .env.development、.env.staging、.env.production
- .env.development
添加配置
VUE_SOCKET_BASE_API = '/dev-socket'
- .stage.development 添加配置
VUE_SOCKET_BASE_API = '/stage-socket'
- .prod.development 添加配置
VUE_SOCKET_BASE_API = '/prod-socket'
# 2. 修改 vue.config.js
在 devServer 下,进行如下修改
devServer: {
...
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: `http://localhost:9080`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
},
[process.env.VUE_SOCKET_BASE_API]: {
target: 'http://localhost:9080',
ws: true, // 开启websocket代理
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_SOCKET_BASE_API]: ''
}
}
}
...
}
# 3. 修改 Navbar.vue
新增内容如下:
data() {
return {
ws: null
}
},
...
mounted() {
// 建立WebSocket连接,进行消息提醒(若项目中无需求,可注释或删除该行代码)
this.connWS()
},
...
methods: {
...
connWS() {
const user = this.$store.state.user
if(user == null) {
return;
}
const { host } = location
const wsuri = `ws://${host}${process.env.VUE_APP_BASE_API}/websocket/message/${user.name}`;
this.ws = new WebSocket(wsuri);
const self = this;
this.ws.onopen = function (event) {
console.log('已建立WebSocket连接!')
};
this.ws.onmessage = function (event) {
self.notifyUser(event.data)
};
this.ws.onclose = function (event) {
console.log('已关闭WebSocket连接!')
};
},
notifyUser(msg) {
const data = JSON.parse(msg)
const that = this
this.$notify({
title: data.title,
type: 'success',
iconClass: 'el-icon-info',
message: data.content,
onClick() {
that.handleNotifyClick(data)
}
});
},
handleNotifyClick(data) {
// 按项目需求实现相关业务逻辑
console.log(data)
}
}
修改内容:
修改 logout() 方法,在 this.$store.dispatch 之前,添加代码
if (this.ws) {
this.ws.close();
this.ws = null;
}
# 三. 生产环境 Nginx 配置
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
...
location ^~/socket/ {
proxy_pass http://x.x.x.x:8080/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
}
...
}
}