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