This commit is contained in:
xd 2024-04-25 13:24:18 +08:00
parent b3a429204f
commit 00cd608378
9 changed files with 201 additions and 57 deletions

View File

@ -9,6 +9,7 @@ import com.ruoyi.framework.websocket.OneToManyWebSocket;
import com.ruoyi.framework.websocket.WebSocketServer; import com.ruoyi.framework.websocket.WebSocketServer;
import com.ruoyi.framework.websocket.WebSocketUsers; import com.ruoyi.framework.websocket.WebSocketUsers;
import com.ruoyi.system.domain.SysNoticeUser; import com.ruoyi.system.domain.SysNoticeUser;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -77,26 +78,9 @@ public class SysNoticeController extends BaseController
{ {
notice.setCreateBy(getUsername()); notice.setCreateBy(getUsername());
noticeService.insertNotice(notice); noticeService.insertNotice(notice);
//推送消息插入中间表
List<SysNoticeUser> sysNoticeUsers = new ArrayList<SysNoticeUser>(); Long userId = getLoginUser().getUserId();//当前登陆者
JSONObject obj = new JSONObject(); insertNoticeUser(userId,notice,noticeService,oneToManyWebSocket,null);
obj.put(WebsocketConst.MSG_ID, notice.getNoticeId());
obj.put(WebsocketConst.MSG_TITLE, notice.getNoticeTitle());
obj.put(WebsocketConst.MSG_CONTENT, notice.getNoticeContent());
if(!"3".equals(notice.getNoticeType())){//系统通知 通知公告
oneToManyWebSocket.sendMessage(obj.toString());
SysNoticeUser sysNoticeUser = null;
Map<String, Session> users = oneToManyWebSocket.getUsers();
for(String userId:users.keySet()){
sysNoticeUser = new SysNoticeUser();
sysNoticeUser.setNoticeId(notice.getNoticeId());
sysNoticeUser.setUserId(Long.valueOf(userId));
sysNoticeUsers.add(sysNoticeUser);
}
noticeService.insertNoticeUserBatch(sysNoticeUsers);
}
return success(); return success();
} }
@ -140,4 +124,65 @@ public class SysNoticeController extends BaseController
} }
return groupedByNoticeType; return groupedByNoticeType;
} }
/**
* 导航面板 消息通知 获取详细信息
*/
@GetMapping(value = "navbarNotice/{noticeId}")
public AjaxResult getNavbarNoticeInfo(@PathVariable Long noticeId)
{
return success(noticeService.selectNoticeById(noticeId));
}
/**
* 推送消息插入中间表
* @param userId 发送对象排除自身
* @param notice 发送信息对象
* @param noticeService 接口
* @param oneToManyWebSocket WebSocket服务
*/
public static void insertNoticeUser(Long userId,SysNotice notice,ISysNoticeService noticeService,OneToManyWebSocket oneToManyWebSocket,List<String> userIds){
List<SysNoticeUser> sysNoticeUsers = new ArrayList<SysNoticeUser>();
JSONObject obj = new JSONObject();
obj.put(WebsocketConst.MSG_ID, notice.getNoticeId());
obj.put(WebsocketConst.MSG_TITLE, notice.getNoticeTitle());
obj.put(WebsocketConst.MSG_CONTENT, notice.getNoticeContent());
if(!"3".equals(notice.getNoticeType())){//系统通知 通知公告
oneToManyWebSocket.sendMessage(obj.toString(),String.valueOf(userId));
SysNoticeUser sysNoticeUser = null;
Map<String, Session> users = oneToManyWebSocket.getUsers();//获取在线用户
for(String userid:users.keySet()){
if(!userid.equals(String.valueOf(userId))){
sysNoticeUser = new SysNoticeUser();
sysNoticeUser.setNoticeId(notice.getNoticeId());
sysNoticeUser.setUserId(Long.valueOf(userid));
sysNoticeUsers.add(sysNoticeUser);
}
}
if(!sysNoticeUsers.isEmpty()){
noticeService.insertNoticeUserBatch(sysNoticeUsers);
}
}else{
if(userIds!=null&&userIds.size()>0){
oneToManyWebSocket.sendMessageByUserIds(obj.toString(),userIds);
SysNoticeUser sysNoticeUser = null;
Map<String, Session> users = oneToManyWebSocket.getUsers();//获取在线用户
for(String userid:users.keySet()){
if(userIds.contains(userid)){
sysNoticeUser = new SysNoticeUser();
sysNoticeUser.setNoticeId(notice.getNoticeId());
sysNoticeUser.setUserId(Long.valueOf(userid));
sysNoticeUsers.add(sysNoticeUser);
}
}
if(!sysNoticeUsers.isEmpty()){
noticeService.insertNoticeUserBatch(sysNoticeUsers);
}
}
}
}
} }

View File

@ -90,29 +90,30 @@ public class OneToManyWebSocket {
/** /**
* 群发消息 * 群发消息
*
* @param message * @param message
* @param userId
*/ */
public static void sendMessage(String message) { public static void sendMessage(String message,String userId) {
for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) { for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) {
Session toSession = sessionEntry.getValue(); Session toSession = sessionEntry.getValue();
String suid = sessionEntry.getKey();
// 排除掉自己 // 排除掉自己
//if (!fromSession.getId().equals(toSession.getId())) { if (!suid.equals(userId)) {
LOGGER.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message); LOGGER.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
toSession.getAsyncRemote().sendText(message); toSession.getAsyncRemote().sendText(message);
//} }
} }
} }
/** /**
* 群发消息 * 群发消息
*
* @param message * @param message
* 消息内容 * @param userIds
*/ */
public static void sendMessage(String message, List<String> ids) {
for (String id : ids) { public static void sendMessageByUserIds(String message, List<String> userIds) {
for (String id : userIds) {
Session session = clients.get(id); Session session = clients.get(id);
if(session!=null){ if(session!=null){
LOGGER.info("服务端给客户端[{}]发送消息{}", session.getId(), message); LOGGER.info("服务端给客户端[{}]发送消息{}", session.getId(), message);

View File

@ -35,6 +35,9 @@ public class SysNotice extends BaseEntity
/** 公告状态0正常 1关闭 */ /** 公告状态0正常 1关闭 */
private String status; private String status;
/** 公告是否已读0未读 1已读 */
private String isRead;
public Long getNoticeId() public Long getNoticeId()
{ {
return noticeId; return noticeId;
@ -94,6 +97,10 @@ public class SysNotice extends BaseEntity
return status; return status;
} }
public String getIsRead() { return isRead; }
public void setIsRead(String isRead) { this.isRead = isRead; }
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@ -92,14 +92,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
inner join sys_user_notice b on a.notice_id = b.noticeId inner join sys_user_notice b on a.notice_id = b.noticeId
<where> <where>
and b.userId = #{userId} and b.userId = #{userId}
and CONVERT(date, create_time) = CONVERT(date, GETDATE()) and CONVERT(date, a.create_time) = CONVERT(date, GETDATE())
and a.status = '0' and a.isRead = '0'
</where> </where>
order by create_time desc order by create_time desc
</select> </select>
<insert id="insertNoticeUserBatch" parameterType="java.util.List"> <insert id="insertNoticeUserBatch">
insert into sys_user_notice insert into sys_user_notice(noticeId,userId)
(noticeId,userId)
values values
<foreach collection="list" item="item" index="index" separator=","> <foreach collection="list" item="item" index="index" separator=",">
( (

View File

@ -7,5 +7,9 @@ ENV = 'development'
# 江南业务系统/开发环境 # 江南业务系统/开发环境
VUE_APP_BASE_API = '/dev-api' VUE_APP_BASE_API = '/dev-api'
# 江南业务系统-WebSocket/开发环境
VUE_APP_WEBSOCKET_API = '/websocket-api'
VUE_APP_WSS_WEBSOCKET_API = '/wss-websocket-api'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@ -6,3 +6,7 @@ ENV = 'production'
# 江南业务系统/生产环境 # 江南业务系统/生产环境
VUE_APP_BASE_API = '/prod-api' VUE_APP_BASE_API = '/prod-api'
# 江南业务系统-WebSocket/开发环境
VUE_APP_WEBSOCKET_API = '/websocket-api'
VUE_APP_WSS_WEBSOCKET_API = '/wss-websocket-api'

View File

@ -51,3 +51,12 @@ export function navbarNoticelist(query) {
params: query params: query
}) })
} }
// 导航面板 消息通知 获取详细信息
export function getNavbarNotice(noticeId) {
return request({
url: '/system/notice/navbarNotice/' + noticeId,
method: 'get'
})
}

View File

@ -3,7 +3,7 @@
<el-dropdown> <el-dropdown>
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<svg-icon icon-class="message" slot="reference" /> <svg-icon icon-class="message" slot="reference" />
<el-badge :value="noteTotal"></el-badge> <el-badge :value="noteTotal" v-if="noteTotal>0"></el-badge>
</span> </span>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
@ -12,21 +12,62 @@
<el-dropdown-item v-for="(item,index) in noticeData" :key="index"> <el-dropdown-item v-for="(item,index) in noticeData" :key="index">
<el-link :underline="false" @click="clickNote(item)" :style="index==0?'': 'margin-top :15px'">{{item.noticeTitle}}</el-link> <el-link :underline="false" @click="clickNote(item)" :style="index==0?'': 'margin-top :15px'">{{item.noticeTitle}}</el-link>
</el-dropdown-item> </el-dropdown-item>
<!--
<el-link :underline="false" style="margin-top :15px" v-if="noticeData.length>5" type="primary">更多消息</el-link> <el-link :underline="false" style="margin-top :15px" v-if="noticeData.length>5" type="primary">更多消息</el-link>
-->
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="系统公告" name="second"> <el-tab-pane label="系统公告" name="second">
<el-dropdown-item v-for="(item,index) in sysLog" :key="index"> <el-dropdown-item v-for="(item,index) in sysLog" :key="index">
<el-link :underline="false" @click="clickNote(item)" :style="index==0?'': 'margin-top :15px'">{{item.noticeTitle}}</el-link> <el-link :underline="false" @click="clickNote(item)" :style="index==0?'': 'margin-top :15px'">{{item.noticeTitle}}</el-link>
</el-dropdown-item> </el-dropdown-item>
<!--
<el-link v-if="sysLog.length>5" type="primary">更多消息</el-link> <el-link v-if="sysLog.length>5" type="primary">更多消息</el-link>
-->
</el-tab-pane>
<el-tab-pane label="业务通知" name="third">
<el-dropdown-item v-for="(item,index) in businessData" :key="index">
<el-link :underline="false" @click="clickNote(item)" :style="index==0?'': 'margin-top :15px'">{{item.noticeTitle}}</el-link>
</el-dropdown-item>
<!--
<el-link :underline="false" style="margin-top :15px" v-if="noticeData.length>5" type="primary">更多消息</el-link>
-->
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-dropdown-menu> </el-dropdown-menu>
<!--<mode-dialog :visible="noteVisible" :noteType="noteType" :noticeId="noticeId" @close="closeMode"></mode-dialog>--> <el-dialog :title="noteTitle" :visible.sync="noteVisible" width="780px" append-to-body>
<el-form ref="form" :model="form" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="标题" prop="noticeTitle">
<el-input v-model="form.noticeTitle" :disabled="true"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="类型" prop="noticeType">
<el-select v-model="form.noticeType" :disabled="true">
<el-option
v-for="dict in dict.type.sys_notice_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="内容">
<editor v-model="form.noticeContent" :min-height="192"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-dialog>
</el-dropdown> </el-dropdown>
</template> </template>
<script> <script>
import { navbarNoticelist } from '@/api/system/notice'; import { navbarNoticelist } from '@/api/system/notice';
import { getNavbarNotice} from "@/api/system/notice";
/* /*
import ModeDialog from '@/components/ModeDialog' import ModeDialog from '@/components/ModeDialog'
*/ */
@ -34,9 +75,7 @@
export default { export default {
name: 'NavbarNotice', name: 'NavbarNotice',
/* dicts: ['sys_notice_type'],
components: { ModeDialog },
*/
data() { data() {
return { return {
activeName: 'first', activeName: 'first',
@ -45,18 +84,25 @@
sysLog: [], sysLog: [],
// //
noticeData: [], noticeData: [],
//
businessData: [],
websock: null, websock: null,
lockReconnect: false, lockReconnect: false,
heartCheck: null,
//
noteTitle: '',
noteVisible: false, noteVisible: false,
noteType: 0, //
noticeId: 0 form: {},
} }
}, },
mounted() { mounted() {
// WebSocket // WebSocket
this.initWebSocket(); this.initWebSocket();
}, },
created(){
this.getList();
},
destroyed: function() { // destroyed: function() { //
this.websocketOnclose(); this.websocketOnclose();
}, },
@ -71,24 +117,42 @@
var sysLog = res[2]?res[2]:[]; var sysLog = res[2]?res[2]:[];
this.sysLog = sysLog.slice(0,5); this.sysLog = sysLog.slice(0,5);
this.noteTotal = this.noticeData.length+this.sysLog.length; var businessData = res[3]?res[3]:[];
this.businessData = businessData.slice(0,5);
this.noteTotal = this.noticeData.length+this.sysLog.length+this.businessData.length;
}) })
}, },
handleClick(tab, event) { handleClick(tab, event) {
}, },
clickNote(data) { clickNote(data) {
getNavbarNotice(data.noticeId).then(response => {
this.form = response.data;
this.noteTitle = "消息详情"
this.noteVisible = true this.noteVisible = true
this.noticeId = data.noticeId });
this.noteType = data.noticeType == '1' ? 0 : 1
},
closeMode() {
this.getList()
this.noteVisible = false
}, },
initWebSocket() { initWebSocket() {
var userId = this.$store.state.user.id; var userId = this.$store.state.user.id;
// WebSocketwshttpwsshttps // WebSocketwshttpwsshttps
var url = 'ws://localhost:3334/websocket/message/'+userId; // Location
const nowLocation = window.location;
// => httphttps
const protocol = nowLocation.protocol;
// hostName => ip
const hostName = nowLocation.hostname;
// host => ip:port
const host = nowLocation.host;
// websocket api
// 使 ws wss
//const websocket_pattern = (hostName == '') ? 'wss-websocket-api' : 'websocket-api';
const websocket_pattern = 'websocket-api';
// websocket
//const webSocketApiUrl = ((protocol.startsWith('https')) ? 'wss://' : 'ws://') + host + '/' + websocket_pattern;
const webSocketApiUrl = 'ws://' + host + '/' + websocket_pattern;
// WebSocket,
// /websocket/template-push/ websocket
let url = webSocketApiUrl + '/websocket/message/'+userId;
this.websock = new WebSocket(url) this.websock = new WebSocket(url)
this.websock.onopen = this.websocketOnopen this.websock.onopen = this.websocketOnopen
this.websock.onerror = this.websocketOnerror this.websock.onerror = this.websocketOnerror
@ -113,6 +177,8 @@
this.getList(); this.getList();
Notification({ Notification({
title: '消息', title: '消息',
duration: 3000,
dangerouslyUseHTMLString: true,
message: JSON.parse(e.data).noticeTitle message: JSON.parse(e.data).noticeTitle
}) })
}, },
@ -162,7 +228,7 @@
width:300px width:300px
} }
.el-dropdown-menu{ .el-dropdown-menu{
width:250px width:300px
} }
::v-deep .el-dropdown-menu { ::v-deep .el-dropdown-menu {
top: 28px; top: 28px;

View File

@ -40,8 +40,16 @@ module.exports = {
pathRewrite: { pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '' ['^' + process.env.VUE_APP_BASE_API]: ''
} }
},
[process.env.VUE_APP_WEBSOCKET_API]: {
target: `http://localhost:3334`,
changeOrigin: true,
ws: true,
pathRewrite: {
['^' + process.env.VUE_APP_WEBSOCKET_API]: ''
} }
}, },
},
disableHostCheck: true disableHostCheck: true
}, },
css: { css: {