Browse Source

feat: 群聊会话增加邀请、解散、退出、移除提示

master
Blue 2 years ago
parent
commit
969ed04c5b
  1. 89
      im-commom/src/main/java/com/bx/imcommon/util/CommaTextUtils.java
  2. 6
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  3. 7
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  4. 5
      im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java
  5. 6
      im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java
  6. 17
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  7. 10
      im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java
  8. 7
      im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java
  9. 6
      im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java
  10. 20
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  11. 123
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  12. 84
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  13. 86
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  14. 8
      im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java
  15. 4
      im-platform/src/main/resources/db/db.sql
  16. 2
      im-ui/src/api/enums.js
  17. 8
      im-ui/src/components/chat/ChatGroupSide.vue
  18. 2
      im-ui/src/components/chat/ChatMessageItem.vue
  19. 16
      im-ui/src/store/chatStore.js
  20. 3
      im-ui/src/view/Group.vue
  21. 90
      im-ui/src/view/Home.vue
  22. 102
      im-uniapp/App.vue
  23. 2
      im-uniapp/common/enums.js
  24. 2
      im-uniapp/components/chat-message-item/chat-message-item.vue
  25. 8
      im-uniapp/pages/group/group-info.vue
  26. 22
      im-uniapp/store/chatStore.js

89
im-commom/src/main/java/com/bx/imcommon/util/CommaTextUtils.java

@ -0,0 +1,89 @@
package com.bx.imcommon.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 逗号分格文本处理工具类
*
* @author: blue
* @date: 2023-11-09 09:52:49
* @version: 1.0
*/
public class CommaTextUtils {
/**
* 文本转列表
*
* @param strText 文件
* @return 列表
*/
public static List<String> asList(String strText) {
if (StrUtil.isEmpty(strText)) {
return new LinkedList<>();
}
return new LinkedList<>(Arrays.asList(strText.split(",")));
}
/**
* 列表转字符串并且自动清空去重排序
*
* @param texts 列表
* @return 文本
*/
public static <T> String asText(Collection<T> texts) {
if (CollUtil.isEmpty(texts)) {
return StrUtil.EMPTY;
}
return texts.stream().map(text -> StrUtil.toString(text)).filter(StrUtil::isNotEmpty).distinct().sorted().collect(Collectors.joining(","));
}
/**
* 追加一个单词
*
* @param strText 文本
* @param word 单词
* @return 文本
*/
public static <T> String appendWord(String strText, T word) {
List<String> texts = asList(strText);
texts.add(StrUtil.toString(word));
return asText(texts);
}
/**
* 删除一个单词
*
* @param strText 文本
* @param word 单词
* @return 文本
*/
public static <T> String removeWord(String strText, T word) {
List<String> texts = asList(strText);
texts.remove(StrUtil.toString(word));
return asText(texts);
}
/**
* 合并
*
* @param strText1 文本1
* @param strText2 文本2
* @return 文本
*/
public static String merge(String strText1, String strText2) {
List<String> texts = asList(strText1);
texts.addAll(asList(strText2));
return asText(texts);
}
}

6
im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java

@ -42,6 +42,12 @@ public class GroupMessageController {
return ResultUtils.success(groupMessageService.loadMessage(minId));
}
@GetMapping("/pullOfflineMessage")
@ApiOperation(value = "拉取离线消息", notes = "拉取离线消息,消息将通过webscoket异步推送")
public Result pullOfflineMessage(@RequestParam Long minId) {
groupMessageService.pullOfflineMessage(minId);
return ResultUtils.success();
}
@PutMapping("/readed")
@ApiOperation(value = "消息已读", notes = "将群聊中的消息状态置为已读")

7
im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java

@ -43,6 +43,13 @@ public class PrivateMessageController {
return ResultUtils.success(privateMessageService.loadMessage(minId));
}
@GetMapping("/pullOfflineMessage")
@ApiOperation(value = "拉取离线消息", notes = "拉取离线消息,消息将通过webscoket异步推送")
public Result pullOfflineMessage(@RequestParam Long minId) {
privateMessageService.pullOfflineMessage(minId);
return ResultUtils.success();
}
@PutMapping("/readed")
@ApiOperation(value = "消息已读", notes = "将会话中接收的消息状态置为已读")
public Result readedMessage(@RequestParam Long friendId) {

5
im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java

@ -69,6 +69,11 @@ public class GroupMember extends Model<GroupMember> {
@TableField("quit")
private Boolean quit;
/**
* 退群时间
*/
@TableField("quit_time")
private Date quitTime;
/**
* 创建时间

6
im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java

@ -50,6 +50,12 @@ public class GroupMessage extends Model<GroupMessage> {
@TableField("send_nick_name")
private String sendNickName;
/**
* 接受用户id,为空表示全体发送
*/
@TableField("recv_ids")
private String recvIds;
/**
* @用户列表
*/

17
im-platform/src/main/java/com/bx/implatform/enums/MessageType.java

@ -1,6 +1,8 @@
package com.bx.implatform.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
public enum MessageType {
@ -35,9 +37,22 @@ public enum MessageType {
READED(11, "已读"),
/**
* 消息已读回执(更新已读数量)
* 消息已读回执
*/
RECEIPT(12, "消息已读回执"),
/**
* 时间提示
*/
TIP_TIME(20,"时间提示"),
/**
* 文字提示
*/
TIP_TEXT(21,"文字提示"),
/**
* 消息加载标记
*/
LOADDING(30,"加载中"),
/**
* 呼叫
*/

10
im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java

@ -24,6 +24,14 @@ public interface IGroupMemberService extends IService<GroupMember> {
*/
List<GroupMember> findByUserId(Long userId);
/**
* 根据用户id查询一个月内退的群
*
* @param userId 用户id
* @return 成员列表
*/
List<GroupMember> findQuitInMonth(Long userId);
/**
* 根据群聊id查询群聊成员包括已退出
*
@ -32,6 +40,8 @@ public interface IGroupMemberService extends IService<GroupMember> {
*/
List<GroupMember> findByGroupId(Long groupId);
/**
* 根据群聊id查询没有退出的群聊成员id
*

7
im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java

@ -32,6 +32,13 @@ public interface IGroupMessageService extends IService<GroupMessage> {
*/
List<GroupMessageVO> loadMessage(Long minId);
/**
* 拉取离线消息只能拉取最近1个月的消息最多拉取1000条
*
* @param minId 消息起始id
*/
void pullOfflineMessage(Long minId);
/**
* 消息已读,同步其他终端清空未读数量
*

6
im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java

@ -44,6 +44,12 @@ public interface IPrivateMessageService extends IService<PrivateMessage> {
*/
List<PrivateMessageVO> loadMessage(Long minId);
/**
* 拉取离线消息只能拉取最近1个月的消息最多拉取1000条
*
* @param minId 消息起始id
*/
void pullOfflineMessage(Long minId);
/**
* 消息已读,将整个会话的消息都置为已读状态

20
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java

@ -9,11 +9,13 @@ import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.mapper.GroupMemberMapper;
import com.bx.implatform.service.IGroupMemberService;
import com.bx.implatform.util.DateTimeUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@ -34,7 +36,7 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
@Override
public GroupMember findByGroupAndUserId(Long groupId, Long userId) {
public GroupMember findByGroupAndUserId(Long groupId, Long userId) {
QueryWrapper<GroupMember> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMember::getGroupId, groupId)
.eq(GroupMember::getUserId, userId);
@ -49,6 +51,16 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
return this.list(memberWrapper);
}
@Override
public List<GroupMember> findQuitInMonth(Long userId) {
Date monthTime = DateTimeUtils.addMonths(new Date(),-1);
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
memberWrapper.eq(GroupMember::getUserId, userId)
.eq(GroupMember::getQuit, true)
.ge(GroupMember::getQuitTime,monthTime);
return this.list(memberWrapper);
}
@Override
public List<GroupMember> findByGroupId(Long groupId) {
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
@ -72,7 +84,8 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
public void removeByGroupId(Long groupId) {
LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(GroupMember::getGroupId, groupId)
.set(GroupMember::getQuit, true);
.set(GroupMember::getQuit, true)
.set(GroupMember::getQuitTime,new Date());
this.update(wrapper);
}
@ -82,7 +95,8 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(GroupMember::getGroupId, groupId)
.eq(GroupMember::getUserId, userId)
.set(GroupMember::getQuit, true);
.set(GroupMember::getQuit, true)
.set(GroupMember::getQuitTime,new Date());
this.update(wrapper);
}
}

123
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java

@ -10,8 +10,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.util.CommaTextUtils;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.entity.Group;
@ -39,6 +41,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Slf4j
@ -63,7 +66,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
// 是否在群聊里面
GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId());
if (Objects.isNull(member) || Boolean.TRUE.equals(member.getQuit())) {
if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法发送消息");
}
// 群聊成员列表
@ -197,6 +200,98 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
return vos;
}
@Override
public void pullOfflineMessage(Long minId) {
UserSession session = SessionContext.getSession();
if(!imClient.isOnline(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "网络连接失败,无法拉取离线消息");
}
// 开启加载中标志
this.sendLoadingMessage(true);
// 查询用户加入的群组
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
Map<Long, GroupMember> groupMemberMap = CollStreamUtil.toIdentityMap(members, GroupMember::getGroupId);
Set<Long> groupIds = groupMemberMap.keySet();
// 只能拉取最近1个月的,最多拉取1000条
Date minDate = DateUtils.addMonths(new Date(), -1);
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.gt(GroupMessage::getId, minId)
.gt(GroupMessage::getSendTime, minDate)
.in(GroupMessage::getGroupId, groupIds)
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId).last("limit 1000");
List<GroupMessage> messages = this.list(wrapper);
// 通过群聊对消息进行分组
Map<Long, List<GroupMessage>> messageGroupMap = messages.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId));
// 退群前的消息
List<GroupMember> quitMembers = groupMemberService.findQuitInMonth(session.getUserId());
for(GroupMember quitMember: quitMembers){
wrapper = Wrappers.lambdaQuery();
wrapper.gt(GroupMessage::getId, minId)
.between(GroupMessage::getSendTime, minDate,quitMember.getQuitTime())
.eq(GroupMessage::getGroupId, quitMember.getGroupId())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId)
.last("limit 100");
List<GroupMessage> groupMessages = this.list(wrapper);
messageGroupMap.put(quitMember.getGroupId(),groupMessages);
groupMemberMap.put(quitMember.getGroupId(),quitMember);
}
// 推送消息
AtomicInteger sendCount = new AtomicInteger();
messageGroupMap.forEach((groupId, groupMessages) -> {
// id从小到大排序
CollectionUtil.reverse(groupMessages);
// 填充消息状态
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
Object o = redisTemplate.opsForHash().get(key, session.getUserId().toString());
long readedMaxId = Objects.isNull(o) ? -1 : Long.parseLong(o.toString());
Map<Object, Object> maxIdMap = null;
for(GroupMessage m:groupMessages){
// 排除加群之前的消息
GroupMember member = groupMemberMap.get(m.getGroupId());
if(DateUtil.compare(member.getCreatedTime(), m.getSendTime()) > 0){
continue;
}
// 排除不需要接收的消息
List<String> recvIds = CommaTextUtils.asList(m.getRecvIds());
if(!recvIds.isEmpty() && !recvIds.contains(session.getUserId().toString())){
continue;
}
// 组装vo
GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class);
// 被@用户列表
if (StringUtils.isNotBlank(m.getAtUserIds()) && Objects.nonNull(vo)) {
List<String> atIds = Splitter.on(",").trimResults().splitToList(m.getAtUserIds());
vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList()));
}
// 填充状态
vo.setStatus(readedMaxId >= m.getId() ? MessageStatus.READED.code() : MessageStatus.UNSEND.code());
// 针对回执消息填充已读人数
if(m.getReceipt()){
if(Objects.isNull(maxIdMap)) {
maxIdMap = redisTemplate.opsForHash().entries(key);
}
int count = getReadedUserIds(maxIdMap, m.getId(),m.getSendId()).size();
vo.setReadedCount(count);
}
// 推送
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(m.getSendId(), IMTerminalType.WEB.code()));
sendMessage.setRecvIds(Arrays.asList(session.getUserId()));
sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal()));
sendMessage.setSendResult(false);
sendMessage.setSendToSelf(false);
sendMessage.setData(vo);
imClient.sendGroupMessage(sendMessage);
sendCount.getAndIncrement();
}
});
// 关闭加载中标志
this.sendLoadingMessage(false);
log.info("拉取离线群聊消息,用户id:{},数量:{}",session.getUserId(),sendCount.get());
}
@Override
public void readedMessage(Long groupId) {
UserSession session = SessionContext.getSession();
@ -251,7 +346,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
msgInfo.setGroupId(groupId);
msgInfo.setReadedCount(readedCount);
msgInfo.setReceiptOk(receiptMessage.getReceiptOk());
msgInfo.setType(MessageType.RECEIPT.code());;
msgInfo.setType(MessageType.RECEIPT.code());
sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvIds(userIds);
@ -265,11 +360,17 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
@Override
public List<Long> findReadedUsers(Long groupId, Long messageId) {
UserSession session = SessionContext.getSession();
GroupMessage message = this.getById(messageId);
if (Objects.isNull(message)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在");
}
// 已读位置key
// 是否在群聊里面
GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId());
if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面");
}
// 已读位置key
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
// 一次获取所有用户的已读位置
Map<Object, Object> maxIdMap = redisTemplate.opsForHash().entries(key);
@ -304,7 +405,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
List<Long> userIds = new LinkedList<>();
maxIdMap.forEach((k, v) -> {
Long userId = Long.valueOf(k.toString());
Long maxId = Long.valueOf(v.toString());
long maxId = Long.parseLong(v.toString());
// 发送者不计入已读人数
if (!sendId.equals(userId) && maxId >= messageId) {
userIds.add(userId);
@ -313,5 +414,19 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
return userIds;
}
private void sendLoadingMessage(Boolean isLoadding){
UserSession session = SessionContext.getSession();
GroupMessageVO msgInfo = new GroupMessageVO();
msgInfo.setType(MessageType.LOADDING.code());
msgInfo.setContent(isLoadding.toString());
IMGroupMessage sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvIds(Arrays.asList(session.getUserId()));
sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal()));
sendMessage.setData(msgInfo);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
imClient.sendGroupMessage(sendMessage);
}
}

84
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@ -1,28 +1,30 @@
package com.bx.implatform.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.util.CommaTextUtils;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.Group;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.User;
import com.bx.implatform.entity.*;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.GroupMapper;
import com.bx.implatform.service.IFriendService;
import com.bx.implatform.service.IGroupMemberService;
import com.bx.implatform.service.IGroupService;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.mapper.GroupMessageMapper;
import com.bx.implatform.service.*;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.GroupInviteVO;
import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.GroupVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -44,6 +46,7 @@ import java.util.stream.Collectors;
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements IGroupService {
private final IUserService userService;
private final IGroupMemberService groupMemberService;
private final GroupMessageMapper groupMessageMapper;
private final IFriendService friendsService;
private final IMClient imClient;
private final RedisTemplate<String, Object> redisTemplate;
@ -85,7 +88,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
}
// 更新成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(), session.getUserId());
if (member == null) {
if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群聊的成员");
}
member.setAliasName(StringUtils.isEmpty(vo.getAliasName()) ? session.getNickName() : vo.getAliasName());
@ -104,6 +107,8 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
if (!group.getOwnerId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "只有群主才有权限解除群聊");
}
// 群聊用户id
List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId);
// 逻辑删除群数据
group.setDeleted(true);
this.updateById(group);
@ -112,6 +117,8 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
// 清理已读缓存
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.delete(key);
// 推送解散群聊提示
this.sendTipMessage(groupId,userIds,String.format("'%s'解散了群聊",session.getNickName()));
log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
}
@ -127,6 +134,8 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
// 清理已读缓存
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.opsForHash().delete(key,userId.toString());
// 推送退出群聊提示
this.sendTipMessage(groupId,Arrays.asList(userId),"您已退出群聊");
log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
}
@ -138,27 +147,33 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群主,没有权限踢人");
}
if (userId.equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "亲,不能自己踢自己哟");
throw new GlobalException(ResultCode.PROGRAM_ERROR, "亲,不能移除自己哟");
}
// 删除群聊成员
groupMemberService.removeByGroupAndUserId(groupId, userId);
// 清理已读缓存
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.opsForHash().delete(key,userId.toString());
// 推送踢出群聊提示
this.sendTipMessage(groupId,Arrays.asList(userId),"您已被移出群聊");
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
}
@Override
public GroupVO findById(Long groupId) {
UserSession session = SessionContext.getSession();
Group group = this.getById(groupId);
Group group = super.getById(groupId);
if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组不存在");
}
GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId());
if (member == null) {
if (Objects.isNull(member)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您未加入群聊");
}
GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class);
vo.setAliasName(member.getAliasName());
vo.setRemark(member.getRemark());
vo.setQuit(member.getQuit());
return vo;
}
@ -166,7 +181,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
@Override
public Group getById(Long groupId) {
Group group = super.getById(groupId);
if (group == null) {
if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组不存在");
}
if (group.getDeleted()) {
@ -180,6 +195,8 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
UserSession session = SessionContext.getSession();
// 查询当前用户的群id列表
List<GroupMember> groupMembers = groupMemberService.findByUserId(session.getUserId());
// 一个月内退的群可能存在退群前的离线消息,一并返回作为前端缓存
groupMembers.addAll(groupMemberService.findQuitInMonth(session.getUserId()));
if (groupMembers.isEmpty()) {
return new LinkedList<>();
}
@ -194,6 +211,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
GroupMember member = groupMembers.stream().filter(m -> g.getId().equals(m.getGroupId())).findFirst().get();
vo.setAliasName(member.getAliasName());
vo.setRemark(member.getRemark());
vo.setQuit(member.getQuit());
return vo;
}).collect(Collectors.toList());
}
@ -202,9 +220,13 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
public void invite(GroupInviteVO vo) {
UserSession session = SessionContext.getSession();
Group group = this.getById(vo.getGroupId());
if (group == null) {
if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
}
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getGroupId(), session.getUserId());
if (Objects.isNull(group) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不在群聊中,邀请失败");
}
// 群聊人数校验
List<GroupMember> members = groupMemberService.findByGroupId(vo.getGroupId());
long size = members.stream().filter(m -> !m.getQuit()).count();
@ -234,6 +256,11 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
if (!groupMembers.isEmpty()) {
groupMemberService.saveOrUpdateBatch(group.getId(), groupMembers);
}
// 推送进入群聊消息
List<Long> userIds = groupMemberService.findUserIdsByGroupId(vo.getGroupId());
String memberNames = groupMembers.stream().map(GroupMember::getAliasName).collect(Collectors.joining(","));
String content = String.format("'%s'邀请'%s'加入了群聊",session.getNickName(), memberNames);
this.sendTipMessage(vo.getGroupId(),userIds,content);
log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), vo.getFriendIds());
}
@ -249,4 +276,33 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
}).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());
}
private void sendTipMessage(Long groupId,List<Long> recvIds,String content){
UserSession session = SessionContext.getSession();
// 消息入库
GroupMessage message = new GroupMessage();
message.setContent(content);
message.setType(MessageType.TIP_TEXT.code());
message.setStatus(MessageStatus.UNSEND.code());
message.setSendTime(new Date());
message.setSendNickName(session.getNickName());
message.setGroupId(groupId);
message.setSendId(session.getUserId());
message.setRecvIds(CommaTextUtils.asText(recvIds));
groupMessageMapper.insert(message);
// 推送
GroupMessageVO msgInfo = BeanUtils.copyProperties(message,GroupMessageVO.class);
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
if(CollUtil.isEmpty(recvIds)){
// 为空表示向全体发送
List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId);
sendMessage.setRecvIds(userIds);
}else{
sendMessage.setRecvIds(recvIds);
}
sendMessage.setData(msgInfo);
sendMessage.setSendResult(false);
sendMessage.setSendToSelf(false);
imClient.sendGroupMessage(sendMessage);
}
}

86
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@ -1,5 +1,6 @@
package com.bx.implatform.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@ -7,6 +8,8 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.dto.PrivateMessageDTO;
@ -23,6 +26,7 @@ import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.util.SensitiveFilterUtil;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -172,23 +176,81 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
return messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class)).collect(Collectors.toList());
}
@Override
public void pullOfflineMessage(Long minId) {
UserSession session = SessionContext.getSession();
if(!imClient.isOnline(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "网络连接失败,无法拉取离线消息");
}
// 开启加载中标志
this.sendLoadingMessage(true);
// 查询用户好友列表
List<Friend> friends = friendService.findFriendByUserId(session.getUserId());
if (friends.isEmpty()) {
return;
}
List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList());
// 获取当前用户的消息
LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery();
// 只能拉取最近1个月的1000条消息
Date minDate = DateUtils.addMonths(new Date(), -1);
queryWrapper.gt(PrivateMessage::getId, minId)
.ge(PrivateMessage::getSendTime, minDate)
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.code())
.and(wrap -> wrap.and(
wp -> wp.eq(PrivateMessage::getSendId, session.getUserId())
.in(PrivateMessage::getRecvId, friendIds))
.or(wp -> wp.eq(PrivateMessage::getRecvId, session.getUserId())
.in(PrivateMessage::getSendId, friendIds)))
.orderByDesc(PrivateMessage::getId)
.last("limit 1000");
List<PrivateMessage> messages = this.list(queryWrapper);
// 消息顺序从小到大
CollectionUtil.reverse(messages);
// 推送消息
for(PrivateMessage m:messages ){
PrivateMessageVO vo = BeanUtils.copyProperties(m, PrivateMessageVO.class);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(m.getSendId(), IMTerminalType.WEB.code()));
sendMessage.setRecvId(m.getRecvId());
sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal()));
sendMessage.setSendToSelf(false);
sendMessage.setData(vo);
sendMessage.setSendResult(true);
imClient.sendPrivateMessage(sendMessage);
}
// 关闭加载中标志
this.sendLoadingMessage(false);
log.info("拉取私聊消息,用户id:{},数量:{}", session.getUserId(), messages.size());
}
@Transactional(rollbackFor = Exception.class)
@Override
public void readedMessage(Long friendId) {
UserSession session = SessionContext.getSession();
// 推送消息
// 推送消息给自己,清空会话列表上的已读数量
PrivateMessageVO msgInfo = new PrivateMessageVO();
msgInfo.setType(MessageType.READED.code());
msgInfo.setSendTime(new Date());
msgInfo.setSendId(session.getUserId());
msgInfo.setRecvId(friendId);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setData(msgInfo);
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(session.getUserId());
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
imClient.sendPrivateMessage(sendMessage);
// 推送回执消息给对方,更新已读状态
msgInfo = new PrivateMessageVO();
msgInfo.setType(MessageType.RECEIPT.code());
msgInfo.setSendId(session.getUserId());
msgInfo.setRecvId(friendId);
sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(friendId);
sendMessage.setSendToSelf(true);
sendMessage.setData(msgInfo);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
sendMessage.setData(msgInfo);
imClient.sendPrivateMessage(sendMessage);
// 修改消息状态为已读
LambdaUpdateWrapper<PrivateMessage> updateWrapper = Wrappers.lambdaUpdate();
@ -217,4 +279,20 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
}
return message.getId();
}
private void sendLoadingMessage(Boolean isLoadding){
UserSession session = SessionContext.getSession();
PrivateMessageVO msgInfo = new PrivateMessageVO();
msgInfo.setType(MessageType.LOADDING.code());
msgInfo.setContent(isLoadding.toString());
IMPrivateMessage sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(session.getUserId());
sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal()));
sendMessage.setData(msgInfo);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
imClient.sendPrivateMessage(sendMessage);
}
}

8
im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java

@ -1,5 +1,6 @@
package com.bx.implatform.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -40,4 +41,11 @@ public class GroupVO {
@ApiModelProperty(value = "群聊显示备注")
private String remark;
@ApiModelProperty(value = "是否已删除")
private Boolean deleted;
@ApiModelProperty(value = "是否已退出")
private Boolean quit;
}

4
im-platform/src/main/resources/db/db.sql

@ -55,9 +55,10 @@ create table `im_group_member`(
`group_id` bigint not null comment '群id',
`user_id` bigint not null comment '用户id',
`alias_name` varchar(255) DEFAULT '' comment '组内显示名称',
`head_image` varchar(255) default '' comment '用户头像',
`head_image` varchar(255) DEFAULT '' comment '用户头像',
`remark` varchar(255) DEFAULT '' comment '备注',
`quit` tinyint(1) DEFAULT 0 comment '是否已退出',
`quit_time` datetime DEFAULT NULL comment '退出时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
key `idx_group_id`(`group_id`),
key `idx_user_id`(`user_id`)
@ -68,6 +69,7 @@ create table `im_group_message`(
`group_id` bigint not null comment '群id',
`send_id` bigint not null comment '发送用户id',
`send_nick_name` varchar(255) DEFAULT '' comment '发送用户昵称',
`recv_ids` varchar(1024) DEFAULT '' comment '接收用户id,逗号分隔,为空表示发给所有成员',
`content` text comment '发送内容',
`at_user_ids` varchar(1024) comment '被@的用户id列表,逗号分隔',
`receipt` tinyint DEFAULT 0 comment '是否回执消息',

2
im-ui/src/api/enums.js

@ -9,6 +9,8 @@ const MESSAGE_TYPE = {
READED:11,
RECEIPT:12,
TIP_TIME:20,
TIP_TEXT:21,
LOADDING:30,
RTC_CALL: 101,
RTC_ACCEPT: 102,
RTC_REJECT: 103,

8
im-ui/src/components/chat/ChatGroupSide.vue

@ -1,12 +1,12 @@
<template>
<div class="chat-group-side">
<div class="group-side-search">
<div v-show="!group.quit" class="group-side-search">
<el-input placeholder="搜索群成员" v-model="searchText">
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
</div>
<el-scrollbar class="group-side-scrollbar">
<div class="group-side-member-list">
<div v-show="!group.quit" class="group-side-member-list">
<div class="group-side-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="showAddGroupMember=true">
<i class="el-icon-plus"></i>
@ -20,7 +20,7 @@
:showDel="false"></group-member>
</div>
</div>
<el-divider content-position="center"></el-divider>
<el-divider v-if="!group.quit" content-position="center"></el-divider>
<el-form labelPosition="top" class="group-side-form" :model="group">
<el-form-item label="群聊名称">
<el-input v-model="group.name" disabled maxlength="20"></el-input>
@ -38,7 +38,7 @@
<el-input v-model="group.aliasName" :disabled="!editing" placeholder="xx" maxlength="20"></el-input>
</el-form-item>
<div class="btn-group">
<div v-show="!group.quit" class="btn-group">
<el-button v-show="editing" type="success" @click="onSaveGroup()">提交</el-button>
<el-button v-show="!editing" type="primary" @click="editing=!editing">编辑</el-button>
<el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button>

2
im-ui/src/components/chat/ChatMessageItem.vue

@ -1,6 +1,6 @@
<template>
<div class="chat-msg-item">
<div class="chat-msg-tip" v-show="msgInfo.type == $enums.MESSAGE_TYPE.RECALL">{{ msgInfo.content }}</div>
<div class="chat-msg-tip" v-show="msgInfo.type == $enums.MESSAGE_TYPE.RECALL || msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">{{ msgInfo.content }}</div>
<div class="chat-msg-tip" v-show="msgInfo.type == $enums.MESSAGE_TYPE.TIP_TIME">
{{ $date.toTimeText(msgInfo.sendTime) }}
</div>

16
im-ui/src/store/chatStore.js

@ -106,14 +106,6 @@ export default {
this.commit("saveToStorage");
}
},
removeGroupChat(state, groupId) {
for (let idx in state.chats) {
if (state.chats[idx].type == 'GROUP' &&
state.chats[idx].targetId == groupId) {
this.commit("removeChat", idx);
}
}
},
removePrivateChat(state, friendId) {
for (let idx in state.chats) {
if (state.chats[idx].type == 'PRIVATE' &&
@ -134,7 +126,7 @@ export default {
// 如果是已存在消息,则覆盖旧的消息数据
let chat = this.getters.findChat(msgInfo);
let message = this.getters.findMessage(chat, msgInfo);
if(message){
if (message) {
Object.assign(message, msgInfo);
this.commit("saveToStorage");
return;
@ -146,13 +138,13 @@ export default {
chat.lastContent = "[文件]";
} else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
chat.lastContent = "[语音]";
} else {
} else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL) {
chat.lastContent = msgInfo.content;
}
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
chat.unreadCount++;
}
// 是否有人@我
@ -182,7 +174,7 @@ export default {
// 获取对方id或群id
let chat = this.getters.findChat(msgInfo);
let message = this.getters.findMessage(chat, msgInfo);
if(message){
if (message) {
// 属性拷贝
Object.assign(message, msgInfo);
this.commit("saveToStorage");

3
im-ui/src/view/Group.vue

@ -12,7 +12,7 @@
</div>
<el-scrollbar class="group-list-items">
<div v-for="(group,index) in groupStore.groups" :key="index">
<group-item v-show="group.remark.startsWith(searchText)" :group="group"
<group-item v-show="!group.quit&&group.remark.startsWith(searchText)" :group="group"
:active="group === groupStore.activeGroup" @click.native="onActiveItem(group,index)">
</group-item>
</div>
@ -189,7 +189,6 @@
}).then(() => {
this.$message.success(`群聊'${this.activeGroup.name}'已解散`);
this.$store.commit("removeGroup", this.activeGroup.id);
this.$store.commit("removeGroupChat", this.activeGroup.id);
this.reset();
});
})

90
im-ui/src/view/Home.vue

@ -3,7 +3,8 @@
<el-aside width="80px" class="navi-bar">
<div class="user-head-image">
<head-image :name="$store.state.userStore.userInfo.nickName"
:url="$store.state.userStore.userInfo.headImageThumb" :size="60" @click.native="showSettingDialog = true">
:url="$store.state.userStore.userInfo.headImageThumb" :size="60"
@click.native="showSettingDialog = true">
</head-image>
</div>
@ -84,8 +85,8 @@ export default {
this.$wsApi.connect(process.env.VUE_APP_WS_URL, sessionStorage.getItem("accessToken"));
this.$wsApi.onConnect(() => {
// 线
this.loadPrivateMessage(this.$store.state.chatStore.privateMsgMaxId);
this.loadGroupMessage(this.$store.state.chatStore.groupMsgMaxId);
this.pullPrivateOfflineMessage(this.$store.state.chatStore.privateMsgMaxId);
this.pullGroupOfflineMessage(this.$store.state.chatStore.groupMsgMaxId);
});
this.$wsApi.onMessage((cmd, msgInfo) => {
if (cmd == 2) {
@ -119,71 +120,41 @@ export default {
console.log("初始化失败", e);
})
},
loadPrivateMessage(minId) {
this.$store.commit("loadingPrivateMsg", true)
pullPrivateOfflineMessage(minId) {
this.$http({
url: "/message/private/loadMessage?minId=" + minId,
url: "/message/private/pullOfflineMessage?minId=" + minId,
method: 'get'
}).then((msgInfos) => {
msgInfos.forEach((msgInfo) => {
msgInfo.selfSend = msgInfo.sendId == this.$store.state.userStore.userInfo.id;
let friendId = msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId;
let friend = this.$store.state.friendStore.friends.find((f) => f.id == friendId);
if (friend) {
this.insertPrivateMessage(friend, msgInfo);
}
})
if (msgInfos.length == 100) {
//
this.loadPrivateMessage(msgInfos[99].id);
} else {
this.$store.commit("loadingPrivateMsg", false)
}
})
});
},
loadGroupMessage(minId) {
this.$store.commit("loadingGroupMsg", true)
pullGroupOfflineMessage(minId) {
this.$http({
url: "/message/group/loadMessage?minId=" + minId,
url: "/message/group/pullOfflineMessage?minId=" + minId,
method: 'get'
}).then((msgInfos) => {
msgInfos.forEach((msgInfo) => {
msgInfo.selfSend = msgInfo.sendId == this.$store.state.userStore.userInfo.id;
let groupId = msgInfo.groupId;
let group = this.$store.state.groupStore.groups.find((g) => g.id == groupId);
if (group) {
this.insertGroupMessage(group, msgInfo);
}
})
if (msgInfos.length == 100) {
//
this.loadGroupMessage(msgInfos[99].id);
} else {
this.$store.commit("loadingGroupMsg", false)
}
})
});
},
handlePrivateMessage(msg) {
//
if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
this.$store.commit("loadingPrivateMsg", JSON.parse(msg.content))
return;
}
//
if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
this.$store.commit("resetUnreadCount", {
type: 'PRIVATE',
targetId: msg.recvId
})
return;
}
// ,
if (msg.type == this.$enums.MESSAGE_TYPE.RECEIPT) {
this.$store.commit("readedMessage", { friendId: msg.sendId })
return;
}
//
msg.selfSend = msg.sendId == this.$store.state.userStore.userInfo.id;
// id
let friendId = msg.selfSend ? msg.recvId : msg.sendId;
//
if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
if (msg.selfSend) {
//
let chatInfo = {
type: 'PRIVATE',
targetId: friendId
}
this.$store.commit("resetUnreadCount", chatInfo)
} else {
//
this.$store.commit("readedMessage", { friendId: friendId })
}
return;
}
this.loadFriendInfo(friendId).then((friend) => {
this.insertPrivateMessage(friend, msg);
})
@ -220,6 +191,11 @@ export default {
}
},
handleGroupMessage(msg) {
//
if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
this.$store.commit("loadingGroupMsg", JSON.parse(msg.content))
return;
}
//
if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
//

102
im-uniapp/App.vue

@ -29,8 +29,8 @@
wsApi.connect(process.env.WS_URL, loginInfo.accessToken);
wsApi.onConnect(() => {
// 线
this.loadPrivateMessage(store.state.chatStore.privateMsgMaxId);
this.loadGroupMessage(store.state.chatStore.groupMsgMaxId);
this.pullPrivateOfflineMessage(store.state.chatStore.privateMsgMaxId);
this.pullGroupOfflineMessage(store.state.chatStore.groupMsgMaxId);
});
wsApi.onMessage((cmd, msgInfo) => {
if (cmd == 2) {
@ -61,73 +61,41 @@
}
})
},
loadPrivateMessage(minId) {
store.commit("loadingPrivateMsg", true)
pullPrivateOfflineMessage(minId) {
http({
url: "/message/private/loadMessage?minId=" + minId,
method: 'GET'
}).then((msgInfos) => {
msgInfos.forEach((msgInfo) => {
msgInfo.selfSend = msgInfo.sendId == store.state.userStore.userInfo.id;
let friendId = msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId;
let friend = store.state.friendStore.friends.find((f) => f.id == friendId);
if (friend) {
this.insertPrivateMessage(friend, msgInfo);
}
})
if (msgInfos.length == 100) {
//
this.loadPrivateMessage(msgInfos[99].id);
} else {
store.commit("loadingPrivateMsg", false)
}
})
url: "/message/private/pullOfflineMessage?minId=" + minId,
method: 'get'
});
},
loadGroupMessage(minId) {
store.commit("loadingGroupMsg", true)
pullGroupOfflineMessage(minId) {
http({
url: "/message/group/loadMessage?minId=" + minId,
method: 'GET'
}).then((msgInfos) => {
msgInfos.forEach((msgInfo) => {
msgInfo.selfSend = msgInfo.sendId == store.state.userStore.userInfo.id;
let groupId = msgInfo.groupId;
let group = store.state.groupStore.groups.find((g) => g.id == groupId);
if (group) {
this.insertGroupMessage(group, msgInfo);
}
})
if (msgInfos.length == 100) {
//
this.loadGroupMessage(msgInfos[99].id);
} else {
store.commit("loadingGroupMsg", false)
}
})
url: "/message/group/pullOfflineMessage?minId=" + minId,
method: 'get'
});
},
handlePrivateMessage(msg) {
//
if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
this.$store.commit("loadingPrivateMsg", JSON.parse(msg.content))
return;
}
//
if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
this.$store.commit("resetUnreadCount", {
type: 'PRIVATE',
targetId: msg.recvId
})
return;
}
// ,
if (msg.type == this.$enums.MESSAGE_TYPE.RECEIPT) {
this.$store.commit("readedMessage", { friendId: msg.sendId })
return;
}
//
msg.selfSend = msg.sendId == store.state.userStore.userInfo.id;
// id
let friendId = msg.selfSend ? msg.recvId : msg.sendId;
//
if (msg.type == enums.MESSAGE_TYPE.READED) {
if (msg.selfSend) {
//
let chatInfo = {
type: 'PRIVATE',
targetId: friendId
}
store.commit("resetUnreadCount", chatInfo)
} else {
//
store.commit("readedMessage", {
friendId: friendId
})
}
return;
}
this.loadFriendInfo(friendId).then((friend) => {
this.insertPrivateMessage(friend, msg);
})
@ -153,15 +121,17 @@
},
handleGroupMessage(msg) {
//
msg.selfSend = msg.sendId == store.state.userStore.userInfo.id;
let groupId = msg.groupId;
//
if (msg.type == this.$enums.MESSAGE_TYPE.LOADDING) {
this.$store.commit("loadingGroupMsg",JSON.parse(msg.content))
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.READED) {
//
let chatInfo = {
type: 'GROUP',
targetId: groupId
targetId: msg.groupId
}
store.commit("resetUnreadCount", chatInfo)
return;
@ -178,7 +148,9 @@
this.$store.commit("updateMessage", msgInfo)
return;
}
this.loadGroupInfo(groupId).then((group) => {
//
msg.selfSend = msg.sendId == store.state.userStore.userInfo.id;
this.loadGroupInfo(msg.groupId).then((group) => {
//
this.insertGroupMessage(group, msg);
})

2
im-uniapp/common/enums.js

@ -9,6 +9,8 @@ const MESSAGE_TYPE = {
READED:11,
RECEIPT:12,
TIP_TIME:20,
TIP_TEXT:21,
LOADDING:30,
RTC_CALL: 101,
RTC_ACCEPT: 102,
RTC_REJECT: 103,

2
im-uniapp/components/chat-message-item/chat-message-item.vue

@ -1,6 +1,6 @@
<template>
<view class="chat-msg-item">
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.RECALL">{{msgInfo.content}}</view>
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.RECALL||msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">{{msgInfo.content}}</view>
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME">
{{$date.toTimeText(msgInfo.sendTime)}}
</view>

8
im-uniapp/pages/group/group-info.vue

@ -1,6 +1,6 @@
<template>
<view v-if="$store.state.userStore.userInfo.type == 1" class="page group-info">
<view class="group-members">
<view v-if="!group.quit" class="group-members">
<view class="member-items">
<view v-for="(member,idx) in groupMembers" :key="idx">
<view class="member-item" v-if="idx<9">
@ -42,9 +42,9 @@
<uni-section title="群公告:" titleFontSize="14px">
<uni-notice-bar :text="group.notice" />
</uni-section>
<view class="group-edit" @click="onEditGroup()">修改群聊资料 > </view>
<view v-if="!group.quit" class="group-edit" @click="onEditGroup()">修改群聊资料 > </view>
</view>
<view class="btn-group">
<view v-if="!group.quit" class="btn-group">
<button class="btn" type="primary" @click="onSendMessage()">发消息</button>
<button class="btn" v-show="!isOwner" type="warn" @click="onQuitGroup()">退出群聊</button>
<button class="btn" v-show="isOwner" type="warn" @click="onDissolveGroup()">解散群聊</button>
@ -111,7 +111,6 @@
url:"/pages/group/group"
});
this.$store.commit("removeGroup", this.groupId);
this.$store.commit("removeGroupChat", this.groupId);
},100)
}
})
@ -141,7 +140,6 @@
url:"/pages/group/group"
});
this.$store.commit("removeGroup", this.groupId);
this.$store.commit("removeGroupChat", this.groupId);
},100)
}
})

22
im-uniapp/store/chatStore.js

@ -1,7 +1,4 @@
import {
MESSAGE_TYPE,
MESSAGE_STATUS
} from '@/common/enums.js';
import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js';
import userStore from './userStore';
export default {
@ -95,14 +92,6 @@ export default {
state.chats.splice(idx, 1);
this.commit("saveToStorage");
},
removeGroupChat(state, groupId) {
for (let idx in state.chats) {
if (state.chats[idx].type == 'GROUP' &&
state.chats[idx].targetId == groupId) {
this.commit("removeChat", idx);
}
}
},
removePrivateChat(state, userId) {
for (let idx in state.chats) {
if (state.chats[idx].type == 'PRIVATE' &&
@ -149,14 +138,15 @@ export default {
chat.lastContent = "[文件]";
} else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
chat.lastContent = "[语音]";
} else {
} else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL) {
chat.lastContent = msgInfo.content;
}
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
}
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED
&& msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
chat.unreadCount++;
}
// 是否有人@我
@ -170,8 +160,6 @@ export default {
chat.atAll = true;
}
}
// 间隔大于10分钟插入时间显示
if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) {
chat.messages.push({
@ -256,7 +244,7 @@ export default {
chat.lastContent = "[文件]";
} else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
chat.lastContent = "[语音]";
} else {
} else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL) {
chat.lastContent = msgInfo.content;
}
chat.lastSendTime = msgInfo.sendTime;

Loading…
Cancel
Save