Browse Source

已读未读显示(开发中)

master
xie.bx 2 years ago
parent
commit
77da9edb61
  1. 11
      im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java
  2. 12
      im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java
  3. 10
      im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java
  4. 11
      im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java
  5. 6
      im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java
  6. 18
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  7. 15
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  8. 2
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  9. 12
      im-platform/src/main/java/com/bx/implatform/enums/FileType.java
  10. 17
      im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java
  11. 11
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  12. 23
      im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java
  13. 4
      im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java
  14. 4
      im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java
  15. 3
      im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java
  16. 163
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  17. 99
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  18. 28
      im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java
  19. 30
      im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java
  20. 3
      im-ui/src/api/emotion.js
  21. 12
      im-ui/src/api/enums.js
  22. 48
      im-ui/src/components/chat/ChatBox.vue
  23. 33
      im-ui/src/components/chat/ChatMessageItem.vue
  24. 145
      im-ui/src/store/chatStore.js
  25. 39
      im-ui/src/store/friendStore.js
  26. 52
      im-ui/src/store/groupStore.js
  27. 27
      im-ui/src/store/index.js
  28. 1
      im-ui/src/store/uiStore.js
  29. 28
      im-ui/src/store/userStore.js
  30. 22
      im-ui/src/view/Chat.vue
  31. 178
      im-ui/src/view/Home.vue

11
im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java

@ -1,7 +1,9 @@
package com.bx.imcommon.enums; package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum IMCmdType { public enum IMCmdType {
LOGIN(0,"登陆"), LOGIN(0,"登陆"),
@ -11,15 +13,10 @@ public enum IMCmdType {
GROUP_MESSAGE(4,"群发消息"); GROUP_MESSAGE(4,"群发消息");
private Integer code; private Integer code;
private String desc; private String desc;
IMCmdType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static IMCmdType fromCode(Integer code){ public static IMCmdType fromCode(Integer code){
for (IMCmdType typeEnum:values()) { for (IMCmdType typeEnum:values()) {
@ -31,10 +28,6 @@ public enum IMCmdType {
} }
public String description() {
return desc;
}
public Integer code(){ public Integer code(){
return this.code; return this.code;
} }

12
im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java

@ -1,5 +1,8 @@
package com.bx.imcommon.enums; package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum IMListenerType{ public enum IMListenerType{
ALL(0,"全部消息"), ALL(0,"全部消息"),
PRIVATE_MESSAGE(1,"私聊消息"), PRIVATE_MESSAGE(1,"私聊消息"),
@ -9,15 +12,6 @@ public enum IMListenerType{
private String desc; private String desc;
IMListenerType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String description() {
return desc;
}
public Integer code(){ public Integer code(){

10
im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java

@ -1,6 +1,8 @@
package com.bx.imcommon.enums; package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum IMSendCode { public enum IMSendCode {
SUCCESS(0,"发送成功"), SUCCESS(0,"发送成功"),
@ -11,11 +13,6 @@ public enum IMSendCode {
private Integer code; private Integer code;
private String desc; private String desc;
// 构造方法
IMSendCode(int code, String desc) {
this.code = code;
this.desc = desc;
}
public static IMSendCode fromCode(Integer code){ public static IMSendCode fromCode(Integer code){
for (IMSendCode typeEnum:values()) { for (IMSendCode typeEnum:values()) {
@ -27,9 +24,6 @@ public enum IMSendCode {
} }
public String description() {
return desc;
}
public Integer code(){ public Integer code(){

11
im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java

@ -1,9 +1,12 @@
package com.bx.imcommon.enums; package com.bx.imcommon.enums;
import lombok.AllArgsConstructor;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@AllArgsConstructor
public enum IMTerminalType { public enum IMTerminalType {
WEB(0,"web"), WEB(0,"web"),
@ -13,10 +16,6 @@ public enum IMTerminalType {
private String desc; private String desc;
IMTerminalType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static IMTerminalType fromCode(Integer code){ public static IMTerminalType fromCode(Integer code){
for (IMTerminalType typeEnum:values()) { for (IMTerminalType typeEnum:values()) {
@ -31,10 +30,6 @@ public enum IMTerminalType {
return Arrays.stream(values()).map(IMTerminalType::code).collect(Collectors.toList()); return Arrays.stream(values()).map(IMTerminalType::code).collect(Collectors.toList());
} }
public String description() {
return desc;
}
public Integer code(){ public Integer code(){
return this.code; return this.code;
} }

6
im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java

@ -3,6 +3,8 @@ package com.bx.imcommon.model;
import com.bx.imcommon.enums.IMTerminalType; import com.bx.imcommon.enums.IMTerminalType;
import lombok.Data; import lombok.Data;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
@Data @Data
@ -14,9 +16,9 @@ public class IMGroupMessage<T> {
private IMUserInfo sender; private IMUserInfo sender;
/** /**
* 接收者id列表(群成员列表) * 接收者id列表(群成员列表,为空则不会推送)
*/ */
private List<Long> recvIds; private List<Long> recvIds = Collections.EMPTY_LIST;
/** /**

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

@ -6,6 +6,7 @@ import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils; import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IGroupMessageService; import com.bx.implatform.service.IGroupMessageService;
import com.bx.implatform.dto.GroupMessageDTO; import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.vo.PrivateMessageVO;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -38,6 +39,7 @@ public class GroupMessageController {
return ResultUtils.success(); return ResultUtils.success();
} }
// todo 删除
@PostMapping("/pullUnreadMessage") @PostMapping("/pullUnreadMessage")
@ApiOperation(value = "拉取未读消息",notes="拉取未读消息") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息")
public Result pullUnreadMessage(){ public Result pullUnreadMessage(){
@ -45,6 +47,22 @@ public class GroupMessageController {
return ResultUtils.success(); return ResultUtils.success();
} }
@GetMapping("/loadMessage")
@ApiOperation(value = "拉取消息",notes="拉取消息,一次最多拉取100条")
public Result<List<GroupMessageVO>> loadMessage(@RequestParam Long minId){
return ResultUtils.success(groupMessageService.loadMessage(minId));
}
@PutMapping("/readed")
@ApiOperation(value = "消息已读",notes="将群聊中的消息状态置为已读")
public Result readedMessage(@RequestParam Long groupId){
groupMessageService.readedMessage(groupId);
return ResultUtils.success();
}
@GetMapping("/history") @GetMapping("/history")
@ApiOperation(value = "查询聊天记录",notes="查询聊天记录") @ApiOperation(value = "查询聊天记录",notes="查询聊天记录")
public Result<List<GroupMessageVO>> recallMessage(@NotNull(message = "群聊id不能为空") @RequestParam Long groupId, public Result<List<GroupMessageVO>> recallMessage(@NotNull(message = "群聊id不能为空") @RequestParam Long groupId,

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

@ -37,7 +37,7 @@ public class PrivateMessageController {
return ResultUtils.success(); return ResultUtils.success();
} }
// todo 删除
@PostMapping("/pullUnreadMessage") @PostMapping("/pullUnreadMessage")
@ApiOperation(value = "拉取未读消息",notes="拉取未读消息") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息")
public Result pullUnreadMessage(){ public Result pullUnreadMessage(){
@ -46,6 +46,19 @@ public class PrivateMessageController {
} }
@GetMapping("/loadMessage")
@ApiOperation(value = "拉取消息",notes="拉取消息,一次最多拉取100条")
public Result<List<PrivateMessageVO>> loadMessage(@RequestParam Long minId){
return ResultUtils.success(privateMessageService.loadMessage(minId));
}
@PutMapping("/readed")
@ApiOperation(value = "消息已读",notes="将会话中接收的消息状态置为已读")
public Result readedMessage(@RequestParam Long friendId){
privateMessageService.readedMessage(friendId);
return ResultUtils.success();
}
@GetMapping("/history") @GetMapping("/history")
@ApiOperation(value = "查询聊天记录",notes="查询聊天记录") @ApiOperation(value = "查询聊天记录",notes="查询聊天记录")
public Result<List<PrivateMessageVO>> recallMessage(@NotNull(message = "好友id不能为空") @RequestParam Long friendId, public Result<List<PrivateMessageVO>> recallMessage(@NotNull(message = "好友id不能为空") @RequestParam Long friendId,

2
im-platform/src/main/java/com/bx/implatform/controller/UserController.java

@ -54,7 +54,7 @@ public class UserController {
@GetMapping("/find/{id}") @GetMapping("/find/{id}")
@ApiOperation(value = "查找用户",notes="根据id查找用户") @ApiOperation(value = "查找用户",notes="根据id查找用户")
public Result findById(@NotEmpty @PathVariable("id") Long id){ public Result<UserVO> findById(@NotEmpty @PathVariable("id") Long id){
return ResultUtils.success(userService.findUserById(id)); return ResultUtils.success(userService.findUserById(id));
} }

12
im-platform/src/main/java/com/bx/implatform/enums/FileType.java

@ -1,5 +1,8 @@
package com.bx.implatform.enums; package com.bx.implatform.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum FileType { public enum FileType {
FILE(0,"文件"), FILE(0,"文件"),
@ -13,15 +16,6 @@ public enum FileType {
private final String desc; private final String desc;
FileType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String description() {
return desc;
}
public Integer code(){ public Integer code(){
return this.code; return this.code;

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

@ -1,24 +1,19 @@
package com.bx.implatform.enums; package com.bx.implatform.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum MessageStatus { public enum MessageStatus {
UNREAD(0,"未读"), UNSEND(0,"未送达"),
ALREADY_READ(1,"已读"), SENDED(1,"送达"),
RECALL(2,"已撤回"); RECALL(2,"撤回"),
READED(3,"已读");
private final Integer code; private final Integer code;
private final String desc; private final String desc;
MessageStatus(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String description() {
return desc;
}
public Integer code(){ public Integer code(){
return this.code; return this.code;

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

@ -1,6 +1,8 @@
package com.bx.implatform.enums; package com.bx.implatform.enums;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum MessageType { public enum MessageType {
TEXT(0,"文字"), TEXT(0,"文字"),
@ -9,6 +11,7 @@ public enum MessageType {
AUDIO(3,"音频"), AUDIO(3,"音频"),
VIDEO(4,"视频"), VIDEO(4,"视频"),
RECALL(10,"撤回"), RECALL(10,"撤回"),
READED(11, "已读"),
RTC_CALL(101,"呼叫"), RTC_CALL(101,"呼叫"),
RTC_ACCEPT(102,"接受"), RTC_ACCEPT(102,"接受"),
@ -22,14 +25,6 @@ public enum MessageType {
private final String desc; private final String desc;
MessageType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String description() {
return desc;
}
public Integer code(){ public Integer code(){
return this.code; return this.code;

23
im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java

@ -1,5 +1,8 @@
package com.bx.implatform.enums; package com.bx.implatform.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/** /**
* 响应码枚举 * 响应码枚举
* *
@ -7,6 +10,8 @@ package com.bx.implatform.enums;
* @date 2020/10/19 * @date 2020/10/19
* *
**/ **/
@Getter
@AllArgsConstructor
public enum ResultCode { public enum ResultCode {
SUCCESS(200,"成功"), SUCCESS(200,"成功"),
NO_LOGIN(400,"未登录"), NO_LOGIN(400,"未登录"),
@ -21,24 +26,6 @@ public enum ResultCode {
private int code; private int code;
private String msg; private String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
} }

4
im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java

@ -29,8 +29,8 @@ public class PrivateMessageListener implements MessageListener<PrivateMessageVO>
if(result.getCode().equals(IMSendCode.SUCCESS.code())){ if(result.getCode().equals(IMSendCode.SUCCESS.code())){
UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>(); UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId()) updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId())
.eq(PrivateMessage::getStatus, MessageStatus.UNREAD.code()) .eq(PrivateMessage::getStatus, MessageStatus.UNSEND.code())
.set(PrivateMessage::getStatus, MessageStatus.ALREADY_READ.code()); .set(PrivateMessage::getStatus, MessageStatus.SENDED.code());
privateMessageService.update(updateWrapper); privateMessageService.update(updateWrapper);
log.info("消息已读,消息id:{},发送者:{},接收者:{},终端:{}",messageInfo.getId(),result.getSender().getId(),result.getReceiver().getId(),result.getReceiver().getTerminal()); log.info("消息已读,消息id:{},发送者:{},接收者:{},终端:{}",messageInfo.getId(),result.getSender().getId(),result.getReceiver().getId(),result.getReceiver().getTerminal());
} }

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

@ -17,5 +17,9 @@ public interface IGroupMessageService extends IService<GroupMessage> {
void pullUnreadMessage(); void pullUnreadMessage();
List<GroupMessageVO> loadMessage(Long minId);
void readedMessage(Long groupId);
List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size); List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size);
} }

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

@ -18,4 +18,7 @@ public interface IPrivateMessageService extends IService<PrivateMessage> {
void pullUnreadMessage(); void pullUnreadMessage();
List<PrivateMessageVO> loadMessage(Long minId);
void readedMessage(Long friendId);
} }

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

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient; import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant; import com.bx.imcommon.contant.IMConstant;
import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.GroupMessageVO; import com.bx.implatform.vo.GroupMessageVO;
import com.bx.imcommon.model.IMGroupMessage; import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMUserInfo; import com.bx.imcommon.model.IMUserInfo;
@ -33,6 +34,7 @@ import org.springframework.stereotype.Service;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -43,7 +45,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
@Autowired @Autowired
private IGroupMemberService groupMemberService; private IGroupMemberService groupMemberService;
@Autowired @Autowired
private RedisTemplate<String,Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@Autowired @Autowired
private IMClient imClient; private IMClient imClient;
@ -57,16 +59,16 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
public Long sendMessage(GroupMessageDTO dto) { public Long sendMessage(GroupMessageDTO dto) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = groupService.getById(dto.getGroupId()); Group group = groupService.getById(dto.getGroupId());
if(group == null){ if (group == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"群聊不存在"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
} }
if(group.getDeleted()){ if (group.getDeleted()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"群聊已解散"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊已解散");
} }
// 判断是否在群里 // 判断是否在群里
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId()); List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
if(!userIds.contains(session.getUserId())){ if (!userIds.contains(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法发送消息"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法发送消息");
} }
// 保存消息 // 保存消息
GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class); GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class);
@ -74,21 +76,19 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
msg.setSendTime(new Date()); msg.setSendTime(new Date());
this.save(msg); this.save(msg);
// 不用发给自己 // 不用发给自己
userIds = userIds.stream().filter(id->!session.getUserId().equals(id)).collect(Collectors.toList()); userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList());
// 群发 // 群发
GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class);
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>(); IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvIds(userIds); sendMessage.setRecvIds(userIds);
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);
imClient.sendGroupMessage(sendMessage); imClient.sendGroupMessage(sendMessage);
log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}",session.getUserId(),dto.getGroupId(),dto.getContent()); log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), dto.getGroupId(), dto.getContent());
return msg.getId(); return msg.getId();
} }
/** /**
* 撤回消息 * 撤回消息
* *
@ -98,19 +98,19 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
public void recallMessage(Long id) { public void recallMessage(Long id) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
GroupMessage msg = this.getById(id); GroupMessage msg = this.getById(id);
if(msg == null){ if (msg == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息不存在"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在");
} }
if(!msg.getSendId().equals(session.getUserId())){ if (!msg.getSendId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"这条消息不是由您发送,无法撤回"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "这条消息不是由您发送,无法撤回");
} }
if(System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000){ if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息已发送超过5分钟,无法撤回"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回");
} }
// 判断是否在群里 // 判断是否在群里
GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(),session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), session.getUserId());
if(member == null){ if (member == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法撤回消息");
} }
// 修改数据库 // 修改数据库
msg.setStatus(MessageStatus.RECALL.code()); msg.setStatus(MessageStatus.RECALL.code());
@ -118,15 +118,15 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
// 群发 // 群发
List<Long> userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId()); List<Long> userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId());
// 不用发给自己 // 不用发给自己
userIds = userIds.stream().filter(uid->!session.getUserId().equals(uid)).collect(Collectors.toList()); userIds = userIds.stream().filter(uid -> !session.getUserId().equals(uid)).collect(Collectors.toList());
GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class);
msgInfo.setType(MessageType.RECALL.code()); msgInfo.setType(MessageType.RECALL.code());
String content = String.format("'%s'撤回了一条消息",member.getAliasName()); String content = String.format("'%s'撤回了一条消息", member.getAliasName());
msgInfo.setContent(content); msgInfo.setContent(content);
msgInfo.setSendTime(new Date()); msgInfo.setSendTime(new Date());
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>(); IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvIds(userIds); sendMessage.setRecvIds(userIds);
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);
sendMessage.setSendResult(false); sendMessage.setSendResult(false);
@ -139,40 +139,39 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
sendMessage.setRecvIds(Collections.emptyList()); sendMessage.setRecvIds(Collections.emptyList());
sendMessage.setRecvTerminals(Collections.emptyList()); sendMessage.setRecvTerminals(Collections.emptyList());
imClient.sendGroupMessage(sendMessage); imClient.sendGroupMessage(sendMessage);
log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}",session.getUserId(),msg.getGroupId(),msg.getContent()); log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), msg.getGroupId(), msg.getContent());
} }
/** /**
* 异步拉取群聊消息通过websocket异步推送 * 异步拉取群聊消息通过websocket异步推送
*
*/ */
@Override @Override
public void pullUnreadMessage() { public void pullUnreadMessage() {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId()); List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
for(GroupMember member:members){ for (GroupMember member : members) {
// 获取群聊已读的最大消息id,只推送未读消息 // 获取群聊已读的最大消息id,只推送未读消息
String key = String.join(":",RedisKey.IM_GROUP_READED_POSITION,member.getGroupId().toString(),session.getUserId().toString()); String key = String.join(":", RedisKey.IM_GROUP_READED_POSITION, member.getGroupId().toString(), session.getUserId().toString());
Integer maxReadedId = (Integer)redisTemplate.opsForValue().get(key); Integer maxReadedId = (Integer) redisTemplate.opsForValue().get(key);
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.eq(GroupMessage::getGroupId,member.getGroupId()) wrapper.eq(GroupMessage::getGroupId, member.getGroupId())
.gt(GroupMessage::getSendTime,member.getCreatedTime()) .gt(GroupMessage::getSendTime, member.getCreatedTime())
.ne(GroupMessage::getSendId, session.getUserId()) .ne(GroupMessage::getSendId, session.getUserId())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code()); .ne(GroupMessage::getStatus, MessageStatus.RECALL.code());
if(maxReadedId!=null){ if (maxReadedId != null) {
wrapper.gt(GroupMessage::getId,maxReadedId); wrapper.gt(GroupMessage::getId, maxReadedId);
} }
wrapper.last("limit 100"); wrapper.last("limit 100");
List<GroupMessage> messages = this.list(wrapper); List<GroupMessage> messages = this.list(wrapper);
if(messages.isEmpty()){ if (messages.isEmpty()) {
continue; continue;
} }
// 推送 // 推送
for (GroupMessage message:messages ){ for (GroupMessage message : messages) {
GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class); GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class);
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>(); IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
// 只推给自己当前终端 // 只推给自己当前终端
sendMessage.setRecvIds(Collections.singletonList(session.getUserId())); sendMessage.setRecvIds(Collections.singletonList(session.getUserId()));
sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal())); sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal()));
@ -180,9 +179,77 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
imClient.sendGroupMessage(sendMessage); imClient.sendGroupMessage(sendMessage);
} }
// 发送消息 // 发送消息
log.info("拉取未读群聊消息,用户id:{},群聊id:{},数量:{}",session.getUserId(),member.getGroupId(),messages.size()); log.info("拉取未读群聊消息,用户id:{},群聊id:{},数量:{}", session.getUserId(), member.getGroupId(), messages.size());
}
}
/**
* 拉取消息只能拉取最近3个月的消息一次拉取100条
*
* @param minId 消息起始id
* @return 聊天消息列表
*/
@Override
public List<GroupMessageVO> loadMessage(Long minId) {
UserSession session = SessionContext.getSession();
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
List<Long> ids = members.stream().map(GroupMember::getGroupId).collect(Collectors.toList());
// 只能拉取最近3个月的
Date minDate = DateTimeUtils.addMonths(new Date(), -1);
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.gt(GroupMessage::getId, minId)
.gt(GroupMessage::getSendTime, minDate)
.in(GroupMessage::getGroupId, ids)
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.last("limit 100");
List<GroupMessage> messages = this.list(wrapper);
// 转成vo
List<GroupMessageVO> vos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList());
// 消息状态,数据库没有存群聊的消息状态,需要从redis取
List<String> keys = ids.stream()
.map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString()))
.collect(Collectors.toList());
List<Object> sendPos = redisTemplate.opsForValue().multiGet(keys);
int idx = 0;
for (Long id : ids) {
Object o = sendPos.get(idx);
Integer sendMaxId = Objects.isNull(o) ? -1 : (Integer) o;
vos.stream().filter(vo -> vo.getGroupId().equals(id)).forEach(vo -> {
if (vo.getId() <= sendMaxId) {
// 已推送过
vo.setStatus(MessageStatus.SENDED.code());
} else {
// 未推送
vo.setStatus(MessageStatus.UNSEND.code());
}
});
idx++;
}
return vos;
} }
/**
* 消息已读,同步其他终端清空未读数量
*
* @param groupId 群聊
*/
@Override
public void readedMessage(Long groupId) {
UserSession session = SessionContext.getSession();
// 推送消息给自己的其他终端
GroupMessageVO msgInfo = new GroupMessageVO();
msgInfo.setType(MessageType.READED.code());
msgInfo.setSendTime(new Date());
msgInfo.setSendId(session.getUserId());
msgInfo.setGroupId(groupId);
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setSendToSelf(true);
sendMessage.setData(msgInfo);
sendMessage.setSendResult(false);
imClient.sendGroupMessage(sendMessage);
} }
/** /**
@ -195,26 +262,26 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
*/ */
@Override @Override
public List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size) { public List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size) {
page = page > 0 ? page:1; page = page > 0 ? page : 1;
size = size > 0 ? size:10; size = size > 0 ? size : 10;
Long userId = SessionContext.getSession().getUserId(); Long userId = SessionContext.getSession().getUserId();
long stIdx = (page-1)* size; long stIdx = (page - 1) * size;
// 群聊成员信息 // 群聊成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(groupId,userId); GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userId);
if(member == null || member.getQuit()){ if (member == null || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊中"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊中");
} }
// 查询聊天记录,只查询加入群聊时间之后的消息 // 查询聊天记录,只查询加入群聊时间之后的消息
QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>(); QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMessage::getGroupId,groupId) wrapper.lambda().eq(GroupMessage::getGroupId, groupId)
.gt(GroupMessage::getSendTime,member.getCreatedTime()) .gt(GroupMessage::getSendTime, member.getCreatedTime())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code()) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId) .orderByDesc(GroupMessage::getId)
.last("limit "+stIdx + ","+size); .last("limit " + stIdx + "," + size);
List<GroupMessage> messages = this.list(wrapper); List<GroupMessage> messages = this.list(wrapper);
List<GroupMessageVO> messageInfos = messages.stream().map(m->BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); List<GroupMessageVO> messageInfos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList());
log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}",userId,groupId,messageInfos.size()); log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}", userId, groupId, messageInfos.size());
return messageInfos; return messageInfos;
} }

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

@ -2,6 +2,7 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient; import com.bx.imclient.IMClient;
@ -9,6 +10,7 @@ import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.model.IMPrivateMessage; import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo; import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.entity.Friend; import com.bx.implatform.entity.Friend;
import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.PrivateMessageVO; import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.entity.PrivateMessage; import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus; import com.bx.implatform.enums.MessageStatus;
@ -25,6 +27,7 @@ import com.bx.implatform.dto.PrivateMessageDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@ -40,6 +43,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
@Autowired @Autowired
private IMClient imClient; private IMClient imClient;
/** /**
* 发送私聊消息 * 发送私聊消息
* *
@ -56,13 +60,13 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
// 保存消息 // 保存消息
PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class); PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class);
msg.setSendId(session.getUserId()); msg.setSendId(session.getUserId());
msg.setStatus(MessageStatus.UNREAD.code()); msg.setStatus(MessageStatus.UNSEND.code());
msg.setSendTime(new Date()); msg.setSendTime(new Date());
this.save(msg); this.save(msg);
// 推送消息 // 推送消息
PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class); PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>(); IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(msgInfo.getRecvId()); sendMessage.setRecvId(msgInfo.getRecvId());
sendMessage.setSendToSelf(true); sendMessage.setSendToSelf(true);
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);
@ -99,7 +103,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
msgInfo.setContent("对方撤回了一条消息"); msgInfo.setContent("对方撤回了一条消息");
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>(); IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(msgInfo.getRecvId()); sendMessage.setRecvId(msgInfo.getRecvId());
sendMessage.setSendToSelf(false); sendMessage.setSendToSelf(false);
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);
@ -147,7 +151,6 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
/** /**
* 异步拉取私聊消息通过websocket异步推送 * 异步拉取私聊消息通过websocket异步推送
*
*/ */
@Override @Override
public void pullUnreadMessage() { public void pullUnreadMessage() {
@ -158,22 +161,22 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
} }
List<Friend> friends = friendService.findFriendByUserId(session.getUserId()); List<Friend> friends = friendService.findFriendByUserId(session.getUserId());
if(friends.isEmpty()){ if (friends.isEmpty()) {
return; return;
} }
List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList()); List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList());
// 获取当前用户所有未读消息 // 获取当前用户所有未读消息
LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(PrivateMessage::getRecvId, session.getUserId()) queryWrapper.eq(PrivateMessage::getRecvId, session.getUserId())
.eq(PrivateMessage::getStatus, MessageStatus.UNREAD) .eq(PrivateMessage::getStatus, MessageStatus.UNSEND)
.in(PrivateMessage::getSendId,friendIds); .in(PrivateMessage::getSendId, friendIds);
List<PrivateMessage> messages = this.list(queryWrapper); List<PrivateMessage> messages = this.list(queryWrapper);
// 上传至redis,等待推送 // 上传至redis,等待推送
for(PrivateMessage message:messages){ for (PrivateMessage message : messages) {
PrivateMessageVO msgInfo = BeanUtils.copyProperties(message, PrivateMessageVO.class); PrivateMessageVO msgInfo = BeanUtils.copyProperties(message, PrivateMessageVO.class);
// 推送消息 // 推送消息
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>(); IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(session.getUserId()); sendMessage.setRecvId(session.getUserId());
sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal())); sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal()));
sendMessage.setSendToSelf(false); sendMessage.setSendToSelf(false);
@ -183,4 +186,82 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
log.info("拉取未读私聊消息,用户id:{},数量:{}", session.getUserId(), messages.size()); log.info("拉取未读私聊消息,用户id:{},数量:{}", session.getUserId(), messages.size());
} }
/**
* 拉取消息只能拉取最近3个月的消息一次拉取100条
*
* @param minId 消息起始id
* @return 聊天消息列表
*/
@Override
public List<PrivateMessageVO> loadMessage(Long minId) {
UserSession session = SessionContext.getSession();
List<Friend> friends = friendService.findFriendByUserId(session.getUserId());
if (friends.isEmpty()) {
return Collections.EMPTY_LIST;
}
List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList());
// 获取当前用户的消息
LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery();
// 只能拉取最近6个月的
Date minDate = DateTimeUtils.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)))
.last("limit 100");
List<PrivateMessage> messages = this.list(queryWrapper);
// 更新发送状态
List<Long> ids = messages.stream()
.filter(m -> !m.getSendId().equals(session.getUserId()) && m.getStatus().equals(MessageStatus.UNSEND.code()))
.map(PrivateMessage::getId)
.collect(Collectors.toList());
if (!ids.isEmpty()) {
LambdaUpdateWrapper<PrivateMessage> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.in(PrivateMessage::getId, ids)
.set(PrivateMessage::getStatus, MessageStatus.SENDED.code());
this.update(updateWrapper);
}
log.info("拉取消息,用户id:{},数量:{}", session.getUserId(), messages.size());
return messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class)).collect(Collectors.toList());
}
/**
* 消息已读,将整个会话的消息都置为已读状态
*
* @param friendId 好友id
*/
@Transactional
@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.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(friendId);
sendMessage.setSendToSelf(true);
sendMessage.setData(msgInfo);
sendMessage.setSendResult(false);
imClient.sendPrivateMessage(sendMessage);
// 修改消息状态为已读
LambdaUpdateWrapper<PrivateMessage> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(PrivateMessage::getSendId,friendId)
.eq(PrivateMessage::getRecvId,session.getUserId())
.eq(PrivateMessage::getStatus,MessageStatus.SENDED.code())
.set(PrivateMessage::getStatus,MessageStatus.READED.code());
this.update(updateWrapper);
log.info("消息已读,接收方id:{},发送方id:{}", session.getUserId(),friendId);
}
} }

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

@ -2,6 +2,7 @@ package com.bx.implatform.vo;
import com.bx.imcommon.serializer.DateToLongSerializer; import com.bx.imcommon.serializer.DateToLongSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
@ -9,34 +10,25 @@ import java.util.Date;
@Data @Data
public class GroupMessageVO { public class GroupMessageVO {
/* @ApiModelProperty(value = "消息id")
* 消息id
*/
private Long id; private Long id;
/* @ApiModelProperty(value = "群聊id")
* 群聊id
*/
private Long groupId; private Long groupId;
/* @ApiModelProperty(value = " 发送者id")
* 发送者id
*/
private Long sendId; private Long sendId;
/* @ApiModelProperty(value = "消息内容")
* 消息内容
*/
private String content; private String content;
/* @ApiModelProperty(value = "消息内容类型 具体枚举值由应用层定义")
* 消息内容类型 具体枚举值由应用层定义
*/
private Integer type; private Integer type;
/** @ApiModelProperty(value = " 状态")
* 发送时间 private Integer status;
*/
@ApiModelProperty(value = "发送时间")
@JsonSerialize(using = DateToLongSerializer.class) @JsonSerialize(using = DateToLongSerializer.class)
private Date sendTime; private Date sendTime;
} }

30
im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java

@ -2,41 +2,35 @@ package com.bx.implatform.vo;
import com.bx.imcommon.serializer.DateToLongSerializer; import com.bx.imcommon.serializer.DateToLongSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
@Data @Data
@ApiModel("私聊消息VO")
public class PrivateMessageVO { public class PrivateMessageVO {
/* @ApiModelProperty(value = " 消息id")
* 消息id
*/
private long id; private long id;
/* @ApiModelProperty(value = " 发送者id")
* 发送者id
*/
private Long sendId; private Long sendId;
/* @ApiModelProperty(value = " 接收者id")
* 接收者id
*/
private Long recvId; private Long recvId;
/* @ApiModelProperty(value = " 发送内容")
* 发送内容
*/
private String content; private String content;
/* @ApiModelProperty(value = "消息内容类型 IMCmdType")
* 消息内容类型 IMCmdType
*/
private Integer type; private Integer type;
/** @ApiModelProperty(value = " 状态")
* 发送时间 private Integer status;
*/
@ApiModelProperty(value = " 发送时间")
@JsonSerialize(using = DateToLongSerializer.class) @JsonSerialize(using = DateToLongSerializer.class)
private Date sendTime; private Date sendTime;
} }

3
im-ui/src/api/emotion.js

@ -15,6 +15,9 @@ let transform = (content) => {
let textToImg = (emoText) => { let textToImg = (emoText) => {
let word = emoText.replace(/\#|\;/gi, ''); let word = emoText.replace(/\#|\;/gi, '');
let idx = emoTextList.indexOf(word); let idx = emoTextList.indexOf(word);
if(idx==-1){
return "";
}
let url = require(`@/assets/emoji/${idx}.gif`); let url = require(`@/assets/emoji/${idx}.gif`);
return `<img src="${url}" style="width:40px;height:40px;vertical-align:bottom;"/>` return `<img src="${url}" style="width:40px;height:40px;vertical-align:bottom;"/>`
} }

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

@ -6,6 +6,7 @@ const MESSAGE_TYPE = {
AUDIO:3, AUDIO:3,
VIDEO:4, VIDEO:4,
RECALL:10, RECALL:10,
READED:11,
TIP_TIME:20, TIP_TIME:20,
RTC_CALL: 101, RTC_CALL: 101,
RTC_ACCEPT: 102, RTC_ACCEPT: 102,
@ -27,8 +28,17 @@ const TERMINAL_TYPE = {
APP: 1 APP: 1
} }
const MESSAGE_STATUS = {
UNSEND: 0,
SENDED: 1,
RECALL:2,
READED:3
}
export { export {
MESSAGE_TYPE, MESSAGE_TYPE,
USER_STATE, USER_STATE,
TERMINAL_TYPE TERMINAL_TYPE,
MESSAGE_STATUS
} }

48
im-ui/src/components/chat/ChatBox.vue

@ -48,7 +48,7 @@
<div class="send-content-area"> <div class="send-content-area">
<textarea v-show="!sendImageUrl" v-model="sendText" ref="sendBox" class="send-text-area" <textarea v-show="!sendImageUrl" v-model="sendText" ref="sendBox" class="send-text-area"
:disabled="lockMessage" @keydown.enter="sendTextMessage()" @paste="handlePaste" :disabled="lockMessage" @keydown.enter="sendTextMessage()" @paste="handlePaste"
placeholder="温馨提示:可以粘贴截图了哦~"></textarea> placeholder="温馨提示:可以粘贴截图到这里了哦~"></textarea>
<div v-show="sendImageUrl" class="send-image-area"> <div v-show="sendImageUrl" class="send-image-area">
<div class="send-image-box"> <div class="send-image-box">
@ -120,7 +120,6 @@
}, },
methods: { methods: {
handlePaste(e) { handlePaste(e) {
console.log(e);
let txt = event.clipboardData.getData('Text') let txt = event.clipboardData.getData('Text')
if (typeof(txt) == 'string') { if (typeof(txt) == 'string') {
this.sendText += txt this.sendText += txt
@ -172,7 +171,8 @@
sendTime: new Date().getTime(), sendTime: new Date().getTime(),
selfSend: true, selfSend: true,
type: 1, type: 1,
loadStatus: "loading" loadStatus: "loading",
status: this.$enums.MESSAGE_STATUS.UNSEND
} }
// id // id
this.fillTargetId(msgInfo, this.chat.targetId); this.fillTargetId(msgInfo, this.chat.targetId);
@ -221,7 +221,8 @@
sendTime: new Date().getTime(), sendTime: new Date().getTime(),
selfSend: true, selfSend: true,
type: 2, type: 2,
loadStatus: "loading" loadStatus: "loading",
status: this.$enums.MESSAGE_STATUS.UNSEND
} }
// id // id
this.fillTargetId(msgInfo, this.chat.targetId); this.fillTargetId(msgInfo, this.chat.targetId);
@ -256,7 +257,6 @@
this.showVoice = false; this.showVoice = false;
}, },
showVideoBox() { showVideoBox() {
console.log(this.friend)
this.$store.commit("showChatPrivateVideoBox", { this.$store.commit("showChatPrivateVideoBox", {
friend: this.friend, friend: this.friend,
master: true master: true
@ -280,11 +280,11 @@
method: 'post', method: 'post',
data: msgInfo data: msgInfo
}).then((id) => { }).then((id) => {
this.$message.success("发送成功");
msgInfo.id = id; msgInfo.id = id;
msgInfo.sendTime = new Date().getTime(); msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true; msgInfo.selfSend = true;
msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND;
this.$store.commit("insertMessage", msgInfo); this.$store.commit("insertMessage", msgInfo);
// //
this.$refs.sendBox.focus(); this.$refs.sendBox.focus();
@ -323,7 +323,7 @@
this.handleImageSuccess(res, file); this.handleImageSuccess(res, file);
}) })
this.sendImageFile = null; this.sendImageFile = null;
this.sendImageUrl= ""; this.sendImageUrl = "";
this.$nextTick(() => this.$refs.sendBox.focus()); this.$nextTick(() => this.$refs.sendBox.focus());
this.scrollToBottom(); this.scrollToBottom();
}, },
@ -344,12 +344,12 @@
method: 'post', method: 'post',
data: msgInfo data: msgInfo
}).then((id) => { }).then((id) => {
this.$message.success("发送成功");
this.sendText = ""; this.sendText = "";
msgInfo.id = id; msgInfo.id = id;
msgInfo.sendTime = new Date().getTime(); msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true; msgInfo.selfSend = true;
msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND;
this.$store.commit("insertMessage", msgInfo); this.$store.commit("insertMessage", msgInfo);
}).finally(() => { }).finally(() => {
// //
@ -390,10 +390,24 @@
msgInfo = JSON.parse(JSON.stringify(msgInfo)); msgInfo = JSON.parse(JSON.stringify(msgInfo));
msgInfo.type = 10; msgInfo.type = 10;
msgInfo.content = '你撤回了一条消息'; msgInfo.content = '你撤回了一条消息';
msgInfo.status = this.$enums.MESSAGE_STATUS.RECALL;
this.$store.commit("insertMessage", msgInfo); this.$store.commit("insertMessage", msgInfo);
}) })
}); });
},
readedMessage() {
if(this.chat.type == "GROUP"){
var url = `/message/group/readed?groupId=${this.chat.targetId}`
}else{
url = `/message/private/readed?friendId=${this.chat.targetId}`
}
this.$http({
url: url,
method: 'put'
}).then(() => {
this.$store.commit("resetUnreadCount",this.chat)
this.scrollToBottom();
})
}, },
loadGroup(groupId) { loadGroup(groupId) {
this.$http({ this.$http({
@ -420,7 +434,6 @@
method: 'get' method: 'get'
}).then((friend) => { }).then((friend) => {
this.friend = friend; this.friend = friend;
console.log(this.friend)
this.$store.commit("updateChatFromFriend", friend); this.$store.commit("updateChatFromFriend", friend);
this.$store.commit("updateFriend", friend); this.$store.commit("updateFriend", friend);
}) })
@ -469,12 +482,16 @@
}, },
messageAction() { messageAction() {
return `/message/${this.chat.type.toLowerCase()}/send`; return `/message/${this.chat.type.toLowerCase()}/send`;
},
unreadCount() {
return this.chat.unreadCount;
} }
}, },
watch: { watch: {
chat: { chat: {
handler(newChat, oldChat) { handler(newChat, oldChat) {
if (newChat.targetId > 0 && (!oldChat || newChat.type != oldChat.type || newChat.targetId != oldChat.targetId)) { if (newChat.targetId > 0 && (!oldChat || newChat.type != oldChat.type || newChat.targetId != oldChat
.targetId)) {
if (this.chat.type == "GROUP") { if (this.chat.type == "GROUP") {
this.loadGroup(this.chat.targetId); this.loadGroup(this.chat.targetId);
} else { } else {
@ -489,6 +506,14 @@
} }
}, },
immediate: true immediate: true
},
unreadCount: {
handler(newCount, oldCount) {
if(newCount > 0){
//
this.readedMessage()
}
}
} }
} }
} }
@ -617,7 +642,6 @@
} }
.send-btn-area { .send-btn-area {
padding: 10px; padding: 10px;
position: absolute; position: absolute;
bottom: 0; bottom: 0;

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

@ -1,7 +1,9 @@
<template> <template>
<div class="chat-msg-item"> <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.content}}</div>
<div class="chat-msg-tip" v-show="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME">{{$date.toTimeText(msgInfo.sendTime)}}</div> <div class="chat-msg-tip" v-show="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME">
{{$date.toTimeText(msgInfo.sendTime)}}
</div>
<div class="chat-msg-normal" v-show="msgInfo.type>=0 && msgInfo.type<10" :class="{'chat-msg-mine':mine}"> <div class="chat-msg-normal" v-show="msgInfo.type>=0 && msgInfo.type<10" :class="{'chat-msg-mine':mine}">
<div class="head-image"> <div class="head-image">
@ -45,6 +47,10 @@
@click="handlePlayVoice()"> @click="handlePlayVoice()">
<audio controls :src="JSON.parse(msgInfo.content).url"></audio> <audio controls :src="JSON.parse(msgInfo.content).url"></audio>
</div> </div>
<span class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status==$enums.MESSAGE_STATUS.READED">已读</span>
<span class="chat-unread" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status!=$enums.MESSAGE_STATUS.READED">未读</span>
</div> </div>
</div> </div>
@ -65,9 +71,9 @@
RightMenu RightMenu
}, },
props: { props: {
mode:{ mode: {
type: Number, type: Number,
default :1 default: 1
}, },
mine: { mine: {
type: Boolean, type: Boolean,
@ -168,9 +174,6 @@
} }
return items; return items;
} }
},
mounted() {
console.log(this.msgInfo);
} }
} }
</script> </script>
@ -230,7 +233,7 @@
line-height: 30px; line-height: 30px;
margin-top: 3px; margin-top: 3px;
padding: 7px; padding: 7px;
background-color: rgb(235,235,245); background-color: rgb(235, 235, 245);
border-radius: 10px; border-radius: 10px;
color: black; color: black;
display: block; display: block;
@ -239,6 +242,7 @@
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
box-shadow: 2px 2px 2px #c0c0f0; box-shadow: 2px 2px 2px #c0c0f0;
&:after { &:after {
content: ""; content: "";
position: absolute; position: absolute;
@ -247,7 +251,7 @@
width: 0; width: 0;
height: 0; height: 0;
border-style: solid dashed dashed; border-style: solid dashed dashed;
border-color: rgb(235,235,245) transparent transparent; border-color: rgb(235, 235, 245) transparent transparent;
overflow: hidden; overflow: hidden;
border-width: 10px; border-width: 10px;
} }
@ -332,6 +336,18 @@
padding: 5px 0; padding: 5px 0;
} }
} }
.chat-unread {
font-size: 10px;
color: #f23c0f;
font-weight: 600;
}
.chat-readed {
font-size: 10px;
color: #aaa;
font-weight: 600;
}
} }
} }
@ -368,6 +384,7 @@
color: #fff; color: #fff;
vertical-align: top; vertical-align: top;
box-shadow: 2px 2px 1px #ccc; box-shadow: 2px 2px 1px #ccc;
&:after { &:after {
left: auto; left: auto;
right: -10px; right: -10px;

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

@ -1,25 +1,42 @@
import {MESSAGE_TYPE} from "../api/enums.js" import {
MESSAGE_TYPE,
MESSAGE_STATUS
} from "../api/enums.js"
import userStore from './userStore';
export default { export default {
state: { state: {
activeIndex: -1, activeIndex: -1,
privateMsgMaxId: 0,
groupMsgMaxId: 0,
loadingPrivateMsg: false,
loadingGroupMsg: false,
chats: [] chats: []
}, },
mutations: { mutations: {
initChatStore(state) { initChats(state, chats) {
state.chats = chats||[];
state.chats.forEach((chat) => {
chat.messages.forEach((msg) => {
// 防止图片一直处在加载中状态 // 防止图片一直处在加载中状态
state.chats.forEach((chat)=>{ if (msg.loadStatus == "loading") {
chat.messages.forEach((msg)=>{
if(msg.loadStatus == "loading"){
msg.loadStatus = "fail" msg.loadStatus = "fail"
} }
// 记录最大私聊消息id
if(chat.type == "PRIVATE" && msg.id && msg.id>state.privateMsgMaxId){
state.privateMsgMaxId = msg.id;
}
// 记录最大群聊消息id
if(chat.type == "GROUP" && msg.id && msg.id>state.groupMsgMaxId){
state.groupMsgMaxId = msg.id;
}
}) })
}) })
}, },
openChat(state, chatInfo) { openChat(state, chatInfo) {
let chat = null; let chat = null;
let activeChat = state.activeIndex>=0?state.chats[state.activeIndex]:null; let activeChat = state.activeIndex >= 0 ? state.chats[state.activeIndex] : null;
for (let i in state.chats) { for (let i in state.chats) {
if (state.chats[i].type == chatInfo.type && if (state.chats[i].type == chatInfo.type &&
state.chats[i].targetId === chatInfo.targetId) { state.chats[i].targetId === chatInfo.targetId) {
@ -45,10 +62,10 @@ export default {
state.chats.unshift(chat); state.chats.unshift(chat);
} }
// 选中会话保持不变 // 选中会话保持不变
if(activeChat){ if (activeChat) {
state.chats.forEach((chat,idx)=>{ state.chats.forEach((chat, idx) => {
if(activeChat.type == chat.type if (activeChat.type == chat.type &&
&& activeChat.targetId == chat.targetId){ activeChat.targetId == chat.targetId) {
state.activeIndex = idx; state.activeIndex = idx;
} }
}) })
@ -56,20 +73,42 @@ export default {
}, },
activeChat(state, idx) { activeChat(state, idx) {
state.activeIndex = idx; state.activeIndex = idx;
state.chats[idx].unreadCount = 0; },
resetUnreadCount(state, chatInfo) {
for (let idx in state.chats) {
if (state.chats[idx].type == chatInfo.type
&& state.chats[idx].targetId == chatInfo.targetId) {
state.chats[idx].unreadCount=0;
}
}
this.commit("saveToStorage");
},
readedMessage(state, friendId) {
for (let idx in state.chats) {
if (state.chats[idx].type == 'PRIVATE'
&& state.chats[idx].targetId == friendId) {
state.chats[idx].messages.forEach((m) => {
if (m.selfSend && m.status != MESSAGE_STATUS.RECALL) {
m.status = MESSAGE_STATUS.READED
}
})
}
}
this.commit("saveToStorage");
}, },
removeChat(state, idx) { removeChat(state, idx) {
state.chats.splice(idx, 1); state.chats.splice(idx, 1);
if (state.activeIndex >= state.chats.length) { if (state.activeIndex >= state.chats.length) {
state.activeIndex = state.chats.length - 1; state.activeIndex = state.chats.length - 1;
} }
this.commit("saveToStorage");
}, },
moveTop(state,idx){ moveTop(state, idx) {
let chat = state.chats[idx]; let chat = state.chats[idx];
// 放置头部 // 放置头部
state.chats.splice(idx, 1); state.chats.splice(idx, 1);
state.chats.unshift(chat); state.chats.unshift(chat);
this.commit("saveToStorage");
}, },
removeGroupChat(state, groupId) { removeGroupChat(state, groupId) {
for (let idx in state.chats) { for (let idx in state.chats) {
@ -79,15 +118,16 @@ export default {
} }
} }
}, },
removePrivateChat(state, userId) { removePrivateChat(state, friendId) {
for (let idx in state.chats) { for (let idx in state.chats) {
if (state.chats[idx].type == 'PRIVATE' && if (state.chats[idx].type == 'PRIVATE' &&
state.chats[idx].targetId == userId) { state.chats[idx].targetId == friendId) {
this.commit("removeChat", idx); this.commit("removeChat", idx);
} }
} }
}, },
insertMessage(state, msgInfo) { insertMessage(state, msgInfo) {
// 获取对方id或群id // 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId;
@ -100,36 +140,45 @@ export default {
} }
} }
// 插入新的数据 // 插入新的数据
if(msgInfo.type == MESSAGE_TYPE.IMAGE ){ if (msgInfo.type == MESSAGE_TYPE.IMAGE) {
chat.lastContent = "[图片]"; chat.lastContent = "[图片]";
}else if(msgInfo.type == MESSAGE_TYPE.FILE){ } else if (msgInfo.type == MESSAGE_TYPE.FILE) {
chat.lastContent = "[文件]"; chat.lastContent = "[文件]";
}else if(msgInfo.type == MESSAGE_TYPE.AUDIO){ } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
chat.lastContent = "[语音]"; chat.lastContent = "[语音]";
}else{ } else {
chat.lastContent = msgInfo.content; chat.lastContent = msgInfo.content;
} }
chat.lastSendTime = msgInfo.sendTime; chat.lastSendTime = msgInfo.sendTime;
// 如果不是当前会话,未读加1 // 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
chat.unreadCount++; chat.unreadCount++;
if(msgInfo.selfSend){ }
chat.unreadCount=0;
// 记录消息的最大id
if (msgInfo.id && type=="PRIVATE" && msgInfo.id > state.privateMsgMaxId) {
state.privateMsgMaxId = msgInfo.id;
}
if (msgInfo.id && type=="GROUP" && msgInfo.id > state.groupMsgMaxId) {
state.groupMsgMaxId = msgInfo.id;
} }
// 如果是已存在消息,则覆盖旧的消息数据 // 如果是已存在消息,则覆盖旧的消息数据
for (let idx in chat.messages) { for (let idx in chat.messages) {
if(msgInfo.id && chat.messages[idx].id == msgInfo.id){ if (msgInfo.id && chat.messages[idx].id == msgInfo.id) {
Object.assign(chat.messages[idx], msgInfo); Object.assign(chat.messages[idx], msgInfo);
this.commit("saveToStorage");
return; return;
} }
// 正在发送中的消息可能没有id,通过发送时间判断 // 正在发送中的消息可能没有id,通过发送时间判断
if(msgInfo.selfSend && chat.messages[idx].selfSend if (msgInfo.selfSend && chat.messages[idx].selfSend &&
&& chat.messages[idx].sendTime == msgInfo.sendTime){ chat.messages[idx].sendTime == msgInfo.sendTime) {
Object.assign(chat.messages[idx], msgInfo); Object.assign(chat.messages[idx], msgInfo);
this.commit("saveToStorage");
return; return;
} }
} }
// 间隔大于10分钟插入时间显示 // 间隔大于10分钟插入时间显示
if(!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600*1000)){ if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) {
chat.messages.push({ chat.messages.push({
sendTime: msgInfo.sendTime, sendTime: msgInfo.sendTime,
type: MESSAGE_TYPE.TIP_TIME, type: MESSAGE_TYPE.TIP_TIME,
@ -138,9 +187,9 @@ export default {
} }
// 新的消息 // 新的消息
chat.messages.push(msgInfo); chat.messages.push(msgInfo);
this.commit("saveToStorage");
}, },
deleteMessage(state, msgInfo){ deleteMessage(state, msgInfo) {
// 获取对方id或群id // 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId;
@ -155,42 +204,68 @@ export default {
for (let idx in chat.messages) { for (let idx in chat.messages) {
// 已经发送成功的,根据id删除 // 已经发送成功的,根据id删除
if(chat.messages[idx].id && chat.messages[idx].id == msgInfo.id){ if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) {
chat.messages.splice(idx, 1); chat.messages.splice(idx, 1);
break; break;
} }
// 正在发送中的消息可能没有id,根据发送时间删除 // 正在发送中的消息可能没有id,根据发送时间删除
if(msgInfo.selfSend && chat.messages[idx].selfSend if (msgInfo.selfSend && chat.messages[idx].selfSend &&
&&chat.messages[idx].sendTime == msgInfo.sendTime){ chat.messages[idx].sendTime == msgInfo.sendTime) {
chat.messages.splice(idx, 1); chat.messages.splice(idx, 1);
break; break;
} }
} }
this.commit("saveToStorage");
}, },
updateChatFromFriend(state, friend) { updateChatFromFriend(state, friend) {
for (let i in state.chats) { for (let i in state.chats) {
let chat = state.chats[i]; let chat = state.chats[i];
if (chat.type=='PRIVATE' && chat.targetId == friend.id) { if (chat.type == 'PRIVATE' && chat.targetId == friend.id) {
chat.headImage = friend.headImageThumb; chat.headImage = friend.headImageThumb;
chat.showName = friend.nickName; chat.showName = friend.nickName;
break; break;
} }
} }
this.commit("saveToStorage");
}, },
updateChatFromGroup(state, group) { updateChatFromGroup(state, group) {
for (let i in state.chats) { for (let i in state.chats) {
let chat = state.chats[i]; let chat = state.chats[i];
if (chat.type=='GROUP' && chat.targetId == group.id) { if (chat.type == 'GROUP' && chat.targetId == group.id) {
chat.headImage = group.headImageThumb; chat.headImage = group.headImageThumb;
chat.showName = group.remark; chat.showName = group.remark;
break; break;
} }
} }
this.commit("saveToStorage");
},
loadingPrivateMsg(state,loadding){
state.loadingPrivateMsg = loadding;
}, },
resetChatStore(state) { loadingGroupMsg(state,loadding){
state.loadingGroupMsg = loadding;
},
saveToStorage(state) {
let userId = userStore.state.userInfo.id;
let key = "chats-" + userId;
localStorage.setItem(key, JSON.stringify(state.chats));
},
clear(state) {
state.activeIndex = -1; state.activeIndex = -1;
state.chats = []; state.chats = [];
} }
}, },
actions: {
loadChat(context) {
return new Promise((resolve, reject) => {
let userId = userStore.state.userInfo.id;
let key = "chats-" + userId;
let item = localStorage.getItem(key)
let chats = JSON.parse(localStorage.getItem(key));
context.commit("initChats", chats);
resolve();
})
}
}
} }

39
im-ui/src/store/friendStore.js

@ -1,4 +1,4 @@
import httpRequest from '../api/httpRequest.js' import http from '../api/httpRequest.js'
import {TERMINAL_TYPE} from "../api/enums.js" import {TERMINAL_TYPE} from "../api/enums.js"
export default { export default {
@ -9,16 +9,6 @@ export default {
timer: null timer: null
}, },
mutations: { mutations: {
initFriendStore(state) {
httpRequest({
url: '/friend/list',
method: 'get'
}).then((friends) => {
this.commit("setFriends",friends);
this.commit("refreshOnlineStatus");
})
},
setFriends(state, friends) { setFriends(state, friends) {
state.friends = friends; state.friends = friends;
}, },
@ -50,7 +40,7 @@ export default {
return; return;
} }
state.friends.forEach((f)=>{userIds.push(f.id)}); state.friends.forEach((f)=>{userIds.push(f.id)});
httpRequest({ http({
url: '/user/terminal/online', url: '/user/terminal/online',
method: 'get', method: 'get',
params: {userIds: userIds.join(',')} params: {userIds: userIds.join(',')}
@ -90,7 +80,6 @@ export default {
return 0; return 0;
}); });
console.log(state.friends)
// 重新排序后,activeIndex指向的好友可能会变化,需要重新指定 // 重新排序后,activeIndex指向的好友可能会变化,需要重新指定
if(state.activeIndex >=0){ if(state.activeIndex >=0){
state.friends.forEach((f,i)=>{ state.friends.forEach((f,i)=>{
@ -99,7 +88,29 @@ export default {
} }
}) })
} }
},
clear(state) {
clearTimeout(state.timer);
state.friends = [];
state.timer = null;
state.activeIndex = -1;
}
},
actions: {
loadFriend(context) {
return new Promise((resolve, reject) => {
http({
url: '/friend/list',
method: 'GET'
}).then((friends) => {
context.commit("setFriends", friends);
context.commit("refreshOnlineStatus");
console.log("loadFriend")
resolve()
}).catch((res) => {
reject();
})
});
} }
} }
} }

52
im-ui/src/store/groupStore.js

@ -1,4 +1,4 @@
import httpRequest from '../api/httpRequest.js' import http from '../api/httpRequest.js'
export default { export default {
@ -7,41 +7,53 @@ export default {
activeIndex: -1, activeIndex: -1,
}, },
mutations: { mutations: {
initGroupStore(state) { setGroups(state, groups) {
httpRequest({
url: '/group/list',
method: 'get'
}).then((groups) => {
this.commit("setGroups",groups);
})
},
setGroups(state,groups){
state.groups = groups; state.groups = groups;
}, },
activeGroup(state,index){ activeGroup(state, index) {
state.activeIndex = index; state.activeIndex = index;
}, },
addGroup(state,group){ addGroup(state, group) {
state.groups.unshift(group); state.groups.unshift(group);
}, },
removeGroup(state,groupId){ removeGroup(state, groupId) {
state.groups.forEach((g,index)=>{ state.groups.forEach((g, index) => {
if(g.id==groupId){ if (g.id == groupId) {
state.groups.splice(index, 1); state.groups.splice(index, 1);
if(state.activeIndex >= state.groups.length){ if (state.activeIndex >= state.groups.length) {
state.activeIndex = state.groups.length-1; state.activeIndex = state.groups.length - 1;
} }
} }
}) })
}, },
updateGroup(state,group){ updateGroup(state, group) {
state.groups.forEach((g,idx)=>{ state.groups.forEach((g, idx) => {
if(g.id==group.id){ if (g.id == group.id) {
// 拷贝属性 // 拷贝属性
Object.assign(state.groups[idx], group); Object.assign(state.groups[idx], group);
} }
}) })
},
clear(state){
state.groups = [];
state.activeGroup = -1;
}
},
actions: {
loadGroup(context) {
return new Promise((resolve, reject) => {
http({
url: '/group/list',
method: 'GET'
}).then((groups) => {
context.commit("setGroups", groups);
console.log("loadGroup")
resolve();
}).catch((res) => {
reject(res);
})
});
} }
} }
} }

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

@ -5,27 +5,28 @@ import friendStore from './friendStore.js';
import userStore from './userStore.js'; import userStore from './userStore.js';
import groupStore from './groupStore.js'; import groupStore from './groupStore.js';
import uiStore from './uiStore.js'; import uiStore from './uiStore.js';
import VuexPersistence from 'vuex-persist'
const vuexLocal = new VuexPersistence({
storage: window.localStorage,
modules: ["userStore","chatStore"]
})
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
modules: {chatStore,friendStore,userStore,groupStore,uiStore}, modules: {chatStore,friendStore,userStore,groupStore,uiStore},
state: {}, state: {},
plugins: [vuexLocal.plugin],
mutations: { mutations: {
initStore(state){ },
this.commit("initFriendStore"); actions: {
this.commit("initGroupStore"); load(context) {
this.commit("initChatStore"); console.log("load")
return this.dispatch("loadUser").then(() => {
const promises = [];
promises.push(this.dispatch("loadFriend"));
promises.push(this.dispatch("loadGroup"));
promises.push(this.dispatch("loadChat"));
return Promise.all(promises);
})
},
unload(context){
context.commit("clear");
} }
}, },
strict: process.env.NODE_ENV !== 'production' strict: process.env.NODE_ENV !== 'production'
}) })

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

@ -20,7 +20,6 @@ export default {
}, },
videoAcceptor:{ // 视频呼叫选择 videoAcceptor:{ // 视频呼叫选择
show: false, show: false,
friend:{} friend:{}
} }

28
im-ui/src/store/userStore.js

@ -1,4 +1,5 @@
import {USER_STATE} from "../api/enums.js" import {USER_STATE} from "../api/enums.js"
import http from '../api/httpRequest.js'
export default { export default {
@ -11,16 +12,29 @@ export default {
mutations: { mutations: {
setUserInfo(state, userInfo) { setUserInfo(state, userInfo) {
// 切换用户后,清理缓存 state.userInfo = userInfo
if(userInfo.id != state.userInfo.id){
console.log("用户切换")
this.commit("resetChatStore");
}
state.userInfo = userInfo;
}, },
setUserState(state, userState) { setUserState(state, userState) {
state.state = userState; state.state = userState;
}, },
clear(state){
state.userInfo = {};
state.state = USER_STATE.FREE;
}
},
actions:{
loadUser(context){
return new Promise((resolve, reject) => {
http({
url: '/user/self',
method: 'GET'
}).then((userInfo) => {
context.commit("setUserInfo",userInfo);
resolve();
}).catch((res)=>{
reject(res);
});
})
}
} }
} }

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

@ -6,13 +6,13 @@
<el-button slot="append" icon="el-icon-search"></el-button> <el-button slot="append" icon="el-icon-search"></el-button>
</el-input> </el-input>
</div> </div>
<el-scrollbar class="l-chat-list" > <div class="l-chat-loadding" v-if="loading" v-loading="true" element-loading-text="消息接收中..."
element-loading-spinner="el-icon-loading" element-loading-background="#eee">
</div>
<el-scrollbar class="l-chat-list">
<div v-for="(chat,index) in chatStore.chats" :key="index"> <div v-for="(chat,index) in chatStore.chats" :key="index">
<chat-item v-show="chat.showName.startsWith(searchText)" <chat-item v-show="chat.showName.startsWith(searchText)" :chat="chat" :index="index"
:chat="chat" :index="index" @click.native="handleActiveItem(index)" @delete="handleDelItem(index)" @top="handleTop(index)"
@click.native="handleActiveItem(index)"
@delete="handleDelItem(index)"
@top="handleTop(index)"
:active="index === chatStore.activeIndex"></chat-item> :active="index === chatStore.activeIndex"></chat-item>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -70,6 +70,9 @@
messages: [] messages: []
} }
return emptyChat; return emptyChat;
},
loading(){
return this.chatStore.loadingGroupMsg || this.chatStore.loadingPrivateMsg
} }
} }
} }
@ -90,7 +93,12 @@
line-height: 50px; line-height: 50px;
} }
.l-friend-ist{ .l-chat-loadding{
height: 50px;
background-color: #eee;
}
.l-friend-ist {
flex: 1; flex: 1;
} }
} }

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

@ -3,8 +3,8 @@
<el-aside width="80px" class="navi-bar"> <el-aside width="80px" class="navi-bar">
<div class="user-head-image"> <div class="user-head-image">
<head-image :name="$store.state.userStore.userInfo.nickName" <head-image :name="$store.state.userStore.userInfo.nickName"
:url="$store.state.userStore.userInfo.headImageThumb" :url="$store.state.userStore.userInfo.headImageThumb" :size="60"
:size="60" @click.native="showSettingDialog=true"> @click.native="showSettingDialog=true">
</head-image> </head-image>
</div> </div>
@ -39,13 +39,16 @@
<router-view></router-view> <router-view></router-view>
</el-main> </el-main>
<setting :visible="showSettingDialog" @close="closeSetting()"></setting> <setting :visible="showSettingDialog" @close="closeSetting()"></setting>
<user-info v-show="uiStore.userInfo.show" :pos="uiStore.userInfo.pos" :user="uiStore.userInfo.user" @close="$store.commit('closeUserInfoBox')"></user-info> <user-info v-show="uiStore.userInfo.show" :pos="uiStore.userInfo.pos" :user="uiStore.userInfo.user"
<full-image :visible="uiStore.fullImage.show" :url="uiStore.fullImage.url" @close="$store.commit('closeFullImageBox')"></full-image> @close="$store.commit('closeUserInfoBox')"></user-info>
<chat-private-video ref="privateVideo" :visible="uiStore.chatPrivateVideo.show" :friend="uiStore.chatPrivateVideo.friend" <full-image :visible="uiStore.fullImage.show" :url="uiStore.fullImage.url"
:master="uiStore.chatPrivateVideo.master" :offer="uiStore.chatPrivateVideo.offer" @close="$store.commit('closeChatPrivateVideoBox')"> @close="$store.commit('closeFullImageBox')"></full-image>
<chat-private-video ref="privateVideo" :visible="uiStore.chatPrivateVideo.show"
:friend="uiStore.chatPrivateVideo.friend" :master="uiStore.chatPrivateVideo.master"
:offer="uiStore.chatPrivateVideo.offer" @close="$store.commit('closeChatPrivateVideoBox')">
</chat-private-video> </chat-private-video>
<chat-video-acceptor ref="videoAcceptor" v-show="uiStore.videoAcceptor.show" :friend="uiStore.videoAcceptor.friend" <chat-video-acceptor ref="videoAcceptor" v-show="uiStore.videoAcceptor.show"
@close="$store.commit('closeVideoAcceptorBox')"> :friend="uiStore.videoAcceptor.friend" @close="$store.commit('closeVideoAcceptorBox')">
</chat-video-acceptor> </chat-video-acceptor>
</el-container> </el-container>
</template> </template>
@ -75,15 +78,15 @@
} }
}, },
methods: { methods: {
init(userInfo) { init() {
this.$store.commit("setUserInfo", userInfo); this.$store.dispatch("load").then(() => {
this.$store.commit("setUserState", this.$enums.USER_STATE.FREE); //
this.$store.commit("initStore"); this.loadPrivateMessage(this.$store.state.chatStore.privateMsgMaxId);
this.loadGroupMessage(this.$store.state.chatStore.groupMsgMaxId);
// ws
this.$wsApi.init(process.env.VUE_APP_WS_URL, sessionStorage.getItem("accessToken")); this.$wsApi.init(process.env.VUE_APP_WS_URL, sessionStorage.getItem("accessToken"));
this.$wsApi.connect(); this.$wsApi.connect();
this.$wsApi.onOpen(() => { this.$wsApi.onOpen();
this.pullUnreadMessage();
});
this.$wsApi.onMessage((cmd, msgInfo) => { this.$wsApi.onMessage((cmd, msgInfo) => {
if (cmd == 2) { if (cmd == 2) {
// 线 // 线
@ -92,63 +95,90 @@
location.href = "/"; location.href = "/";
}, 1000) }, 1000)
} else if (cmd == 3) { } else if (cmd == 3) {
//
msgInfo.selfSend = msgInfo.sendId==this.$store.state.userStore.userInfo.id;
// //
this.handlePrivateMessage(msgInfo); this.handlePrivateMessage(msgInfo);
} else if (cmd == 4) { } else if (cmd == 4) {
//
msgInfo.selfSend = msgInfo.sendId==this.$store.state.userStore.userInfo.id;
// //
this.handleGroupMessage(msgInfo); this.handleGroupMessage(msgInfo);
} }
}) })
this.$wsApi.onClose((e) => { this.$wsApi.onClose((e) => {
console.log(e); console.log(e);
if(e.code == 1006){ if (e.code == 1006) {
// //
this.$message.error("连接已断开,请重新登录"); this.$message.error("连接已断开,请重新登录");
location.href = "/"; location.href = "/";
}else{ } else {
this.$wsApi.connect(); this.$wsApi.connect();
} }
}); });
}).catch((e) => {
console.log("初始化失败",e);
})
}, },
pullUnreadMessage() { loadPrivateMessage(minId) {
// this.$store.commit("loadingPrivateMsg",true)
this.$http({ this.$http({
url: "/message/private/pullUnreadMessage", url: "/message/private/loadMessage?minId=" + minId,
method: 'post' method: 'get'
}); }).then((msgInfos) => {
// msgInfos.forEach((msgInfo) => {
this.handlePrivateMessage(msgInfo);
})
if (msgInfos.length == 100) {
//
this.loadPrivateMessage(msgInfos[99].id);
}else{
this.$store.commit("loadingPrivateMsg",false)
}
})
},
loadGroupMessage(minId) {
this.$store.commit("loadingGroupMsg",true)
this.$http({ this.$http({
url: "/message/group/pullUnreadMessage", url: "/message/group/loadMessage?minId=" + minId,
method: 'post' method: 'get'
}); }).then((msgInfos) => {
msgInfos.forEach((msgInfo) => {
this.handleGroupMessage(msgInfo);
})
if (msgInfos.length == 100) {
//
this.loadGroupMessage(msgInfos[99].id);
}else{
this.$store.commit("loadingGroupMsg",false)
}
})
}, },
handlePrivateMessage(msg) { handlePrivateMessage(msg) {
// //
let friendId = msg.selfSend?msg.recvId:msg.sendId; msg.selfSend = msg.sendId == this.$store.state.userStore.userInfo.id;
let friend = this.$store.state.friendStore.friends.find((f) => f.id == friendId); // id
if (friend) { let friendId = msg.selfSend ? msg.recvId : msg.sendId;
this.insertPrivateMessage(friend, msg); //
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)
}
return; return;
} }
//
this.$http({ this.loadFriendInfo(friendId).then((friend) => {
url: `/friend/find/${msg.sendId}`,
method: 'get'
}).then((friend) => {
this.insertPrivateMessage(friend, msg); this.insertPrivateMessage(friend, msg);
this.$store.commit("addFriend", friend);
}) })
}, },
insertPrivateMessage(friend, msg) { insertPrivateMessage(friend, msg) {
// webrtc // webrtc
if (msg.type >= this.$enums.MESSAGE_TYPE.RTC_CALL && if (msg.type >= this.$enums.MESSAGE_TYPE.RTC_CALL &&
msg.type <= this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) { msg.type <= this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) {
// //
if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CALL || if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CALL ||
msg.type == this.$enums.MESSAGE_TYPE.RTC_CANCEL) { msg.type == this.$enums.MESSAGE_TYPE.RTC_CANCEL) {
@ -176,19 +206,22 @@
}, },
handleGroupMessage(msg) { handleGroupMessage(msg) {
// //
let group = this.$store.state.groupStore.groups.find((g) => g.id == msg.groupId); msg.selfSend = msg.sendId == this.$store.state.userStore.userInfo.id;
if (group) { let groupId = msg.groupId;
this.insertGroupMessage(group, msg); //
if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
//
let chatInfo = {
type: 'GROUP',
targetId: groupId
}
this.$store.commit("resetUnreadCount", chatInfo)
return; return;
} }
// this.loadGroupInfo(groupId).then((group) => {
this.$http({ //
url: `/group/find/${msg.groupId}`,
method: 'get'
}).then((group) => {
this.insertGroupMessage(group, msg); this.insertGroupMessage(group, msg);
this.$store.commit("addGroup", group);
}) })
}, },
insertGroupMessage(group, msg) { insertGroupMessage(group, msg) {
@ -221,6 +254,38 @@
}, },
closeSetting() { closeSetting() {
this.showSettingDialog = false; this.showSettingDialog = false;
},
loadFriendInfo(id) {
return new Promise((resolve, reject) => {
let friend = this.$store.state.friendStore.friends.find((f) => f.id == id);
if (friend) {
resolve(friend);
} else {
this.$http({
url: `/friend/find/${id}`,
method: 'get'
}).then((friend) => {
this.$store.commit("addFriend", friend);
resolve(friend)
})
}
});
},
loadGroupInfo(id) {
return new Promise((resolve, reject) => {
let group = this.$store.state.groupStore.groups.find((g) => g.id == id);
if (group) {
resolve(group);
} else {
this.$http({
url: `/group/find/${id}`,
method: 'get'
}).then((group) => {
resolve(group)
this.$store.commit("addGroup", group);
})
}
});
} }
}, },
computed: { computed: {
@ -246,12 +311,7 @@
} }
}, },
mounted() { mounted() {
this.$http({ this.init();
url: "/user/self",
methods: 'get'
}).then((userInfo) => {
this.init(userInfo);
})
}, },
unmounted() { unmounted() {
this.$wsApi.close(); this.$wsApi.close();

Loading…
Cancel
Save