Commit 4751c51c authored by 耿迪迪's avatar 耿迪迪

websocket 开发

parent 836b1fa9
...@@ -59,6 +59,11 @@ ...@@ -59,6 +59,11 @@
<artifactId>precision-effect-system</artifactId> <artifactId>precision-effect-system</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>
\ No newline at end of file
...@@ -97,7 +97,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter ...@@ -97,7 +97,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
// 过滤请求 // 过滤请求
.authorizeRequests() .authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问 // 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/captchaImage").anonymous() .antMatchers("/login", "/captchaImage", "/webSocket/**").anonymous()
.antMatchers( .antMatchers(
HttpMethod.GET, HttpMethod.GET,
"/*.html", "/*.html",
......
package com.zehong.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author geng
* webScocket
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
package com.zehong.framework.web.domain.server;
import javax.websocket.Session;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <websocket信息对象>
* <用于存储secket连接信息>
* @author wzh
* @version 2018-07-08 18:49
* @see [相关类/方法] (可选)
**/
public class WebSocketBean {
/**
* 连接session对象
*/
private Session session;
/**
* 用户id
*/
private Long userId;
/**
* 连接错误次数
*/
private AtomicInteger erroerLinkCount = new AtomicInteger(0);
public int getErroerLinkCount() {
// 线程安全,以原子方式将当前值加1,注意:这里返回的是自增前的值
return erroerLinkCount.getAndIncrement();
}
public void cleanErrorNum()
{
// 清空计数
erroerLinkCount = new AtomicInteger(0);
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
\ No newline at end of file
package com.zehong.framework.web.service;
import com.alibaba.fastjson.JSONArray;
import com.zehong.common.utils.spring.SpringUtils;
import com.zehong.framework.web.domain.server.WebSocketBean;
import com.zehong.system.domain.SysNotice;
import com.zehong.system.service.ISysNoticeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @author geng
* webSocket工具类
*/
@Component
@ServerEndpoint("/webSocket/{roles}/{userId}")
public class WebSocketServer {
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 错误最大重试次数
*/
private static final int MAX_ERROR_NUM = 10;
/**
* 用来存放每个客户端对应的webSocket对象。
*/
private static Map<String,List<WebSocketBean>> webSocketInfo;
static
{
// concurrent包的线程安全map
webSocketInfo = new ConcurrentHashMap<>();
}
@OnOpen
public void onOpen(Session session, @PathParam("roles") String roles, @PathParam("userId") String userId) {
for(String role : roles.split(",")){
WebSocketBean bean = new WebSocketBean();
bean.setSession(session);
bean.setUserId(Long.valueOf(userId));
if(webSocketInfo.containsKey(role)){
List<WebSocketBean> beans = webSocketInfo.get(role);
// 连接成功当前对象放入webSocket对象集合
beans.add(bean);
sendMessage(bean,initNotice(userId));
return;
}
List<WebSocketBean> beans = new ArrayList<>();
beans.add(bean);
webSocketInfo.put(role,beans);
sendMessage(bean,initNotice(userId));
}
log.info("客户端连接服务器session id :"+session.getId()+",当前连接数:" + webSocketInfo.size());
}
private String initNotice(String userId){
SysNotice notice = new SysNotice();
notice.setUserId(Long.valueOf(userId));
ISysNoticeService sysNoticeService = SpringUtils.getBean(ISysNoticeService.class);
List<SysNotice> notices = sysNoticeService.selectNoticeList(notice);
if(CollectionUtils.isEmpty(notices)){
return "";
}
return JSONArray.toJSONString(notices);
}
@OnClose
public void onClose(Session session) {
// 客户端断开连接移除websocket对象
for (Map.Entry<String, List<WebSocketBean>> entry : webSocketInfo.entrySet()) {
List<WebSocketBean> beans = entry.getValue().stream().filter(item ->item.getSession().getId().equals(session.getId())).collect(Collectors.toList());
entry.getValue().removeAll(beans);
}
log.info("客户端断开连接,当前连接数:" + webSocketInfo.size());
}
@OnMessage
public void onMessage(Session session, String message) {
log.info("客户端 session id: "+session.getId()+",消息:" + message);
// 此方法为客户端给服务器发送消息后进行的处理,可以根据业务自己处理,这里返回页面
//sendMessage(session, "服务端返回" + message);
}
@OnError
public void onError(Session session, Throwable throwable) {
log.error("发生错误"+ throwable.getMessage(),throwable);
}
/**
* 查找发送消息用户
* @param role 角色
* @param userId 用户id
* @param message 消息体
*/
public void findMessageUser(String role,Long userId, String message) {
List<WebSocketBean> beans = webSocketInfo.get(role);
if(!CollectionUtils.isEmpty(beans)){
//发送给指定角色
if(null == userId){
beans.forEach(item ->{
sendMessage(item,message);
});
return;
}
//发送给指定用户
List<WebSocketBean> userBean = beans.stream().filter(item -> item.getUserId().equals(userId)).collect(Collectors.toList());
userBean.stream().forEach(item -> {
sendMessage(item,message);
});
}
}
/**
* 发送消息
* @param bean webSocket对象
* @param message 消息体
*/
private void sendMessage(WebSocketBean bean, String message) {
try{
// 发送消息
bean.getSession().getBasicRemote().sendText(message);
// 清空错误计数
bean.cleanErrorNum();
}catch (Exception e){
log.error("发送消息失败"+ e.getMessage(),e);
int errorNum = bean.getErroerLinkCount();
// 小于最大重试次数重发
if(errorNum <= MAX_ERROR_NUM){
sendMessage(bean, message);
}else{
log.error("发送消息失败超过最大次数");
// 清空错误计数
bean.cleanErrorNum();
}
}
}
}
package com.zehong.system.domain; package com.zehong.system.domain;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.zehong.common.annotation.Excel;
import com.zehong.common.core.domain.BaseEntity; import com.zehong.common.core.domain.BaseEntity;
/** /**
* 通知公告 sys_notice * 通知公告对象 sys_notice
* *
* @author zehong * @author zehong
* @date 2023-06-16
*/ */
public class SysNotice extends BaseEntity public class SysNotice extends BaseEntity
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 公告ID */ /** 公告ID */
private Long noticeId; private Integer noticeId;
/** 公告标题 */ /** 公告标题 */
@Excel(name = "公告标题")
private String noticeTitle; private String noticeTitle;
/** 公告类型(1通知 2公告) */ /** 公告类型(1通知 2公告) */
@Excel(name = "公告类型", readConverterExp = "1=通知,2=公告")
private String noticeType; private String noticeType;
/** 公告内容 */ /** 公告内容 */
@Excel(name = "公告内容")
private String noticeContent; private String noticeContent;
/** 用户id */
@Excel(name = "用户id")
private Long userId;
/** 公告状态(0正常 1关闭) */ /** 公告状态(0正常 1关闭) */
@Excel(name = "公告状态", readConverterExp = "0=正常,1=关闭")
private String status; private String status;
public Long getNoticeId() public void setNoticeId(Integer noticeId)
{ {
return noticeId; this.noticeId = noticeId;
} }
public void setNoticeId(Long noticeId) public Integer getNoticeId()
{ {
this.noticeId = noticeId; return noticeId;
} }
public void setNoticeTitle(String noticeTitle)
public void setNoticeTitle(String noticeTitle)
{ {
this.noticeTitle = noticeTitle; this.noticeTitle = noticeTitle;
} }
@NotBlank(message = "公告标题不能为空") public String getNoticeTitle()
@Size(min = 0, max = 50, message = "公告标题不能超过50个字符")
public String getNoticeTitle()
{ {
return noticeTitle; return noticeTitle;
} }
public void setNoticeType(String noticeType)
public void setNoticeType(String noticeType)
{ {
this.noticeType = noticeType; this.noticeType = noticeType;
} }
public String getNoticeType() public String getNoticeType()
{ {
return noticeType; return noticeType;
} }
public void setNoticeContent(String noticeContent)
public void setNoticeContent(String noticeContent)
{ {
this.noticeContent = noticeContent; this.noticeContent = noticeContent;
} }
public String getNoticeContent() public String getNoticeContent()
{ {
return noticeContent; return noticeContent;
} }
public void setUserId(Long userId)
{
this.userId = userId;
}
public void setStatus(String status) public Long getUserId()
{
return userId;
}
public void setStatus(String status)
{ {
this.status = status; this.status = status;
} }
public String getStatus() public String getStatus()
{ {
return status; return status;
} }
...@@ -89,6 +100,7 @@ public class SysNotice extends BaseEntity ...@@ -89,6 +100,7 @@ public class SysNotice extends BaseEntity
.append("noticeTitle", getNoticeTitle()) .append("noticeTitle", getNoticeTitle())
.append("noticeType", getNoticeType()) .append("noticeType", getNoticeType())
.append("noticeContent", getNoticeContent()) .append("noticeContent", getNoticeContent())
.append("userId", getUserId())
.append("status", getStatus()) .append("status", getStatus())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
......
...@@ -5,85 +5,92 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ...@@ -5,85 +5,92 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<mapper namespace="com.zehong.system.mapper.SysNoticeMapper"> <mapper namespace="com.zehong.system.mapper.SysNoticeMapper">
<resultMap type="SysNotice" id="SysNoticeResult"> <resultMap type="SysNotice" id="SysNoticeResult">
<result property="noticeId" column="notice_id" /> <result property="noticeId" column="notice_id" />
<result property="noticeTitle" column="notice_title" /> <result property="noticeTitle" column="notice_title" />
<result property="noticeType" column="notice_type" /> <result property="noticeType" column="notice_type" />
<result property="noticeContent" column="notice_content" /> <result property="noticeContent" column="notice_content" />
<result property="status" column="status" /> <result property="userId" column="user_id" />
<result property="createBy" column="create_by" /> <result property="status" column="status" />
<result property="createTime" column="create_time" /> <result property="createBy" column="create_by" />
<result property="updateBy" column="update_by" /> <result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" /> <result property="updateBy" column="update_by" />
<result property="remark" column="remark" /> <result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap> </resultMap>
<sql id="selectNoticeVo"> <sql id="selectSysNoticeVo">
select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark select notice_id, notice_title, notice_type, notice_content, user_id, status, create_by, create_time, update_by, update_time, remark from sys_notice
from sys_notice
</sql> </sql>
<select id="selectNoticeById" parameterType="Long" resultMap="SysNoticeResult">
<include refid="selectNoticeVo"/>
where notice_id = #{noticeId}
</select>
<select id="selectNoticeList" parameterType="SysNotice" resultMap="SysNoticeResult"> <select id="selectNoticeList" parameterType="SysNotice" resultMap="SysNoticeResult">
<include refid="selectNoticeVo"/> <include refid="selectSysNoticeVo"/>
<where> <where>
<if test="noticeTitle != null and noticeTitle != ''"> <if test="noticeTitle != null and noticeTitle != ''"> and notice_title = #{noticeTitle}</if>
AND notice_title like concat('%', #{noticeTitle}, '%') <if test="noticeType != null and noticeType != ''"> and notice_type = #{noticeType}</if>
</if> <if test="noticeContent != null and noticeContent != ''"> and notice_content = #{noticeContent}</if>
<if test="noticeType != null and noticeType != ''"> <if test="userId != null "> and user_id = #{userId}</if>
AND notice_type = #{noticeType} <if test="status != null and status != ''"> and status = #{status}</if>
</if> </where>
<if test="createBy != null and createBy != ''">
AND create_by like concat('%', #{createBy}, '%')
</if>
</where>
</select> </select>
<insert id="insertNotice" parameterType="SysNotice"> <select id="selectNoticeById" parameterType="Integer" resultMap="SysNoticeResult">
insert into sys_notice ( <include refid="selectSysNoticeVo"/>
<if test="noticeTitle != null and noticeTitle != '' ">notice_title, </if> where notice_id = #{noticeId}
<if test="noticeType != null and noticeType != '' ">notice_type, </if> </select>
<if test="noticeContent != null and noticeContent != '' ">notice_content, </if>
<if test="status != null and status != '' ">status, </if> <insert id="insertNotice" parameterType="SysNotice" useGeneratedKeys="true" keyProperty="noticeId">
<if test="remark != null and remark != ''">remark,</if> insert into sys_notice
<if test="createBy != null and createBy != ''">create_by,</if> <trim prefix="(" suffix=")" suffixOverrides=",">
create_time <if test="noticeTitle != null and noticeTitle != ''">notice_title,</if>
)values( <if test="noticeType != null and noticeType != ''">notice_type,</if>
<if test="noticeTitle != null and noticeTitle != ''">#{noticeTitle}, </if> <if test="noticeContent != null">notice_content,</if>
<if test="noticeType != null and noticeType != ''">#{noticeType}, </if> <if test="userId != null">user_id,</if>
<if test="noticeContent != null and noticeContent != ''">#{noticeContent}, </if> <if test="status != null">status,</if>
<if test="status != null and status != ''">#{status}, </if> <if test="createBy != null">create_by,</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="createTime != null">create_time,</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="updateBy != null">update_by,</if>
sysdate() <if test="updateTime != null">update_time,</if>
) <if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="noticeTitle != null and noticeTitle != ''">#{noticeTitle},</if>
<if test="noticeType != null and noticeType != ''">#{noticeType},</if>
<if test="noticeContent != null">#{noticeContent},</if>
<if test="userId != null">#{userId},</if>
<if test="status != null">#{status},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert> </insert>
<update id="updateNotice" parameterType="SysNotice"> <update id="updateNotice" parameterType="SysNotice">
update sys_notice update sys_notice
<set> <trim prefix="SET" suffixOverrides=",">
<if test="noticeTitle != null and noticeTitle != ''">notice_title = #{noticeTitle}, </if> <if test="noticeTitle != null and noticeTitle != ''">notice_title = #{noticeTitle},</if>
<if test="noticeType != null and noticeType != ''">notice_type = #{noticeType}, </if> <if test="noticeType != null and noticeType != ''">notice_type = #{noticeType},</if>
<if test="noticeContent != null">notice_content = #{noticeContent}, </if> <if test="noticeContent != null">notice_content = #{noticeContent},</if>
<if test="status != null and status != ''">status = #{status}, </if> <if test="userId != null">user_id = #{userId},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="status != null">status = #{status},</if>
update_time = sysdate() <if test="createBy != null">create_by = #{createBy},</if>
</set> <if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where notice_id = #{noticeId} where notice_id = #{noticeId}
</update> </update>
<delete id="deleteNoticeById" parameterType="Long"> <delete id="deleteNoticeById" parameterType="Integer">
delete from sys_notice where notice_id = #{noticeId} delete from sys_notice where notice_id = #{noticeId}
</delete> </delete>
<delete id="deleteNoticeByIds" parameterType="Long"> <delete id="deleteNoticeByIds" parameterType="String">
delete from sys_notice where notice_id in delete from sys_notice where notice_id in
<foreach item="noticeId" collection="array" open="(" separator="," close=")"> <foreach item="noticeId" collection="array" open="(" separator="," close=")">
#{noticeId} #{noticeId}
</foreach> </foreach>
</delete> </delete>
</mapper> </mapper>
\ No newline at end of file
<template>
<div class="instantMessage">
<el-dropdown trigger="click" :popper-append-to-body="false" style="height: 30px">
<div>
<el-badge :value= "this.msgInfo.length" :max="99" class="messageMark">
   <!--<i class="el-icon-chat-dot-round" />-->
<i class="el-icon-message-solid messageDing"/>
 </el-badge>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="this.msgInfo.length == 0">
<span>暂无消息</span>
</el-dropdown-item>
<div v-if="this.msgInfo.length >= 0">
<el-row>
<el-col :span="12">
<el-dropdown-item>标题</el-dropdown-item>
</el-col>
<el-col :span="12">
<el-dropdown-item>类型</el-dropdown-item>
</el-col>
</el-row>
<el-row v-for="item in msgInfo">
<el-col :span="12">
<el-dropdown-item class="region-item-style">{{ item.noticeTitle }}</el-dropdown-item>
</el-col>
<el-col :span="12">
<el-dropdown-item v-if="item.noticeType == '1'">通知</el-dropdown-item>
<el-dropdown-item v-if="item.noticeType == '2'">公告</el-dropdown-item>
</el-col>
</el-row>
</div>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: "message",
data(){
return{
msgInfo: {}
}
},
created(){
//登录成功后创建websocket
this.$websocket.initWebSocket("ws://localhost:8668/precisionEffect/webSocket/" + this.$store.state.user.roles.join(",") + "/" + this.$store.state.user.userId);
this.$websocket.addEvent("onmessage",(msg) =>{
console.log("您有新的消息请注意接收:", JSON.parse(msg.data));
this.msgInfo = JSON.parse(msg.data);
console
})
},
methods:{
}
}
</script>
<style>
.region-item-style {
max-width: 200px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>
<style scoped lang="scss">
.instantMessage{
display: inline;
.messageMark{
width: 25px;
height: 60px;
margin-right: 9px;
}
.messageDing{
font-size: 28px;
color: #5a5e66;
position: relative;
top: -5px;
}
}
</style>
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<!-- </el-tooltip>--> <!-- </el-tooltip>-->
</template> </template>
<Message/>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click"> <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<!-- <img :src="avatar" class="user-avatar">--> <!-- <img :src="avatar" class="user-avatar">-->
...@@ -48,7 +48,7 @@ import Hamburger from '@/components/Hamburger' ...@@ -48,7 +48,7 @@ import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull' import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect' import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch' import Search from '@/components/HeaderSearch'
import Message from "./Message/Message";
export default { export default {
components: { components: {
Breadcrumb, Breadcrumb,
...@@ -56,7 +56,8 @@ export default { ...@@ -56,7 +56,8 @@ export default {
Hamburger, Hamburger,
Screenfull, Screenfull,
SizeSelect, SizeSelect,
Search Search,
Message
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
...@@ -94,6 +95,8 @@ export default { ...@@ -94,6 +95,8 @@ export default {
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.$store.dispatch('LogOut').then(() => { this.$store.dispatch('LogOut').then(() => {
//关闭websocket
this.$websocket.close();
location.href = '/index'; location.href = '/index';
}) })
}).catch(() => {}); }).catch(() => {});
......
...@@ -71,3 +71,6 @@ new Vue({ ...@@ -71,3 +71,6 @@ new Vue({
store, store,
render: h => h(App) render: h => h(App)
}) })
import websocket from "@/utils/websocket.js";
Vue.prototype.$websocket = websocket;
import ElementUI from 'element-ui';
let lockReconnect = false;
let socket;
let timer;
let count = 10;
let wsUrl;
function initWebSocket(wsUri) {
wsUrl = wsUri;
//这里面的this都指向vue
socket = new WebSocket(wsUri);
socket.onerror = webSocketOnError;
socket.onclose = closeWebsocket;
socket.onopen = openWebsocket;
}
//错误事件监听
function webSocketOnError(e) {
ElementUI.Notification({
title: '',
message: "WebSocket连接发生错误" + e,
type: 'error',
duration: 0,
});
}
// websocket关闭监听事件
function closeWebsocket() {
reconnect();
}
//开始事件
function openWebsocket() {
if(timer){
clearInterval(timer);
}
}
// 关闭 websocket
function close() {
lockReconnect = true;
socket.close();
}
//消息发送
function webSocketSend(agentData) {
socket.send(agentData);
}
//重连机制
function reconnect(){
if(lockReconnect){
return
}
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
timer = setTimeout(function(){
count --;
initWebSocket(wsUrl);
this.lockReconnect = false
if(count == 0){
clearInterval(timer);
}
},2000)
}
//监听事件添加
function addEvent(type,event){
if(type == "onopen"){
socket.onopen = event;
}else if(type == "onmessage"){
socket.onmessage = event;
}else if(type == "onclose"){
socket.onclose = event;
}else if(type == "onerror"){
socket.onerror = event;
}else{
socket.addEventListener(type,event);
}
}
export default {
initWebSocket, close, addEvent
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment