mirror of
https://github.com/zongzibinbin/MallChat.git
synced 2025-12-26 04:47:53 +08:00
项目基础搭建
This commit is contained in:
parent
783ad1ee26
commit
ceb28ace8b
114
mallchat-common/pom.xml
Normal file
114
mallchat-common/pom.xml
Normal file
@ -0,0 +1,114 @@
|
||||
<?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-common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
<!-- MyBatis-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!--代码生成器-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-generator</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>mybatis-plus-extension</artifactId>
|
||||
<groupId>com.baomidou</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.0</version>
|
||||
</dependency>
|
||||
<!--Mysql数据库驱动-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<!--JWT(Json Web Token)登录支持-->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
</dependency>
|
||||
<!-- 阿里云OSS -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<!-- netty -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-mp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<!--使用Swagger2-->
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>6.0.1.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>3.19.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,60 @@
|
||||
package com.abin.mallchat.common.chat.dao;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
|
||||
import com.abin.mallchat.common.chat.mapper.MessageMapper;
|
||||
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.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 消息表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-25
|
||||
*/
|
||||
@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 -> {
|
||||
wrapper.eq(Message::getRoomId, roomId);
|
||||
wrapper.eq(Message::getStatus, MessageStatusEnum.NORMAL.getStatus());
|
||||
}, Message::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐观更新消息类型
|
||||
*/
|
||||
public boolean riseOptimistic(Long id, Integer oldType, Integer newType) {
|
||||
return lambdaUpdate()
|
||||
.eq(Message::getId, id)
|
||||
.eq(Message::getType, oldType)
|
||||
.set(Message::getType, newType)
|
||||
.update();
|
||||
}
|
||||
|
||||
public Integer getGapCount(Long roomId, Long fromId, Long toId) {
|
||||
return lambdaQuery()
|
||||
.eq(Message::getRoomId, roomId)
|
||||
.gt(Message::getId, fromId)
|
||||
.le(Message::getId, toId)
|
||||
.count();
|
||||
}
|
||||
|
||||
public void updateGapCount(Long id, Integer gapCount) {
|
||||
lambdaUpdate()
|
||||
.eq(Message::getId, id)
|
||||
.set(Message::getGapCount, gapCount);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.abin.mallchat.common.chat.dao;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
|
||||
import com.abin.mallchat.common.chat.mapper.MessageMarkMapper;
|
||||
import com.abin.mallchat.common.chat.service.IMessageMarkService;
|
||||
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
|
||||
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-04-08
|
||||
*/
|
||||
@Service
|
||||
public class MessageMarkDao extends ServiceImpl<MessageMarkMapper, MessageMark> {
|
||||
|
||||
public MessageMark get(Long uid, Long msgId, Integer markType) {
|
||||
return lambdaQuery().eq(MessageMark::getUid, uid)
|
||||
.eq(MessageMark::getMsgId, msgId)
|
||||
.eq(MessageMark::getType, markType)
|
||||
.one();
|
||||
}
|
||||
|
||||
public Integer getMarkCount(Long msgId, Integer markType) {
|
||||
return lambdaQuery().eq(MessageMark::getMsgId, msgId)
|
||||
.eq(MessageMark::getType, markType)
|
||||
.eq(MessageMark::getStatus, YesOrNoEnum.NO.getStatus())
|
||||
.count();
|
||||
}
|
||||
|
||||
public List<MessageMark> getValidMarkByMsgIdBatch(List<Long> msgIds) {
|
||||
return lambdaQuery()
|
||||
.in(MessageMark::getMsgId, msgIds)
|
||||
.eq(MessageMark::getStatus, YesOrNoEnum.NO.getStatus())
|
||||
.list();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.abin.mallchat.common.chat.dao;
|
||||
|
||||
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.chat.domain.enums.RoomTypeEnum;
|
||||
import com.abin.mallchat.common.chat.mapper.RoomMapper;
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 会话表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-25
|
||||
*/
|
||||
@Service
|
||||
public class RoomDao extends ServiceImpl<RoomMapper, Room> {
|
||||
@Autowired
|
||||
private CursorUtils cursorUtils;
|
||||
|
||||
public CursorPageBaseResp<Room> getCursorPage(CursorPageBaseReq request) {
|
||||
return cursorUtils.getCursorPageByMysql(this, request, wrapper -> {
|
||||
wrapper.ne(Room::getType, RoomTypeEnum.GROUP.getStatus());
|
||||
}, Room::getActiveTime);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package com.abin.mallchat.common.chat.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 消息表
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-25
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("message")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Message 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;
|
||||
|
||||
/**
|
||||
* 消息发送者uid
|
||||
*/
|
||||
@TableField("from_uid")
|
||||
private Long fromUid;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
@TableField("content")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 回复的消息内容
|
||||
*/
|
||||
@TableField("reply_msg_id")
|
||||
private Long replyMsgId;
|
||||
|
||||
/**
|
||||
* 消息状态 0正常 1删除
|
||||
* @see com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum
|
||||
*/
|
||||
@TableField("status")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 与回复消息的间隔条数
|
||||
*/
|
||||
@TableField("gap_count")
|
||||
private Integer gapCount;
|
||||
|
||||
/**
|
||||
* 消息类型 1正常文本 2.爆赞 (点赞超过10)3.危险发言(举报超5)
|
||||
* @see com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum
|
||||
*/
|
||||
@TableField("type")
|
||||
private Integer type;
|
||||
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package com.abin.mallchat.common.chat.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 消息标记表
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-04-08
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("message_mark")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MessageMark implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 消息表id
|
||||
*/
|
||||
@TableField("msg_id")
|
||||
private Long msgId;
|
||||
|
||||
/**
|
||||
* 标记人uid
|
||||
*/
|
||||
@TableField("uid")
|
||||
private Long uid;
|
||||
|
||||
/**
|
||||
* 标记类型 1点赞 2举报
|
||||
* @see com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum
|
||||
*/
|
||||
@TableField("type")
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 消息状态 0正常 1取消
|
||||
*/
|
||||
@TableField("status")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package com.abin.mallchat.common.chat.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 会话表
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-25
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("room")
|
||||
public class Room implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会话名
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 会话类型 1大群聊 2沸点
|
||||
* @see com.abin.mallchat.common.chat.domain.enums.RoomTypeEnum
|
||||
*/
|
||||
@TableField("type")
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 最后活跃时间-排序
|
||||
*/
|
||||
@TableField("active_time")
|
||||
private Date activeTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
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 MessageMarkTypeEnum {
|
||||
LIKE(1, "点赞",10,MessageTypeEnum.LIKE),
|
||||
DISLIKE(2, "点踩",5,MessageTypeEnum.DISLIKE),
|
||||
;
|
||||
|
||||
private final Integer type;
|
||||
private final String desc;
|
||||
private final Integer riseNum;//需要多少个标记升级
|
||||
private final MessageTypeEnum riseEnum;//消息升级成什么类型的消息
|
||||
|
||||
private static Map<Integer, MessageMarkTypeEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(MessageMarkTypeEnum.values()).collect(Collectors.toMap(MessageMarkTypeEnum::getType, Function.identity()));
|
||||
}
|
||||
|
||||
public static MessageMarkTypeEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -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 MessageStatusEnum {
|
||||
NORMAL(0, "正常"),
|
||||
DELETE(1, "删除"),
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Integer, MessageStatusEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(MessageStatusEnum.values()).collect(Collectors.toMap(MessageStatusEnum::getStatus, Function.identity()));
|
||||
}
|
||||
|
||||
public static MessageStatusEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
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 MessageTypeEnum {
|
||||
NORMAL(1, "正常"),
|
||||
LIKE(2, "爆赞"),
|
||||
DISLIKE(3, "危险发言"),
|
||||
;
|
||||
|
||||
private final Integer type;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Integer, MessageTypeEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(MessageTypeEnum.values()).collect(Collectors.toMap(MessageTypeEnum::getType, Function.identity()));
|
||||
}
|
||||
|
||||
public static MessageTypeEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -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 RoomTypeEnum {
|
||||
GROUP(1, "大群聊"),
|
||||
BOILING(2, "沸点"),
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Integer, RoomTypeEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(RoomTypeEnum.values()).collect(Collectors.toMap(RoomTypeEnum::getStatus, Function.identity()));
|
||||
}
|
||||
|
||||
public static RoomTypeEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.chat.mapper;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 消息表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-25
|
||||
*/
|
||||
public interface MessageMapper extends BaseMapper<Message> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.chat.mapper;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 消息标记表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-04-08
|
||||
*/
|
||||
public interface MessageMarkMapper extends BaseMapper<MessageMark> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package com.abin.mallchat.common.chat.mapper;
|
||||
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.Room;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 会话表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-25
|
||||
*/
|
||||
public interface RoomMapper extends BaseMapper<Room> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.chat.service;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 消息标记表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-04-08
|
||||
*/
|
||||
public interface IMessageMarkService extends IService<MessageMark> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.chat.service;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 消息表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-25
|
||||
*/
|
||||
public interface IMessageService extends IService<Message> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
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-03-25
|
||||
*/
|
||||
public interface IRoomService extends IService<Room> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package com.abin.mallchat.common.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 频控注解
|
||||
*/
|
||||
@Repeatable(FrequencyControlContainer.class)//可重复
|
||||
@Retention(RetentionPolicy.RUNTIME)//运行时生效
|
||||
@Target(ElementType.METHOD)//作用在方法上
|
||||
public @interface FrequencyControl {
|
||||
/**
|
||||
* key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做频控,就自己指定
|
||||
*
|
||||
* @return key的前缀
|
||||
*/
|
||||
String prefixKey() default "";
|
||||
|
||||
/**
|
||||
* 频控对象,默认el表达指定具体的频控对象
|
||||
* 对于ip 和uid模式,需要是http入口的对象,保证RequestHolder里有值
|
||||
*
|
||||
* @return 对象
|
||||
*/
|
||||
Target target() default Target.EL;
|
||||
|
||||
/**
|
||||
* springEl 表达式,target=EL必填
|
||||
*
|
||||
* @return 表达式
|
||||
*/
|
||||
String spEl() default "";
|
||||
|
||||
/**
|
||||
* 频控时间范围,默认单位秒
|
||||
*
|
||||
* @return 时间范围
|
||||
*/
|
||||
int time();
|
||||
|
||||
/**
|
||||
* 频控时间单位,默认秒
|
||||
*
|
||||
* @return 单位
|
||||
*/
|
||||
TimeUnit unit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* 单位时间内最大访问次数
|
||||
*
|
||||
* @return 次数
|
||||
*/
|
||||
int count();
|
||||
|
||||
enum Target {
|
||||
UID, IP, EL
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.abin.mallchat.common.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)//运行时生效
|
||||
@Target(ElementType.METHOD)//作用在方法上
|
||||
public @interface FrequencyControlContainer {
|
||||
FrequencyControl[] value();
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package com.abin.mallchat.common.common.aspect;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.abin.mallchat.common.common.annotation.FrequencyControl;
|
||||
import com.abin.mallchat.common.common.exception.BusinessException;
|
||||
import com.abin.mallchat.common.common.exception.CommonErrorEnum;
|
||||
import com.abin.mallchat.common.common.utils.RedisUtils;
|
||||
import com.abin.mallchat.common.common.utils.RequestHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Description: 频控实现
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-04-20
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class FrequencyControlAspect {
|
||||
@Autowired
|
||||
private RedisUtils redisUtils;
|
||||
private final ExpressionParser parser = new SpelExpressionParser();
|
||||
private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
@Around("@annotation(com.abin.mallchat.common.common.annotation.FrequencyControl)||@annotation(com.abin.mallchat.common.common.annotation.FrequencyControlContainer)")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
|
||||
FrequencyControl[] annotationsByType = method.getAnnotationsByType(FrequencyControl.class);
|
||||
Map<String, FrequencyControl> keyMap = new HashMap<>();
|
||||
for (int i = 0; i < annotationsByType.length; i++) {
|
||||
FrequencyControl frequencyControl = annotationsByType[i];
|
||||
String prefix = StrUtil.isBlank(frequencyControl.prefixKey()) ? method.toGenericString() + ":index:" + i : frequencyControl.prefixKey();//默认方法限定名+注解排名(可能多个)
|
||||
String key = "";
|
||||
switch (frequencyControl.target()) {
|
||||
case EL:
|
||||
key = parseSpEl(method, joinPoint.getArgs(), frequencyControl.spEl());
|
||||
break;
|
||||
case IP:
|
||||
key = RequestHolder.get().getIp();
|
||||
break;
|
||||
case UID:
|
||||
key = RequestHolder.get().getUid().toString();
|
||||
}
|
||||
keyMap.put(prefix + ":" + key, frequencyControl);
|
||||
}
|
||||
//批量获取redis统计的值
|
||||
ArrayList<String> keyList = new ArrayList<>(keyMap.keySet());
|
||||
List<Integer> countList = redisUtils.mget(keyList, Integer.class);
|
||||
for (int i = 0; i < keyList.size(); i++) {
|
||||
String key = keyList.get(i);
|
||||
Integer count = countList.get(i);
|
||||
FrequencyControl frequencyControl = keyMap.get(key);
|
||||
if (Objects.nonNull(count) && count >= frequencyControl.count()) {//频率超过了
|
||||
log.warn("frequencyControl limit key:{},count:{}", key, count);
|
||||
throw new BusinessException(CommonErrorEnum.FREQUENCY_LIMIT);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
//不管成功还是失败,都增加次数
|
||||
keyMap.forEach((k, v) -> {
|
||||
redisUtils.inc(k,v.time(),v.unit());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private String parseSpEl(Method method, Object[] args, String spEl) {
|
||||
String[] params = parameterNameDiscoverer.getParameterNames(method);//解析参数名
|
||||
EvaluationContext context = new StandardEvaluationContext();//el解析需要的上下文对象
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去
|
||||
}
|
||||
Expression expression = parser.parseExpression(spEl);
|
||||
return expression.getValue(context, String.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.abin.mallchat.common.common.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@EnableCaching
|
||||
@Configuration
|
||||
public class CacheConfig extends CachingConfigurerSupport {
|
||||
|
||||
@Bean("caffeineCacheManager")
|
||||
@Primary
|
||||
public CacheManager caffeineCacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||
// 方案一(常用):定制化缓存Cache
|
||||
cacheManager.setCaffeine(Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.initialCapacity(100)
|
||||
.maximumSize(200));
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.abin.mallchat.common.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
/**
|
||||
* 新增分页拦截器,并设置数据库类型为mysql
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.abin.mallchat.common.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import org.springframework.data.redis.serializer.SerializationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Objects;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
// 创建模板
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
// 设置连接工厂
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
// 设置序列化工具
|
||||
MyRedisSerializerCustomized jsonRedisSerializer =
|
||||
new MyRedisSerializerCustomized();
|
||||
// key和 hashKey采用 string序列化
|
||||
redisTemplate.setKeySerializer(RedisSerializer.string());
|
||||
redisTemplate.setHashKeySerializer(RedisSerializer.string());
|
||||
// value和 hashValue采用 JSON序列化
|
||||
redisTemplate.setValueSerializer(jsonRedisSerializer);
|
||||
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
|
||||
return redisTemplate;
|
||||
}
|
||||
public class MyRedisSerializerCustomized extends GenericJackson2JsonRedisSerializer {
|
||||
@Override
|
||||
public byte[] serialize(Object source) throws SerializationException {
|
||||
if (Objects.nonNull(source)) {
|
||||
if (source instanceof String || source instanceof Character) {
|
||||
return source.toString().getBytes();
|
||||
}
|
||||
}
|
||||
return super.serialize(source);
|
||||
}
|
||||
@Override
|
||||
public <T> T deserialize(byte[] source, Class<T> type) throws SerializationException {
|
||||
Assert.notNull(type,
|
||||
"Deserialization type must not be null! Please provide Object.class to make use of Jackson2 default typing.");
|
||||
if (source == null || source.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (type.isInstance(String.class) || type.isInstance(Character.class)) {
|
||||
return (T) new String(source);
|
||||
}
|
||||
return super.deserialize(source, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package com.abin.mallchat.common.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* Description: 线程池配置
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-04-09
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class ThreadPoolConfig implements AsyncConfigurer {
|
||||
/**
|
||||
* 项目共用线程池
|
||||
*/
|
||||
public static final String MALLCHAT_EXECUTOR = "mallchatExecutor";
|
||||
/**
|
||||
* websocket通信线程池
|
||||
*/
|
||||
public static final String WS_EXECUTOR = "websocketExecutor";
|
||||
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
return mallchatExecutor();
|
||||
}
|
||||
|
||||
@Bean(MALLCHAT_EXECUTOR)
|
||||
@Primary
|
||||
public ThreadPoolTaskExecutor mallchatExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(200);
|
||||
executor.setThreadNamePrefix("mallchat-executor-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//满了调用线程执行,认为重要任务
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean(WS_EXECUTOR)
|
||||
public ThreadPoolTaskExecutor websocketExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(16);
|
||||
executor.setMaxPoolSize(16);
|
||||
executor.setQueueCapacity(1000);//支持同时推送1000人
|
||||
executor.setThreadNamePrefix("websocket-executor-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());//满了直接丢弃,默认为不重要消息推送
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.abin.mallchat.common.common.constant;
|
||||
|
||||
public interface MDCKey {
|
||||
String TID = "tid";
|
||||
String UID = "uid";
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.abin.mallchat.common.common.constant;
|
||||
|
||||
/**
|
||||
* @author zhongzb create on 2021/06/10
|
||||
*/
|
||||
public class RedisKey {
|
||||
private static final String BASE_KEY = "mallchat:";
|
||||
|
||||
/**
|
||||
* 在线用户列表
|
||||
*/
|
||||
public static final String ONLINE_UID_ZET = "online";
|
||||
|
||||
/**
|
||||
* 离线用户列表
|
||||
*/
|
||||
public static final String OFFLINE_UID_ZET = "offline";
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
public static final String USER_INFO_STRING = "userInfo:uid_%d";
|
||||
|
||||
/**
|
||||
* 用户token存放
|
||||
*/
|
||||
public static final String USER_TOKEN_STRING = "userToken:uid_%d";
|
||||
|
||||
public static String getKey(String key, Object... objects) {
|
||||
return BASE_KEY + String.format(key, objects);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.abin.mallchat.common.common.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Description: web请求信息收集类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-04-05
|
||||
*/
|
||||
@Data
|
||||
public class RequestInfo {
|
||||
private Long uid;
|
||||
private String ip;
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.abin.mallchat.common.common.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 IdempotentEnum {
|
||||
UID(1, "uid"),
|
||||
MSG_ID(2, "消息id"),
|
||||
;
|
||||
|
||||
private final Integer type;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Integer, IdempotentEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(IdempotentEnum.values()).collect(Collectors.toMap(IdempotentEnum::getType, Function.identity()));
|
||||
}
|
||||
|
||||
public static IdempotentEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.abin.mallchat.common.common.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 YesOrNoEnum {
|
||||
NO(0, "否"),
|
||||
YES(1, "是"),
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Integer, YesOrNoEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(YesOrNoEnum.values()).collect(Collectors.toMap(YesOrNoEnum::getStatus, Function.identity()));
|
||||
}
|
||||
|
||||
public static YesOrNoEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
|
||||
public static Integer toStatus(Boolean bool){
|
||||
return bool?YES.getStatus():NO.getStatus();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.abin.mallchat.common.common.domain.vo.request;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.generator.config.querys.XuguQuery;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("游标翻页请求")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CursorPageBaseReq {
|
||||
|
||||
@ApiModelProperty("页面大小")
|
||||
private Integer pageSize = 10;
|
||||
|
||||
@ApiModelProperty("游标(初始为null,后续请求附带上次翻页的游标)")
|
||||
private String cursor;
|
||||
|
||||
public Page plusPage() {
|
||||
return new Page(1, this.pageSize);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Boolean isFirstPage() {
|
||||
return Objects.isNull(cursor);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.abin.mallchat.common.common.domain.vo.request;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("基础翻页请求")
|
||||
public class PageBaseReq{
|
||||
|
||||
@ApiModelProperty("页面大小")
|
||||
private Integer pageSize = 10;
|
||||
|
||||
@ApiModelProperty("页面索引(从1开始)")
|
||||
private Integer pageNo = 1;
|
||||
|
||||
/**
|
||||
* 获取mybatisPlus的page
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Page plusPage() {
|
||||
return new Page(pageNo, pageSize);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.abin.mallchat.common.common.domain.vo.response;
|
||||
|
||||
import com.abin.mallchat.common.common.exception.ErrorEnum;
|
||||
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-23
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("基础返回体")
|
||||
public class ApiResult<T> {
|
||||
@ApiModelProperty("成功标识true or false")
|
||||
private Boolean success;
|
||||
@ApiModelProperty("错误码")
|
||||
private Integer errCode;
|
||||
@ApiModelProperty("错误消息")
|
||||
private String errMsg;
|
||||
@ApiModelProperty("返回对象")
|
||||
private T data;
|
||||
|
||||
public static <T> ApiResult<T> success() {
|
||||
ApiResult<T> result = new ApiResult<T>();
|
||||
result.setData(null);
|
||||
result.setSuccess(Boolean.TRUE);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> ApiResult<T> success(T data) {
|
||||
ApiResult<T> result = new ApiResult<T>();
|
||||
result.setData(data);
|
||||
result.setSuccess(Boolean.TRUE);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> ApiResult<T> fail(Integer code, String msg) {
|
||||
ApiResult<T> result = new ApiResult<T>();
|
||||
result.setSuccess(Boolean.FALSE);
|
||||
result.setErrCode(code);
|
||||
result.setErrMsg(msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> ApiResult<T> fail(ErrorEnum errorEnum) {
|
||||
ApiResult<T> result = new ApiResult<T>();
|
||||
result.setSuccess(Boolean.FALSE);
|
||||
result.setErrCode(errorEnum.getErrorCode());
|
||||
result.setErrMsg(errorEnum.getErrorMsg());
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return this.success;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.abin.mallchat.common.common.domain.vo.response;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("游标翻页返回")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CursorPageBaseResp<T> {
|
||||
|
||||
@ApiModelProperty("游标(下次翻页带上这参数)")
|
||||
private String cursor;
|
||||
|
||||
@ApiModelProperty("是否最后一页")
|
||||
private Boolean isLast = Boolean.FALSE;
|
||||
|
||||
@ApiModelProperty("数据列表")
|
||||
private List<T> list;
|
||||
|
||||
public static CursorPageBaseResp init(CursorPageBaseResp cursorPage, List list) {
|
||||
CursorPageBaseResp cursorPageBaseResp = new CursorPageBaseResp();
|
||||
cursorPageBaseResp.setIsLast(cursorPage.getIsLast());
|
||||
cursorPageBaseResp.setList(list);
|
||||
cursorPageBaseResp.setCursor(cursorPage.getCursor());
|
||||
return cursorPageBaseResp;
|
||||
}
|
||||
@JsonIgnore
|
||||
public Boolean isEmpty() {
|
||||
return CollectionUtil.isEmpty(list);
|
||||
}
|
||||
|
||||
public static CursorPageBaseResp empty() {
|
||||
CursorPageBaseResp cursorPageBaseResp = new CursorPageBaseResp();
|
||||
cursorPageBaseResp.setIsLast(true);
|
||||
cursorPageBaseResp.setList(new ArrayList());
|
||||
return cursorPageBaseResp;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.abin.mallchat.common.common.domain.vo.response;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author zhongzb create on 2021/05/31
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IdRespVO {
|
||||
@ApiModelProperty("id")
|
||||
private long id;
|
||||
|
||||
public static IdRespVO id(Long id) {
|
||||
IdRespVO idRespVO = new IdRespVO();
|
||||
idRespVO.setId(id);
|
||||
return idRespVO;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package com.abin.mallchat.common.common.domain.vo.response;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ApiModel("基础翻页返回")
|
||||
public class PageBaseResp<T> {
|
||||
|
||||
@ApiModelProperty("当前页数")
|
||||
private Integer pageNo;
|
||||
|
||||
@ApiModelProperty("每页查询数量")
|
||||
private Integer pageSize;
|
||||
|
||||
@ApiModelProperty("总记录数")
|
||||
private Long totalRecords;
|
||||
|
||||
@ApiModelProperty("是否最后一页")
|
||||
private Boolean isLast = Boolean.FALSE;
|
||||
|
||||
@ApiModelProperty("数据列表")
|
||||
private List<T> list;
|
||||
|
||||
|
||||
public static PageBaseResp empty() {
|
||||
PageBaseResp r = new PageBaseResp();
|
||||
r.setPageNo(1);
|
||||
r.setPageSize(0);
|
||||
r.setIsLast(true);
|
||||
r.setTotalRecords(0L);
|
||||
r.setList(new ArrayList());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> PageBaseResp<T> init(Integer pageNo, Integer pageSize, Long totalRecords, Boolean isLast, List<T> list) {
|
||||
return new PageBaseResp<>(pageNo, pageSize, totalRecords, isLast, list);
|
||||
}
|
||||
|
||||
public static <T> PageBaseResp<T> init(Integer pageNo, Integer pageSize, Long totalRecords, List<T> list) {
|
||||
return new PageBaseResp<>(pageNo, pageSize, totalRecords, isLastPage(totalRecords, pageNo, pageSize), list);
|
||||
}
|
||||
|
||||
public static <T> PageBaseResp<T> init(IPage<T> page) {
|
||||
return init((int) page.getCurrent(), (int) page.getSize(), page.getTotal(), page.getRecords());
|
||||
}
|
||||
|
||||
public static <T> PageBaseResp<T> init(IPage page, List<T> list) {
|
||||
return init((int) page.getCurrent(), (int) page.getSize(), page.getTotal(), list);
|
||||
}
|
||||
|
||||
public static <T> PageBaseResp<T> init(PageBaseResp resp, List<T> list) {
|
||||
return init(resp.getPageNo(), resp.getPageSize(), resp.getTotalRecords(), resp.getIsLast(), list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是最后一页
|
||||
*/
|
||||
public static Boolean isLastPage(long totalRecords, int pageNo, int pageSize) {
|
||||
if (pageSize == 0) {
|
||||
return false;
|
||||
}
|
||||
long pageTotal = totalRecords / pageSize + (totalRecords % pageSize == 0 ? 0 : 1);
|
||||
return pageNo >= pageTotal ? true : false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.abin.mallchat.common.common.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Description: 业务校验异常码
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum BusinessErrorEnum implements ErrorEnum {
|
||||
//==================================common==================================
|
||||
BUSINESS_ERROR(1001, "{}"),
|
||||
//==================================user==================================
|
||||
//==================================chat==================================
|
||||
SYSTEM_ERROR(1001, "系统出小差了,请稍后再试哦~~"),
|
||||
;
|
||||
private Integer code;
|
||||
private String msg;
|
||||
|
||||
@Override
|
||||
public Integer getErrorCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMsg() {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package com.abin.mallchat.common.common.exception;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BusinessException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
protected Integer errorCode;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
protected String errorMsg;
|
||||
|
||||
public BusinessException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BusinessException(String errorMsg) {
|
||||
super(errorMsg);
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
public BusinessException(Integer errorCode, String errorMsg) {
|
||||
super(errorMsg);
|
||||
this.errorCode = errorCode;
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
public BusinessException(Integer errorCode, String errorMsg, Throwable cause) {
|
||||
super(errorMsg, cause);
|
||||
this.errorCode = errorCode;
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
public BusinessException(ErrorEnum error) {
|
||||
super(error.getErrorMsg());
|
||||
this.errorCode = error.getErrorCode();
|
||||
this.errorMsg = error.getErrorMsg();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.abin.mallchat.common.common.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Description: 通用异常码
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum CommonErrorEnum implements ErrorEnum {
|
||||
|
||||
SYSTEM_ERROR(-1, "系统出小差了,请稍后再试哦~~"),
|
||||
PARAM_VALID(-2, "参数校验失败"),
|
||||
FREQUENCY_LIMIT(-3, "请求太频繁了,请稍后再试哦~~"),
|
||||
;
|
||||
private Integer code;
|
||||
private String msg;
|
||||
|
||||
@Override
|
||||
public Integer getErrorCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.abin.mallchat.common.common.exception;
|
||||
|
||||
public interface ErrorEnum {
|
||||
|
||||
Integer getErrorCode();
|
||||
|
||||
String getErrorMsg();
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.abin.mallchat.common.common.exception;
|
||||
|
||||
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* validation参数校验异常
|
||||
*/
|
||||
@ExceptionHandler(value = MethodArgumentNotValidException.class)
|
||||
public ApiResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException e) {
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
e.getBindingResult().getFieldErrors().forEach(x -> errorMsg.append(x.getField()).append(x.getDefaultMessage()).append(","));
|
||||
String message = errorMsg.toString();
|
||||
log.info("validation parameters error!The reason is:{}", message);
|
||||
return ApiResult.fail(-1, message.substring(0, message.length() - 1));
|
||||
}
|
||||
/**
|
||||
* 处理空指针的异常
|
||||
*/
|
||||
@ExceptionHandler(value = NullPointerException.class)
|
||||
public ApiResult exceptionHandler( NullPointerException e) {
|
||||
log.error("null point exception!The reason is:{}", e.getMessage(), e);
|
||||
return ApiResult.fail(CommonErrorEnum.SYSTEM_ERROR);
|
||||
}
|
||||
/**
|
||||
* 未知异常
|
||||
*/
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
public ApiResult systemExceptionHandler( Exception e) {
|
||||
log.error("system exception!The reason is:{}", e.getMessage(), e);
|
||||
return ApiResult.fail(CommonErrorEnum.SYSTEM_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义校验异常(如参数校验等)
|
||||
*/
|
||||
@ExceptionHandler(value = BusinessException.class)
|
||||
public ApiResult businessExceptionHandler(BusinessException e) {
|
||||
log.info("business exception!The reason is:{}", e.getMessage(), e);
|
||||
return ApiResult.fail(e.getErrorCode(), e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.abin.mallchat.common.common.exception;
|
||||
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
|
||||
import com.google.common.base.Charsets;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.CharSet;
|
||||
import org.apache.commons.lang3.CharSetUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import sun.awt.CharsetString;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Description: 业务校验异常码
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum HttpErrorEnum implements ErrorEnum {
|
||||
ACCESS_DENIED(401, "登录失效,请重新登录"),
|
||||
;
|
||||
private Integer httpCode;
|
||||
private String msg;
|
||||
|
||||
@Override
|
||||
public Integer getErrorCode() {
|
||||
return httpCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void sendHttpError(HttpServletResponse response) throws IOException {
|
||||
response.setStatus(this.getErrorCode());
|
||||
ApiResult responseData = ApiResult.fail(this);
|
||||
response.setContentType(ContentType.JSON.toString(Charsets.UTF_8));
|
||||
response.getWriter().write(JSONUtil.toJsonStr(responseData));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package com.abin.mallchat.common.common.utils;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.abin.mallchat.common.common.exception.BusinessErrorEnum;
|
||||
import com.abin.mallchat.common.common.exception.BusinessException;
|
||||
import com.abin.mallchat.common.common.exception.ErrorEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.UserBackpack;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 校验工具类
|
||||
*/
|
||||
public class AssertUtil {
|
||||
|
||||
//如果不是true,则抛异常
|
||||
public static void isTrue(boolean expression, String msg) {
|
||||
if (!expression) {
|
||||
throwException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void isTrue(boolean expression, ErrorEnum errorEnum, Object... args) {
|
||||
if (!expression) {
|
||||
throwException(errorEnum, args);
|
||||
}
|
||||
}
|
||||
|
||||
//如果是true,则抛异常
|
||||
public static void isFalse(boolean expression, String msg) {
|
||||
if (expression) {
|
||||
throwException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
//如果是true,则抛异常
|
||||
public static void isFalse(boolean expression, ErrorEnum errorEnum, Object... args) {
|
||||
if (expression) {
|
||||
throwException(errorEnum, args);
|
||||
}
|
||||
}
|
||||
|
||||
//如果不是非空对象,则抛异常
|
||||
public static void isNotEmpty(Object obj, String msg) {
|
||||
if (isEmpty(obj)) {
|
||||
throwException(msg);
|
||||
}
|
||||
}
|
||||
//如果不是非空对象,则抛异常
|
||||
public static void isEmpty(Object obj, String msg) {
|
||||
if (!isEmpty(obj)) {
|
||||
throwException(msg);
|
||||
}
|
||||
}
|
||||
public static void equal(Object o1,Object o2, String msg) {
|
||||
if (!ObjectUtil.equal(o1,o2)) {
|
||||
throwException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEmpty(Object obj) {
|
||||
return ObjectUtil.isEmpty(obj);
|
||||
}
|
||||
|
||||
private static void throwException(String msg) {
|
||||
throwException(null, msg);
|
||||
}
|
||||
|
||||
private static void throwException(ErrorEnum errorEnum, Object... arg) {
|
||||
if (Objects.isNull(errorEnum)) {
|
||||
errorEnum = BusinessErrorEnum.BUSINESS_ERROR;
|
||||
}
|
||||
throw new BusinessException(errorEnum.getErrorCode(), String.format(errorEnum.getErrorMsg(), arg));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.abin.mallchat.common.common.utils;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.lang.Tuple;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.request.PageBaseReq;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.PageBaseResp;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.google.errorprone.annotations.Var;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.ZSetOperations;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: 游标分页工具类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-28
|
||||
*/
|
||||
@Component
|
||||
public class CursorUtils {
|
||||
@Autowired
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
public <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());
|
||||
} else {
|
||||
typedTuples = redisUtils.zReverseRangeByScoreWithScores(redisKey, Double.parseDouble(cursorPageBaseReq.getCursor()), cursorPageBaseReq.getPageSize());
|
||||
}
|
||||
List<Pair<T, Double>> result = typedTuples
|
||||
.stream()
|
||||
.map(t -> Pair.of(typeConvert.apply(t.getValue()), t.getScore()))
|
||||
.sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
String cursor = Optional.ofNullable(CollectionUtil.getLast(result))
|
||||
.map(Pair::getValue)
|
||||
.map(String::valueOf)
|
||||
.orElse(null);
|
||||
Boolean isLast = result.size() != cursorPageBaseReq.getPageSize();
|
||||
return new CursorPageBaseResp(cursor, isLast, result);
|
||||
}
|
||||
|
||||
public <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())) {
|
||||
wrapper.lt(cursorColumn, request.getCursor());
|
||||
}
|
||||
wrapper.orderByDesc(cursorColumn);
|
||||
Page<T> page = mapper.page(request.plusPage(), wrapper);
|
||||
String cursor = Optional.ofNullable(CollectionUtil.getLast(page.getRecords()))
|
||||
.map(cursorColumn)
|
||||
.map(String::valueOf)
|
||||
.orElse(null);
|
||||
Boolean isLast = page.getRecords().size() != request.getPageSize();
|
||||
return new CursorPageBaseResp(cursor, isLast, page.getRecords());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
package com.abin.mallchat.common.common.utils;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.Claim;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Description: jwt的token生成与解析
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-04-03
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
/**
|
||||
* token秘钥,请勿泄露,请勿随便修改
|
||||
*/
|
||||
@Value("jwt.secret")
|
||||
private String secret;
|
||||
|
||||
private static final String UID_CLAIM = "uid";
|
||||
|
||||
/**
|
||||
* JWT生成Token.<br/>
|
||||
* <p>
|
||||
* JWT构成: header, payload, signature
|
||||
*/
|
||||
public String createToken(Long uid) {
|
||||
// build token
|
||||
String token = JWT.create()
|
||||
.withClaim(UID_CLAIM, uid) // 只存一个uid信息,其他的自己去redis查
|
||||
.sign(Algorithm.HMAC256(secret)); // signature
|
||||
return token;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
JwtUtils jwtUtils = new JwtUtils();
|
||||
String token = jwtUtils.createToken(123L);
|
||||
System.out.println(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密Token
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Claim> verifyToken(String token) {
|
||||
if (Objects.isNull(token)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
|
||||
DecodedJWT jwt = verifier.verify(token);
|
||||
return jwt.getClaims();
|
||||
} catch (Exception e) {
|
||||
log.info("decode error,token:{}", token, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据Token获取uid
|
||||
*
|
||||
* @param token
|
||||
* @return uid
|
||||
*/
|
||||
public Long getUidOrNull(String token) {
|
||||
return Optional.ofNullable(verifyToken(token))
|
||||
.map(map -> map.get(UID_CLAIM))
|
||||
.map(Claim::asLong)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,25 @@
|
||||
package com.abin.mallchat.common.common.utils;
|
||||
|
||||
import com.abin.mallchat.common.common.domain.dto.RequestInfo;
|
||||
|
||||
/**
|
||||
* Description: 请求上下文
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-04-05
|
||||
*/
|
||||
public class RequestHolder {
|
||||
|
||||
private static final ThreadLocal<RequestInfo> threadLocal = new ThreadLocal<>();
|
||||
|
||||
public static void set(RequestInfo requestInfo) {
|
||||
threadLocal.set(requestInfo);
|
||||
}
|
||||
|
||||
public static RequestInfo get() {
|
||||
return threadLocal.get();
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
threadLocal.remove();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.abin.mallchat.common.user.dao;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.ItemConfig;
|
||||
import com.abin.mallchat.common.user.mapper.ItemConfigMapper;
|
||||
import com.abin.mallchat.common.user.service.IItemConfigService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
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-03-19
|
||||
*/
|
||||
@Service
|
||||
public class ItemConfigDao extends ServiceImpl<ItemConfigMapper, ItemConfig> {
|
||||
|
||||
public List<ItemConfig> getByType(Integer type) {
|
||||
return lambdaQuery().eq(ItemConfig::getType, type).list();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package com.abin.mallchat.common.user.dao;
|
||||
|
||||
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.common.user.domain.entity.UserBackpack;
|
||||
import com.abin.mallchat.common.user.mapper.UserBackpackMapper;
|
||||
import com.abin.mallchat.common.user.service.IUserBackpackService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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-03-19
|
||||
*/
|
||||
@Service
|
||||
public class UserBackpackDao extends ServiceImpl<UserBackpackMapper, UserBackpack> {
|
||||
|
||||
public Integer getCountByValidItemId(Long uid, Long itemId) {
|
||||
LambdaQueryWrapper<UserBackpack> wrapper = new QueryWrapper<UserBackpack>().lambda()
|
||||
.eq(UserBackpack::getUid, uid)
|
||||
.eq(UserBackpack::getItemId, itemId)
|
||||
.eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus());
|
||||
return count(wrapper);
|
||||
}
|
||||
|
||||
public UserBackpack getFirstValidItem(Long uid, Long itemId) {
|
||||
LambdaQueryWrapper<UserBackpack> wrapper = new QueryWrapper<UserBackpack>().lambda()
|
||||
.eq(UserBackpack::getUid, uid)
|
||||
.eq(UserBackpack::getItemId, itemId)
|
||||
.eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus())
|
||||
.last("limit 1");
|
||||
return getOne(wrapper);
|
||||
}
|
||||
|
||||
public boolean invalidItem(Long itemId) {
|
||||
UserBackpack update = new UserBackpack();
|
||||
update.setItemId(itemId);
|
||||
update.setStatus(YesOrNoEnum.YES.getStatus());
|
||||
return updateById(update);
|
||||
}
|
||||
|
||||
public List<UserBackpack> getByItemIds(Long uid, List<Long> itemIds) {
|
||||
return lambdaQuery().eq(UserBackpack::getUid, uid)
|
||||
.in(UserBackpack::getItemId, itemIds)
|
||||
.eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus())
|
||||
.list();
|
||||
}
|
||||
|
||||
public UserBackpack getByIdp(String idempotent) {
|
||||
return lambdaQuery().eq(UserBackpack::getIdempotent, idempotent).one();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package com.abin.mallchat.common.user.dao;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.common.user.mapper.UserMapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Service
|
||||
public class UserDao extends ServiceImpl<UserMapper, User> {
|
||||
|
||||
public User getByOpenId(String openId) {
|
||||
LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda().eq(User::getOpenId, openId);
|
||||
return getOne(wrapper);
|
||||
}
|
||||
|
||||
public void modifyName(Long uid, String name) {
|
||||
User update = new User();
|
||||
update.setId(uid);
|
||||
update.setName(name);
|
||||
updateById(update);
|
||||
}
|
||||
|
||||
public void wearingBadge(Long uid, Long badgeId) {
|
||||
User update = new User();
|
||||
update.setId(uid);
|
||||
update.setItemId(badgeId);
|
||||
updateById(update);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.abin.mallchat.common.user.domain.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Description:
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-04-18
|
||||
*/
|
||||
@Data
|
||||
public class IpResult<T> implements Serializable {
|
||||
@ApiModelProperty("错误码")
|
||||
private Integer code;
|
||||
@ApiModelProperty("错误消息")
|
||||
private String msg;
|
||||
@ApiModelProperty("返回对象")
|
||||
private T data;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return Objects.nonNull(this.code) && this.code == 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.abin.mallchat.common.user.domain.entity;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户ip信息
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class IpDetail implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
//注册时的ip
|
||||
private String ip;
|
||||
//最新登录的ip
|
||||
private String isp;
|
||||
private String isp_id;
|
||||
private String city;
|
||||
private String city_id;
|
||||
private String country;
|
||||
private String country_id;
|
||||
private String region;
|
||||
private String region_id;
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package com.abin.mallchat.common.user.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户ip信息
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class IpInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
//注册时的ip
|
||||
private String createIp;
|
||||
//注册时的ip详情
|
||||
private IpDetail createIpDetail;
|
||||
//最新登录的ip
|
||||
private String updateIp;
|
||||
//最新登录的ip详情
|
||||
private IpDetail updateIpDetail;
|
||||
|
||||
public void refreshIp(String ip) {
|
||||
if (Objects.isNull(ip)) {
|
||||
return;
|
||||
}
|
||||
updateIp = ip;
|
||||
if (createIp == null) {
|
||||
createIp = ip;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要刷新的ip,这里判断更新ip就够,初始化的时候ip也是相同的,只需要设置的时候多设置进去就行
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String needRefreshIp() {
|
||||
boolean notNeedRefresh = Optional.ofNullable(updateIpDetail)
|
||||
.map(IpDetail::getIp)
|
||||
.filter(ip -> ip.equals(updateIp))
|
||||
.isPresent();
|
||||
return notNeedRefresh ? null : updateIp;
|
||||
}
|
||||
|
||||
public void refreshIpDetail(IpDetail ipDetail) {
|
||||
if (Objects.equals(createIp, ipDetail)) {
|
||||
createIpDetail = ipDetail;
|
||||
}
|
||||
if (Objects.equals(updateIp, ipDetail)) {
|
||||
updateIpDetail = ipDetail;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package com.abin.mallchat.common.user.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 功能物品配置表
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("item_config")
|
||||
public class ItemConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId("id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 物品类型 1改名卡 2徽章
|
||||
*/
|
||||
@TableField("type")
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 物品图片
|
||||
*/
|
||||
@TableField("img")
|
||||
private String img;
|
||||
|
||||
/**
|
||||
* 物品功能描述
|
||||
*/
|
||||
@TableField("`describe`")
|
||||
private String describe;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
package com.abin.mallchat.common.user.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户表
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName("user")
|
||||
public class User implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
@TableField("avatar")
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 性别 1为男性,2为女性
|
||||
*/
|
||||
@TableField("sex")
|
||||
private Integer sex;
|
||||
|
||||
/**
|
||||
* 微信openid用户标识
|
||||
*/
|
||||
@TableField("open_id")
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 最后上下线时间
|
||||
*/
|
||||
@TableField("last_opt_time")
|
||||
private Date lastOptTime;
|
||||
|
||||
/**
|
||||
* 最后上下线时间
|
||||
*/
|
||||
@TableField(value = "ip_info", typeHandler = JacksonTypeHandler.class)
|
||||
private IpInfo ipInfo;
|
||||
|
||||
/**
|
||||
* 佩戴的徽章id
|
||||
*/
|
||||
@TableField("item_id")
|
||||
private Long itemId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
|
||||
public IpInfo getIpInfo() {
|
||||
if (ipInfo == null) {
|
||||
ipInfo = new IpInfo();
|
||||
}
|
||||
return ipInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package com.abin.mallchat.common.user.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户背包表
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName("user_backpack")
|
||||
public class UserBackpack implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* uid
|
||||
*/
|
||||
@TableField("uid")
|
||||
private Long uid;
|
||||
|
||||
/**
|
||||
* 物品id
|
||||
*/
|
||||
@TableField("item_id")
|
||||
private Long itemId;
|
||||
|
||||
/**
|
||||
* 使用状态 0.未失效 1失效
|
||||
*/
|
||||
@TableField("status")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 幂等号
|
||||
*/
|
||||
@TableField("idempotent")
|
||||
private String idempotent;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.abin.mallchat.common.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 ChatActiveStatusEnum {
|
||||
ONLINE(1, "在线"),
|
||||
OFFLINE(2, "离线"),
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Integer, ChatActiveStatusEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(ChatActiveStatusEnum.values()).collect(Collectors.toMap(ChatActiveStatusEnum::getStatus, Function.identity()));
|
||||
}
|
||||
|
||||
public static ChatActiveStatusEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.abin.mallchat.common.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: 物品枚举
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-19
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ItemEnum {
|
||||
MODIFY_NAME_CARD(1L, ItemTypeEnum.MODIFY_NAME_CARD, "改名卡"),
|
||||
LIKE_BADGE(2L, ItemTypeEnum.BADGE, "爆赞徽章"),
|
||||
REG_TOP10_BADGE(2L, ItemTypeEnum.BADGE, "爆赞徽章"),
|
||||
REG_TOP100_BADGE(2L, ItemTypeEnum.BADGE, "爆赞徽章"),
|
||||
;
|
||||
|
||||
private final Long id;
|
||||
private final ItemTypeEnum typeEnum;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Long, ItemEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(ItemEnum.values()).collect(Collectors.toMap(ItemEnum::getId, Function.identity()));
|
||||
}
|
||||
|
||||
public static ItemEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.abin.mallchat.common.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: 物品枚举
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-19
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ItemTypeEnum {
|
||||
MODIFY_NAME_CARD(1, "改名卡"),
|
||||
BADGE(2, "徽章"),
|
||||
;
|
||||
|
||||
private final Integer type;
|
||||
private final String desc;
|
||||
|
||||
private static Map<Integer, ItemTypeEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(ItemTypeEnum.values()).collect(Collectors.toMap(ItemTypeEnum::getType, Function.identity()));
|
||||
}
|
||||
|
||||
public static ItemTypeEnum of(Integer type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.user.mapper;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.ItemConfig;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 功能物品配置表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
public interface ItemConfigMapper extends BaseMapper<ItemConfig> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.user.mapper;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.UserBackpack;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户背包表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
public interface UserBackpackMapper extends BaseMapper<UserBackpack> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.user.mapper;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.user.service;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.ItemConfig;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 功能物品配置表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
public interface IItemConfigService extends IService<ItemConfig> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.abin.mallchat.common.user.service;
|
||||
|
||||
import com.abin.mallchat.common.common.domain.enums.IdempotentEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.UserBackpack;
|
||||
import com.abin.mallchat.common.user.domain.enums.ItemEnum;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户背包表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
public interface IUserBackpackService {
|
||||
|
||||
|
||||
/**
|
||||
* 用户获取一个物品
|
||||
* @param uid 用户id
|
||||
* @param itemId 物品id
|
||||
* @param idempotentEnum 幂等类型
|
||||
* @param businessId 上层业务发送的唯一标识
|
||||
*/
|
||||
void acquireItem(Long uid, Long itemId, IdempotentEnum idempotentEnum, String businessId);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.abin.mallchat.common.user.service;
|
||||
|
||||
public interface IpService {
|
||||
/**
|
||||
* 异步更新用户ip详情
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
void refreshIpDetailAsync(Long id);
|
||||
}
|
||||
42
mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/ItemCache.java
vendored
Normal file
42
mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/ItemCache.java
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package com.abin.mallchat.common.user.service.cache;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import com.abin.mallchat.common.common.constant.RedisKey;
|
||||
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.abin.mallchat.common.common.utils.RedisUtils;
|
||||
import com.abin.mallchat.common.user.dao.ItemConfigDao;
|
||||
import com.abin.mallchat.common.user.dao.UserDao;
|
||||
import com.abin.mallchat.common.user.domain.entity.ItemConfig;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
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-03-27
|
||||
*/
|
||||
@Component
|
||||
public class ItemCache {//todo 多级缓存
|
||||
|
||||
@Autowired
|
||||
private ItemConfigDao itemConfigDao;
|
||||
|
||||
@Cacheable(cacheNames = "item", key = "'itemsByType:'+#type")
|
||||
public List<ItemConfig> getByType(Integer type) {
|
||||
return itemConfigDao.getByType(type);
|
||||
}
|
||||
|
||||
@Cacheable(cacheNames = "item", key = "'item:'+#itemId")
|
||||
public ItemConfig getById(Long itemId) {
|
||||
return itemConfigDao.getById(itemId);
|
||||
}
|
||||
}
|
||||
112
mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/UserCache.java
vendored
Normal file
112
mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/UserCache.java
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
package com.abin.mallchat.common.user.service.cache;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import com.abin.mallchat.common.common.constant.RedisKey;
|
||||
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.abin.mallchat.common.common.utils.RedisUtils;
|
||||
import com.abin.mallchat.common.user.dao.UserDao;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.security.PublicKey;
|
||||
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-27
|
||||
*/
|
||||
@Component
|
||||
public class UserCache {
|
||||
|
||||
@Autowired
|
||||
private RedisUtils redisUtils;
|
||||
@Autowired
|
||||
private CursorUtils cursorUtils;
|
||||
@Autowired
|
||||
private UserDao userDao;
|
||||
|
||||
public Long getOnlineNum() {
|
||||
String onlineKey = RedisKey.getKey(RedisKey.ONLINE_UID_ZET);
|
||||
return redisUtils.zCard(onlineKey);
|
||||
}
|
||||
|
||||
public Long getOfflineNum() {
|
||||
String offlineKey = RedisKey.getKey(RedisKey.OFFLINE_UID_ZET);
|
||||
return redisUtils.zCard(offlineKey);
|
||||
}
|
||||
|
||||
//用户上线
|
||||
public void online(Long uid, Date optTime) {
|
||||
String onlineKey = RedisKey.getKey(RedisKey.ONLINE_UID_ZET);
|
||||
String offlineKey = RedisKey.getKey(RedisKey.OFFLINE_UID_ZET);
|
||||
//移除离线表
|
||||
redisUtils.zRemove(offlineKey, uid);
|
||||
//更新上线表
|
||||
redisUtils.zAdd(onlineKey, uid, optTime.getTime());
|
||||
}
|
||||
|
||||
//用户下线
|
||||
public void offline(Long uid, Date optTime) {
|
||||
String onlineKey = RedisKey.getKey(RedisKey.ONLINE_UID_ZET);
|
||||
String offlineKey = RedisKey.getKey(RedisKey.OFFLINE_UID_ZET);
|
||||
//移除上线线表
|
||||
redisUtils.zRemove(onlineKey, uid);
|
||||
//更新上线表
|
||||
redisUtils.zAdd(offlineKey, uid, optTime.getTime());
|
||||
}
|
||||
|
||||
public CursorPageBaseResp<Pair<Long, Double>> getOnlineCursorPage(CursorPageBaseReq pageBaseReq) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息,盘路缓存模式
|
||||
*
|
||||
* @param uid
|
||||
* @return
|
||||
*/
|
||||
public User getUserInfo(Long uid) {//todo 后期做二级缓存
|
||||
return getUserInfoBatch(Collections.singleton(uid)).get(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息,盘路缓存模式
|
||||
*
|
||||
* @param uids
|
||||
* @return
|
||||
*/
|
||||
public Map<Long, User> getUserInfoBatch(Set<Long> uids) {
|
||||
List<String> keys = uids.stream().map(a -> RedisKey.getKey(RedisKey.USER_INFO_STRING, a)).collect(Collectors.toList());
|
||||
List<User> mget = redisUtils.mget(keys, User.class);
|
||||
Map<Long, User> map = mget.stream().collect(Collectors.toMap(User::getId, Function.identity()));
|
||||
//还需要load更新的uid
|
||||
List<Long> needLoadUidList = uids.stream().filter(a -> !map.containsKey(a)).collect(Collectors.toList());
|
||||
if (CollUtil.isNotEmpty(needLoadUidList)) {
|
||||
List<User> needLoadUserList = userDao.listByIds(needLoadUidList);
|
||||
Map<String, User> redisMap = needLoadUserList.stream().collect(Collectors.toMap(a -> RedisKey.getKey(RedisKey.USER_INFO_STRING, a.getId()), Function.identity()));
|
||||
redisUtils.mset(redisMap, 5 * 60);
|
||||
map.putAll(needLoadUserList.stream().collect(Collectors.toMap(User::getId, Function.identity())));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public void delUserInfo(Long uid) {
|
||||
String key = RedisKey.getKey(RedisKey.USER_INFO_STRING, uid);
|
||||
redisUtils.del(key);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package com.abin.mallchat.common.user.service.impl;
|
||||
|
||||
import cn.hutool.core.lang.TypeReference;
|
||||
import cn.hutool.core.thread.NamedThreadFactory;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
|
||||
import com.abin.mallchat.common.user.dao.UserDao;
|
||||
import com.abin.mallchat.common.user.domain.dto.IpResult;
|
||||
import com.abin.mallchat.common.user.domain.entity.IpDetail;
|
||||
import com.abin.mallchat.common.user.domain.entity.IpInfo;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.common.user.service.IpService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* Description: ip
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-04-18
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class IpServiceImpl implements IpService, DisposableBean {
|
||||
private static ExecutorService executor = new ThreadPoolExecutor(1, 1,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(500), new NamedThreadFactory("refresh-ipDetail", false));
|
||||
|
||||
@Autowired
|
||||
private UserDao userDao;
|
||||
|
||||
@Override
|
||||
public void refreshIpDetailAsync(Long uid) {
|
||||
executor.execute(() -> {
|
||||
User user = userDao.getById(uid);
|
||||
IpInfo ipInfo = user.getIpInfo();
|
||||
String ip = ipInfo.needRefreshIp();
|
||||
if (StrUtil.isBlank(ip)) {
|
||||
return;
|
||||
}
|
||||
IpDetail ipDetail = TryGetIpDetailOrNullTreeTimes(ip);
|
||||
if (Objects.nonNull(ipDetail)) {
|
||||
ipInfo.refreshIpDetail(ipDetail);
|
||||
User update = new User();
|
||||
update.setId(uid);
|
||||
update.setIpInfo(ipInfo);
|
||||
userDao.updateById(update);
|
||||
} else {
|
||||
log.error("get ip detail fail ip:{},uid:{}", ip, uid);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static IpDetail TryGetIpDetailOrNullTreeTimes(String ip) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
IpDetail ipDetail = getIpDetailOrNull(ip);
|
||||
if (Objects.nonNull(ipDetail)) {
|
||||
return ipDetail;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IpDetail getIpDetailOrNull(String ip) {
|
||||
String body = HttpUtil.get("https://ip.taobao.com/outGetIpInfo?ip=" + ip + "&accessKey=alibaba-inc");
|
||||
try {
|
||||
IpResult<IpDetail> result = JSONUtil.toBean(body, new TypeReference<IpResult<IpDetail>>() {
|
||||
}, false);
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//测试耗时结果 100次查询总耗时约100s,平均一次成功查询需要1s,可以接受
|
||||
//第99次成功,目前耗时:99545ms
|
||||
public static void main(String[] args) {
|
||||
Date begin = new Date();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int finalI = i;
|
||||
executor.execute(() -> {
|
||||
IpDetail ipDetail = TryGetIpDetailOrNullTreeTimes("113.90.36.126");
|
||||
if (Objects.nonNull(ipDetail)) {
|
||||
Date date = new Date();
|
||||
System.out.println(String.format("第%d次成功,目前耗时:%dms", finalI, (date.getTime() - begin.getTime())));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws InterruptedException {
|
||||
executor.shutdown();
|
||||
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {//最多等30秒,处理不完就拉倒
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Timed out while waiting for executor [{}] to terminate", executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package com.abin.mallchat.common.user.service.impl;
|
||||
|
||||
import com.abin.mallchat.common.common.domain.enums.IdempotentEnum;
|
||||
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
|
||||
import com.abin.mallchat.common.user.dao.ItemConfigDao;
|
||||
import com.abin.mallchat.common.user.dao.UserBackpackDao;
|
||||
import com.abin.mallchat.common.user.domain.entity.ItemConfig;
|
||||
import com.abin.mallchat.common.user.domain.entity.UserBackpack;
|
||||
import com.abin.mallchat.common.user.domain.enums.ItemTypeEnum;
|
||||
import com.abin.mallchat.common.user.service.IUserBackpackService;
|
||||
import com.abin.mallchat.common.user.service.cache.ItemCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户背包表 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@Service
|
||||
public class UserBackpackServiceImpl implements IUserBackpackService {
|
||||
@Autowired
|
||||
private UserBackpackDao userBackpackDao;
|
||||
@Autowired
|
||||
private ItemConfigDao itemConfigDao;
|
||||
@Autowired
|
||||
private ItemCache itemCache;
|
||||
|
||||
@Override
|
||||
public void acquireItem(Long uid, Long itemId, IdempotentEnum idempotentEnum, String businessId) {//todo 分布式锁
|
||||
String idempotent = getIdempotent(itemId, idempotentEnum, businessId);
|
||||
UserBackpack userBackpack = userBackpackDao.getByIdp(idempotent);
|
||||
//幂等检查
|
||||
if (Objects.nonNull(userBackpack)) {
|
||||
return;
|
||||
}
|
||||
//业务检查
|
||||
ItemConfig itemConfig = itemCache.getById(itemId);
|
||||
if (ItemTypeEnum.BADGE.getType().equals(itemConfig.getType())) {//徽章类型做唯一性检查
|
||||
Integer countByValidItemId = userBackpackDao.getCountByValidItemId(uid, itemId);
|
||||
if (countByValidItemId > 0) {//已经有徽章了不发
|
||||
return;
|
||||
}
|
||||
}
|
||||
//发物品
|
||||
UserBackpack insert = UserBackpack.builder()
|
||||
.itemId(itemId)
|
||||
.status(YesOrNoEnum.NO.getStatus())
|
||||
.idempotent(idempotent)
|
||||
.build();
|
||||
userBackpackDao.save(insert);
|
||||
}
|
||||
|
||||
private String getIdempotent(Long itemId, IdempotentEnum idempotentEnum, String businessId) {
|
||||
return String.format("%d_%d_%s", itemId, idempotentEnum.getType(), businessId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#todo 记得把这些配置信息补充了
|
||||
##################mysql配置##################
|
||||
mallchat.mysql.ip=127.0.0.1
|
||||
mallchat.mysql.port=3306
|
||||
mallchat.mysql.db=mallchat
|
||||
mallchat.mysql.username=root
|
||||
mallchat.mysql.password=123456
|
||||
##################redis配置##################
|
||||
mallchat.redis.host=127.0.0.1
|
||||
mallchat.redis.port=6379
|
||||
mallchat.redis.password=123456
|
||||
##################jwt##################
|
||||
mallchat.jwt.secret=dsfsdfsdfsdfsd
|
||||
##################微信公众号信息##################
|
||||
mallchat.wx.callback=http://127.0.0.1:8080
|
||||
mallchat.wx.appId=appid
|
||||
mallchat.wx.secret=380bfc1c9147fdsf4sf07
|
||||
# 接口配置里的Token值
|
||||
mallchat.wx.token=sdfsf
|
||||
# 接口配置里的EncodingAESKey值
|
||||
mallchat.wx.aesKey=sha1
|
||||
60
mallchat-common/src/main/resources/application.yml
Normal file
60
mallchat-common/src/main/resources/application.yml
Normal file
@ -0,0 +1,60 @@
|
||||
logging:
|
||||
level:
|
||||
org.springframework.web: INFO
|
||||
com.github.binarywang.demo.wx.mp: DEBUG
|
||||
me.chanjar.weixin: DEBUG
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath:mapper/**/*.xml
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
map-underscore-to-camel-case: true
|
||||
spring:
|
||||
profiles:
|
||||
#运行的环境
|
||||
active: test
|
||||
application:
|
||||
name: mallchat
|
||||
datasource:
|
||||
url: jdbc:mysql://${mallchat.mysql.ip}:${mallchat.mysql.port}/${mallchat.mysql.db}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
|
||||
username: ${mallchat.mysql.username}
|
||||
password: ${mallchat.mysql.password}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
mvc:
|
||||
pathmatch:
|
||||
matching-strategy: ANT_PATH_MATCHER
|
||||
redis:
|
||||
# Redis服务器地址
|
||||
host: ${mallchat.redis.host}
|
||||
# Redis服务器端口号
|
||||
port: ${mallchat.redis.port}
|
||||
# 使用的数据库索引,默认是0
|
||||
database: 0
|
||||
# 连接超时时间
|
||||
timeout: 1800000
|
||||
# 设置密码
|
||||
password: ${mallchat.redis.password}
|
||||
lettuce:
|
||||
pool:
|
||||
# 最大阻塞等待时间,负数表示没有限制
|
||||
max-wait: -1
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 5
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中最大连接数,负数表示没有限制
|
||||
max-active: 20
|
||||
jackson:
|
||||
serialization:
|
||||
write-dates-as-timestamps: true
|
||||
jwt:
|
||||
secret: ${mallchat.jwt.secret}
|
||||
wx:
|
||||
mp:
|
||||
# callback: http://f4cd-113-92-129-127.ngrok.io
|
||||
callback: ${mallchat.wx.callback}
|
||||
configs:
|
||||
- appId: ${mallchat.wx.appId} # 第一个公众号的appid
|
||||
secret: ${mallchat.wx.secret} # 公众号的appsecret
|
||||
token: ${mallchat.wx.token} # 接口配置里的Token值
|
||||
aesKey: ${mallchat.wx.aesKey} # 接口配置里的EncodingAESKey值
|
||||
@ -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.MessageMapper">
|
||||
|
||||
</mapper>
|
||||
@ -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.MessageMarkMapper">
|
||||
|
||||
</mapper>
|
||||
@ -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>
|
||||
@ -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.user.mapper.ItemConfigMapper">
|
||||
|
||||
</mapper>
|
||||
@ -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.user.mapper.UserBackpackMapper">
|
||||
|
||||
</mapper>
|
||||
@ -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.user.mapper.UserMapper">
|
||||
|
||||
</mapper>
|
||||
29
mallchat-custom-server/pom.xml
Normal file
29
mallchat-custom-server/pom.xml
Normal 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>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package com.abin.mallchat.custom.chat.controller;
|
||||
|
||||
|
||||
import com.abin.mallchat.common.common.annotation.FrequencyControl;
|
||||
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.ApiResult;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.IdRespVO;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||
import com.abin.mallchat.common.common.utils.RequestHolder;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 群聊相关接口
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* @since 2023-03-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/capi/chat")
|
||||
@Api(tags = "聊天室相关接口")
|
||||
public class ChatController {
|
||||
@Autowired
|
||||
private ChatService chatService;
|
||||
@GetMapping("/public/room/page")
|
||||
@ApiOperation("会话列表")
|
||||
public ApiResult<CursorPageBaseResp<ChatRoomResp>> getRoomPage(CursorPageBaseReq request) {
|
||||
return ApiResult.success(chatService.getRoomPage(request, RequestHolder.get().getUid()));
|
||||
}
|
||||
@GetMapping("/public/member/page")
|
||||
@ApiOperation("群成员列表")
|
||||
public ApiResult<CursorPageBaseResp<ChatMemberResp>> getMemberPage(CursorPageBaseReq request) {
|
||||
return ApiResult.success(chatService.getMemberPage(request));
|
||||
}
|
||||
|
||||
@GetMapping("/public/member/statistic")
|
||||
@ApiOperation("群成员人数统计")
|
||||
public ApiResult<ChatMemberStatisticResp> getMemberStatistic() {
|
||||
return ApiResult.success(chatService.getMemberStatistic());
|
||||
}
|
||||
|
||||
@GetMapping("/public/msg/page")
|
||||
@ApiOperation("消息列表")
|
||||
public ApiResult<CursorPageBaseResp<ChatMessageResp>> getMsgPage(ChatMessagePageReq request) {
|
||||
return ApiResult.success(chatService.getMsgPage(request, RequestHolder.get().getUid()));
|
||||
}
|
||||
|
||||
@PostMapping("/msg")
|
||||
@ApiOperation("发送消息")
|
||||
@FrequencyControl(time = 5,count = 2)
|
||||
@FrequencyControl(time = 30,count = 5)
|
||||
@FrequencyControl(time = 60,count = 10)
|
||||
public ApiResult<IdRespVO> sendMsg(@Valid @RequestBody ChatMessageReq request) {
|
||||
return ApiResult.success(IdRespVO.id(chatService.sendMsg(request, RequestHolder.get().getUid())));
|
||||
}
|
||||
|
||||
@PutMapping("/msg/mark")
|
||||
@ApiOperation("消息标记")
|
||||
public ApiResult<Void> setMsgMark(@Valid @RequestBody ChatMessageMarkReq request) {//分布式锁
|
||||
chatService.setMsgMark(RequestHolder.get().getUid(),request);
|
||||
return ApiResult.success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.request;
|
||||
|
||||
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Description: 消息标记请求
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-29
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMessageMarkReq{
|
||||
@NotNull
|
||||
@ApiModelProperty("消息id")
|
||||
private Long msgId;
|
||||
|
||||
@NotNull
|
||||
@ApiModelProperty("标记类型 1点赞 2举报")
|
||||
private Integer markType;
|
||||
|
||||
@NotNull
|
||||
@ApiModelProperty("动作类型 1确认 2取消")
|
||||
private Integer actType;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.request;
|
||||
|
||||
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Description: 消息列表请求
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-29
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMessagePageReq extends CursorPageBaseReq {
|
||||
@NotNull
|
||||
@ApiModelProperty("会话id")
|
||||
private Long roomId;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.request;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* Description: 消息发送请求体
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMessageReq {
|
||||
|
||||
@NotNull
|
||||
@Length( max = 10000,message = "消息内容过长,服务器扛不住啊,兄dei")
|
||||
@ApiModelProperty("消息内容")
|
||||
private String content;
|
||||
|
||||
@NotNull
|
||||
@ApiModelProperty("会话id")
|
||||
private Long roomId;
|
||||
|
||||
@ApiModelProperty("回复的消息id,如果没有别传就好")
|
||||
private Long replyMsgId;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.response;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Description: 群成员列表的成员信息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMemberResp {
|
||||
@ApiModelProperty("uid")
|
||||
private Long uid;
|
||||
@ApiModelProperty("用户名称")
|
||||
private String name;
|
||||
@ApiModelProperty("头像")
|
||||
private String avatar;
|
||||
/**
|
||||
* @see com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum
|
||||
*/
|
||||
@ApiModelProperty("在线状态 1在线 2离线")
|
||||
private Integer activeStatus;
|
||||
@ApiModelProperty("最后一次上下线时间")
|
||||
private Date lastOptTime;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.response;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Description: 群成员统计信息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMemberStatisticResp {
|
||||
|
||||
@ApiModelProperty("在线人数")
|
||||
private Long onlineNum;//在线人数
|
||||
@ApiModelProperty("总人数")
|
||||
private Long totalNum;//总人数
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.response;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description: 消息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMessageResp {
|
||||
|
||||
@ApiModelProperty("发送者信息")
|
||||
private UserInfo fromUser;
|
||||
@ApiModelProperty("消息详情")
|
||||
private Message message;
|
||||
|
||||
@Data
|
||||
public static class UserInfo {
|
||||
@ApiModelProperty("用户名称")
|
||||
private String username;
|
||||
@ApiModelProperty("用户id")
|
||||
private Long uid;
|
||||
@ApiModelProperty("头像")
|
||||
private String avatar;
|
||||
@ApiModelProperty("徽章标识,如果没有展示null")
|
||||
private Badge badge;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Message {
|
||||
@ApiModelProperty("消息id")
|
||||
private Long id;
|
||||
@ApiModelProperty("消息发送时间")
|
||||
private Date sendTime;
|
||||
@ApiModelProperty("消息内容")
|
||||
private String content;
|
||||
@ApiModelProperty("消息类型 1正常文本 2.爆赞 (点赞超过10)3.危险发言(举报超5)")
|
||||
private Integer type;
|
||||
@ApiModelProperty("消息标记")
|
||||
private MessageMark messageMark;
|
||||
@ApiModelProperty("父消息,如果没有父消息,返回的是null")
|
||||
private ReplyMsg reply;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ReplyMsg {
|
||||
@ApiModelProperty("消息id")
|
||||
private Long id;
|
||||
@ApiModelProperty("用户名称")
|
||||
private String username;
|
||||
@ApiModelProperty("消息内容")
|
||||
private String content;
|
||||
@ApiModelProperty("是否可消息跳转 0否 1是")
|
||||
private Integer canCallback;
|
||||
@ApiModelProperty("跳转间隔的消息条数")
|
||||
private Integer gapCount;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MessageMark {
|
||||
@ApiModelProperty("点赞数")
|
||||
private Integer likeCount;
|
||||
@ApiModelProperty("该用户是否已经点赞 0否 1是")
|
||||
private Integer userLike;
|
||||
@ApiModelProperty("举报数")
|
||||
private Integer dislikeCount;
|
||||
@ApiModelProperty("该用户是否已经举报 0否 1是")
|
||||
private Integer userDislike;
|
||||
}
|
||||
@Data
|
||||
private static class Badge {
|
||||
@ApiModelProperty("徽章图像")
|
||||
private String img;
|
||||
@ApiModelProperty("徽章说明")
|
||||
private String describe;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.response;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Description: 群成员列表的成员信息
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatRoomResp {
|
||||
@ApiModelProperty("会话id")
|
||||
private Long id;
|
||||
@ApiModelProperty("会话名称")
|
||||
private String name;
|
||||
@ApiModelProperty("会话类型 1大群聊 2沸点")
|
||||
private Integer type;
|
||||
@ApiModelProperty("房间最后活跃时间")
|
||||
private Date lastActiveTime;
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package com.abin.mallchat.custom.chat.service;
|
||||
|
||||
import com.abin.mallchat.common.chat.dao.MessageDao;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.MessageAdapter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Description: 消息处理类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
public interface ChatService {
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
Long sendMsg(ChatMessageReq request, Long uid);
|
||||
|
||||
/**
|
||||
* 根据消息获取消息前端展示的物料
|
||||
* @param message
|
||||
* @param receiveUid 接受消息的uid,可null
|
||||
* @return
|
||||
*/
|
||||
ChatMessageResp getMsgResp(Message message,Long receiveUid);
|
||||
|
||||
/**
|
||||
* 获取群成员列表
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
CursorPageBaseResp<ChatMemberResp> getMemberPage(CursorPageBaseReq request);
|
||||
|
||||
/**
|
||||
* 获取消息列表
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq request,@Nullable Long receiveUid);
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* @param request
|
||||
* @param uid
|
||||
* @return
|
||||
*/
|
||||
CursorPageBaseResp<ChatRoomResp> getRoomPage(CursorPageBaseReq request, Long uid);
|
||||
|
||||
ChatMemberStatisticResp getMemberStatistic();
|
||||
|
||||
void setMsgMark(Long uid, ChatMessageMarkReq request);
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.abin.mallchat.custom.chat.service.adapter;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum;
|
||||
import com.abin.mallchat.common.user.service.cache.UserCache;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: 成员适配器
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MemberAdapter {
|
||||
@Autowired
|
||||
private UserCache userCache;
|
||||
|
||||
public List<ChatMemberResp> buildMember(List<Pair<Long, Double>> list, ChatActiveStatusEnum statusEnum) {
|
||||
return list.stream().map(a -> {
|
||||
ChatMemberResp resp = new ChatMemberResp();
|
||||
resp.setActiveStatus(statusEnum.getStatus());
|
||||
resp.setLastOptTime(new Date(a.getValue().longValue()));
|
||||
resp.setUid(a.getKey());
|
||||
User userInfo = userCache.getUserInfo(a.getKey());
|
||||
resp.setName(userInfo.getName());
|
||||
resp.setAvatar(userInfo.getAvatar());
|
||||
return resp;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
package com.abin.mallchat.custom.chat.service.adapter;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
||||
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.sun.org.apache.regexp.internal.RE;
|
||||
import org.yaml.snakeyaml.error.Mark;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: 消息适配器
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
public class MessageAdapter {
|
||||
public static final int CAN_CALLBACK_GAP_COUNT = 100;
|
||||
|
||||
public static Message buildMsgSave(ChatMessageReq request, Long uid) {
|
||||
return Message.builder()
|
||||
.replyMsgId(request.getReplyMsgId())
|
||||
.content(request.getContent())
|
||||
.fromUid(uid)
|
||||
.roomId(request.getRoomId())
|
||||
.status(MessageStatusEnum.NORMAL.getStatus())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
public static List<ChatMessageResp> buildMsgResp(List<Message> messages, Map<Long, Message> replyMap, Map<Long, User> userMap, List<MessageMark> msgMark, Long receiveUid) {
|
||||
Map<Long, List<MessageMark>> markMap = msgMark.stream().collect(Collectors.groupingBy(MessageMark::getMsgId));
|
||||
return messages.stream().map(a -> {
|
||||
ChatMessageResp resp = new ChatMessageResp();
|
||||
resp.setFromUser(buildFromUser(userMap.get(a.getFromUid())));
|
||||
resp.setMessage(buildMessage(a, replyMap, userMap, markMap.getOrDefault(a.getId(), new ArrayList<>()), receiveUid));
|
||||
return resp;
|
||||
})
|
||||
.sorted(Comparator.comparing(a -> a.getMessage().getSendTime()))//帮前端排好序,更方便它展示
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static ChatMessageResp.Message buildMessage(Message message, Map<Long, Message> replyMap, Map<Long, User> userMap, List<MessageMark> marks, Long receiveUid) {
|
||||
ChatMessageResp.Message messageVO = new ChatMessageResp.Message();
|
||||
BeanUtil.copyProperties(message, messageVO);
|
||||
messageVO.setSendTime(message.getCreateTime());
|
||||
Message replyMessage = replyMap.get(message.getReplyMsgId());
|
||||
//回复消息
|
||||
if (Objects.nonNull(replyMessage)) {
|
||||
ChatMessageResp.ReplyMsg replyMsgVO = new ChatMessageResp.ReplyMsg();
|
||||
replyMsgVO.setId(replyMessage.getId());
|
||||
replyMsgVO.setContent(replyMessage.getContent());
|
||||
User replyUser = userMap.get(replyMessage.getFromUid());
|
||||
replyMsgVO.setUsername(replyUser.getName());
|
||||
replyMsgVO.setCanCallback(YesOrNoEnum.toStatus(Objects.nonNull(message.getGapCount()) && message.getGapCount() <= CAN_CALLBACK_GAP_COUNT));
|
||||
replyMsgVO.setGapCount(message.getGapCount());
|
||||
messageVO.setReply(replyMsgVO);
|
||||
}
|
||||
//消息标记
|
||||
messageVO.setMessageMark(buildMsgMark(marks, receiveUid));
|
||||
return messageVO;
|
||||
}
|
||||
|
||||
private static ChatMessageResp.MessageMark buildMsgMark(List<MessageMark> marks, Long receiveUid) {
|
||||
Map<Integer, List<MessageMark>> typeMap = marks.stream().collect(Collectors.groupingBy(MessageMark::getType));
|
||||
List<MessageMark> likeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.LIKE.getType(), new ArrayList<>());
|
||||
List<MessageMark> dislikeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.DISLIKE.getType(), new ArrayList<>());
|
||||
ChatMessageResp.MessageMark mark = new ChatMessageResp.MessageMark();
|
||||
mark.setLikeCount(likeMarks.size());
|
||||
mark.setUserLike(Optional.ofNullable(receiveUid).filter(uid -> likeMarks.stream().anyMatch(a -> a.getUid().equals(uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus()));
|
||||
mark.setDislikeCount(dislikeMarks.size());
|
||||
mark.setUserDislike(Optional.ofNullable(receiveUid).filter(uid -> dislikeMarks.stream().anyMatch(a -> a.getUid().equals(uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus()));
|
||||
return mark;
|
||||
}
|
||||
|
||||
private static ChatMessageResp.UserInfo buildFromUser(User fromUser) {
|
||||
ChatMessageResp.UserInfo userInfo = new ChatMessageResp.UserInfo();
|
||||
userInfo.setUsername(fromUser.getName());
|
||||
userInfo.setAvatar(fromUser.getAvatar());
|
||||
userInfo.setUid(fromUser.getId());
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.abin.mallchat.custom.chat.service.adapter;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Room;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: 消息适配器
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
public class RoomAdapter {
|
||||
|
||||
|
||||
public static List<ChatRoomResp> buildResp(List<Room> list) {
|
||||
return list.stream()
|
||||
.map(a -> {
|
||||
ChatRoomResp resp = new ChatRoomResp();
|
||||
BeanUtil.copyProperties(a, resp);
|
||||
resp.setLastActiveTime(a.getActiveTime());
|
||||
return resp;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.abin.mallchat.custom.chat.service.helper;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum;
|
||||
|
||||
/**
|
||||
* Description: 成员列表工具类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-28
|
||||
*/
|
||||
public class ChatMemberHelper {
|
||||
private static final String SEPARATOR = "_";
|
||||
|
||||
public static Pair<ChatActiveStatusEnum, String> getCursorPair(String cursor) {
|
||||
ChatActiveStatusEnum activeStatusEnum = ChatActiveStatusEnum.ONLINE;
|
||||
String timeCursor = null;
|
||||
if (StrUtil.isNotBlank(cursor)) {
|
||||
String activeStr = cursor.split(SEPARATOR)[0];
|
||||
String timeStr = cursor.split(SEPARATOR)[1];
|
||||
activeStatusEnum = ChatActiveStatusEnum.of(Integer.parseInt(activeStr));
|
||||
timeCursor = timeStr;
|
||||
}
|
||||
return Pair.of(activeStatusEnum, timeCursor);
|
||||
}
|
||||
|
||||
public static String generateCursor(ChatActiveStatusEnum activeStatusEnum, String timeCursor) {
|
||||
return activeStatusEnum.getStatus() + SEPARATOR + timeCursor;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
package com.abin.mallchat.custom.chat.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import com.abin.mallchat.common.chat.dao.MessageDao;
|
||||
import com.abin.mallchat.common.chat.dao.MessageMarkDao;
|
||||
import com.abin.mallchat.common.chat.dao.RoomDao;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Room;
|
||||
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
|
||||
import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
|
||||
import com.abin.mallchat.common.common.exception.BusinessException;
|
||||
import com.abin.mallchat.common.common.utils.AssertUtil;
|
||||
import com.abin.mallchat.common.user.dao.UserDao;
|
||||
import com.abin.mallchat.common.user.domain.entity.User;
|
||||
import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum;
|
||||
import com.abin.mallchat.common.user.service.cache.UserCache;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.MemberAdapter;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.RoomAdapter;
|
||||
import com.abin.mallchat.custom.chat.service.helper.ChatMemberHelper;
|
||||
import com.abin.mallchat.custom.common.event.MessageMarkEvent;
|
||||
import com.abin.mallchat.custom.common.event.MessageSendEvent;
|
||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||
import com.abin.mallchat.custom.chat.service.adapter.MessageAdapter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Description: 消息处理类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ChatServiceImpl implements ChatService {
|
||||
public static final long ROOM_GROUP_ID = 1L;
|
||||
@Autowired
|
||||
private MessageDao messageDao;
|
||||
@Autowired
|
||||
private UserDao userDao;
|
||||
@Autowired
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
@Autowired
|
||||
private UserCache userCache;
|
||||
@Autowired
|
||||
private MemberAdapter memberAdapter;
|
||||
@Autowired
|
||||
private RoomDao roomDao;
|
||||
@Autowired
|
||||
private MessageMarkDao messageMarkDao;
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Long sendMsg(ChatMessageReq request, Long uid) {
|
||||
//校验下回复消息
|
||||
Message replyMsg =null;
|
||||
if(Objects.nonNull(request.getReplyMsgId())){
|
||||
replyMsg = messageDao.getById(request.getReplyMsgId());
|
||||
AssertUtil.isNotEmpty(replyMsg,"回复消息不存在");
|
||||
AssertUtil.equal(replyMsg.getRoomId(),request.getRoomId(),"只能回复相同会话内的消息");
|
||||
|
||||
}
|
||||
Message insert = MessageAdapter.buildMsgSave(request, uid);
|
||||
messageDao.save(insert);
|
||||
//如果有回复消息
|
||||
if(Objects.nonNull(replyMsg)){
|
||||
Integer gapCount = messageDao.getGapCount(request.getRoomId(), replyMsg.getId(), insert.getId());
|
||||
messageDao.updateGapCount(insert.getId(),gapCount);
|
||||
}
|
||||
//发布消息发送事件
|
||||
applicationEventPublisher.publishEvent(new MessageSendEvent(this, insert.getId()));
|
||||
return insert.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatMessageResp getMsgResp(Message message, Long receiveUid) {
|
||||
return CollUtil.getFirst(getMsgRespBatch(Collections.singletonList(message), receiveUid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPageBaseResp<ChatMemberResp> getMemberPage(CursorPageBaseReq request) {
|
||||
Pair<ChatActiveStatusEnum, String> pair = ChatMemberHelper.getCursorPair(request.getCursor());
|
||||
ChatActiveStatusEnum activeStatusEnum = pair.getKey();
|
||||
String timeCursor = pair.getValue();
|
||||
List<ChatMemberResp> resultList = new ArrayList<>();//最终列表
|
||||
Boolean isLast = Boolean.FALSE;
|
||||
if (activeStatusEnum == ChatActiveStatusEnum.ONLINE) {//在线列表
|
||||
CursorPageBaseResp<Pair<Long, Double>> cursorPage = userCache.getOnlineCursorPage(new CursorPageBaseReq(request.getPageSize(), timeCursor));
|
||||
resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.ONLINE));//添加在线列表
|
||||
if (cursorPage.getIsLast()) {//如果是最后一页,从离线列表再补点数据
|
||||
Integer leftSize = request.getPageSize() - cursorPage.getList().size();
|
||||
cursorPage = userCache.getOfflineCursorPage(new CursorPageBaseReq(leftSize, null));
|
||||
resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.OFFLINE));//添加离线线列表
|
||||
activeStatusEnum = ChatActiveStatusEnum.OFFLINE;
|
||||
}
|
||||
timeCursor = cursorPage.getCursor();
|
||||
isLast = cursorPage.getIsLast();
|
||||
} else if (activeStatusEnum == ChatActiveStatusEnum.OFFLINE) {//离线列表
|
||||
CursorPageBaseResp<Pair<Long, Double>> cursorPage = userCache.getOfflineCursorPage(new CursorPageBaseReq(request.getPageSize(), timeCursor));
|
||||
resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.OFFLINE));//添加离线线列表
|
||||
timeCursor = cursorPage.getCursor();
|
||||
isLast = cursorPage.getIsLast();
|
||||
}
|
||||
//组装结果
|
||||
return new CursorPageBaseResp<>(ChatMemberHelper.generateCursor(activeStatusEnum, timeCursor), isLast, resultList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq request, Long receiveUid) {
|
||||
CursorPageBaseResp<Message> cursorPage = messageDao.getCursorPage(request.getRoomId(), request);
|
||||
if (cursorPage.isEmpty()) {
|
||||
return CursorPageBaseResp.empty();
|
||||
}
|
||||
return CursorPageBaseResp.init(cursorPage, getMsgRespBatch(cursorPage.getList(), receiveUid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPageBaseResp<ChatRoomResp> getRoomPage(CursorPageBaseReq request, Long uid) {
|
||||
CursorPageBaseResp<Room> cursorPage = roomDao.getCursorPage(request);
|
||||
if (request.isFirstPage()) {
|
||||
//第一页插入置顶的大群聊
|
||||
Room group = roomDao.getById(ROOM_GROUP_ID);
|
||||
cursorPage.getList().add(0, group);
|
||||
}
|
||||
return CursorPageBaseResp.init(cursorPage, RoomAdapter.buildResp(cursorPage.getList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatMemberStatisticResp getMemberStatistic() {
|
||||
System.out.println(Thread.currentThread().getName());
|
||||
Long onlineNum = userCache.getOnlineNum();
|
||||
Long offlineNum = userCache.getOfflineNum();
|
||||
ChatMemberStatisticResp resp = new ChatMemberStatisticResp();
|
||||
resp.setOnlineNum(onlineNum);
|
||||
resp.setTotalNum(onlineNum + offlineNum);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMsgMark(Long uid, ChatMessageMarkReq request) {
|
||||
//用户对该消息的标记
|
||||
MessageMark messageMark = messageMarkDao.get(uid, request.getMsgId(), request.getMarkType());
|
||||
if (Objects.nonNull(messageMark)) {//有标记过消息修改一下就好
|
||||
MessageMark update = MessageMark.builder()
|
||||
.id(messageMark.getId())
|
||||
.status(transformAct(request.getActType()))
|
||||
.build();
|
||||
messageMarkDao.updateById(update);
|
||||
}
|
||||
//没标记过消息,插入一条新消息
|
||||
MessageMark insert = MessageMark.builder()
|
||||
.uid(uid)
|
||||
.msgId(request.getMsgId())
|
||||
.type(request.getMarkType())
|
||||
.status(transformAct(request.getActType()))
|
||||
.build();
|
||||
messageMarkDao.save(insert);
|
||||
//发布消息标记事件
|
||||
applicationEventPublisher.publishEvent(new MessageMarkEvent(this,request));
|
||||
}
|
||||
|
||||
private Integer transformAct(Integer actType) {
|
||||
if (actType == 1) {
|
||||
return YesOrNoEnum.NO.getStatus();
|
||||
} else if (actType == 2) {
|
||||
return YesOrNoEnum.YES.getStatus();
|
||||
}
|
||||
throw new BusinessException("动作类型 1确认 2取消");
|
||||
}
|
||||
|
||||
public List<ChatMessageResp> getMsgRespBatch(List<Message> messages, Long receiveUid) {
|
||||
if (CollectionUtil.isEmpty(messages)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Map<Long, Message> replyMap = new HashMap<>();
|
||||
Map<Long, User> userMap = new HashMap<>();
|
||||
//批量查出回复的消息
|
||||
List<Long> replyIds = messages.stream().map(Message::getReplyMsgId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(replyIds)) {
|
||||
replyMap = messageDao.listByIds(replyIds).stream().collect(Collectors.toMap(Message::getId, Function.identity()));
|
||||
}
|
||||
//批量查询消息关联用户
|
||||
Set<Long> uidSet = Stream.concat(replyMap.values().stream().map(Message::getFromUid), messages.stream().map(Message::getFromUid)).collect(Collectors.toSet());
|
||||
userMap = userCache.getUserInfoBatch(uidSet);
|
||||
//查询消息标志
|
||||
List<MessageMark> msgMark = messageMarkDao.getValidMarkByMsgIdBatch(messages.stream().map(Message::getId).collect(Collectors.toList()));
|
||||
return MessageAdapter.buildMsgResp(messages, replyMap, userMap,msgMark,receiveUid);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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/**");
|
||||
}
|
||||
}
|
||||
@ -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) // 是否开启 swagger:true -> 开启,false -> 关闭
|
||||
// ;
|
||||
// }
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user