diff --git a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java index 1d3dc87..f4d0b83 100644 --- a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java +++ b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java @@ -14,8 +14,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; @Service public class IMSender { @@ -34,7 +32,7 @@ public class IMSender { Integer serverId = (Integer)redisTemplate.opsForValue().get(key); // 如果对方在线,将数据存储至redis,等待拉取推送 if (serverId != null) { - String sendKey = String.join(":", IMRedisKey.IM_UNREAD_PRIVATE_QUEUE, serverId.toString()); + String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString()); IMRecvInfo recvInfo = new IMRecvInfo(); recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code()); recvInfo.setSendResult(message.getSendResult()); @@ -63,7 +61,7 @@ public class IMSender { Integer serverId = (Integer)redisTemplate.opsForValue().get(key); // 如果终端在线,将数据存储至redis,等待拉取推送 if (serverId != null) { - String sendKey = String.join(":", IMRedisKey.IM_UNREAD_PRIVATE_QUEUE, serverId.toString()); + String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString()); IMRecvInfo recvInfo = new IMRecvInfo(); // 自己的消息不需要回推消息结果 recvInfo.setSendResult(false); @@ -112,7 +110,7 @@ public class IMSender { recvInfo.setSendResult(message.getSendResult()); recvInfo.setData(message.getData()); // 推送至队列 - String key = String.join(":", IMRedisKey.IM_UNREAD_GROUP_QUEUE, entry.getKey().toString()); + String key = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, entry.getKey().toString()); redisTemplate.opsForList().rightPush(key, recvInfo); } // 对离线用户回复消息状态 @@ -144,7 +142,7 @@ public class IMSender { // 自己的消息不需要回推消息结果 recvInfo.setSendResult(false); recvInfo.setData(message.getData()); - String sendKey = String.join(":", IMRedisKey.IM_UNREAD_GROUP_QUEUE, serverId.toString()); + String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, serverId.toString()); redisTemplate.opsForList().rightPush(sendKey, recvInfo); } } diff --git a/im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java b/im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java index a3d7ad4..a40f8d0 100644 --- a/im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java +++ b/im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java @@ -7,9 +7,9 @@ public class IMRedisKey { // 用户ID所连接的IM-server的ID public final static String IM_USER_SERVER_ID = "im:user:server_id"; // 未读私聊消息队列 - public final static String IM_UNREAD_PRIVATE_QUEUE = "im:unread:private"; + public final static String IM_MESSAGE_PRIVATE_QUEUE = "im:message:private"; // 未读群聊消息队列 - public final static String IM_UNREAD_GROUP_QUEUE = "im:unread:group"; + public final static String IM_MESSAGE_GROUP_QUEUE = "im:message:group"; // 私聊消息发送结果队列 public final static String IM_RESULT_PRIVATE_QUEUE = "im:result:private"; // 群聊消息发送结果队列 diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java index 6ae35de..2311468 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java @@ -1,7 +1,9 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum IMCmdType { LOGIN(0,"登陆"), @@ -11,15 +13,10 @@ public enum IMCmdType { GROUP_MESSAGE(4,"群发消息"); - private Integer code; private String desc; - IMCmdType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } public static IMCmdType fromCode(Integer code){ for (IMCmdType typeEnum:values()) { @@ -31,10 +28,6 @@ public enum IMCmdType { } - public String description() { - return desc; - } - public Integer code(){ return this.code; } diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java index 6ec1828..c0de103 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java @@ -1,5 +1,8 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; + +@AllArgsConstructor public enum IMListenerType{ ALL(0,"全部消息"), PRIVATE_MESSAGE(1,"私聊消息"), @@ -9,15 +12,6 @@ public enum IMListenerType{ private String desc; - IMListenerType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - - public String description() { - return desc; - } public Integer code(){ diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java index 37de1d7..5189a2f 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java @@ -1,6 +1,8 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum IMSendCode { SUCCESS(0,"发送成功"), @@ -11,11 +13,6 @@ public enum IMSendCode { private Integer code; private String desc; - // 构造方法 - IMSendCode(int code, String desc) { - this.code = code; - this.desc = desc; - } public static IMSendCode fromCode(Integer code){ for (IMSendCode typeEnum:values()) { @@ -27,9 +24,6 @@ public enum IMSendCode { } - public String description() { - return desc; - } public Integer code(){ diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java index 7ca76f1..d0f4b81 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java @@ -1,9 +1,12 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; + import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +@AllArgsConstructor public enum IMTerminalType { WEB(0,"web"), @@ -13,10 +16,6 @@ public enum IMTerminalType { private String desc; - IMTerminalType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } public static IMTerminalType fromCode(Integer code){ for (IMTerminalType typeEnum:values()) { @@ -31,10 +30,6 @@ public enum IMTerminalType { return Arrays.stream(values()).map(IMTerminalType::code).collect(Collectors.toList()); } - public String description() { - return desc; - } - public Integer code(){ return this.code; } diff --git a/im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java b/im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java index 9bbc905..2c298ca 100644 --- a/im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java +++ b/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 lombok.Data; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; @Data @@ -14,9 +16,9 @@ public class IMGroupMessage { private IMUserInfo sender; /** - * 接收者id列表(群成员列表) + * 接收者id列表(群成员列表,为空则不会推送) */ - private List recvIds; + private List recvIds = Collections.EMPTY_LIST; /** diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java index 8d57a81..73abf24 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java @@ -6,6 +6,7 @@ import com.bx.implatform.result.Result; import com.bx.implatform.result.ResultUtils; import com.bx.implatform.service.IGroupMessageService; import com.bx.implatform.dto.GroupMessageDTO; +import com.bx.implatform.vo.PrivateMessageVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +39,7 @@ public class GroupMessageController { return ResultUtils.success(); } + // todo 删除 @PostMapping("/pullUnreadMessage") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息") public Result pullUnreadMessage(){ @@ -45,6 +47,22 @@ public class GroupMessageController { return ResultUtils.success(); } + @GetMapping("/loadMessage") + @ApiOperation(value = "拉取消息",notes="拉取消息,一次最多拉取100条") + public Result> 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") @ApiOperation(value = "查询聊天记录",notes="查询聊天记录") public Result> recallMessage(@NotNull(message = "群聊id不能为空") @RequestParam Long groupId, diff --git a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java index cde3c38..ea55123 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java @@ -37,7 +37,7 @@ public class PrivateMessageController { return ResultUtils.success(); } - + // todo 删除 @PostMapping("/pullUnreadMessage") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息") public Result pullUnreadMessage(){ @@ -46,6 +46,19 @@ public class PrivateMessageController { } + @GetMapping("/loadMessage") + @ApiOperation(value = "拉取消息",notes="拉取消息,一次最多拉取100条") + public Result> 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") @ApiOperation(value = "查询聊天记录",notes="查询聊天记录") public Result> recallMessage(@NotNull(message = "好友id不能为空") @RequestParam Long friendId, diff --git a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java index 22222c0..1c7cf09 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java @@ -28,12 +28,6 @@ public class UserController { private IUserService userService; - @GetMapping("/online") - @ApiOperation(value = "判断用户是否在线",notes="返回在线的用户id集合") - public Result checkOnline(@NotEmpty @RequestParam("userIds") String userIds){ - List onlineIds = userService.checkOnline(userIds); - return ResultUtils.success(onlineIds); - } @GetMapping("/terminal/online") @ApiOperation(value = "判断用户哪个终端在线",notes="返回在线的用户id的终端集合") @@ -54,7 +48,7 @@ public class UserController { @GetMapping("/find/{id}") @ApiOperation(value = "查找用户",notes="根据id查找用户") - public Result findById(@NotEmpty @PathVariable("id") Long id){ + public Result findById(@NotEmpty @PathVariable("id") Long id){ return ResultUtils.success(userService.findUserById(id)); } diff --git a/im-platform/src/main/java/com/bx/implatform/enums/FileType.java b/im-platform/src/main/java/com/bx/implatform/enums/FileType.java index dc2071f..912029b 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/FileType.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/FileType.java @@ -1,5 +1,8 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; + +@AllArgsConstructor public enum FileType { FILE(0,"文件"), @@ -13,15 +16,6 @@ public enum FileType { private final String desc; - FileType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - - public String description() { - return desc; - } public Integer code(){ return this.code; diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java index 8a7968e..3707a7f 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java @@ -1,24 +1,19 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum MessageStatus { - UNREAD(0,"未读"), - ALREADY_READ(1,"已读"), - RECALL(2,"已撤回"); + UNSEND(0,"未送达"), + SENDED(1,"送达"), + RECALL(2,"撤回"), + READED(3,"已读"); private final Integer code; private final String desc; - MessageStatus(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - public String description() { - return desc; - } public Integer code(){ return this.code; diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java index 5e6d2e8..559531c 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java @@ -1,6 +1,8 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum MessageType { TEXT(0,"文字"), @@ -9,6 +11,7 @@ public enum MessageType { AUDIO(3,"音频"), VIDEO(4,"视频"), RECALL(10,"撤回"), + READED(11, "已读"), RTC_CALL(101,"呼叫"), RTC_ACCEPT(102,"接受"), @@ -22,14 +25,6 @@ public enum MessageType { private final String desc; - MessageType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - public String description() { - return desc; - } public Integer code(){ return this.code; diff --git a/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java index 8b8a4b0..109ac59 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java @@ -1,5 +1,8 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; +import lombok.Getter; + /** * 响应码枚举 * @@ -7,6 +10,8 @@ package com.bx.implatform.enums; * @date 2020/10/19 * **/ +@Getter +@AllArgsConstructor public enum ResultCode { SUCCESS(200,"成功"), NO_LOGIN(400,"未登录"), @@ -21,24 +26,6 @@ public enum ResultCode { private int code; 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; - } } diff --git a/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java index a99522c..0648a13 100644 --- a/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java +++ b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java @@ -22,6 +22,7 @@ public class GroupMessageListener implements MessageListener { @Override public void process(IMSendResult result){ GroupMessageVO messageInfo = result.getData(); + // todo 删除 // 保存该用户已拉取的最大消息id if(result.getCode().equals(IMSendCode.SUCCESS.code())) { String key = String.join(":",RedisKey.IM_GROUP_READED_POSITION,messageInfo.getGroupId().toString(),result.getReceiver().getId().toString()); diff --git a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java index 68a6f2b..fe82817 100644 --- a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java +++ b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java @@ -29,8 +29,8 @@ public class PrivateMessageListener implements MessageListener if(result.getCode().equals(IMSendCode.SUCCESS.code())){ UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId()) - .eq(PrivateMessage::getStatus, MessageStatus.UNREAD.code()) - .set(PrivateMessage::getStatus, MessageStatus.ALREADY_READ.code()); + .eq(PrivateMessage::getStatus, MessageStatus.UNSEND.code()) + .set(PrivateMessage::getStatus, MessageStatus.SENDED.code()); privateMessageService.update(updateWrapper); log.info("消息已读,消息id:{},发送者:{},接收者:{},终端:{}",messageInfo.getId(),result.getSender().getId(),result.getReceiver().getId(),result.getReceiver().getTerminal()); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java index a003afe..304a00c 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java @@ -17,5 +17,9 @@ public interface IGroupMessageService extends IService { void pullUnreadMessage(); + List loadMessage(Long minId); + + void readedMessage(Long groupId); + List findHistoryMessage(Long groupId, Long page, Long size); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java index 0b42d21..f0036e0 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java @@ -18,4 +18,7 @@ public interface IPrivateMessageService extends IService { void pullUnreadMessage(); + List loadMessage(Long minId); + + void readedMessage(Long friendId); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IUserService.java b/im-platform/src/main/java/com/bx/implatform/service/IUserService.java index c675567..af22b5c 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IUserService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IUserService.java @@ -30,8 +30,6 @@ public interface IUserService extends IService { List findUserByName(String name); - List checkOnline(String userIds); - List getOnlineTerminals(String userIds); diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java index c40cf10..3d450a8 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java @@ -79,7 +79,7 @@ public class FriendServiceImpl extends ServiceImpl impleme @Override public void delFriend(Long friendId) { long userId = SessionContext.getSession().getUserId(); - // 互相解除好友关系 + // 互相解除好友关系,走代理清理缓存 FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy(); proxy.unbindFriend(userId,friendId); proxy.unbindFriend(friendId,userId); diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java index c2f992a..5be6caf 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java @@ -1,11 +1,14 @@ package com.bx.implatform.service.impl; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.imclient.IMClient; import com.bx.imcommon.contant.IMConstant; +import com.bx.implatform.entity.PrivateMessage; +import com.bx.implatform.util.DateTimeUtils; import com.bx.implatform.vo.GroupMessageVO; import com.bx.imcommon.model.IMGroupMessage; import com.bx.imcommon.model.IMUserInfo; @@ -26,6 +29,7 @@ import com.bx.implatform.session.UserSession; import com.bx.implatform.util.BeanUtils; import com.bx.implatform.dto.GroupMessageDTO; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -33,6 +37,7 @@ import org.springframework.stereotype.Service; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; @Slf4j @@ -43,12 +48,12 @@ public class GroupMessageServiceImpl extends ServiceImpl redisTemplate; + private RedisTemplate redisTemplate; @Autowired private IMClient imClient; /** - * 发送群聊消息(与mysql所有交换都要进行缓存) + * 发送群聊消息(高并发接口,查询mysql接口都要进行缓存) * * @param dto 群聊消息 * @return 群聊id @@ -57,16 +62,16 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(group.getId()); - if(!userIds.contains(session.getUserId())){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法发送消息"); + if (!userIds.contains(session.getUserId())) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法发送消息"); } // 保存消息 GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class); @@ -74,21 +79,19 @@ public class GroupMessageServiceImpl extends ServiceImpl!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); IMGroupMessage sendMessage = new IMGroupMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvIds(userIds); sendMessage.setData(msgInfo); 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(); } - - /** * 撤回消息 * @@ -98,19 +101,19 @@ public class GroupMessageServiceImpl extends ServiceImpl IMConstant.ALLOW_RECALL_SECOND * 1000){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息已发送超过5分钟,无法撤回"); + if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回"); } // 判断是否在群里 - GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(),session.getUserId()); - if(member == null){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息"); + GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), session.getUserId()); + if (member == null) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法撤回消息"); } // 修改数据库 msg.setStatus(MessageStatus.RECALL.code()); @@ -118,15 +121,15 @@ public class GroupMessageServiceImpl extends ServiceImpl 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); msgInfo.setType(MessageType.RECALL.code()); - String content = String.format("'%s'撤回了一条消息",member.getAliasName()); + String content = String.format("'%s'撤回了一条消息", member.getAliasName()); msgInfo.setContent(content); msgInfo.setSendTime(new Date()); IMGroupMessage sendMessage = new IMGroupMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvIds(userIds); sendMessage.setData(msgInfo); sendMessage.setSendResult(false); @@ -139,40 +142,39 @@ public class GroupMessageServiceImpl extends ServiceImpl members = groupMemberService.findByUserId(session.getUserId()); - for(GroupMember member:members){ + for (GroupMember member : members) { // 获取群聊已读的最大消息id,只推送未读消息 - String key = String.join(":",RedisKey.IM_GROUP_READED_POSITION,member.getGroupId().toString(),session.getUserId().toString()); - Integer maxReadedId = (Integer)redisTemplate.opsForValue().get(key); + String key = String.join(":", RedisKey.IM_GROUP_READED_POSITION, member.getGroupId().toString(), session.getUserId().toString()); + Integer maxReadedId = (Integer) redisTemplate.opsForValue().get(key); LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(GroupMessage::getGroupId,member.getGroupId()) - .gt(GroupMessage::getSendTime,member.getCreatedTime()) + wrapper.eq(GroupMessage::getGroupId, member.getGroupId()) + .gt(GroupMessage::getSendTime, member.getCreatedTime()) .ne(GroupMessage::getSendId, session.getUserId()) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()); - if(maxReadedId!=null){ - wrapper.gt(GroupMessage::getId,maxReadedId); + if (maxReadedId != null) { + wrapper.gt(GroupMessage::getId, maxReadedId); } wrapper.last("limit 100"); List messages = this.list(wrapper); - if(messages.isEmpty()){ + if (messages.isEmpty()) { continue; } // 推送 - for (GroupMessage message:messages ){ + for (GroupMessage message : messages) { GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class); IMGroupMessage 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.setRecvTerminals(Collections.singletonList(session.getTerminal())); @@ -180,8 +182,88 @@ public class GroupMessageServiceImpl extends ServiceImpl loadMessage(Long minId) { + UserSession session = SessionContext.getSession(); + List members = groupMemberService.findByUserId(session.getUserId()); + List ids = members.stream().map(GroupMember::getGroupId).collect(Collectors.toList()); + // 只能拉取最近1个月的 + Date minDate = DateTimeUtils.addMonths(new Date(), -1); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.gt(GroupMessage::getId, minId) + .gt(GroupMessage::getSendTime, minDate) + .in(GroupMessage::getGroupId, ids) + .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()) + .orderByAsc(GroupMessage::getId) + .last("limit 100"); + + List messages = this.list(wrapper); + // 转成vo + List vos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); + // 消息状态,数据库没有存群聊的消息状态,需要从redis取 + List keys = ids.stream() + .map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString())) + .collect(Collectors.toList()); + List 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.READED.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 sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setSendToSelf(true); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + imClient.sendGroupMessage(sendMessage); + + // 记录已读位置 + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(GroupMessage::getGroupId, groupId) + .orderByDesc(GroupMessage::getId) + .last("limit 1") + .select(GroupMessage::getId); + GroupMessage message = this.getOne(wrapper); + String key = StrUtil.join(":",RedisKey.IM_GROUP_READED_POSITION,groupId,session.getUserId()); + redisTemplate.opsForValue().set(key, message.getId()); } @@ -189,32 +271,32 @@ public class GroupMessageServiceImpl extends ServiceImpl findHistoryMessage(Long groupId, Long page, Long size) { - page = page > 0 ? page:1; - size = size > 0 ? size:10; + page = page > 0 ? page : 1; + size = size > 0 ? size : 10; Long userId = SessionContext.getSession().getUserId(); - long stIdx = (page-1)* size; + long stIdx = (page - 1) * size; // 群聊成员信息 - GroupMember member = groupMemberService.findByGroupAndUserId(groupId,userId); - if(member == null || member.getQuit()){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊中"); + GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userId); + if (member == null || member.getQuit()) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊中"); } // 查询聊天记录,只查询加入群聊时间之后的消息 QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(GroupMessage::getGroupId,groupId) - .gt(GroupMessage::getSendTime,member.getCreatedTime()) + wrapper.lambda().eq(GroupMessage::getGroupId, groupId) + .gt(GroupMessage::getSendTime, member.getCreatedTime()) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()) .orderByDesc(GroupMessage::getId) - .last("limit "+stIdx + ","+size); + .last("limit " + stIdx + "," + size); List messages = this.list(wrapper); - List messageInfos = messages.stream().map(m->BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); - log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}",userId,groupId,messageInfos.size()); + List messageInfos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); + log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}", userId, groupId, messageInfos.size()); return messageInfos; } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java index 0093db6..88f34b5 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java @@ -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.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.IMUserInfo; import com.bx.implatform.entity.Friend; +import com.bx.implatform.util.DateTimeUtils; import com.bx.implatform.vo.PrivateMessageVO; import com.bx.implatform.entity.PrivateMessage; import com.bx.implatform.enums.MessageStatus; @@ -25,6 +27,7 @@ import com.bx.implatform.dto.PrivateMessageDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.Collections; import java.util.Date; @@ -40,8 +43,9 @@ public class PrivateMessageServiceImpl extends ServiceImpl sendMessage = new IMPrivateMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(msgInfo.getRecvId()); sendMessage.setSendToSelf(true); sendMessage.setData(msgInfo); @@ -99,7 +103,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl sendMessage = new IMPrivateMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(msgInfo.getRecvId()); sendMessage.setSendToSelf(false); sendMessage.setData(msgInfo); @@ -147,7 +151,6 @@ public class PrivateMessageServiceImpl extends ServiceImpl friends = friendService.findFriendByUserId(session.getUserId()); - if(friends.isEmpty()){ + if (friends.isEmpty()) { return; } List friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList()); // 获取当前用户所有未读消息 LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(PrivateMessage::getRecvId, session.getUserId()) - .eq(PrivateMessage::getStatus, MessageStatus.UNREAD) - .in(PrivateMessage::getSendId,friendIds); + .eq(PrivateMessage::getStatus, MessageStatus.UNSEND) + .in(PrivateMessage::getSendId, friendIds); List messages = this.list(queryWrapper); // 上传至redis,等待推送 - for(PrivateMessage message:messages){ + for (PrivateMessage message : messages) { PrivateMessageVO msgInfo = BeanUtils.copyProperties(message, PrivateMessageVO.class); // 推送消息 IMPrivateMessage sendMessage = new IMPrivateMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(session.getUserId()); sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal())); sendMessage.setSendToSelf(false); @@ -183,4 +186,83 @@ public class PrivateMessageServiceImpl extends ServiceImpl loadMessage(Long minId) { + UserSession session = SessionContext.getSession(); + List friends = friendService.findFriendByUserId(session.getUserId()); + if (friends.isEmpty()) { + return Collections.EMPTY_LIST; + } + List friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList()); + // 获取当前用户的消息 + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + // 只能拉取最近1个月的 + 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))) + .orderByAsc(PrivateMessage::getId) + .last("limit 100"); + + List messages = this.list(queryWrapper); + // 更新发送状态 + List 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 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 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 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); + } } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java index 0d9a8bf..8e905dc 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java @@ -246,18 +246,6 @@ public class UserServiceImpl extends ServiceImpl implements IU }).collect(Collectors.toList()); } - /** - * 判断用户是否在线,返回在线的用户id列表 - * - * @param userIds 用户id,多个用‘,’分割 - * @return 在线用户id列表 - */ - @Override - public List checkOnline(String userIds) { - List userIdList = Arrays.stream(userIds.split(",")) - .map(Long::parseLong).collect(Collectors.toList()); - return imClient.getOnlineUser(userIdList); - } /** diff --git a/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java b/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java index f10cfee..911d940 100644 --- a/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java +++ b/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java @@ -106,7 +106,10 @@ public class MinioUtil { if (StringUtils.isBlank(originalFilename)){ throw new RuntimeException(); } - String fileName = System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf(".")); + String fileName = System.currentTimeMillis()+""; + if(originalFilename.lastIndexOf(".") >= 0){ + fileName +=originalFilename.substring(originalFilename.lastIndexOf(".")); + } String objectName = DateTimeUtils.getFormatDate(new Date(),DateTimeUtils.PARTDATEFORMAT)+ "/" + fileName; try { PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path+"/" +objectName) diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java index 72480e8..f00cccc 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java +++ b/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.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; @@ -9,34 +10,25 @@ import java.util.Date; @Data public class GroupMessageVO { - /* - * 消息id - */ + @ApiModelProperty(value = "消息id") private Long id; - /* - * 群聊id - */ + @ApiModelProperty(value = "群聊id") private Long groupId; - /* - * 发送者id - */ + @ApiModelProperty(value = " 发送者id") private Long sendId; - /* - * 消息内容 - */ + @ApiModelProperty(value = "消息内容") private String content; - /* - * 消息内容类型 具体枚举值由应用层定义 - */ + @ApiModelProperty(value = "消息内容类型 具体枚举值由应用层定义") private Integer type; - /** - * 发送时间 - */ + @ApiModelProperty(value = " 状态") + private Integer status; + + @ApiModelProperty(value = "发送时间") @JsonSerialize(using = DateToLongSerializer.class) private Date sendTime; } diff --git a/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java index 1b7c41a..431dc9a 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java +++ b/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.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; @Data +@ApiModel("私聊消息VO") public class PrivateMessageVO { - /* - * 消息id - */ + @ApiModelProperty(value = " 消息id") private long id; - /* - * 发送者id - */ + @ApiModelProperty(value = " 发送者id") private Long sendId; - /* - * 接收者id - */ + @ApiModelProperty(value = " 接收者id") private Long recvId; - /* - * 发送内容 - */ + @ApiModelProperty(value = " 发送内容") private String content; - /* - * 消息内容类型 IMCmdType - */ + @ApiModelProperty(value = "消息内容类型 IMCmdType") private Integer type; - /** - * 发送时间 - */ + @ApiModelProperty(value = " 状态") + private Integer status; + + @ApiModelProperty(value = " 发送时间") @JsonSerialize(using = DateToLongSerializer.class) private Date sendTime; } diff --git a/im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java b/im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java index 1e89d73..ddf9f74 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java +++ b/im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java @@ -59,7 +59,7 @@ public class TcpSocketServer implements IMServer { protected void initChannel(Channel ch) throws Exception { // 获取职责链 ChannelPipeline pipeline = ch.pipeline(); - pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS)); + pipeline.addLast(new IdleStateHandler(120, 0, 0, TimeUnit.SECONDS)); pipeline.addLast("encode",new MessageProtocolEncoder()); pipeline.addLast("decode",new MessageProtocolDecoder()); pipeline.addLast("handler", new IMChannelHandler()); diff --git a/im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java b/im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java index efd4d6e..3a0fe93 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java +++ b/im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java @@ -63,7 +63,7 @@ public class WebSocketServer implements IMServer { protected void initChannel(Channel ch) throws Exception { // 获取职责链 ChannelPipeline pipeline = ch.pipeline(); - pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS)); + pipeline.addLast(new IdleStateHandler(120, 0, 0, TimeUnit.SECONDS)); pipeline.addLast("http-codec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(65535)); pipeline.addLast("http-chunked", new ChunkedWriteHandler()); diff --git a/im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java b/im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java index cfe7044..1787b59 100644 --- a/im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java +++ b/im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java @@ -23,7 +23,7 @@ public class PullUnreadGroupMessageTask extends AbstractPullMessageTask { @Override public void pullMessage() { // 从redis拉取未读消息 - String key = String.join(":", IMRedisKey.IM_UNREAD_GROUP_QUEUE,IMServerGroup.serverId+""); + String key = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE,IMServerGroup.serverId+""); JSONObject jsonObject = (JSONObject)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS); if(jsonObject != null){ IMRecvInfo recvInfo = jsonObject.toJavaObject(IMRecvInfo.class); diff --git a/im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java b/im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java index 4ba5048..4b70ee6 100644 --- a/im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java +++ b/im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java @@ -25,7 +25,7 @@ public class PullUnreadPrivateMessageTask extends AbstractPullMessageTask { @Override public void pullMessage() { // 从redis拉取未读消息 - String key = String.join(":", IMRedisKey.IM_UNREAD_PRIVATE_QUEUE ,IMServerGroup.serverId+""); + String key = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE,IMServerGroup.serverId+""); JSONObject jsonObject = (JSONObject)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS); if(jsonObject!=null){ IMRecvInfo recvInfo = jsonObject.toJavaObject(IMRecvInfo.class); diff --git a/im-ui/src/api/emotion.js b/im-ui/src/api/emotion.js index 89fc18c..86af031 100644 --- a/im-ui/src/api/emotion.js +++ b/im-ui/src/api/emotion.js @@ -15,6 +15,9 @@ let transform = (content) => { let textToImg = (emoText) => { let word = emoText.replace(/\#|\;/gi, ''); let idx = emoTextList.indexOf(word); + if(idx==-1){ + return ""; + } let url = require(`@/assets/emoji/${idx}.gif`); return `` } diff --git a/im-ui/src/api/enums.js b/im-ui/src/api/enums.js index 27bfb78..d5f2390 100644 --- a/im-ui/src/api/enums.js +++ b/im-ui/src/api/enums.js @@ -6,6 +6,7 @@ const MESSAGE_TYPE = { AUDIO:3, VIDEO:4, RECALL:10, + READED:11, TIP_TIME:20, RTC_CALL: 101, RTC_ACCEPT: 102, @@ -27,8 +28,17 @@ const TERMINAL_TYPE = { APP: 1 } +const MESSAGE_STATUS = { + UNSEND: 0, + SENDED: 1, + RECALL:2, + READED:3 +} + + export { MESSAGE_TYPE, USER_STATE, - TERMINAL_TYPE + TERMINAL_TYPE, + MESSAGE_STATUS } diff --git a/im-ui/src/components/chat/ChatBox.vue b/im-ui/src/components/chat/ChatBox.vue index fe5b806..e4f06b4 100644 --- a/im-ui/src/components/chat/ChatBox.vue +++ b/im-ui/src/components/chat/ChatBox.vue @@ -2,17 +2,19 @@ {{title}} - + - +
  • - +
@@ -20,31 +22,44 @@
-
+
- +
- +
-
+
- -
- 发送 +
+ + +
+
+ + +
+
+
+ 发送 +
@@ -56,7 +71,8 @@ - + @@ -89,6 +105,8 @@ group: {}, groupMembers: [], sendText: "", + sendImageUrl: "", + sendImageFile: "", showVoice: false, // 是否显示语音录制弹窗 showSide: false, // 是否显示群聊信息栏 showEmotion: false, // 是否显示emoji表情 @@ -97,13 +115,34 @@ y: 0 }, showHistory: false, // 是否显示历史聊天记录 - lockMessage: false // 是否锁定发送 + lockMessage: false, // 是否锁定发送, + showMinIdx: 0 // 下标低于showMinIdx的消息不显示,否则页面会很卡 } }, methods: { - handleImageSuccess(res, file) { - let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); - msgInfo.content = JSON.stringify(res.data); + handlePaste(e) { + let txt = event.clipboardData.getData('Text') + if (typeof(txt) == 'string') { + this.sendText += txt + } + const items = (event.clipboardData || window.clipboardData).items + if (items.length) { + for (let i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + let file = items[i].getAsFile(); + this.sendImageFile = file; + this.sendImageUrl = URL.createObjectURL(file); + } + } + } + }, + removeSendImage() { + this.sendImageUrl = ""; + this.sendImageFile = null; + }, + handleImageSuccess(data, file) { + let msgInfo = JSON.parse(JSON.stringify(file.msgInfo || file.raw.msgInfo)); + msgInfo.content = JSON.stringify(data); this.$http({ url: this.messageAction, method: 'post', @@ -114,8 +153,8 @@ this.$store.commit("insertMessage", msgInfo); }) }, - handleImageFail(res, file) { - let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); + handleImageFail(e, file) { + let msgInfo = JSON.parse(JSON.stringify(file.msgInfo || file.raw.msgInfo)); msgInfo.loadStatus = 'fail'; this.$store.commit("insertMessage", msgInfo); }, @@ -133,7 +172,8 @@ sendTime: new Date().getTime(), selfSend: true, type: 1, - loadStatus: "loading" + loadStatus: "loading", + status: this.$enums.MESSAGE_STATUS.UNSEND } // 填充对方id this.fillTargetId(msgInfo, this.chat.targetId); @@ -144,11 +184,11 @@ // 借助file对象保存 file.msgInfo = msgInfo; }, - handleFileSuccess(res, file) { + handleFileSuccess(url, file) { let data = { name: file.name, size: file.size, - url: res.data + url: url } let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); msgInfo.content = JSON.stringify(data); @@ -162,7 +202,8 @@ this.$store.commit("insertMessage", msgInfo); }) }, - handleFileFail(res, file) { + handleFileFail(e, file) { + let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); msgInfo.loadStatus = 'fail'; this.$store.commit("insertMessage", msgInfo); @@ -181,7 +222,8 @@ sendTime: new Date().getTime(), selfSend: true, type: 2, - loadStatus: "loading" + loadStatus: "loading", + status: this.$enums.MESSAGE_STATUS.UNSEND } // 填充对方id this.fillTargetId(msgInfo, this.chat.targetId); @@ -195,6 +237,18 @@ handleCloseSide() { this.showSide = false; }, + handleScrollToTop() { + // 多展示10条信息 + this.showMinIdx = this.showMinIdx > 10 ? this.showMinIdx - 10 : 0; + }, + handleScroll(e) { + let scrollElement = e.target + let scrollTop = scrollElement.scrollTop + if (scrollTop < 30 ) { // 在顶部,不滚动的情况 + // 多展示20条信息 + this.showMinIdx = this.showMinIdx > 20 ? this.showMinIdx - 20 : 0; + } + }, switchEmotionBox() { this.showEmotion = !this.showEmotion; let width = this.$refs.emotion.offsetWidth; @@ -216,7 +270,6 @@ this.showVoice = false; }, showVideoBox() { - console.log(this.friend) this.$store.commit("showChatPrivateVideoBox", { friend: this.friend, master: true @@ -240,11 +293,11 @@ method: 'post', data: msgInfo }).then((id) => { - this.$message.success("发送成功"); msgInfo.id = id; msgInfo.sendTime = new Date().getTime(); msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.selfSend = true; + msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND; this.$store.commit("insertMessage", msgInfo); // 保持输入框焦点 this.$refs.sendBox.focus(); @@ -261,9 +314,34 @@ msgInfo.recvId = targetId; } }, + handleSendMessage() { + if (this.sendImageFile) { + this.sendImageMessage(); + } else { + this.sendTextMessage(); + } + }, + sendImageMessage() { + let file = this.sendImageFile; + this.handleImageBefore(this.sendImageFile); + let formData = new FormData() + formData.append('file', file.raw || file) + this.$http.post("/image/upload", formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }).then((data) => { + this.handleImageSuccess(data, file); + }).catch((res) => { + this.handleImageSuccess(res, file); + }) + this.sendImageFile = null; + this.sendImageUrl = ""; + this.$nextTick(() => this.$refs.sendBox.focus()); + this.scrollToBottom(); + }, sendTextMessage() { if (!this.sendText.trim()) { - this.$message.error("不能发送空白信息"); return } let msgInfo = { @@ -278,12 +356,12 @@ method: 'post', data: msgInfo }).then((id) => { - this.$message.success("发送成功"); this.sendText = ""; msgInfo.id = id; msgInfo.sendTime = new Date().getTime(); msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.selfSend = true; + msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND; this.$store.commit("insertMessage", msgInfo); }).finally(() => { // 解除锁定 @@ -324,10 +402,24 @@ msgInfo = JSON.parse(JSON.stringify(msgInfo)); msgInfo.type = 10; msgInfo.content = '你撤回了一条消息'; + msgInfo.status = this.$enums.MESSAGE_STATUS.RECALL; 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) { this.$http({ @@ -354,7 +446,6 @@ method: 'get' }).then((friend) => { this.friend = friend; - console.log(this.friend) this.$store.commit("updateChatFromFriend", friend); this.$store.commit("updateFriend", friend); }) @@ -378,7 +469,7 @@ }, scrollToBottom() { this.$nextTick(() => { - const div = document.getElementById("chatScrollBox"); + let div = document.getElementById("chatScrollBox"); div.scrollTop = div.scrollHeight; }); } @@ -403,19 +494,29 @@ }, messageAction() { return `/message/${this.chat.type.toLowerCase()}/send`; + }, + unreadCount() { + return this.chat.unreadCount; } }, watch: { chat: { 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") { this.loadGroup(this.chat.targetId); } else { this.loadFriend(this.chat.targetId); } + // 滚到底部 this.scrollToBottom(); this.sendText = ""; + // 消息已读 + this.readedMessage() + // 初始状态只显示30条消息 + let size = this.chat.messages.length; + this.showMinIdx = size > 30 ? size - 30 : 0; // 保持输入框焦点 this.$nextTick(() => { this.$refs.sendBox.focus(); @@ -423,7 +524,19 @@ } }, immediate: true + }, + unreadCount: { + handler(newCount, oldCount) { + if (newCount > 0) { + // 消息已读 + this.readedMessage() + } + } } + }, + mounted() { + let div = document.getElementById("chatScrollBox"); + div.addEventListener('scroll', this.handleScroll) } } @@ -498,22 +611,65 @@ } } - .send-text-area { - box-sizing: border-box; - padding: 5px; - width: 100%; - flex: 1; - resize: none; - font-size: 16px; - color: black; + .send-content-area { + display: flex; + flex-direction: column; + height: 100%; background-color: #f8f8f8 !important; outline-color: rgba(83, 160, 231, 0.61); - } - .im-chat-send { - text-align: right; - padding: 7px; + .send-text-area { + box-sizing: border-box; + padding: 5px; + width: 100%; + flex: 1; + resize: none; + font-size: 16px; + color: black; + background-color: #f8f8f8 !important; + outline-color: rgba(83, 160, 231, 0.61); + + } + + .send-image-area { + text-align: left; + + .send-image-box { + position: relative; + display: inline-block; + + .send-image { + max-height: 190px; + border: 1px solid #ccc; + border-radius: 2%; + margin: 2px; + } + + .send-image-close { + position: absolute; + padding: 3px; + right: 7px; + top: 7px; + color: white; + cursor: pointer; + font-size: 15px; + font-weight: 600; + background-color: #aaa; + border-radius: 50%; + border: 1px solid #ccc; + } + } + + } + + .send-btn-area { + padding: 10px; + position: absolute; + bottom: 0; + right: 0; + } } + } .chat-group-side-box { @@ -521,4 +677,4 @@ animation: rtl-drawer-in .3s 1ms; } } - + \ No newline at end of file diff --git a/im-ui/src/components/chat/ChatMessageItem.vue b/im-ui/src/components/chat/ChatMessageItem.vue index 58a0f6e..b1a2e41 100644 --- a/im-ui/src/components/chat/ChatMessageItem.vue +++ b/im-ui/src/components/chat/ChatMessageItem.vue @@ -1,11 +1,13 @@