fix:联系人优化

This commit is contained in:
zhongzb
2023-07-23 11:23:11 +08:00
parent 77a92a0cc5
commit f16db31e28
76 changed files with 1751 additions and 290 deletions

View File

@@ -28,7 +28,7 @@ CREATE TABLE `user_apply` (
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uniq_target_id_uid` (`target_id`,`uid`) USING BTREE,
KEY `idx_target_id_uid_status` (`target_id`,`uid`,`status`) USING BTREE,
KEY `idx_target_id` (`target_id`) USING BTREE,
KEY `idx_create_time` (`create_time`) USING BTREE,
KEY `idx_update_time` (`update_time`) USING BTREE
@@ -38,10 +38,11 @@ CREATE TABLE `user_friend` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`uid` bigint(20) NOT NULL COMMENT 'uid',
`friend_uid` bigint(20) NOT NULL COMMENT '好友uid',
`delete_status` int(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-正常,1-删除)',
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uniq_uid_friend_uid` (`uid`,`friend_uid`) USING BTREE,
KEY `idx_uid_friend_uid` (`uid`,`friend_uid`) USING BTREE,
KEY `idx_create_time` (`create_time`) USING BTREE,
KEY `idx_update_time` (`update_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户联系人表';
@@ -50,7 +51,7 @@ CREATE TABLE `group_member` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`room_id` bigint(20) NOT NULL COMMENT '房间id',
`uid` bigint(20) NOT NULL COMMENT '成员uid',
`type` int(11) NOT NULL COMMENT '成员类型 1群主 2管理员 3普通成员',
`role` int(11) NOT NULL COMMENT '成员角色 1群主 2管理员 3普通成员',
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,

View File

@@ -1,20 +0,0 @@
package com.abin.mallchat.common.chat.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* <p>
* 会话列表 前端控制器
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-16
*/
@Controller
@RequestMapping("/contact")
public class ContactController {
}

View File

@@ -1,20 +0,0 @@
package com.abin.mallchat.common.chat.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* <p>
* 群成员表 前端控制器
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-16
*/
@Controller
@RequestMapping("/groupMember")
public class GroupMemberController {
}

View File

@@ -1,20 +0,0 @@
package com.abin.mallchat.common.chat.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* <p>
* 房间表 前端控制器
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-16
*/
@Controller
@RequestMapping("/room")
public class RoomController {
}

View File

@@ -1,8 +1,11 @@
package com.abin.mallchat.common.chat.dao;
import com.abin.mallchat.common.chat.domain.entity.Contact;
import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.mapper.ContactMapper;
import com.abin.mallchat.common.chat.service.IContactService;
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.utils.CursorUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@@ -15,6 +18,57 @@ import org.springframework.stereotype.Service;
* @since 2023-07-16
*/
@Service
public class ContactDao extends ServiceImpl<ContactMapper, Contact> implements IContactService {
public class ContactDao extends ServiceImpl<ContactMapper, Contact> {
public Contact get(Long uid, Long roomId) {
return lambdaQuery()
.eq(Contact::getUid, uid)
.eq(Contact::getRoomId, roomId)
.one();
}
public Integer getReadCount(Message message) {
return lambdaQuery()
.eq(Contact::getRoomId, message.getRoomId())
.ge(Contact::getReadTime, message.getCreateTime())
.count();
}
public Integer getTotalCount(Long roomId) {
return lambdaQuery()
.eq(Contact::getRoomId, roomId)
.count();
}
public Integer getUnReadCount(Message message) {
return lambdaQuery()
.eq(Contact::getRoomId, message.getRoomId())
.lt(Contact::getReadTime, message.getCreateTime())
.count();
}
public CursorPageBaseResp<Contact> getReadPage(Message message, CursorPageBaseReq cursorPageBaseReq) {
return CursorUtils.getCursorPageByMysql(this, cursorPageBaseReq, wrapper -> {
wrapper.eq(Contact::getRoomId, message.getRoomId());
wrapper.ne(Contact::getUid, message.getFromUid());//不需要查询出自己
wrapper.ge(Contact::getReadTime, message.getCreateTime());//已读时间大于等于消息发送时间
}, Contact::getReadTime);
}
public CursorPageBaseResp<Contact> getUnReadPage(Message message, CursorPageBaseReq cursorPageBaseReq) {
return CursorUtils.getCursorPageByMysql(this, cursorPageBaseReq, wrapper -> {
wrapper.eq(Contact::getRoomId, message.getRoomId());
wrapper.ne(Contact::getUid, message.getFromUid());//不需要查询出自己
wrapper.lt(Contact::getReadTime, message.getCreateTime());//已读时间小于消息发送时间
}, Contact::getReadTime);
}
/**
* 获取用户会话列表
*/
public CursorPageBaseResp<Contact> getContactPage(Long uid, CursorPageBaseReq request) {
return CursorUtils.getCursorPageByMysql(this, request, wrapper -> {
wrapper.eq(Contact::getUid, uid);
}, Contact::getActiveTime);
}
}

View File

@@ -7,7 +7,6 @@ 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.utils.CursorUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
@@ -20,11 +19,9 @@ import org.springframework.stereotype.Service;
*/
@Service
public class MessageDao extends ServiceImpl<MessageMapper, Message> {
@Autowired
private CursorUtils cursorUtils;
public CursorPageBaseResp<Message> getCursorPage(Long roomId, CursorPageBaseReq request) {
return cursorUtils.getCursorPageByMysql(this, request, wrapper -> {
return CursorUtils.getCursorPageByMysql(this, request, wrapper -> {
wrapper.eq(Message::getRoomId, roomId);
wrapper.eq(Message::getStatus, MessageStatusEnum.NORMAL.getStatus());
}, Message::getId);

View File

@@ -2,7 +2,7 @@ package com.abin.mallchat.common.chat.dao;
import com.abin.mallchat.common.chat.domain.entity.Room;
import com.abin.mallchat.common.chat.mapper.RoomMapper;
import com.abin.mallchat.common.chat.service.IRoomService;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@@ -15,6 +15,6 @@ import org.springframework.stereotype.Service;
* @since 2023-07-16
*/
@Service
public class RoomDao extends ServiceImpl<RoomMapper, Room> implements IRoomService {
public class RoomDao extends ServiceImpl<RoomMapper, Room> implements IService<Room> {
}

View File

@@ -0,0 +1,38 @@
package com.abin.mallchat.common.chat.dao;
import com.abin.mallchat.common.chat.domain.entity.RoomFriend;
import com.abin.mallchat.common.chat.mapper.RoomFriendMapper;
import com.abin.mallchat.common.common.domain.enums.NormalOrNoEnum;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 单聊房间表 服务实现类
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-22
*/
@Service
public class RoomFriendDao extends ServiceImpl<RoomFriendMapper, RoomFriend> {
public RoomFriend getByKey(String key) {
return lambdaQuery().eq(RoomFriend::getKey, key).one();
}
public void restoreRoom(Long id) {
lambdaUpdate()
.eq(RoomFriend::getId, id)
.set(RoomFriend::getStatus, NormalOrNoEnum.NORMAL.getStatus())
.update();
}
public List<RoomFriend> listByRoomIds(List<Long> roomIds) {
return lambdaQuery()
.eq(RoomFriend::getRoomId, roomIds)
.list();
}
}

View File

@@ -0,0 +1,26 @@
package com.abin.mallchat.common.chat.dao;
import com.abin.mallchat.common.chat.domain.entity.RoomGroup;
import com.abin.mallchat.common.chat.mapper.RoomGroupMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 群聊房间表 服务实现类
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-22
*/
@Service
public class RoomGroupDao extends ServiceImpl<RoomGroupMapper, RoomGroup> {
public List<RoomGroup> listByRoomIds(List<Long> roomIds) {
return lambdaQuery()
.in(RoomGroup::getRoomId, roomIds)
.list();
}
}

View File

@@ -0,0 +1,28 @@
package com.abin.mallchat.common.chat.domain.dto;
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 MsgReadInfoDTO {
@ApiModelProperty("消息id")
private Long msgId;
@ApiModelProperty("已读数")
private Integer readCount;
@ApiModelProperty("未读数")
private Integer unReadCount;
}

View File

@@ -0,0 +1,19 @@
package com.abin.mallchat.common.chat.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* Description: 房间详情
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
@Data
public class RoomBaseInfo {
@ApiModelProperty("房间id")
private Long roomId;
@ApiModelProperty("会话名称")
private String name;
@ApiModelProperty("会话头像")
private String avatar;
}

View File

@@ -38,22 +38,10 @@ public class Contact implements Serializable {
private Long uid;
/**
* 聊天类型 1单聊 2普通群聊
* 房间id
*/
@TableField("type")
private Integer type;
/**
* 是否全员展示 0否 1是
*/
@TableField("hot_flag")
private Integer hotFlag;
/**
* 聊天对象type=1:uidtype=2:房间id
*/
@TableField("target_id")
private Long targetId;
@TableField("room_id")
private Long roomId;
/**
* 阅读到的时间
@@ -67,6 +55,12 @@ public class Contact implements Serializable {
@TableField("active_time")
private LocalDateTime activeTime;
/**
* 最后一条消息id
*/
@TableField("last_msg_id")
private Long lastMsgId;
/**
* 创建时间
*/

View File

@@ -44,10 +44,10 @@ public class GroupMember implements Serializable {
private Long uid;
/**
* 成员类型 1群主 2管理员 3普通成员
* 成员角色1群主 2管理员 3普通成员
*/
@TableField("type")
private Integer type;
@TableField("role")
private Integer role;
/**
* 创建时间

View File

@@ -24,7 +24,6 @@ import java.time.LocalDateTime;
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@@ -32,25 +31,15 @@ public class Room implements Serializable {
private Long id;
/**
* 群名称
*/
@TableField("name")
private String name;
/**
* 群头像
*/
@TableField("avatar")
private String avatar;
/**
* 房间类型 1群聊
* 房间类型 1群聊 2单聊
*/
@TableField("type")
private Integer type;
/**
* 是否全员展示 0否 1是
*
* @see com.abin.mallchat.common.chat.domain.enums.HotFlagEnum
*/
@TableField("hot_flag")
private Integer hotFlag;

View File

@@ -0,0 +1,76 @@
package com.abin.mallchat.common.chat.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 单聊房间表
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-22
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("room_friend")
public class RoomFriend implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 房间id
*/
@TableField("room_id")
private Long roomId;
/**
* uid1更小的uid
*/
@TableField("uid1")
private Long uid1;
/**
* uid2更大的uid
*/
@TableField("uid2")
private Long uid2;
/**
* 房间key由两个uid拼接先做排序uid1_uid2
*/
@TableField("key")
private String key;
/**
* 房间状态 0正常 1禁用(删好友了禁用)
*/
@TableField("status")
private Integer status;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 修改时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,75 @@
package com.abin.mallchat.common.chat.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 群聊房间表
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-22
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("room_group")
public class RoomGroup implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 房间id
*/
@TableField("room_id")
private Long roomId;
/**
* 群名称
*/
@TableField("name")
private String name;
/**
* 群头像
*/
@TableField("avatar")
private String avatar;
/**
* 额外信息(根据不同类型房间有不同存储的东西)
*/
@TableField("ext_json")
private String extJson;
/**
* 逻辑删除(0-正常,1-删除)
*/
@TableField("delete_status")
@TableLogic(value = "0", delval = "1")
private Integer deleteStatus;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 修改时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,35 @@
package com.abin.mallchat.common.chat.domain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Description: 热点枚举
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@AllArgsConstructor
@Getter
public enum HotFlagEnum {
NOT(0, "非热点"),
YES(1, "热点"),
;
private final Integer type;
private final String desc;
private static Map<Integer, HotFlagEnum> cache;
static {
cache = Arrays.stream(HotFlagEnum.values()).collect(Collectors.toMap(HotFlagEnum::getType, Function.identity()));
}
public static HotFlagEnum of(Integer type) {
return cache.get(type);
}
}

View File

@@ -16,17 +16,17 @@ import java.util.stream.Collectors;
@AllArgsConstructor
@Getter
public enum RoomTypeEnum {
GROUP(1, "群聊"),
BOILING(2, "沸点"),
GROUP(1, "群聊"),
FRIEND(2, "单聊"),
;
private final Integer status;
private final Integer type;
private final String desc;
private static Map<Integer, RoomTypeEnum> cache;
static {
cache = Arrays.stream(RoomTypeEnum.values()).collect(Collectors.toMap(RoomTypeEnum::getStatus, Function.identity()));
cache = Arrays.stream(RoomTypeEnum.values()).collect(Collectors.toMap(RoomTypeEnum::getType, Function.identity()));
}
public static RoomTypeEnum of(Integer type) {

View File

@@ -0,0 +1,16 @@
package com.abin.mallchat.common.chat.mapper;
import com.abin.mallchat.common.chat.domain.entity.RoomFriend;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 单聊房间表 Mapper 接口
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-22
*/
public interface RoomFriendMapper extends BaseMapper<RoomFriend> {
}

View File

@@ -0,0 +1,16 @@
package com.abin.mallchat.common.chat.mapper;
import com.abin.mallchat.common.chat.domain.entity.RoomGroup;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 群聊房间表 Mapper 接口
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-22
*/
public interface RoomGroupMapper extends BaseMapper<RoomGroup> {
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.abin.mallchat.common.chat.mapper.RoomFriendMapper">
</mapper>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.abin.mallchat.common.chat.mapper.RoomGroupMapper">
</mapper>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.abin.mallchat.common.chat.mapper.RoomMapper">
</mapper>

View File

@@ -0,0 +1,29 @@
package com.abin.mallchat.common.chat.service;
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 java.util.List;
import java.util.Map;
/**
* <p>
* 会话列表 服务类
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-16
*/
public interface ContactService {
/**
* 创建会话
*/
Contact createContact(Long uid, Long roomId);
Integer getMsgReadCount(Message message);
Integer getMsgUnReadCount(Message message);
Map<Long, MsgReadInfoDTO> getMsgReadInfo(List<Message> messages);
}

View File

@@ -1,16 +0,0 @@
package com.abin.mallchat.common.chat.service;
import com.abin.mallchat.common.chat.domain.entity.Contact;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 会话列表 服务类
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-16
*/
public interface IContactService extends IService<Contact> {
}

View File

@@ -1,16 +0,0 @@
package com.abin.mallchat.common.chat.service;
import com.abin.mallchat.common.chat.domain.entity.Room;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 房间表 服务类
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-07-16
*/
public interface IRoomService extends IService<Room> {
}

View File

@@ -1,16 +0,0 @@
package com.abin.mallchat.common.chat.service;
import com.abin.mallchat.common.chat.domain.entity.WxMsg;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 微信消息表 服务类
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-05-16
*/
public interface IWxMsgService extends IService<WxMsg> {
}

View File

@@ -0,0 +1,18 @@
package com.abin.mallchat.common.chat.service;
import com.abin.mallchat.common.chat.domain.entity.RoomFriend;
import java.util.List;
/**
* Description: 房间底层管理
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
public interface RoomService {
/**
* 创建一个单聊房间
*/
RoomFriend createFriendRoom(List<Long> uidList);
}

View File

@@ -0,0 +1,68 @@
package com.abin.mallchat.common.chat.service.adapter;
import com.abin.mallchat.common.chat.domain.entity.Contact;
import com.abin.mallchat.common.chat.domain.entity.Room;
import com.abin.mallchat.common.chat.domain.entity.RoomFriend;
import com.abin.mallchat.common.chat.domain.enums.HotFlagEnum;
import com.abin.mallchat.common.chat.domain.enums.RoomTypeEnum;
import com.abin.mallchat.common.common.domain.enums.NormalOrNoEnum;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
public class ChatAdapter {
public static final String SEPARATOR = ",";
public static String generateRoomKey(List<Long> uidList) {
return uidList.stream()
.sorted()
.map(String::valueOf)
.collect(Collectors.joining(SEPARATOR));
}
public static Room buildRoom(RoomTypeEnum typeEnum) {
Room room = new Room();
room.setType(typeEnum.getType());
room.setHotFlag(HotFlagEnum.NOT.getType());
return room;
}
public static RoomFriend buildFriendRoom(Long roomId, List<Long> uidList) {
List<Long> collect = uidList.stream().sorted().collect(Collectors.toList());
RoomFriend roomFriend = new RoomFriend();
roomFriend.setRoomId(roomId);
roomFriend.setUid1(collect.get(0));
roomFriend.setUid2(collect.get(1));
roomFriend.setKey(generateRoomKey(uidList));
roomFriend.setStatus(NormalOrNoEnum.NORMAL.getStatus());
return roomFriend;
}
public static Contact buildContact(Long uid, Long roomId) {
Contact contact = new Contact();
contact.setRoomId(roomId);
contact.setUid(uid);
return contact;
}
public static Set<Long> getFriendUidSet(Collection<RoomFriend> values, Long uid) {
return values.stream()
.map(a -> getFriendUid(a, uid))
.collect(Collectors.toSet());
}
/**
* 获取好友uid
*/
public static Long getFriendUid(RoomFriend roomFriend, Long uid) {
return Objects.equals(uid, roomFriend.getUid1()) ? roomFriend.getUid2() : roomFriend.getUid1();
}
}

View File

@@ -0,0 +1,46 @@
package com.abin.mallchat.common.chat.service.cache;
import com.abin.mallchat.common.chat.dao.RoomDao;
import com.abin.mallchat.common.chat.dao.RoomFriendDao;
import com.abin.mallchat.common.chat.domain.entity.Room;
import com.abin.mallchat.common.common.constant.RedisKey;
import com.abin.mallchat.common.common.service.cache.AbstractRedisStringCache;
import com.abin.mallchat.common.user.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Description: 房间基本信息的缓存
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-06-10
*/
@Component
public class RoomCache extends AbstractRedisStringCache<Long, Room> {
@Autowired
private UserDao userDao;
@Autowired
private RoomDao roomDao;
@Autowired
private RoomFriendDao roomFriendDao;
@Override
protected String getKey(Long roomId) {
return RedisKey.getKey(RedisKey.ROOM_INFO_STRING, roomId);
}
@Override
protected Long getExpireSeconds() {
return 5 * 60L;
}
@Override
protected Map<Long, Room> load(List<Long> roomIds) {
List<Room> rooms = roomDao.listByIds(roomIds);
return rooms.stream().collect(Collectors.toMap(Room::getId, Function.identity()));
}
}

View File

@@ -0,0 +1,40 @@
package com.abin.mallchat.common.chat.service.cache;
import com.abin.mallchat.common.chat.dao.RoomFriendDao;
import com.abin.mallchat.common.chat.domain.entity.RoomFriend;
import com.abin.mallchat.common.common.constant.RedisKey;
import com.abin.mallchat.common.common.service.cache.AbstractRedisStringCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Description: 群组基本信息的缓存
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-06-10
*/
@Component
public class RoomFriendCache extends AbstractRedisStringCache<Long, RoomFriend> {
@Autowired
private RoomFriendDao roomFriendDao;
@Override
protected String getKey(Long groupId) {
return RedisKey.getKey(RedisKey.GROUP_INFO_STRING, groupId);
}
@Override
protected Long getExpireSeconds() {
return 5 * 60L;
}
@Override
protected Map<Long, RoomFriend> load(List<Long> roomIds) {
List<RoomFriend> roomGroups = roomFriendDao.listByRoomIds(roomIds);
return roomGroups.stream().collect(Collectors.toMap(RoomFriend::getRoomId, Function.identity()));
}
}

View File

@@ -0,0 +1,40 @@
package com.abin.mallchat.common.chat.service.cache;
import com.abin.mallchat.common.chat.dao.RoomGroupDao;
import com.abin.mallchat.common.chat.domain.entity.RoomGroup;
import com.abin.mallchat.common.common.constant.RedisKey;
import com.abin.mallchat.common.common.service.cache.AbstractRedisStringCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Description: 群组基本信息的缓存
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-06-10
*/
@Component
public class RoomGroupCache extends AbstractRedisStringCache<Long, RoomGroup> {
@Autowired
private RoomGroupDao roomGroupDao;
@Override
protected String getKey(Long groupId) {
return RedisKey.getKey(RedisKey.GROUP_INFO_STRING, groupId);
}
@Override
protected Long getExpireSeconds() {
return 5 * 60L;
}
@Override
protected Map<Long, RoomGroup> load(List<Long> roomIds) {
List<RoomGroup> roomGroups = roomGroupDao.listByRoomIds(roomIds);
return roomGroups.stream().collect(Collectors.toMap(RoomGroup::getRoomId, Function.identity()));
}
}

View File

@@ -0,0 +1,68 @@
package com.abin.mallchat.common.chat.service.impl;
import com.abin.mallchat.common.chat.dao.ContactDao;
import com.abin.mallchat.common.chat.dao.MessageDao;
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.service.ContactService;
import com.abin.mallchat.common.chat.service.adapter.ChatAdapter;
import com.abin.mallchat.common.common.utils.AssertUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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 ContactServiceImpl implements ContactService {
@Autowired
private ContactDao contactDao;
@Autowired
private MessageDao messageDao;
@Override
public Contact createContact(Long uid, Long roomId) {
Contact contact = contactDao.get(uid, roomId);
if (Objects.isNull(contact)) {
contact = ChatAdapter.buildContact(uid, roomId);
contactDao.save(contact);
}
return contact;
}
@Override
public Integer getMsgReadCount(Message message) {
return contactDao.getReadCount(message);
}
@Override
public Integer getMsgUnReadCount(Message message) {
return contactDao.getUnReadCount(message);
}
@Override
public Map<Long, MsgReadInfoDTO> getMsgReadInfo(List<Message> messages) {
Map<Long, List<Message>> roomGroup = messages.stream().collect(Collectors.groupingBy(Message::getRoomId));
AssertUtil.equal(roomGroup.size(), 1, "只能查相同房间下的消息");
Long roomId = roomGroup.keySet().iterator().next();
Integer totalCount = contactDao.getTotalCount(roomId);
return messages.stream().map(message -> {
MsgReadInfoDTO readInfoDTO = new MsgReadInfoDTO();
readInfoDTO.setMsgId(message.getId());
Integer readCount = contactDao.getReadCount(message);
readInfoDTO.setReadCount(readCount);
readInfoDTO.setUnReadCount(totalCount - readCount);
return readInfoDTO;
}).collect(Collectors.toMap(MsgReadInfoDTO::getMsgId, Function.identity()));
}
}

View File

@@ -0,0 +1,66 @@
package com.abin.mallchat.common.chat.service.impl;
import com.abin.mallchat.common.chat.dao.RoomDao;
import com.abin.mallchat.common.chat.dao.RoomFriendDao;
import com.abin.mallchat.common.chat.domain.entity.Room;
import com.abin.mallchat.common.chat.domain.entity.RoomFriend;
import com.abin.mallchat.common.chat.domain.enums.RoomTypeEnum;
import com.abin.mallchat.common.chat.service.RoomService;
import com.abin.mallchat.common.chat.service.adapter.ChatAdapter;
import com.abin.mallchat.common.common.domain.enums.NormalOrNoEnum;
import com.abin.mallchat.common.common.utils.AssertUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-07-22
*/
@Service
public class RoomServiceImpl implements RoomService {
@Autowired
private RoomFriendDao roomFriendDao;
@Autowired
private RoomDao roomDao;
@Override
@Transactional(rollbackFor = Exception.class)
public RoomFriend createFriendRoom(List<Long> uidList) {
AssertUtil.isNotEmpty(uidList, "房间创建失败,好友数量不对");
AssertUtil.equal(uidList.size(), 2, "房间创建失败,好友数量不对");
String key = ChatAdapter.generateRoomKey(uidList);
RoomFriend roomFriend = roomFriendDao.getByKey(key);
if (Objects.nonNull(roomFriend)) { //如果存在房间就恢复,适用于恢复好友场景
restoreRoomIfNeed(roomFriend);
} else {//新建房间
Room room = createRoom(RoomTypeEnum.GROUP);
roomFriend = createFriendRoom(room.getId(), uidList);
}
return roomFriend;
}
private RoomFriend createFriendRoom(Long roomId, List<Long> uidList) {
RoomFriend insert = ChatAdapter.buildFriendRoom(roomId, uidList);
roomFriendDao.save(insert);
return insert;
}
private Room createRoom(RoomTypeEnum typeEnum) {
Room insert = ChatAdapter.buildRoom(typeEnum);
roomDao.save(insert);
return insert;
}
private void restoreRoomIfNeed(RoomFriend room) {
if (Objects.equals(room.getStatus(), NormalOrNoEnum.NOT_NORMAL.getStatus())) {
roomFriendDao.restoreRoom(room.getId());
}
}
}

View File

@@ -21,6 +21,16 @@ public class RedisKey {
*/
public static final String USER_INFO_STRING = "userInfo:uid_%d";
/**
* 房间详情
*/
public static final String ROOM_INFO_STRING = "roomInfo:roomId_%d";
/**
* 群组详情
*/
public static final String GROUP_INFO_STRING = "groupInfo:roomId_%d";
/**
* 用户token存放
*/

View File

@@ -36,13 +36,13 @@ public class PageBaseResp<T> {
private List<T> list;
public static PageBaseResp empty() {
PageBaseResp r = new PageBaseResp();
public static <T> PageBaseResp<T> empty() {
PageBaseResp<T> r = new PageBaseResp<>();
r.setPageNo(1);
r.setPageSize(0);
r.setIsLast(true);
r.setTotalRecords(0L);
r.setList(new ArrayList());
r.setList(new ArrayList<>());
return r;
}

View File

@@ -0,0 +1,16 @@
package com.abin.mallchat.common.common.event;
import com.abin.mallchat.common.user.domain.entity.UserApply;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class UserApplyEvent extends ApplicationEvent {
private UserApply userApply;
public UserApplyEvent(Object source, UserApply userApply) {
super(source);
this.userApply = userApply;
}
}

View File

@@ -35,6 +35,9 @@ public abstract class AbstractRedisStringCache<IN, OUT> implements BatchCache<IN
@Override
public Map<IN, OUT> getBatch(List<IN> req) {
if (CollectionUtil.isEmpty(req)) {//防御性编程
return new HashMap<>();
}
req = req.stream().distinct().collect(Collectors.toList());
List<String> keys = req.stream().map(this::getKey).collect(Collectors.toList());
List<OUT> valueList = RedisUtils.mget(keys, outClass);

View File

@@ -10,7 +10,6 @@ import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@@ -24,10 +23,9 @@ import java.util.stream.Collectors;
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-28
*/
@Component
public class CursorUtils {
public <T> CursorPageBaseResp<Pair<T, Double>> getCursorPageByRedis(CursorPageBaseReq cursorPageBaseReq, String redisKey, Function<String, T> typeConvert) {
public static <T> CursorPageBaseResp<Pair<T, Double>> getCursorPageByRedis(CursorPageBaseReq cursorPageBaseReq, String redisKey, Function<String, T> typeConvert) {
Set<ZSetOperations.TypedTuple<String>> typedTuples;
if (StrUtil.isBlank(cursorPageBaseReq.getCursor())) {//第一次
typedTuples = RedisUtils.zReverseRangeWithScores(redisKey, cursorPageBaseReq.getPageSize());
@@ -47,7 +45,7 @@ public class CursorUtils {
return new CursorPageBaseResp<>(cursor, isLast, result);
}
public <T> CursorPageBaseResp<T> getCursorPageByMysql(IService<T> mapper, CursorPageBaseReq request, Consumer<LambdaQueryWrapper<T>> initWrapper, SFunction<T, ?> cursorColumn) {
public static <T> CursorPageBaseResp<T> getCursorPageByMysql(IService<T> mapper, CursorPageBaseReq request, Consumer<LambdaQueryWrapper<T>> initWrapper, SFunction<T, ?> cursorColumn) {
LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
initWrapper.accept(wrapper);
if (StrUtil.isNotBlank(request.getCursor())) {

View File

@@ -1,11 +1,20 @@
package com.abin.mallchat.common.user.dao;
import com.abin.mallchat.common.user.domain.entity.UserApply;
import com.abin.mallchat.common.user.domain.enums.ApplyStatusEnum;
import com.abin.mallchat.common.user.domain.enums.ApplyTypeEnum;
import com.abin.mallchat.common.user.mapper.UserApplyMapper;
import com.abin.mallchat.common.user.service.IUserApplyService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
import static com.abin.mallchat.common.user.domain.enums.ApplyReadStatusEnum.READ;
import static com.abin.mallchat.common.user.domain.enums.ApplyReadStatusEnum.UNREAD;
import static com.abin.mallchat.common.user.domain.enums.ApplyStatusEnum.AGREE;
/**
* <p>
* 用户申请表 服务实现类
@@ -15,6 +24,42 @@ import org.springframework.stereotype.Service;
* @since 2023-07-16
*/
@Service
public class UserApplyDao extends ServiceImpl<UserApplyMapper, UserApply> implements IUserApplyService {
public class UserApplyDao extends ServiceImpl<UserApplyMapper, UserApply> {
public UserApply getFriendApproving(Long uid, Long targetUid) {
return lambdaQuery().eq(UserApply::getUid, uid)
.eq(UserApply::getTargetId, targetUid)
.eq(UserApply::getStatus, ApplyStatusEnum.WAIT_APPROVAL)
.eq(UserApply::getType, ApplyTypeEnum.ADD_FRIEND.getCode())
.one();
}
public Integer getUnReadCount(Long targetId) {
return lambdaQuery().eq(UserApply::getTargetId, targetId)
.eq(UserApply::getReadStatus, UNREAD.getCode())
.count();
}
public IPage<UserApply> FriendApplyPage(Long uid, Page page) {
return lambdaQuery()
.eq(UserApply::getTargetId, uid)
.eq(UserApply::getType, ApplyTypeEnum.ADD_FRIEND.getCode())
.orderByAsc(UserApply::getCreateTime)
.page(page);
}
public void readApples(Long uid, List<Long> applyIds) {
lambdaUpdate()
.set(UserApply::getReadStatus, READ.getCode())
.eq(UserApply::getReadStatus, UNREAD.getCode())
.in(UserApply::getId, applyIds)
.eq(UserApply::getTargetId, uid)
.update();
}
public void agree(Long applyId) {
lambdaUpdate().set(UserApply::getStatus, AGREE.getCode())
.eq(UserApply::getId, applyId)
.update();
}
}

View File

@@ -2,10 +2,11 @@ package com.abin.mallchat.common.user.dao;
import com.abin.mallchat.common.user.domain.entity.UserFriend;
import com.abin.mallchat.common.user.mapper.UserFriendMapper;
import com.abin.mallchat.common.user.service.IUserFriendService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 用户联系人表 服务实现类
@@ -15,6 +16,17 @@ import org.springframework.stereotype.Service;
* @since 2023-07-16
*/
@Service
public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> implements IUserFriendService {
public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
public List<UserFriend> getByFriends(Long uid, List<Long> uidList) {
return lambdaQuery().eq(UserFriend::getUid, uid)
.in(UserFriend::getFriendUid, uidList)
.list();
}
public UserFriend getByFriend(Long uid, Long targetUid) {
return lambdaQuery().eq(UserFriend::getUid, uid)
.eq(UserFriend::getFriendUid, targetUid)
.one();
}
}

View File

@@ -54,6 +54,4 @@ public class UserRole implements Serializable {
*/
@TableField("update_time")
private LocalDateTime updateTime;
}

View File

@@ -31,8 +31,6 @@ import java.util.stream.Collectors;
@Component
public class UserCache {
@Autowired
private CursorUtils cursorUtils;
@Autowired
private UserDao userDao;
@Autowired
@@ -88,11 +86,11 @@ public class UserCache {
}
public CursorPageBaseResp<Pair<Long, Double>> getOnlineCursorPage(CursorPageBaseReq pageBaseReq) {
return cursorUtils.getCursorPageByRedis(pageBaseReq, RedisKey.getKey(RedisKey.ONLINE_UID_ZET), Long::parseLong);
return CursorUtils.getCursorPageByRedis(pageBaseReq, RedisKey.getKey(RedisKey.ONLINE_UID_ZET), Long::parseLong);
}
public CursorPageBaseResp<Pair<Long, Double>> getOfflineCursorPage(CursorPageBaseReq pageBaseReq) {
return cursorUtils.getCursorPageByRedis(pageBaseReq, RedisKey.getKey(RedisKey.OFFLINE_UID_ZET), Long::parseLong);
return CursorUtils.getCursorPageByRedis(pageBaseReq, RedisKey.getKey(RedisKey.OFFLINE_UID_ZET), Long::parseLong);
}
public List<Long> getUserModifyTime(List<Long> uidList) {

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.common.user.service.IUserApplyService;
import com.abin.mallchat.common.user.service.IUserFriendService;
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,27 +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.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
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
@@ -44,15 +50,24 @@ import static com.abin.mallchat.common.user.domain.enums.ApplyTypeEnum.ADD_FRIEN
@Service
public class FriendServiceImpl implements FriendService {
@Resource
private IUserFriendService friendService;
@Resource
private IUserApplyService applyService;
@Resource
@Autowired
private WebSocketService webSocketService;
@Autowired
private UserFriendDao userFriendDao;
@Autowired
private UserApplyDao userApplyDao;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private RoomFriendDao roomFriendDao;
@Autowired
private RoomService roomService;
@Autowired
private ContactService contactService;
@Autowired
private ChatService chatService;
/**
* 检查
* 检查是否是自己好友
@@ -63,15 +78,13 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public FriendCheckResp check(Long uid, FriendCheckReq request) {
LambdaQueryChainWrapper<UserFriend> wrapper = friendService.lambdaQuery();
wrapper.eq(UserFriend::getUid, uid)
.in(UserFriend::getFriendUid, request.getUidList());
List<UserFriend> friendList = friendService.list(wrapper);
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);
@@ -84,25 +97,20 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public void apply(Long uid, FriendApplyReq request) {
LambdaQueryChainWrapper<UserApply> wrapper = applyService.lambdaQuery();
wrapper.eq(UserApply::getUid, uid)
.eq(UserApply::getTargetId, request.getTargetUid());
UserApply userApply = applyService.getOne(wrapper);
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());
applyService.save(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));
}
/**
@@ -113,23 +121,15 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public PageBaseResp<FriendApplyResp> pageApplyFriend(Long uid, PageBaseReq request) {
// todo 分页
LambdaQueryChainWrapper<UserApply> wrapper = applyService.lambdaQuery();
wrapper.eq(UserApply::getUid, uid)
.or()
.eq(UserApply::getTargetId, uid);
List<UserApply> userApplyList = applyService.list(wrapper);
List<FriendApplyResp> friendApplyResps = userApplyList.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());
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()));
}
/**
@@ -139,34 +139,38 @@ public class FriendServiceImpl implements FriendService {
*/
@Override
public FriendUnreadResp unread(Long uid) {
LambdaQueryChainWrapper<UserApply> wrapper = applyService.lambdaQuery();
wrapper.eq(UserApply::getTargetId, uid)
.eq(UserApply::getReadStatus, UNREAD.getCode());
return new FriendUnreadResp(applyService.count(wrapper));
Integer unReadCount = userApplyDao.getUnReadCount(uid);
return new FriendUnreadResp(unReadCount);
}
@Override
@Transactional
public void applyApprove(FriendApproveReq request) {
UserApply userApply = applyService.getById(request.getApplyId());
if (Objects.isNull(userApply)) {
log.error("不存在申请记录:{}", request.getApplyId());
return;
}
if (Objects.equals(userApply.getStatus(), AGREE.getCode())) {
log.error("已同意好友申请:{}", request.getApplyId());
return;
}
LambdaUpdateChainWrapper<UserApply> updateWrapper = applyService.lambdaUpdate();
updateWrapper.set(UserApply::getStatus, AGREE.getCode())
.eq(UserApply::getId, request.getApplyId());
applyService.update(updateWrapper);
@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());
friendService.saveBatch(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));
}
}