pom引用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
后端代码
websocket工具类
package com.bokesoft.erp.zfy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
/**
* 封装WebSocketUtil工具类,用于提供对session链接、断开连接、推送消息的简单控制。
* @ClassName : WebsocketUtil
* @Description : TODO
* @author : zhoufy
* @date : 2023-05-31 10:48:55
*/
public class WebsocketUtil {
/**
* 记录当前在线的Session
*/
private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<> ();
/**
* 添加session
* @param userId
* @param session
*/
public static void addSession(String userId, Session session){
// 此处只允许一个用户的session链接。一个用户的多个连接,我们视为无效。
ONLINE_SESSION.putIfAbsent ( userId, session );
}
/**
* 关闭session
* @param userId
*/
public static void removeSession(String userId){
ONLINE_SESSION.remove ( userId );
}
/**
* 根据session给单个用户推送消息 这个方法的使用主要还是在处理类中
* @param session
* @param message
*/
public static void sendMessage(Session session, String message){
if(session == null){
return;
}
//判断当前连接是否打开
if(session.isOpen()) {
// 同步
RemoteEndpoint.Async async = session.getAsyncRemote ();
async.sendText ( message );
}
}
/**
* 根据用户ID给单个用户推送消息 建立此方法允许其它操作给指定用户发送信息
* @param sessionId 指定用户ID
* @param message
*/
public static void sendMessage(String sessionId, String message){
Session session = ONLINE_SESSION.get(sessionId);
if(session == null){
return;
}
if(session.isOpen()) {
// 同步
RemoteEndpoint.Async async = session.getAsyncRemote ();
async.sendText ( message );
}
}
/**
* 向所有在线人发送消息
* @param message
*/
public static void sendMessageForAll(String message) {
//jdk8 新方法
ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));
}
}
websocket接口处理类
package com.bokesoft.erp.zfy;
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.json.JSONObject;
import org.springframework.stereotype.Component;
/**
* 处理类前端连接访问到此类
* @ClassName : WebsocketController
* @Description : TODO
* @author : zhoufy
* @date : 2023-05-31 10:48:55
*/
@Component
@ServerEndpoint ( value = "/chat/{userid}" )
public class WebsocketController {
/**
* 登入人员ID
*/
private String id;
/**
* 连接事件,加入注解
* @param userId 传入进来的用户ID
* @param session
*/
@OnOpen
public void onOpen( @PathParam ( value = "userid" ) String userId, Session session ) {
this.id = userId;
String message ="[" + userId + "]加入聊天室!!";
// 添加到session的映射关系中
WebsocketUtil.addSession ( userId, session );
// 广播通知,某用户上线了
WebsocketUtil.sendMessageForAll ( message );
}
/**
* 连接事件,加入注解
* 用户断开链接
*
*/
@OnClose
public void onClose(Session session ) {
String msg ="[" + this.id + "]退出了聊天室...";
// 删除映射关系
WebsocketUtil.removeSession (this.id);
// 广播通知,用户下线了
WebsocketUtil.sendMessageForAll ( msg );
}
/**
* 当接收到用户上传的消息
* @param message 前端传入进来的数据 是json形式的
*/
@OnMessage
public void onMessage(String message) {
JSONObject jsonObject = new JSONObject(message);
String msg = jsonObject.get("userId") + ":" + jsonObject.get("message");
// 直接广播
WebsocketUtil.sendMessageForAll ( msg );
}
/**
* 处理用户活连接异常
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
throwable.printStackTrace();
}
}
启动类配置
package com.bokesoft.erp.all;
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@SpringBootApplication(exclude = { MongoAutoConfiguration.class })
public class StartYigoERP {
public static void main(String[] args) throws IOException {
SpringApplication app = new SpringApplication(StartYigoERP.class);
app.run(args);
}
/**
* 会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
* 要注意,如果使用独立的servlet容器,
* 而不是直接使用springboot的内置容器,
* 就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
测试的方法
前端
此处是用户登入后进行的长连接,已将用户部分信息储存在了cookie中
这段js直接写在Body下面且没使用方法封装所以登入直接跳转就会去进行长连接
var socket;
socket = new WebSocket("ws://localhost:8089/erp/chat/"+$.cookie("userID"));
//连接成功时会进入此方法
socket.onopen = function () {
console.log("Socket 已打开");
//send传入的参只有一个所以可以采用json传输信息
let userData = {userId : $.cookie("userID"),message : "嗨!"}
//send向服务端发送请求
socket.send(JSON.stringify(userData));
};
// 接收消息时触发
socket.onmessage = function (msg) {
console.log(msg.data);
$(".info_prompt", nav).removeClass('info_hidden');
$(".info_prompt", nav).addClass('info_visible');
};
// 连接关闭的回调函数
socket.onclose = function () {
console.log("close");
};
//连接失败触发
socket.onerror = function () {
alert("发生了错误");
}
//刷新界面触发
window.unload = function () {
console.log("unload");
//关闭连接
socket.close();
};
//此处为界面的注销按钮点击时同时关闭连接
$(".logout", nav).click(function(e) {
Svr.SvrMgr.doLogout().done(function() {
$.cookie("clientID", null);
$.cookie("oldURL",null);
window.location.reload();
});
socket.close();
})
基于简单的发送改动后的前端发送扩展
(function () {
$.fn.extend({
infojs: function() {
var nav = this.get(0);
/*var infoServerCheck = setInterval(function(){
var paras = {};
paras.service = "GeneralInfoService";
paras.cmd = "ServerInfoCmd";
var result = new Svr.Request().getSyncData(Svr.SvrMgr.ServletURL ,paras);
console.log(result);
$(".info_prompt", nav).removeClass('info_hidden');
$(".info_prompt", nav).addClass('info_visible');
},5000);*/
let infoText
var socket;
var url = window.location.href.replace("http","ws").replace("https","wws");
socket = new WebSocket(url+"chat/"+$.cookie("userID"));
socket.onopen = function () {
console.log("Socket 已打开");
// let userData = {operationID : $.cookie("userID"),senderID : $.cookie("userID") ,infoTitle : "",message : "嗨!",powerLevel : 3}
// socket.send(JSON.stringify(userData));
};
socket.onmessage = function (msg) {
infoText = $.parseJSON(msg.data.replace(/[\r]/g, "\\r").replace(/[\n]/g, "\\n"));
switch (infoText.powerLevel) {
// 通知等级1级的情况进行界面弹框(不是浏览器的提示框)
case 1:
$(".info_prompt", nav).removeClass('info_hidden');
$(".info_prompt", nav).addClass('info_visible');
let formKey = "JT_SystemInfo";
let builder = new YIUI.YIUIBuilder(formKey);
builder.setTarget(YIUI.FormTarget.MODAL);
builder.newEmpty().then(function(emptyForm){
let cxt = new View.Context(emptyForm);
emptyForm.regEvent('ERPOnLoad', function (opt) {
emptyForm.setInitOperationState(YIUI.Form_OperationState.Default);
emptyForm.setOperationState(YIUI.Form_OperationState.Default);
emptyForm.eval("SetShowInfo("+infoText.infoTitle+","+infoText.message+",GetDictValue(\"Operator\", "+infoText.senderID+", \"Name\"))", cxt, null);
});
emptyForm.setPara('infoTitle',infoText.infoTitle);
emptyForm.setPara('infoText',infoText.message);
emptyForm.setPara('infoOperationID',infoText.senderID);
builder.builder(emptyForm);
});
break;
//浏览器提示框
case 2:
alert(infoText.message)
break;
//打印在控制台调试
case 3:
console.log("["+infoText.operationID+"]:"+infoText.message);
break;
}
};
socket.onclose = function () {
console.log("close");
};
socket.onerror = function () {
alert("发生了错误");
}
window.unload = function () {
console.log("unload");
socket.close();
};
//账号注销
$(".logout", nav).click(function(e) {
Svr.SvrMgr.doLogout().done(function() {
$.cookie("clientID", null);
$.cookie("oldURL",null);
window.location.reload();
});
socket.close();
});
//界面新增了一个按钮 用来查看最新的通知信息以下是其点击事件
$(".info_prompt", nav).click(function(e) {
let formKey = "JT_SystemInfo";
let builder = new YIUI.YIUIBuilder(formKey);
builder.setTarget(YIUI.FormTarget.MODAL);
builder.newEmpty().then(function(emptyForm){
let cxt = new View.Context(emptyForm);
emptyForm.regEvent('ERPOnLoad', function (opt) {
emptyForm.setInitOperationState(YIUI.Form_OperationState.Default);
emptyForm.setOperationState(YIUI.Form_OperationState.Default);
//emptyForm.eval("SetShowInfo("+infoText.infoTitle+","+infoText.message+",GetDictValue(\"Operator\", "+infoText.senderID+", \"Name\"))", cxt, null);
});
emptyForm.setPara('infoTitle',infoText.infoTitle);
emptyForm.setPara('infoText',infoText.message);
emptyForm.setPara('infoOperationID',infoText.senderID);
builder.builder(emptyForm);
});
});
}
});
})();
后续业务后端扩展
信息实体类:
package com.bokesoft.erp.jt.entity;
import com.bokesoft.erp.common.ERPStringUtil;
import java.util.Date;
import java.util.HashSet;
/**
/**
* @author : zhoufy
* @date : 2023/6/1 9:33
* @version : 1.0
*/
public class InfoText {
private int infoID;
/** 操作员ID*/
private String operationID;
/** 发件人ID 允许代称系统管理员发送*/
private String senderID;
/** 通知等级*/
private int powerLevel;
/** 发送的消息标题*/
private String infoTitle;
/** 发送的消息*/
private String message;
/** 发送消息的时间*/
private Date sendTimeStart;
/** 发送消息的最晚接收时间*/
private Date sendTimeEnd;
/** 收件人 0则是所有人*/
private String recipient;
/** 未读人 0则是所有人*/
private final HashSet<String> unreadPerson = new HashSet<>();
/** 已读人 */
private final HashSet<String> readPerson = new HashSet<>();
/** 只给在线人发送 1为是 0为否*/
private int IsLine;
/** 通知等级1 打开模态表单显示通知信息*/
public static final int POWER_LEVEL_01 = 1;
/** 通知等级2 浏览器弹框显示通知信息*/
public static final int POWER_LEVEL_02 = 2;
/** 通知等级3 浏览器控制台显示通知信息*/
public static final int POWER_LEVEL_03 = 3;
public InfoText(){
}
public String getOperationID() {
return operationID;
}
public void setOperationID(String operationID) {
this.operationID = operationID;
}
public void setInfoID(int infoID) {
this.infoID = infoID;
}
public int getInfoID() {
return infoID;
}
public String getSenderID() {
return senderID;
}
public void setSenderID(String senderID) {
this.senderID = senderID;
}
public int getPowerLevel() {
return powerLevel;
}
public void setPowerLevel(int powerLevel) {
switch (powerLevel) {
case POWER_LEVEL_01:
case POWER_LEVEL_02:
case POWER_LEVEL_03:
this.powerLevel = powerLevel;
break;
default:
this.powerLevel = POWER_LEVEL_03;
}
}
public String getInfoTitle() {
return infoTitle;
}
public void setInfoTitle(String infoTitle) {
this.infoTitle = infoTitle;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Date getSendTimeStart() {
return sendTimeStart;
}
public void setSendTimeStart(Date sendTimeStart) {
this.sendTimeStart = sendTimeStart;
}
public Date getSendTimeEnd() {
return sendTimeEnd;
}
public void setSendTimeEnd(Date sendTimeEnd) {
this.sendTimeEnd = sendTimeEnd;
}
public String getRecipient() {
return recipient;
}
public void setRecipient(String recipient) {
this.recipient = recipient;
}
public String getUnreadPerson() {
StringBuilder str= new StringBuilder();
for (String person : this.unreadPerson) {
str.append(person).append(ERPStringUtil.COMMA);
}
return str.length()>0?str.toString().substring(0,str.length()-1):ERPStringUtil.STR_EMPTY;
}
public void addUnreadPerson(String unreadPerson) {
this.unreadPerson.add(unreadPerson);
}
/**
* 每次从未读人中移除用户则给已读人增加一个用户
* @param unreadPerson 操作员ID
*/
public void removeUnreadPerson(String unreadPerson){
this.unreadPerson.remove(unreadPerson);
addReadPerson(unreadPerson);
}
/**
* 返回未读总数
* @return 未读总数
*/
public int UnreadPersonAmount(){
return this.unreadPerson.size();
}
public void addReadPerson(String readPerson) {
this.readPerson.add(readPerson);
}
/**
* 拿到所有已读人并符合多选字典的格式
* @return 逗号隔开的操作员id
*/
public String getReadPerson() {
StringBuilder str= new StringBuilder();
for (String person : this.readPerson) {
str.append(person).append(ERPStringUtil.COMMA);
}
return str.length()>0?str.toString().substring(0,str.length()-1):ERPStringUtil.STR_EMPTY;
}
/**
* 返回已读总数
* @return 已读总数
*/
public int ReadPersonAmount(){
return this.readPerson.size();
}
public int getIsLine() {
return IsLine;
}
public void setIsLine(int isLine) {
this.IsLine = isLine;
}
@Override
public String toString() {
return "{" +
" \"operationID\":" + operationID +
", \"senderID\":" + senderID +
", \"powerLevel\":" + powerLevel +
", \"infoTitle\": \"" + infoTitle + "\"" +
", \"message\": \"" + message + "\"" +
'}';
}
}
实际方法
/**
* 给通知界面设置通知内容
* @param infoTitle 通知标题
* @param infoText 通知内容
* @param infoOperationName 发送人
* @throws Throwable 异常
*/
@FunctionGetValueScope(FunctionGetValueScopeType.Document)
public void SetSystemInfo(String infoTitle,String infoText,String infoOperationName) throws Throwable {
//JT_SystemInfo只在通知等级为1时才用到的自定义弹框
JT_SystemInfo jt_systemInfo = JT_SystemInfo.parseDocument(getDocument());
jt_systemInfo.setInfoTitle(infoTitle);
jt_systemInfo.setInfoMessage(infoText);
jt_systemInfo.setSender(infoOperationName);
//刷新缓存
getDocument().addDirtyTableFlag(EJT_SystemInfo.EJT_SystemInfo);
}
/**
* 给通知编辑界面设置信息
* @throws Throwable 异常
*/
@FunctionGetValueScope(FunctionGetValueScopeType.Document)
public void SetSystemInfoSend() throws Throwable {
LinkedList<InfoText> infos = WebsocketUtil.getLastInfoText();
if(infos.size() > 0) {
JT_SystemInfoSend jt_SystemInfoSend = JT_SystemInfoSend.parseDocument(getDocument());
List<JT_SystemInfoSend_Table1> jt_systemInfoSend_table1s = jt_SystemInfoSend.jt_systemInfoSend_Table1s();
for (JT_SystemInfoSend_Table1 entity:jt_systemInfoSend_table1s){
jt_SystemInfoSend.deleteJT_SystemInfoSend_Table1(entity);
}
//将缓存里的最近通知显示
while(infos.size()!=0){
InfoText info = infos.pop();
JT_SystemInfoSend_Table1 jt_systemInfoSend_table1 = jt_SystemInfoSend.newJT_SystemInfoSend_Table1();
jt_systemInfoSend_table1.setInfoTitleDtl(info.getInfoTitle());
jt_systemInfoSend_table1.setRecipientDtl(info.getRecipient());
jt_systemInfoSend_table1.setPowerLevelDtl(info.getPowerLevel());
jt_systemInfoSend_table1.setSenderDtl(info.getSenderID());
jt_systemInfoSend_table1.setOperatorID(TypeConvertor.toLong(info.getOperationID()));
jt_systemInfoSend_table1.setSendTimeStartDtl(new Timestamp(info.getSendTimeStart().getTime()));
jt_systemInfoSend_table1.setSendTimeEndDtl(new Timestamp(info.getSendTimeEnd().getTime()));
jt_systemInfoSend_table1.setInfoMessageDtl(info.getMessage());
jt_systemInfoSend_table1.setIsLineDtl(info.getIsLine());
jt_systemInfoSend_table1.setunreadPerson(info.getUnreadPerson());
jt_systemInfoSend_table1.setreadPerson(info.getReadPerson());
jt_systemInfoSend_table1.setunreadReadAmount(info.ReadPersonAmount() + LexDef.S_T_DIV + info.UnreadPersonAmount());
}
getDocument().addDirtyTableFlag(JT_SystemInfoSend_Table1.JT_SystemInfoSend_Table1);
}
}
/**
* 向所有用户或指定用户发送消息
* @param infoTitle 标题
* @param infoText 内容
* @param recipients 用户数组
* @param powerLevel 信息等级
* @throws Throwable 异常
*/
@FunctionGetValueScope(FunctionGetValueScopeType.Document)
public void SendInfo(String infoTitle,String infoText,String recipients,
String sender,int powerLevel,Date SendTimeStart,Date SendTimeEnd,int IsLine) throws Throwable {
if(ERPStringUtil.isEmpty(recipients))
throw new Throwable("请选择收件人!");
InfoText info = new InfoText();
String operatioID = TypeConvertor.toString(getEnv().getOperatorID());
info.setSenderID(sender.equals("21")?sender: operatioID);
info.setInfoTitle(infoTitle);
info.setMessage(infoText);
info.setPowerLevel(powerLevel);
info.setOperationID(operatioID);
//为null默认为当前时间立即发送
Date timeStart = new Date();
Date timeEnd = new Date();
if(SendTimeStart!=null)
timeStart = SendTimeStart;
if(SendTimeEnd!=null)
timeEnd = SendTimeEnd;
info.setSendTimeStart(timeStart);
info.setSendTimeEnd(timeEnd);
info.setIsLine(IsLine);
//设置消息的收件人
info.setRecipient(recipients);
//多选字典全选是0 这代表选择了所有用户
if(recipients.equals("0")){
//除了自己以外所有在线人发送消息
if(IsLine==1) {
//给在线人员发送认为没有未读的全设置为空字符串
//info.addUnreadPerson("");
WebsocketUtil.sendMessageForAllExceptOneself(info);
}
else{ //给所有人发消息
//拿到所有操作员 第三个参数 7代表查所有1代表查启用的2代表停用的4是作废的
List<Item> operator = getMidContext().getDefaultContext().getVE().getDictCache().getAllItems("Operator", null, DictStateMask.Enable);
//拿到所有操作员ID
Set<Long> collect = operator.stream().map(Item::getID).collect(Collectors.toSet());
for(Long id:collect){
String ids = TypeConvertor.toString(id);
//不给点发送通知的人发
if(operatioID.equals(ids))
continue;
info.addUnreadPerson(ids);
WebsocketUtil.sendMessage(ids, info,IsLine);
}
}
}else{
//给指定人员发送消息
String[] split = recipients.split(ERPStringUtil.COMMA);
for (String s : split) {
info.addUnreadPerson(s);
WebsocketUtil.sendMessage(s, info,IsLine);
}
}
//设置当前通知为最近一条通知
WebsocketUtil.addLastInfoText(info);
}