Browse Source

群聊功能开发中

master
xie.bx 3 years ago
parent
commit
b1c6896685
  1. 26
      commom/src/main/java/com/lx/common/contant/RedisKey.java
  2. 2
      commom/src/main/java/com/lx/common/enums/WSCmdEnum.java
  3. 24
      commom/src/main/java/com/lx/common/model/im/GroupMessageInfo.java
  4. 6
      commom/src/main/java/com/lx/common/model/im/PrivateMessageInfo.java
  5. 23
      im-platform/src/main/java/com/lx/implatform/controller/GroupMessageController.java
  6. 4
      im-platform/src/main/java/com/lx/implatform/entity/GroupMessage.java
  7. 14
      im-platform/src/main/java/com/lx/implatform/service/IGroupMemberService.java
  8. 15
      im-platform/src/main/java/com/lx/implatform/service/IGroupMessageService.java
  9. 9
      im-platform/src/main/java/com/lx/implatform/service/IPrivateMessageService.java
  10. 25
      im-platform/src/main/java/com/lx/implatform/service/impl/GroupMemberServiceImpl.java
  11. 87
      im-platform/src/main/java/com/lx/implatform/service/impl/GroupMessageServiceImpl.java
  12. 23
      im-platform/src/main/java/com/lx/implatform/service/impl/PrivateMessageServiceImpl.java
  13. 4
      im-platform/src/main/java/com/lx/implatform/service/impl/UserServiceImpl.java
  14. 2
      im-platform/src/main/java/com/lx/implatform/task/PullAlreadyReadMessageTask.java
  15. 28
      im-platform/src/main/java/com/lx/implatform/vo/GroupMessageVO.java
  16. 2
      im-platform/src/main/java/com/lx/implatform/vo/PrivateMessageVO.java
  17. 5
      im-server/src/main/java/com/lx/implatform/imserver/IMServerApp.java
  18. 49
      im-server/src/main/java/com/lx/implatform/imserver/task/AbstractPullMessageTask.java
  19. 46
      im-server/src/main/java/com/lx/implatform/imserver/task/PullUnreadGroupMessageTask.java
  20. 21
      im-server/src/main/java/com/lx/implatform/imserver/task/PullUnreadPrivateMessageTask.java
  21. 8
      im-server/src/main/java/com/lx/implatform/imserver/websocket/WebSocketHandler.java
  22. 29
      im-server/src/main/java/com/lx/implatform/imserver/websocket/WebsocketServer.java
  23. 46
      im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/GroupMessageProcessor.java
  24. 14
      im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/HeartbeatProcessor.java
  25. 1
      im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/MessageProcessor.java
  26. 21
      im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/PrivateMessageProcessor.java
  27. 4
      im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/ProcessorFactory.java
  28. 5
      im-ui/src/store/chatStore.js
  29. 1
      im-ui/src/store/index.js
  30. 261
      im-ui/src/view/Chat.vue
  31. 3
      im-ui/src/view/Friend.vue
  32. 10
      im-ui/src/view/Group.vue
  33. 10
      im-ui/src/view/Home.vue

26
commom/src/main/java/com/lx/common/contant/RedisKey.java

@ -2,17 +2,25 @@ package com.lx.common.contant;
public class RedisKey { public class RedisKey {
// im-server最大id,从0开始递增
public final static String IM_MAX_SERVER_ID = "im:max_server_id";
// 用户ID所连接的IM-server的ID
public final static String IM_USER_SERVER_ID = "im:user:server_id:";
// 未读私聊消息队列
public final static String IM_UNREAD_PRIVATE_MESSAGE = "im:unread:private:";
// 未读群聊消息队列
public final static String IM_UNREAD_GROUP_MESSAGE = "im:unread:group:";
// 已读私聊消息id队列
public final static String IM_READED_PRIVATE_MESSAGE_ID = "im:readed:private:id";
// 已读群聊消息位置(已读最大id)
public final static String IM_GROUP_READED_POSITION = "im:readed:group:position";
// 缓存前缀
public final static String IM_CACHE = "im:cache:"; public final static String IM_CACHE = "im:cache:";
// 缓存是否好友:bool
public final static String IM_CACHE_FRIEND = IM_CACHE+"friend"; public final static String IM_CACHE_FRIEND = IM_CACHE+"friend";
// 缓存群聊信息
public final static String IM_CACHE_GROUP = IM_CACHE+"group"; public final static String IM_CACHE_GROUP = IM_CACHE+"group";
// 缓存群聊成员id
public final static String IM_CACHE_GROUP_MEMBER_ID = IM_CACHE+"group_member_ids";
public final static String IM_CACHE_GROUP_MEMBER = IM_CACHE+"groupMember";
public final static String IM_USER_SERVER_ID = "im:user_server_id:";
public final static String IM_UNREAD_MESSAGE = "im:unread_msg:";
public final static String IM_ALREADY_READED_MESSAGE = "im:already_read_msg";
} }

2
commom/src/main/java/com/lx/common/enums/WSCmdEnum.java

@ -3,7 +3,7 @@ package com.lx.common.enums;
public enum WSCmdEnum { public enum WSCmdEnum {
HEARTBEAT(0,"心跳"), HEARTBEAT(0,"心跳"),
SINGLE_MESSAGE(1,"单发消息"), PRIVATE_MESSAGE(1,"私聊消息"),
GROUP_MESSAGE(2,"群发消息"); GROUP_MESSAGE(2,"群发消息");

24
commom/src/main/java/com/lx/common/model/im/GroupMessageInfo.java

@ -0,0 +1,24 @@
package com.lx.common.model.im;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class GroupMessageInfo {
private Long id;
private Long groupId;
private Long sendId;
private List<Long> recvIds;
private String content;
private Integer type;
private Date sendTime;
}

6
commom/src/main/java/com/lx/common/model/im/SingleMessageInfo.java → commom/src/main/java/com/lx/common/model/im/PrivateMessageInfo.java

@ -5,13 +5,13 @@ import lombok.Data;
import java.util.Date; import java.util.Date;
@Data @Data
public class SingleMessageInfo { public class PrivateMessageInfo {
private long id; private long id;
private Long sendUserId; private Long sendId;
private Long recvUserId; private Long recvId;
private String content; private String content;

23
im-platform/src/main/java/com/lx/implatform/controller/GroupMessageController.java

@ -1,14 +1,37 @@
package com.lx.implatform.controller; package com.lx.implatform.controller;
import com.lx.common.result.Result;
import com.lx.common.result.ResultUtils;
import com.lx.implatform.service.IGroupMemberService;
import com.lx.implatform.service.IGroupMessageService;
import com.lx.implatform.vo.GroupMessageVO;
import com.lx.implatform.vo.PrivateMessageVO;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController @RestController
@RequestMapping("/group/message") @RequestMapping("/group/message")
public class GroupMessageController { public class GroupMessageController {
@Autowired
private IGroupMessageService groupMessageService;
@PostMapping("/send")
@ApiOperation(value = "发送群聊消息",notes="发送群聊消息")
public Result register(@Valid @RequestBody GroupMessageVO vo){
groupMessageService.sendMessage(vo);
return ResultUtils.success();
}
} }

4
im-platform/src/main/java/com/lx/implatform/entity/GroupMessage.java

@ -45,8 +45,8 @@ public class GroupMessage extends Model<GroupMessage> {
/** /**
* 发送用户id * 发送用户id
*/ */
@TableField("send_user_id") @TableField("send_id")
private Long sendUserId; private Long sendId;
/** /**
* 发送内容 * 发送内容

14
im-platform/src/main/java/com/lx/implatform/service/IGroupMemberService.java

@ -18,17 +18,19 @@ public interface IGroupMemberService extends IService<GroupMember> {
GroupMember findByGroupAndUserId(long groupId,long userId); GroupMember findByGroupAndUserId(Long groupId,Long userId);
List<GroupMember> findByUserId(long userId); List<GroupMember> findByUserId(Long userId);
List<GroupMember> findByGroupId(long groupId); List<GroupMember> findByGroupId(Long groupId);
List<Long> findUserIdsByGroupId(Long groupId);
boolean save(GroupMember member); boolean save(GroupMember member);
boolean saveBatch(long groupId,List<GroupMember> members); boolean saveBatch(Long groupId,List<GroupMember> members);
void removeByGroupId(long groupId); void removeByGroupId(Long groupId);
void removeByGroupAndUserId(long groupId,long userId); void removeByGroupAndUserId(Long groupId,Long userId);
} }

15
im-platform/src/main/java/com/lx/implatform/service/IGroupMessageService.java

@ -2,15 +2,14 @@ package com.lx.implatform.service;
import com.lx.implatform.entity.GroupMessage; import com.lx.implatform.entity.GroupMessage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.lx.implatform.vo.GroupMessageVO;
import com.lx.implatform.vo.PrivateMessageVO;
/**
* <p>
* 群消息 服务类
* </p>
*
* @author blue
* @since 2022-10-31
*/
public interface IGroupMessageService extends IService<GroupMessage> { public interface IGroupMessageService extends IService<GroupMessage> {
void sendMessage(GroupMessageVO vo);
void pullUnreadMessage();
} }

9
im-platform/src/main/java/com/lx/implatform/service/IPrivateMessageService.java

@ -4,14 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.lx.implatform.entity.PrivateMessage; import com.lx.implatform.entity.PrivateMessage;
import com.lx.implatform.vo.PrivateMessageVO; import com.lx.implatform.vo.PrivateMessageVO;
/**
* <p>
* 服务类
* </p>
*
* @author blue
* @since 2022-10-01
*/
public interface IPrivateMessageService extends IService<PrivateMessage> { public interface IPrivateMessageService extends IService<PrivateMessage> {
void sendMessage(PrivateMessageVO vo); void sendMessage(PrivateMessageVO vo);

25
im-platform/src/main/java/com/lx/implatform/service/impl/GroupMemberServiceImpl.java

@ -8,13 +8,14 @@ import com.lx.implatform.service.IGroupMemberService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP_MEMBER) @CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP_MEMBER_ID)
@Service @Service
public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, GroupMember> implements IGroupMemberService { public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, GroupMember> implements IGroupMemberService {
@ -41,7 +42,7 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
*/ */
@CacheEvict(key="#groupId") @CacheEvict(key="#groupId")
@Override @Override
public boolean saveBatch(long groupId,List<GroupMember> members) { public boolean saveBatch(Long groupId,List<GroupMember> members) {
return super.saveBatch(members); return super.saveBatch(members);
} }
@ -53,7 +54,7 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
* @return * @return
*/ */
@Override @Override
public GroupMember findByGroupAndUserId(long groupId, long userId) { public GroupMember findByGroupAndUserId(Long groupId, Long userId) {
QueryWrapper<GroupMember> wrapper = new QueryWrapper<>(); QueryWrapper<GroupMember> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMember::getGroupId,groupId) wrapper.lambda().eq(GroupMember::getGroupId,groupId)
.eq(GroupMember::getUserId,userId); .eq(GroupMember::getUserId,userId);
@ -67,7 +68,7 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
* @return * @return
*/ */
@Override @Override
public List<GroupMember> findByUserId(long userId) { public List<GroupMember> findByUserId(Long userId) {
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper(); QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getUserId, userId); memberWrapper.lambda().eq(GroupMember::getUserId, userId);
return this.list(memberWrapper); return this.list(memberWrapper);
@ -80,12 +81,20 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
* @return * @return
*/ */
@Override @Override
public List<GroupMember> findByGroupId(long groupId) { public List<GroupMember> findByGroupId(Long groupId) {
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper(); QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getGroupId, groupId); memberWrapper.lambda().eq(GroupMember::getGroupId, groupId);
return this.list(memberWrapper); return this.list(memberWrapper);
} }
@Cacheable(key="#groupId")
@Override
public List<Long> findUserIdsByGroupId(Long groupId) {
List<GroupMember> members = this.findByGroupId(groupId);
return members.stream().map(m->m.getUserId()).collect(Collectors.toList());
}
/** /**
*根据群聊id删除成员信息 *根据群聊id删除成员信息
* *
@ -94,7 +103,7 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
*/ */
@CacheEvict(key = "#groupId") @CacheEvict(key = "#groupId")
@Override @Override
public void removeByGroupId(long groupId) { public void removeByGroupId(Long groupId) {
QueryWrapper<GroupMember> wrapper = new QueryWrapper(); QueryWrapper<GroupMember> wrapper = new QueryWrapper();
wrapper.lambda().eq(GroupMember::getGroupId,groupId); wrapper.lambda().eq(GroupMember::getGroupId,groupId);
this.remove(wrapper); this.remove(wrapper);
@ -109,7 +118,7 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
*/ */
@CacheEvict(key = "#groupId") @CacheEvict(key = "#groupId")
@Override @Override
public void removeByGroupAndUserId(long groupId, long userId) { public void removeByGroupAndUserId(Long groupId, Long userId) {
QueryWrapper<GroupMember> wrapper = new QueryWrapper<>(); QueryWrapper<GroupMember> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMember::getGroupId,groupId) wrapper.lambda().eq(GroupMember::getGroupId,groupId)
.eq(GroupMember::getUserId,userId); .eq(GroupMember::getUserId,userId);

87
im-platform/src/main/java/com/lx/implatform/service/impl/GroupMessageServiceImpl.java

@ -1,20 +1,91 @@
package com.lx.implatform.service.impl; package com.lx.implatform.service.impl;
import com.lx.common.contant.RedisKey;
import com.lx.common.enums.ResultCode;
import com.lx.common.model.im.GroupMessageInfo;
import com.lx.common.util.BeanUtils;
import com.lx.implatform.entity.Group;
import com.lx.implatform.entity.GroupMessage; import com.lx.implatform.entity.GroupMessage;
import com.lx.implatform.exception.GlobalException;
import com.lx.implatform.mapper.GroupMessageMapper; import com.lx.implatform.mapper.GroupMessageMapper;
import com.lx.implatform.service.IGroupMemberService;
import com.lx.implatform.service.IGroupMessageService; import com.lx.implatform.service.IGroupMessageService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lx.implatform.service.IGroupService;
import com.lx.implatform.session.SessionContext;
import com.lx.implatform.vo.GroupMessageVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** import java.util.*;
* <p> import java.util.concurrent.ConcurrentHashMap;
* 群消息 服务实现类
* </p>
*
* @author blue
* @since 2022-10-31
*/
@Service @Service
public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements IGroupMessageService { public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements IGroupMessageService {
@Autowired
private IGroupService groupService;
@Autowired
private IGroupMemberService groupMemberService;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 发送群聊消息(与mysql所有交换都要进行缓存)
*
* @param vo
* @return
*/
@Override
public void sendMessage(GroupMessageVO vo) {
Long userId = SessionContext.getSession().getId();
Group group = groupService.findById(vo.getGroupId());
if(group == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"群聊不存在或已解散");
}
// 保存消息
GroupMessage msg = BeanUtils.copyProperties(vo, GroupMessage.class);
msg.setSendId(userId);
msg.setSendTime(new Date());
this.save(msg);
// 根据群聊每个成员所连的IM-server,进行分组
Map<Integer,List<Long>> serverMap = new ConcurrentHashMap<>();
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
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<Long> list = Collections.synchronizedList(new LinkedList<Long>());
list.add(id);
serverMap.put(serverId,list);
}
}
});
// 逐个server发送
for (Map.Entry<Integer,List<Long>> entry : serverMap.entrySet()) {
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
msgInfo.setRecvIds(entry.getValue());
String key = RedisKey.IM_UNREAD_PRIVATE_MESSAGE +entry.getKey();
redisTemplate.opsForList().rightPush(key,msgInfo);
}
}
/**
* 异步拉取群聊消息通过websocket异步推送
*
* @return
*/
@Override
public void pullUnreadMessage() {
}
} }

23
im-platform/src/main/java/com/lx/implatform/service/impl/PrivateMessageServiceImpl.java

@ -32,6 +32,12 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
@Autowired @Autowired
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
/**
* 发送私聊消息
*
* @param vo
* @return
*/
@Override @Override
public void sendMessage(PrivateMessageVO vo) { public void sendMessage(PrivateMessageVO vo) {
Long userId = SessionContext.getSession().getId(); Long userId = SessionContext.getSession().getId();
@ -47,22 +53,27 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
this.save(msg); this.save(msg);
// 获取对方连接的channelId // 获取对方连接的channelId
String key = RedisKey.IM_USER_SERVER_ID+msg.getRecvId(); String key = RedisKey.IM_USER_SERVER_ID+msg.getRecvId();
String serverId = (String)redisTemplate.opsForValue().get(key); Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
// 如果对方在线,将数据存储至redis,等待拉取推送 // 如果对方在线,将数据存储至redis,等待拉取推送
if(!StringUtils.isEmpty(serverId)){ if(serverId != null){
String sendKey = RedisKey.IM_UNREAD_MESSAGE + serverId; String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class); PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
redisTemplate.opsForList().rightPush(sendKey,msgInfo); redisTemplate.opsForList().rightPush(sendKey,msgInfo);
} }
} }
/**
* 异步拉取私聊消息通过websocket异步推送
*
* @return
*/
@Override @Override
public void pullUnreadMessage() { public void pullUnreadMessage() {
// 获取当前连接的channelId // 获取当前连接的channelId
Long userId = SessionContext.getSession().getId(); Long userId = SessionContext.getSession().getId();
String key = RedisKey.IM_USER_SERVER_ID+userId; String key = RedisKey.IM_USER_SERVER_ID+userId;
String serverId = (String)redisTemplate.opsForValue().get(key); Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
if(StringUtils.isEmpty(serverId)){ if(serverId == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户未建立连接"); throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户未建立连接");
} }
// 获取当前用户所有未读消息 // 获取当前用户所有未读消息
@ -76,7 +87,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(m, PrivateMessageInfo.class); PrivateMessageInfo msgInfo = BeanUtils.copyProperties(m, PrivateMessageInfo.class);
return msgInfo; return msgInfo;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
String sendKey = RedisKey.IM_UNREAD_MESSAGE + serverId; String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
redisTemplate.opsForList().rightPushAll(sendKey,infos.toArray()); redisTemplate.opsForList().rightPushAll(sendKey,infos.toArray());
} }
} }

4
im-platform/src/main/java/com/lx/implatform/service/impl/UserServiceImpl.java

@ -109,7 +109,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
private boolean isOnline(Long userId){ private boolean isOnline(Long userId){
String key = RedisKey.IM_USER_SERVER_ID + userId; String key = RedisKey.IM_USER_SERVER_ID + userId;
String serverId = (String) redisTemplate.opsForValue().get(key); Integer serverId = (Integer) redisTemplate.opsForValue().get(key);
return StringUtils.isNotEmpty(serverId); return serverId!=null && serverId>=0;
} }
} }

2
im-platform/src/main/java/com/lx/implatform/task/PullAlreadyReadMessageTask.java

@ -40,7 +40,7 @@ public class PullAlreadyReadMessageTask {
@Override @Override
public void run() { public void run() {
try { try {
String key = RedisKey.IM_ALREADY_READED_MESSAGE; String key = RedisKey.IM_READED_PRIVATE_MESSAGE_ID;
Integer msgId = (Integer)redisTemplate.opsForList().leftPop(key,1, TimeUnit.SECONDS); Integer msgId = (Integer)redisTemplate.opsForList().leftPop(key,1, TimeUnit.SECONDS);
if(msgId!=null){ if(msgId!=null){
UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>(); UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>();

28
im-platform/src/main/java/com/lx/implatform/vo/GroupMessageVO.java

@ -0,0 +1,28 @@
package com.lx.implatform.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("群聊消息VO")
public class GroupMessageVO {
@NotNull(message="群聊id不可为空")
@ApiModelProperty(value = "群聊id")
private Long groupId;
@Length(max=1024,message = "内容长度不得大于1024")
@NotEmpty(message="发送内容不可为空")
@ApiModelProperty(value = "发送内容")
private String content;
@NotNull(message="发送内容不可为空")
@ApiModelProperty(value = "消息类型")
private Integer type;
}

2
im-platform/src/main/java/com/lx/implatform/vo/PrivateMessageVO.java

@ -10,7 +10,7 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@Data @Data
@ApiModel("单发消息VO") @ApiModel("私聊消息VO")
public class PrivateMessageVO { public class PrivateMessageVO {

5
im-server/src/main/java/com/lx/implatform/imserver/IMServerApp.java

@ -2,6 +2,7 @@ package com.lx.implatform.imserver;
import com.lx.implatform.imserver.websocket.WebsocketServer; import com.lx.implatform.imserver.websocket.WebsocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner; import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
@ -20,6 +21,8 @@ public class IMServerApp implements CommandLineRunner {
@Value("${websocket.port}") @Value("${websocket.port}")
private int port; private int port;
@Autowired
private WebsocketServer WSServer;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(IMServerApp.class); SpringApplication.run(IMServerApp.class);
@ -27,6 +30,6 @@ public class IMServerApp implements CommandLineRunner {
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
new WebsocketServer().start(port); WSServer.start(port);
} }
} }

49
im-server/src/main/java/com/lx/implatform/imserver/task/AbstractPullMessageTask.java

@ -0,0 +1,49 @@
package com.lx.implatform.imserver.task;
import com.lx.implatform.imserver.websocket.WebsocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public abstract class AbstractPullMessageTask{
private int threadNum = 1;
private ExecutorService executorService;
@Autowired
private WebsocketServer WSServer;
public AbstractPullMessageTask(){
this.threadNum = 1;
}
public AbstractPullMessageTask(int threadNum){
this.threadNum = threadNum;
}
@PostConstruct
public void init(){
// 初始化定时器
executorService = Executors.newFixedThreadPool(threadNum);
executorService.execute(new Runnable() {
@Override
public void run() {
try{
if(WSServer.isReady()){
pullMessage();
}
Thread.sleep(100);
}catch (Exception e){
log.error("任务调度异常",e);
}
executorService.execute(this);
}
});
}
public abstract void pullMessage();
}

46
im-server/src/main/java/com/lx/implatform/imserver/task/PullUnreadGroupMessageTask.java

@ -0,0 +1,46 @@
package com.lx.implatform.imserver.task;
import com.lx.common.contant.RedisKey;
import com.lx.common.enums.WSCmdEnum;
import com.lx.common.model.im.GroupMessageInfo;
import com.lx.common.model.im.PrivateMessageInfo;
import com.lx.implatform.imserver.websocket.WebsocketChannelCtxHloder;
import com.lx.implatform.imserver.websocket.WebsocketServer;
import com.lx.implatform.imserver.websocket.processor.MessageProcessor;
import com.lx.implatform.imserver.websocket.processor.ProcessorFactory;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class PullUnreadGroupMessageTask extends AbstractPullMessageTask {
@Autowired
private WebsocketServer WSServer;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public void pullMessage() {
// 从redis拉取未读消息
String key = RedisKey.IM_UNREAD_GROUP_MESSAGE + WSServer.getServerId();
List messageInfos = redisTemplate.opsForList().range(key,0,-1);
for(Object o: messageInfos){
redisTemplate.opsForList().leftPop(key);
GroupMessageInfo messageInfo = (GroupMessageInfo)o;
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.PRIVATE_MESSAGE);
processor.process(null,messageInfo);
}
}
}

21
im-server/src/main/java/com/lx/implatform/imserver/task/PullUnreadMessageTask.java → im-server/src/main/java/com/lx/implatform/imserver/task/PullUnreadPrivateMessageTask.java

@ -3,7 +3,7 @@ package com.lx.implatform.imserver.task;
import com.lx.common.contant.RedisKey; import com.lx.common.contant.RedisKey;
import com.lx.common.enums.WSCmdEnum; import com.lx.common.enums.WSCmdEnum;
import com.lx.common.model.im.SingleMessageInfo; import com.lx.common.model.im.PrivateMessageInfo;
import com.lx.implatform.imserver.websocket.WebsocketChannelCtxHloder; import com.lx.implatform.imserver.websocket.WebsocketChannelCtxHloder;
import com.lx.implatform.imserver.websocket.WebsocketServer; import com.lx.implatform.imserver.websocket.WebsocketServer;
import com.lx.implatform.imserver.websocket.processor.MessageProcessor; import com.lx.implatform.imserver.websocket.processor.MessageProcessor;
@ -20,26 +20,29 @@ import java.util.List;
@Slf4j @Slf4j
@Component @Component
public class PullUnreadMessageTask { public class PullUnreadPrivateMessageTask extends AbstractPullMessageTask {
@Autowired
private WebsocketServer WSServer;
@Autowired @Autowired
private RedisTemplate<String,Object> redisTemplate; private RedisTemplate<String,Object> redisTemplate;
@Scheduled(fixedRate=100) @Override
public void pullUnreadMessage() { public void pullMessage() {
log.info(Thread.currentThread().getName());
// 从redis拉取未读消息 // 从redis拉取未读消息
String key = RedisKey.IM_UNREAD_MESSAGE + WebsocketServer.LOCAL_SERVER_ID; String key = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + WSServer.getServerId();
List messageInfos = redisTemplate.opsForList().range(key,0,-1); List messageInfos = redisTemplate.opsForList().range(key,0,-1);
for(Object o: messageInfos){ for(Object o: messageInfos){
redisTemplate.opsForList().leftPop(key); redisTemplate.opsForList().leftPop(key);
SingleMessageInfo messageInfo = (SingleMessageInfo)o; PrivateMessageInfo messageInfo = (PrivateMessageInfo)o;
ChannelHandlerContext ctx = WebsocketChannelCtxHloder.getChannelCtx(messageInfo.getRecvUserId()); ChannelHandlerContext ctx = WebsocketChannelCtxHloder.getChannelCtx(messageInfo.getRecvId());
if(ctx != null){ if(ctx != null){
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.SINGLE_MESSAGE); MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.PRIVATE_MESSAGE);
processor.process(ctx,messageInfo); processor.process(ctx,messageInfo);
} }
} }
} }
} }

8
im-server/src/main/java/com/lx/implatform/imserver/websocket/WebSocketHandler.java

@ -1,7 +1,9 @@
package com.lx.implatform.imserver.websocket; package com.lx.implatform.imserver.websocket;
import cn.hutool.core.bean.BeanUtil;
import com.lx.common.contant.RedisKey; import com.lx.common.contant.RedisKey;
import com.lx.common.enums.WSCmdEnum; import com.lx.common.enums.WSCmdEnum;
import com.lx.common.model.im.HeartbeatInfo;
import com.lx.common.model.im.SendInfo; import com.lx.common.model.im.SendInfo;
import com.lx.common.util.SpringContextHolder; import com.lx.common.util.SpringContextHolder;
import com.lx.implatform.imserver.websocket.processor.MessageProcessor; import com.lx.implatform.imserver.websocket.processor.MessageProcessor;
@ -14,6 +16,8 @@ import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import java.util.HashMap;
/** /**
* WebSocket 长连接下 文本帧的处理器 * WebSocket 长连接下 文本帧的处理器
@ -27,8 +31,10 @@ public class WebSocketHandler extends SimpleChannelInboundHandler<SendInfo> {
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, SendInfo sendInfo) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, SendInfo sendInfo) throws Exception {
// 创建处理器进行处理 // 创建处理器进行处理
HashMap map = (HashMap)sendInfo.getData();
HeartbeatInfo beatInfo = BeanUtil.fillBeanWithMap(map, new HeartbeatInfo(), false);
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.fromCode(sendInfo.getCmd())); MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.fromCode(sendInfo.getCmd()));
processor.process(ctx,processor.transform(sendInfo.getData())); processor.process(ctx,beatInfo);
} }
/** /**

29
im-server/src/main/java/com/lx/implatform/imserver/websocket/WebsocketServer.java

@ -1,5 +1,6 @@
package com.lx.implatform.imserver.websocket; package com.lx.implatform.imserver.websocket;
import com.lx.common.contant.RedisKey;
import com.lx.implatform.imserver.websocket.endecode.MessageProtocolDecoder; import com.lx.implatform.imserver.websocket.endecode.MessageProtocolDecoder;
import com.lx.implatform.imserver.websocket.endecode.MessageProtocolEncoder; import com.lx.implatform.imserver.websocket.endecode.MessageProtocolEncoder;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
@ -11,7 +12,9 @@ import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.timeout.IdleStateHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -21,13 +24,27 @@ import java.util.concurrent.TimeUnit;
@Component @Component
public class WebsocketServer { public class WebsocketServer {
public static String LOCAL_SERVER_ID = UUID.randomUUID().toString(); public static long serverId = 0;
@Autowired
RedisTemplate<String,Object> redisTemplate;
private volatile boolean ready = false;
@Value("${websocket.port}")
private int port;
@PostConstruct @PostConstruct
public void init(){ public void init(){
//this.start(port); // 初始化SERVER_ID
String key = RedisKey.IM_MAX_SERVER_ID;
serverId = redisTemplate.opsForValue().increment(key,1);
}
public boolean isReady(){
return ready;
}
public long getServerId(){
return serverId;
} }
public void start(int port) { public void start(int port) {
@ -64,10 +81,12 @@ public class WebsocketServer {
.option(ChannelOption.SO_BACKLOG, 5) .option(ChannelOption.SO_BACKLOG, 5)
// 表示连接保活,相当于心跳机制,默认为7200s // 表示连接保活,相当于心跳机制,默认为7200s
.childOption(ChannelOption.SO_KEEPALIVE, true); .childOption(ChannelOption.SO_KEEPALIVE, true);
// 就绪标志
this.ready = true;
try { try {
// 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理 // 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理
Channel channel = bootstrap.bind(port).sync().channel(); Channel channel = bootstrap.bind(port).sync().channel();
// 等待服务端口关闭 // 等待服务端口关闭
channel.closeFuture().sync(); channel.closeFuture().sync();
} catch (InterruptedException e) { } catch (InterruptedException e) {

46
im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/GroupMessageProcessor.java

@ -0,0 +1,46 @@
package com.lx.implatform.imserver.websocket.processor;
import com.lx.common.contant.RedisKey;
import com.lx.common.enums.WSCmdEnum;
import com.lx.common.model.im.GroupMessageInfo;
import com.lx.common.model.im.SendInfo;
import com.lx.implatform.imserver.websocket.WebsocketChannelCtxHloder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class GroupMessageProcessor implements MessageProcessor<GroupMessageInfo> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Async
@Override
public void process(ChannelHandlerContext ctx, GroupMessageInfo data) {
log.info("接收到群消息,发送者:{},群id:{},内容:{}",data.getSendId(),data.getGroupId(),data.getContent());
List<Long> recvIds = data.getRecvIds();
// 接收者id列表不需要传输,节省带宽
data.setRecvIds(null);
for(Long recvId:recvIds){
ChannelHandlerContext channelCtx = WebsocketChannelCtxHloder.getChannelCtx(recvId);
if(channelCtx != null){
// 推送消息到用户
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.GROUP_MESSAGE.getCode());
sendInfo.setData(data);
channelCtx.channel().writeAndFlush(sendInfo);
// 设置已读最大id
String key = RedisKey.IM_GROUP_READED_POSITION + data.getGroupId();
redisTemplate.opsForValue().set(key,data.getId());
}
}
}
}

14
im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/HeartbeatProcessor.java

@ -21,12 +21,17 @@ import java.util.concurrent.TimeUnit;
@Component @Component
public class HeartbeatProcessor implements MessageProcessor<HeartbeatInfo> { public class HeartbeatProcessor implements MessageProcessor<HeartbeatInfo> {
@Autowired
private WebsocketServer WSServer;
@Autowired @Autowired
RedisTemplate<String,Object> redisTemplate; RedisTemplate<String,Object> redisTemplate;
@Override @Override
public void process(ChannelHandlerContext ctx, HeartbeatInfo beatInfo) { public void process(ChannelHandlerContext ctx, HeartbeatInfo beatInfo) {
log.info("接收到心跳,channelId:{},userId:{}",ctx.channel().id().asLongText(),beatInfo.getUserId()); log.info("接收到心跳,userId:{}",beatInfo.getUserId());
// 绑定用户和channel // 绑定用户和channel
WebsocketChannelCtxHloder.addChannelCtx(beatInfo.getUserId(),ctx); WebsocketChannelCtxHloder.addChannelCtx(beatInfo.getUserId(),ctx);
// 设置属性 // 设置属性
@ -35,7 +40,7 @@ public class HeartbeatProcessor implements MessageProcessor<HeartbeatInfo> {
// 在redis上记录每个user的channelId,15秒没有心跳,则自动过期 // 在redis上记录每个user的channelId,15秒没有心跳,则自动过期
String key = RedisKey.IM_USER_SERVER_ID+beatInfo.getUserId(); String key = RedisKey.IM_USER_SERVER_ID+beatInfo.getUserId();
redisTemplate.opsForValue().set(key, WebsocketServer.LOCAL_SERVER_ID,15, TimeUnit.SECONDS); redisTemplate.opsForValue().set(key, WSServer.getServerId(),15, TimeUnit.SECONDS);
// 响应ws // 响应ws
SendInfo sendInfo = new SendInfo(); SendInfo sendInfo = new SendInfo();
@ -43,9 +48,4 @@ public class HeartbeatProcessor implements MessageProcessor<HeartbeatInfo> {
ctx.channel().writeAndFlush(sendInfo); ctx.channel().writeAndFlush(sendInfo);
} }
public HeartbeatInfo transform(Object o){
HashMap map = (HashMap)o;
HeartbeatInfo beatInfo =BeanUtil.fillBeanWithMap(map, new HeartbeatInfo(), false);
return beatInfo;
}
} }

1
im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/MessageProcessor.java

@ -6,5 +6,4 @@ public interface MessageProcessor<T> {
void process(ChannelHandlerContext ctx,T data); void process(ChannelHandlerContext ctx,T data);
T transform(Object o);
} }

21
im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/SingleMessageProcessor.java → im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/PrivateMessageProcessor.java

@ -1,10 +1,9 @@
package com.lx.implatform.imserver.websocket.processor; package com.lx.implatform.imserver.websocket.processor;
import cn.hutool.core.bean.BeanUtil;
import com.lx.common.contant.RedisKey; import com.lx.common.contant.RedisKey;
import com.lx.common.enums.WSCmdEnum; import com.lx.common.enums.WSCmdEnum;
import com.lx.common.model.im.SendInfo; import com.lx.common.model.im.SendInfo;
import com.lx.common.model.im.SingleMessageInfo; import com.lx.common.model.im.PrivateMessageInfo;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -12,34 +11,26 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap;
@Slf4j @Slf4j
@Component @Component
public class SingleMessageProcessor implements MessageProcessor<SingleMessageInfo> { public class PrivateMessageProcessor implements MessageProcessor<PrivateMessageInfo> {
@Autowired @Autowired
private RedisTemplate<String,Object> redisTemplate; private RedisTemplate<String,Object> redisTemplate;
@Async @Async
@Override @Override
public void process(ChannelHandlerContext ctx, SingleMessageInfo data) { public void process(ChannelHandlerContext ctx, PrivateMessageInfo data) {
log.info("接收到消息,发送者:{},接收者:{},内容:{}",data.getSendUserId(),data.getRecvUserId(),data.getContent()); log.info("接收到消息,发送者:{},接收者:{},内容:{}",data.getSendId(),data.getRecvId(),data.getContent());
// 推送消息到用户 // 推送消息到用户
SendInfo sendInfo = new SendInfo(); SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.SINGLE_MESSAGE.getCode()); sendInfo.setCmd(WSCmdEnum.PRIVATE_MESSAGE.getCode());
sendInfo.setData(data); sendInfo.setData(data);
ctx.channel().writeAndFlush(sendInfo); ctx.channel().writeAndFlush(sendInfo);
// 已读消息推送至redis,等待更新数据库 // 已读消息推送至redis,等待更新数据库
String key = RedisKey.IM_ALREADY_READED_MESSAGE; String key = RedisKey.IM_READED_PRIVATE_MESSAGE_ID;
redisTemplate.opsForList().rightPush(key,data.getId()); redisTemplate.opsForList().rightPush(key,data.getId());
} }
@Override
public SingleMessageInfo transform(Object o) {
HashMap map = (HashMap)o;
SingleMessageInfo info = BeanUtil.fillBeanWithMap(map, new SingleMessageInfo(), false);
return info;
}
} }

4
im-server/src/main/java/com/lx/implatform/imserver/websocket/processor/ProcessorFactory.java

@ -11,8 +11,8 @@ public class ProcessorFactory {
case HEARTBEAT: case HEARTBEAT:
processor = (MessageProcessor) SpringContextHolder.getApplicationContext().getBean("heartbeatProcessor"); processor = (MessageProcessor) SpringContextHolder.getApplicationContext().getBean("heartbeatProcessor");
break; break;
case SINGLE_MESSAGE: case PRIVATE_MESSAGE:
processor = (MessageProcessor)SpringContextHolder.getApplicationContext().getBean("singleMessageProcessor"); processor = (MessageProcessor)SpringContextHolder.getApplicationContext().getBean("privateMessageProcessor");
break; break;
default: default:
break; break;

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

@ -6,6 +6,9 @@ export default {
}, },
mutations: { mutations: {
initChatStore(state) {
state.activeIndex = -1;
},
openChat(state,chatInfo){ openChat(state,chatInfo){
let chat = null; let chat = null;
for(let i in state.chats){ for(let i in state.chats){
@ -45,7 +48,7 @@ export default {
}, },
insertMessage(state, msgInfo) { insertMessage(state, msgInfo) {
let targetId = msgInfo.selfSend?msgInfo.recvUserId:msgInfo.sendUserId; let targetId = msgInfo.selfSend?msgInfo.recvId:msgInfo.sendId;
let chat = state.chats.find((chat)=>chat.targetId==targetId); let chat = state.chats.find((chat)=>chat.targetId==targetId);
chat.lastContent = msgInfo.content; chat.lastContent = msgInfo.content;

1
im-ui/src/store/index.js

@ -24,6 +24,7 @@ export default new Vuex.Store({
initStore(state){ initStore(state){
this.commit("initFriendStore"); this.commit("initFriendStore");
this.commit("initGroupStore"); this.commit("initGroupStore");
this.commit("initChatStore");
} }
}, },

261
im-ui/src/view/Chat.vue

@ -7,55 +7,12 @@
</el-input> </el-input>
</div> </div>
<div v-for="(chat,index) in chatStore.chats" :key="chat.targetId"> <div v-for="(chat,index) in chatStore.chats" :key="chat.targetId">
<chat-item :chat="chat" :index="index" @click.native="handleActiveItem(index)" @del="handleDelItem(chat,index)" :active="index === chatStore.activeIndex"></chat-item> <chat-item :chat="chat" :index="index" @click.native="handleActiveItem(index)" @del="handleDelItem(chat,index)"
:active="index === chatStore.activeIndex"></chat-item>
</div> </div>
</el-aside> </el-aside>
<el-container class="r-chat-box"> <el-container class="r-chat-box">
<el-header height="60px"> <chat-private :chat="activeChat"></chat-private>
{{activeChat.showName}}
</el-header>
<el-main class="im-chat-main" id="chatScrollBox">
<div class="im-chat-box">
<ul>
<li v-for="msgInfo in activeChat.messages" :key="msgInfo.id">
<message-item :mine="msgInfo.sendUserId == $store.state.userStore.userInfo.id"
:headImage="headImage(msgInfo)"
:showName="showName(msgInfo)" :msgInfo="msgInfo">
</message-item>
</li>
</ul>
</div>
</el-main>
<el-footer height="25%" class="im-chat-footer">
<div class="chat-tool-bar">
<div class="el-icon-service"></div>
<div>
<file-upload action="/api/image/upload"
:maxSize="5*1024*1024"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/gif']"
@before="handleImageBefore"
@success="handleImageSuccess"
@fail="handleImageFail" >
<i class="el-icon-picture-outline"></i>
</file-upload>
</div>
<div>
<file-upload action="/api/file/upload"
:maxSize="10*1024*1024"
@before="handleFileBefore"
@success="handleFileSuccess"
@fail="handleFileFail" >
<i class="el-icon-wallet"></i>
</file-upload>
</div>
<div class="el-icon-chat-dot-round"></div>
</div>
<textarea v-model="messageContent" ref="sendBox" class="send-text-area" @keyup.enter="handleSendMessage()"></textarea>
<div class="im-chat-send">
<el-button type="primary" @click="handleSendMessage()">发送</el-button>
</div>
</el-footer>
</el-container> </el-container>
</el-container> </el-container>
</template> </template>
@ -66,7 +23,8 @@
import MessageItem from "../components/chat/MessageItem.vue"; import MessageItem from "../components/chat/MessageItem.vue";
import HeadImage from "../components/common/HeadImage.vue"; import HeadImage from "../components/common/HeadImage.vue";
import FileUpload from "../components/common/FileUpload.vue"; import FileUpload from "../components/common/FileUpload.vue";
import ChatPrivate from "../components/chat/ChatPrivate.vue";
export default { export default {
name: "chat", name: "chat",
components: { components: {
@ -74,7 +32,8 @@
ChatTime, ChatTime,
HeadImage, HeadImage,
FileUpload, FileUpload,
MessageItem MessageItem,
ChatPrivate
}, },
data() { data() {
return { return {
@ -85,155 +44,34 @@
methods: { methods: {
handleActiveItem(index) { handleActiveItem(index) {
this.$store.commit("activeChat", index); this.$store.commit("activeChat", index);
// let chat = this.chatStore.chats[index];
let userId = this.chatStore.chats[index].targetId; if (chat.type == "GROUP") {
this.$http({ let groupId = this.chatStore.chats[index].targetId;
url: `/api/user/find/${userId}`,
method: 'get' } else {
}).then((user) => { this.refreshNameAndHeadImage(chat);
//
let chat = this.chatStore.chats[index];
if (user.headImageThumb != chat.headImage ||
user.nickName != chat.showName) {
this.updateFriendInfo(user, index)
}
})
},
handleSendMessage() {
let msgInfo = {
recvUserId: this.activeChat.targetId,
content: this.messageContent,
type: 0
} }
this.sendMessage(msgInfo);
}, },
handleDelItem(chat, index) { handleDelItem(chat, index) {
this.$store.commit("removeChat", index); this.$store.commit("removeChat", index);
}, },
sendGroupMessage() {
handleImageSuccess(res, file) {
let msgInfo = {
recvUserId: file.raw.targetId,
content: JSON.stringify(res.data),
type: 1
}
this.$http({
url: '/api/message/single/send',
method: 'post',
data: msgInfo
}).then((data) => {
let info = {
targetId : file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(res.data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleImageFail(res,file){
let info = {
targetId : file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleImageBefore(file) {
let url = URL.createObjectURL(file);
let data = {
originUrl : url,
thumbUrl: url
}
let msgInfo = {
fileId: file.uid,
sendUserId: this.$store.state.userStore.userInfo.id,
recvUserId: this.activeChat.targetId,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 1,
loadStatus: "loadding"
}
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.activeChat.targetId;
},
handleFileSuccess(res, file) {
console.log(res.data);
let data = {
name: file.name,
size: file.size,
url: res.data
}
let msgInfo = {
recvUserId: file.raw.targetId,
content: JSON.stringify(data),
type: 2
}
this.$http({
url: '/api/message/single/send',
method: 'post',
data: msgInfo
}).then(() => {
let info = {
targetId : file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleFileFail(res, file) {
let info = {
targetId : file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleFileBefore(file) {
let url = URL.createObjectURL(file);
let data = {
name: file.name,
size: file.size,
url: url
}
let msgInfo = { let msgInfo = {
fileId: file.uid, groupId: this.activeChat.targetId,
sendUserId: this.$store.state.userStore.userInfo.id, content: this.messageContent,
recvUserId: this.activeChat.targetId, type: 0
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 2,
loadStatus: "loading"
} }
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.activeChat.targetId;
},
sendMessage(msgInfo) {
this.$http({ this.$http({
url: '/api/message/single/send', url: '/api/message/group/send',
method: 'post', method: 'post',
data: msgInfo data: msgInfo
}).then((data) => { }).then((data) => {
this.$message.success("发送成功"); this.$message.success("发送成功");
this.messageContent = ""; this.messageContent = "";
msgInfo.sendTime = new Date().getTime(); msgInfo.sendTime = new Date().getTime();
msgInfo.sendUserId = this.$store.state.userStore.userInfo.id; msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true; msgInfo.selfSend = true;
msgInfo.loadStatus = "ok";
this.$store.commit("insertMessage", msgInfo); this.$store.commit("insertMessage", msgInfo);
// //
this.$refs.sendBox.focus(); this.$refs.sendBox.focus();
@ -241,7 +79,22 @@
this.scrollToBottom(); this.scrollToBottom();
}) })
}, },
updateFriendInfo(user, index) { refreshNameAndHeadImage(chat){
//
let userId = chat.targetId;
this.$http({
url: `/api/user/find/${userId}`,
method: 'get'
}).then((user) => {
//
if (user.headImageThumb != chat.headImage ||
user.nickName != chat.showName) {
this.updateFriendInfo(user)
this.$store.commit("updateChatFromUser", user);
}
})
},
updateFriendInfo(user) {
let friendInfo = { let friendInfo = {
id: user.id, id: user.id,
nickName: user.nickName, nickName: user.nickName,
@ -253,29 +106,8 @@
data: friendInfo data: friendInfo
}).then(() => { }).then(() => {
this.$store.commit("updateFriend", friendInfo); this.$store.commit("updateFriend", friendInfo);
this.$store.commit("updateChatFromUser", user);
}) })
}, },
showName(msg) {
if (msg.sendUserId == this.$store.state.userStore.userInfo.id) {
return this.$store.state.userStore.userInfo.nickName;
}
return this.activeChat.showName;
},
headImage(msg) {
if (msg.sendUserId == this.$store.state.userStore.userInfo.id) {
return this.$store.state.userStore.userInfo.headImageThumb;
}
return this.activeChat.headImage;
},
scrollToBottom(){
this.$nextTick(() => {
const div = document.getElementById("chatScrollBox");
div.scrollTop = div.scrollHeight;
});
}
}, },
computed: { computed: {
chatStore() { chatStore() {
@ -287,16 +119,14 @@
if (index >= 0 && chats.length > 0) { if (index >= 0 && chats.length > 0) {
return chats[index]; return chats[index];
} }
return this.emptyChat; //
}, let emptyChat = {
emptyChat() {
//
return {
targetId: -1, targetId: -1,
showName: "", showName: "",
headImage: "", headImage: "",
messages: [] messages: []
} }
return emptyChat;
} }
} }
} }
@ -308,6 +138,7 @@
border: #dddddd solid 1px; border: #dddddd solid 1px;
background: white; background: white;
width: 3rem; width: 3rem;
.l-chat-header { .l-chat-header {
padding: 5px; padding: 5px;
background-color: white; background-color: white;
@ -318,40 +149,42 @@
.r-chat-box { .r-chat-box {
background: white; background: white;
border: #dddddd solid 1px; border: #dddddd solid 1px;
.el-header { .el-header {
padding: 5px; padding: 5px;
background-color: white; background-color: white;
line-height: 50px; line-height: 50px;
} }
.im-chat-main { .im-chat-main {
padding: 0; padding: 0;
border: #dddddd solid 1px; border: #dddddd solid 1px;
.im-chat-box { .im-chat-box {
ul { ul {
padding: 20px; padding: 20px;
li { li {
list-style-type:none; list-style-type: none;
} }
} }
} }
} }
.im-chat-footer { .im-chat-footer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0; padding: 0;
.chat-tool-bar { .chat-tool-bar {
display: flex; display: flex;
position: relative; position: relative;
width: 100%; width: 100%;
height: 40px; height: 40px;
text-align: left; text-align: left;
border: #dddddd solid 1px; border: #dddddd solid 1px;
>div { >div {
margin-left: 10px; margin-left: 10px;
font-size: 22px; font-size: 22px;
@ -361,10 +194,8 @@
&:hover { &:hover {
color: black; color: black;
} }
} }
} }
.send-text-area { .send-text-area {

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

@ -87,12 +87,11 @@
handleSendMessage() { handleSendMessage() {
let user = this.userInfo; let user = this.userInfo;
let chat = { let chat = {
type: 'single', type: 'PRIVATE',
targetId: user.id, targetId: user.id,
showName: user.nickName, showName: user.nickName,
headImage: user.headImage, headImage: user.headImage,
}; };
console.log(chat);
this.$store.commit("openChat", chat); this.$store.commit("openChat", chat);
this.$store.commit("activeChat", 0); this.$store.commit("activeChat", 0);
this.$router.push("/home/chat"); this.$router.push("/home/chat");

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

@ -193,7 +193,15 @@
}, },
handleSendMessage() { handleSendMessage() {
let chat = {
type: 'GROUP',
targetId: this.activeGroup.id,
showName: this.activeGroup.remark,
headImage: this.activeGroup.headImage,
};
this.$store.commit("openChat", chat);
this.$store.commit("activeChat", 0);
this.$router.push("/home/chat");
}, },
loadGroupMembers() { loadGroupMembers() {
this.$http({ this.$http({

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

@ -61,21 +61,21 @@
console.log(e); console.log(e);
if(e.cmd==1){ if(e.cmd==1){
// //
this.handleSingleMessage(e.data); this.handlePrivateMessage(e.data);
} }
}) })
}, },
pullUnreadMessage(){ pullUnreadMessage(){
this.$http({ this.$http({
url: "/api/message/single/pullUnreadMessage", url: "/api/message/private/pullUnreadMessage",
method: 'post' method: 'post'
}) })
}, },
handleSingleMessage(msg){ handlePrivateMessage(msg){
// //
let f = this.$store.state.friendStore.friends.find((f)=>f.id==msg.sendUserId); let f = this.$store.state.friendStore.friends.find((f)=>f.id==msg.sendId);
let chatInfo = { let chatInfo = {
type: 'single', type: 'PRIVATE',
targetId: f.id, targetId: f.id,
showName: f.nickName, showName: f.nickName,
headImage: f.headImage headImage: f.headImage

Loading…
Cancel
Save