Merge remote-tracking branch 'origin/friend' into friend

# Conflicts:
#	mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserApplyDao.java
#	mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserFriendDao.java
#	mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/FriendServiceImpl.java
This commit is contained in:
limeng
2023-07-23 11:27:55 +08:00
76 changed files with 1726 additions and 308 deletions

View File

@@ -1,6 +1,7 @@
package com.abin.mallchat.custom.chat.controller;
import com.abin.mallchat.common.chat.domain.dto.MsgReadInfoDTO;
import com.abin.mallchat.common.common.annotation.FrequencyControl;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
@@ -19,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -51,7 +53,6 @@ public class ChatController {
@ApiOperation("群成员列表")
@FrequencyControl(time = 120, count = 20, target = FrequencyControl.Target.IP)
public ApiResult<CursorPageBaseResp<ChatMemberResp>> getMemberPage(@Valid CursorPageBaseReq request) {
// black(request);
CursorPageBaseResp<ChatMemberResp> memberPage = chatService.getMemberPage(request);
filterBlackMember(memberPage);
return ApiResult.success(memberPage);
@@ -59,6 +60,7 @@ public class ChatController {
@GetMapping("/member/list")
@ApiOperation("房间内的所有群成员列表-@专用")
@Deprecated
public ApiResult<List<ChatMemberListResp>> getMemberList(@Valid ChatMessageMemberReq chatMessageMemberReq) {
return ApiResult.success(chatService.getMemberList(chatMessageMemberReq));
}
@@ -74,6 +76,7 @@ public class ChatController {
@GetMapping("public/member/statistic")
@ApiOperation("群成员人数统计")
@Deprecated
public ApiResult<ChatMemberStatisticResp> getMemberStatistic() {
return ApiResult.success(chatService.getMemberStatistic());
}
@@ -85,7 +88,6 @@ public class ChatController {
@ApiOperation("消息列表")
@FrequencyControl(time = 120, count = 20, target = FrequencyControl.Target.IP)
public ApiResult<CursorPageBaseResp<ChatMessageResp>> getMsgPage(@Valid ChatMessagePageReq request) {
// black(request);
CursorPageBaseResp<ChatMessageResp> msgPage = chatService.getMsgPage(request, RequestHolder.get().getUid());
filterBlackMsg(msgPage);
return ApiResult.success(msgPage);
@@ -101,7 +103,7 @@ public class ChatController {
@FrequencyControl(time = 5, count = 3, target = FrequencyControl.Target.UID)
@FrequencyControl(time = 30, count = 5, target = FrequencyControl.Target.UID)
@FrequencyControl(time = 60, count = 10, target = FrequencyControl.Target.UID)
public ApiResult<ChatMessageResp> sendMsg(@Valid @RequestBody ChatMessageReq request) {
public ApiResult<ChatMessageResp> sendMsg(@Valid @RequestBody ChatMessageReq request) {//todo 发送给单聊
Long msgId = chatService.sendMsg(request, RequestHolder.get().getUid());
//返回完整消息格式,方便前端展示
return ApiResult.success(chatService.getMsgResp(msgId, RequestHolder.get().getUid()));
@@ -122,5 +124,19 @@ public class ChatController {
chatService.recallMsg(RequestHolder.get().getUid(), request);
return ApiResult.success();
}
@GetMapping("/msg/read/page")
@ApiOperation("消息的已读未读列表")
public ApiResult<CursorPageBaseResp<ChatMessageReadResp>> getReadPage(@Valid ChatMessageReadReq request) {
Long uid = RequestHolder.get().getUid();
return ApiResult.success(chatService.getReadPage(uid, request));
}
@GetMapping("/msg/read")
@ApiOperation("获取消息的已读未读总数")
public ApiResult<Collection<MsgReadInfoDTO>> getReadInfo(@Valid ChatMessageReadInfoReq request) {
Long uid = RequestHolder.get().getUid();
return ApiResult.success(chatService.getMsgReadInfo(uid, request));
}
}

View File

@@ -0,0 +1,48 @@
package com.abin.mallchat.custom.chat.controller;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
import com.abin.mallchat.common.common.utils.RequestHolder;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
import com.abin.mallchat.custom.chat.service.ChatService;
import com.abin.mallchat.custom.chat.service.RoomService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* <p>
* 会话相关接口
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-03-19
*/
@RestController
@RequestMapping("/capi/chat")
@Api(tags = "聊天室相关接口")
@Slf4j
public class ContactController {
@Autowired
private ChatService chatService;
@Autowired
private RoomService roomService;
@GetMapping("/public/contact/page")
@ApiOperation("会话列表")
public ApiResult<CursorPageBaseResp<ChatRoomResp>> getRoomPage(@Valid CursorPageBaseReq request) {
Long uid = RequestHolder.get().getUid();
return ApiResult.success(roomService.getContactPage(request, uid));
}
}

View File

@@ -0,0 +1,79 @@
package com.abin.mallchat.custom.chat.controller;
import com.abin.mallchat.common.common.annotation.FrequencyControl;
import com.abin.mallchat.common.common.domain.vo.request.IdReqVO;
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
import com.abin.mallchat.common.common.utils.RequestHolder;
import com.abin.mallchat.common.user.service.cache.UserCache;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMemberReq;
import com.abin.mallchat.custom.chat.domain.vo.request.MemberAddReq;
import com.abin.mallchat.custom.chat.domain.vo.request.MemberDelReq;
import com.abin.mallchat.custom.chat.domain.vo.request.MemberReq;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberListResp;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
import com.abin.mallchat.custom.chat.domain.vo.response.MemberResp;
import com.abin.mallchat.custom.chat.service.ChatService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* <p>
* 房间相关接口
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-03-19
*/
@RestController
@RequestMapping("/capi/room")
@Api(tags = "聊天室相关接口")
@Slf4j
public class RoomController {
@Autowired
private ChatService chatService;
@Autowired
private UserCache userCache;
@GetMapping("/public/group")
@ApiOperation("群组详情")
public ApiResult<MemberResp> groupDetail(@Valid IdReqVO request) {
return ApiResult.success();
}
@GetMapping("/public/group/member/page")
@ApiOperation("群成员列表")
@FrequencyControl(time = 120, count = 20, target = FrequencyControl.Target.IP)
public ApiResult<CursorPageBaseResp<ChatMemberResp>> getMemberPage(@Valid MemberReq request) {
CursorPageBaseResp<ChatMemberResp> memberPage = chatService.getMemberPage(request);
return ApiResult.success(memberPage);
}
@GetMapping("/group/member/list")
@ApiOperation("房间内的所有群成员列表-@专用")
public ApiResult<List<ChatMemberListResp>> getMemberList(@Valid ChatMessageMemberReq request) {
return ApiResult.success(chatService.getMemberList(request));
}
@DeleteMapping("/group/member")
@ApiOperation("移除成员")
public ApiResult<Void> delMember(@Valid @RequestBody MemberDelReq request) {
Long uid = RequestHolder.get().getUid();
return ApiResult.success();
}
@PostMapping("/group/member")
@ApiOperation("邀请好友")
public ApiResult<List<ChatMemberListResp>> addMember(@Valid @RequestBody MemberAddReq request) {
Long uid = RequestHolder.get().getUid();
return ApiResult.success();
}
}

View File

@@ -0,0 +1,25 @@
package com.abin.mallchat.custom.chat.domain.vo.request;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Size;
import java.util.List;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-17
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessageReadInfoReq {
@ApiModelProperty("消息id集合只查本人")
@Size(max = 20)
private List<Long> msgIds;
}

View File

@@ -0,0 +1,29 @@
package com.abin.mallchat.custom.chat.domain.vo.request;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-17
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessageReadReq extends CursorPageBaseReq {
@ApiModelProperty("消息id")
@NotNull
private Long msgId;
@ApiModelProperty("查询类型 1已读 2未读")
@NotNull
private Long searchType;
}

View File

@@ -24,7 +24,7 @@ import javax.validation.constraints.NotNull;
@NoArgsConstructor
public class ChatMessageReq {
@NotNull
@ApiModelProperty("会话id")
@ApiModelProperty("房间id")
private Long roomId;
@ApiModelProperty("消息类型")

View File

@@ -0,0 +1,31 @@
package com.abin.mallchat.custom.chat.domain.vo.request;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
/**
* Description: 移除群成员
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-29
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberAddReq {
@NotNull
@ApiModelProperty("会话id")
private Long roomId;
@NotNull
@Size(min = 1, max = 50)
@ApiModelProperty("邀请的uid")
private List<Long> uidList;
}

View File

@@ -0,0 +1,28 @@
package com.abin.mallchat.custom.chat.domain.vo.request;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* Description: 移除群成员
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-29
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberDelReq {
@NotNull
@ApiModelProperty("会话id")
private Long roomId;
@NotNull
@ApiModelProperty("被移除的uid主动退群填自己")
private Long uid;
}

View File

@@ -0,0 +1,25 @@
package com.abin.mallchat.custom.chat.domain.vo.request;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-17
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberReq extends CursorPageBaseReq {
@ApiModelProperty("房间号")
@NotNull
private Long roomId;
}

View File

@@ -0,0 +1,28 @@
package com.abin.mallchat.custom.chat.domain.vo.response;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-17
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessageReadInfoResp {
@ApiModelProperty("消息id")
private Long msgId;
@ApiModelProperty("已读数")
private Integer readCount;
@ApiModelProperty("未读数")
private Integer unReadCount;
}

View File

@@ -0,0 +1,21 @@
package com.abin.mallchat.custom.chat.domain.vo.response;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-23
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessageReadResp {
@ApiModelProperty("已读或者未读的用户uid")
private Long uid;
}

View File

@@ -18,12 +18,20 @@ import java.util.Date;
@AllArgsConstructor
@NoArgsConstructor
public class ChatRoomResp {
@ApiModelProperty("会话id")
private Long id;
@ApiModelProperty("房间id")
private Long roomId;
@ApiModelProperty("房间类型 1群聊 2单聊")
private Integer type;
@ApiModelProperty("是否全员展示的会话 0否 1是")
private Integer hot_Flag;
@ApiModelProperty("最新消息")
private String text;
@ApiModelProperty("会话名称")
private String name;
@ApiModelProperty("会话类型 1大群聊 2沸点")
private Integer type;
@ApiModelProperty("房间最后活跃时间")
@ApiModelProperty("会话头像")
private String avatar;
@ApiModelProperty("房间最后活跃时间(用来排序)")
private Date lastActiveTime;
@ApiModelProperty("未读数")
private Integer unreadCount;
}

View File

@@ -0,0 +1,29 @@
package com.abin.mallchat.custom.chat.domain.vo.response;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-17
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberResp {
@ApiModelProperty("群id")
private Long id;
@ApiModelProperty("群名称")
private Long groupName;
@ApiModelProperty("在线人数")
private Long onlineNum;//在线人数
@ApiModelProperty("群聊描述")
private String desc;//在线人数
@ApiModelProperty("成员角色 1群主 2管理员 3普通成员 4踢出群聊")
private Integer role;//在线人数
}

View File

@@ -1,5 +1,6 @@
package com.abin.mallchat.custom.chat.service;
import com.abin.mallchat.common.chat.domain.dto.MsgReadInfoDTO;
import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
@@ -7,6 +8,7 @@ import com.abin.mallchat.custom.chat.domain.vo.request.*;
import com.abin.mallchat.custom.chat.domain.vo.response.*;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
/**
@@ -73,4 +75,8 @@ public interface ChatService {
void recallMsg(Long uid, ChatMessageBaseReq request);
List<ChatMemberListResp> getMemberList(ChatMessageMemberReq chatMessageMemberReq);
Collection<MsgReadInfoDTO> getMsgReadInfo(Long uid, ChatMessageReadInfoReq request);
CursorPageBaseResp<ChatMessageReadResp> getReadPage(Long uid, ChatMessageReadReq request);
}

View File

@@ -0,0 +1,17 @@
package com.abin.mallchat.custom.chat.service;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
public interface RoomService {
/**
* 获取会话列表--支持未登录态
*/
CursorPageBaseResp<ChatRoomResp> getContactPage(CursorPageBaseReq request, Long uid);
}

View File

@@ -5,20 +5,16 @@ import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
import com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum;
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
import com.abin.mallchat.common.user.domain.entity.UserApply;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
import com.abin.mallchat.custom.chat.domain.vo.request.msg.TextMsgReq;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
import com.abin.mallchat.custom.chat.service.strategy.msg.AbstractMsgHandler;
import com.abin.mallchat.custom.chat.service.strategy.msg.MsgHandlerFactory;
import com.abin.mallchat.custom.user.domain.vo.response.ws.WSApplyMessage;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -99,4 +95,13 @@ public class MessageAdapter {
}
public static ChatMessageReq buildAgreeMsg(Long roomId) {
ChatMessageReq chatMessageReq = new ChatMessageReq();
chatMessageReq.setRoomId(roomId);
chatMessageReq.setMsgType(MessageTypeEnum.TEXT.getType());
TextMsgReq textMsgReq = new TextMsgReq();
textMsgReq.setContent("我们已经成为好友了,开始聊天吧");
chatMessageReq.setBody(textMsgReq);
return chatMessageReq;
}
}

View File

@@ -1,7 +1,9 @@
package com.abin.mallchat.custom.chat.service.adapter;
import cn.hutool.core.bean.BeanUtil;
import com.abin.mallchat.common.chat.domain.entity.Contact;
import com.abin.mallchat.common.chat.domain.entity.Room;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageReadResp;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
import java.util.List;
@@ -24,4 +26,12 @@ public class RoomAdapter {
return resp;
}).collect(Collectors.toList());
}
public static List<ChatMessageReadResp> buildReadResp(List<Contact> list) {
return list.stream().map(contact -> {
ChatMessageReadResp resp = new ChatMessageReadResp();
resp.setUid(contact.getUid());
return resp;
}).collect(Collectors.toList());
}
}

View File

@@ -5,14 +5,18 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair;
import com.abin.mallchat.common.chat.dao.ContactDao;
import com.abin.mallchat.common.chat.dao.MessageDao;
import com.abin.mallchat.common.chat.dao.MessageMarkDao;
import com.abin.mallchat.common.chat.dao.RoomDao;
import com.abin.mallchat.common.chat.domain.dto.MsgReadInfoDTO;
import com.abin.mallchat.common.chat.domain.entity.Contact;
import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
import com.abin.mallchat.common.chat.domain.entity.Room;
import com.abin.mallchat.common.chat.domain.enums.MessageMarkActTypeEnum;
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
import com.abin.mallchat.common.chat.service.ContactService;
import com.abin.mallchat.common.common.annotation.RedissonLock;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
@@ -44,6 +48,7 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -77,6 +82,10 @@ public class ChatServiceImpl implements ChatService {
private IRoleService iRoleService;
@Autowired
private RecallMsgHandler recallMsgHandler;
@Autowired
private ContactService contactService;
@Autowired
private ContactDao contactDao;
/**
* 发送消息
@@ -205,6 +214,32 @@ public class ChatServiceImpl implements ChatService {
return null;
}
@Override
public Collection<MsgReadInfoDTO> getMsgReadInfo(Long uid, ChatMessageReadInfoReq request) {
List<Message> messages = messageDao.listByIds(request.getMsgIds());
messages.forEach(message -> {
AssertUtil.equal(uid, message.getFromUid(), "只能查询自己发送的消息");
});
return contactService.getMsgReadInfo(messages).values();
}
@Override
public CursorPageBaseResp<ChatMessageReadResp> getReadPage(@Nullable Long uid, ChatMessageReadReq request) {
Message message = messageDao.getById(request.getMsgId());
AssertUtil.isNotEmpty(message, "消息id有误");
AssertUtil.equal(uid, message.getFromUid(), "只能查看自己的消息");
CursorPageBaseResp<Contact> page;
if (request.getSearchType() == 1) {//已读
page = contactDao.getReadPage(message, request);
} else {
page = contactDao.getUnReadPage(message, request);
}
if (CollectionUtil.isEmpty(page.getList())) {
return CursorPageBaseResp.empty();
}
return CursorPageBaseResp.init(page, RoomAdapter.buildReadResp(page.getList()));
}
private void checkRecall(Long uid, Message message) {
AssertUtil.isNotEmpty(message, "消息有误");
AssertUtil.notEqual(message.getType(), MessageTypeEnum.RECALL, "消息无法撤回");

View File

@@ -0,0 +1,114 @@
package com.abin.mallchat.custom.chat.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.abin.mallchat.common.chat.dao.ContactDao;
import com.abin.mallchat.common.chat.dao.MessageDao;
import com.abin.mallchat.common.chat.domain.dto.RoomBaseInfo;
import com.abin.mallchat.common.chat.domain.entity.*;
import com.abin.mallchat.common.chat.domain.enums.RoomTypeEnum;
import com.abin.mallchat.common.chat.service.adapter.ChatAdapter;
import com.abin.mallchat.common.chat.service.cache.RoomCache;
import com.abin.mallchat.common.chat.service.cache.RoomFriendCache;
import com.abin.mallchat.common.chat.service.cache.RoomGroupCache;
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
import com.abin.mallchat.common.user.domain.entity.User;
import com.abin.mallchat.common.user.service.cache.UserInfoCache;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
import com.abin.mallchat.custom.chat.service.RoomService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
@Service
public class RoomServiceImpl implements RoomService {
@Autowired
private ContactDao contactDao;
@Autowired
private RoomCache roomCache;
@Autowired
private RoomGroupCache roomGroupCache;
@Autowired
private RoomFriendCache roomFriendCache;
@Autowired
private UserInfoCache userInfoCache;
@Autowired
private MessageDao messageDao;
@Override
public CursorPageBaseResp<ChatRoomResp> getContactPage(CursorPageBaseReq request, Long uid) {
if (Objects.nonNull(uid)) {
CursorPageBaseResp<Contact> contactPage = contactDao.getContactPage(uid, request);
List<Long> roomIds = contactPage.getList().stream().map(Contact::getRoomId).collect(Collectors.toList());
//表情和头像
Map<Long, RoomBaseInfo> roomBaseInfoMap = getRoomBaseInfoMap(roomIds, uid);
//最后一条消息
List<Long> msgIds = contactPage.getList().stream().map(Contact::getLastMsgId).collect(Collectors.toList());
List<Message> messages = messageDao.listByIds(msgIds);
Map<Long, Message> msgMap = messages.stream().collect(Collectors.toMap(Message::getId, Function.identity()));
List<ChatRoomResp> collect = contactPage.getList().stream().map(contact -> {
ChatRoomResp resp = new ChatRoomResp();
BeanUtil.copyProperties(contact, resp);
RoomBaseInfo roomBaseInfo = roomBaseInfoMap.get(contact.getRoomId());
resp.setAvatar(roomBaseInfo.getAvatar());
resp.setName(roomBaseInfo.getName());
Message message = msgMap.get(contact.getLastMsgId());
if (Objects.nonNull(message)) {
resp.setText();
}
return resp;
}).collect(Collectors.toList());
CursorPageBaseResp.init(contactPage, collect);
}
return null;
}
private Map<Long, User> getFriendRoomMap(List<Long> roomIds, Long uid) {
Map<Long, RoomFriend> roomFriendMap = roomFriendCache.getBatch(roomIds);
Set<Long> friendUidSet = ChatAdapter.getFriendUidSet(roomFriendMap.values(), uid);
Map<Long, User> userBatch = userInfoCache.getBatch(new ArrayList<>(friendUidSet));
return roomFriendMap.values()
.stream()
.collect(Collectors.toMap(RoomFriend::getRoomId, roomFriend -> {
Long friendUid = ChatAdapter.getFriendUid(roomFriend, uid);
return userBatch.get(friendUid);
}));
}
private Map<Long, RoomBaseInfo> getRoomBaseInfoMap(List<Long> roomIds, Long uid) {
Map<Long, Room> roomMap = roomCache.getBatch(roomIds);
Map<Integer, List<Long>> groupRoomIdMap = roomMap.values().stream().collect(Collectors.groupingBy(Room::getType,
Collectors.mapping(Room::getId, Collectors.toList())));
//获取群组信息
List<Long> groupRoomId = groupRoomIdMap.get(RoomTypeEnum.GROUP.getType());
Map<Long, RoomGroup> roomInfoBatch = roomGroupCache.getBatch(groupRoomId);
//获取好友信息
List<Long> friendRoomId = groupRoomIdMap.get(RoomTypeEnum.FRIEND.getType());
Map<Long, User> friendRoomMap = getFriendRoomMap(friendRoomId, uid);
return roomMap.values().stream().map(room -> {
RoomBaseInfo roomBaseInfo = new RoomBaseInfo();
roomBaseInfo.setRoomId(room.getId());
if (RoomTypeEnum.of(room.getType()) == RoomTypeEnum.GROUP) {
RoomGroup roomGroup = roomInfoBatch.get(room.getId());
roomBaseInfo.setName(roomGroup.getName());
roomBaseInfo.setAvatar(roomGroup.getAvatar());
} else if (RoomTypeEnum.of(room.getType()) == RoomTypeEnum.FRIEND) {
User user = friendRoomMap.get(room.getId());
roomBaseInfo.setName(user.getName());
roomBaseInfo.setAvatar(user.getAvatar());
}
return roomBaseInfo;
}).collect(Collectors.toMap(RoomBaseInfo::getRoomId, Function.identity()));
}
}

View File

@@ -18,14 +18,29 @@ public abstract class AbstractMsgHandler {
MsgHandlerFactory.register(getMsgTypeEnum().getType(), this);
}
/**
* 消息类型
*/
abstract MessageTypeEnum getMsgTypeEnum();
/**
* 校验消息——保存前校验
*/
public abstract void checkMsg(ChatMessageReq req, Long uid);
/**
* 保存消息
*/
public abstract void saveMsg(Message msg, ChatMessageReq req);
/**
* 展示消息
*/
public abstract Object showMsg(Message msg);
/**
* 被回复时——展示的消息
*/
public abstract Object showReplyMsg(Message msg);
}

View File

@@ -0,0 +1,36 @@
package com.abin.mallchat.custom.common.event.listener;
import com.abin.mallchat.common.common.event.UserApplyEvent;
import com.abin.mallchat.common.user.dao.UserApplyDao;
import com.abin.mallchat.common.user.domain.entity.UserApply;
import com.abin.mallchat.custom.user.domain.vo.response.ws.WSFriendApply;
import com.abin.mallchat.custom.user.service.WebSocketService;
import com.abin.mallchat.custom.user.service.adapter.WSAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
/**
* 好友申请监听器
*
* @author zhongzb create on 2022/08/26
*/
@Slf4j
@Component
public class UserApplyListener {
@Autowired
private UserApplyDao userApplyDao;
@Autowired
private WebSocketService webSocketService;
@Async
@TransactionalEventListener(classes = UserApplyEvent.class, fallbackExecution = true)
public void notifyFriend(UserApplyEvent event) {
UserApply userApply = event.getUserApply();
Integer unReadCount = userApplyDao.getUnReadCount(userApply.getTargetId());
webSocketService.sendToFriend(WSAdapter.buildApplySend(new WSFriendApply(userApply.getUid(), unReadCount)), userApply.getTargetId());
}
}

View File

@@ -88,7 +88,7 @@ public class FriendController {
@PutMapping("/apply")
@ApiOperation("申请审批")
public ApiResult<Void> applyApprove(@Valid @RequestBody FriendApproveReq request) {
friendService.applyApprove(request);
friendService.applyApprove(RequestHolder.get().getUid(), request);
return ApiResult.success();
}

View File

@@ -26,7 +26,9 @@ public enum WSRespTypeEnum {
BLACK(7, "拉黑用户", WSBlack.class),
MARK(8, "消息标记", WSMsgMark.class),
RECALL(9, "消息撤回", WSMsgRecall.class),
APPLY(10,"好友申请", WSApplyMessage.class),
APPLY(10, "好友申请", WSFriendApply.class),
MEMBER_CHANGE(11, "成员变动", WSMemberChange.class),
MESSAGE_READ(12, "消息已读数", WSMessageRead.class),
;
private final Integer type;

View File

@@ -1,14 +0,0 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
import lombok.AllArgsConstructor;
/**
* @author : limeng
* @description : 好友申请消息推送
* @date : 2023/07/21
*/
@AllArgsConstructor
public class WSApplyMessage extends ChatMessageResp {
}

View File

@@ -0,0 +1,23 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSFriendApply {
@ApiModelProperty("申请人")
private Long uid;
@ApiModelProperty("申请未读数")
private Integer unreadCount;
}

View File

@@ -0,0 +1,34 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSMemberChange {
@ApiModelProperty("群组id")
private Long roomId;
@ApiModelProperty("变动人")
private Long uid;
@ApiModelProperty("变动类型 1加入群组 2移除群组")
private Integer changeType;
/**
* @see com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum
*/
@ApiModelProperty("在线状态 1在线 2离线")
private Integer activeStatus;
@ApiModelProperty("最后一次上下线时间")
private Date lastOptTime;
}

View File

@@ -0,0 +1,23 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSMessageRead {
@ApiModelProperty("消息")
private Long msgId;
@ApiModelProperty("阅读人数可能为0")
private Integer readCount;
}

View File

@@ -21,7 +21,4 @@ import java.util.List;
public class WSOnlineOfflineNotify {
private List<ChatMemberResp> changeList = new ArrayList<>();//新的上下线用户
private Long onlineNum;//在线人数
@Deprecated
private Long totalNum;//总人数
}

View File

@@ -56,5 +56,5 @@ public interface FriendService {
* @param uid uid
* @param request 请求
*/
void applyApprove(FriendApproveReq request);
void applyApprove(Long uid, FriendApproveReq request);
}

View File

@@ -0,0 +1,43 @@
package com.abin.mallchat.custom.user.service.adapter;
import com.abin.mallchat.common.user.domain.entity.UserApply;
import com.abin.mallchat.custom.user.domain.vo.request.friend.FriendApplyReq;
import com.abin.mallchat.custom.user.domain.vo.response.friend.FriendApplyResp;
import java.util.List;
import java.util.stream.Collectors;
import static com.abin.mallchat.common.user.domain.enums.ApplyReadStatusEnum.UNREAD;
import static com.abin.mallchat.common.user.domain.enums.ApplyStatusEnum.WAIT_APPROVAL;
import static com.abin.mallchat.common.user.domain.enums.ApplyTypeEnum.ADD_FRIEND;
/**
* Description: 好友适配器
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
public class FriendAdapter {
public static UserApply buildFriendApply(Long uid, FriendApplyReq request) {
UserApply userApplyNew = new UserApply();
userApplyNew.setUid(uid);
userApplyNew.setMsg(request.getMsg());
userApplyNew.setType(ADD_FRIEND.getCode());
userApplyNew.setTargetId(request.getTargetUid());
userApplyNew.setStatus(WAIT_APPROVAL.getCode());
userApplyNew.setReadStatus(UNREAD.getCode());
return userApplyNew;
}
public static List<FriendApplyResp> buildFriendApplyList(List<UserApply> records) {
return records.stream().map(userApply -> {
FriendApplyResp friendApplyResp = new FriendApplyResp();
friendApplyResp.setUid(userApply.getUid());
friendApplyResp.setType(userApply.getType());
friendApplyResp.setMsg(userApply.getMsg());
friendApplyResp.setStatus(userApply.getStatus());
return friendApplyResp;
}).collect(Collectors.toList());
}
}

View File

@@ -133,8 +133,8 @@ public class WSAdapter {
return wsBaseResp;
}
public static WSBaseResp<WSApplyMessage> buildApplySend(WSApplyMessage resp) {
WSBaseResp<WSApplyMessage> wsBaseResp = new WSBaseResp<>();
public static WSBaseResp<WSFriendApply> buildApplySend(WSFriendApply resp) {
WSBaseResp<WSFriendApply> wsBaseResp = new WSBaseResp<>();
wsBaseResp.setType(WSRespTypeEnum.APPLY.getType());
wsBaseResp.setData(resp);
return wsBaseResp;

View File

@@ -1,11 +1,20 @@
package com.abin.mallchat.custom.user.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.abin.mallchat.common.chat.dao.RoomFriendDao;
import com.abin.mallchat.common.chat.domain.entity.RoomFriend;
import com.abin.mallchat.common.chat.service.ContactService;
import com.abin.mallchat.common.chat.service.RoomService;
import com.abin.mallchat.common.common.annotation.RedissonLock;
import com.abin.mallchat.common.common.domain.vo.request.PageBaseReq;
import com.abin.mallchat.common.common.domain.vo.response.PageBaseResp;
import com.abin.mallchat.common.common.event.UserApplyEvent;
import com.abin.mallchat.common.common.utils.AssertUtil;
import com.abin.mallchat.common.user.dao.UserApplyDao;
import com.abin.mallchat.common.user.dao.UserFriendDao;
import com.abin.mallchat.common.user.domain.entity.UserApply;
import com.abin.mallchat.common.user.domain.entity.UserFriend;
import com.abin.mallchat.custom.chat.service.ChatService;
import com.abin.mallchat.custom.chat.service.adapter.MessageAdapter;
import com.abin.mallchat.custom.user.domain.vo.request.friend.FriendApplyReq;
import com.abin.mallchat.custom.user.domain.vo.request.friend.FriendApproveReq;
@@ -13,25 +22,24 @@ import com.abin.mallchat.custom.user.domain.vo.request.friend.FriendCheckReq;
import com.abin.mallchat.custom.user.domain.vo.response.friend.FriendApplyResp;
import com.abin.mallchat.custom.user.domain.vo.response.friend.FriendCheckResp;
import com.abin.mallchat.custom.user.domain.vo.response.friend.FriendUnreadResp;
import com.abin.mallchat.custom.user.domain.vo.response.ws.WSApplyMessage;
import com.abin.mallchat.custom.user.service.FriendService;
import com.abin.mallchat.custom.user.service.WebSocketService;
import com.abin.mallchat.custom.user.service.adapter.WSAdapter;
import com.abin.mallchat.custom.user.service.adapter.FriendAdapter;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static com.abin.mallchat.common.user.domain.enums.ApplyReadStatusEnum.UNREAD;
import static com.abin.mallchat.common.user.domain.enums.ApplyStatusEnum.AGREE;
import static com.abin.mallchat.common.user.domain.enums.ApplyStatusEnum.WAIT_APPROVAL;
import static com.abin.mallchat.common.user.domain.enums.ApplyTypeEnum.ADD_FRIEND;
/**
* @author : limeng
@@ -41,14 +49,24 @@ import static com.abin.mallchat.common.user.domain.enums.ApplyTypeEnum.ADD_FRIEN
@Slf4j
@Service
public class FriendServiceImpl implements FriendService {
@Resource
@Autowired
private WebSocketService webSocketService;
@Resource
@Autowired
private UserFriendDao userFriendDao;
@Resource
@Autowired
private UserApplyDao userApplyDao;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private RoomFriendDao roomFriendDao;
@Autowired
private RoomService roomService;
@Autowired
private ContactService contactService;
@Autowired
private ChatService chatService;
/**
* 检查
@@ -60,12 +78,13 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public FriendCheckResp check(Long uid, FriendCheckReq request) {
List<UserFriend> friendList = userFriendDao.queryUserFriend(uid, request.getUidList());
Map<Long, UserFriend> friendMap = friendList.stream().collect(Collectors.toMap(UserFriend::getFriendUid, friend -> friend));
List<UserFriend> friendList = userFriendDao.getByFriends(uid, request.getUidList());
Set<Long> friendUidSet = friendList.stream().map(UserFriend::getFriendUid).collect(Collectors.toSet());
List<FriendCheckResp.FriendCheck> friendCheckList = request.getUidList().stream().map(friendUid -> {
FriendCheckResp.FriendCheck friendCheck = new FriendCheckResp.FriendCheck();
friendCheck.setUid(friendUid);
friendCheck.setIsFriend(friendMap.containsKey(friendUid));
friendCheck.setIsFriend(friendUidSet.contains(friendUid));
return friendCheck;
}).collect(Collectors.toList());
return new FriendCheckResp(friendCheckList);
@@ -78,22 +97,20 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public void apply(Long uid, FriendApplyReq request) {
UserApply userApply = userApplyDao.queryUserApply(uid, request.getTargetUid());
if (Objects.nonNull(userApply)) {
//是否有好友关系
UserFriend friend = userFriendDao.getByFriend(uid, request.getTargetUid());
AssertUtil.isEmpty(friend, "你们已经是好友了");
//是否有待审批的申请记录
UserApply friendApproving = userApplyDao.getFriendApproving(uid, request.getTargetUid());
if (Objects.nonNull(friendApproving)) {
log.info("已有好友申请记录,uid:{}, targetId:{}", uid, request.getTargetUid());
return;
}
UserApply userApplyNew = new UserApply();
userApplyNew.setUid(uid);
userApplyNew.setMsg(request.getMsg());
userApplyNew.setType(ADD_FRIEND.getCode());
userApplyNew.setTargetId(request.getTargetUid());
userApplyNew.setStatus(WAIT_APPROVAL.getCode());
userApplyNew.setReadStatus(UNREAD.getCode());
userApplyDao.insert(userApplyNew);
WSApplyMessage applyMessage = MessageAdapter.buildApplyResp(userApplyNew);
webSocketService.sendToFriend(WSAdapter.buildApplySend(applyMessage), request.getTargetUid());
//申请入库
UserApply insert = FriendAdapter.buildFriendApply(uid, request);
userApplyDao.save(insert);
//申请事件
applicationEventPublisher.publishEvent(new UserApplyEvent(this, insert));
}
/**
@@ -104,20 +121,15 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public PageBaseResp<FriendApplyResp> pageApplyFriend(Long uid, PageBaseReq request) {
// todo 分页
List<UserApply> userApplyList = userApplyDao.queryUserApplyList(uid);
List<FriendApplyResp> friendApplyResps = userApplyList.stream().map(userApply -> {
FriendApplyResp friendApplyResp = new FriendApplyResp();
friendApplyResp.setApplyId(userApply.getId());
friendApplyResp.setUid(userApply.getUid());
friendApplyResp.setType(userApply.getType());
friendApplyResp.setMsg(userApply.getMsg());
friendApplyResp.setStatus(userApply.getStatus());
return friendApplyResp;
}).collect(Collectors.toList());
PageBaseResp<FriendApplyResp> pageBaseResp = new PageBaseResp<>();
pageBaseResp.setList(friendApplyResps);
return pageBaseResp;
IPage<UserApply> userApplyIPage = userApplyDao.FriendApplyPage(uid, request.plusPage());
if (CollectionUtil.isEmpty(userApplyIPage.getRecords())) {
return PageBaseResp.empty();
}
//将这些申请列表设为已读
List<Long> applyIds = userApplyIPage.getRecords().stream().map(UserApply::getId).collect(Collectors.toList());
userApplyDao.readApples(uid, applyIds);
//返回消息
return PageBaseResp.init(userApplyIPage, FriendAdapter.buildFriendApplyList(userApplyIPage.getRecords()));
}
/**
@@ -127,28 +139,38 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public FriendUnreadResp unread(Long uid) {
return new FriendUnreadResp(userApplyDao.unreadCount(uid));
Integer unReadCount = userApplyDao.getUnReadCount(uid);
return new FriendUnreadResp(unReadCount);
}
@Override
@Transactional
public void applyApprove(FriendApproveReq request) {
UserApply userApply = userApplyDao.queryUserApplyById(request.getApplyId());
if (Objects.isNull(userApply)) {
log.error("不存在申请记录:{}", request.getApplyId());
return;
}
if (Objects.equals(userApply.getStatus(), AGREE.getCode())) {
log.error("已同意好友申请:{}", request.getApplyId());
return;
}
userApplyDao.agreeUserApply(request.getApplyId());
@RedissonLock(key = "#uid")
public void applyApprove(Long uid, FriendApproveReq request) {
UserApply userApply = userApplyDao.getById(request.getApplyId());
AssertUtil.isNotEmpty(userApply, "不存在申请记录");
AssertUtil.equal(userApply.getTargetId(), uid, "不存在申请记录");
AssertUtil.equal(userApply.getStatus(), AGREE.getCode(), "已同意好友申请");
//同意申请
userApplyDao.agree(request.getApplyId());
//创建双方好友关系
createFriend(uid, userApply.getUid());
//创建一个聊天房间
RoomFriend roomFriend = roomService.createFriendRoom(Arrays.asList(uid, userApply.getUid()));
//创建双方的会话
contactService.createContact(uid, roomFriend.getRoomId());
contactService.createContact(userApply.getUid(), roomFriend.getRoomId());
//发送一条同意消息。。我们已经是好友了,开始聊天吧
chatService.sendMsg(MessageAdapter.buildAgreeMsg(roomFriend.getRoomId()), uid);
}
private void createFriend(Long uid, Long targetUid) {
UserFriend userFriend1 = new UserFriend();
userFriend1.setUid(userApply.getUid());
userFriend1.setFriendUid(userApply.getTargetId());
userFriend1.setUid(uid);
userFriend1.setFriendUid(targetUid);
UserFriend userFriend2 = new UserFriend();
userFriend2.setUid(userApply.getTargetId());
userFriend2.setFriendUid(userApply.getUid());
userFriendDao.insertBatch(Lists.newArrayList(userFriend1, userFriend2));
userFriend2.setUid(targetUid);
userFriend2.setFriendUid(uid);
userFriendDao.saveBatch(Lists.newArrayList(userFriend1, userFriend2))
}
}

View File

@@ -0,0 +1,18 @@
package com.abin.mallchat.custom.common;
import org.junit.Test;
import static com.abin.mallchat.custom.user.service.adapter.FriendAdapter.buildFriendRoomKey;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
public class CommonTest {
@Test
public void test() {
System.out.println(buildFriendRoomKey(100L, 102L));
}
}