SpringBoot项目 websocket通信前后端使用

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);
	}

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值