mirror of
https://github.com/zongzibinbin/MallChat.git
synced 2026-03-15 23:23:43 +08:00
项目基础搭建
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
package com.abin.mallchat.custom.chat.controller;
|
||||
|
||||
|
||||
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;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.IdRespVO;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||
import com.abin.mallchat.common.common.utils.RequestHolder;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
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 = "聊天室相关接口")
|
||||
public class ChatController {
|
||||
@Autowired
|
||||
private ChatService chatService;
|
||||
@GetMapping("/public/room/page")
|
||||
@ApiOperation("会话列表")
|
||||
public ApiResult<CursorPageBaseResp<ChatRoomResp>> getRoomPage(CursorPageBaseReq request) {
|
||||
return ApiResult.success(chatService.getRoomPage(request, RequestHolder.get().getUid()));
|
||||
}
|
||||
@GetMapping("/public/member/page")
|
||||
@ApiOperation("群成员列表")
|
||||
public ApiResult<CursorPageBaseResp<ChatMemberResp>> getMemberPage(CursorPageBaseReq request) {
|
||||
return ApiResult.success(chatService.getMemberPage(request));
|
||||
}
|
||||
|
||||
@GetMapping("/public/member/statistic")
|
||||
@ApiOperation("群成员人数统计")
|
||||
public ApiResult<ChatMemberStatisticResp> getMemberStatistic() {
|
||||
return ApiResult.success(chatService.getMemberStatistic());
|
||||
}
|
||||
|
||||
@GetMapping("/public/msg/page")
|
||||
@ApiOperation("消息列表")
|
||||
public ApiResult<CursorPageBaseResp<ChatMessageResp>> getMsgPage(ChatMessagePageReq request) {
|
||||
return ApiResult.success(chatService.getMsgPage(request, RequestHolder.get().getUid()));
|
||||
}
|
||||
|
||||
@PostMapping("/msg")
|
||||
@ApiOperation("发送消息")
|
||||
@FrequencyControl(time = 5,count = 2)
|
||||
@FrequencyControl(time = 30,count = 5)
|
||||
@FrequencyControl(time = 60,count = 10)
|
||||
public ApiResult<IdRespVO> sendMsg(@Valid @RequestBody ChatMessageReq request) {
|
||||
return ApiResult.success(IdRespVO.id(chatService.sendMsg(request, RequestHolder.get().getUid())));
|
||||
}
|
||||
|
||||
@PutMapping("/msg/mark")
|
||||
@ApiOperation("消息标记")
|
||||
public ApiResult<Void> setMsgMark(@Valid @RequestBody ChatMessageMarkReq request) {//分布式锁
|
||||
chatService.setMsgMark(RequestHolder.get().getUid(),request);
|
||||
return ApiResult.success();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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 lombok.experimental.Accessors;
|
||||
|
||||
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 ChatMessageMarkReq{
|
||||
@NotNull
|
||||
@ApiModelProperty("消息id")
|
||||
private Long msgId;
|
||||
|
||||
@NotNull
|
||||
@ApiModelProperty("标记类型 1点赞 2举报")
|
||||
private Integer markType;
|
||||
|
||||
@NotNull
|
||||
@ApiModelProperty("动作类型 1确认 2取消")
|
||||
private Integer actType;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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 lombok.experimental.Accessors;
|
||||
|
||||
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 ChatMessagePageReq extends CursorPageBaseReq {
|
||||
@NotNull
|
||||
@ApiModelProperty("会话id")
|
||||
private Long roomId;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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 lombok.experimental.Accessors;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* Description: 消息发送请求体
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMessageReq {
|
||||
|
||||
@NotNull
|
||||
@Length( max = 10000,message = "消息内容过长,服务器扛不住啊,兄dei")
|
||||
@ApiModelProperty("消息内容")
|
||||
private String content;
|
||||
|
||||
@NotNull
|
||||
@ApiModelProperty("会话id")
|
||||
private Long roomId;
|
||||
|
||||
@ApiModelProperty("回复的消息id,如果没有别传就好")
|
||||
private Long replyMsgId;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.response;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Description: 群成员列表的成员信息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMemberResp {
|
||||
@ApiModelProperty("uid")
|
||||
private Long uid;
|
||||
@ApiModelProperty("用户名称")
|
||||
private String name;
|
||||
@ApiModelProperty("头像")
|
||||
private String avatar;
|
||||
/**
|
||||
* @see com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum
|
||||
*/
|
||||
@ApiModelProperty("在线状态 1在线 2离线")
|
||||
private Integer activeStatus;
|
||||
@ApiModelProperty("最后一次上下线时间")
|
||||
private Date lastOptTime;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Description: 群成员统计信息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMemberStatisticResp {
|
||||
|
||||
@ApiModelProperty("在线人数")
|
||||
private Long onlineNum;//在线人数
|
||||
@ApiModelProperty("总人数")
|
||||
private Long totalNum;//总人数
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.response;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description: 消息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMessageResp {
|
||||
|
||||
@ApiModelProperty("发送者信息")
|
||||
private UserInfo fromUser;
|
||||
@ApiModelProperty("消息详情")
|
||||
private Message message;
|
||||
|
||||
@Data
|
||||
public static class UserInfo {
|
||||
@ApiModelProperty("用户名称")
|
||||
private String username;
|
||||
@ApiModelProperty("用户id")
|
||||
private Long uid;
|
||||
@ApiModelProperty("头像")
|
||||
private String avatar;
|
||||
@ApiModelProperty("徽章标识,如果没有展示null")
|
||||
private Badge badge;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Message {
|
||||
@ApiModelProperty("消息id")
|
||||
private Long id;
|
||||
@ApiModelProperty("消息发送时间")
|
||||
private Date sendTime;
|
||||
@ApiModelProperty("消息内容")
|
||||
private String content;
|
||||
@ApiModelProperty("消息类型 1正常文本 2.爆赞 (点赞超过10)3.危险发言(举报超5)")
|
||||
private Integer type;
|
||||
@ApiModelProperty("消息标记")
|
||||
private MessageMark messageMark;
|
||||
@ApiModelProperty("父消息,如果没有父消息,返回的是null")
|
||||
private ReplyMsg reply;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ReplyMsg {
|
||||
@ApiModelProperty("消息id")
|
||||
private Long id;
|
||||
@ApiModelProperty("用户名称")
|
||||
private String username;
|
||||
@ApiModelProperty("消息内容")
|
||||
private String content;
|
||||
@ApiModelProperty("是否可消息跳转 0否 1是")
|
||||
private Integer canCallback;
|
||||
@ApiModelProperty("跳转间隔的消息条数")
|
||||
private Integer gapCount;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MessageMark {
|
||||
@ApiModelProperty("点赞数")
|
||||
private Integer likeCount;
|
||||
@ApiModelProperty("该用户是否已经点赞 0否 1是")
|
||||
private Integer userLike;
|
||||
@ApiModelProperty("举报数")
|
||||
private Integer dislikeCount;
|
||||
@ApiModelProperty("该用户是否已经举报 0否 1是")
|
||||
private Integer userDislike;
|
||||
}
|
||||
@Data
|
||||
private static class Badge {
|
||||
@ApiModelProperty("徽章图像")
|
||||
private String img;
|
||||
@ApiModelProperty("徽章说明")
|
||||
private String describe;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Description: 群成员列表的成员信息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatRoomResp {
|
||||
@ApiModelProperty("会话id")
|
||||
private Long id;
|
||||
@ApiModelProperty("会话名称")
|
||||
private String name;
|
||||
@ApiModelProperty("会话类型 1大群聊 2沸点")
|
||||
private Integer type;
|
||||
@ApiModelProperty("房间最后活跃时间")
|
||||
private Date lastActiveTime;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.abin.mallchat.custom.chat.service;
|
||||
|
||||
import com.abin.mallchat.common.chat.dao.MessageDao;
|
||||
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;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.MessageAdapter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Description: 消息处理类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
public interface ChatService {
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
Long sendMsg(ChatMessageReq request, Long uid);
|
||||
|
||||
/**
|
||||
* 根据消息获取消息前端展示的物料
|
||||
* @param message
|
||||
* @param receiveUid 接受消息的uid,可null
|
||||
* @return
|
||||
*/
|
||||
ChatMessageResp getMsgResp(Message message,Long receiveUid);
|
||||
|
||||
/**
|
||||
* 获取群成员列表
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
CursorPageBaseResp<ChatMemberResp> getMemberPage(CursorPageBaseReq request);
|
||||
|
||||
/**
|
||||
* 获取消息列表
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq request,@Nullable Long receiveUid);
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* @param request
|
||||
* @param uid
|
||||
* @return
|
||||
*/
|
||||
CursorPageBaseResp<ChatRoomResp> getRoomPage(CursorPageBaseReq request, Long uid);
|
||||
|
||||
ChatMemberStatisticResp getMemberStatistic();
|
||||
|
||||
void setMsgMark(Long uid, ChatMessageMarkReq request);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.abin.mallchat.custom.chat.service.adapter;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum;
|
||||
import com.abin.mallchat.common.user.service.cache.UserCache;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: 成员适配器
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MemberAdapter {
|
||||
@Autowired
|
||||
private UserCache userCache;
|
||||
|
||||
public List<ChatMemberResp> buildMember(List<Pair<Long, Double>> list, ChatActiveStatusEnum statusEnum) {
|
||||
return list.stream().map(a -> {
|
||||
ChatMemberResp resp = new ChatMemberResp();
|
||||
resp.setActiveStatus(statusEnum.getStatus());
|
||||
resp.setLastOptTime(new Date(a.getValue().longValue()));
|
||||
resp.setUid(a.getKey());
|
||||
User userInfo = userCache.getUserInfo(a.getKey());
|
||||
resp.setName(userInfo.getName());
|
||||
resp.setAvatar(userInfo.getAvatar());
|
||||
return resp;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.abin.mallchat.custom.chat.service.adapter;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
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.User;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.sun.org.apache.regexp.internal.RE;
|
||||
import org.yaml.snakeyaml.error.Mark;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: 消息适配器
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
public class MessageAdapter {
|
||||
public static final int CAN_CALLBACK_GAP_COUNT = 100;
|
||||
|
||||
public static Message buildMsgSave(ChatMessageReq request, Long uid) {
|
||||
return Message.builder()
|
||||
.replyMsgId(request.getReplyMsgId())
|
||||
.content(request.getContent())
|
||||
.fromUid(uid)
|
||||
.roomId(request.getRoomId())
|
||||
.status(MessageStatusEnum.NORMAL.getStatus())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
public static List<ChatMessageResp> buildMsgResp(List<Message> messages, Map<Long, Message> replyMap, Map<Long, User> userMap, List<MessageMark> msgMark, Long receiveUid) {
|
||||
Map<Long, List<MessageMark>> markMap = msgMark.stream().collect(Collectors.groupingBy(MessageMark::getMsgId));
|
||||
return messages.stream().map(a -> {
|
||||
ChatMessageResp resp = new ChatMessageResp();
|
||||
resp.setFromUser(buildFromUser(userMap.get(a.getFromUid())));
|
||||
resp.setMessage(buildMessage(a, replyMap, userMap, markMap.getOrDefault(a.getId(), new ArrayList<>()), receiveUid));
|
||||
return resp;
|
||||
})
|
||||
.sorted(Comparator.comparing(a -> a.getMessage().getSendTime()))//帮前端排好序,更方便它展示
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static ChatMessageResp.Message buildMessage(Message message, Map<Long, Message> replyMap, Map<Long, User> userMap, List<MessageMark> marks, Long receiveUid) {
|
||||
ChatMessageResp.Message messageVO = new ChatMessageResp.Message();
|
||||
BeanUtil.copyProperties(message, messageVO);
|
||||
messageVO.setSendTime(message.getCreateTime());
|
||||
Message replyMessage = replyMap.get(message.getReplyMsgId());
|
||||
//回复消息
|
||||
if (Objects.nonNull(replyMessage)) {
|
||||
ChatMessageResp.ReplyMsg replyMsgVO = new ChatMessageResp.ReplyMsg();
|
||||
replyMsgVO.setId(replyMessage.getId());
|
||||
replyMsgVO.setContent(replyMessage.getContent());
|
||||
User replyUser = userMap.get(replyMessage.getFromUid());
|
||||
replyMsgVO.setUsername(replyUser.getName());
|
||||
replyMsgVO.setCanCallback(YesOrNoEnum.toStatus(Objects.nonNull(message.getGapCount()) && message.getGapCount() <= CAN_CALLBACK_GAP_COUNT));
|
||||
replyMsgVO.setGapCount(message.getGapCount());
|
||||
messageVO.setReply(replyMsgVO);
|
||||
}
|
||||
//消息标记
|
||||
messageVO.setMessageMark(buildMsgMark(marks, receiveUid));
|
||||
return messageVO;
|
||||
}
|
||||
|
||||
private static ChatMessageResp.MessageMark buildMsgMark(List<MessageMark> marks, Long receiveUid) {
|
||||
Map<Integer, List<MessageMark>> typeMap = marks.stream().collect(Collectors.groupingBy(MessageMark::getType));
|
||||
List<MessageMark> likeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.LIKE.getType(), new ArrayList<>());
|
||||
List<MessageMark> dislikeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.DISLIKE.getType(), new ArrayList<>());
|
||||
ChatMessageResp.MessageMark mark = new ChatMessageResp.MessageMark();
|
||||
mark.setLikeCount(likeMarks.size());
|
||||
mark.setUserLike(Optional.ofNullable(receiveUid).filter(uid -> likeMarks.stream().anyMatch(a -> a.getUid().equals(uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus()));
|
||||
mark.setDislikeCount(dislikeMarks.size());
|
||||
mark.setUserDislike(Optional.ofNullable(receiveUid).filter(uid -> dislikeMarks.stream().anyMatch(a -> a.getUid().equals(uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus()));
|
||||
return mark;
|
||||
}
|
||||
|
||||
private static ChatMessageResp.UserInfo buildFromUser(User fromUser) {
|
||||
ChatMessageResp.UserInfo userInfo = new ChatMessageResp.UserInfo();
|
||||
userInfo.setUsername(fromUser.getName());
|
||||
userInfo.setAvatar(fromUser.getAvatar());
|
||||
userInfo.setUid(fromUser.getId());
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.abin.mallchat.custom.chat.service.adapter;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Room;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: 消息适配器
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
public class RoomAdapter {
|
||||
|
||||
|
||||
public static List<ChatRoomResp> buildResp(List<Room> list) {
|
||||
return list.stream()
|
||||
.map(a -> {
|
||||
ChatRoomResp resp = new ChatRoomResp();
|
||||
BeanUtil.copyProperties(a, resp);
|
||||
resp.setLastActiveTime(a.getActiveTime());
|
||||
return resp;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.abin.mallchat.custom.chat.service.helper;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum;
|
||||
|
||||
/**
|
||||
* Description: 成员列表工具类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-28
|
||||
*/
|
||||
public class ChatMemberHelper {
|
||||
private static final String SEPARATOR = "_";
|
||||
|
||||
public static Pair<ChatActiveStatusEnum, String> getCursorPair(String cursor) {
|
||||
ChatActiveStatusEnum activeStatusEnum = ChatActiveStatusEnum.ONLINE;
|
||||
String timeCursor = null;
|
||||
if (StrUtil.isNotBlank(cursor)) {
|
||||
String activeStr = cursor.split(SEPARATOR)[0];
|
||||
String timeStr = cursor.split(SEPARATOR)[1];
|
||||
activeStatusEnum = ChatActiveStatusEnum.of(Integer.parseInt(activeStr));
|
||||
timeCursor = timeStr;
|
||||
}
|
||||
return Pair.of(activeStatusEnum, timeCursor);
|
||||
}
|
||||
|
||||
public static String generateCursor(ChatActiveStatusEnum activeStatusEnum, String timeCursor) {
|
||||
return activeStatusEnum.getStatus() + SEPARATOR + timeCursor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.abin.mallchat.custom.chat.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
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.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.common.domain.enums.YesOrNoEnum;
|
||||
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.common.exception.BusinessException;
|
||||
import com.abin.mallchat.common.common.utils.AssertUtil;
|
||||
import com.abin.mallchat.common.user.dao.UserDao;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum;
|
||||
import com.abin.mallchat.common.user.service.cache.UserCache;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.MemberAdapter;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.RoomAdapter;
|
||||
import com.abin.mallchat.custom.chat.service.helper.ChatMemberHelper;
|
||||
import com.abin.mallchat.custom.common.event.MessageMarkEvent;
|
||||
import com.abin.mallchat.custom.common.event.MessageSendEvent;
|
||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.MessageAdapter;
|
||||
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 java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Description: 消息处理类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ChatServiceImpl implements ChatService {
|
||||
public static final long ROOM_GROUP_ID = 1L;
|
||||
@Autowired
|
||||
private MessageDao messageDao;
|
||||
@Autowired
|
||||
private UserDao userDao;
|
||||
@Autowired
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
@Autowired
|
||||
private UserCache userCache;
|
||||
@Autowired
|
||||
private MemberAdapter memberAdapter;
|
||||
@Autowired
|
||||
private RoomDao roomDao;
|
||||
@Autowired
|
||||
private MessageMarkDao messageMarkDao;
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Long sendMsg(ChatMessageReq request, Long uid) {
|
||||
//校验下回复消息
|
||||
Message replyMsg =null;
|
||||
if(Objects.nonNull(request.getReplyMsgId())){
|
||||
replyMsg = messageDao.getById(request.getReplyMsgId());
|
||||
AssertUtil.isNotEmpty(replyMsg,"回复消息不存在");
|
||||
AssertUtil.equal(replyMsg.getRoomId(),request.getRoomId(),"只能回复相同会话内的消息");
|
||||
|
||||
}
|
||||
Message insert = MessageAdapter.buildMsgSave(request, uid);
|
||||
messageDao.save(insert);
|
||||
//如果有回复消息
|
||||
if(Objects.nonNull(replyMsg)){
|
||||
Integer gapCount = messageDao.getGapCount(request.getRoomId(), replyMsg.getId(), insert.getId());
|
||||
messageDao.updateGapCount(insert.getId(),gapCount);
|
||||
}
|
||||
//发布消息发送事件
|
||||
applicationEventPublisher.publishEvent(new MessageSendEvent(this, insert.getId()));
|
||||
return insert.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatMessageResp getMsgResp(Message message, Long receiveUid) {
|
||||
return CollUtil.getFirst(getMsgRespBatch(Collections.singletonList(message), receiveUid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPageBaseResp<ChatMemberResp> getMemberPage(CursorPageBaseReq request) {
|
||||
Pair<ChatActiveStatusEnum, String> pair = ChatMemberHelper.getCursorPair(request.getCursor());
|
||||
ChatActiveStatusEnum activeStatusEnum = pair.getKey();
|
||||
String timeCursor = pair.getValue();
|
||||
List<ChatMemberResp> resultList = new ArrayList<>();//最终列表
|
||||
Boolean isLast = Boolean.FALSE;
|
||||
if (activeStatusEnum == ChatActiveStatusEnum.ONLINE) {//在线列表
|
||||
CursorPageBaseResp<Pair<Long, Double>> cursorPage = userCache.getOnlineCursorPage(new CursorPageBaseReq(request.getPageSize(), timeCursor));
|
||||
resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.ONLINE));//添加在线列表
|
||||
if (cursorPage.getIsLast()) {//如果是最后一页,从离线列表再补点数据
|
||||
Integer leftSize = request.getPageSize() - cursorPage.getList().size();
|
||||
cursorPage = userCache.getOfflineCursorPage(new CursorPageBaseReq(leftSize, null));
|
||||
resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.OFFLINE));//添加离线线列表
|
||||
activeStatusEnum = ChatActiveStatusEnum.OFFLINE;
|
||||
}
|
||||
timeCursor = cursorPage.getCursor();
|
||||
isLast = cursorPage.getIsLast();
|
||||
} else if (activeStatusEnum == ChatActiveStatusEnum.OFFLINE) {//离线列表
|
||||
CursorPageBaseResp<Pair<Long, Double>> cursorPage = userCache.getOfflineCursorPage(new CursorPageBaseReq(request.getPageSize(), timeCursor));
|
||||
resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.OFFLINE));//添加离线线列表
|
||||
timeCursor = cursorPage.getCursor();
|
||||
isLast = cursorPage.getIsLast();
|
||||
}
|
||||
//组装结果
|
||||
return new CursorPageBaseResp<>(ChatMemberHelper.generateCursor(activeStatusEnum, timeCursor), isLast, resultList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq request, Long receiveUid) {
|
||||
CursorPageBaseResp<Message> cursorPage = messageDao.getCursorPage(request.getRoomId(), request);
|
||||
if (cursorPage.isEmpty()) {
|
||||
return CursorPageBaseResp.empty();
|
||||
}
|
||||
return CursorPageBaseResp.init(cursorPage, getMsgRespBatch(cursorPage.getList(), receiveUid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPageBaseResp<ChatRoomResp> getRoomPage(CursorPageBaseReq request, Long uid) {
|
||||
CursorPageBaseResp<Room> cursorPage = roomDao.getCursorPage(request);
|
||||
if (request.isFirstPage()) {
|
||||
//第一页插入置顶的大群聊
|
||||
Room group = roomDao.getById(ROOM_GROUP_ID);
|
||||
cursorPage.getList().add(0, group);
|
||||
}
|
||||
return CursorPageBaseResp.init(cursorPage, RoomAdapter.buildResp(cursorPage.getList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatMemberStatisticResp getMemberStatistic() {
|
||||
System.out.println(Thread.currentThread().getName());
|
||||
Long onlineNum = userCache.getOnlineNum();
|
||||
Long offlineNum = userCache.getOfflineNum();
|
||||
ChatMemberStatisticResp resp = new ChatMemberStatisticResp();
|
||||
resp.setOnlineNum(onlineNum);
|
||||
resp.setTotalNum(onlineNum + offlineNum);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMsgMark(Long uid, ChatMessageMarkReq request) {
|
||||
//用户对该消息的标记
|
||||
MessageMark messageMark = messageMarkDao.get(uid, request.getMsgId(), request.getMarkType());
|
||||
if (Objects.nonNull(messageMark)) {//有标记过消息修改一下就好
|
||||
MessageMark update = MessageMark.builder()
|
||||
.id(messageMark.getId())
|
||||
.status(transformAct(request.getActType()))
|
||||
.build();
|
||||
messageMarkDao.updateById(update);
|
||||
}
|
||||
//没标记过消息,插入一条新消息
|
||||
MessageMark insert = MessageMark.builder()
|
||||
.uid(uid)
|
||||
.msgId(request.getMsgId())
|
||||
.type(request.getMarkType())
|
||||
.status(transformAct(request.getActType()))
|
||||
.build();
|
||||
messageMarkDao.save(insert);
|
||||
//发布消息标记事件
|
||||
applicationEventPublisher.publishEvent(new MessageMarkEvent(this,request));
|
||||
}
|
||||
|
||||
private Integer transformAct(Integer actType) {
|
||||
if (actType == 1) {
|
||||
return YesOrNoEnum.NO.getStatus();
|
||||
} else if (actType == 2) {
|
||||
return YesOrNoEnum.YES.getStatus();
|
||||
}
|
||||
throw new BusinessException("动作类型 1确认 2取消");
|
||||
}
|
||||
|
||||
public List<ChatMessageResp> getMsgRespBatch(List<Message> messages, Long receiveUid) {
|
||||
if (CollectionUtil.isEmpty(messages)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Map<Long, Message> replyMap = new HashMap<>();
|
||||
Map<Long, User> userMap = new HashMap<>();
|
||||
//批量查出回复的消息
|
||||
List<Long> replyIds = messages.stream().map(Message::getReplyMsgId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(replyIds)) {
|
||||
replyMap = messageDao.listByIds(replyIds).stream().collect(Collectors.toMap(Message::getId, Function.identity()));
|
||||
}
|
||||
//批量查询消息关联用户
|
||||
Set<Long> uidSet = Stream.concat(replyMap.values().stream().map(Message::getFromUid), messages.stream().map(Message::getFromUid)).collect(Collectors.toSet());
|
||||
userMap = userCache.getUserInfoBatch(uidSet);
|
||||
//查询消息标志
|
||||
List<MessageMark> msgMark = messageMarkDao.getValidMarkByMsgIdBatch(messages.stream().map(Message::getId).collect(Collectors.toList()));
|
||||
return MessageAdapter.buildMsgResp(messages, replyMap, userMap,msgMark,receiveUid);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user