项目基础搭建

This commit is contained in:
zhongzb
2023-04-22 21:13:22 +08:00
parent 783ad1ee26
commit ceb28ace8b
149 changed files with 8026 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mallchat</artifactId>
<groupId>com.abin.mallchat</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mallchat-custom-server</artifactId>
<dependencies>
<dependency>
<groupId>com.abin.mallchat</groupId>
<artifactId>mallchat-common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,22 @@
package com.abin.mallchat.custom;
import com.abin.mallchat.custom.user.controller.WxPortalController;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
/**
* @author zhongzb
* @date 2021/05/27
*/
@SpringBootApplication(scanBasePackages = {"com.abin.mallchat"})
@MapperScan({"com.abin.mallchat.common.**.mapper" })
@ServletComponentScan
public class MallchatCustomApplication {
public static void main(String[] args) {
SpringApplication.run(MallchatCustomApplication.class);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;//总人数
}

View File

@@ -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.爆赞 点赞超过103.危险发言举报超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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,29 @@
package com.abin.mallchat.custom.common.config;
import com.abin.mallchat.custom.common.intecepter.CollectorInterceptor;
import com.abin.mallchat.custom.common.intecepter.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Description: 配置所有拦截器
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-04-05
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Autowired
private CollectorInterceptor collectorInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/capi/**");
registry.addInterceptor(collectorInterceptor)
.addPathPatterns("/capi/**");
}
}

View File

@@ -0,0 +1,67 @@
package com.abin.mallchat.custom.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-23
*/
@Configuration
@EnableSwagger2WebMvc
public class SwaggerConfig {
@Bean(value = "defaultApi2")
Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
//配置网站的基本信息
.apiInfo(new ApiInfoBuilder()
//网站标题
.title("mallchat接口文档")
//标题后面的版本号
.version("v1.0")
.description("mallchat接口文档")
//联系人信息
.contact(new Contact("阿斌", "http://www.mallchat.cn", "972627721@qq.com"))
.build())
.select()
//指定接口的位置
.apis(RequestHandlerSelectors
.withClassAnnotation(RestController.class)
)
.paths(PathSelectors.any())
.build();
}
/**
* swagger 配置
* @param environment 环境
*/
// @Bean
// public Docket docket(Environment environment) {
//
// // 设置环境范围
// Profiles profiles = Profiles.of("dev","test");
// // 如果在该环境返回内则返回true反之返回 false
// boolean flag = environment.acceptsProfiles(profiles);
//
// // 创建一个 swagger 的 bean 实例
// return new Docket(DocumentationType.SWAGGER_2)
// .enable(flag) // 是否开启 swaggertrue -> 开启false -> 关闭
// ;
// }
}

View File

@@ -0,0 +1,84 @@
package com.abin.mallchat.custom.common.config;
import com.abin.mallchat.custom.user.service.handler.*;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.redis.JedisWxRedisOps;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import java.util.List;
import java.util.stream.Collectors;
import static me.chanjar.weixin.common.api.WxConsts.EventType;
import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY;
/**
* wechat mp configuration
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfiguration {
private final LogHandler logHandler;
private final MsgHandler msgHandler;
private final SubscribeHandler subscribeHandler;
private final ScanHandler scanHandler;
private final WxMpProperties properties;
@Bean
public WxMpService wxMpService() {
// 代码里 getConfigs()处报错的同学请注意仔细阅读项目说明你的IDE需要引入lombok插件
final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
if (configs == null) {
throw new RuntimeException("大哥拜托先看下项目首页的说明readme文件添加下相关配置注意别配错了");
}
WxMpService service = new WxMpServiceImpl();
service.setMultiConfigStorages(configs
.stream().map(a -> {
WxMpDefaultConfigImpl configStorage;
configStorage = new WxMpDefaultConfigImpl();
configStorage.setAppId(a.getAppId());
configStorage.setSecret(a.getSecret());
configStorage.setToken(a.getToken());
configStorage.setAesKey(a.getAesKey());
return configStorage;
}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
return service;
}
@Bean
public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 记录所有事件的日志 (异步执行)
newRouter.rule().handler(this.logHandler).next();
// 关注事件
newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end();
// 扫码事件
newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end();
// 默认
newRouter.rule().async(false).handler(this.msgHandler).end();
return newRouter;
}
}

View File

@@ -0,0 +1,82 @@
package com.abin.mallchat.custom.common.config;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* wechat mp properties
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
/**
* 是否使用redis存储access token
*/
private boolean useRedis;
/**
* redis 配置
*/
private RedisConfig redisConfig;
@Data
public static class RedisConfig {
/**
* redis服务器 主机地址
*/
private String host;
/**
* redis服务器 端口号
*/
private Integer port;
/**
* redis服务器 密码
*/
private String password;
/**
* redis 服务连接超时时间
*/
private Integer timeout;
}
/**
* 多个公众号配置信息
*/
private List<MpConfig> configs;
@Data
public static class MpConfig {
/**
* 设置微信公众号的appid
*/
private String appId;
/**
* 设置微信公众号的app secret
*/
private String secret;
/**
* 设置微信公众号的token
*/
private String token;
/**
* 设置微信公众号的EncodingAESKey
*/
private String aesKey;
}
@Override
public String toString() {
return JSONUtil.toJsonStr(this);
}
}

View File

@@ -0,0 +1,15 @@
package com.abin.mallchat.custom.common.event;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class MessageMarkEvent extends ApplicationEvent {
private ChatMessageMarkReq req;
public MessageMarkEvent(Object source, ChatMessageMarkReq req) {
super(source);
this.req = req;
}
}

View File

@@ -0,0 +1,15 @@
package com.abin.mallchat.custom.common.event;
import com.abin.mallchat.common.chat.domain.entity.Message;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class MessageSendEvent extends ApplicationEvent {
private Long msgId;
public MessageSendEvent(Object source, Long msgId) {
super(source);
this.msgId = msgId;
}
}

View File

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

View File

@@ -0,0 +1,17 @@
package com.abin.mallchat.custom.common.event;
import com.abin.mallchat.common.user.domain.entity.User;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import java.util.Date;
@Getter
public class UserOnlineEvent extends ApplicationEvent {
private User user;
public UserOnlineEvent(Object source, User user) {
super(source);
this.user = user;
}
}

View File

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

View File

@@ -0,0 +1,56 @@
package com.abin.mallchat.custom.common.event.listener;
import com.abin.mallchat.common.chat.dao.MessageDao;
import com.abin.mallchat.common.chat.dao.MessageMarkDao;
import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum;
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
import com.abin.mallchat.common.common.domain.enums.IdempotentEnum;
import com.abin.mallchat.common.user.domain.enums.ItemEnum;
import com.abin.mallchat.common.user.service.IUserBackpackService;
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
import com.abin.mallchat.custom.common.event.MessageMarkEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 消息标记监听器
*
* @author zhongzb create on 2022/08/26
*/
@Slf4j
@Component
public class MessageMarkListener {
@Autowired
private MessageMarkDao messageMarkDao;
@Autowired
private MessageDao messageDao;
@Autowired
private IUserBackpackService iUserBackpackService;
@Async
@EventListener(classes = MessageMarkEvent.class)
public void changeMsgType(MessageMarkEvent event) {
ChatMessageMarkReq req = event.getReq();
Message msg = messageDao.getById(req.getMsgId());
if (!Objects.equals(msg, MessageTypeEnum.NORMAL.getType())) {//普通消息才需要升级
return;
}
//消息被标记次数
Integer markCount = messageMarkDao.getMarkCount(req.getMsgId(), req.getMarkType());
MessageMarkTypeEnum markTypeEnum = MessageMarkTypeEnum.of(req.getMarkType());
if (markCount < markTypeEnum.getRiseNum()) {
return;
}
boolean updateSuccess = messageDao.riseOptimistic(msg.getId(), msg.getType(), markTypeEnum.getRiseEnum().getType());
if (MessageMarkTypeEnum.LIKE.getType().equals(req.getMarkType()) && updateSuccess) {//尝试给用户发送一张徽章
iUserBackpackService.acquireItem(msg.getFromUid(), ItemEnum.LIKE_BADGE.getId(), IdempotentEnum.MSG_ID, msg.getId().toString());
}
}
}

View File

@@ -0,0 +1,39 @@
package com.abin.mallchat.custom.common.event.listener;
import com.abin.mallchat.common.chat.dao.MessageDao;
import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
import com.abin.mallchat.custom.common.event.MessageSendEvent;
import com.abin.mallchat.custom.chat.service.ChatService;
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.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 消息发送监听器
*
* @author zhongzb create on 2022/08/26
*/
@Slf4j
@Component
public class MessageSendListener {
@Autowired
private WebSocketService webSocketService;
@Autowired
private ChatService chatService;
@Autowired
private MessageDao messageDao;
@Async
@EventListener(classes = MessageSendEvent.class)
public void notifyAllOnline(MessageSendEvent event) {
Message message = messageDao.getById(event.getMsgId());
ChatMessageResp msgResp = chatService.getMsgResp(message, null);
webSocketService.sendToAllOnline(WSAdapter.buildMsgSend(msgResp), message.getFromUid());
}
}

View File

@@ -0,0 +1,52 @@
package com.abin.mallchat.custom.common.event.listener;
import com.abin.mallchat.common.user.dao.UserDao;
import com.abin.mallchat.common.user.domain.entity.User;
import com.abin.mallchat.common.user.service.cache.UserCache;
import com.abin.mallchat.custom.common.event.UserOfflineEvent;
import com.abin.mallchat.custom.common.event.UserOnlineEvent;
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.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 用户下线监听器
*
* @author zhongzb create on 2022/08/26
*/
@Slf4j
@Component
public class UserOfflineListener {
@Autowired
private WebSocketService webSocketService;
@Autowired
private UserDao userDao;
@Autowired
private UserCache userCache;
@Autowired
private WSAdapter wsAdapter;
@Async
@EventListener(classes = UserOfflineEvent.class)
public void saveRedisAndPush(UserOfflineEvent event) {
User user = event.getUser();
userCache.offline(user.getId(), user.getLastOptTime());
//推送给所有在线用户,该用户下线
webSocketService.sendToAllOnline(wsAdapter.buildOfflineNotifyResp(event.getUser()), event.getUser().getId());
}
@Async
@EventListener(classes = UserOfflineEvent.class)
public void saveDB(UserOfflineEvent event) {
User user = event.getUser();
User update = new User();
update.setId(user.getId());
update.setLastOptTime(user.getLastOptTime());
userDao.updateById(update);
}
}

View File

@@ -0,0 +1,62 @@
package com.abin.mallchat.custom.common.event.listener;
import com.abin.mallchat.common.chat.dao.MessageDao;
import com.abin.mallchat.common.chat.domain.entity.Message;
import com.abin.mallchat.common.user.dao.UserDao;
import com.abin.mallchat.common.user.domain.entity.User;
import com.abin.mallchat.common.user.service.IpService;
import com.abin.mallchat.common.user.service.cache.UserCache;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
import com.abin.mallchat.custom.chat.service.ChatService;
import com.abin.mallchat.custom.common.event.MessageSendEvent;
import com.abin.mallchat.custom.common.event.UserOnlineEvent;
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.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 用户上线监听器
*
* @author zhongzb create on 2022/08/26
*/
@Slf4j
@Component
public class UserOnlineListener {
@Autowired
private WebSocketService webSocketService;
@Autowired
private UserDao userDao;
@Autowired
private UserCache userCache;
@Autowired
private WSAdapter wsAdapter;
@Autowired
private IpService ipService;
@Async
@EventListener(classes = UserOnlineEvent.class)
public void saveRedisAndPush(UserOnlineEvent event) {
User user = event.getUser();
userCache.online(user.getId(), user.getLastOptTime());
//推送给所有在线用户,该用户登录成功
webSocketService.sendToAllOnline(wsAdapter.buildOnlineNotifyResp(event.getUser()), event.getUser().getId());
}
@Async
@EventListener(classes = UserOnlineEvent.class)
public void saveDB(UserOnlineEvent event) {
User user = event.getUser();
User update = new User();
update.setId(user.getId());
update.setLastOptTime(user.getLastOptTime());
update.setIpInfo(user.getIpInfo());
userDao.updateById(update);
//更新用户ip详情
ipService.refreshIpDetailAsync(user.getId());
}
}

View File

@@ -0,0 +1,53 @@
package com.abin.mallchat.custom.common.event.listener;
import com.abin.mallchat.common.common.domain.enums.IdempotentEnum;
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.ItemEnum;
import com.abin.mallchat.common.user.service.IUserBackpackService;
import com.abin.mallchat.common.user.service.cache.UserCache;
import com.abin.mallchat.custom.common.event.UserOnlineEvent;
import com.abin.mallchat.custom.common.event.UserRegisterEvent;
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.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 用户上线监听器
*
* @author zhongzb create on 2022/08/26
*/
@Slf4j
@Component
public class UserRegisterListener {
@Autowired
private WebSocketService webSocketService;
@Autowired
private UserDao userDao;
@Autowired
private UserCache userCache;
@Autowired
private WSAdapter wsAdapter;
@Autowired
private IUserBackpackService iUserBackpackService;
@Async
@EventListener(classes = UserRegisterEvent.class)
public void sendCard(UserRegisterEvent event) {
User user = event.getUser();
//送一张改名卡
iUserBackpackService.acquireItem(user.getId(), ItemEnum.MODIFY_NAME_CARD.getId(), IdempotentEnum.UID, user.getId().toString());
}
@Async
@EventListener(classes = UserRegisterEvent.class)
public void sendBadge(UserOnlineEvent event) {
User user = event.getUser();
int count = userDao.count();
}
}

View File

@@ -0,0 +1,45 @@
package com.abin.mallchat.custom.common.intecepter;
import cn.hutool.extra.servlet.ServletUtil;
import com.abin.mallchat.common.common.utils.RequestHolder;
import com.abin.mallchat.common.common.domain.dto.RequestInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
/**
* 信息收集的拦截器
*/
@Order
@Slf4j
@Component
public class CollectorInterceptor implements HandlerInterceptor, WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this)
.addPathPatterns("/**");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
RequestInfo info = new RequestInfo();
info.setUid(Optional.ofNullable(request.getAttribute(TokenInterceptor.ATTRIBUTE_UID)).map(Object::toString).map(Long::parseLong).orElse(null));
info.setIp(ServletUtil.getClientIP(request));
RequestHolder.set(info);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
RequestHolder.remove();
}
}

View File

@@ -0,0 +1,33 @@
package com.abin.mallchat.custom.common.intecepter;
import com.abin.mallchat.common.common.constant.MDCKey;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.*;
import javax.servlet.FilterConfig;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
/**
* Description: 设置链路追踪的值,初期单体项目先简单用
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-04-05
*/
@Slf4j
@WebFilter(urlPatterns = "/*")
public class HttpTraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String tid = UUID.randomUUID().toString();
MDC.put(MDCKey.TID, tid);
chain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,77 @@
package com.abin.mallchat.custom.common.intecepter;
import cn.hutool.http.HttpUtil;
import com.abin.mallchat.common.common.constant.MDCKey;
import com.abin.mallchat.common.common.exception.HttpErrorEnum;
import com.abin.mallchat.custom.user.service.LoginService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@Order(-2)
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String AUTHORIZATION_SCHEMA = "Bearer ";
public static final String ATTRIBUTE_UID = "uid";
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取用户登录token
String token = getToken(request);
Long validUid = loginService.getValidUid(token);
if (Objects.nonNull(validUid)) {//有登录态
request.setAttribute(ATTRIBUTE_UID, validUid);
} else {
boolean isPublicURI = isPublicURI(request.getRequestURI());
if (!isPublicURI) {//又没有登录态又不是公开路径直接401
HttpErrorEnum.ACCESS_DENIED.sendHttpError(response);
return false;
}
}
MDC.put(MDCKey.UID, String.valueOf(validUid));
return true;
}
/**
* 判断是不是公共方法,可以未登录访问的
*
* @param requestURI
*/
private boolean isPublicURI(String requestURI) {
String[] split = requestURI.split("/");
return split.length > 2 && "public".equals(split[3]);
}
private String getToken(HttpServletRequest request) {
String header = request.getHeader(AUTHORIZATION_HEADER);
return Optional.ofNullable(header)
.filter(h -> h.startsWith(AUTHORIZATION_SCHEMA))
.map(h -> h.substring(AUTHORIZATION_SCHEMA.length()))
.orElse(null);
}
}

View File

@@ -0,0 +1,74 @@
package com.abin.mallchat.custom.common.intecepter;
import cn.hutool.core.date.StopWatch;
import cn.hutool.json.JSONUtil;
import com.abin.mallchat.common.common.utils.RequestHolder;
import com.abin.mallchat.common.common.domain.dto.RequestInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 日志切面
*
* @author wayne
*/
@Aspect
@Slf4j
@Component
public class WebLogAspect {
/**
* 接收到请求,记录请求内容
* 所有controller包下所有的类的方法都是切点
* <p>
* 如果ApiResult返回success=false则打印warn日志
* warn日志只能打印在同一行因为只有等到ApiResult结果才知道是success=false。
* <p>
* 如果ApiResult返回success=true则打印info日志
* 特别注意由于info级别日志已经包含了warn级别日志。如果开了info级别日志warn就不会打印了。
*/
@Around("execution(* com..controller..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String method = request.getMethod();
String uri = request.getRequestURI();
//如果参数有HttpRequest,ServletResponse直接移除不打印这些
List<Object> paramList = Stream.of(joinPoint.getArgs())
.filter(args -> !(args instanceof ServletRequest))
.filter(args -> !(args instanceof ServletResponse))
.collect(Collectors.toList());
String printParamStr = paramList.size() == 1 ? JSONUtil.toJsonStr(paramList.get(0)) : JSONUtil.toJsonStr(paramList);
RequestInfo requestInfo = RequestHolder.get();
String userHeaderStr = JSONUtil.toJsonStr(requestInfo);
if (log.isInfoEnabled()) {
log.info("[{}][{}]【base:{}】【request:{}】", method, uri, userHeaderStr, printParamStr);
}
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
long cost = stopWatch.getTotalTimeMillis();
String printResultStr = JSONUtil.toJsonStr(result);
if (log.isInfoEnabled()) {
log.info("[{}]【response:{}】[cost:{}ms]", uri, printResultStr, cost);
}
return result;
}
}

View File

@@ -0,0 +1,60 @@
package com.abin.mallchat.custom.user.controller;
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
import com.abin.mallchat.common.common.utils.RequestHolder;
import com.abin.mallchat.custom.user.domain.vo.request.user.ModifyNameReq;
import com.abin.mallchat.custom.user.domain.vo.request.user.WearingBadgeReq;
import com.abin.mallchat.custom.user.domain.vo.response.user.BadgeResp;
import com.abin.mallchat.custom.user.service.UserService;
import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp;
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;
import java.util.List;
/**
* <p>
* 用户表 前端控制器
* </p>
*
* @author <a href="https://github.com/zongzibinbin">abin</a>
* @since 2023-03-19
*/
@RestController
@RequestMapping("/capi/user")
@Api(tags = "用户管理相关接口")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/userInfo")
@ApiOperation("用户详情")
public ApiResult<UserInfoResp> getUserInfo() {
return ApiResult.success(userService.getUserInfo(RequestHolder.get().getUid()));
}
@PutMapping("/name")
@ApiOperation("修改用户名")
public ApiResult<Void> modifyName(@Valid @RequestBody ModifyNameReq req) {
userService.modifyName(RequestHolder.get().getUid(), req);
return ApiResult.success();
}
@GetMapping("/badges")
@ApiOperation("可选徽章预览")
public ApiResult<List<BadgeResp>> badges() {
return ApiResult.success(userService.badges(RequestHolder.get().getUid()));
}
@PutMapping("/badge")
@ApiOperation("佩戴徽章")
public ApiResult<Void> wearingBadge(@Valid @RequestBody WearingBadgeReq req) {
userService.wearingBadge(RequestHolder.get().getUid(),req);
return ApiResult.success();
}
}

View File

@@ -0,0 +1,118 @@
package com.abin.mallchat.custom.user.controller;
import com.abin.mallchat.custom.user.service.WxMsgService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
/**
* Description: 微信api交互接口
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("wx/portal/public")
public class WxPortalController {
private final WxMpService wxService;
private final WxMpMessageRouter messageRouter;
private final WxMsgService wxMsgService;
@GetMapping(produces = "text/plain;charset=utf-8")
public String authGet(@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echostr) {
log.info("\n接收到来自微信服务器的认证消息[{}, {}, {}, {}]", signature,
timestamp, nonce, echostr);
if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
throw new IllegalArgumentException("请求参数非法,请核实!");
}
if (wxService.checkSignature(timestamp, nonce, signature)) {
return echostr;
}
return "非法请求";
}
@GetMapping("/callBack")
public RedirectView callBack(@RequestParam String code) throws WxErrorException {
try {
WxOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code);
WxOAuth2UserInfo userInfo = wxService.getOAuth2Service().getUserInfo(accessToken, "zh_CN");
wxMsgService.authorize(userInfo);
}catch (Exception e){
log.error("callBack error",e);
}
RedirectView redirectView = new RedirectView();
redirectView.setUrl("https://mp.weixin.qq.com/s/MKCWzoCIzvh5G_1sK5sLoA");
return redirectView;
}
@PostMapping(produces = "application/xml; charset=UTF-8")
public String post(@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("openid") String openid,
@RequestParam(name = "encrypt_type", required = false) String encType,
@RequestParam(name = "msg_signature", required = false) String msgSignature) {
log.info("\n接收微信请求[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
if (!wxService.checkSignature(timestamp, nonce, signature)) {
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
}
String out = null;
if (encType == null) {
// 明文传输的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toXml();
} else if ("aes".equalsIgnoreCase(encType)) {
// aes加密的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(),
timestamp, nonce, msgSignature);
log.debug("\n消息解密后内容为\n{} ", inMessage.toString());
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage());
}
log.debug("\n组装回复信息{}", out);
return out;
}
private WxMpXmlOutMessage route(WxMpXmlMessage message) {
try {
return this.messageRouter.route(message);
} catch (Exception e) {
log.error("路由消息时出现异常!", e);
}
return null;
}
}

View File

@@ -0,0 +1,25 @@
package com.abin.mallchat.custom.user.domain.dto.ws;
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-21
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSChannelExtraDTO {
/**
* 前端如果登录了记录uid
*/
private Long uid;
}

View File

@@ -0,0 +1,36 @@
package com.abin.mallchat.custom.user.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: ws前端请求类型枚举
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@AllArgsConstructor
@Getter
public enum WSReqTypeEnum {
LOGIN(1, "请求登录二维码"),
HEARTBEAT(2, "心跳包"),
AUTHORIZE(3, "登录认证"),
;
private final Integer type;
private final String desc;
private static Map<Integer, WSReqTypeEnum> cache;
static {
cache = Arrays.stream(WSReqTypeEnum.values()).collect(Collectors.toMap(WSReqTypeEnum::getType, Function.identity()));
}
public static WSReqTypeEnum of(Integer type) {
return cache.get(type);
}
}

View File

@@ -0,0 +1,44 @@
package com.abin.mallchat.custom.user.domain.enums;
import com.abin.mallchat.custom.user.domain.vo.response.ws.WSLoginSuccess;
import com.abin.mallchat.custom.user.domain.vo.response.ws.WSLoginUrl;
import com.abin.mallchat.custom.user.domain.vo.response.ws.WSMessage;
import com.abin.mallchat.custom.user.domain.vo.response.ws.WSOnlineOfflineNotify;
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: ws前端请求类型枚举
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@AllArgsConstructor
@Getter
public enum WSRespTypeEnum {
LOGIN_URL(1, "登录二维码返回", WSLoginUrl.class),
LOGIN_SCAN_SUCCESS(2, "用户扫描成功等待授权", null),
LOGIN_SUCCESS(3, "用户登录成功返回用户信息", WSLoginSuccess.class),
MESSAGE(4, "新消息", WSMessage.class),
ONLINE_OFFLINE_NOTIFY(5, "上下线通知", WSOnlineOfflineNotify.class),
INVALIDATE_TOKEN(6, "使前端的token失效意味着前端需要重新登录", null),
;
private final Integer type;
private final String desc;
private final Class dataClass;
private static Map<Integer, WSRespTypeEnum> cache;
static {
cache = Arrays.stream(WSRespTypeEnum.values()).collect(Collectors.toMap(WSRespTypeEnum::getType, Function.identity()));
}
public static WSRespTypeEnum of(Integer type) {
return cache.get(type);
}
}

View File

@@ -0,0 +1,30 @@
package com.abin.mallchat.custom.user.domain.vo.request.user;
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 ModifyNameReq {
@NotNull
@Length(max = 6,message = "用户名可别取太长,不然我记不住噢")
@ApiModelProperty("用户名")
private String name;
}

View File

@@ -0,0 +1,29 @@
package com.abin.mallchat.custom.user.domain.vo.request.user;
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 WearingBadgeReq {
@NotNull
@ApiModelProperty("徽章id")
private Long badgeId;
}

View File

@@ -0,0 +1,20 @@
package com.abin.mallchat.custom.user.domain.vo.request.ws;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSAuthorize {
private String token;
}

View File

@@ -0,0 +1,22 @@
package com.abin.mallchat.custom.user.domain.vo.request.ws;
import lombok.Data;
/**
* Description: websocket前端请求体
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
public class WSBaseReq {
/**
* 请求类型 1.请求登录二维码2心跳检测
* @see com.abin.mallchat.custom.user.domain.enums.WSReqTypeEnum
*/
private Integer type;
/**
* 每个请求包具体的数据,类型不同结果不同
*/
private String data;
}

View File

@@ -0,0 +1,30 @@
package com.abin.mallchat.custom.user.domain.vo.response.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* Description: 徽章信息
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-22
*/
@Data
@ApiModel("徽章信息")
public class BadgeResp {
@ApiModelProperty(value = "徽章id")
private Long id;
@ApiModelProperty(value = "徽章图标")
private String image;
@ApiModelProperty(value = "徽章描述")
private String describe;
@ApiModelProperty(value = "是否拥有 0否 1是")
private Integer obtain;
@ApiModelProperty(value = "是否佩戴 0否 1是")
private Integer wearing;
}

View File

@@ -0,0 +1,31 @@
package com.abin.mallchat.custom.user.domain.vo.response.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* Description: 用户信息返回
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-22
*/
@Data
@ApiModel("用户详情")
public class UserInfoResp {
@ApiModelProperty(value = "用户id")
private Long id;
@ApiModelProperty(value = "用户昵称")
private String name;
@ApiModelProperty(value = "用户头像")
private String avatar;
@ApiModelProperty(value = "性别 1为男性2为女性")
private Integer sex;
@ApiModelProperty(value = "剩余改名次数")
private Integer modifyNameChance;
}

View File

@@ -0,0 +1,18 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import lombok.Data;
/**
* Description: ws的基本返回信息体
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
public class WSBaseResp<T> {
/**
* ws推送给前端的消息
* @see com.abin.mallchat.custom.user.domain.enums.WSRespTypeEnum
*/
private Integer type;
private T data;
}

View File

@@ -0,0 +1,23 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSLoginSuccess {
private Long uid;
private String avatar;
private String token;
private String name;
}

View File

@@ -0,0 +1,20 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* Description:
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSLoginUrl {
private String loginUrl;
}

View File

@@ -0,0 +1,21 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
/**
* Description: 用户消息推送
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
public class WSMessage extends ChatMessageResp {
}

View File

@@ -0,0 +1,28 @@
package com.abin.mallchat.custom.user.domain.vo.response.ws;
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
/**
* Description:用户上下线变动的推送类
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-03-19
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSOnlineOfflineNotify {
private List<ChatMemberResp> changeList = new ArrayList<>();//新的上下线用户
private Long onlineNum;//在线人数
private Long totalNum;//总人数
}

Some files were not shown because too many files have changed in this diff Show More