diff --git a/commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java b/commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java index f2dc541..de53313 100644 --- a/commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java +++ b/commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java @@ -6,7 +6,8 @@ public enum MessageTypeEnum { TEXT(0,"文字"), FILE(1,"文件"), IMAGE(2,"图片"), - VIDEO(3,"视频"); + VIDEO(3,"视频"), + TIP(10,"系统提示"); private Integer code; diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java index 0d7a62b..aac8764 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java @@ -40,7 +40,7 @@ public class GroupController { @ApiOperation(value = "解散群聊",notes="解散群聊") @DeleteMapping("/delete/{groupId}") - public Result deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){ + public Result deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){ groupService.deleteGroup(groupId); return ResultUtils.success(); } @@ -72,14 +72,14 @@ public class GroupController { @ApiOperation(value = "退出群聊",notes="退出群聊") @DeleteMapping("/quit/{groupId}") - public Result quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){ + public Result quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){ groupService.quitGroup(groupId); return ResultUtils.success(); } @ApiOperation(value = "踢出群聊",notes="将用户踢出群聊") @DeleteMapping("/kick/{groupId}") - public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId, + public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId, @NotNull(message = "用户id不能为空") @RequestParam Long userId){ groupService.kickGroup(groupId,userId); return ResultUtils.success(); diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java index 8151303..99bac42 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java @@ -8,13 +8,10 @@ import com.bx.implatform.vo.GroupMessageVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.validation.Valid; - +import javax.validation.constraints.NotNull; @Api(tags = "群聊消息") @@ -28,11 +25,16 @@ public class GroupMessageController { @PostMapping("/send") @ApiOperation(value = "发送群聊消息",notes="发送群聊消息") - public Result register(@Valid @RequestBody GroupMessageVO vo){ - groupMessageService.sendMessage(vo); - return ResultUtils.success(); + public Result sendMessage(@Valid @RequestBody GroupMessageVO vo){ + return ResultUtils.success(groupMessageService.sendMessage(vo)); } + @DeleteMapping("/recall/{id}") + @ApiOperation(value = "撤回消息",notes="撤回群聊消息") + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id){ + groupMessageService.recallMessage(id); + return ResultUtils.success(); + } @PostMapping("/pullUnreadMessage") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息") diff --git a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java index 67f0487..5fd0286 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java @@ -8,12 +8,10 @@ import com.bx.implatform.vo.PrivateMessageVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.validation.Valid; +import javax.validation.constraints.NotNull; @Api(tags = "私聊消息") @RestController @@ -24,12 +22,20 @@ public class PrivateMessageController { private IPrivateMessageService privateMessageService; @PostMapping("/send") - @ApiOperation(value = "发送消息",notes="发送单人消息") - public Result register(@Valid @RequestBody PrivateMessageVO vo){ - privateMessageService.sendMessage(vo); + @ApiOperation(value = "发送消息",notes="发送私聊消息") + public Result sendMessage(@Valid @RequestBody PrivateMessageVO vo){ + return ResultUtils.success(privateMessageService.sendMessage(vo)); + } + + + @DeleteMapping("/recall/{id}") + @ApiOperation(value = "撤回消息",notes="撤回私聊消息") + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id){ + privateMessageService.recallMessage(id); return ResultUtils.success(); } + @PostMapping("/pullUnreadMessage") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息") public Result pullUnreadMessage(){ diff --git a/im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java b/im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java index 991dc78..e38012e 100644 --- a/im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java +++ b/im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java @@ -51,7 +51,7 @@ public class PrivateMessage extends Model { private String content; /** - * 消息类型 + * 消息类型 0:文字 1:图片 2:文件 3:语音 10:撤回消息 */ @TableField("type") private Integer type; diff --git a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java index 435d693..0bd85fb 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java @@ -8,7 +8,9 @@ import com.bx.implatform.vo.GroupMessageVO; public interface IGroupMessageService extends IService { - void sendMessage(GroupMessageVO vo); + Long sendMessage(GroupMessageVO vo); + + void recallMessage(Long id); void pullUnreadMessage(); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java index 691b530..e6c8069 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java @@ -7,7 +7,9 @@ import com.bx.implatform.vo.PrivateMessageVO; public interface IPrivateMessageService extends IService { - void sendMessage(PrivateMessageVO vo); + Long sendMessage(PrivateMessageVO vo); + + void recallMessage(Long id); void pullUnreadMessage(); diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java index 31ef659..2f4859f 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java @@ -3,6 +3,7 @@ package com.bx.implatform.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.common.contant.RedisKey; +import com.bx.common.enums.MessageTypeEnum; import com.bx.common.enums.ResultCode; import com.bx.common.model.im.GroupMessageInfo; import com.bx.common.util.BeanUtils; @@ -46,7 +47,7 @@ public class GroupMessageServiceImpl extends ServiceImpl> serverMap = new ConcurrentHashMap<>(); - userIds.parallelStream().forEach(id->{ - String key = RedisKey.IM_USER_SERVER_ID + id; - Integer serverId = (Integer)redisTemplate.opsForValue().get(key); - if(serverId != null){ - if(serverMap.containsKey(serverId)){ - serverMap.get(serverId).add(id); - }else { - List list = Collections.synchronizedList(new LinkedList()); - list.add(id); - serverMap.put(serverId,list); - } - } - }); - // 逐个server发送 - for (Map.Entry> entry : serverMap.entrySet()) { - GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class); - msgInfo.setRecvIds(new LinkedList<>(entry.getValue())); - String key = RedisKey.IM_UNREAD_GROUP_MESSAGE +entry.getKey(); - redisTemplate.opsForList().rightPush(key,msgInfo); + // 群发 + GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class); + this.sendMessage(userIds,msgInfo); + log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}",userId,vo.getGroupId(),vo.getContent()); + return msg.getId(); + } + + + + + /** + * 撤回消息 + * + * @param id 消息id + */ + @Override + public void recallMessage(Long id) { + Long userId = SessionContext.getSession().getId(); + GroupMessage msg = this.getById(id); + if(msg == null){ + throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息不存在"); + } + if(msg.getSendId() != userId){ + throw new GlobalException(ResultCode.PROGRAM_ERROR,"这条消息不是您发送的呢"); + } + // 判断是否在群里 + GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(),userId); + if(member == null){ + throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息"); } - log.info("发送群聊消息,发送id:{},群聊id:{}",userId,vo.getGroupId()); + // 直接物理删除 + this.removeById(id); + // 群发 + List userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId()); + GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class); + msgInfo.setType(MessageTypeEnum.TIP.getCode()); + String content = String.format("'%s'撤回了一条消息",member.getAliasName()); + msgInfo.setContent(content); + this.sendMessage(userIds,msgInfo); + log.info("删除群聊消息,发送id:{},群聊id:{},内容:{}",userId,msg.getGroupId(),msg.getContent()); } + /** * 异步拉取群聊消息,通过websocket异步推送 * @@ -129,6 +148,35 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds, GroupMessageInfo msgInfo){ + // 根据群聊每个成员所连的IM-server,进行分组 + Map> serverMap = new ConcurrentHashMap<>(); + userIds.parallelStream().forEach(id->{ + String key = RedisKey.IM_USER_SERVER_ID + id; + Integer serverId = (Integer)redisTemplate.opsForValue().get(key); + if(serverId != null){ + if(serverMap.containsKey(serverId)){ + serverMap.get(serverId).add(id); + }else { + // 此处需要加锁,否则list可以会被覆盖 + synchronized(serverMap){ + List list = Collections.synchronizedList(new LinkedList()); + list.add(id); + serverMap.put(serverId,list); + } + } + } + }); + // 逐个server发送 + for (Map.Entry> entry : serverMap.entrySet()) { + msgInfo.setRecvIds(new LinkedList<>(entry.getValue())); + String key = RedisKey.IM_UNREAD_GROUP_MESSAGE +entry.getKey(); + redisTemplate.opsForList().rightPush(key,msgInfo); } } } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java index e4c5721..67a1506 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.common.contant.RedisKey; import com.bx.common.enums.MessageStatusEnum; +import com.bx.common.enums.MessageTypeEnum; import com.bx.common.enums.ResultCode; import com.bx.common.model.im.PrivateMessageInfo; import com.bx.common.util.BeanUtils; @@ -36,10 +37,10 @@ public class PrivateMessageServiceImpl extends ServiceImpl
  • + :showName="showName(msgInfo)" :msgInfo="msgInfo" + @delete="deleteMessage" + @recall="recallMessage">
  • @@ -108,10 +110,11 @@ url: this.messageAction, method: 'post', data: msgInfo - }).then((data) => { + }).then((id) => { let info = { type: this.chat.type, targetId: file.raw.targetId, + msgId : id, fileId: file.raw.uid, content: JSON.stringify(res.data), loadStatus: "ok" @@ -135,6 +138,7 @@ thumbUrl: url } let msgInfo = { + id:0, fileId: file.uid, sendId: this.mine.id, content: JSON.stringify(data), @@ -168,11 +172,12 @@ url: this.messageAction, method: 'post', data: msgInfo - }).then(() => { + }).then((id) => { let info = { type: this.chat.type, targetId: file.raw.targetId, fileId: file.raw.uid, + msgId : id, content: JSON.stringify(data), loadStatus: "ok" } @@ -196,6 +201,7 @@ url: url } let msgInfo = { + id: 0, fileId: file.uid, sendId: this.mine.id, content: JSON.stringify(data), @@ -210,7 +216,7 @@ this.$store.commit("insertMessage", msgInfo); // 滚动到底部 this.scrollToBottom(); - // 借助file对象保存对方id + // 借助file对象 file.targetId = this.chat.targetId; }, handleCloseSide() { @@ -232,7 +238,6 @@ }, showVoiceBox() { this.showVoice = true; - }, closeVoiceBox() { this.showVoice = false; @@ -248,8 +253,9 @@ url: this.messageAction, method: 'post', data: msgInfo - }).then(() => { + }).then((id) => { this.$message.success("发送成功"); + msgInfo.id = id; msgInfo.sendTime = new Date().getTime(); msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.selfSend = true; @@ -270,7 +276,6 @@ } }, sendTextMessage() { - if (!this.sendText.trim()) { this.$message.error("不能发送空白信息"); return @@ -285,9 +290,10 @@ url: this.messageAction, method: 'post', data: msgInfo - }).then((data) => { + }).then((id) => { this.$message.success("发送成功"); this.sendText = ""; + msgInfo.id = id; msgInfo.sendTime = new Date().getTime(); msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.selfSend = true; @@ -304,6 +310,35 @@ return false; } }, + deleteMessage(msgInfo){ + this.$confirm( '确认删除消息?','删除消息', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.$store.commit("deleteMessage",msgInfo); + }); + }, + recallMessage(msgInfo){ + this.$confirm('确认撤回消息?','撤回消息', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + let url = `/message/${this.chat.type.toLowerCase()}/recall/${msgInfo.id}` + this.$http({ + url: url, + method: 'delete' + }).then(() => { + this.$message.success("消息已撤回"); + msgInfo = JSON.parse(JSON.stringify(msgInfo)); + msgInfo.type = 10; + msgInfo.content = '你撤回了一条消息'; + this.$store.commit("insertMessage",msgInfo); + }) + }); + + }, loadGroup(groupId) { this.$http({ url: `/group/find/${groupId}`, @@ -378,6 +413,7 @@ messageAction() { return `/message/${this.chat.type.toLowerCase()}/send`; } + }, watch: { chat: { @@ -433,7 +469,7 @@ border: #dddddd solid 1px; .im-chat-box { - ul { + >ul { padding: 20px; li { diff --git a/im-ui/src/components/chat/MessageItem.vue b/im-ui/src/components/chat/MessageItem.vue index 2b2ce20..88d0317 100644 --- a/im-ui/src/components/chat/MessageItem.vue +++ b/im-ui/src/components/chat/MessageItem.vue @@ -1,58 +1,62 @@ + + diff --git a/im-ui/src/main.js b/im-ui/src/main.js index fce1265..5fb1b03 100644 --- a/im-ui/src/main.js +++ b/im-ui/src/main.js @@ -6,8 +6,8 @@ import 'element-ui/lib/theme-chalk/index.css'; import './assets/iconfont/iconfont.css'; import httpRequest from './api/httpRequest'; import * as socketApi from './api/wssocket'; -import emotion from './api/emotion.js'; -import element from './api/element.js'; +import emotion from './api/emotion.js'; +import element from './api/element.js'; import store from './store'; diff --git a/im-ui/src/store/chatStore.js b/im-ui/src/store/chatStore.js index 2a26d71..8170872 100644 --- a/im-ui/src/store/chatStore.js +++ b/im-ui/src/store/chatStore.js @@ -84,7 +84,7 @@ export default { break; } } - console.log(msgInfo.type) + // 插入新的数据 if(msgInfo.type == 1){ chat.lastContent = "[图片]"; }else if(msgInfo.type == 2){ @@ -95,12 +95,48 @@ export default { chat.lastContent = msgInfo.content; } chat.lastSendTime = msgInfo.sendTime; - chat.messages.push(msgInfo); // 如果不是当前会话,未读加1 chat.unreadCount++; if(msgInfo.selfSend){ chat.unreadCount=0; } + console.log(msgInfo); + // 如果是已存在消息,则覆盖旧的消息数据 + for (let idx in chat.messages) { + if(msgInfo.id && chat.messages[idx].id == msgInfo.id){ + Object.assign(chat.messages[idx], msgInfo); + return; + } + } + // 新的消息 + chat.messages.push(msgInfo); + + }, + deleteMessage(state, msgInfo){ + // 获取对方id或群id + let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; + let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; + let chat = null; + for (let idx in state.chats) { + if (state.chats[idx].type == type && + state.chats[idx].targetId === targetId) { + chat = state.chats[idx]; + break; + } + } + + for (let idx in chat.messages) { + // 已经发送成功的,根据id删除 + if(chat.messages[idx].id && chat.messages[idx].id == msgInfo.id){ + chat.messages.splice(idx, 1); + break; + } + // 没有发送成功的,根据发送时间删除 + if(!chat.messages[idx].id && chat.messages[idx].sendTime == msgInfo.sendTime){ + chat.messages.splice(idx, 1); + break; + } + } }, handleFileUpload(state, info) { // 文件上传后数据更新 @@ -110,6 +146,9 @@ export default { if (info.content) { msg.content = info.content; } + if(info.msgId){ + msg.id = info.msgId; + } }, updateChatFromFriend(state, friend) { for (let i in state.chats) { diff --git a/im-ui/src/store/friendStore.js b/im-ui/src/store/friendStore.js index 20ebeaf..3377c25 100644 --- a/im-ui/src/store/friendStore.js +++ b/im-ui/src/store/friendStore.js @@ -25,7 +25,7 @@ export default { state.friends.forEach((f,index)=>{ if(f.id==friend.id){ // 拷贝属性 - state.friends[index] = Object.assign(state.friends[index], friend); + Object.assign(state.friends[index], friend); } }) }, diff --git a/im-ui/src/store/groupStore.js b/im-ui/src/store/groupStore.js index 36e0bdc..ec1ede2 100644 --- a/im-ui/src/store/groupStore.js +++ b/im-ui/src/store/groupStore.js @@ -39,7 +39,7 @@ export default { state.groups.forEach((g,idx)=>{ if(g.id==group.id){ // 拷贝属性 - state.groups[idx] = Object.assign(state.groups[idx], group); + Object.assign(state.groups[idx], group); } }) } diff --git a/im-ui/src/store/uiStore.js b/im-ui/src/store/uiStore.js index a6ce3d2..351f6f4 100644 --- a/im-ui/src/store/uiStore.js +++ b/im-ui/src/store/uiStore.js @@ -12,6 +12,7 @@ export default { show: false, url: "" } + }, mutations: { showUserInfoBox(state,user){ diff --git a/im-ui/src/view/Home.vue b/im-ui/src/view/Home.vue index 4163930..f50ab7f 100644 --- a/im-ui/src/view/Home.vue +++ b/im-ui/src/view/Home.vue @@ -277,4 +277,4 @@ text-align: center; } - +