From 641449be7ce0b4dec936583e4f6c5ca86435d9a6 Mon Sep 17 00:00:00 2001 From: ivmiku <124345843+ivmiku@users.noreply.github.com> Date: Sat, 24 Aug 2024 18:19:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=81=E4=BF=A1=E5=92=8C=E5=85=B3?= =?UTF-8?q?=E6=B3=A8=E3=80=81=E7=B2=89=E4=B8=9D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- user-8072/pom.xml | 8 + .../tutorial/config/MybatisPlusConfig.java | 24 ++ .../tutorial/config/RabbitMqConfig.java | 27 ++ .../tutorial/config/SaTokenConfigure.java | 15 ++ .../tutorial/config/WebSocketConfig.java | 34 +++ .../controller/MessageController.java | 39 +++ .../controller/RelationController.java | 42 ++++ .../tutorial/controller/WebSocketServer.java | 142 +++++++++++ .../com/ivmiku/tutorial/entity/ChatId.java | 18 ++ .../com/ivmiku/tutorial/entity/Friend.java | 18 ++ .../ivmiku/tutorial/entity/FriendQuery.java | 12 + .../ivmiku/tutorial/entity/HistoryQuery.java | 16 ++ .../ivmiku/tutorial/entity/IgnoreUser.java | 21 ++ .../com/ivmiku/tutorial/entity/Message.java | 25 ++ .../com/ivmiku/tutorial/entity/SubUser.java | 18 ++ .../com/ivmiku/tutorial/entity/Subscribe.java | 13 + .../tutorial/mapper/BlackListMapper.java | 8 + .../ivmiku/tutorial/mapper/ChatIdMapper.java | 8 + .../ivmiku/tutorial/mapper/FriendMapper.java | 8 + .../ivmiku/tutorial/mapper/MessageMapper.java | 8 + .../tutorial/mapper/SubscribeMapper.java | 8 + .../tutorial/service/MessageService.java | 236 ++++++++++++++++++ .../tutorial/service/RelationService.java | 120 +++++++++ .../ivmiku/tutorial/service/UserService.java | 16 ++ .../com/ivmiku/tutorial/utils/DateUtil.java | 43 ++++ .../ivmiku/tutorial/utils/MessageUtil.java | 36 +++ .../com/ivmiku/tutorial/utils/RedisUtil.java | 103 +++++++- 27 files changed, 1062 insertions(+), 4 deletions(-) create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/config/MybatisPlusConfig.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/config/RabbitMqConfig.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/config/SaTokenConfigure.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/config/WebSocketConfig.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/controller/MessageController.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/controller/RelationController.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/controller/WebSocketServer.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/ChatId.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/Friend.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/FriendQuery.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/HistoryQuery.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/IgnoreUser.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/Message.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/SubUser.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/entity/Subscribe.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/mapper/BlackListMapper.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/mapper/ChatIdMapper.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/mapper/FriendMapper.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/mapper/MessageMapper.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/mapper/SubscribeMapper.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/service/MessageService.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/service/RelationService.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/service/UserService.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/utils/DateUtil.java create mode 100644 user-8072/src/main/java/com/ivmiku/tutorial/utils/MessageUtil.java diff --git a/user-8072/pom.xml b/user-8072/pom.xml index 7b5121b..5ca0ed8 100644 --- a/user-8072/pom.xml +++ b/user-8072/pom.xml @@ -117,6 +117,14 @@ commons 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-amqp + diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/config/MybatisPlusConfig.java b/user-8072/src/main/java/com/ivmiku/tutorial/config/MybatisPlusConfig.java new file mode 100644 index 0000000..71ed480 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/config/MybatisPlusConfig.java @@ -0,0 +1,24 @@ +package com.ivmiku.tutorial.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan("com.ivmiku.tutorial.mapper") +public class MybatisPlusConfig { + + /** + * 添加分页插件 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + //interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType + return interceptor; + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/config/RabbitMqConfig.java b/user-8072/src/main/java/com/ivmiku/tutorial/config/RabbitMqConfig.java new file mode 100644 index 0000000..d9ee381 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/config/RabbitMqConfig.java @@ -0,0 +1,27 @@ +package com.ivmiku.tutorial.config; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitMqConfig { + @Bean + public Queue queue1() { + return new Queue("queue1",true); + } + + @Bean + public FanoutExchange exchange1() { + return new FanoutExchange("exchange1",true, false); + } + + @Bean + public Binding binding1() { + return BindingBuilder.bind(queue1()).to(exchange1()); + } +} + diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/config/SaTokenConfigure.java b/user-8072/src/main/java/com/ivmiku/tutorial/config/SaTokenConfigure.java new file mode 100644 index 0000000..85ca18f --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/config/SaTokenConfigure.java @@ -0,0 +1,15 @@ +package com.ivmiku.tutorial.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册 Sa-Token 拦截器,打开注解式鉴权功能 + registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/config/WebSocketConfig.java b/user-8072/src/main/java/com/ivmiku/tutorial/config/WebSocketConfig.java new file mode 100644 index 0000000..b36ddc7 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/config/WebSocketConfig.java @@ -0,0 +1,34 @@ +package com.ivmiku.tutorial.config; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; +import org.springframework.web.util.WebAppRootListener; + +@Configuration +public class WebSocketConfig implements ServletContextInitializer { + @Bean + public ServerEndpointExporter serverEndpointExporter (){ + return new ServerEndpointExporter(); + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + servletContext.addListener(WebAppRootListener.class); + servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","102400000"); + } + + @Bean + public ServletServerContainerFactoryBean createWebSocketContainer() { + ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); + // 在此处设置bufferSize + container.setMaxTextMessageBufferSize(50*1024*1024); + container.setMaxBinaryMessageBufferSize(50*1024*1024); + container.setMaxSessionIdleTimeout(15 * 60000L); + return container; + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/controller/MessageController.java b/user-8072/src/main/java/com/ivmiku/tutorial/controller/MessageController.java new file mode 100644 index 0000000..326b872 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/controller/MessageController.java @@ -0,0 +1,39 @@ +package com.ivmiku.tutorial.controller; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import com.alibaba.fastjson2.JSON; +import com.ivmiku.tutorial.entity.HistoryQuery; +import com.ivmiku.tutorial.response.Result; +import com.ivmiku.tutorial.service.MessageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.text.ParseException; + +@SaCheckLogin +@RestController +@RequestMapping("/api/message") +public class MessageController { + @Autowired + private MessageService messageService; + + @PostMapping("/history") + public Object getChatHistory(@RequestBody HistoryQuery input) throws ParseException { + Result result = Result.ok(); + if (input.getStartDate() != null && input.getEndDate() != null) { + result.setData(messageService.getChatHistoryByDate(input.getUser1Id(), input.getUser2Id(), input.getStartDate(), input.getEndDate(), input.getPage(), input.getSize())); + } else { + if (input.getPage() <= 0 || input.getSize() <=0) { + return Result.error("请输入合法分页参数"); + } else { + result.setData(messageService.getChatHistory(input.getUser1Id(), input.getUser2Id(), input.getPage(), input.getSize())); + } + } + return JSON.toJSON(result); + } + + @GetMapping("/list") + public Object getChatList(@RequestParam String user_id, @RequestParam int page, @RequestParam int size) { + return Result.ok(messageService.getChatList(user_id, page, size)); + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/controller/RelationController.java b/user-8072/src/main/java/com/ivmiku/tutorial/controller/RelationController.java new file mode 100644 index 0000000..d7dcc88 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/controller/RelationController.java @@ -0,0 +1,42 @@ +package com.ivmiku.tutorial.controller; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.stp.StpUtil; +import com.ivmiku.tutorial.entity.IgnoreUser; +import com.ivmiku.tutorial.response.Result; +import com.ivmiku.tutorial.service.RelationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@SaCheckLogin +@RestController +@RequestMapping("/api/relation") +public class RelationController { + @Autowired + private RelationService relationService; + + @PostMapping("/ignore") + public Object ignoreUser(@RequestBody IgnoreUser input) { + relationService.IgnoreUser(input.getUserId(), input.getToIgnore()); + return Result.ok(); + } + + @GetMapping("subscribe") + public Object subscribe(@RequestParam String subId) { + String userId = (String) StpUtil.getLoginId(); + relationService.subscribe(userId, subId); + return Result.ok(); + } + + @GetMapping("fan") + public Object getFanList(@RequestParam int page, @RequestParam int size) { + String userId = (String) StpUtil.getLoginId(); + return Result.ok(relationService.getFanList(userId, page, size)); + } + + @GetMapping("follow") + public Object getFollowList(@RequestParam int page, @RequestParam int size) { + String userId = (String) StpUtil.getLoginId(); + return Result.ok(relationService.getSubList(userId, page, size)); + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/controller/WebSocketServer.java b/user-8072/src/main/java/com/ivmiku/tutorial/controller/WebSocketServer.java new file mode 100644 index 0000000..993c05f --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/controller/WebSocketServer.java @@ -0,0 +1,142 @@ +package com.ivmiku.tutorial.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.alibaba.fastjson2.JSON; +import com.ivmiku.tutorial.entity.Message; +import com.ivmiku.tutorial.service.MessageService; +import com.ivmiku.tutorial.service.RelationService; +import com.ivmiku.tutorial.utils.DateUtil; +import com.ivmiku.tutorial.utils.MessageUtil; +import jakarta.websocket.*; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import org.springframework.amqp.core.ExchangeTypes; +import org.springframework.amqp.rabbit.annotation.Exchange; +import org.springframework.amqp.rabbit.annotation.Queue; +import org.springframework.amqp.rabbit.annotation.QueueBinding; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Controller; + +import java.io.IOException; +import java.text.ParseException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import static jakarta.websocket.CloseReason.CloseCodes.CLOSED_ABNORMALLY; + +@Controller +@ServerEndpoint(value = "/chat/{satoken}") +public class WebSocketServer implements ApplicationContextAware { + public static Map sessionMap = new ConcurrentHashMap<>(); + + public static Map controlMap = new HashMap<>(); + + private static ApplicationContext applicationContext; + + @Autowired + private MessageService messageService; + + private RabbitTemplate rabbitTemplate; + + private RelationService relationService; + + @OnMessage + public void onMessage(String message, Session session) throws IOException { + Message msg = JSON.parseObject(message, Message.class); + msg.setDate(DateUtil.getCurrentTime()); + if (MessageUtil.checkMessage(msg.getMessage())) { + session.getBasicRemote().sendText("发送的信息含有敏感词,请进行调整"); + if (!controlMap.containsKey(msg.getFromId())){ + controlMap.put(msg.getFromId(), 0); + } + if (controlMap.get(msg.getFromId()) == 4){ + session.getBasicRemote().sendText("由于多次违反社区规则,您已被封禁1小时"); + session.close(new CloseReason(CLOSED_ABNORMALLY, "账号被封禁")); + StpUtil.kickout(msg.getFromId()); + StpUtil.disable(msg.getFromId(), 3600); + controlMap.put(msg.getFromId(), 0); + } + controlMap.put(msg.getFromId(), controlMap.get(msg.getFromId())+1); + } else + if (relationService.ifIgnored(msg.getToId(), msg.getFromId())) { + session.getBasicRemote().sendText("您已被对方屏蔽"); + } else { + rabbitTemplate.convertAndSend("exchange1", "", JSON.toJSONString(msg)); + } + } + + + @OnOpen + public void onOpen(Session session, EndpointConfig endpointConfig, @PathParam("satoken") String satoken) throws IOException { + String userId = (String) StpUtil.getLoginIdByToken(satoken); + if (userId == null) { + session.getBasicRemote().sendText("Invalid Token"); + session.close(); + } + this.messageService = WebSocketServer.applicationContext.getBean(MessageService.class); + this.rabbitTemplate = WebSocketServer.applicationContext.getBean(RabbitTemplate.class); + this.relationService = WebSocketServer.applicationContext.getBean(RelationService.class); + sessionMap.put(userId, session); + List unreadList = messageService.getUnreadMsg(userId); + for(Message msg : unreadList) { + session.getBasicRemote().sendText(JSON.toJSONString(msg)); + } + } + + @OnClose + public void onClose(CloseReason closeReason, Session session){ + sessionMap.remove(session.getId()); + } + + @OnError + public void onError(Throwable throwable) throws IOException { + throwable.printStackTrace(); + } + + public void sendToUser(Message msg) throws IOException, ParseException { + if (sessionMap.containsKey(msg.getToId())){ + sessionMap.get(msg.getToId()).getBasicRemote().sendText(JSON.toJSONString(msg)); + } + else { + messageService.insertUnreadMsg(msg.getToId(), msg); + } + msg.setChatId(messageService.getChatId(msg.getFromId(), msg.getToId())); + messageService.insertToMysql(msg); + messageService.insertToRedis(msg); + } + + public void sendToPublic(Message msg) throws IOException, ParseException { + for (Session session : sessionMap.values()) { + session.getBasicRemote().sendText(JSON.toJSONString(msg)); + } + msg.setChatId(messageService.getChatId(msg.getFromId(), msg.getToId())); + messageService.insertToMysql(msg); + messageService.insertToRedis(msg); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + WebSocketServer.applicationContext = applicationContext; + } + + @RabbitHandler + @RabbitListener(bindings = @QueueBinding( + value = @Queue(), + exchange = @Exchange(value = "exchange1",type = ExchangeTypes.FANOUT) + )) + public void sendMsg(String message) throws IOException, ParseException { + Message msg = JSON.parseObject(message, Message.class); + if (sessionMap.containsKey(msg.getToId())) { + sessionMap.get(msg.getToId()).getBasicRemote().sendText(JSON.toJSONString(msg)); + } + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/ChatId.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/ChatId.java new file mode 100644 index 0000000..7da0f60 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/ChatId.java @@ -0,0 +1,18 @@ +package com.ivmiku.tutorial.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author Aurora + */ +@Data +@TableName("chatid") +public class ChatId { + @TableId(type = IdType.ASSIGN_ID) + private String id; + private String user1Id; + private String user2Id; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/Friend.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/Friend.java new file mode 100644 index 0000000..11cb2e4 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/Friend.java @@ -0,0 +1,18 @@ +package com.ivmiku.tutorial.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Aurora + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("friend") +public class Friend { + private String user1Id; + private String user2Id; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/FriendQuery.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/FriendQuery.java new file mode 100644 index 0000000..b31d876 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/FriendQuery.java @@ -0,0 +1,12 @@ +package com.ivmiku.tutorial.entity; + +import lombok.Data; + +/** + * @author Aurora + */ +@Data +public class FriendQuery { + private String user1Id; + private String user2Id; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/HistoryQuery.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/HistoryQuery.java new file mode 100644 index 0000000..8b6149e --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/HistoryQuery.java @@ -0,0 +1,16 @@ +package com.ivmiku.tutorial.entity; + +import lombok.Data; + +/** + * @author Aurora + */ +@Data +public class HistoryQuery { + private String user1Id; + private String user2Id; + private int page; + private int size; + private String startDate; + private String endDate; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/IgnoreUser.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/IgnoreUser.java new file mode 100644 index 0000000..d3c592f --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/IgnoreUser.java @@ -0,0 +1,21 @@ +package com.ivmiku.tutorial.entity; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Aurora + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("blacklist") +public class IgnoreUser { + @JSONField(ordinal = 1) + private String userId; + @JSONField(ordinal = 2) + private String toIgnore; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/Message.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/Message.java new file mode 100644 index 0000000..e96f45a --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/Message.java @@ -0,0 +1,25 @@ +package com.ivmiku.tutorial.entity; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Aurora + */ +@Data +@TableName("message") +public class Message implements Serializable { + @JSONField(ordinal = 1) + private String chatId; + @JSONField(ordinal = 2) + private String fromId; + @JSONField(ordinal = 3) + private String toId; + @JSONField(ordinal = 4) + private String message; + @JSONField(ordinal = 5) + private String date; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/SubUser.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/SubUser.java new file mode 100644 index 0000000..60bccb5 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/SubUser.java @@ -0,0 +1,18 @@ +package com.ivmiku.tutorial.entity; + +import lombok.Data; + +@Data +public class SubUser { + private String id; + private String username; + private String avatarUrl; + + public static SubUser setUser(User user) { + SubUser subUser = new SubUser(); + subUser.setId(user.getOpenid()); + subUser.setUsername(user.getNickname()); + subUser.setAvatarUrl(user.getAvatarUrl()); + return subUser; + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/Subscribe.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/Subscribe.java new file mode 100644 index 0000000..054070f --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/Subscribe.java @@ -0,0 +1,13 @@ +package com.ivmiku.tutorial.entity; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author Aurora + */ +@TableName("subscribe") +@Data +public class Subscribe { + private Long id; + private Long subId; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/mapper/BlackListMapper.java b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/BlackListMapper.java new file mode 100644 index 0000000..7d99648 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/BlackListMapper.java @@ -0,0 +1,8 @@ +package com.ivmiku.tutorial.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ivmiku.tutorial.entity.IgnoreUser; + +public interface BlackListMapper extends BaseMapper { + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/mapper/ChatIdMapper.java b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/ChatIdMapper.java new file mode 100644 index 0000000..ec9ddaa --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/ChatIdMapper.java @@ -0,0 +1,8 @@ +package com.ivmiku.tutorial.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ivmiku.tutorial.entity.ChatId; + +public interface ChatIdMapper extends BaseMapper { + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/mapper/FriendMapper.java b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/FriendMapper.java new file mode 100644 index 0000000..32ea471 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/FriendMapper.java @@ -0,0 +1,8 @@ +package com.ivmiku.tutorial.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ivmiku.tutorial.entity.Friend; + +public interface FriendMapper extends BaseMapper { + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/mapper/MessageMapper.java b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/MessageMapper.java new file mode 100644 index 0000000..b6d5ee1 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/MessageMapper.java @@ -0,0 +1,8 @@ +package com.ivmiku.tutorial.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ivmiku.tutorial.entity.Message; + +public interface MessageMapper extends BaseMapper { + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/mapper/SubscribeMapper.java b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/SubscribeMapper.java new file mode 100644 index 0000000..53d67c9 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/SubscribeMapper.java @@ -0,0 +1,8 @@ +package com.ivmiku.tutorial.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ivmiku.tutorial.entity.Subscribe; + +public interface SubscribeMapper extends BaseMapper { + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/service/MessageService.java b/user-8072/src/main/java/com/ivmiku/tutorial/service/MessageService.java new file mode 100644 index 0000000..d7e3c88 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/service/MessageService.java @@ -0,0 +1,236 @@ +package com.ivmiku.tutorial.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ivmiku.tutorial.entity.ChatId; +import com.ivmiku.tutorial.entity.Message; +import com.ivmiku.tutorial.mapper.ChatIdMapper; +import com.ivmiku.tutorial.mapper.MessageMapper; +import com.ivmiku.tutorial.utils.RedisUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author Aurora + */ +@Service +public class MessageService { + @Autowired + private MessageMapper messageMapper; + + @Autowired + private ChatIdMapper chatIdMapper; + + @Autowired + private RedisUtil redisUtil; + + /** + * 获取会话id + * @param user1Id 用户1id + * @param user2Id 用户2id + * @return 查询结果 + */ + public String getChatId(String user1Id, String user2Id) { + if (Objects.equals(user1Id, "public") || Objects.equals(user2Id, "public")) { + return "0"; + } + String id1, id2; + if (user1Id.compareTo(user2Id) < 0) { + id1 = user1Id; + id2 = user2Id; + } else { + id1 = user2Id; + id2 = user1Id; + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user1_id", id1); + queryWrapper.eq("user2_id", id2); + ChatId chatId = chatIdMapper.selectOne(queryWrapper); + if (chatId == null) { + chatId = new ChatId(); + chatId.setUser1Id(id1); + chatId.setUser2Id(id2); + chatIdMapper.insert(chatId); + } + return chatIdMapper.selectOne(queryWrapper).getId(); + } + + /** + * 聊天记录写redis + * @param msg 要写入的信息 + * @throws ParseException + */ + @Async + public void insertToRedis(Message msg) throws ParseException { + if (!(msg.getMessage().length() >1000)) { + if (redisUtil.getZsetSize("history:" + msg.getChatId()) >= 50) { + redisUtil.zsetRightPop("history:" + msg.getChatId()); + } + redisUtil.zsetAdd("history:" + msg.getChatId(), msg); + redisUtil.setExpireTime("history:" + msg.getChatId()); + } + } + + /** + * 聊天记录写入mysql + * @param msg 要写入的消息 + */ + @Async + public void insertToMysql(Message msg) { + if (!(msg.getMessage().length() >1000)) { + messageMapper.insert(msg); + } + } + + /** + * 未读消息写入redis + * @param userId 用户id + * @param msg 未读消息 + */ + @Async + public void insertUnreadMsg(String userId, Message msg) { + redisUtil.listAdd("unread:" + userId, msg); + } + + /** + * 获取未读消息列表 + * @param userId 用户id + * @return 查询结果 + */ + public List getUnreadMsg(String userId) { + List result = redisUtil.listGet("unread:" + userId, 0, -1); + redisUtil.listClear(userId); + return result; + } + + /** + * 从数据库获取聊天记录 + * @param chatId 会话id + * @param current 分页参数 + * @param size 分页参数 + * @return 返回的查询结果 + */ + public List getChatHistoryFromDB(String chatId, int current, int size) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + Page page = new Page<>(current, size); + queryWrapper.eq("chat_id", chatId); + queryWrapper.orderByDesc("date"); + return messageMapper.selectPage(page, queryWrapper).getRecords(); + } + + /** + * 从数据库获取聊天记录,查询一定范围内 + * @param chatId 会话id + * @param current 分页参数 + * @param size 分页参数 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 返回的查询结果 + */ + public List getChatHistoryFromDBByDate(String chatId, int current, int size, String startDate, String endDate) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.between("date", startDate, endDate); + Page page = new Page<>(current, size); + queryWrapper.eq("chat_id", chatId); + queryWrapper.orderByDesc("date"); + return messageMapper.selectPage(page, queryWrapper).getRecords(); + } + + /** + * redis获取聊天记录 + * @param chatId 会话id + * @param s 开始 + * @param e 结束 + * @return 查询结果 + */ + public List getChatHistoryFromRedis(String chatId, int s, int e) { + return redisUtil.zsetGet("history:" + chatId, s, e); + } + + /** + * 查询聊天记录 + * @param user1Id 用户1id + * @param user2Id 用户2id + * @param page 分页参数 + * @param size 分页参数 + * @return 查询结果 + * @throws ParseException + */ + public List getChatHistory(String user1Id, String user2Id, int page, int size) throws ParseException { + int start = page * size - size; + int end = page * size - 1; + String chatId = getChatId(user1Id, user2Id); + loadCache(chatId); + List result = new ArrayList<>(redisUtil.zsetGet("history:" + chatId, start, end)); + if ((end -start + 1) == result.size()) { + return result; + } + int redisSize = result.size(); + List dbList = getChatHistoryFromDB(chatId, ((end - result.size()) / size) + 1, size); + result.addAll(dbList.subList(redisSize, dbList.size())); + redisUtil.refreshExpire("history:" + chatId); + return result; + } + + /** + * 在一定时间范围内查询聊天记录 + * @param user1Id 用户1id + * @param user2Id 用户2id + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param page 分页参数 + * @param size 分页参数 + * @return 查询结果 + * @throws ParseException + */ + public List getChatHistoryByDate(String user1Id, String user2Id, String startDate, String endDate, int page, int size) throws ParseException { + int start = page * size - size; + int end = page * size - 1; + String chatId = getChatId(user1Id, user2Id); + loadCache(chatId); + List result = new ArrayList<>(redisUtil.zsetGetByDate("history:" + chatId, startDate, endDate, start, size)); + redisUtil.refreshExpire("history:" + chatId); + if (result.size() == (end - start + 1)) { + return result; + } + int redisSize = result.size(); + List dbList = getChatHistoryFromDBByDate(chatId, ((end - result.size()) / size) + 1, size, startDate, endDate).subList(result.size(), size); + result.addAll(dbList.subList(redisSize, dbList.size())); + return result; + } + + /** + * 获取会话列表 + * @param userId 用户id + * @param current 分页参数 + * @param size 分页参数 + * @return 查询结果 + */ + public List getChatList(String userId, int current, int size) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user1_id", userId).or().eq("user2_id", userId); + queryWrapper.orderByDesc("id"); + Page page = new Page<>(current, size); + return chatIdMapper.selectPage(page, queryWrapper).getRecords(); + } + + /** + * 加载聊天记录到redis + * @param chatId 会话id + * @throws ParseException + */ + public void loadCache(String chatId) throws ParseException { + if (!redisUtil.ifExist("history:" + chatId)) { + List list = getChatHistoryFromDB(chatId, 1, 20); + for (Message message : list) { + insertToRedis(message); + } + } + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/service/RelationService.java b/user-8072/src/main/java/com/ivmiku/tutorial/service/RelationService.java new file mode 100644 index 0000000..8dc4bd3 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/service/RelationService.java @@ -0,0 +1,120 @@ +package com.ivmiku.tutorial.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.ivmiku.tutorial.entity.*; +import com.ivmiku.tutorial.mapper.BlackListMapper; +import com.ivmiku.tutorial.mapper.FriendMapper; +import com.ivmiku.tutorial.mapper.SubscribeMapper; +import com.ivmiku.tutorial.utils.RedisUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Aurora + */ +@Service +public class RelationService { + @Autowired + private BlackListMapper blackListMapper; + + @Autowired + private FriendMapper friendMapper; + + @Autowired + private RedisUtil redisUtil; + + @Autowired + private SubscribeMapper subscribeMapper; + + @Autowired + private UserService userService; + + /** + * 屏蔽用户 + * @param userId 用户id + * @param toIgnore 要屏蔽的用户id + */ + public void IgnoreUser(String userId, String toIgnore) { + blackListMapper.insert(new IgnoreUser(userId, toIgnore)); + if (redisUtil.ifExist("blacklist:" + userId)) { + redisUtil.listAdd("blacklist:" + userId, toIgnore); + } + } + + /** + * 查询用户是否屏蔽了该用户 + * @param userId 用户id + * @param ignoreId 要查询的id + * @return 查询结果 + */ + public boolean ifIgnored(String userId, String ignoreId) { + loadCache(userId); + List blackList = redisUtil.getStringList("blacklist:" + userId, 0, -1); + redisUtil.refreshExpire("blacklist:" + userId); + return blackList.contains(ignoreId); + } + + /** + * 加载缓存 + * @param userId 用户id + */ + public void loadCache(String userId) { + if (!redisUtil.ifExist("blacklist:" + userId)) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", userId); + List list = blackListMapper.selectList(queryWrapper); + List result = new ArrayList<>(); + if (list != null) { + for (IgnoreUser object : list) { + result.add(object.getToIgnore()); + } + } + for (String toIgnore : result) { + redisUtil.listAdd("blacklist:" + userId, toIgnore); + } + redisUtil.setExpireTime("blacklist:" + userId); + } + } + + public void subscribe(String id, String toId) { + Subscribe subscribe = new Subscribe(); + subscribe.setId(Long.valueOf(id)); + subscribe.setSubId(Long.valueOf(toId)); + subscribeMapper.insert(subscribe); + } + + public List getFanList(String userId, int page, int size) { + List list = new ArrayList<>(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + Page pager = new Page<>(page, size); + queryWrapper.eq("sub_id", userId); + List array = subscribeMapper.selectPage(pager, queryWrapper).getRecords(); + for (Subscribe subscribe : array) { + String queryId = String.valueOf(subscribe.getId()); + User user = userService.selectUserById(queryId); + SubUser subUser = SubUser.setUser(user); + list.add(subUser); + } + return list; + } + + public List getSubList(String userId, int page, int size) { + List list = new ArrayList<>(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + Page pager = new Page<>(page, size); + queryWrapper.eq("id", userId); + List array = subscribeMapper.selectPage(pager, queryWrapper).getRecords(); + for (Subscribe subscribe : array) { + String queryId = String.valueOf(subscribe.getSubId()); + User user = userService.selectUserById(queryId); + SubUser subUser = SubUser.setUser(user); + list.add(subUser); + } + return list; + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/service/UserService.java b/user-8072/src/main/java/com/ivmiku/tutorial/service/UserService.java new file mode 100644 index 0000000..86c6c77 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/service/UserService.java @@ -0,0 +1,16 @@ +package com.ivmiku.tutorial.service; + +import com.ivmiku.tutorial.entity.User; +import com.ivmiku.tutorial.mapper.UserMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +@Service +public class UserService { + @Resource + private UserMapper userMapper; + + public User selectUserById(String id) { + return userMapper.selectById(id); + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/utils/DateUtil.java b/user-8072/src/main/java/com/ivmiku/tutorial/utils/DateUtil.java new file mode 100644 index 0000000..8430632 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/utils/DateUtil.java @@ -0,0 +1,43 @@ +package com.ivmiku.tutorial.utils; + +import jakarta.annotation.PostConstruct; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * @author Aurora + */ +public class DateUtil { + /** + * 获取当前时间 + * @return 当前时间字符串 + */ + public static String getCurrentTime() { + Date date = new Date(); + SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return ft.format(date); + } + + /** + * 转换为时间戳 + * @param time 时间字符串 + * @return 时间戳 + * @throws ParseException + */ + public static long toTimeSig(String time) throws ParseException { + SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date date = ft.parse(time); + return date.getTime(); + } + + /** + * 设置当前时区GMT+8 + */ + @PostConstruct + public void setTimeZone() { + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/utils/MessageUtil.java b/user-8072/src/main/java/com/ivmiku/tutorial/utils/MessageUtil.java new file mode 100644 index 0000000..da73040 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/utils/MessageUtil.java @@ -0,0 +1,36 @@ +package com.ivmiku.tutorial.utils; + +import com.ivmiku.tutorial.service.RelationService; +import org.springframework.stereotype.Component; + +/** + * 敏感词检测 + * @author Aurora + */ +@Component +public class MessageUtil { + private static final String[] SENSITIVE = {"你妈", "你妈逼的"}; + + private static RelationService relationService = null; + + public MessageUtil(RelationService relationService) { + MessageUtil.relationService = relationService; + } + + /** + * 查看发送的信息是否含有敏感词 + * @param message 要发送的信息 + * @return 检查结果 + */ + public static boolean checkMessage(String message) { + if (message != null) { + for(String keyword : SENSITIVE) { + if (message.contains(keyword)){ + return true; + } + } + } + return false; + } + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/utils/RedisUtil.java b/user-8072/src/main/java/com/ivmiku/tutorial/utils/RedisUtil.java index 8ff2877..4f03f73 100644 --- a/user-8072/src/main/java/com/ivmiku/tutorial/utils/RedisUtil.java +++ b/user-8072/src/main/java/com/ivmiku/tutorial/utils/RedisUtil.java @@ -1,9 +1,18 @@ package com.ivmiku.tutorial.utils; +import com.alibaba.fastjson2.JSON; +import com.ivmiku.tutorial.entity.Message; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; + /** * @author Aurora */ @@ -12,11 +21,97 @@ public class RedisUtil { @Autowired private RedisTemplate redisTemplate; - public void insertKey(String userId, String sessionKey) { - redisTemplate.opsForValue().set("sessionkey:" + userId, sessionKey); + public void listAdd(String key, Object value) { + redisTemplate.opsForList().leftPush(key, value); } - public String getKey(String userId) { - return (String) redisTemplate.opsForValue().get("sessionkey:" + userId); + public List listGet(String key, int s, int e) { + List list = redisTemplate.opsForList().range(key, s, e); + List result = new ArrayList<>(); + if (list != null) { + for (Object json : list) { + result.add(JSON.parseObject(JSON.toJSONString(json), Message.class)); + } + } + return result; + } + + public void listClear(String key) { + redisTemplate.opsForList().trim(key, 1, 0); + } + + public Long getListSize(String key) { + return redisTemplate.opsForList().size(key); + } + + public Set getKey() { + return redisTemplate.keys("history:*"); + } + + public Object rightPop(String key) { + return redisTemplate.opsForList().rightPop(key); + } + + public void zsetAdd(String key, Message value) throws ParseException { + redisTemplate.opsForZSet().add(key, value, DateUtil.toTimeSig(value.getDate())); + } + + public List zsetGet(String key, int s, int e) { + Set list = redisTemplate.opsForZSet().reverseRange(key, s, e); + List result = new ArrayList<>(); + if (list != null) { + for (Object json : list) { + result.add(JSON.parseObject(JSON.toJSONString(json), Message.class)); + } + } + return result; + } + + public Long getzsetSize(String key) { + return redisTemplate.opsForZSet().size(key); + } + + public List zsetGetByDate(String key, String startDate, String endDate, int offset, int count) throws ParseException { + Set list = redisTemplate.opsForZSet().reverseRangeByScore(key, DateUtil.toTimeSig(startDate), DateUtil.toTimeSig(endDate), offset, count); + List result = new ArrayList<>(); + if (list != null) { + for (Object json : list) { + result.add(JSON.parseObject(JSON.toJSONString(json), Message.class)); + } + } + return result; + } + + public Message zsetRightPop(String key) { + return JSON.parseObject(JSON.toJSONString(Objects.requireNonNull(redisTemplate.opsForZSet().popMin(key)).getValue()), Message.class); + } + + public Long getZsetSize(String key) { + return redisTemplate.opsForZSet().size(key); + } + + public void setExpireTime(String key) { + if (redisTemplate.opsForValue().getOperations().getExpire(key) > 0) { + redisTemplate.expire(key, 3, TimeUnit.DAYS); + } + } + + public void refreshExpire(String key) { + redisTemplate.persist(key); + redisTemplate.expire(key, 3, TimeUnit.DAYS); + } + + public boolean ifExist(String key) { + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } + + public List getStringList(String key, int s, int e) { + List list = redisTemplate.opsForList().range(key, s, e); + List result = new ArrayList<>(); + assert list != null; + for (Object object : list) { + result.add((String) object); + } + return result; } }